]> Pileus Git - ~andy/gtk/blob - gtk/gtkopenwithwidget.c
Use Related instead of the somewhat jargonny fallback
[~andy/gtk] / gtk / gtkopenwithwidget.c
1 /*
2  * gtkopenwithwidget.c: an open-with widget
3  *
4  * Copyright (C) 2004 Novell, Inc.
5  * Copyright (C) 2007, 2010 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
19  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  *
22  * Authors: Dave Camp <dave@novell.com>
23  *          Alexander Larsson <alexl@redhat.com>
24  *          Cosimo Cecchi <ccecchi@redhat.com>
25  */
26
27 #include <config.h>
28
29 #include "gtkopenwithwidget.h"
30
31 #include "gtkintl.h"
32 #include "gtkmarshalers.h"
33 #include "gtkopenwith.h"
34 #include "gtkopenwithprivate.h"
35
36 #include <string.h>
37 #include <glib/gi18n-lib.h>
38 #include <gtk/gtk.h>
39 #include <gio/gio.h>
40
41 struct _GtkOpenWithWidgetPrivate {
42   GAppInfo *selected_app_info;
43
44   char *content_type;
45   GtkOpenWithWidgetShowMode show_mode;
46
47   GtkWidget *program_list;
48   GtkWidget *show_more;
49   GtkListStore *program_list_store;
50
51   GtkCellRenderer *padding_renderer;
52
53   gboolean show_more_clicked;
54 };
55
56 enum {
57   COLUMN_APP_INFO,
58   COLUMN_GICON,
59   COLUMN_NAME,
60   COLUMN_DESC,
61   COLUMN_EXEC,
62   COLUMN_HEADING,
63   COLUMN_HEADING_TEXT,
64   COLUMN_RECOMMENDED,
65   COLUMN_FALLBACK,
66   NUM_COLUMNS
67 };
68
69
70 enum {
71   PROP_CONTENT_TYPE = 1,
72   PROP_GFILE,
73   PROP_SHOW_MODE,
74   N_PROPERTIES
75 };
76
77 enum {
78   SIGNAL_APPLICATION_SELECTED,
79   SIGNAL_APPLICATION_ACTIVATED,
80   N_SIGNALS
81 };
82
83 static guint signals[N_SIGNALS] = { 0, };
84
85 static void gtk_open_with_widget_iface_init (GtkOpenWithIface *iface);
86
87 G_DEFINE_TYPE_WITH_CODE (GtkOpenWithWidget, gtk_open_with_widget, GTK_TYPE_BOX,
88                          G_IMPLEMENT_INTERFACE (GTK_TYPE_OPEN_WITH,
89                                                 gtk_open_with_widget_iface_init));
90
91 static void
92 refresh_and_emit_app_selected (GtkOpenWithWidget *self,
93                                GtkTreeSelection *selection)
94 {
95   GtkTreeModel *model;
96   GtkTreeIter iter;
97   GAppInfo *info = NULL;
98   gboolean should_emit = FALSE;
99
100   if (gtk_tree_selection_get_selected (selection, &model, &iter))
101     {
102       gtk_tree_model_get (model, &iter,
103                           COLUMN_APP_INFO, &info,
104                           -1);
105     }
106
107   if (info == NULL)
108     return;
109
110   if (self->priv->selected_app_info)
111     {
112       if (!g_app_info_equal (self->priv->selected_app_info, info))
113         {
114           should_emit = TRUE;
115           g_object_unref (self->priv->selected_app_info);
116
117           self->priv->selected_app_info = info;
118         }
119     }
120   else
121     {
122       should_emit = TRUE;
123       self->priv->selected_app_info = info;
124     }
125
126   if (should_emit)
127     g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0,
128                    self->priv->selected_app_info);
129 }
130
131 static gboolean
132 path_is_heading (GtkTreeView *view,
133                  GtkTreePath *path)
134 {
135   GtkTreeIter iter;
136   GtkTreeModel *model;
137   gboolean res;
138
139   model = gtk_tree_view_get_model (view);
140   gtk_tree_model_get_iter (model, &iter, path);
141   gtk_tree_model_get (model, &iter,
142                       COLUMN_HEADING, &res,
143                       -1);
144
145   return res;
146 }
147
148 static void
149 program_list_selection_activated (GtkTreeView *view,
150                                   GtkTreePath *path,
151                                   GtkTreeViewColumn *column,
152                                   gpointer user_data)
153 {
154   GtkOpenWithWidget *self = user_data;
155   GtkTreeSelection *selection;
156
157   if (path_is_heading (view, path))
158     return;
159
160   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
161
162   refresh_and_emit_app_selected (self, selection);
163
164   g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0,
165                  self->priv->selected_app_info);
166 }
167
168 static void
169 item_forget_association_cb (GtkMenuItem *item,
170                             gpointer user_data)
171 {
172   GtkOpenWithWidget *self = user_data;
173   GtkTreePath *path = NULL;
174   GtkTreeIter iter;
175   GtkTreeModel *model;
176   GAppInfo *info;
177
178   gtk_tree_view_get_cursor (GTK_TREE_VIEW (self->priv->program_list), &path, NULL);
179   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));
180
181   if (path != NULL)
182     {
183       gtk_tree_model_get_iter (model, &iter, path);
184       gtk_tree_model_get (model, &iter,
185                           COLUMN_APP_INFO, &info,
186                           -1);
187
188       if (info != NULL && g_app_info_can_remove_supports_type (info))
189         g_app_info_remove_supports_type (info, self->priv->content_type, NULL);
190     }
191
192   _gtk_open_with_widget_refilter (self);
193 }
194
195 static GtkWidget *
196 gtk_open_with_widget_build_popup_menu (GtkOpenWithWidget *self)
197 {
198   GtkWidget *menu;
199   GtkWidget *item;
200
201   menu = gtk_menu_new ();
202
203   item = gtk_menu_item_new_with_label (_("Forget association"));
204   g_signal_connect (item, "activate",
205                     G_CALLBACK (item_forget_association_cb), self);
206   gtk_widget_show (item);
207
208   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
209
210   return menu;
211 }
212
213 static gboolean
214 should_show_menu (GtkOpenWithWidget *self,
215                   GdkEventButton *event)
216 {
217   GtkTreeIter iter;
218   GtkTreePath *path;
219   GtkTreeModel *model;
220   gboolean recommended, retval;
221   GAppInfo *info;
222
223   gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->program_list),
224                                  event->x, event->y,
225                                  &path, NULL, NULL, NULL);
226
227   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));
228   gtk_tree_model_get_iter (model, &iter, path);
229
230   gtk_tree_model_get (model, &iter,
231                       COLUMN_RECOMMENDED, &recommended,
232                       COLUMN_APP_INFO, &info,
233                       -1);
234
235   retval = recommended && (info != NULL);
236
237   gtk_tree_path_free (path);
238
239   if (info != NULL)
240     g_object_unref (info);
241
242   return retval;
243 }
244
245 static void
246 do_popup_menu (GtkOpenWithWidget *self,
247                GdkEventButton *event)
248 {
249   GtkWidget *menu;
250
251   if (!should_show_menu (self, event))
252     return;
253
254   menu = gtk_open_with_widget_build_popup_menu (self);
255   gtk_menu_attach_to_widget (GTK_MENU (menu), self->priv->program_list, NULL);
256   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
257                   event->button, event->time);
258 }
259
260 static gboolean
261 program_list_button_press_event_cb (GtkWidget *treeview,
262                                     GdkEventButton *event,
263                                     gpointer user_data)
264 {
265   GtkOpenWithWidget *self = user_data;
266
267   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
268     do_popup_menu (self, event);
269
270   return FALSE;
271 }
272
273 static gboolean
274 gtk_open_with_search_equal_func (GtkTreeModel *model,
275                                  int column,
276                                  const char *key,
277                                  GtkTreeIter *iter,
278                                  gpointer user_data)
279 {
280   char *normalized_key;
281   char *name, *normalized_name;
282   char *path, *normalized_path;
283   char *basename, *normalized_basename;
284   gboolean ret;
285
286   if (key != NULL)
287     {
288       normalized_key = g_utf8_casefold (key, -1);
289       g_assert (normalized_key != NULL);
290
291       ret = TRUE;
292
293       gtk_tree_model_get (model, iter,
294                           COLUMN_NAME, &name,
295                           COLUMN_EXEC, &path,
296                           -1);
297
298       if (name != NULL)
299         {
300           normalized_name = g_utf8_casefold (name, -1);
301           g_assert (normalized_name != NULL);
302
303           if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0) {
304             ret = FALSE;
305           }
306
307           g_free (normalized_name);
308         }
309
310       if (ret && path != NULL)
311         {
312           normalized_path = g_utf8_casefold (path, -1);
313           g_assert (normalized_path != NULL);
314
315           basename = g_path_get_basename (path);
316           g_assert (basename != NULL);
317
318           normalized_basename = g_utf8_casefold (basename, -1);
319           g_assert (normalized_basename != NULL);
320
321           if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
322               strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0) {
323             ret = FALSE;
324           }
325
326           g_free (basename);
327           g_free (normalized_basename);
328           g_free (normalized_path);
329         }
330
331       g_free (name);
332       g_free (path);
333       g_free (normalized_key);
334
335       return ret;
336     }
337   else
338     {
339       return TRUE;
340     }
341 }
342
343 static gint
344 gtk_open_with_sort_func (GtkTreeModel *model,
345                          GtkTreeIter *a,
346                          GtkTreeIter *b,
347                          gpointer user_data)
348 {
349   gboolean a_recommended, b_recommended;
350   gboolean a_fallback, b_fallback;
351   gboolean a_heading, b_heading;
352   gchar *a_name, *b_name, *a_casefold, *b_casefold;
353   gint retval;
354
355   /* this returns:
356    * - <0 if a should show before b
357    * - =0 if a is the same as b
358    * - >0 if a should show after b
359    */
360
361   gtk_tree_model_get (model, a,
362                       COLUMN_NAME, &a_name,
363                       COLUMN_RECOMMENDED, &a_recommended,
364                       COLUMN_FALLBACK, &a_fallback,
365                       COLUMN_HEADING, &a_heading,
366                       -1);
367
368   gtk_tree_model_get (model, b,
369                       COLUMN_NAME, &b_name,
370                       COLUMN_RECOMMENDED, &b_recommended,
371                       COLUMN_FALLBACK, &b_fallback,
372                       COLUMN_HEADING, &b_heading,
373                       -1);
374
375   /* the recommended one always wins */
376   if (a_recommended && !b_recommended)
377     {
378       retval = -1;
379       goto out;
380     }
381
382   if (b_recommended && !a_recommended)
383     {
384       retval = 1;
385       goto out;
386     }
387
388   /* the recommended one always wins */
389   if (a_fallback && !b_fallback)
390     {
391       retval = -1;
392       goto out;
393     }
394
395   if (b_fallback && !a_fallback)
396     {
397       retval = 1;
398       goto out;
399     }
400
401   /* they're both recommended/falback or not, so if one is a heading, wins */
402   if (a_heading)
403     {
404       return -1;
405       goto out;
406     }
407
408   if (b_heading)
409     {
410       return 1;
411       goto out;
412     }
413
414   a_casefold = a_name != NULL ?
415     g_utf8_casefold (a_name, -1) : NULL;
416   b_casefold = b_name != NULL ?
417     g_utf8_casefold (b_name, -1) : NULL;
418
419   retval = g_strcmp0 (a_casefold, b_casefold);
420
421   g_free (a_casefold);
422   g_free (b_casefold);
423
424  out:
425   g_free (a_name);
426   g_free (b_name);
427
428   return retval;
429 }
430
431 static void
432 padding_cell_renderer_func (GtkTreeViewColumn *column,
433                             GtkCellRenderer *cell,
434                             GtkTreeModel *model,
435                             GtkTreeIter *iter,
436                             gpointer user_data)
437 {
438   gboolean heading;
439
440   gtk_tree_model_get (model, iter,
441                       COLUMN_HEADING, &heading,
442                       -1);
443   if (heading)
444     g_object_set (cell,
445                   "visible", FALSE,
446                   "xpad", 0,
447                   "ypad", 0,
448                   NULL);
449   else
450     g_object_set (cell,
451                   "visible", TRUE,
452                   "xpad", 3,
453                   "ypad", 3,
454                   NULL);
455 }
456
457 static gboolean
458 gtk_open_with_selection_func (GtkTreeSelection *selection,
459                               GtkTreeModel *model,
460                               GtkTreePath *path,
461                               gboolean path_currently_selected,
462                               gpointer user_data)
463 {
464   GtkTreeIter iter;
465   gboolean heading;
466
467   gtk_tree_model_get_iter (model, &iter, path);
468   gtk_tree_model_get (model, &iter,
469                       COLUMN_HEADING, &heading,
470                       -1);
471
472   return !heading;
473 }
474
475 static gint
476 compare_apps_func (gconstpointer a,
477                    gconstpointer b)
478 {
479   return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b));
480 }
481
482 static void
483 gtk_open_with_widget_add_section (GtkOpenWithWidget *self,
484                                   const gchar *heading_title,
485                                   gboolean show_headings,
486                                   gboolean recommended,
487                                   gboolean fallback,
488                                   GList *applications,
489                                   GList *exclude_apps)
490 {
491   gboolean heading_added, unref_icon;
492   GtkTreeIter iter;
493   GAppInfo *app;
494   gchar *app_string, *bold_string;
495   GIcon *icon;
496   GList *l;
497
498   heading_added = FALSE;
499   bold_string = g_strdup_printf ("<b>%s</b>", heading_title);
500   
501   for (l = applications; l != NULL; l = l->next)
502     {
503       app = l->data;
504
505       if (!g_app_info_supports_uris (app) &&
506           !g_app_info_supports_files (app))
507         continue;
508
509       if (exclude_apps != NULL &&
510           g_list_find_custom (exclude_apps, app,
511                               (GCompareFunc) compare_apps_func))
512         continue;
513
514       if (!heading_added && show_headings)
515         {
516           gtk_list_store_append (self->priv->program_list_store, &iter);
517           gtk_list_store_set (self->priv->program_list_store, &iter,
518                               COLUMN_HEADING_TEXT, bold_string,
519                               COLUMN_HEADING, TRUE,
520                               COLUMN_RECOMMENDED, recommended,
521                               COLUMN_FALLBACK, fallback,
522                               -1);
523
524           heading_added = TRUE;
525         }
526
527       app_string = g_strdup_printf ("<b>%s</b>\n<i>%s</i>",
528                                     g_app_info_get_display_name (app) != NULL ?
529                                     g_app_info_get_display_name (app) : "",
530                                     g_app_info_get_description (app) != NULL ?
531                                     g_app_info_get_description (app) : "");
532
533       icon = g_app_info_get_icon (app);
534       if (icon == NULL)
535         {
536           icon = g_themed_icon_new ("application-x-executable");
537           unref_icon = TRUE;
538         }
539
540       gtk_list_store_append (self->priv->program_list_store, &iter);
541       gtk_list_store_set (self->priv->program_list_store, &iter,
542                           COLUMN_APP_INFO, app,
543                           COLUMN_GICON, icon,
544                           COLUMN_NAME, g_app_info_get_display_name (app),
545                           COLUMN_DESC, app_string,
546                           COLUMN_EXEC, g_app_info_get_executable (app),
547                           COLUMN_HEADING, FALSE,
548                           COLUMN_RECOMMENDED, recommended,
549                           COLUMN_FALLBACK, fallback,
550                           -1);
551
552       g_free (app_string);
553       if (unref_icon)
554         g_object_unref (icon);
555
556       unref_icon = FALSE;
557     }
558
559   g_free (bold_string);
560 }
561
562 static void
563 add_no_applications_label (GtkOpenWithWidget *self)
564 {
565   gchar *string, *string2, *desc;
566   GtkTreeIter iter;
567
568   desc = g_content_type_get_description (self->priv->content_type);
569   string2 = g_strdup_printf (_("No applications available to open \"%s\""),
570                              desc);
571
572   string = g_strdup_printf ("<big><b>%s</b></big>\n<i>%s</i>",
573                             string2,
574                             _("Click \"Show other applications\" for more options"));
575
576   gtk_list_store_append (self->priv->program_list_store, &iter);
577   gtk_list_store_set (self->priv->program_list_store, &iter,
578                       COLUMN_HEADING_TEXT, string,
579                       COLUMN_HEADING, TRUE,
580                       COLUMN_RECOMMENDED, TRUE,
581                       -1);
582
583   g_free (string);
584   g_free (string2); 
585   g_free (desc); 
586 }
587
588 static void
589 gtk_open_with_widget_real_add_items (GtkOpenWithWidget *self)
590 {
591   GList *all_applications = NULL, *content_type_apps = NULL, *recommended_apps = NULL, *fallback_apps = NULL;
592   gboolean show_recommended, show_headings, show_all;
593
594   if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_RECOMMENDED)
595     {
596       show_all = FALSE;
597       show_headings = FALSE;
598       show_recommended = TRUE;
599     }
600   else if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_ALL)
601     {
602       show_all = TRUE;
603       show_headings = FALSE;
604       show_recommended = FALSE;
605     }
606   else
607     {
608       show_all = self->priv->show_more_clicked;
609       show_headings = TRUE;
610       show_recommended = TRUE;
611     }
612
613   if (show_recommended)
614     {
615       recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
616       fallback_apps = g_app_info_get_fallback_for_type (self->priv->content_type);
617       content_type_apps = g_list_concat (g_list_copy (recommended_apps),
618                                          g_list_copy (fallback_apps));
619     }
620
621   if (show_all)
622     all_applications = g_app_info_get_all ();
623
624   if (show_recommended)
625     {
626       if (recommended_apps != NULL)
627         gtk_open_with_widget_add_section (self, _("Recommended Applications"),
628                                           show_headings, TRUE, FALSE, recommended_apps, NULL);
629       else if (!self->priv->show_more_clicked)
630         add_no_applications_label (self);
631     }
632
633   if (show_all)
634     gtk_open_with_widget_add_section (self, _("Related Applications"),
635                                       show_headings, FALSE, TRUE, fallback_apps, recommended_apps);
636
637   if (show_all)
638     gtk_open_with_widget_add_section (self, _("Other Applications"),
639                                       show_headings, FALSE, FALSE, all_applications, content_type_apps);
640
641   if (content_type_apps != NULL)
642     g_list_free_full (content_type_apps, g_object_unref);
643
644   if (all_applications != NULL)
645     g_list_free_full (all_applications, g_object_unref);
646 }
647
648 static void
649 gtk_open_with_widget_add_items (GtkOpenWithWidget *self)
650 {
651   GtkCellRenderer *renderer;
652   GtkTreeViewColumn *column;
653   GtkTreeModel *sort;
654
655   /* create list store */
656   self->priv->program_list_store = gtk_list_store_new (NUM_COLUMNS,
657                                                        G_TYPE_APP_INFO,
658                                                        G_TYPE_ICON,
659                                                        G_TYPE_STRING,
660                                                        G_TYPE_STRING,
661                                                        G_TYPE_STRING,
662                                                        G_TYPE_BOOLEAN,
663                                                        G_TYPE_STRING,
664                                                        G_TYPE_BOOLEAN,
665                                                        G_TYPE_BOOLEAN);
666   sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->priv->program_list_store));
667
668   /* populate the widget */
669   gtk_open_with_widget_real_add_items (self);
670
671   gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->program_list), 
672                            GTK_TREE_MODEL (sort));
673   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort),
674                                         COLUMN_NAME,
675                                         GTK_SORT_ASCENDING);
676   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort),
677                                    COLUMN_NAME,
678                                    gtk_open_with_sort_func,
679                                    self, NULL);
680   gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->priv->program_list),
681                                        gtk_open_with_search_equal_func,
682                                        NULL, NULL);
683
684   column = gtk_tree_view_column_new ();
685
686   /* initial padding */
687   renderer = gtk_cell_renderer_text_new ();
688   gtk_tree_view_column_pack_start (column, renderer, FALSE);
689   g_object_set (renderer,
690                 "xpad", (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS) ? 6 : 0,
691                 NULL);
692   self->priv->padding_renderer = renderer;
693
694   /* heading text renderer */
695   renderer = gtk_cell_renderer_text_new ();
696   gtk_tree_view_column_pack_start (column, renderer, FALSE);
697   gtk_tree_view_column_set_attributes (column, renderer,
698                                        "markup", COLUMN_HEADING_TEXT,
699                                        "visible", COLUMN_HEADING,
700                                        NULL);
701   g_object_set (renderer,
702                 "ypad", 6,
703                 "xpad", 0,
704                 "wrap-width", 350,
705                 "wrap-mode", PANGO_WRAP_WORD,
706                 NULL);
707
708   /* padding renderer for non-heading cells */
709   renderer = gtk_cell_renderer_text_new ();
710   gtk_tree_view_column_pack_start (column, renderer, FALSE);
711   gtk_tree_view_column_set_cell_data_func (column, renderer,
712                                            padding_cell_renderer_func,
713                                            NULL, NULL);
714
715   /* app icon renderer */
716   renderer = gtk_cell_renderer_pixbuf_new ();
717   gtk_tree_view_column_pack_start (column, renderer, FALSE);
718   gtk_tree_view_column_set_attributes (column, renderer,
719                                        "gicon", COLUMN_GICON,
720                                        NULL);
721   g_object_set (renderer,
722                 "stock-size", GTK_ICON_SIZE_DIALOG,
723                 NULL);
724
725   /* app name renderer */
726   renderer = gtk_cell_renderer_text_new ();
727   gtk_tree_view_column_pack_start (column, renderer, TRUE);
728   gtk_tree_view_column_set_attributes (column, renderer,
729                                        "markup", COLUMN_DESC,
730                                        NULL);
731   g_object_set (renderer,
732                 "ellipsize", PANGO_ELLIPSIZE_END,
733                 "ellipsize-set", TRUE,
734                 NULL);
735   
736   gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
737   gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->program_list), column);
738 }
739
740 static void
741 gtk_open_with_widget_set_property (GObject *object,
742                                    guint property_id,
743                                    const GValue *value,
744                                    GParamSpec *pspec)
745 {
746   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
747
748   switch (property_id)
749     {
750     case PROP_CONTENT_TYPE:
751       self->priv->content_type = g_value_dup_string (value);
752       break;
753     case PROP_SHOW_MODE:
754       gtk_open_with_widget_set_show_mode (self,
755                                           g_value_get_enum (value));
756       break;
757     default:
758       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
759       break;
760     }
761 }
762
763 static void
764 gtk_open_with_widget_get_property (GObject *object,
765                                    guint property_id,
766                                    GValue *value,
767                                    GParamSpec *pspec)
768 {
769   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
770
771   switch (property_id)
772     {
773     case PROP_CONTENT_TYPE:
774       g_value_set_string (value, self->priv->content_type);
775       break;
776     case PROP_SHOW_MODE:
777       g_value_set_enum (value, self->priv->show_mode);
778       break;
779     default:
780       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
781       break;
782     }
783 }
784
785 static void
786 show_more_button_clicked_cb (GtkButton *button,
787                              gpointer user_data)
788 {
789   GtkOpenWithWidget *self = user_data;
790
791   self->priv->show_more_clicked = TRUE;
792   gtk_widget_hide (GTK_WIDGET (button));
793
794   _gtk_open_with_widget_refilter (self);
795 }
796
797 static void
798 gtk_open_with_widget_ensure_show_more_button (GtkOpenWithWidget *self)
799 {
800   if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS)
801     {
802       if (!self->priv->show_more_clicked)
803         gtk_widget_show (self->priv->show_more);
804     }
805   else
806     {
807       gtk_widget_hide (self->priv->show_more);
808     }
809 }
810
811 static void
812 gtk_open_with_widget_constructed (GObject *object)
813 {
814   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
815
816   g_assert (self->priv->content_type != NULL);
817
818   if (G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->constructed != NULL)
819     G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->constructed (object);
820
821   gtk_open_with_widget_ensure_show_more_button (self);
822   gtk_open_with_widget_add_items (self);
823 }
824
825 static void
826 gtk_open_with_widget_finalize (GObject *object)
827 {
828   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
829
830   g_free (self->priv->content_type);
831
832   G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->finalize (object);
833 }
834
835 static void
836 gtk_open_with_widget_dispose (GObject *object)
837 {
838   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
839
840   if (self->priv->selected_app_info != NULL)
841     {
842       g_object_unref (self->priv->selected_app_info);
843       self->priv->selected_app_info = NULL;
844     }
845
846   G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->dispose (object);
847 }
848
849 static void
850 gtk_open_with_widget_class_init (GtkOpenWithWidgetClass *klass)
851 {
852   GObjectClass *gobject_class;
853   GParamSpec *pspec;
854
855   gobject_class = G_OBJECT_CLASS (klass);
856   gobject_class->dispose = gtk_open_with_widget_dispose;
857   gobject_class->finalize = gtk_open_with_widget_finalize;
858   gobject_class->set_property = gtk_open_with_widget_set_property;
859   gobject_class->get_property = gtk_open_with_widget_get_property;
860   gobject_class->constructed = gtk_open_with_widget_constructed;
861
862   g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
863
864   /**
865    * GtkOpenWithWidget::show-mode:
866    *
867    * The #GtkOpenWithWidgetShowMode for this widget.
868    **/
869   pspec =
870     g_param_spec_enum ("show-mode",
871                        P_("The widget show mode"),
872                        P_("The show mode for this widget"),
873                        GTK_TYPE_OPEN_WITH_WIDGET_SHOW_MODE,
874                        GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS,
875                        G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
876                        G_PARAM_STATIC_STRINGS);
877   g_object_class_install_property (gobject_class, PROP_SHOW_MODE, pspec);
878
879   signals[SIGNAL_APPLICATION_SELECTED] =
880     g_signal_new ("application-selected",
881                   GTK_TYPE_OPEN_WITH_WIDGET,
882                   G_SIGNAL_RUN_FIRST,
883                   G_STRUCT_OFFSET (GtkOpenWithWidgetClass, application_selected),
884                   NULL, NULL,
885                   _gtk_marshal_VOID__OBJECT,
886                   G_TYPE_NONE,
887                   1, G_TYPE_APP_INFO);
888
889   signals[SIGNAL_APPLICATION_ACTIVATED] =
890     g_signal_new ("application-activated",
891                   GTK_TYPE_OPEN_WITH_WIDGET,
892                   G_SIGNAL_RUN_FIRST,
893                   G_STRUCT_OFFSET (GtkOpenWithWidgetClass, application_activated),
894                   NULL, NULL,
895                   _gtk_marshal_VOID__OBJECT,
896                   G_TYPE_NONE,
897                   1, G_TYPE_APP_INFO);
898
899   g_type_class_add_private (klass, sizeof (GtkOpenWithWidgetPrivate));
900 }
901
902 static void
903 gtk_open_with_widget_init (GtkOpenWithWidget *self)
904 {
905   GtkWidget *scrolled_window, *button, *w;
906   GtkTreeSelection *selection;
907
908   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_OPEN_WITH_WIDGET,
909                                             GtkOpenWithWidgetPrivate);
910   gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
911
912   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
913   gtk_widget_set_size_request (scrolled_window, 400, 300);
914   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
915                                        GTK_SHADOW_IN);
916   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
917                                   GTK_POLICY_NEVER,
918                                   GTK_POLICY_AUTOMATIC);
919   gtk_widget_show (scrolled_window);
920
921   self->priv->program_list = gtk_tree_view_new ();
922   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->program_list),
923                                      FALSE);
924   gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->program_list);
925   gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
926   gtk_widget_show (self->priv->program_list);
927
928   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
929   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
930   gtk_tree_selection_set_select_function (selection, gtk_open_with_selection_func,
931                                           self, NULL);
932   g_signal_connect_swapped (selection, "changed",
933                             G_CALLBACK (refresh_and_emit_app_selected),
934                             self);
935   g_signal_connect (self->priv->program_list, "row-activated",
936                     G_CALLBACK (program_list_selection_activated),
937                     self);
938   g_signal_connect (self->priv->program_list, "button-press-event",
939                     G_CALLBACK (program_list_button_press_event_cb),
940                     self);
941
942   button = gtk_button_new_with_label (_("Show other applications"));
943   w = gtk_image_new_from_stock (GTK_STOCK_ADD,
944                                 GTK_ICON_SIZE_BUTTON);
945
946   gtk_button_set_image (GTK_BUTTON (button), w);
947   gtk_box_pack_start (GTK_BOX (self), button, FALSE, FALSE, 6);
948
949   g_signal_connect (button, "clicked",
950                     G_CALLBACK (show_more_button_clicked_cb), self);
951
952   self->priv->show_more = button;
953 }
954
955 static GAppInfo *
956 gtk_open_with_widget_get_app_info (GtkOpenWith *object)
957 {
958   GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);
959
960   if (self->priv->selected_app_info == NULL)
961     return NULL;
962
963   return g_object_ref (self->priv->selected_app_info);
964 }
965
966 static void
967 gtk_open_with_widget_iface_init (GtkOpenWithIface *iface)
968 {
969   iface->get_app_info = gtk_open_with_widget_get_app_info;
970 }
971
972 void
973 _gtk_open_with_widget_refilter (GtkOpenWithWidget *self)
974 {
975
976   gtk_open_with_widget_ensure_show_more_button (self);
977
978   if (self->priv->program_list_store != NULL)
979     {
980       gtk_list_store_clear (self->priv->program_list_store);
981
982       /* don't add additional xpad if we don't have headings */
983       g_object_set (self->priv->padding_renderer,
984                     "visible", self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS,
985                     NULL);
986
987       gtk_open_with_widget_real_add_items (self);
988     }
989 }
990
991 GtkWidget *
992 gtk_open_with_widget_new (const gchar *content_type)
993 {
994   return g_object_new (GTK_TYPE_OPEN_WITH_WIDGET,
995                        "content-type", content_type,
996                        NULL);
997 }
998
999 /**
1000  * gtk_open_with_widget_set_show_mode:
1001  * @self: a #GtkOpenWithWidget
1002  * @show_mode: the new show mode for this widget
1003  *
1004  * Sets the mode for the widget to show the list of applications.
1005  * See #GtkOpenWithWidgetShowMode for more details.
1006  *
1007  * Since: 3.0
1008  **/
1009 void
1010 gtk_open_with_widget_set_show_mode (GtkOpenWithWidget *self,
1011                                     GtkOpenWithWidgetShowMode show_mode)
1012 {
1013   g_return_if_fail (GTK_IS_OPEN_WITH_WIDGET (self));
1014
1015   if (self->priv->show_mode != show_mode)
1016     {
1017       self->priv->show_mode = show_mode;
1018       g_object_notify (G_OBJECT (self), "show-mode");
1019
1020       self->priv->show_more_clicked = FALSE;
1021       _gtk_open_with_widget_refilter (self);
1022     }
1023 }
1024
1025 /**
1026  * gtk_open_with_widget_get_show_mode:
1027  * @self: a #GtkOpenWithWidget
1028  *
1029  * Returns the current mode for the widget to show the list of applications.
1030  * See #GtkOpenWithWidgetShowMode for mode details.
1031  *
1032  * Returns: a #GtkOpenWithWidgetShowMode
1033  *
1034  * Since: 3.0
1035  **/
1036 GtkOpenWithWidgetShowMode
1037 gtk_open_with_widget_get_show_mode (GtkOpenWithWidget *self)
1038 {
1039   g_return_val_if_fail (GTK_IS_OPEN_WITH_WIDGET (self), FALSE);
1040
1041   return self->priv->show_mode;
1042 }