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