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
60typedef struct _GtkNativeDialogPrivate GtkNativeDialogPrivate;
61
62struct _GtkNativeDialogPrivate
63{
64 GtkWindow *transient_for;
65 char *title;
66
67 guint visible : 1;
68 guint modal : 1;
69};
70
71enum {
72 PROP_0,
73 PROP_TITLE,
74 PROP_VISIBLE,
75 PROP_MODAL,
76 PROP_TRANSIENT_FOR,
77
78 LAST_ARG,
79};
80
81enum {
82 RESPONSE,
83
84 LAST_SIGNAL
85};
86
87static GParamSpec *native_props[LAST_ARG] = { NULL, };
88static guint native_signals[LAST_SIGNAL];
89
90G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkNativeDialog, gtk_native_dialog, G_TYPE_OBJECT,
91 G_ADD_PRIVATE (GtkNativeDialog))
92
93static void
94gtk_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
129static void
130gtk_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
162static void parent_destroyed (GtkWidget *parent,
163 GtkNativeDialog *self);
164
165static void
166gtk_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
183static void
184gtk_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
195static void
196gtk_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
278static void
279gtk_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 */
295void
296gtk_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 */
328void
329gtk_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 */
366void
367gtk_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
374void
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 */
393gboolean
394gtk_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 */
416void
417gtk_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 */
441gboolean
442gtk_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 */
458void
459gtk_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 */
482const char *
483gtk_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
492static void
493parent_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 */
514void
515gtk_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 */
545GtkWindow *
546gtk_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

source code of gtk/gtk/gtknativedialog.c