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