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