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 | |
191 | enum { |
192 | MODE_FALLBACK, |
193 | MODE_WIN32, |
194 | MODE_QUARTZ, |
195 | MODE_PORTAL, |
196 | }; |
197 | |
198 | enum { |
199 | PROP_0, |
200 | PROP_ACCEPT_LABEL, |
201 | PROP_CANCEL_LABEL, |
202 | LAST_ARG, |
203 | }; |
204 | |
205 | static GParamSpec *native_props[LAST_ARG] = { NULL, }; |
206 | |
207 | static void _gtk_file_chooser_native_iface_init (GtkFileChooserIface *iface); |
208 | |
209 | G_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 | */ |
222 | const char * |
223 | gtk_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 | */ |
244 | void |
245 | gtk_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 | */ |
264 | const char * |
265 | gtk_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 | */ |
286 | void |
287 | gtk_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 | |
298 | static GtkFileChooserNativeChoice * |
299 | find_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 | |
315 | static void |
316 | gtk_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 | |
326 | static void |
327 | gtk_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 | |
357 | static void |
358 | gtk_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 | |
377 | static void |
378 | gtk_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 | |
404 | static const char * |
405 | gtk_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 | |
423 | static void |
424 | gtk_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 | |
454 | static void |
455 | gtk_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 | |
483 | static void |
484 | gtk_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 | |
502 | static void |
503 | gtk_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 | */ |
531 | GtkFileChooserNative * |
532 | gtk_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 | |
551 | static void |
552 | dialog_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 | |
564 | static void |
565 | show_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 | |
601 | static void |
602 | hide_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 | |
608 | static gboolean |
609 | gtk_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 | |
630 | static gboolean |
631 | gtk_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 | |
652 | static void |
653 | gtk_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 | |
666 | static GListModel * |
667 | gtk_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 | |
693 | static void |
694 | portal_error_handler (GtkFileChooserNative *self) |
695 | { |
696 | self->mode = MODE_FALLBACK; |
697 | show_dialog (self); |
698 | } |
699 | |
700 | static void |
701 | gtk_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 | |
725 | static void |
726 | gtk_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 | |
753 | static void |
754 | gtk_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 | |
797 | static 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 | |