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