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 "gtknativedialogprivate.h" |
23 | |
24 | #include "gtkprivate.h" |
25 | #include "gtkfilechooserdialog.h" |
26 | #include "gtkfilechooserprivate.h" |
27 | #include "gtkfilechooserwidget.h" |
28 | #include "gtkfilechooserwidgetprivate.h" |
29 | #include "gtkfilechooserutils.h" |
30 | #include "gtksizerequest.h" |
31 | #include "gtktypebuiltins.h" |
32 | #include "gtkintl.h" |
33 | #include "gtksettings.h" |
34 | #include "gtktogglebutton.h" |
35 | #include "gtkheaderbar.h" |
36 | #include "gtklabel.h" |
37 | |
38 | /** |
39 | * GtkNativeDialog: |
40 | * |
41 | * Native dialogs are platform dialogs that don't use `GtkDialog`. |
42 | * |
43 | * They are used in order to integrate better with a platform, by |
44 | * looking the same as other native applications and supporting |
45 | * platform specific features. |
46 | * |
47 | * The [class@Gtk.Dialog] functions cannot be used on such objects, |
48 | * but we need a similar API in order to drive them. The `GtkNativeDialog` |
49 | * object is an API that allows you to do this. It allows you to set |
50 | * various common properties on the dialog, as well as show and hide |
51 | * it and get a [signal@Gtk.NativeDialog::response] signal when the user |
52 | * finished with the dialog. |
53 | * |
54 | * Note that unlike `GtkDialog`, `GtkNativeDialog` objects are not |
55 | * toplevel widgets, and GTK does not keep them alive. It is your |
56 | * responsibility to keep a reference until you are done with the |
57 | * object. |
58 | */ |
59 | |
60 | typedef struct _GtkNativeDialogPrivate GtkNativeDialogPrivate; |
61 | |
62 | struct _GtkNativeDialogPrivate |
63 | { |
64 | GtkWindow *transient_for; |
65 | char *title; |
66 | |
67 | guint visible : 1; |
68 | guint modal : 1; |
69 | }; |
70 | |
71 | enum { |
72 | PROP_0, |
73 | PROP_TITLE, |
74 | PROP_VISIBLE, |
75 | PROP_MODAL, |
76 | PROP_TRANSIENT_FOR, |
77 | |
78 | LAST_ARG, |
79 | }; |
80 | |
81 | enum { |
82 | RESPONSE, |
83 | |
84 | LAST_SIGNAL |
85 | }; |
86 | |
87 | static GParamSpec *native_props[LAST_ARG] = { NULL, }; |
88 | static guint native_signals[LAST_SIGNAL]; |
89 | |
90 | G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkNativeDialog, gtk_native_dialog, G_TYPE_OBJECT, |
91 | G_ADD_PRIVATE (GtkNativeDialog)) |
92 | |
93 | static void |
94 | gtk_native_dialog_set_property (GObject *object, |
95 | guint prop_id, |
96 | const GValue *value, |
97 | GParamSpec *pspec) |
98 | |
99 | { |
100 | GtkNativeDialog *self = GTK_NATIVE_DIALOG (ptr: object); |
101 | |
102 | switch (prop_id) |
103 | { |
104 | case PROP_TITLE: |
105 | gtk_native_dialog_set_title (self, title: g_value_get_string (value)); |
106 | break; |
107 | |
108 | case PROP_MODAL: |
109 | gtk_native_dialog_set_modal (self, modal: g_value_get_boolean (value)); |
110 | break; |
111 | |
112 | case PROP_VISIBLE: |
113 | if (g_value_get_boolean (value)) |
114 | gtk_native_dialog_show (self); |
115 | else |
116 | gtk_native_dialog_hide (self); |
117 | break; |
118 | |
119 | case PROP_TRANSIENT_FOR: |
120 | gtk_native_dialog_set_transient_for (self, parent: g_value_get_object (value)); |
121 | break; |
122 | |
123 | default: |
124 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
125 | break; |
126 | } |
127 | } |
128 | |
129 | static void |
130 | gtk_native_dialog_get_property (GObject *object, |
131 | guint prop_id, |
132 | GValue *value, |
133 | GParamSpec *pspec) |
134 | { |
135 | GtkNativeDialog *self = GTK_NATIVE_DIALOG (ptr: object); |
136 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
137 | |
138 | switch (prop_id) |
139 | { |
140 | case PROP_TITLE: |
141 | g_value_set_string (value, v_string: priv->title); |
142 | break; |
143 | |
144 | case PROP_MODAL: |
145 | g_value_set_boolean (value, v_boolean: priv->modal); |
146 | break; |
147 | |
148 | case PROP_VISIBLE: |
149 | g_value_set_boolean (value, v_boolean: priv->visible); |
150 | break; |
151 | |
152 | case PROP_TRANSIENT_FOR: |
153 | g_value_set_object (value, v_object: priv->transient_for); |
154 | break; |
155 | |
156 | default: |
157 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
158 | break; |
159 | } |
160 | } |
161 | |
162 | static void parent_destroyed (GtkWidget *parent, |
163 | GtkNativeDialog *self); |
164 | |
165 | static void |
166 | gtk_native_dialog_dispose (GObject *object) |
167 | { |
168 | GtkNativeDialog *self = GTK_NATIVE_DIALOG (ptr: object); |
169 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
170 | |
171 | if (priv->transient_for) |
172 | { |
173 | g_signal_handlers_disconnect_by_func (priv->transient_for, parent_destroyed, self); |
174 | priv->transient_for = NULL; |
175 | } |
176 | |
177 | if (priv->visible) |
178 | gtk_native_dialog_hide (self); |
179 | |
180 | G_OBJECT_CLASS (gtk_native_dialog_parent_class)->dispose (object); |
181 | } |
182 | |
183 | static void |
184 | gtk_native_dialog_finalize (GObject *object) |
185 | { |
186 | GtkNativeDialog *self = GTK_NATIVE_DIALOG (ptr: object); |
187 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
188 | |
189 | g_clear_pointer (&priv->title, g_free); |
190 | g_clear_object (&priv->transient_for); |
191 | |
192 | G_OBJECT_CLASS (gtk_native_dialog_parent_class)->finalize (object); |
193 | } |
194 | |
195 | static void |
196 | gtk_native_dialog_class_init (GtkNativeDialogClass *class) |
197 | { |
198 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
199 | |
200 | gobject_class->set_property = gtk_native_dialog_set_property; |
201 | gobject_class->get_property = gtk_native_dialog_get_property; |
202 | gobject_class->finalize = gtk_native_dialog_finalize; |
203 | gobject_class->dispose = gtk_native_dialog_dispose; |
204 | |
205 | /** |
206 | * GtkNativeDialog:title: (attributes org.gtk.Property.get=gtk_native_dialog_get_title org.gtk.Property.set=gtk_native_dialog_set_title) |
207 | * |
208 | * The title of the dialog window |
209 | */ |
210 | native_props[PROP_TITLE] = |
211 | g_param_spec_string (name: "title" , |
212 | P_("Dialog Title" ), |
213 | P_("The title of the file chooser dialog" ), |
214 | NULL, |
215 | GTK_PARAM_READWRITE); |
216 | |
217 | /** |
218 | * GtkNativeDialog:modal: (attributes org.gtk.Property.get=gtk_native_dialog_get_modal org.gtk.Property.set=gtk_native_dialog_set_modal) |
219 | * |
220 | * Whether the window should be modal with respect to its transient parent. |
221 | */ |
222 | native_props[PROP_MODAL] = |
223 | g_param_spec_boolean (name: "modal" , |
224 | P_("Modal" ), |
225 | P_("If TRUE, the dialog is modal (other windows are not usable while this one is up)" ), |
226 | FALSE, |
227 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
228 | |
229 | /** |
230 | * GtkNativeDialog:visible: (attributes org.gtk.Property.get=gtk_native_dialog_get_visible) |
231 | * |
232 | * Whether the window is currently visible. |
233 | */ |
234 | native_props[PROP_VISIBLE] = |
235 | g_param_spec_boolean (name: "visible" , |
236 | P_("Visible" ), |
237 | P_("Whether the dialog is currently visible" ), |
238 | FALSE, |
239 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
240 | |
241 | /** |
242 | * GtkNativeDialog:transient-for: (attributes org.gtk.Property.get=gtk_native_dialog_get_transient_for org.gtk.Property.set=gtk_native_dialog_set_transient_for) |
243 | * |
244 | * The transient parent of the dialog, or %NULL for none. |
245 | */ |
246 | native_props[PROP_TRANSIENT_FOR] = |
247 | g_param_spec_object (name: "transient-for" , |
248 | P_("Transient for Window" ), |
249 | P_("The transient parent of the dialog" ), |
250 | GTK_TYPE_WINDOW, |
251 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY); |
252 | |
253 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_ARG, pspecs: native_props); |
254 | |
255 | /** |
256 | * GtkNativeDialog::response: |
257 | * @self: the object on which the signal is emitted |
258 | * @response_id: the response ID |
259 | * |
260 | * Emitted when the user responds to the dialog. |
261 | * |
262 | * When this is called the dialog has been hidden. |
263 | * |
264 | * If you call [method@Gtk.NativeDialog.hide] before the user |
265 | * responds to the dialog this signal will not be emitted. |
266 | */ |
267 | native_signals[RESPONSE] = |
268 | g_signal_new (I_("response" ), |
269 | G_OBJECT_CLASS_TYPE (class), |
270 | signal_flags: G_SIGNAL_RUN_LAST, |
271 | G_STRUCT_OFFSET (GtkNativeDialogClass, response), |
272 | NULL, NULL, |
273 | NULL, |
274 | G_TYPE_NONE, n_params: 1, |
275 | G_TYPE_INT); |
276 | } |
277 | |
278 | static void |
279 | gtk_native_dialog_init (GtkNativeDialog *self) |
280 | { |
281 | } |
282 | |
283 | /** |
284 | * gtk_native_dialog_show: |
285 | * @self: a `GtkNativeDialog` |
286 | * |
287 | * Shows the dialog on the display. |
288 | * |
289 | * When the user accepts the state of the dialog the dialog will |
290 | * be automatically hidden and the [signal@Gtk.NativeDialog::response] |
291 | * signal will be emitted. |
292 | * |
293 | * Multiple calls while the dialog is visible will be ignored. |
294 | */ |
295 | void |
296 | gtk_native_dialog_show (GtkNativeDialog *self) |
297 | { |
298 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
299 | GtkNativeDialogClass *klass; |
300 | |
301 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
302 | |
303 | if (priv->visible) |
304 | return; |
305 | |
306 | klass = GTK_NATIVE_DIALOG_GET_CLASS (ptr: self); |
307 | |
308 | g_return_if_fail (klass->show != NULL); |
309 | |
310 | klass->show (self); |
311 | |
312 | priv->visible = TRUE; |
313 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_VISIBLE]); |
314 | } |
315 | |
316 | /** |
317 | * gtk_native_dialog_hide: |
318 | * @self: a `GtkNativeDialog` |
319 | * |
320 | * Hides the dialog if it is visible, aborting any interaction. |
321 | * |
322 | * Once this is called the [signal@Gtk.NativeDialog::response] signal |
323 | * will *not* be emitted until after the next call to |
324 | * [method@Gtk.NativeDialog.show]. |
325 | * |
326 | * If the dialog is not visible this does nothing. |
327 | */ |
328 | void |
329 | gtk_native_dialog_hide (GtkNativeDialog *self) |
330 | { |
331 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
332 | GtkNativeDialogClass *klass; |
333 | |
334 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
335 | |
336 | if (!priv->visible) |
337 | return; |
338 | |
339 | priv->visible = FALSE; |
340 | |
341 | klass = GTK_NATIVE_DIALOG_GET_CLASS (ptr: self); |
342 | |
343 | g_return_if_fail (klass->hide != NULL); |
344 | |
345 | klass->hide (self); |
346 | |
347 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_VISIBLE]); |
348 | } |
349 | |
350 | /** |
351 | * gtk_native_dialog_destroy: |
352 | * @self: a `GtkNativeDialog` |
353 | * |
354 | * Destroys a dialog. |
355 | * |
356 | * When a dialog is destroyed, it will break any references it holds |
357 | * to other objects. |
358 | * |
359 | * If it is visible it will be hidden and any underlying window system |
360 | * resources will be destroyed. |
361 | * |
362 | * Note that this does not release any reference to the object (as opposed |
363 | * to destroying a `GtkWindow`) because there is no reference from the |
364 | * windowing system to the `GtkNativeDialog`. |
365 | */ |
366 | void |
367 | gtk_native_dialog_destroy (GtkNativeDialog *self) |
368 | { |
369 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
370 | |
371 | g_object_run_dispose (G_OBJECT (self)); |
372 | } |
373 | |
374 | void |
375 | _gtk_native_dialog_emit_response (GtkNativeDialog *self, |
376 | int response_id) |
377 | { |
378 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
379 | priv->visible = FALSE; |
380 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_VISIBLE]); |
381 | |
382 | g_signal_emit (instance: self, signal_id: native_signals[RESPONSE], detail: 0, response_id); |
383 | } |
384 | |
385 | /** |
386 | * gtk_native_dialog_get_visible: (attributes org.gtk.Method.get_property=visible) |
387 | * @self: a `GtkNativeDialog` |
388 | * |
389 | * Determines whether the dialog is visible. |
390 | * |
391 | * Returns: %TRUE if the dialog is visible |
392 | */ |
393 | gboolean |
394 | gtk_native_dialog_get_visible (GtkNativeDialog *self) |
395 | { |
396 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
397 | |
398 | g_return_val_if_fail (GTK_IS_NATIVE_DIALOG (self), FALSE); |
399 | |
400 | return priv->visible; |
401 | } |
402 | |
403 | /** |
404 | * gtk_native_dialog_set_modal: (attributes org.gtk.Method.set_property=modal) |
405 | * @self: a `GtkNativeDialog` |
406 | * @modal: whether the window is modal |
407 | * |
408 | * Sets a dialog modal or non-modal. |
409 | * |
410 | * Modal dialogs prevent interaction with other windows in the same |
411 | * application. To keep modal dialogs on top of main application |
412 | * windows, use [method@Gtk.NativeDialog.set_transient_for] to make |
413 | * the dialog transient for the parent; most window managers will |
414 | * then disallow lowering the dialog below the parent. |
415 | */ |
416 | void |
417 | gtk_native_dialog_set_modal (GtkNativeDialog *self, |
418 | gboolean modal) |
419 | { |
420 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
421 | |
422 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
423 | |
424 | modal = modal != FALSE; |
425 | |
426 | if (priv->modal == modal) |
427 | return; |
428 | |
429 | priv->modal = modal; |
430 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_MODAL]); |
431 | } |
432 | |
433 | /** |
434 | * gtk_native_dialog_get_modal: (attributes org.gtk.Method.get_property=modal) |
435 | * @self: a `GtkNativeDialog` |
436 | * |
437 | * Returns whether the dialog is modal. |
438 | * |
439 | * Returns: %TRUE if the dialog is set to be modal |
440 | */ |
441 | gboolean |
442 | gtk_native_dialog_get_modal (GtkNativeDialog *self) |
443 | { |
444 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
445 | |
446 | g_return_val_if_fail (GTK_IS_NATIVE_DIALOG (self), FALSE); |
447 | |
448 | return priv->modal; |
449 | } |
450 | |
451 | /** |
452 | * gtk_native_dialog_set_title: (attributes org.gtk.Method.set_property=title) |
453 | * @self: a `GtkNativeDialog` |
454 | * @title: title of the dialog |
455 | * |
456 | * Sets the title of the `GtkNativeDialog.` |
457 | */ |
458 | void |
459 | gtk_native_dialog_set_title (GtkNativeDialog *self, |
460 | const char *title) |
461 | { |
462 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
463 | |
464 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
465 | |
466 | g_free (mem: priv->title); |
467 | priv->title = g_strdup (str: title); |
468 | |
469 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_TITLE]); |
470 | } |
471 | |
472 | /** |
473 | * gtk_native_dialog_get_title: (attributes org.gtk.Method.get_property=title) |
474 | * @self: a `GtkNativeDialog` |
475 | * |
476 | * Gets the title of the `GtkNativeDialog`. |
477 | * |
478 | * Returns: (nullable): the title of the dialog, or %NULL if none has |
479 | * been set explicitly. The returned string is owned by the widget |
480 | * and must not be modified or freed. |
481 | */ |
482 | const char * |
483 | gtk_native_dialog_get_title (GtkNativeDialog *self) |
484 | { |
485 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
486 | |
487 | g_return_val_if_fail (GTK_IS_NATIVE_DIALOG (self), NULL); |
488 | |
489 | return priv->title; |
490 | } |
491 | |
492 | static void |
493 | parent_destroyed (GtkWidget *parent, |
494 | GtkNativeDialog *self) |
495 | { |
496 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
497 | |
498 | priv->transient_for = NULL; |
499 | } |
500 | |
501 | /** |
502 | * gtk_native_dialog_set_transient_for: (attributes org.gtk.Method.set_property=transient-for) |
503 | * @self: a `GtkNativeDialog` |
504 | * @parent: (nullable): parent window |
505 | * |
506 | * Dialog windows should be set transient for the main application |
507 | * window they were spawned from. |
508 | * |
509 | * This allows window managers to e.g. keep the dialog on top of the |
510 | * main window, or center the dialog over the main window. |
511 | * |
512 | * Passing %NULL for @parent unsets the current transient window. |
513 | */ |
514 | void |
515 | gtk_native_dialog_set_transient_for (GtkNativeDialog *self, |
516 | GtkWindow *parent) |
517 | { |
518 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
519 | |
520 | g_return_if_fail (GTK_IS_NATIVE_DIALOG (self)); |
521 | |
522 | if (parent == priv->transient_for) |
523 | return; |
524 | |
525 | if (priv->transient_for) |
526 | g_signal_handlers_disconnect_by_func (priv->transient_for, parent_destroyed, self); |
527 | |
528 | priv->transient_for = parent; |
529 | |
530 | if (parent) |
531 | g_signal_connect (parent, "destroy" , G_CALLBACK (parent_destroyed), self); |
532 | |
533 | g_object_notify_by_pspec (G_OBJECT (self), pspec: native_props[PROP_TRANSIENT_FOR]); |
534 | } |
535 | |
536 | /** |
537 | * gtk_native_dialog_get_transient_for: (attributes org.gtk.Method.get_property=transient-for) |
538 | * @self: a `GtkNativeDialog` |
539 | * |
540 | * Fetches the transient parent for this window. |
541 | * |
542 | * Returns: (nullable) (transfer none): the transient parent for this window, |
543 | * or %NULL if no transient parent has been set. |
544 | */ |
545 | GtkWindow * |
546 | gtk_native_dialog_get_transient_for (GtkNativeDialog *self) |
547 | { |
548 | GtkNativeDialogPrivate *priv = gtk_native_dialog_get_instance_private (self); |
549 | |
550 | g_return_val_if_fail (GTK_IS_NATIVE_DIALOG (self), NULL); |
551 | |
552 | return priv->transient_for; |
553 | } |
554 | |