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