1/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2/* GTK - The GIMP Toolkit
3 * gtkfilechooserdialog.c: File selector dialog
4 * Copyright (C) 2003, 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 "gtkfilechooserdialog.h"
23
24#include "gtkfilechooserprivate.h"
25#include "gtkfilechooserwidget.h"
26#include "gtkfilechooserwidgetprivate.h"
27#include "gtkfilechooserutils.h"
28#include "gtksizerequest.h"
29#include "gtktypebuiltins.h"
30#include "gtkintl.h"
31#include "gtksettings.h"
32#include "gtktogglebutton.h"
33#include "gtkheaderbar.h"
34#include "gtkdialogprivate.h"
35#include "gtklabel.h"
36#include "gtkfilechooserentry.h"
37#include "gtkbox.h"
38
39#include <stdarg.h>
40
41
42/**
43 * GtkFileChooserDialog:
44 *
45 * `GtkFileChooserDialog` is a dialog suitable for use with
46 * “File Open” or “File Save” commands.
47 *
48 * ![An example GtkFileChooserDialog](filechooser.png)
49 *
50 * This widget works by putting a [class@Gtk.FileChooserWidget]
51 * inside a [class@Gtk.Dialog]. It exposes the [iface@Gtk.FileChooser]
52 * interface, so you can use all of the [iface@Gtk.FileChooser] functions
53 * on the file chooser dialog as well as those for [class@Gtk.Dialog].
54 *
55 * Note that `GtkFileChooserDialog` does not have any methods of its
56 * own. Instead, you should use the functions that work on a
57 * [iface@Gtk.FileChooser].
58 *
59 * If you want to integrate well with the platform you should use the
60 * [class@Gtk.FileChooserNative] API, which will use a platform-specific
61 * dialog if available and fall back to `GtkFileChooserDialog`
62 * otherwise.
63 *
64 * ## Typical usage
65 *
66 * In the simplest of cases, you can the following code to use
67 * `GtkFileChooserDialog` to select a file for opening:
68 *
69 * ```c
70 * static void
71 * on_open_response (GtkDialog *dialog,
72 * int response)
73 * {
74 * if (response == GTK_RESPONSE_ACCEPT)
75 * {
76 * GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
77 *
78 * g_autoptr(GFile) file = gtk_file_chooser_get_file (chooser);
79 *
80 * open_file (file);
81 * }
82 *
83 * gtk_window_destroy (GTK_WINDOW (dialog));
84 * }
85 *
86 * // ...
87 * GtkWidget *dialog;
88 * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
89 *
90 * dialog = gtk_file_chooser_dialog_new ("Open File",
91 * parent_window,
92 * action,
93 * _("_Cancel"),
94 * GTK_RESPONSE_CANCEL,
95 * _("_Open"),
96 * GTK_RESPONSE_ACCEPT,
97 * NULL);
98 *
99 * gtk_widget_show (dialog);
100 *
101 * g_signal_connect (dialog, "response",
102 * G_CALLBACK (on_open_response),
103 * NULL);
104 * ```
105 *
106 * To use a dialog for saving, you can use this:
107 *
108 * ```c
109 * static void
110 * on_save_response (GtkDialog *dialog,
111 * int response)
112 * {
113 * if (response == GTK_RESPONSE_ACCEPT)
114 * {
115 * GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
116 *
117 * g_autoptr(GFile) file = gtk_file_chooser_get_file (chooser);
118 *
119 * save_to_file (file);
120 * }
121 *
122 * gtk_window_destroy (GTK_WINDOW (dialog));
123 * }
124 *
125 * // ...
126 * GtkWidget *dialog;
127 * GtkFileChooser *chooser;
128 * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
129 *
130 * dialog = gtk_file_chooser_dialog_new ("Save File",
131 * parent_window,
132 * action,
133 * _("_Cancel"),
134 * GTK_RESPONSE_CANCEL,
135 * _("_Save"),
136 * GTK_RESPONSE_ACCEPT,
137 * NULL);
138 * chooser = GTK_FILE_CHOOSER (dialog);
139 *
140 * if (user_edited_a_new_document)
141 * gtk_file_chooser_set_current_name (chooser, _("Untitled document"));
142 * else
143 * gtk_file_chooser_set_file (chooser, existing_filename);
144 *
145 * gtk_widget_show (dialog);
146 *
147 * g_signal_connect (dialog, "response",
148 * G_CALLBACK (on_save_response),
149 * NULL);
150 * ```
151 *
152 * ## Setting up a file chooser dialog
153 *
154 * There are various cases in which you may need to use a `GtkFileChooserDialog`:
155 *
156 * - To select a file for opening, use %GTK_FILE_CHOOSER_ACTION_OPEN.
157 *
158 * - To save a file for the first time, use %GTK_FILE_CHOOSER_ACTION_SAVE,
159 * and suggest a name such as “Untitled” with
160 * [method@Gtk.FileChooser.set_current_name].
161 *
162 * - To save a file under a different name, use %GTK_FILE_CHOOSER_ACTION_SAVE,
163 * and set the existing file with [method@Gtk.FileChooser.set_file].
164 *
165 * - To choose a folder instead of a filem use %GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER.
166 *
167 * In general, you should only cause the file chooser to show a specific
168 * folder when it is appropriate to use [method@Gtk.FileChooser.set_file],
169 * i.e. when you are doing a “Save As” command and you already have a file
170 * saved somewhere.
171
172 * ## Response Codes
173 *
174 * `GtkFileChooserDialog` inherits from [class@Gtk.Dialog], so buttons that
175 * go in its action area have response codes such as %GTK_RESPONSE_ACCEPT and
176 * %GTK_RESPONSE_CANCEL. For example, you could call
177 * [ctor@Gtk.FileChooserDialog.new] as follows:
178 *
179 * ```c
180 * GtkWidget *dialog;
181 * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
182 *
183 * dialog = gtk_file_chooser_dialog_new ("Open File",
184 * parent_window,
185 * action,
186 * _("_Cancel"),
187 * GTK_RESPONSE_CANCEL,
188 * _("_Open"),
189 * GTK_RESPONSE_ACCEPT,
190 * NULL);
191 * ```
192 *
193 * This will create buttons for “Cancel” and “Open” that use predefined
194 * response identifiers from [enum@Gtk.ResponseType]. For most dialog
195 * boxes you can use your own custom response codes rather than the
196 * ones in [enum@Gtk.ResponseType], but `GtkFileChooserDialog` assumes that
197 * its “accept”-type action, e.g. an “Open” or “Save” button,
198 * will have one of the following response codes:
199 *
200 * - %GTK_RESPONSE_ACCEPT
201 * - %GTK_RESPONSE_OK
202 * - %GTK_RESPONSE_YES
203 * - %GTK_RESPONSE_APPLY
204 *
205 * This is because `GtkFileChooserDialog` must intercept responses and switch
206 * to folders if appropriate, rather than letting the dialog terminate — the
207 * implementation uses these known response codes to know which responses can
208 * be blocked if appropriate.
209 *
210 * To summarize, make sure you use a predefined response code
211 * when you use `GtkFileChooserDialog` to ensure proper operation.
212 */
213
214typedef struct _GtkFileChooserDialogPrivate GtkFileChooserDialogPrivate;
215typedef struct _GtkFileChooserDialogClass GtkFileChooserDialogClass;
216
217struct _GtkFileChooserDialog
218{
219 GtkDialog parent_instance;
220};
221
222struct _GtkFileChooserDialogClass
223{
224 GtkDialogClass parent_class;
225};
226
227struct _GtkFileChooserDialogPrivate
228{
229 GtkWidget *widget;
230
231 GtkSizeGroup *buttons;
232
233 /* for use with GtkFileChooserEmbed */
234 gboolean response_requested;
235 gboolean search_setup;
236 gboolean has_entry;
237};
238
239static void gtk_file_chooser_dialog_dispose (GObject *object);
240static void gtk_file_chooser_dialog_set_property (GObject *object,
241 guint prop_id,
242 const GValue *value,
243 GParamSpec *pspec);
244static void gtk_file_chooser_dialog_get_property (GObject *object,
245 guint prop_id,
246 GValue *value,
247 GParamSpec *pspec);
248static void gtk_file_chooser_dialog_notify (GObject *object,
249 GParamSpec *pspec);
250
251static void gtk_file_chooser_dialog_realize (GtkWidget *widget);
252static void gtk_file_chooser_dialog_map (GtkWidget *widget);
253static void gtk_file_chooser_dialog_unmap (GtkWidget *widget);
254static void gtk_file_chooser_dialog_size_allocate (GtkWidget *widget,
255 int width,
256 int height,
257 int baseline);
258static void gtk_file_chooser_dialog_activate_response (GtkWidget *widget,
259 const char *action_name,
260 GVariant *parameters);
261
262static void response_cb (GtkDialog *dialog,
263 int response_id);
264
265static void setup_save_entry (GtkFileChooserDialog *dialog);
266
267G_DEFINE_TYPE_WITH_CODE (GtkFileChooserDialog, gtk_file_chooser_dialog, GTK_TYPE_DIALOG,
268 G_ADD_PRIVATE (GtkFileChooserDialog)
269 G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
270 _gtk_file_chooser_delegate_iface_init))
271
272static void
273gtk_file_chooser_dialog_class_init (GtkFileChooserDialogClass *class)
274{
275 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
276 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
277
278 gobject_class->dispose = gtk_file_chooser_dialog_dispose;
279 gobject_class->set_property = gtk_file_chooser_dialog_set_property;
280 gobject_class->get_property = gtk_file_chooser_dialog_get_property;
281 gobject_class->notify = gtk_file_chooser_dialog_notify;
282
283 widget_class->realize = gtk_file_chooser_dialog_realize;
284 widget_class->map = gtk_file_chooser_dialog_map;
285 widget_class->unmap = gtk_file_chooser_dialog_unmap;
286 widget_class->size_allocate = gtk_file_chooser_dialog_size_allocate;
287
288 _gtk_file_chooser_install_properties (klass: gobject_class);
289
290 /* Bind class to template
291 */
292 gtk_widget_class_set_template_from_resource (widget_class,
293 resource_name: "/org/gtk/libgtk/ui/gtkfilechooserdialog.ui");
294
295 gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDialog, widget);
296 gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDialog, buttons);
297 gtk_widget_class_bind_template_callback (widget_class, response_cb);
298
299 /**
300 * GtkFileChooserDialog|response.activate:
301 *
302 * Activate the default response of the dialog.
303 */
304 gtk_widget_class_install_action (widget_class, action_name: "response.activate", NULL, activate: gtk_file_chooser_dialog_activate_response);
305}
306
307static void
308gtk_file_chooser_dialog_init (GtkFileChooserDialog *dialog)
309{
310 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
311
312 priv->response_requested = FALSE;
313
314 gtk_widget_init_template (GTK_WIDGET (dialog));
315 gtk_dialog_set_use_header_bar_from_setting (GTK_DIALOG (dialog));
316
317 _gtk_file_chooser_set_delegate (GTK_FILE_CHOOSER (dialog),
318 GTK_FILE_CHOOSER (priv->widget));
319}
320
321static GtkWidget *
322get_accept_action_widget (GtkDialog *dialog,
323 gboolean sensitive_only)
324{
325 int response[] = {
326 GTK_RESPONSE_ACCEPT,
327 GTK_RESPONSE_OK,
328 GTK_RESPONSE_YES,
329 GTK_RESPONSE_APPLY
330 };
331 int i;
332 GtkWidget *widget;
333
334 for (i = 0; i < G_N_ELEMENTS (response); i++)
335 {
336 widget = gtk_dialog_get_widget_for_response (dialog, response_id: response[i]);
337 if (widget)
338 {
339 if (!sensitive_only)
340 return widget;
341
342 if (gtk_widget_is_sensitive (widget))
343 return widget;
344 }
345 }
346
347 return NULL;
348}
349
350static gboolean
351is_accept_response_id (int response_id)
352{
353 return (response_id == GTK_RESPONSE_ACCEPT ||
354 response_id == GTK_RESPONSE_OK ||
355 response_id == GTK_RESPONSE_YES ||
356 response_id == GTK_RESPONSE_APPLY);
357}
358
359static void
360gtk_file_chooser_dialog_activate_response (GtkWidget *widget,
361 const char *action_name,
362 GVariant *parameters)
363{
364 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
365 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
366 GtkWidget *button;
367
368 priv->response_requested = TRUE;
369
370 button = get_accept_action_widget (GTK_DIALOG (dialog), TRUE);
371 if (button)
372 {
373 gtk_widget_activate (widget: button);
374 return;
375 }
376
377 priv->response_requested = FALSE;
378}
379
380static void
381gtk_file_chooser_dialog_dispose (GObject *object)
382{
383 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (GTK_FILE_CHOOSER_DIALOG (object));
384
385 g_clear_pointer ((GtkWidget **)&priv->widget, gtk_widget_unparent);
386
387 G_OBJECT_CLASS (gtk_file_chooser_dialog_parent_class)->dispose (object);
388}
389
390static void
391gtk_file_chooser_dialog_set_property (GObject *object,
392 guint prop_id,
393 const GValue *value,
394 GParamSpec *pspec)
395
396{
397 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (GTK_FILE_CHOOSER_DIALOG (object));
398
399 g_object_set_property (G_OBJECT (priv->widget), property_name: pspec->name, value);
400}
401
402static void
403gtk_file_chooser_dialog_get_property (GObject *object,
404 guint prop_id,
405 GValue *value,
406 GParamSpec *pspec)
407{
408 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (GTK_FILE_CHOOSER_DIALOG (object));
409
410 g_object_get_property (G_OBJECT (priv->widget), property_name: pspec->name, value);
411}
412
413static void
414gtk_file_chooser_dialog_notify (GObject *object,
415 GParamSpec *pspec)
416{
417 if (strcmp (s1: pspec->name, s2: "action") == 0)
418 setup_save_entry (GTK_FILE_CHOOSER_DIALOG (object));
419
420 if (G_OBJECT_CLASS (gtk_file_chooser_dialog_parent_class)->notify)
421 G_OBJECT_CLASS (gtk_file_chooser_dialog_parent_class)->notify (object, pspec);
422}
423
424static void
425add_button (GtkWidget *button, gpointer data)
426{
427 GtkFileChooserDialog *dialog = data;
428 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
429
430 if (GTK_IS_BUTTON (button))
431 gtk_size_group_add_widget (size_group: priv->buttons, widget: button);
432}
433
434static gboolean
435translate_subtitle_to_visible (GBinding *binding,
436 const GValue *from_value,
437 GValue *to_value,
438 gpointer user_data)
439{
440 const char *subtitle = g_value_get_string (value: from_value);
441
442 g_value_set_boolean (value: to_value, v_boolean: subtitle != NULL);
443
444 return TRUE;
445}
446
447static void
448setup_search (GtkFileChooserDialog *dialog)
449{
450 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
451 gboolean use_header;
452 GtkWidget *child;
453
454 if (priv->search_setup)
455 return;
456
457 priv->search_setup = TRUE;
458
459 g_object_get (object: dialog, first_property_name: "use-header-bar", &use_header, NULL);
460 if (use_header)
461 {
462 GtkWidget *button;
463 GtkWidget *header;
464 GtkWidget *box;
465 GtkWidget *label;
466
467 button = gtk_toggle_button_new ();
468 gtk_widget_set_focus_on_click (widget: button, FALSE);
469 gtk_widget_set_valign (widget: button, align: GTK_ALIGN_CENTER);
470 gtk_button_set_icon_name (GTK_BUTTON (button), icon_name: "edit-find-symbolic");
471 gtk_widget_show (widget: button);
472
473 header = gtk_dialog_get_header_bar (GTK_DIALOG (dialog));
474 gtk_header_bar_pack_end (GTK_HEADER_BAR (header), child: button);
475
476 g_object_bind_property (source: button, source_property: "active",
477 target: priv->widget, target_property: "search-mode",
478 flags: G_BINDING_BIDIRECTIONAL);
479
480 if (!priv->has_entry)
481 {
482 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
483 gtk_widget_set_valign (widget: box, align: GTK_ALIGN_CENTER);
484
485 label = gtk_label_new (NULL);
486 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_CENTER);
487 gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
488 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_END);
489 gtk_label_set_width_chars (GTK_LABEL (label), n_chars: 5);
490 gtk_widget_add_css_class (widget: label, css_class: "title");
491 gtk_widget_set_parent (widget: label, parent: box);
492
493 g_object_bind_property (source: dialog, source_property: "title",
494 target: label, target_property: "label",
495 flags: G_BINDING_SYNC_CREATE);
496
497 label = gtk_label_new (NULL);
498 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_CENTER);
499 gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
500 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_END);
501 gtk_widget_add_css_class (widget: label, css_class: "subtitle");
502 gtk_widget_set_parent (widget: label, parent: box);
503
504 g_object_bind_property (source: priv->widget, source_property: "subtitle",
505 target: label, target_property: "label",
506 flags: G_BINDING_SYNC_CREATE);
507 g_object_bind_property_full (source: priv->widget, source_property: "subtitle",
508 target: label, target_property: "visible",
509 flags: G_BINDING_SYNC_CREATE,
510 transform_to: translate_subtitle_to_visible,
511 NULL, NULL, NULL);
512
513 gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header), title_widget: box);
514 }
515
516 for (child = gtk_widget_get_first_child (widget: header);
517 child != NULL;
518 child = gtk_widget_get_next_sibling (widget: child))
519 add_button (button: child, data: dialog);
520 }
521}
522
523static void
524setup_save_entry (GtkFileChooserDialog *dialog)
525{
526 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
527 gboolean use_header;
528 GtkFileChooserAction action;
529 gboolean need_entry;
530 GtkWidget *header;
531
532 g_object_get (object: dialog,
533 first_property_name: "use-header-bar", &use_header,
534 "action", &action,
535 NULL);
536
537 if (!use_header)
538 return;
539
540 header = gtk_dialog_get_header_bar (GTK_DIALOG (dialog));
541
542 need_entry = action == GTK_FILE_CHOOSER_ACTION_SAVE;
543
544 if (need_entry && !priv->has_entry)
545 {
546 GtkWidget *box;
547 GtkWidget *label;
548 GtkWidget *entry;
549
550 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
551 label = gtk_label_new_with_mnemonic (_("_Name"));
552 entry = _gtk_file_chooser_entry_new (FALSE, FALSE);
553 g_object_set (object: label, first_property_name: "margin-start", 6, "margin-end", 6, NULL);
554 g_object_set (object: entry, first_property_name: "margin-start", 6, "margin-end", 6, NULL);
555 gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget: entry);
556 gtk_box_append (GTK_BOX (box), child: label);
557 gtk_box_append (GTK_BOX (box), child: entry);
558
559 gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header), title_widget: box);
560 gtk_file_chooser_widget_set_save_entry (GTK_FILE_CHOOSER_WIDGET (priv->widget), entry);
561 }
562 else if (!need_entry && priv->has_entry)
563 {
564 gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header), NULL);
565 gtk_file_chooser_widget_set_save_entry (GTK_FILE_CHOOSER_WIDGET (priv->widget), NULL);
566 }
567
568 priv->has_entry = need_entry;
569}
570
571static void
572ensure_default_response (GtkFileChooserDialog *dialog)
573{
574 GtkWidget *widget;
575
576 widget = get_accept_action_widget (GTK_DIALOG (dialog), TRUE);
577 if (widget)
578 gtk_window_set_default_widget (GTK_WINDOW (dialog), default_widget: widget);
579}
580
581static void
582gtk_file_chooser_dialog_realize (GtkWidget *widget)
583{
584 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
585 GSettings *settings;
586 int width, height;
587
588 settings = _gtk_file_chooser_get_settings_for_widget (widget);
589 g_settings_get (settings, SETTINGS_KEY_WINDOW_SIZE, format: "(ii)", &width, &height);
590
591 if (width != 0 && height != 0)
592 gtk_window_set_default_size (GTK_WINDOW (dialog), width, height);
593
594 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->realize (widget);
595}
596
597static void
598gtk_file_chooser_dialog_map (GtkWidget *widget)
599{
600 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
601 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog);
602
603 setup_search (dialog);
604 setup_save_entry (dialog);
605 ensure_default_response (dialog);
606
607 gtk_file_chooser_widget_initial_focus (GTK_FILE_CHOOSER_WIDGET (priv->widget));
608
609 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->map (widget);
610}
611
612static void
613save_dialog_geometry (GtkFileChooserDialog *dialog)
614{
615 GtkWindow *window;
616 GSettings *settings;
617 int old_width, old_height;
618 int width, height;
619
620 settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (dialog));
621
622 window = GTK_WINDOW (dialog);
623
624 gtk_window_get_default_size (window, width: &width, height: &height);
625
626 g_settings_get (settings, SETTINGS_KEY_WINDOW_SIZE, format: "(ii)", &old_width, &old_height);
627 if (old_width != width || old_height != height)
628 g_settings_set (settings, SETTINGS_KEY_WINDOW_SIZE, format: "(ii)", width, height);
629
630 g_settings_apply (settings);
631}
632
633static void
634gtk_file_chooser_dialog_unmap (GtkWidget *widget)
635{
636 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
637
638 save_dialog_geometry (dialog);
639
640 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->unmap (widget);
641}
642
643static void
644gtk_file_chooser_dialog_size_allocate (GtkWidget *widget,
645 int width,
646 int height,
647 int baseline)
648{
649 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->size_allocate (widget,
650 width,
651 height,
652 baseline);
653 if (gtk_widget_is_drawable (widget))
654 save_dialog_geometry (GTK_FILE_CHOOSER_DIALOG (widget));
655}
656
657/* We do a signal connection here rather than overriding the method in
658 * class_init because GtkDialog::response is a RUN_LAST signal. We want *our*
659 * handler to be run *first*, regardless of whether the user installs response
660 * handlers of his own.
661 */
662static void
663response_cb (GtkDialog *dialog,
664 int response_id)
665{
666 GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (GTK_FILE_CHOOSER_DIALOG (dialog));
667
668 /* Act only on response IDs we recognize */
669 if (is_accept_response_id (response_id) &&
670 !priv->response_requested &&
671 !gtk_file_chooser_widget_should_respond (GTK_FILE_CHOOSER_WIDGET (priv->widget)))
672 {
673 g_signal_stop_emission_by_name (instance: dialog, detailed_signal: "response");
674 }
675
676 priv->response_requested = FALSE;
677}
678
679static GtkWidget *
680gtk_file_chooser_dialog_new_valist (const char *title,
681 GtkWindow *parent,
682 GtkFileChooserAction action,
683 const char *first_button_text,
684 va_list varargs)
685{
686 GtkWidget *result;
687 const char *button_text = first_button_text;
688 int response_id;
689
690 result = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
691 first_property_name: "title", title,
692 "action", action,
693 NULL);
694
695 if (parent)
696 gtk_window_set_transient_for (GTK_WINDOW (result), parent);
697
698 while (button_text)
699 {
700 response_id = va_arg (varargs, int);
701 gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id);
702 button_text = va_arg (varargs, const char *);
703 }
704
705 return result;
706}
707
708/**
709 * gtk_file_chooser_dialog_new:
710 * @title: (nullable): Title of the dialog
711 * @parent: (nullable): Transient parent of the dialog
712 * @action: Open or save mode for the dialog
713 * @first_button_text: (nullable): text to go in the first button
714 * @...: response ID for the first button, then additional (button, id) pairs, ending with %NULL
715 *
716 * Creates a new `GtkFileChooserDialog`.
717 *
718 * This function is analogous to [ctor@Gtk.Dialog.new_with_buttons].
719 *
720 * Returns: a new `GtkFileChooserDialog`
721 */
722GtkWidget *
723gtk_file_chooser_dialog_new (const char *title,
724 GtkWindow *parent,
725 GtkFileChooserAction action,
726 const char *first_button_text,
727 ...)
728{
729 GtkWidget *result;
730 va_list varargs;
731
732 va_start (varargs, first_button_text);
733 result = gtk_file_chooser_dialog_new_valist (title, parent, action,
734 first_button_text,
735 varargs);
736 va_end (varargs);
737
738 return result;
739}
740

source code of gtk/gtk/gtkfilechooserdialog.c