]> Pileus Git - ~andy/gtk/blob - gtk/gtkappchooserbutton.c
Merge branch 'bgo593793-filechooser-recent-folders-master'
[~andy/gtk] / gtk / gtkappchooserbutton.c
1 /*
2  * gtkappchooserbutton.h: an app-chooser combobox
3  *
4  * Copyright (C) 2010 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
18  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Cosimo Cecchi <ccecchi@redhat.com>
22  */
23
24 /**
25  * SECTION:gtkappchooserbutton
26  * @Title: GtkAppChooserButton
27  * @Short_description: A button to launch an application chooser dialog
28  *
29  * The #GtkAppChooserButton is a widget that lets the user select
30  * an application. It implements the #GtkAppChooser interface.
31  *
32  * Initially, a #GtkAppChooserButton selects the first application
33  * in its list, which will either be the most-recently used application
34  * or, if #GtkAppChooserButton::show-default-item is %TRUE, the
35  * default application.
36  *
37  * The list of applications shown in a #GtkAppChooserButton includes
38  * the recommended applications for the given content type. When
39  * #GtkAppChooserButton::show-default-item is set, the default application
40  * is also included. To let the user chooser other applications,
41  * you can set the #GtkAppChooserButton::show-dialog-item property,
42  * which allows to open a full #GtkAppChooserDialog.
43  *
44  * It is possible to add custom items to the list, using
45  * gtk_app_chooser_button_append_custom_item(). These items cause
46  * the #GtkAppChooserButton::custom-item-activated signal to be
47  * emitted when they are selected.
48  *
49  * To track changes in the selected application, use the
50  * #GtkComboBox::changed signal.
51  */
52 #include "config.h"
53
54 #include "gtkappchooserbutton.h"
55
56 #include "gtkappchooser.h"
57 #include "gtkappchooserdialog.h"
58 #include "gtkappchooserprivate.h"
59 #include "gtkcelllayout.h"
60 #include "gtkcellrendererpixbuf.h"
61 #include "gtkcellrenderertext.h"
62 #include "gtkcombobox.h"
63 #include "gtkdialog.h"
64 #include "gtkintl.h"
65 #include "gtkmarshalers.h"
66
67 enum {
68   PROP_CONTENT_TYPE = 1,
69   PROP_SHOW_DIALOG_ITEM,
70   PROP_SHOW_DEFAULT_ITEM,
71   PROP_HEADING
72 };
73
74 enum {
75   SIGNAL_CUSTOM_ITEM_ACTIVATED,
76   NUM_SIGNALS
77 };
78
79 enum {
80   COLUMN_APP_INFO,
81   COLUMN_NAME,
82   COLUMN_LABEL,
83   COLUMN_ICON,
84   COLUMN_CUSTOM,
85   COLUMN_SEPARATOR,
86   NUM_COLUMNS,
87 };
88
89 #define CUSTOM_ITEM_OTHER_APP "gtk-internal-item-other-app"
90
91 static void app_chooser_iface_init (GtkAppChooserIface *iface);
92
93 static void real_insert_custom_item (GtkAppChooserButton *self,
94                                      const gchar *name,
95                                      const gchar *label,
96                                      GIcon *icon,
97                                      gboolean custom,
98                                      GtkTreeIter *iter);
99
100 static void real_insert_separator (GtkAppChooserButton *self,
101                                    gboolean custom,
102                                    GtkTreeIter *iter);
103
104 static guint signals[NUM_SIGNALS] = { 0, };
105
106 G_DEFINE_TYPE_WITH_CODE (GtkAppChooserButton, gtk_app_chooser_button, GTK_TYPE_COMBO_BOX,
107                          G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
108                                                 app_chooser_iface_init));
109
110 struct _GtkAppChooserButtonPrivate {
111   GtkListStore *store;
112
113   gchar *content_type;
114   gchar *heading;
115   gint last_active;
116   gboolean show_dialog_item;
117   gboolean show_default_item;
118
119   GHashTable *custom_item_names;
120 };
121
122 static gboolean
123 row_separator_func (GtkTreeModel *model,
124                     GtkTreeIter  *iter,
125                     gpointer      user_data)
126 {
127   gboolean separator;
128
129   gtk_tree_model_get (model, iter,
130                       COLUMN_SEPARATOR, &separator,
131                       -1);
132
133   return separator;
134 }
135
136 static void
137 get_first_iter (GtkListStore *store,
138                 GtkTreeIter  *iter)
139 {
140   GtkTreeIter iter2;
141
142   if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), iter))
143     {
144       /* the model is empty, append */
145       gtk_list_store_append (store, iter);
146     }
147   else
148     {
149       gtk_list_store_insert_before (store, &iter2, iter);
150       *iter = iter2;
151     }
152 }
153
154 typedef struct {
155   GtkAppChooserButton *self;
156   GAppInfo *info;
157   gint active_index;
158 } SelectAppData;
159
160 static void
161 select_app_data_free (SelectAppData *data)
162 {
163   g_clear_object (&data->self);
164   g_clear_object (&data->info);
165
166   g_slice_free (SelectAppData, data);
167 }
168
169 static gboolean
170 select_application_func_cb (GtkTreeModel *model,
171                             GtkTreePath *path,
172                             GtkTreeIter *iter,
173                             gpointer user_data)
174 {
175   SelectAppData *data = user_data;
176   GAppInfo *app_to_match = data->info, *app = NULL;
177   gboolean custom;
178   gboolean result;
179
180   gtk_tree_model_get (model, iter,
181                       COLUMN_APP_INFO, &app,
182                       COLUMN_CUSTOM, &custom,
183                       -1);
184
185   /* custom items are always after GAppInfos, so iterating further here
186    * is just useless.
187    */
188   if (custom)
189     result = TRUE;
190   else if (g_app_info_equal (app, app_to_match))
191     {
192       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter);
193       result = TRUE;
194     }
195   else
196     result = FALSE;
197
198   g_object_unref (app);
199
200   return result;
201 }
202
203 static void
204 gtk_app_chooser_button_select_application (GtkAppChooserButton *self,
205                                            GAppInfo *info)
206 {
207   SelectAppData *data;
208
209   data = g_slice_new0 (SelectAppData);
210   data->self = g_object_ref (self);
211   data->info = g_object_ref (info);
212
213   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->store),
214                           select_application_func_cb, data);
215
216   select_app_data_free (data);
217 }
218
219 static void
220 other_application_dialog_response_cb (GtkDialog *dialog,
221                                       gint response_id,
222                                       gpointer user_data)
223 {
224   GtkAppChooserButton *self = user_data;
225   GAppInfo *info;
226
227   if (response_id != GTK_RESPONSE_OK)
228     {
229       /* reset the active item, otherwise we are stuck on
230        * 'Other application...'
231        */
232       gtk_combo_box_set_active (GTK_COMBO_BOX (self), self->priv->last_active);
233       gtk_widget_destroy (GTK_WIDGET (dialog));
234       return;
235     }
236
237   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
238
239   gtk_widget_destroy (GTK_WIDGET (dialog));
240
241   /* refresh the combobox to get the new application */
242   gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
243   gtk_app_chooser_button_select_application (self, info);
244
245   g_object_unref (info);
246 }
247
248 static void
249 other_application_item_activated_cb (GtkAppChooserButton *self)
250 {
251   GtkWidget *dialog, *widget;
252   GtkWindow *toplevel;
253
254   toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
255   dialog = gtk_app_chooser_dialog_new_for_content_type (toplevel, GTK_DIALOG_DESTROY_WITH_PARENT,
256                                                         self->priv->content_type);
257
258   gtk_window_set_modal (GTK_WINDOW (dialog), gtk_window_get_modal (toplevel));
259   gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog),
260                                       self->priv->heading);
261
262   widget = gtk_app_chooser_dialog_get_widget (GTK_APP_CHOOSER_DIALOG (dialog));
263   g_object_set (widget,
264                 "show-fallback", TRUE,
265                 "show-other", TRUE,
266                 NULL);
267   gtk_widget_show (dialog);
268
269   g_signal_connect (dialog, "response",
270                     G_CALLBACK (other_application_dialog_response_cb), self);
271 }
272
273 static void
274 gtk_app_chooser_button_ensure_dialog_item (GtkAppChooserButton *self,
275                                            GtkTreeIter *prev_iter)
276 {
277   GtkTreeIter iter, iter2;
278
279   if (!self->priv->show_dialog_item || !self->priv->content_type)
280     return;
281
282   if (prev_iter == NULL)
283     gtk_list_store_append (self->priv->store, &iter);
284   else
285     gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
286
287   real_insert_separator (self, FALSE, &iter);
288   iter2 = iter;
289
290   gtk_list_store_insert_after (self->priv->store, &iter, &iter2);
291   real_insert_custom_item (self, CUSTOM_ITEM_OTHER_APP,
292                            _("Other application..."), NULL,
293                            FALSE, &iter);
294 }
295
296 static void
297 insert_one_application (GtkAppChooserButton *self,
298                         GAppInfo            *app,
299                         GtkTreeIter         *iter)
300 {
301   GIcon *icon;
302
303   icon = g_app_info_get_icon (app);
304
305   if (icon == NULL)
306     icon = g_themed_icon_new ("application-x-executable");
307   else
308     g_object_ref (icon);
309
310   gtk_list_store_set (self->priv->store, iter,
311                       COLUMN_APP_INFO, app,
312                       COLUMN_LABEL, g_app_info_get_name (app),
313                       COLUMN_ICON, icon,
314                       COLUMN_CUSTOM, FALSE,
315                       -1);
316
317   g_object_unref (icon);
318 }
319
320 static void
321 gtk_app_chooser_button_populate (GtkAppChooserButton *self)
322 {
323   GList *recommended_apps = NULL, *l;
324   GAppInfo *app, *default_app = NULL;
325   GtkTreeIter iter, iter2;
326   gboolean cycled_recommended;
327
328 #ifndef G_OS_WIN32
329   if (self->priv->content_type)
330     recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
331 #endif
332   cycled_recommended = FALSE;
333
334   if (self->priv->show_default_item)
335     {
336       default_app = g_app_info_get_default_for_type (self->priv->content_type, FALSE);
337
338       if (default_app != NULL)
339         {
340           get_first_iter (self->priv->store, &iter);
341           cycled_recommended = TRUE;
342
343           insert_one_application (self, default_app, &iter);
344
345           g_object_unref (default_app);
346         }
347     }
348
349   for (l = recommended_apps; l != NULL; l = l->next)
350     {
351       app = l->data;
352
353       if (default_app != NULL && g_app_info_equal (app, default_app))
354         continue;
355
356       if (cycled_recommended)
357         {
358           gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
359           iter = iter2;
360         }
361       else
362         {
363           get_first_iter (self->priv->store, &iter);
364           cycled_recommended = TRUE;
365         }
366
367       insert_one_application (self, app, &iter);
368     }
369
370   if (recommended_apps != NULL)
371     g_list_free_full (recommended_apps, g_object_unref);
372
373   if (!cycled_recommended)
374     gtk_app_chooser_button_ensure_dialog_item (self, NULL);
375   else
376     gtk_app_chooser_button_ensure_dialog_item (self, &iter);
377
378   gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0);
379 }
380
381 static void
382 gtk_app_chooser_button_build_ui (GtkAppChooserButton *self)
383 {
384   GtkCellRenderer *cell;
385   GtkCellArea *area;
386
387   gtk_combo_box_set_model (GTK_COMBO_BOX (self),
388                            GTK_TREE_MODEL (self->priv->store));
389
390   area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (self));
391
392   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (self),
393                                         row_separator_func, NULL, NULL);
394
395   cell = gtk_cell_renderer_pixbuf_new ();
396   gtk_cell_area_add_with_properties (area, cell,
397                                      "align", FALSE,
398                                      "expand", FALSE,
399                                      "fixed-size", FALSE,
400                                      NULL);
401   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
402                                   "gicon", COLUMN_ICON,
403                                   NULL);
404
405   cell = gtk_cell_renderer_text_new ();
406   gtk_cell_area_add_with_properties (area, cell,
407                                      "align", FALSE,
408                                      "expand", TRUE,
409                                      NULL);
410   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
411                                   "text", COLUMN_LABEL,
412                                   NULL);
413
414   gtk_app_chooser_button_populate (self);
415 }
416
417 static void
418 gtk_app_chooser_button_remove_non_custom (GtkAppChooserButton *self)
419 {
420   GtkTreeModel *model;
421   GtkTreeIter iter;
422   gboolean custom, res;
423
424   model = GTK_TREE_MODEL (self->priv->store);
425
426   if (!gtk_tree_model_get_iter_first (model, &iter))
427     return;
428
429   do {
430     gtk_tree_model_get (model, &iter,
431                         COLUMN_CUSTOM, &custom,
432                         -1);
433     if (custom)
434       res = gtk_tree_model_iter_next (model, &iter);
435     else
436       res = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
437   } while (res);
438 }
439
440 static void
441 gtk_app_chooser_button_changed (GtkComboBox *object)
442 {
443   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
444   GtkTreeIter iter;
445   gchar *name = NULL;
446   gboolean custom;
447   GQuark name_quark;
448
449   if (!gtk_combo_box_get_active_iter (object, &iter))
450     return;
451
452   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
453                       COLUMN_NAME, &name,
454                       COLUMN_CUSTOM, &custom,
455                       -1);
456
457   if (name != NULL)
458     {
459       if (custom)
460         {
461           name_quark = g_quark_from_string (name);
462           g_signal_emit (self, signals[SIGNAL_CUSTOM_ITEM_ACTIVATED], name_quark, name);
463           self->priv->last_active = gtk_combo_box_get_active (object);
464         }
465       else
466         {
467           /* trigger the dialog internally */
468           other_application_item_activated_cb (self);
469         }
470
471       g_free (name);
472     }
473   else
474     self->priv->last_active = gtk_combo_box_get_active (object);
475 }
476
477 static void
478 gtk_app_chooser_button_refresh (GtkAppChooser *object)
479 {
480   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
481
482   gtk_app_chooser_button_remove_non_custom (self);
483   gtk_app_chooser_button_populate (self);
484 }
485
486 static GAppInfo *
487 gtk_app_chooser_button_get_app_info (GtkAppChooser *object)
488 {
489   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
490   GtkTreeIter iter;
491   GAppInfo *info;
492
493   if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &iter))
494     return NULL;
495
496   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
497                       COLUMN_APP_INFO, &info,
498                       -1);
499
500   return info;
501 }
502
503 static void
504 gtk_app_chooser_button_constructed (GObject *obj)
505 {
506   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
507
508   if (G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed != NULL)
509     G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed (obj);
510
511   gtk_app_chooser_button_build_ui (self);
512 }
513
514 static void
515 gtk_app_chooser_button_set_property (GObject      *obj,
516                                      guint         property_id,
517                                      const GValue *value,
518                                      GParamSpec   *pspec)
519 {
520   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
521
522   switch (property_id)
523     {
524     case PROP_CONTENT_TYPE:
525       self->priv->content_type = g_value_dup_string (value);
526       break;
527     case PROP_SHOW_DIALOG_ITEM:
528       gtk_app_chooser_button_set_show_dialog_item (self, g_value_get_boolean (value));
529       break;
530     case PROP_SHOW_DEFAULT_ITEM:
531       gtk_app_chooser_button_set_show_default_item (self, g_value_get_boolean (value));
532       break;
533     case PROP_HEADING:
534       gtk_app_chooser_button_set_heading (self, g_value_get_string (value));
535       break;
536     default:
537       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
538       break;
539     }
540 }
541
542 static void
543 gtk_app_chooser_button_get_property (GObject    *obj,
544                                      guint       property_id,
545                                      GValue     *value,
546                                      GParamSpec *pspec)
547 {
548   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
549
550   switch (property_id)
551     {
552     case PROP_CONTENT_TYPE:
553       g_value_set_string (value, self->priv->content_type);
554       break;
555     case PROP_SHOW_DIALOG_ITEM:
556       g_value_set_boolean (value, self->priv->show_dialog_item);
557       break;
558     case PROP_SHOW_DEFAULT_ITEM:
559       g_value_set_boolean (value, self->priv->show_default_item);
560       break;
561     case PROP_HEADING:
562       g_value_set_string (value, self->priv->heading);
563       break;
564     default:
565       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
566       break;
567     }
568 }
569
570 static void
571 gtk_app_chooser_button_finalize (GObject *obj)
572 {
573   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
574
575   g_hash_table_destroy (self->priv->custom_item_names);
576   g_free (self->priv->content_type);
577   g_free (self->priv->heading);
578
579   g_object_unref (self->priv->store);
580
581   G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->finalize (obj);
582 }
583
584 static void
585 app_chooser_iface_init (GtkAppChooserIface *iface)
586 {
587   iface->get_app_info = gtk_app_chooser_button_get_app_info;
588   iface->refresh = gtk_app_chooser_button_refresh;
589 }
590
591 static void
592 gtk_app_chooser_button_class_init (GtkAppChooserButtonClass *klass)
593 {
594   GObjectClass *oclass = G_OBJECT_CLASS (klass);
595   GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (klass);
596   GParamSpec *pspec;
597
598   oclass->set_property = gtk_app_chooser_button_set_property;
599   oclass->get_property = gtk_app_chooser_button_get_property;
600   oclass->finalize = gtk_app_chooser_button_finalize;
601   oclass->constructed = gtk_app_chooser_button_constructed;
602
603   combo_class->changed = gtk_app_chooser_button_changed;
604
605   g_object_class_override_property (oclass, PROP_CONTENT_TYPE, "content-type");
606
607   /**
608    * GtkAppChooserButton:show-dialog-item:
609    *
610    * The #GtkAppChooserButton:show-dialog-item property determines
611    * whether the dropdown menu should show an item that triggers
612    * a #GtkAppChooserDialog when clicked.
613    */
614   pspec =
615     g_param_spec_boolean ("show-dialog-item",
616                           P_("Include an 'Other...' item"),
617                           P_("Whether the combobox should include an item that triggers a GtkAppChooserDialog"),
618                           FALSE,
619                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
620   g_object_class_install_property (oclass, PROP_SHOW_DIALOG_ITEM, pspec);
621
622   /**
623    * GtkAppChooserButton:show-default-item:
624    *
625    * The #GtkAppChooserButton:show-default-item property determines
626    * whether the dropdown menu should show the default application
627    * on top for the provided content type.
628    *
629    * Since: 3.2
630    */
631   pspec =
632     g_param_spec_boolean ("show-default-item",
633                           P_("Show default item"),
634                           P_("Whether the combobox should show the default application on top"),
635                           FALSE,
636                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
637   g_object_class_install_property (oclass, PROP_SHOW_DEFAULT_ITEM, pspec);
638
639
640   /**
641    * GtkAppChooserButton:heading:
642    *
643    * The text to show at the top of the dialog that can be
644    * opened from the button. The string may contain Pango markup.
645    */
646   pspec = g_param_spec_string ("heading",
647                                P_("Heading"),
648                                P_("The text to show at the top of the dialog"),
649                                NULL,
650                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
651   g_object_class_install_property (oclass, PROP_HEADING, pspec);
652
653
654   /**
655    * GtkAppChooserButton::custom-item-activated:
656    * @self: the object which received the signal
657    * @item_name: the name of the activated item
658    *
659    * Emitted when a custom item, previously added with
660    * gtk_app_chooser_button_append_custom_item(), is activated from the
661    * dropdown menu.
662    */
663   signals[SIGNAL_CUSTOM_ITEM_ACTIVATED] =
664     g_signal_new ("custom-item-activated",
665                   GTK_TYPE_APP_CHOOSER_BUTTON,
666                   G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
667                   G_STRUCT_OFFSET (GtkAppChooserButtonClass, custom_item_activated),
668                   NULL, NULL,
669                   _gtk_marshal_VOID__STRING,
670                   G_TYPE_NONE,
671                   1, G_TYPE_STRING);
672
673   g_type_class_add_private (klass, sizeof (GtkAppChooserButtonPrivate));
674 }
675
676 static void
677 gtk_app_chooser_button_init (GtkAppChooserButton *self)
678 {
679   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_BUTTON,
680                                             GtkAppChooserButtonPrivate);
681   self->priv->custom_item_names =
682     g_hash_table_new_full (g_str_hash, g_str_equal,
683                            g_free, NULL);
684
685   self->priv->store = gtk_list_store_new (NUM_COLUMNS,
686                                           G_TYPE_APP_INFO,
687                                           G_TYPE_STRING, /* name */
688                                           G_TYPE_STRING, /* label */
689                                           G_TYPE_ICON,
690                                           G_TYPE_BOOLEAN, /* separator */
691                                           G_TYPE_BOOLEAN); /* custom */
692 }
693
694 static gboolean
695 app_chooser_button_iter_from_custom_name (GtkAppChooserButton *self,
696                                           const gchar         *name,
697                                           GtkTreeIter         *set_me)
698 {
699   GtkTreeIter iter;
700   gchar *custom_name = NULL;
701
702   if (!gtk_tree_model_get_iter_first
703       (GTK_TREE_MODEL (self->priv->store), &iter))
704     return FALSE;
705
706   do {
707     gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
708                         COLUMN_NAME, &custom_name,
709                         -1);
710
711     if (g_strcmp0 (custom_name, name) == 0)
712       {
713         g_free (custom_name);
714         *set_me = iter;
715
716         return TRUE;
717       }
718
719     g_free (custom_name);
720   } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->store), &iter));
721
722   return FALSE;
723 }
724
725 static void
726 real_insert_custom_item (GtkAppChooserButton *self,
727                          const gchar *name,
728                          const gchar *label,
729                          GIcon *icon,
730                          gboolean custom,
731                          GtkTreeIter *iter)
732 {
733   if (custom)
734     {
735       if (g_hash_table_lookup (self->priv->custom_item_names,
736                                name) != NULL)
737         {
738           g_warning ("Attempting to add custom item %s to GtkAppChooserButton, "
739                      "when there's already an item with the same name", name);
740           return;
741         }
742
743       g_hash_table_insert (self->priv->custom_item_names,
744                            g_strdup (name), GINT_TO_POINTER (1));
745     }
746
747   gtk_list_store_set (self->priv->store, iter,
748                       COLUMN_NAME, name,
749                       COLUMN_LABEL, label,
750                       COLUMN_ICON, icon,
751                       COLUMN_CUSTOM, custom,
752                       COLUMN_SEPARATOR, FALSE,
753                       -1);
754 }
755
756 static void
757 real_insert_separator (GtkAppChooserButton *self,
758                        gboolean custom,
759                        GtkTreeIter *iter)
760 {
761   gtk_list_store_set (self->priv->store, iter,
762                       COLUMN_CUSTOM, custom,
763                       COLUMN_SEPARATOR, TRUE,
764                       -1);
765 }
766
767 /**
768  * gtk_app_chooser_button_new:
769  * @content_type: the content type to show applications for
770  *
771  * Creates a new #GtkAppChooserButton for applications
772  * that can handle content of the given type.
773  *
774  * Returns: a newly created #GtkAppChooserButton
775  *
776  * Since: 3.0
777  */
778 GtkWidget *
779 gtk_app_chooser_button_new (const gchar *content_type)
780 {
781   g_return_val_if_fail (content_type != NULL, NULL);
782
783   return g_object_new (GTK_TYPE_APP_CHOOSER_BUTTON,
784                        "content-type", content_type,
785                        NULL);
786 }
787
788 /**
789  * gtk_app_chooser_button_append_separator:
790  * @self: a #GtkAppChooserButton
791  *
792  * Appends a separator to the list of applications that is shown
793  * in the popup.
794  *
795  * Since: 3.0
796  */
797 void
798 gtk_app_chooser_button_append_separator (GtkAppChooserButton *self)
799 {
800   GtkTreeIter iter;
801
802   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
803
804   gtk_list_store_append (self->priv->store, &iter);
805   real_insert_separator (self, TRUE, &iter);
806 }
807
808 /**
809  * gtk_app_chooser_button_append_custom_item:
810  * @self: a #GtkAppChooserButton
811  * @name: the name of the custom item
812  * @label: the label for the custom item
813  * @icon: the icon for the custom item
814  *
815  * Appends a custom item to the list of applications that is shown
816  * in the popup; the item name must be unique per-widget.
817  * Clients can use the provided name as a detail for the
818  * #GtkAppChooserButton::custom-item-activated signal, to add a
819  * callback for the activation of a particular custom item in the list.
820  * See also gtk_app_chooser_button_append_separator().
821  *
822  * Since: 3.0
823  */
824 void
825 gtk_app_chooser_button_append_custom_item (GtkAppChooserButton *self,
826                                            const gchar         *name,
827                                            const gchar         *label,
828                                            GIcon               *icon)
829 {
830   GtkTreeIter iter;
831
832   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
833   g_return_if_fail (name != NULL);
834
835   gtk_list_store_append (self->priv->store, &iter);
836   real_insert_custom_item (self, name, label, icon, TRUE, &iter);
837 }
838
839 /**
840  * gtk_app_chooser_button_set_active_custom_item:
841  * @self: a #GtkAppChooserButton
842  * @name: the name of the custom item
843  *
844  * Selects a custom item previously added with
845  * gtk_app_chooser_button_append_custom_item().
846  *
847  * Use gtk_app_chooser_refresh() to bring the selection
848  * to its initial state.
849  *
850  * Since: 3.0
851  */
852 void
853 gtk_app_chooser_button_set_active_custom_item (GtkAppChooserButton *self,
854                                                const gchar         *name)
855 {
856   GtkTreeIter iter;
857
858   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
859   g_return_if_fail (name != NULL);
860
861   if (g_hash_table_lookup (self->priv->custom_item_names, name) == NULL ||
862       !app_chooser_button_iter_from_custom_name (self, name, &iter))
863     {
864       g_warning ("Can't find the item named %s in the app chooser.",
865                  name);
866       return;
867     }
868
869   gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter);
870 }
871
872 /**
873  * gtk_app_chooser_button_get_show_dialog_item:
874  * @self: a #GtkAppChooserButton
875  *
876  * Returns the current value of the #GtkAppChooserButton:show-dialog-item
877  * property.
878  *
879  * Returns: the value of #GtkAppChooserButton:show-dialog-item
880  *
881  * Since: 3.0
882  */
883 gboolean
884 gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self)
885 {
886   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE);
887
888   return self->priv->show_dialog_item;
889 }
890
891 /**
892  * gtk_app_chooser_button_set_show_dialog_item:
893  * @self: a #GtkAppChooserButton
894  * @setting: the new value for #GtkAppChooserButton:show-dialog-item
895  *
896  * Sets whether the dropdown menu of this button should show an
897  * entry to trigger a #GtkAppChooserDialog.
898  *
899  * Since: 3.0
900  */
901 void
902 gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
903                                              gboolean             setting)
904 {
905   if (self->priv->show_dialog_item != setting)
906     {
907       self->priv->show_dialog_item = setting;
908
909       g_object_notify (G_OBJECT (self), "show-dialog-item");
910
911       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
912     }
913 }
914
915 /**
916  * gtk_app_chooser_button_get_show_default_item:
917  * @self: a #GtkAppChooserButton
918  *
919  * Returns the current value of the #GtkAppChooserButton:show-default-item
920  * property.
921  *
922  * Returns: the value of #GtkAppChooserButton:show-default-item
923  *
924  * Since: 3.2
925  */
926 gboolean
927 gtk_app_chooser_button_get_show_default_item (GtkAppChooserButton *self)
928 {
929   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE);
930
931   return self->priv->show_default_item;
932 }
933
934 /**
935  * gtk_app_chooser_button_set_show_default_item:
936  * @self: a #GtkAppChooserButton
937  * @setting: the new value for #GtkAppChooserButton:show-default-item
938  *
939  * Sets whether the dropdown menu of this button should show the
940  * default application for the given content type at top.
941  *
942  * Since: 3.2
943  */
944 void
945 gtk_app_chooser_button_set_show_default_item (GtkAppChooserButton *self,
946                                               gboolean             setting)
947 {
948   if (self->priv->show_default_item != setting)
949     {
950       self->priv->show_default_item = setting;
951
952       g_object_notify (G_OBJECT (self), "show-default-item");
953
954       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
955     }
956 }
957
958 /**
959  * gtk_app_chooser_button_set_heading:
960  * @self: a #GtkAppChooserButton
961  * @heading: a string containing Pango markup
962  *
963  * Sets the text to display at the top of the dialog.
964  * If the heading is not set, the dialog displays a default text.
965  */
966 void
967 gtk_app_chooser_button_set_heading (GtkAppChooserButton *self,
968                                     const gchar         *heading)
969 {
970   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
971
972   g_free (self->priv->heading);
973   self->priv->heading = g_strdup (heading);
974
975   g_object_notify (G_OBJECT (self), "heading");
976 }
977
978 /**
979  * gtk_app_chooser_button_get_heading:
980  * @self: a #GtkAppChooserButton
981  *
982  * Returns the text to display at the top of the dialog.
983  *
984  * Returns: the text to display at the top of the dialog,
985  *     or %NULL, in which case a default text is displayed
986  */
987 const gchar *
988 gtk_app_chooser_button_get_heading (GtkAppChooserButton *self)
989 {
990   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), NULL);
991
992   return self->priv->heading;
993 }