1/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2/* GTK - The GIMP Toolkit
3 * gtkfilechoosernative.c: Native File selector dialog
4 * Copyright (C) 2015, Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "config.h"
21
22#include "gtkfilechoosernativeprivate.h"
23#include "gtknativedialogprivate.h"
24
25#include "gtkprivate.h"
26#include "gtkfilechooserdialog.h"
27#include "gtkfilechooserprivate.h"
28#include "gtkfilechooserwidget.h"
29#include "gtkfilechooserwidgetprivate.h"
30#include "gtkfilechooserutils.h"
31#include "gtksizerequest.h"
32#include "gtktypebuiltins.h"
33#include "gtkintl.h"
34#include "gtksettings.h"
35#include "gtktogglebutton.h"
36#include "gtkheaderbar.h"
37#include "gtklabel.h"
38#include "gtkfilefilterprivate.h"
39
40/**
41 * GtkFileChooserNative:
42 *
43 * `GtkFileChooserNative` is an abstraction of a dialog suitable
44 * for use with “File Open” or “File Save as” commands.
45 *
46 * By default, this just uses a `GtkFileChooserDialog` to implement
47 * the actual dialog. However, on some platforms, such as Windows and
48 * macOS, the native platform file chooser is used instead. When the
49 * application is running in a sandboxed environment without direct
50 * filesystem access (such as Flatpak), `GtkFileChooserNative` may call
51 * the proper APIs (portals) to let the user choose a file and make it
52 * available to the application.
53 *
54 * While the API of `GtkFileChooserNative` closely mirrors `GtkFileChooserDialog`,
55 * the main difference is that there is no access to any `GtkWindow` or `GtkWidget`
56 * for the dialog. This is required, as there may not be one in the case of a
57 * platform native dialog.
58 *
59 * Showing, hiding and running the dialog is handled by the
60 * [class@Gtk.NativeDialog] functions.
61 *
62 * Note that unlike `GtkFileChooserDialog`, `GtkFileChooserNative` objects
63 * are not toplevel widgets, and GTK does not keep them alive. It is your
64 * responsibility to keep a reference until you are done with the
65 * object.
66
67 * ## Typical usage
68 *
69 * In the simplest of cases, you can the following code to use
70 * `GtkFileChooserNative` to select a file for opening:
71 *
72 * ```c
73 * static void
74 * on_response (GtkNativeDialog *native,
75 * int response)
76 * {
77 * if (response == GTK_RESPONSE_ACCEPT)
78 * {
79 * GtkFileChooser *chooser = GTK_FILE_CHOOSER (native);
80 * GFile *file = gtk_file_chooser_get_file (chooser);
81 *
82 * open_file (file);
83 *
84 * g_object_unref (file);
85 * }
86 *
87 * g_object_unref (native);
88 * }
89 *
90 * // ...
91 * GtkFileChooserNative *native;
92 * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
93 *
94 * native = gtk_file_chooser_native_new ("Open File",
95 * parent_window,
96 * action,
97 * "_Open",
98 * "_Cancel");
99 *
100 * g_signal_connect (native, "response", G_CALLBACK (on_response), NULL);
101 * gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
102 * ```
103 *
104 * To use a `GtkFileChooserNative` for saving, you can use this:
105 *
106 * ```c
107 * static void
108 * on_response (GtkNativeDialog *native,
109 * int response)
110 * {
111 * if (response == GTK_RESPONSE_ACCEPT)
112 * {
113 * GtkFileChooser *chooser = GTK_FILE_CHOOSER (native);
114 * GFile *file = gtk_file_chooser_get_file (chooser);
115 *
116 * save_to_file (file);
117 *
118 * g_object_unref (file);
119 * }
120 *
121 * g_object_unref (native);
122 * }
123 *
124 * // ...
125 * GtkFileChooserNative *native;
126 * GtkFileChooser *chooser;
127 * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
128 *
129 * native = gtk_file_chooser_native_new ("Save File",
130 * parent_window,
131 * action,
132 * "_Save",
133 * "_Cancel");
134 * chooser = GTK_FILE_CHOOSER (native);
135 *
136 * if (user_edited_a_new_document)
137 * gtk_file_chooser_set_current_name (chooser, _("Untitled document"));
138 * else
139 * gtk_file_chooser_set_file (chooser, existing_file, NULL);
140 *
141 * g_signal_connect (native, "response", G_CALLBACK (on_response), NULL);
142 * gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
143 * ```
144 *
145 * For more information on how to best set up a file dialog,
146 * see the [class@Gtk.FileChooserDialog] documentation.
147 *
148 * ## Response Codes
149 *
150 * `GtkFileChooserNative` inherits from [class@Gtk.NativeDialog],
151 * which means it will return %GTK_RESPONSE_ACCEPT if the user accepted,
152 * and %GTK_RESPONSE_CANCEL if he pressed cancel. It can also return
153 * %GTK_RESPONSE_DELETE_EVENT if the window was unexpectedly closed.
154 *
155 * ## Differences from `GtkFileChooserDialog`
156 *
157 * There are a few things in the [iface@Gtk.FileChooser] interface that
158 * are not possible to use with `GtkFileChooserNative`, as such use would
159 * prohibit the use of a native dialog.
160 *
161 * No operations that change the dialog work while the dialog is visible.
162 * Set all the properties that are required before showing the dialog.
163 *
164 * ## Win32 details
165 *
166 * On windows the `IFileDialog` implementation (added in Windows Vista) is
167 * used. It supports many of the features that `GtkFileChooser` has, but
168 * there are some things it does not handle:
169 *
170 * * Any [class@Gtk.FileFilter] added using a mimetype
171 *
172 * If any of these features are used the regular `GtkFileChooserDialog`
173 * will be used in place of the native one.
174 *
175 * ## Portal details
176 *
177 * When the `org.freedesktop.portal.FileChooser` portal is available on
178 * the session bus, it is used to bring up an out-of-process file chooser.
179 * Depending on the kind of session the application is running in, this may
180 * or may not be a GTK file chooser.
181 *
182 * ## macOS details
183 *
184 * On macOS the `NSSavePanel` and `NSOpenPanel` classes are used to provide
185 * native file chooser dialogs. Some features provided by `GtkFileChooser`
186 * are not supported:
187 *
188 * * Shortcut folders.
189 */
190
191enum {
192 MODE_FALLBACK,
193 MODE_WIN32,
194 MODE_QUARTZ,
195 MODE_PORTAL,
196};
197
198enum {
199 PROP_0,
200 PROP_ACCEPT_LABEL,
201 PROP_CANCEL_LABEL,
202 LAST_ARG,
203};
204
205static GParamSpec *native_props[LAST_ARG] = { NULL, };
206
207static void _gtk_file_chooser_native_iface_init (GtkFileChooserIface *iface);
208
209G_DEFINE_TYPE_WITH_CODE (GtkFileChooserNative, gtk_file_chooser_native, GTK_TYPE_NATIVE_DIALOG,
210 G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
211 _gtk_file_chooser_native_iface_init))
212
213
214/**
215 * gtk_file_chooser_native_get_accept_label: (attributes org.gtk.Method.get_property=accept-label)
216 * @self: a `GtkFileChooserNative`
217 *
218 * Retrieves the custom label text for the accept button.
219 *
220 * Returns: (nullable): The custom label
221 */
222const char *
223gtk_file_chooser_native_get_accept_label (GtkFileChooserNative *self)
224{
225 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self), NULL);
226
227 return self->accept_label;
228}
229
230/**
231 * gtk_file_chooser_native_set_accept_label: (attributes org.gtk.Method.set_property=accept-label)
232 * @self: a `GtkFileChooserNative`
233 * @accept_label: (nullable): custom label
234 *
235 * Sets the custom label text for the accept button.
236 *
237 * If characters in @label are preceded by an underscore, they are
238 * underlined. If you need a literal underscore character in a label,
239 * use “__” (two underscores). The first underlined character represents
240 * a keyboard accelerator called a mnemonic.
241 *
242 * Pressing Alt and that key should activate the button.
243 */
244void
245gtk_file_chooser_native_set_accept_label (GtkFileChooserNative *self,
246 const char *accept_label)
247{
248 g_return_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self));
249
250 g_free (mem: self->accept_label);
251 self->accept_label = g_strdup (str: accept_label);
252
253 g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_ACCEPT_LABEL]);
254}
255
256/**
257 * gtk_file_chooser_native_get_cancel_label: (attributes org.gtk.Method.get_property=cancel-label)
258 * @self: a `GtkFileChooserNative`
259 *
260 * Retrieves the custom label text for the cancel button.
261 *
262 * Returns: (nullable): The custom label
263 */
264const char *
265gtk_file_chooser_native_get_cancel_label (GtkFileChooserNative *self)
266{
267 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self), NULL);
268
269 return self->cancel_label;
270}
271
272/**
273 * gtk_file_chooser_native_set_cancel_label: (attributes org.gtk.Method.set_property=cancel-label)
274 * @self: a `GtkFileChooserNative`
275 * @cancel_label: (nullable): custom label
276 *
277 * Sets the custom label text for the cancel button.
278 *
279 * If characters in @label are preceded by an underscore, they are
280 * underlined. If you need a literal underscore character in a label,
281 * use “__” (two underscores). The first underlined character represents
282 * a keyboard accelerator called a mnemonic.
283 *
284 * Pressing Alt and that key should activate the button.
285 */
286void
287gtk_file_chooser_native_set_cancel_label (GtkFileChooserNative *self,
288 const char *cancel_label)
289{
290 g_return_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self));
291
292 g_free (mem: self->cancel_label);
293 self->cancel_label = g_strdup (str: cancel_label);
294
295 g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_CANCEL_LABEL]);
296}
297
298static GtkFileChooserNativeChoice *
299find_choice (GtkFileChooserNative *self,
300 const char *id)
301{
302 GSList *l;
303
304 for (l = self->choices; l; l = l->next)
305 {
306 GtkFileChooserNativeChoice *choice = l->data;
307
308 if (strcmp (s1: choice->id, s2: id) == 0)
309 return choice;
310 }
311
312 return NULL;
313}
314
315static void
316gtk_file_chooser_native_choice_free (GtkFileChooserNativeChoice *choice)
317{
318 g_free (mem: choice->id);
319 g_free (mem: choice->label);
320 g_strfreev (str_array: choice->options);
321 g_strfreev (str_array: choice->option_labels);
322 g_free (mem: choice->selected);
323 g_free (mem: choice);
324}
325
326static void
327gtk_file_chooser_native_add_choice (GtkFileChooser *chooser,
328 const char *id,
329 const char *label,
330 const char **options,
331 const char **option_labels)
332{
333 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
334 GtkFileChooserNativeChoice *choice = find_choice (self, id);
335
336 if (choice != NULL)
337 {
338 g_warning ("Choice with id %s already added to %s %p", id, G_OBJECT_TYPE_NAME (self), self);
339 return;
340 }
341
342 g_assert ((options == NULL && option_labels == NULL) ||
343 g_strv_length ((char **)options) == g_strv_length ((char **)option_labels));
344
345 choice = g_new0 (GtkFileChooserNativeChoice, 1);
346 choice->id = g_strdup (str: id);
347 choice->label = g_strdup (str: label);
348 choice->options = g_strdupv (str_array: (char **)options);
349 choice->option_labels = g_strdupv (str_array: (char **)option_labels);
350
351 self->choices = g_slist_append (list: self->choices, data: choice);
352
353 gtk_file_chooser_add_choice (GTK_FILE_CHOOSER (self->dialog),
354 id, label, options, option_labels);
355}
356
357static void
358gtk_file_chooser_native_remove_choice (GtkFileChooser *chooser,
359 const char *id)
360{
361 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
362 GtkFileChooserNativeChoice *choice = find_choice (self, id);
363
364 if (choice == NULL)
365 {
366 g_warning ("No choice with id %s found in %s %p", id, G_OBJECT_TYPE_NAME (self), self);
367 return;
368 }
369
370 self->choices = g_slist_remove (list: self->choices, data: choice);
371
372 gtk_file_chooser_native_choice_free (choice);
373
374 gtk_file_chooser_remove_choice (GTK_FILE_CHOOSER (self->dialog), id);
375}
376
377static void
378gtk_file_chooser_native_set_choice (GtkFileChooser *chooser,
379 const char *id,
380 const char *selected)
381{
382 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
383 GtkFileChooserNativeChoice *choice = find_choice (self, id);
384
385 if (choice == NULL)
386 {
387 g_warning ("No choice with id %s found in %s %p", id, G_OBJECT_TYPE_NAME (self), self);
388 return;
389 }
390
391 if ((choice->options && !g_strv_contains (strv: (const char *const*)choice->options, str: selected)) ||
392 (!choice->options && !g_str_equal (v1: selected, v2: "true") && !g_str_equal (v1: selected, v2: "false")))
393 {
394 g_warning ("Not a valid option for %s: %s", id, selected);
395 return;
396 }
397
398 g_free (mem: choice->selected);
399 choice->selected = g_strdup (str: selected);
400
401 gtk_file_chooser_set_choice (GTK_FILE_CHOOSER (self->dialog), id, option: selected);
402}
403
404static const char *
405gtk_file_chooser_native_get_choice (GtkFileChooser *chooser,
406 const char *id)
407{
408 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
409 GtkFileChooserNativeChoice *choice = find_choice (self, id);
410
411 if (choice == NULL)
412 {
413 g_warning ("No choice with id %s found in %s %p", id, G_OBJECT_TYPE_NAME (self), self);
414 return NULL;
415 }
416
417 if (self->mode == MODE_FALLBACK)
418 return gtk_file_chooser_get_choice (GTK_FILE_CHOOSER (self->dialog), id);
419
420 return choice->selected;
421}
422
423static void
424gtk_file_chooser_native_set_property (GObject *object,
425 guint prop_id,
426 const GValue *value,
427 GParamSpec *pspec)
428
429{
430 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: object);
431
432 switch (prop_id)
433 {
434 case PROP_ACCEPT_LABEL:
435 gtk_file_chooser_native_set_accept_label (self, accept_label: g_value_get_string (value));
436 break;
437
438 case PROP_CANCEL_LABEL:
439 gtk_file_chooser_native_set_cancel_label (self, cancel_label: g_value_get_string (value));
440 break;
441
442 case GTK_FILE_CHOOSER_PROP_FILTER:
443 self->current_filter = g_value_get_object (value);
444 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (self->dialog), filter: self->current_filter);
445 g_object_notify (G_OBJECT (self), property_name: "filter");
446 break;
447
448 default:
449 g_object_set_property (G_OBJECT (self->dialog), property_name: pspec->name, value);
450 break;
451 }
452}
453
454static void
455gtk_file_chooser_native_get_property (GObject *object,
456 guint prop_id,
457 GValue *value,
458 GParamSpec *pspec)
459{
460 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: object);
461
462 switch (prop_id)
463 {
464 case PROP_ACCEPT_LABEL:
465 g_value_set_string (value, v_string: self->accept_label);
466 break;
467
468 case PROP_CANCEL_LABEL:
469 g_value_set_string (value, v_string: self->cancel_label);
470 break;
471
472 case GTK_FILE_CHOOSER_PROP_FILTER:
473 self->current_filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (self->dialog));
474 g_value_set_object (value, v_object: self->current_filter);
475 break;
476
477 default:
478 g_object_get_property (G_OBJECT (self->dialog), property_name: pspec->name, value);
479 break;
480 }
481}
482
483static void
484gtk_file_chooser_native_finalize (GObject *object)
485{
486 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: object);
487
488 g_clear_pointer (&self->current_name, g_free);
489 g_clear_object (&self->current_file);
490 g_clear_object (&self->current_folder);
491
492 g_clear_pointer (&self->accept_label, g_free);
493 g_clear_pointer (&self->cancel_label, g_free);
494 gtk_window_destroy (GTK_WINDOW (self->dialog));
495
496 g_slist_free_full (list: self->custom_files, free_func: g_object_unref);
497 g_slist_free_full (list: self->choices, free_func: (GDestroyNotify)gtk_file_chooser_native_choice_free);
498
499 G_OBJECT_CLASS (gtk_file_chooser_native_parent_class)->finalize (object);
500}
501
502static void
503gtk_file_chooser_native_init (GtkFileChooserNative *self)
504{
505 /* We always create a File chooser dialog and delegate all properties to it.
506 * This way we can reuse that store, plus we always have a dialog we can use
507 * in case something makes the native one not work (like the custom widgets) */
508 self->dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG, NULL);
509 self->cancel_button = gtk_dialog_add_button (GTK_DIALOG (self->dialog), _("_Cancel"), response_id: GTK_RESPONSE_CANCEL);
510 self->accept_button = gtk_dialog_add_button (GTK_DIALOG (self->dialog), _("_Open"), response_id: GTK_RESPONSE_ACCEPT);
511
512 gtk_dialog_set_default_response (GTK_DIALOG (self->dialog), response_id: GTK_RESPONSE_ACCEPT);
513 gtk_window_set_hide_on_close (GTK_WINDOW (self->dialog), TRUE);
514
515 /* This is used, instead of the standard delegate, to ensure that signals are not delegated. */
516 g_object_set_qdata (G_OBJECT (self), GTK_FILE_CHOOSER_DELEGATE_QUARK, data: self->dialog);
517}
518
519/**
520 * gtk_file_chooser_native_new:
521 * @title: (nullable): Title of the native
522 * @parent: (nullable): Transient parent of the native
523 * @action: Open or save mode for the dialog
524 * @accept_label: (nullable): text to go in the accept button, or %NULL for the default
525 * @cancel_label: (nullable): text to go in the cancel button, or %NULL for the default
526 *
527 * Creates a new `GtkFileChooserNative`.
528 *
529 * Returns: a new `GtkFileChooserNative`
530 */
531GtkFileChooserNative *
532gtk_file_chooser_native_new (const char *title,
533 GtkWindow *parent,
534 GtkFileChooserAction action,
535 const char *accept_label,
536 const char *cancel_label)
537{
538 GtkFileChooserNative *result;
539
540 result = g_object_new (GTK_TYPE_FILE_CHOOSER_NATIVE,
541 first_property_name: "title", title,
542 "action", action,
543 "transient-for", parent,
544 "accept-label", accept_label,
545 "cancel-label", cancel_label,
546 NULL);
547
548 return result;
549}
550
551static void
552dialog_response_cb (GtkDialog *dialog,
553 int response_id,
554 gpointer data)
555{
556 GtkFileChooserNative *self = data;
557
558 g_signal_handlers_disconnect_by_func (self->dialog, dialog_response_cb, self);
559 gtk_widget_hide (widget: self->dialog);
560
561 _gtk_native_dialog_emit_response (self: GTK_NATIVE_DIALOG (ptr: self), response_id);
562}
563
564static void
565show_dialog (GtkFileChooserNative *self)
566{
567 GtkFileChooserAction action;
568 const char *accept_label, *cancel_label;
569
570 action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self->dialog));
571
572 accept_label = self->accept_label;
573 if (accept_label == NULL)
574 accept_label = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? _("_Save") : _("_Open");
575
576 gtk_button_set_label (GTK_BUTTON (self->accept_button), label: accept_label);
577
578 cancel_label = self->cancel_label;
579 if (cancel_label == NULL)
580 cancel_label = _("_Cancel");
581
582 gtk_button_set_label (GTK_BUTTON (self->cancel_button), label: cancel_label);
583
584 gtk_window_set_title (GTK_WINDOW (self->dialog),
585 title: gtk_native_dialog_get_title (self: GTK_NATIVE_DIALOG (ptr: self)));
586
587 gtk_window_set_transient_for (GTK_WINDOW (self->dialog),
588 parent: gtk_native_dialog_get_transient_for (self: GTK_NATIVE_DIALOG (ptr: self)));
589
590 gtk_window_set_modal (GTK_WINDOW (self->dialog),
591 modal: gtk_native_dialog_get_modal (self: GTK_NATIVE_DIALOG (ptr: self)));
592
593 g_signal_connect (self->dialog,
594 "response",
595 G_CALLBACK (dialog_response_cb),
596 self);
597
598 gtk_window_present (GTK_WINDOW (self->dialog));
599}
600
601static void
602hide_dialog (GtkFileChooserNative *self)
603{
604 g_signal_handlers_disconnect_by_func (self->dialog, dialog_response_cb, self);
605 gtk_widget_hide (widget: self->dialog);
606}
607
608static gboolean
609gtk_file_chooser_native_set_current_folder (GtkFileChooser *chooser,
610 GFile *file,
611 GError **error)
612{
613 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
614 gboolean res;
615
616 res = gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (self->dialog),
617 file, error);
618
619
620 if (res)
621 {
622 g_set_object (&self->current_folder, file);
623
624 g_clear_object (&self->current_file);
625 }
626
627 return res;
628}
629
630static gboolean
631gtk_file_chooser_native_select_file (GtkFileChooser *chooser,
632 GFile *file,
633 GError **error)
634{
635 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
636 gboolean res;
637
638 res = gtk_file_chooser_select_file (GTK_FILE_CHOOSER (self->dialog),
639 file, error);
640
641 if (res)
642 {
643 g_set_object (&self->current_file, file);
644
645 g_clear_object (&self->current_folder);
646 g_clear_pointer (&self->current_name, g_free);
647 }
648
649 return res;
650}
651
652static void
653gtk_file_chooser_native_set_current_name (GtkFileChooser *chooser,
654 const char *name)
655{
656 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
657
658 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (self->dialog), name);
659
660 g_clear_pointer (&self->current_name, g_free);
661 self->current_name = g_strdup (str: name);
662
663 g_clear_object (&self->current_file);
664}
665
666static GListModel *
667gtk_file_chooser_native_get_files (GtkFileChooser *chooser)
668{
669 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: chooser);
670
671 switch (self->mode)
672 {
673 case MODE_PORTAL:
674 case MODE_WIN32:
675 case MODE_QUARTZ:
676 {
677 GListStore *store;
678 GSList *l;
679
680 store = g_list_store_new (G_TYPE_FILE);
681 for (l = self->custom_files; l; l = l->next)
682 g_list_store_append (store, item: l->data);
683
684 return G_LIST_MODEL (ptr: store);
685 }
686
687 case MODE_FALLBACK:
688 default:
689 return gtk_file_chooser_get_files (GTK_FILE_CHOOSER (self->dialog));
690 }
691}
692
693static void
694portal_error_handler (GtkFileChooserNative *self)
695{
696 self->mode = MODE_FALLBACK;
697 show_dialog (self);
698}
699
700static void
701gtk_file_chooser_native_show (GtkNativeDialog *native)
702{
703 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: native);
704
705 self->mode = MODE_FALLBACK;
706
707#ifdef GDK_WINDOWING_WIN32
708 if (gtk_file_chooser_native_win32_show (self))
709 self->mode = MODE_WIN32;
710#endif
711
712#ifdef GDK_WINDOWING_MACOS
713 if (gtk_file_chooser_native_quartz_show (self))
714 self->mode = MODE_QUARTZ;
715#endif
716
717 if (self->mode == MODE_FALLBACK &&
718 gtk_file_chooser_native_portal_show (self, error_handler: portal_error_handler))
719 self->mode = MODE_PORTAL;
720
721 if (self->mode == MODE_FALLBACK)
722 show_dialog (self);
723}
724
725static void
726gtk_file_chooser_native_hide (GtkNativeDialog *native)
727{
728 GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (ptr: native);
729
730 switch (self->mode)
731 {
732 case MODE_FALLBACK:
733 hide_dialog (self);
734 break;
735 case MODE_WIN32:
736#ifdef GDK_WINDOWING_WIN32
737 gtk_file_chooser_native_win32_hide (self);
738#endif
739 break;
740 case MODE_QUARTZ:
741#ifdef GDK_WINDOWING_MACOS
742 gtk_file_chooser_native_quartz_hide (self);
743#endif
744 break;
745 case MODE_PORTAL:
746 gtk_file_chooser_native_portal_hide (self);
747 break;
748 default:
749 break;
750 }
751}
752
753static void
754gtk_file_chooser_native_class_init (GtkFileChooserNativeClass *class)
755{
756 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
757 GtkNativeDialogClass *native_dialog_class = GTK_NATIVE_DIALOG_CLASS (ptr: class);
758
759 gobject_class->finalize = gtk_file_chooser_native_finalize;
760 gobject_class->set_property = gtk_file_chooser_native_set_property;
761 gobject_class->get_property = gtk_file_chooser_native_get_property;
762
763 native_dialog_class->show = gtk_file_chooser_native_show;
764 native_dialog_class->hide = gtk_file_chooser_native_hide;
765
766 _gtk_file_chooser_install_properties (klass: gobject_class);
767
768 /**
769 * GtkFileChooserNative:accept-label: (attributes org.gtk.Property.get=gtk_file_chooser_native_get_accept_label org.gtk.Property.set=gtk_file_chooser_native_set_accept_label)
770 *
771 * The text used for the label on the accept button in the dialog, or
772 * %NULL to use the default text.
773 */
774 native_props[PROP_ACCEPT_LABEL] =
775 g_param_spec_string (name: "accept-label",
776 P_("Accept label"),
777 P_("The label on the accept button"),
778 NULL,
779 GTK_PARAM_READWRITE);
780
781 /**
782 * GtkFileChooserNative:cancel-label: (attributes org.gtk.Property.get=gtk_file_chooser_native_get_cancel_label org.gtk.Property.set=gtk_file_chooser_native_set_cancel_label)
783 *
784 * The text used for the label on the cancel button in the dialog, or
785 * %NULL to use the default text.
786 */
787 native_props[PROP_CANCEL_LABEL] =
788 g_param_spec_string (name: "cancel-label",
789 P_("Cancel label"),
790 P_("The label on the cancel button"),
791 NULL,
792 GTK_PARAM_READWRITE);
793
794 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_ARG, pspecs: native_props);
795}
796
797static void
798_gtk_file_chooser_native_iface_init (GtkFileChooserIface *iface)
799{
800 _gtk_file_chooser_delegate_iface_init (iface);
801 iface->select_file = gtk_file_chooser_native_select_file;
802 iface->set_current_name = gtk_file_chooser_native_set_current_name;
803 iface->set_current_folder = gtk_file_chooser_native_set_current_folder;
804 iface->get_files = gtk_file_chooser_native_get_files;
805 iface->add_choice = gtk_file_chooser_native_add_choice;
806 iface->remove_choice = gtk_file_chooser_native_remove_choice;
807 iface->set_choice = gtk_file_chooser_native_set_choice;
808 iface->get_choice = gtk_file_chooser_native_get_choice;
809}
810

source code of gtk/gtk/gtkfilechoosernative.c