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 | |
65 | typedef struct _GtkAppChooserDialogClass GtkAppChooserDialogClass; |
66 | |
67 | struct _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 | |
91 | struct _GtkAppChooserDialogClass { |
92 | GtkDialogClass parent_class; |
93 | }; |
94 | |
95 | enum { |
96 | PROP_GFILE = 1, |
97 | PROP_CONTENT_TYPE, |
98 | PROP_HEADING |
99 | }; |
100 | |
101 | static void gtk_app_chooser_dialog_iface_init (GtkAppChooserIface *iface); |
102 | G_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 | |
107 | static void |
108 | add_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 | |
125 | static void |
126 | gtk_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 | |
146 | static void |
147 | widget_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 | |
154 | static void |
155 | widget_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 | |
162 | static char * |
163 | get_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 | |
175 | static void |
176 | set_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 ; |
186 | GtkWidget *; |
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 | |
280 | static void |
281 | show_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 | |
296 | static void |
297 | widget_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 | |
312 | static void |
313 | construct_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 | |
348 | static void |
349 | set_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 | |
367 | static GAppInfo * |
368 | gtk_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 | |
375 | static void |
376 | gtk_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 | |
383 | static void |
384 | show_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 | |
406 | static void |
407 | software_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 | |
432 | static void |
433 | ensure_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 | |
446 | static void |
447 | setup_search (GtkAppChooserDialog *self) |
448 | { |
449 | gboolean ; |
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 *; |
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 | |
478 | static void |
479 | gtk_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 | |
492 | static void |
493 | gtk_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 | |
504 | static void |
505 | gtk_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 | |
515 | static void |
516 | gtk_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 | |
542 | static void |
543 | gtk_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 | |
568 | static void |
569 | gtk_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 | |
575 | static void |
576 | gtk_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 | |
639 | static void |
640 | gtk_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 | |
652 | static void |
653 | set_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 | */ |
679 | GtkWidget * |
680 | gtk_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 | */ |
709 | GtkWidget * |
710 | gtk_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 | */ |
735 | GtkWidget * |
736 | gtk_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 | */ |
752 | void |
753 | gtk_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 | */ |
786 | const char * |
787 | gtk_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 | |