]> Pileus Git - ~andy/gtk/blob - gtk/gtkappchooserdialog.c
app-chooser-dialog: make sure to hold a ref when doing async ops
[~andy/gtk] / gtk / gtkappchooserdialog.c
1 /*
2  * gtkappchooserdialog.c: an app-chooser dialog
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 /**
28  * SECTION:gtkappchooserdialog
29  * @Title: GtkAppChooserDialog
30  * @Short_description: An application chooser dialog
31  *
32  * #GtkAppChooserDialog shows a #GtkAppChooserWidget inside a #GtkDialog.
33  *
34  * Note that #GtkAppChooserDialog does not have any interesting methods
35  * of its own. Instead, you should get the embedded #GtkAppChooserWidget
36  * using gtk_app_chooser_dialog_get_widget() and call its methods if
37  * the generic #GtkAppChooser interface is not sufficient for your needs.
38  *
39  * To set the heading that is shown above the #GtkAppChooserWidget,
40  * use gtk_app_chooser_dialog_set_heading().
41  */
42 #include "config.h"
43
44 #include "gtkappchooserdialog.h"
45
46 #include "gtkintl.h"
47 #include "gtkappchooser.h"
48 #include "gtkappchooseronline.h"
49 #include "gtkappchooserprivate.h"
50 #include "gtkappchooserprivate.h"
51
52 #include "gtkmessagedialog.h"
53 #include "gtklabel.h"
54 #include "gtkbbox.h"
55 #include "gtkbutton.h"
56 #include "gtkmenuitem.h"
57 #include "gtkstock.h"
58
59 #include <string.h>
60 #include <glib/gi18n-lib.h>
61 #include <gio/gio.h>
62
63 #define sure_string(s) ((const char *) ((s) != NULL ? (s) : ""))
64
65 struct _GtkAppChooserDialogPrivate {
66   char *content_type;
67   GFile *gfile;
68   char *heading;
69
70   GtkWidget *label;
71   GtkWidget *button;
72   GtkWidget *online_button;
73
74   GtkWidget *open_label;
75
76   GtkWidget *app_chooser_widget;
77   GtkWidget *show_more_button;
78
79   GtkAppChooserOnline *online;
80   GCancellable *online_cancellable;
81
82   gboolean show_more_clicked;
83   gboolean dismissed;
84 };
85
86 enum {
87   PROP_GFILE = 1,
88   PROP_CONTENT_TYPE,
89   PROP_HEADING
90 };
91
92 static void gtk_app_chooser_dialog_iface_init (GtkAppChooserIface *iface);
93 G_DEFINE_TYPE_WITH_CODE (GtkAppChooserDialog, gtk_app_chooser_dialog, GTK_TYPE_DIALOG,
94                          G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
95                                                 gtk_app_chooser_dialog_iface_init));
96
97 static void
98 show_error_dialog (const gchar *primary,
99                    const gchar *secondary,
100                    GtkWindow   *parent)
101 {
102   GtkWidget *message_dialog;
103
104   message_dialog = gtk_message_dialog_new (parent, 0,
105                                            GTK_MESSAGE_ERROR,
106                                            GTK_BUTTONS_OK,
107                                            NULL);
108   g_object_set (message_dialog,
109                 "text", primary,
110                 "secondary-text", secondary,
111                 NULL);
112   gtk_dialog_set_default_response (GTK_DIALOG (message_dialog), GTK_RESPONSE_OK);
113
114   gtk_widget_show (message_dialog);
115
116   g_signal_connect (message_dialog, "response",
117                     G_CALLBACK (gtk_widget_destroy), NULL);
118 }
119
120 static void
121 search_for_mimetype_ready_cb (GObject      *source,
122                               GAsyncResult *res,
123                               gpointer      user_data)
124 {
125   GtkAppChooserOnline *online = GTK_APP_CHOOSER_ONLINE (source);
126   GtkAppChooserDialog *self = user_data;
127   GError *error = NULL;
128
129   gdk_threads_enter ();
130
131   _gtk_app_chooser_online_search_for_mimetype_finish (online, res, &error);
132
133   if (self->priv->dismissed)
134     goto out;
135
136   if (error != NULL &&
137       !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
138     {
139       show_error_dialog (_("Failed to look for applications online"),
140                          error->message, GTK_WINDOW (self));
141     }
142   else
143     {
144       gtk_widget_set_sensitive (self->priv->online_button, TRUE);
145       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
146     }
147
148  out:
149   g_clear_error (&error);
150   g_object_unref (self);
151
152   gdk_threads_leave ();
153 }
154
155 static void
156 online_button_clicked_cb (GtkButton *b,
157                           gpointer   user_data)
158 {
159   GtkAppChooserDialog *self = user_data;
160
161   self->priv->online_cancellable = g_cancellable_new ();
162   gtk_widget_set_sensitive (self->priv->online_button, FALSE);
163
164   _gtk_app_chooser_online_search_for_mimetype_async (self->priv->online,
165                                                      self->priv->content_type,
166                                                      GTK_WINDOW (self),
167                                                      self->priv->online_cancellable,
168                                                      search_for_mimetype_ready_cb,
169                                                      g_object_ref (self));
170 }
171
172 static void
173 app_chooser_online_get_default_ready_cb (GObject *source,
174                                          GAsyncResult *res,
175                                          gpointer user_data)
176 {
177   GtkAppChooserDialog *self = user_data;
178
179   gdk_threads_enter ();
180
181   self->priv->online = _gtk_app_chooser_online_get_default_finish (source, res);
182
183   if (self->priv->online != NULL &&
184       !self->priv->dismissed)
185     {
186       GtkWidget *action_area;
187
188       action_area = gtk_dialog_get_action_area (GTK_DIALOG (self));
189       self->priv->online_button = gtk_button_new_with_label (_("Find applications online"));
190       gtk_box_pack_start (GTK_BOX (action_area), self->priv->online_button,
191                           FALSE, FALSE, 0);
192       gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), self->priv->online_button,
193                                           TRUE);
194       g_signal_connect (self->priv->online_button, "clicked",
195                         G_CALLBACK (online_button_clicked_cb), self);
196
197
198       if (!self->priv->content_type)
199         gtk_widget_set_sensitive (self->priv->online_button, FALSE);
200
201       gtk_widget_show (self->priv->online_button);
202     }
203
204   g_object_unref (self);
205
206   gdk_threads_leave ();
207 }
208
209 static void
210 ensure_online_button (GtkAppChooserDialog *self)
211 {
212   _gtk_app_chooser_online_get_default_async (app_chooser_online_get_default_ready_cb,
213                                              g_object_ref (self));
214 }
215
216 /* An application is valid if:
217  *
218  * 1) The file exists
219  * 2) The user has permissions to run the file
220  */
221 static gboolean
222 check_application (GtkAppChooserDialog  *self,
223                    GAppInfo            **app_out)
224 {
225   const char *command;
226   char *path = NULL;
227   char **argv = NULL;
228   int argc;
229   GError *error = NULL;
230   gint retval = TRUE;
231   GAppInfo *info;
232
233   command = NULL;
234
235   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
236   if (info == NULL)
237     {
238       *app_out = NULL;
239       return FALSE;
240     }
241
242   command = g_app_info_get_executable (info);
243
244   g_shell_parse_argv (command, &argc, &argv, &error);
245
246   if (error)
247     {
248       show_error_dialog (_("Could not run application"),
249                          error->message,
250                          GTK_WINDOW (self));
251       g_error_free (error);
252       retval = FALSE;
253       goto cleanup;
254     }
255
256   path = g_find_program_in_path (argv[0]);
257   if (!path)
258     {
259       char *error_message;
260
261       error_message = g_strdup_printf (_("Could not find '%s'"),
262                                        argv[0]);
263
264       show_error_dialog (_("Could not find application"),
265                          error_message,
266                          GTK_WINDOW (self));
267       g_free (error_message);
268       retval = FALSE;
269       goto cleanup;
270     }
271
272   *app_out = info;
273
274  cleanup:
275   g_strfreev (argv);
276   g_free (path);
277
278   return retval;
279 }
280
281 static void
282 add_or_find_application (GtkAppChooserDialog *self)
283 {
284   GAppInfo *app;
285
286   app = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self));
287
288   if (app)
289     {
290       /* we don't care about reporting errors here */
291       if (self->priv->content_type)
292         g_app_info_set_as_last_used_for_type (app,
293                                               self->priv->content_type,
294                                               NULL);
295       g_object_unref (app);
296     }
297 }
298
299 static void
300 gtk_app_chooser_dialog_response (GtkDialog *dialog,
301                                  gint       response_id,
302                                  gpointer   user_data)
303 {
304   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (dialog);
305
306   switch (response_id)
307     {
308     case GTK_RESPONSE_OK:
309       add_or_find_application (self);
310       break;
311     case GTK_RESPONSE_CANCEL:
312     case GTK_RESPONSE_DELETE_EVENT:
313       self->priv->dismissed = TRUE;
314     default :
315       break;
316     }
317 }
318
319 static void
320 widget_application_selected_cb (GtkAppChooserWidget *widget,
321                                 GAppInfo            *app_info,
322                                 gpointer             user_data)
323 {
324   GtkAppChooserDialog *self = user_data;
325
326   gtk_widget_set_sensitive (self->priv->button, TRUE);
327 }
328
329 static void
330 widget_application_activated_cb (GtkAppChooserWidget *widget,
331                                  GAppInfo            *app_info,
332                                  gpointer             user_data)
333 {
334   GtkAppChooserDialog *self = user_data;
335
336   gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
337 }
338
339 static char *
340 get_extension (const char *basename)
341 {
342   char *p;
343
344   p = strrchr (basename, '.');
345
346   if (p && *(p + 1) != '\0')
347     return g_strdup (p + 1);
348
349   return NULL;
350 }
351
352 static void
353 set_dialog_properties (GtkAppChooserDialog *self)
354 {
355   gchar *label;
356   gchar *name;
357   gchar *extension;
358   gchar *description;
359   gchar *default_text;
360   gchar *string;
361   gboolean unknown;
362   PangoFontDescription *font_desc;
363
364   name = NULL;
365   extension = NULL;
366   label = NULL;
367   description = NULL;
368   unknown = TRUE;
369
370   if (self->priv->gfile != NULL)
371     {
372       name = g_file_get_basename (self->priv->gfile);
373       extension = get_extension (name);
374     }
375
376   if (self->priv->content_type)
377     {
378       description = g_content_type_get_description (self->priv->content_type);
379       unknown = g_content_type_is_unknown (self->priv->content_type);
380     }
381
382   gtk_window_set_title (GTK_WINDOW (self), "");
383
384   if (name != NULL)
385     {
386       /* Translators: %s is a filename */
387       label = g_strdup_printf (_("Select an application to open \"%s\""), name);
388       string = g_strdup_printf (_("No applications available to open \"%s\""),
389                                 name);
390     }
391   else
392     {
393       /* Translators: %s is a file type description */
394       label = g_strdup_printf (_("Select an application for \"%s\" files"),
395                                unknown ? self->priv->content_type : description);
396       string = g_strdup_printf (_("No applications available to open \"%s\" files"),
397                                unknown ? self->priv->content_type : description);
398     }
399
400   font_desc = pango_font_description_new ();
401   pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD);
402   gtk_widget_override_font (self->priv->label, font_desc);
403   pango_font_description_free (font_desc);
404
405   if (self->priv->heading != NULL)
406     gtk_label_set_markup (GTK_LABEL (self->priv->label), self->priv->heading);
407   else
408     gtk_label_set_markup (GTK_LABEL (self->priv->label), label);
409
410   default_text = g_strdup_printf ("<big><b>%s</b></big>\n%s",
411                                   string,
412                                   _("Click \"Show other applications\", for more options, or "
413                                     "\"Find applications online\" to install a new application"));
414
415   gtk_app_chooser_widget_set_default_text (GTK_APP_CHOOSER_WIDGET (self->priv->app_chooser_widget),
416                                            default_text);
417
418   g_free (label);
419   g_free (name);
420   g_free (extension);
421   g_free (description);
422   g_free (string);
423   g_free (default_text);
424 }
425
426 static void
427 show_more_button_clicked_cb (GtkButton *button,
428                              gpointer   user_data)
429 {
430   GtkAppChooserDialog *self = user_data;
431
432   g_object_set (self->priv->app_chooser_widget,
433                 "show-recommended", TRUE,
434                 "show-fallback", TRUE,
435                 "show-other", TRUE,
436                 NULL);
437
438   gtk_widget_hide (self->priv->show_more_button);
439   self->priv->show_more_clicked = TRUE;
440 }
441
442 static void
443 widget_notify_for_button_cb (GObject    *source,
444                              GParamSpec *pspec,
445                              gpointer    user_data)
446 {
447   GtkAppChooserDialog *self = user_data;
448   GtkAppChooserWidget *widget = GTK_APP_CHOOSER_WIDGET (source);
449   gboolean should_hide;
450
451   should_hide = gtk_app_chooser_widget_get_show_other (widget) ||
452     self->priv->show_more_clicked;
453
454   if (should_hide)
455     gtk_widget_hide (self->priv->show_more_button);
456 }
457
458 static void
459 forget_menu_item_activate_cb (GtkMenuItem *item,
460                               gpointer     user_data)
461 {
462   GtkAppChooserDialog *self = user_data;
463   GAppInfo *info;
464
465   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self));
466
467   if (info != NULL)
468     {
469       g_app_info_remove_supports_type (info, self->priv->content_type, NULL);
470
471       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
472
473       g_object_unref (info);
474     }
475 }
476
477 static GtkWidget *
478 build_forget_menu_item (GtkAppChooserDialog *self)
479 {
480   GtkWidget *retval;
481
482   retval = gtk_menu_item_new_with_label (_("Forget association"));
483   gtk_widget_show (retval);
484
485   g_signal_connect (retval, "activate",
486                     G_CALLBACK (forget_menu_item_activate_cb), self);
487
488   return retval;
489 }
490
491 static void
492 widget_populate_popup_cb (GtkAppChooserWidget *widget,
493                           GtkMenu             *menu,
494                           GAppInfo            *info,
495                           gpointer             user_data)
496 {
497   GtkAppChooserDialog *self = user_data;
498   GtkWidget *menu_item;
499
500   if (g_app_info_can_remove_supports_type (info))
501     {
502       menu_item = build_forget_menu_item (self);
503       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
504     }
505 }
506
507 static void
508 build_dialog_ui (GtkAppChooserDialog *self)
509 {
510   GtkWidget *vbox;
511   GtkWidget *vbox2;
512   GtkWidget *button, *w;
513   GAppInfo *info;
514
515   gtk_container_set_border_width (GTK_CONTAINER (self), 5);
516
517   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
518   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
519   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), vbox, TRUE, TRUE, 0);
520   gtk_widget_show (vbox);
521
522   vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
523   gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
524   gtk_widget_show (vbox2);
525
526   self->priv->label = gtk_label_new ("");
527   gtk_widget_set_halign (self->priv->label, GTK_ALIGN_START);
528   gtk_widget_set_valign (self->priv->label, GTK_ALIGN_CENTER);
529   gtk_label_set_line_wrap (GTK_LABEL (self->priv->label), TRUE);
530   gtk_box_pack_start (GTK_BOX (vbox2), self->priv->label,
531                       FALSE, FALSE, 0);
532   gtk_widget_show (self->priv->label);
533
534   self->priv->app_chooser_widget =
535     gtk_app_chooser_widget_new (self->priv->content_type);
536   gtk_box_pack_start (GTK_BOX (vbox2), self->priv->app_chooser_widget, TRUE, TRUE, 0);
537   gtk_widget_show (self->priv->app_chooser_widget);
538
539   g_signal_connect (self->priv->app_chooser_widget, "application-selected",
540                     G_CALLBACK (widget_application_selected_cb), self);
541   g_signal_connect (self->priv->app_chooser_widget, "application-activated",
542                     G_CALLBACK (widget_application_activated_cb), self);
543   g_signal_connect (self->priv->app_chooser_widget, "notify::show-other",
544                     G_CALLBACK (widget_notify_for_button_cb), self);
545   g_signal_connect (self->priv->app_chooser_widget, "populate-popup",
546                     G_CALLBACK (widget_populate_popup_cb), self);
547
548   button = gtk_button_new_with_label (_("Show other applications"));
549   self->priv->show_more_button = button;
550   w = gtk_image_new_from_stock (GTK_STOCK_ADD,
551                                 GTK_ICON_SIZE_BUTTON);
552   gtk_button_set_image (GTK_BUTTON (button), w);
553   gtk_box_pack_start (GTK_BOX (self->priv->app_chooser_widget), button, FALSE, FALSE, 6);
554   gtk_widget_show_all (button);
555
556   g_signal_connect (button, "clicked",
557                     G_CALLBACK (show_more_button_clicked_cb), self);
558
559   gtk_dialog_add_button (GTK_DIALOG (self),
560                          GTK_STOCK_CANCEL,
561                          GTK_RESPONSE_CANCEL);
562
563   self->priv->button = gtk_dialog_add_button (GTK_DIALOG (self),
564                                               _("_Select"),
565                                               GTK_RESPONSE_OK);
566
567   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
568   gtk_widget_set_sensitive (self->priv->button, info != NULL);
569   if (info)
570     g_object_unref (info);
571
572   gtk_dialog_set_default_response (GTK_DIALOG (self),
573                                    GTK_RESPONSE_OK);
574 }
575
576 static void
577 set_gfile_and_content_type (GtkAppChooserDialog *self,
578                             GFile *file)
579 {
580   GFileInfo *info;
581
582   if (file == NULL)
583     return;
584
585   self->priv->gfile = g_object_ref (file);
586
587   info = g_file_query_info (self->priv->gfile,
588                             G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
589                             0, NULL, NULL);
590   self->priv->content_type = g_strdup (g_file_info_get_content_type (info));
591
592   g_object_unref (info);
593 }
594
595 static GAppInfo *
596 gtk_app_chooser_dialog_get_app_info (GtkAppChooser *object)
597 {
598   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
599   GAppInfo *app = NULL;
600
601   if (!check_application (self, &app))
602     return NULL;
603
604   return app;
605 }
606
607 static void
608 gtk_app_chooser_dialog_refresh (GtkAppChooser *object)
609 {
610   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
611
612   gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
613 }
614
615 static void
616 gtk_app_chooser_dialog_constructed (GObject *object)
617 {
618   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
619
620   if (G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->constructed != NULL)
621     G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->constructed (object);
622
623   build_dialog_ui (self);
624   set_dialog_properties (self);
625   ensure_online_button (self);
626 }
627
628 static void
629 gtk_app_chooser_dialog_dispose (GObject *object)
630 {
631   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
632   
633   g_clear_object (&self->priv->gfile);
634   g_clear_object (&self->priv->online);
635
636   if (self->priv->online_cancellable != NULL)
637     {
638       g_cancellable_cancel (self->priv->online_cancellable);
639       g_clear_object (&self->priv->online_cancellable);
640     }
641
642   G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->dispose (object);
643 }
644
645 static void
646 gtk_app_chooser_dialog_finalize (GObject *object)
647 {
648   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
649
650   g_free (self->priv->content_type);
651
652   G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->finalize (object);
653 }
654
655 static void
656 gtk_app_chooser_dialog_set_property (GObject      *object,
657                                      guint         property_id,
658                                      const GValue *value,
659                                      GParamSpec   *pspec)
660 {
661   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
662
663   switch (property_id)
664     {
665     case PROP_GFILE:
666       set_gfile_and_content_type (self, g_value_get_object (value));
667       break;
668     case PROP_CONTENT_TYPE:
669       /* don't try to override a value previously set with the GFile */
670       if (self->priv->content_type == NULL)
671         self->priv->content_type = g_value_dup_string (value);
672       break;
673     case PROP_HEADING:
674       gtk_app_chooser_dialog_set_heading (self, g_value_get_string (value));
675       break;
676     default:
677       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
678       break;
679     }
680 }
681
682 static void
683 gtk_app_chooser_dialog_get_property (GObject    *object,
684                                      guint       property_id,
685                                      GValue     *value,
686                                      GParamSpec *pspec)
687 {
688   GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
689
690   switch (property_id)
691     {
692     case PROP_GFILE:
693       if (self->priv->gfile != NULL)
694         g_value_set_object (value, self->priv->gfile);
695       break;
696     case PROP_CONTENT_TYPE:
697       g_value_set_string (value, self->priv->content_type);
698       break;
699     case PROP_HEADING:
700       g_value_set_string (value, self->priv->heading);
701       break;
702     default:
703       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
704       break;
705     }
706 }
707
708 static void
709 gtk_app_chooser_dialog_iface_init (GtkAppChooserIface *iface)
710 {
711   iface->get_app_info = gtk_app_chooser_dialog_get_app_info;
712   iface->refresh = gtk_app_chooser_dialog_refresh;
713 }
714
715 static void
716 gtk_app_chooser_dialog_class_init (GtkAppChooserDialogClass *klass)
717 {
718   GObjectClass *gobject_class;
719   GParamSpec *pspec;
720
721   gobject_class = G_OBJECT_CLASS (klass);
722   gobject_class->dispose = gtk_app_chooser_dialog_dispose;
723   gobject_class->finalize = gtk_app_chooser_dialog_finalize;
724   gobject_class->set_property = gtk_app_chooser_dialog_set_property;
725   gobject_class->get_property = gtk_app_chooser_dialog_get_property;
726   gobject_class->constructed = gtk_app_chooser_dialog_constructed;
727
728   g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
729
730   /**
731    * GtkAppChooserDialog:gfile:
732    *
733    * The GFile used by the #GtkAppChooserDialog.
734    * The dialog's #GtkAppChooserWidget content type will be guessed from the
735    * file, if present.
736    */
737   pspec = g_param_spec_object ("gfile",
738                                P_("GFile"),
739                                P_("The GFile used by the app chooser dialog"),
740                                G_TYPE_FILE,
741                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
742                                G_PARAM_STATIC_STRINGS);
743   g_object_class_install_property (gobject_class, PROP_GFILE, pspec);
744
745   /**
746    * GtkAppChooserDialog:heading:
747    *
748    * The text to show at the top of the dialog.
749    * The string may contain Pango markup.
750    */
751   pspec = g_param_spec_string ("heading",
752                                P_("Heading"),
753                                P_("The text to show at the top of the dialog"),
754                                NULL,
755                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
756   g_object_class_install_property (gobject_class, PROP_HEADING, pspec);
757
758
759   g_type_class_add_private (klass, sizeof (GtkAppChooserDialogPrivate));
760 }
761
762 static void
763 gtk_app_chooser_dialog_init (GtkAppChooserDialog *self)
764 {
765   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_DIALOG,
766                                             GtkAppChooserDialogPrivate);
767
768   /* we can't override the class signal handler here, as it's a RUN_LAST;
769    * we want our signal handler instead to be executed before any user code.
770    */
771   g_signal_connect (self, "response",
772                     G_CALLBACK (gtk_app_chooser_dialog_response), NULL);
773 }
774
775 static void
776 set_parent_and_flags (GtkWidget      *dialog,
777                       GtkWindow      *parent,
778                       GtkDialogFlags  flags)
779 {
780   if (parent != NULL)
781     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
782
783   if (flags & GTK_DIALOG_MODAL)
784     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
785
786   if (flags & GTK_DIALOG_DESTROY_WITH_PARENT)
787     gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
788 }
789
790 /**
791  * gtk_app_chooser_dialog_new:
792  * @parent: (allow-none): a #GtkWindow, or %NULL
793  * @flags: flags for this dialog
794  * @file: a #GFile
795  *
796  * Creates a new #GtkAppChooserDialog for the provided #GFile,
797  * to allow the user to select an application for it.
798  *
799  * Returns: a newly created #GtkAppChooserDialog
800  *
801  * Since: 3.0
802  **/
803 GtkWidget *
804 gtk_app_chooser_dialog_new (GtkWindow      *parent,
805                             GtkDialogFlags  flags,
806                             GFile          *file)
807 {
808   GtkWidget *retval;
809
810   g_return_val_if_fail (G_IS_FILE (file), NULL);
811
812   retval = g_object_new (GTK_TYPE_APP_CHOOSER_DIALOG,
813                          "gfile", file,
814                          NULL);
815
816   set_parent_and_flags (retval, parent, flags);
817
818   return retval;
819 }
820
821 /**
822  * gtk_app_chooser_dialog_new_for_content_type:
823  * @parent: (allow-none): a #GtkWindow, or %NULL
824  * @flags: flags for this dialog
825  * @content_type: a content type string
826  *
827  * Creates a new #GtkAppChooserDialog for the provided content type,
828  * to allow the user to select an application for it.
829  *
830  * Returns: a newly created #GtkAppChooserDialog
831  *
832  * Since: 3.0
833  **/
834 GtkWidget *
835 gtk_app_chooser_dialog_new_for_content_type (GtkWindow      *parent,
836                                              GtkDialogFlags  flags,
837                                              const gchar    *content_type)
838 {
839   GtkWidget *retval;
840
841   g_return_val_if_fail (content_type != NULL, NULL);
842
843   retval = g_object_new (GTK_TYPE_APP_CHOOSER_DIALOG,
844                          "content-type", content_type,
845                          NULL);
846
847   set_parent_and_flags (retval, parent, flags);
848
849   return retval;
850 }
851
852 /**
853  * gtk_app_chooser_dialog_get_widget:
854  * @self: a #GtkAppChooserDialog
855  *
856  * Returns the #GtkAppChooserWidget of this dialog.
857  *
858  * Returns: (transfer none): the #GtkAppChooserWidget of @self
859  *
860  * Since: 3.0
861  */
862 GtkWidget *
863 gtk_app_chooser_dialog_get_widget (GtkAppChooserDialog *self)
864 {
865   g_return_val_if_fail (GTK_IS_APP_CHOOSER_DIALOG (self), NULL);
866
867   return self->priv->app_chooser_widget;
868 }
869
870 /**
871  * gtk_app_chooser_dialog_set_heading:
872  * @self: a #GtkAppChooserDialog
873  * @heading: a string containing Pango markup
874  *
875  * Sets the text to display at the top of the dialog.
876  * If the heading is not set, the dialog displays a default text.
877  */
878 void
879 gtk_app_chooser_dialog_set_heading (GtkAppChooserDialog *self,
880                                     const gchar         *heading)
881 {
882   g_return_if_fail (GTK_IS_APP_CHOOSER_DIALOG (self));
883
884   g_free (self->priv->heading);
885   self->priv->heading = g_strdup (heading);
886
887   if (self->priv->label && self->priv->heading)
888     gtk_label_set_markup (GTK_LABEL (self->priv->label), self->priv->heading);
889
890   g_object_notify (G_OBJECT (self), "heading");
891 }
892
893 /**
894  * gtk_app_chooser_dialog_get_heading:
895  * @self: a #GtkAppChooserDialog
896  *
897  * Returns the text to display at the top of the dialog.
898  *
899  * Returns: the text to display at the top of the dialog, or %NULL, in which
900  *     case a default text is displayed
901  */
902 const gchar *
903 gtk_app_chooser_dialog_get_heading (GtkAppChooserDialog *self)
904 {
905   g_return_val_if_fail (GTK_IS_APP_CHOOSER_DIALOG (self), NULL);
906
907   return self->priv->heading;
908 }