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 | |
214 | typedef struct _GtkFileChooserDialogPrivate GtkFileChooserDialogPrivate; |
215 | typedef struct _GtkFileChooserDialogClass GtkFileChooserDialogClass; |
216 | |
217 | struct _GtkFileChooserDialog |
218 | { |
219 | GtkDialog parent_instance; |
220 | }; |
221 | |
222 | struct _GtkFileChooserDialogClass |
223 | { |
224 | GtkDialogClass parent_class; |
225 | }; |
226 | |
227 | struct _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 | |
239 | static void gtk_file_chooser_dialog_dispose (GObject *object); |
240 | static void gtk_file_chooser_dialog_set_property (GObject *object, |
241 | guint prop_id, |
242 | const GValue *value, |
243 | GParamSpec *pspec); |
244 | static void gtk_file_chooser_dialog_get_property (GObject *object, |
245 | guint prop_id, |
246 | GValue *value, |
247 | GParamSpec *pspec); |
248 | static void gtk_file_chooser_dialog_notify (GObject *object, |
249 | GParamSpec *pspec); |
250 | |
251 | static void gtk_file_chooser_dialog_realize (GtkWidget *widget); |
252 | static void gtk_file_chooser_dialog_map (GtkWidget *widget); |
253 | static void gtk_file_chooser_dialog_unmap (GtkWidget *widget); |
254 | static void gtk_file_chooser_dialog_size_allocate (GtkWidget *widget, |
255 | int width, |
256 | int height, |
257 | int baseline); |
258 | static void gtk_file_chooser_dialog_activate_response (GtkWidget *widget, |
259 | const char *action_name, |
260 | GVariant *parameters); |
261 | |
262 | static void response_cb (GtkDialog *dialog, |
263 | int response_id); |
264 | |
265 | static void setup_save_entry (GtkFileChooserDialog *dialog); |
266 | |
267 | G_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 | |
272 | static void |
273 | gtk_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 | |
307 | static void |
308 | gtk_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 | |
321 | static GtkWidget * |
322 | get_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 | |
350 | static gboolean |
351 | is_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 | |
359 | static void |
360 | gtk_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 | |
380 | static void |
381 | gtk_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 | |
390 | static void |
391 | gtk_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 | |
402 | static void |
403 | gtk_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 | |
413 | static void |
414 | gtk_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 | |
424 | static void |
425 | add_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 | |
434 | static gboolean |
435 | translate_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 | |
447 | static void |
448 | setup_search (GtkFileChooserDialog *dialog) |
449 | { |
450 | GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog); |
451 | gboolean ; |
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 *; |
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 | |
523 | static void |
524 | setup_save_entry (GtkFileChooserDialog *dialog) |
525 | { |
526 | GtkFileChooserDialogPrivate *priv = gtk_file_chooser_dialog_get_instance_private (self: dialog); |
527 | gboolean ; |
528 | GtkFileChooserAction action; |
529 | gboolean need_entry; |
530 | GtkWidget *; |
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 | |
571 | static void |
572 | ensure_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 | |
581 | static void |
582 | gtk_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 | |
597 | static void |
598 | gtk_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 | |
612 | static void |
613 | save_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 | |
633 | static void |
634 | gtk_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 | |
643 | static void |
644 | gtk_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 | */ |
662 | static void |
663 | response_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 | |
679 | static GtkWidget * |
680 | gtk_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 | */ |
722 | GtkWidget * |
723 | gtk_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 | |