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

source code of gtk/gtk/gtkappchooserdialog.c