]> Pileus Git - ~andy/gtk/blob - gtk/gtkappchooserbutton.c
app-chooser-button: include gtk-docs for show-dialog-item
[~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 #include "config.h"
25
26 #include "gtkappchooserbutton.h"
27
28 #include "gtkappchooser.h"
29 #include "gtkappchooserdialog.h"
30 #include "gtkappchooserprivate.h"
31 #include "gtkcelllayout.h"
32 #include "gtkcellrendererpixbuf.h"
33 #include "gtkcellrenderertext.h"
34 #include "gtkcombobox.h"
35 #include "gtkdialog.h"
36 #include "gtkintl.h"
37
38 enum {
39   PROP_CONTENT_TYPE = 1,
40   PROP_SHOW_DIALOG_ITEM,
41 };
42
43 enum {
44   COLUMN_APP_INFO,
45   COLUMN_NAME,
46   COLUMN_ICON,
47   COLUMN_CUSTOM,
48   COLUMN_SEPARATOR,
49   COLUMN_CALLBACK,
50   NUM_COLUMNS,
51 };
52
53 typedef struct {
54   GtkAppChooserButtonItemFunc func;
55   gpointer user_data;
56 } CustomAppComboData;
57
58 static gpointer
59 custom_app_data_copy (gpointer boxed)
60 {
61   CustomAppComboData *retval, *original;
62
63   original = boxed;
64
65   retval = g_slice_new0 (CustomAppComboData);
66   retval->func = original->func;
67   retval->user_data = original->user_data;
68
69   return retval;
70 }
71
72 static void
73 custom_app_data_free (gpointer boxed)
74 {
75   g_slice_free (CustomAppComboData, boxed);
76 }
77
78 #define CUSTOM_COMBO_DATA_TYPE custom_app_combo_data_get_type()
79 G_DEFINE_BOXED_TYPE (CustomAppComboData, custom_app_combo_data,
80                      custom_app_data_copy,
81                      custom_app_data_free);
82
83 static void app_chooser_iface_init (GtkAppChooserIface *iface);
84
85 static void real_insert_custom_item (GtkAppChooserButton *self,
86                                      const gchar *label,
87                                      GIcon *icon,
88                                      GtkAppChooserButtonItemFunc func,
89                                      gpointer user_data,
90                                      gboolean custom,
91                                      GtkTreeIter *iter);
92
93 static void real_insert_separator (GtkAppChooserButton *self,
94                                    gboolean custom,
95                                    GtkTreeIter *iter);
96
97 G_DEFINE_TYPE_WITH_CODE (GtkAppChooserButton, gtk_app_chooser_button, GTK_TYPE_COMBO_BOX,
98                          G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
99                                                 app_chooser_iface_init));
100
101 struct _GtkAppChooserButtonPrivate {
102   GtkListStore *store;
103
104   gchar *content_type;
105   gboolean show_dialog_item;
106 };
107
108 static gboolean
109 row_separator_func (GtkTreeModel *model,
110                     GtkTreeIter  *iter,
111                     gpointer      user_data)
112 {
113   gboolean separator;
114
115   gtk_tree_model_get (model, iter,
116                       COLUMN_SEPARATOR, &separator,
117                       -1);
118
119   return separator;
120 }
121
122 static void
123 get_first_iter (GtkListStore *store,
124                 GtkTreeIter  *iter)
125 {
126   GtkTreeIter iter2;
127
128   if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), iter))
129     {
130       /* the model is empty, append */
131       gtk_list_store_append (store, iter);
132     }
133   else
134     {
135       gtk_list_store_insert_before (store, &iter2, iter);
136       *iter = iter2;
137     }
138 }
139
140 typedef struct {
141   GtkAppChooserButton *self;
142   GAppInfo *info;
143   gint active_index;
144 } SelectAppData;
145
146 static void
147 select_app_data_free (SelectAppData *data)
148 {
149   g_clear_object (&data->self);
150   g_clear_object (&data->info);
151
152   g_slice_free (SelectAppData, data);
153 }
154
155 static gboolean
156 select_application_func_cb (GtkTreeModel *model,
157                             GtkTreePath *path,
158                             GtkTreeIter *iter,
159                             gpointer user_data)
160 {
161   SelectAppData *data = user_data;
162   GAppInfo *app_to_match = data->info, *app = NULL;
163   gboolean custom;
164
165   gtk_tree_model_get (model, iter,
166                       COLUMN_APP_INFO, &app,
167                       COLUMN_CUSTOM, &custom,
168                       -1);
169
170   /* cutsom items are always after GAppInfos, so iterating further here
171    * is just useless.
172    */
173   if (custom)
174     return TRUE;
175
176   if (g_app_info_equal (app, app_to_match))
177     {
178       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter);
179       return TRUE;
180     }
181
182   return FALSE;
183 }
184
185 static void
186 gtk_app_chooser_button_select_application (GtkAppChooserButton *self,
187                                            GAppInfo *info)
188 {
189   SelectAppData *data;
190
191   data = g_slice_new0 (SelectAppData);
192   data->self = g_object_ref (self);
193   data->info = g_object_ref (info);
194
195   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->store),
196                           select_application_func_cb, data);
197
198   select_app_data_free (data);
199 }
200
201 static void
202 other_application_dialog_response_cb (GtkDialog *dialog,
203                                       gint response_id,
204                                       gpointer user_data)
205 {
206   GtkAppChooserButton *self = user_data;
207   GAppInfo *info;
208
209   if (response_id != GTK_RESPONSE_OK)
210     {
211       /* reset the active item, otherwise we are stuck on
212        * 'Other application...'
213        */
214       gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0);
215       gtk_widget_destroy (GTK_WIDGET (dialog));
216       return;
217     }
218
219   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
220
221   /* refresh the combobox to get the new application */
222   gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
223   gtk_app_chooser_button_select_application (self, info);
224
225   g_object_unref (info);
226 }
227
228 static void
229 other_application_item_activated_cb (GtkAppChooserButton *self,
230                                      gpointer _user_data)
231 {
232   GtkWidget *dialog, *widget;
233   GtkWindow *toplevel;
234
235   toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
236   dialog = gtk_app_chooser_dialog_new_for_content_type (toplevel, GTK_DIALOG_DESTROY_WITH_PARENT,
237                                                         self->priv->content_type);
238   widget = gtk_app_chooser_dialog_get_widget (GTK_APP_CHOOSER_DIALOG (dialog));
239   g_object_set (widget,
240                 "show-fallback", TRUE,
241                 "show-other", TRUE,
242                 NULL);
243   gtk_widget_show (dialog);
244
245   g_signal_connect (dialog, "response",
246                     G_CALLBACK (other_application_dialog_response_cb), self);
247 }
248
249 static void
250 gtk_app_chooser_button_ensure_dialog_item (GtkAppChooserButton *self,
251                                            GtkTreeIter *prev_iter)
252 {
253   GIcon *icon;
254   GtkTreeIter iter;
255
256   if (!self->priv->show_dialog_item)
257     return;
258
259   icon = g_themed_icon_new ("application-x-executable");
260
261   gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
262   real_insert_separator (self, FALSE, &iter);
263   *prev_iter = iter;
264
265   gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
266   real_insert_custom_item (self,
267                            _("Other application..."), icon,
268                            other_application_item_activated_cb,
269                            NULL, FALSE, &iter);
270
271   g_object_unref (icon);
272 }
273
274 static void
275 gtk_app_chooser_button_populate (GtkAppChooserButton *self)
276 {
277   GList *recommended_apps = NULL, *l;
278   GAppInfo *app;
279   GtkTreeIter iter, iter2;
280   GIcon *icon;
281   gboolean first;
282
283   recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
284   first = TRUE;
285
286   for (l = recommended_apps; l != NULL; l = l->next)
287     {
288       app = l->data;
289
290       icon = g_app_info_get_icon (app);
291
292       if (icon == NULL)
293         icon = g_themed_icon_new ("application-x-executable");
294       else
295         g_object_ref (icon);
296
297       if (first)
298         {
299           get_first_iter (self->priv->store, &iter);
300           first = FALSE;
301         }
302       else
303         {
304           gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
305           iter = iter2;
306         }
307
308       gtk_list_store_set (self->priv->store, &iter,
309                           COLUMN_APP_INFO, app,
310                           COLUMN_NAME, g_app_info_get_display_name (app),
311                           COLUMN_ICON, icon,
312                           COLUMN_CUSTOM, FALSE,
313                           -1);
314
315       g_object_unref (icon);
316     }
317
318   gtk_app_chooser_button_ensure_dialog_item (self, &iter);
319   gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0);
320 }
321
322 static void
323 gtk_app_chooser_button_build_ui (GtkAppChooserButton *self)
324 {
325   GtkCellRenderer *cell;
326
327   self->priv->store = gtk_list_store_new (NUM_COLUMNS,
328                                           G_TYPE_APP_INFO,
329                                           G_TYPE_STRING,
330                                           G_TYPE_ICON,
331                                           G_TYPE_BOOLEAN,
332                                           G_TYPE_BOOLEAN,
333                                           CUSTOM_COMBO_DATA_TYPE);
334
335   gtk_combo_box_set_model (GTK_COMBO_BOX (self),
336                            GTK_TREE_MODEL (self->priv->store));
337
338   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (self),
339                                         row_separator_func, NULL, NULL);
340
341   cell = gtk_cell_renderer_pixbuf_new ();
342   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, FALSE);
343   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
344                                   "gicon", COLUMN_ICON,
345                                   NULL);
346
347   cell = gtk_cell_renderer_text_new ();
348   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE);
349   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
350                                   "text", COLUMN_NAME,
351                                   "xpad", 6,
352                                   NULL);
353
354   gtk_app_chooser_button_populate (self);
355 }
356
357 static void
358 gtk_app_chooser_button_remove_non_custom (GtkAppChooserButton *self)
359 {
360   GtkTreeModel *model;
361   GtkTreeIter iter;
362   gboolean custom, res;
363
364   model = GTK_TREE_MODEL (self->priv->store);
365
366   if (!gtk_tree_model_get_iter_first (model, &iter))
367     return;
368
369   do {
370     gtk_tree_model_get (model, &iter,
371                         COLUMN_CUSTOM, &custom,
372                         -1);
373     if (custom)
374       res = gtk_tree_model_iter_next (model, &iter);
375     else
376       res = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
377   } while (res);
378 }
379
380 static void
381 gtk_app_chooser_button_changed (GtkComboBox *object)
382 {
383   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
384   GtkTreeIter iter;
385   CustomAppComboData *custom_data = NULL;
386
387   if (!gtk_combo_box_get_active_iter (object, &iter))
388     return;
389
390   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
391                       COLUMN_CALLBACK, &custom_data,
392                       -1);
393
394   if (custom_data != NULL && custom_data->func != NULL)
395     custom_data->func (self, custom_data->user_data);
396 }
397
398 static void
399 gtk_app_chooser_button_refresh (GtkAppChooser *object)
400 {
401   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
402
403   gtk_app_chooser_button_remove_non_custom (self);
404   gtk_app_chooser_button_populate (self);
405 }
406
407 static GAppInfo *
408 gtk_app_chooser_button_get_app_info (GtkAppChooser *object)
409 {
410   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
411   GtkTreeIter iter;
412   GAppInfo *info;
413
414   if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &iter))
415     return NULL;
416
417   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
418                       COLUMN_APP_INFO, &info,
419                       -1);
420
421   return info;
422 }
423
424 static void
425 gtk_app_chooser_button_constructed (GObject *obj)
426 {
427   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
428
429   if (G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed != NULL)
430     G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed (obj);
431
432   g_assert (self->priv->content_type != NULL);
433
434   gtk_app_chooser_button_build_ui (self);
435 }
436
437 static void
438 gtk_app_chooser_button_set_property (GObject      *obj,
439                                      guint         property_id,
440                                      const GValue *value,
441                                      GParamSpec   *pspec)
442 {
443   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
444
445   switch (property_id)
446     {
447     case PROP_CONTENT_TYPE:
448       self->priv->content_type = g_value_dup_string (value);
449       break;
450     case PROP_SHOW_DIALOG_ITEM:
451       gtk_app_chooser_button_set_show_dialog_item (self, g_value_get_boolean (value));
452       break;
453     default:
454       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
455       break;
456     }
457 }
458
459 static void
460 gtk_app_chooser_button_get_property (GObject    *obj,
461                                      guint       property_id,
462                                      GValue     *value,
463                                      GParamSpec *pspec)
464 {
465   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
466
467   switch (property_id)
468     {
469     case PROP_CONTENT_TYPE:
470       g_value_set_string (value, self->priv->content_type);
471       break;
472     case PROP_SHOW_DIALOG_ITEM:
473       g_value_set_boolean (value, self->priv->show_dialog_item);
474       break;
475     default:
476       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
477       break;
478     }
479 }
480
481 static void
482 gtk_app_chooser_button_finalize (GObject *obj)
483 {
484   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
485
486   g_free (self->priv->content_type);
487
488   G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->finalize (obj);
489 }
490
491 static void
492 app_chooser_iface_init (GtkAppChooserIface *iface)
493 {
494   iface->get_app_info = gtk_app_chooser_button_get_app_info;
495   iface->refresh = gtk_app_chooser_button_refresh;
496 }
497
498 static void
499 gtk_app_chooser_button_class_init (GtkAppChooserButtonClass *klass)
500 {
501   GObjectClass *oclass = G_OBJECT_CLASS (klass);
502   GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (klass);
503   GParamSpec *pspec;
504
505   oclass->set_property = gtk_app_chooser_button_set_property;
506   oclass->get_property = gtk_app_chooser_button_get_property;
507   oclass->finalize = gtk_app_chooser_button_finalize;
508   oclass->constructed = gtk_app_chooser_button_constructed;
509
510   combo_class->changed = gtk_app_chooser_button_changed;
511
512   g_object_class_override_property (oclass, PROP_CONTENT_TYPE, "content-type");
513
514   /**
515    * GtkAppChooserButton:show-dialog-item:
516    *
517    * The ::show-dialog-item property determines whether the dropdown menu
518    * should show an item that triggers a #GtkAppChooserDialog when clicked.
519    */
520   pspec = g_param_spec_boolean ("show-dialog-item",
521                                 P_("Include an 'Other...' item"),
522                                 P_("Whether the combobox should include an item that triggers a GtkAppChooserDialog"),
523                                 FALSE,
524                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
525   g_object_class_install_property (oclass, PROP_SHOW_DIALOG_ITEM, pspec);
526
527   g_type_class_add_private (klass, sizeof (GtkAppChooserButtonPrivate));
528 }
529
530 static void
531 gtk_app_chooser_button_init (GtkAppChooserButton *self)
532 {
533   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_BUTTON,
534                                             GtkAppChooserButtonPrivate);
535 }
536
537 static void
538 real_insert_custom_item (GtkAppChooserButton *self,
539                          const gchar *label,
540                          GIcon *icon,
541                          GtkAppChooserButtonItemFunc func,
542                          gpointer user_data,
543                          gboolean custom,
544                          GtkTreeIter *iter)
545 {
546   CustomAppComboData *data;
547
548   data = g_slice_new0 (CustomAppComboData);
549   data->func = func;
550   data->user_data = user_data;
551
552   gtk_list_store_set (self->priv->store, iter,
553                       COLUMN_NAME, label,
554                       COLUMN_ICON, icon,
555                       COLUMN_CALLBACK, data,
556                       COLUMN_CUSTOM, custom,
557                       COLUMN_SEPARATOR, FALSE,
558                       -1);
559 }
560
561 static void
562 real_insert_separator (GtkAppChooserButton *self,
563                        gboolean custom,
564                        GtkTreeIter *iter)
565 {
566   gtk_list_store_set (self->priv->store, iter,
567                       COLUMN_CUSTOM, custom,
568                       COLUMN_SEPARATOR, TRUE,
569                       -1);
570 }
571
572 /**
573  * gtk_app_chooser_button_new:
574  * @content_type: the content type to show applications for
575  *
576  * Creates a new #GtkAppChooserButton for applications
577  * that can handle content of the given type.
578  *
579  * Returns: a newly created #GtkAppChooserButton
580  *
581  * Since: 3.0
582  */
583 GtkWidget *
584 gtk_app_chooser_button_new (const gchar *content_type)
585 {
586   g_return_val_if_fail (content_type != NULL, NULL);
587
588   return g_object_new (GTK_TYPE_APP_CHOOSER_BUTTON,
589                        "content-type", content_type,
590                        NULL);
591 }
592
593 /**
594  * gtk_app_chooser_button_append_separator:
595  * @self: a #GtkAppChooserButton
596  *
597  * Appends a separator to the list of applications that is shown
598  * in the popup.
599  *
600  * Since: 3.0
601  */
602 void
603 gtk_app_chooser_button_append_separator (GtkAppChooserButton *self)
604 {
605   GtkTreeIter iter;
606
607   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
608
609   gtk_list_store_append (self->priv->store, &iter);
610   real_insert_separator (self, TRUE, &iter);
611 }
612
613 /**
614  * gtk_app_chooser_button_append_custom_item:
615  * @self: a #GtkAppChooserButton
616  * @label: the label for the custom item
617  * @icon: the icon for the custom item
618  * @func: callback to call if the item is activated
619  * @user_data: user data for @func
620  *
621  * Appends a custom item to the list of applications that is shown
622  * in the popup. See also gtk_app_chooser_button_append_separator().
623  *
624  * Since: 3.0
625  */
626 void
627 gtk_app_chooser_button_append_custom_item (GtkAppChooserButton         *self,
628                                            const gchar                   *label,
629                                            GIcon                         *icon,
630                                            GtkAppChooserButtonItemFunc  func,
631                                            gpointer                       user_data)
632 {
633   GtkTreeIter iter;
634
635   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
636
637   gtk_list_store_append (self->priv->store, &iter);
638   real_insert_custom_item (self, label, icon,
639                            func, user_data, TRUE, &iter);
640 }
641
642 /**
643  * gtk_app_chooser_button_get_show_dialog_item:
644  * @self: a #GtkAppChooserButton
645  *
646  * Returns the current value of the #GtkAppChooserButton:show-dialog-item
647  * property.
648  *
649  * Returns: the value of #GtkAppChooserButton:show-dialog-item
650  *
651  * Since: 3.0
652  */
653 gboolean
654 gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self)
655 {
656   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE);
657
658   return self->priv->show_dialog_item;
659 }
660
661 /**
662  * gtk_app_chooser_button_get_show_dialog_item:
663  * @self: a #GtkAppChooserButton
664  * @setting: the new value for #GtkAppChooserButton:show-dialog-item
665  *
666  * Sets whether the dropdown menu of this button should show an
667  * entry to trigger a #GtkAppChooserDialog.
668  *
669  * Since: 3.0
670  */
671 void
672 gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
673                                              gboolean setting)
674 {
675   if (self->priv->show_dialog_item != setting)
676     {
677       self->priv->show_dialog_item = setting;
678
679       g_object_notify (G_OBJECT (self), "show-dialog-item");
680
681       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
682     }
683 }