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