]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserbutton.c
Rework of GtkFileChooserButton, some cleanups. Fixes #154388, #154390,
[~andy/gtk] / gtk / gtkfilechooserbutton.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2
3 /* GTK+: gtkfilechooserbutton.c
4  * 
5  * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <config.h>
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30
31 #include <string.h>
32
33 #include "gtkalias.h"
34 #include "gtkintl.h"
35 #include "gtkdnd.h"
36 #include "gtkentry.h"
37 #include "gtkhbox.h"
38 #include "gtkicontheme.h"
39 #include "gtkimage.h"
40 #include "gtklabel.h"
41 #include "gtkstock.h"
42 #include "gtktogglebutton.h"
43 #include "gtkvseparator.h"
44 #include "gtkfilechooserdialog.h"
45 #include "gtkfilechooserentry.h"
46 #include "gtkfilechooserprivate.h"
47 #include "gtkfilechooserutils.h"
48
49 #include "gtkfilechooserbutton.h"
50
51
52 /* **************** *
53  *  Private Macros  *
54  * **************** */
55
56 #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)
57
58 #define DEFAULT_FILENAME        N_("(None)")
59 #define MIN_LABEL_WIDTH         100
60 #define ENTRY_BUTTON_SPACING    0
61 #define FALLBACK_ICON_SIZE      20
62 #define FALLBACK_ICON_NAME      "stock_unknown"
63 #define NEW_FILE_ICON_NAME      "stock_new"
64 #define NEW_DIR_ICON_NAME       "stock_new-dir"
65
66 /* ********************** *
67  *  Private Enumerations  *
68  * ********************** */
69
70 /* Property IDs */
71 enum
72 {
73   PROP_0,
74
75   PROP_DIALOG,
76   PROP_TITLE,
77   PROP_ACTIVE,
78   PROP_WIDTH_CHARS
79 };
80
81
82 /* ******************** *
83  *  Private Structures  *
84  * ******************** */
85
86 struct _GtkFileChooserButtonPrivate
87 {
88   GtkWidget *dialog;
89   GtkWidget *accept_button;
90   GtkWidget *entry_box;
91   GtkWidget *entry_image;
92   GtkWidget *entry;
93   GtkWidget *label_box;
94   GtkWidget *label_image;
95   GtkWidget *label;
96   GtkWidget *button;
97
98   gchar *backend;
99   gulong entry_changed_id;
100   gulong dialog_file_activated_id;
101   gulong dialog_folder_changed_id;
102   gulong dialog_selection_changed_id;
103   gulong dialog_selection_changed_proxy_id;
104   gulong settings_signal_id;
105   guint update_id;
106   gint icon_size;
107 };
108
109
110 /* ************* *
111  *  DnD Support  *
112  * ************* */
113
114 enum
115 {
116   TEXT_PLAIN,
117   TEXT_URI_LIST
118 };
119
120 /* ********************* *
121  *  Function Prototypes  *
122  * ********************* */
123
124 /* GObject Functions */
125 static GObject *gtk_file_chooser_button_constructor        (GType             type,
126                                                             guint             n_params,
127                                                             GObjectConstructParam *params);
128 static void     gtk_file_chooser_button_set_property       (GObject          *object,
129                                                             guint             param_id,
130                                                             const GValue     *value,
131                                                             GParamSpec       *pspec);
132 static void     gtk_file_chooser_button_get_property       (GObject          *object,
133                                                             guint             param_id,
134                                                             GValue           *value,
135                                                             GParamSpec       *pspec);
136
137 /* GtkObject Functions */
138 static void     gtk_file_chooser_button_destroy            (GtkObject        *object);
139
140 /* GtkWidget Functions */
141 static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
142                                                             GdkDragContext   *context,
143                                                             gint              x,
144                                                             gint              y,
145                                                             GtkSelectionData *data,
146                                                             guint             info,
147                                                             guint             drag_time);
148 static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
149 static void     gtk_file_chooser_button_hide_all           (GtkWidget        *widget);
150 static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
151 static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
152 static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
153                                                             gboolean          group_cycling);
154 static void     gtk_file_chooser_button_style_set          (GtkWidget        *widget,
155                                                             GtkStyle         *old_style);
156 static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
157                                                             GdkScreen        *old_screen);
158
159 /* Child Widget Callbacks */
160 static void     dialog_update_preview_cb                   (GtkFileChooser   *dialog,
161                                                             gpointer          user_data);
162 static void     dialog_selection_changed_cb                (GtkFileChooser   *dialog,
163                                                             gpointer          user_data);
164 static void     dialog_selection_changed_proxy_cb          (GtkFileChooser   *dialog,
165                                                             gpointer          user_data);
166 static void     dialog_file_activated_cb                   (GtkFileChooser   *dialog,
167                                                             gpointer          user_data);
168 static void     dialog_current_folder_changed_cb           (GtkFileChooser   *dialog,
169                                                             gpointer          user_data);
170 static void     dialog_notify_cb                           (GObject          *dialog,
171                                                             GParamSpec       *pspec,
172                                                             gpointer          user_data);
173 static gboolean dialog_delete_event_cb                     (GtkWidget        *dialog,
174                                                             GdkEvent         *event,
175                                                             gpointer          user_data);
176 static void     dialog_response_cb                         (GtkFileChooser   *dialog,
177                                                             gint              response,
178                                                             gpointer          user_data);
179
180 static void     button_toggled_cb                          (GtkToggleButton  *real_button,
181                                                             gpointer          user_data);
182 static void     button_notify_active_cb                    (GObject          *real_button,
183                                                             GParamSpec       *pspec,
184                                                             gpointer          user_data);
185
186 static void     entry_size_allocate_cb                     (GtkWidget        *entry,
187                                                             GtkAllocation    *allocation,
188                                                             gpointer          user_data);
189 static void     entry_changed_cb                           (GtkEditable      *editable,
190                                                             gpointer          user_data);
191
192 /* Utility Functions */
193 static void     remove_settings_signal                     (GtkFileChooserButton *button,
194                                                             GdkScreen            *screen);
195
196 static void     update_dialog                              (GtkFileChooserButton *button);
197 static void     update_entry                               (GtkFileChooserButton *button);
198 static void     update_label                               (GtkFileChooserButton *button);
199 static void     update_icons                               (GtkFileChooserButton *button);
200
201
202 /* ******************* *
203  *  GType Declaration  *
204  * ******************* */
205
206 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
207     G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \
208 });
209
210
211 /* ***************** *
212  *  GType Functions  *
213  * ***************** */
214
215 static void
216 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
217 {
218   GObjectClass *gobject_class;
219   GtkObjectClass *gtkobject_class;
220   GtkWidgetClass *widget_class;
221
222   gobject_class = G_OBJECT_CLASS (class);
223   gtkobject_class = GTK_OBJECT_CLASS (class);
224   widget_class = GTK_WIDGET_CLASS (class);
225
226   gobject_class->constructor = gtk_file_chooser_button_constructor;
227   gobject_class->set_property = gtk_file_chooser_button_set_property;
228   gobject_class->get_property = gtk_file_chooser_button_get_property;
229
230   gtkobject_class->destroy = gtk_file_chooser_button_destroy;
231
232   widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
233   widget_class->show_all = gtk_file_chooser_button_show_all;
234   widget_class->hide_all = gtk_file_chooser_button_hide_all;
235   widget_class->show = gtk_file_chooser_button_show;
236   widget_class->hide = gtk_file_chooser_button_hide;
237   widget_class->style_set = gtk_file_chooser_button_style_set;
238   widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
239   widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
240
241   g_object_class_install_property (gobject_class, PROP_DIALOG,
242                                    g_param_spec_object ("dialog",
243                                                         P_("Dialog"),
244                                                         P_("The file chooser dialog to use."),
245                                                         GTK_TYPE_FILE_CHOOSER_DIALOG,
246                                                         (G_PARAM_WRITABLE |
247                                                          G_PARAM_CONSTRUCT_ONLY)));
248   g_object_class_install_property (gobject_class, PROP_TITLE,
249                                    g_param_spec_string ("title",
250                                                         P_("Title"),
251                                                         P_("The title of the file chooser dialog."),
252                                                         _("Select a File"),
253                                                         G_PARAM_READWRITE));
254   g_object_class_install_property (gobject_class, PROP_ACTIVE,
255                                    g_param_spec_boolean ("active",
256                                                          P_("Active"),
257                                                          P_("Whether the browse dialog is visible or not."),
258                                                          FALSE, G_PARAM_READWRITE));
259   g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
260                                    g_param_spec_int ("width-chars",
261                                                      P_("Width In Characters"),
262                                                      P_("The desired width of the button widget, in characters."),
263                                                      -1, G_MAXINT, -1,
264                                                      G_PARAM_READWRITE));
265
266   _gtk_file_chooser_install_properties (gobject_class);
267
268   g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
269 }
270
271
272 static void
273 gtk_file_chooser_button_init (GtkFileChooserButton *button)
274 {
275   GtkFileChooserButtonPrivate *priv;
276   GtkWidget *box, *image, *sep;
277
278   gtk_box_set_spacing (GTK_BOX (button), ENTRY_BUTTON_SPACING);
279
280   priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
281                                       GtkFileChooserButtonPrivate);
282   button->priv = priv;
283
284   priv->icon_size = FALLBACK_ICON_SIZE;
285
286   gtk_widget_push_composite_child ();
287
288   priv->entry_box = gtk_hbox_new (FALSE, 4);
289   gtk_container_add (GTK_CONTAINER (button), priv->entry_box);
290
291   priv->entry_image = gtk_image_new ();
292   gtk_box_pack_start (GTK_BOX (priv->entry_box), priv->entry_image,
293                       FALSE, FALSE, 0);
294   gtk_widget_show (priv->entry_image);
295
296   priv->entry = _gtk_file_chooser_entry_new (FALSE);
297   gtk_container_add (GTK_CONTAINER (priv->entry_box), priv->entry);
298   gtk_widget_show (priv->entry);
299
300   priv->button = gtk_toggle_button_new ();
301   g_signal_connect (priv->button, "toggled",
302                     G_CALLBACK (button_toggled_cb), button);
303   g_signal_connect (priv->button, "notify::active",
304                     G_CALLBACK (button_notify_active_cb), button);
305   g_signal_connect (priv->entry, "size-allocate",
306                     G_CALLBACK (entry_size_allocate_cb), priv->button);
307   gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
308   gtk_widget_show (priv->button);
309
310   box = gtk_hbox_new (FALSE, 4);
311   gtk_container_add (GTK_CONTAINER (priv->button), box);
312   gtk_widget_show (box);
313   
314   priv->label_box = gtk_hbox_new (FALSE, 6);
315   gtk_container_add (GTK_CONTAINER (box), priv->label_box);
316   gtk_widget_show (priv->label_box);
317
318   priv->label_image = gtk_image_new ();
319   gtk_box_pack_start (GTK_BOX (priv->label_box), priv->label_image,
320                       FALSE, FALSE, 0);
321   gtk_widget_show (priv->label_image);
322
323   priv->label = gtk_label_new (_(DEFAULT_FILENAME));
324   gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
325   gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
326   gtk_container_add (GTK_CONTAINER (priv->label_box), priv->label);
327   gtk_widget_show (priv->label);
328
329   sep = gtk_vseparator_new ();
330   gtk_box_pack_end (GTK_BOX (priv->label_box), sep, FALSE, FALSE, 0);
331   gtk_widget_show (sep);
332
333   image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
334                                     GTK_ICON_SIZE_SMALL_TOOLBAR);
335   gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0);
336   gtk_widget_show (image);
337
338   gtk_widget_pop_composite_child ();
339
340   /* DnD */
341   gtk_drag_dest_unset (priv->entry);
342   gtk_drag_dest_set (GTK_WIDGET (button),
343                      (GTK_DEST_DEFAULT_ALL),
344                      NULL, 0,
345                      GDK_ACTION_COPY);
346   gtk_drag_dest_add_text_targets (GTK_WIDGET (button));
347   gtk_target_list_add_uri_targets (gtk_drag_dest_get_target_list (GTK_WIDGET (button)), 
348                                    TEXT_URI_LIST);
349 }
350
351
352 /* ******************* *
353  *  GObject Functions  *
354  * ******************* */
355
356 static GObject *
357 gtk_file_chooser_button_constructor (GType                  type,
358                                      guint                  n_params,
359                                      GObjectConstructParam *params)
360 {
361   GObject *object;
362   GtkFileChooserButtonPrivate *priv;
363   GtkFilePath *path;
364
365   object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
366                                                                                   n_params,
367                                                                                   params);
368   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
369
370   if (!priv->dialog)
371     {
372       if (priv->backend)
373         priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
374                                                                  GTK_FILE_CHOOSER_ACTION_OPEN,
375                                                                  priv->backend, NULL);
376       else
377         priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
378                                                     GTK_FILE_CHOOSER_ACTION_OPEN,
379                                                     NULL);
380
381       gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
382                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
383       priv->accept_button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
384                                                    GTK_STOCK_OPEN,
385                                                    GTK_RESPONSE_ACCEPT);
386       gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
387                                        GTK_RESPONSE_ACCEPT);
388
389       gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
390                                                GTK_RESPONSE_ACCEPT,
391                                                GTK_RESPONSE_CANCEL,
392                                                -1);
393     }
394
395   g_free (priv->backend);
396   priv->backend = NULL;
397
398   g_signal_connect (priv->dialog, "delete-event",
399                     G_CALLBACK (dialog_delete_event_cb), object);
400   g_signal_connect (priv->dialog, "response",
401                     G_CALLBACK (dialog_response_cb), object);
402
403   /* This is used, instead of the standard delegate, to ensure that signals are only
404    * delegated when the OK button is pressed. */
405   g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
406   priv->dialog_folder_changed_id =
407     g_signal_connect (priv->dialog, "current-folder-changed",
408                       G_CALLBACK (dialog_current_folder_changed_cb), object);
409   priv->dialog_file_activated_id =
410     g_signal_connect (priv->dialog, "file-activated",
411                       G_CALLBACK (dialog_file_activated_cb), object);
412   priv->dialog_selection_changed_id =
413     g_signal_connect (priv->dialog, "selection-changed",
414                       G_CALLBACK (dialog_selection_changed_cb), object);
415   priv->dialog_selection_changed_proxy_id =
416     g_signal_connect (priv->dialog, "selection-changed",
417                       G_CALLBACK (dialog_selection_changed_proxy_cb), object);
418   g_signal_connect (priv->dialog, "update-preview",
419                     G_CALLBACK (dialog_update_preview_cb), object);
420   g_signal_connect (priv->dialog, "notify",
421                     G_CALLBACK (dialog_notify_cb), object);
422   g_object_add_weak_pointer (G_OBJECT (priv->dialog),
423                              (gpointer *) (&priv->dialog));
424
425   _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry),
426                                            _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
427   path = gtk_file_path_new_steal ("/");
428   _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry),
429                                            path);
430   priv->entry_changed_id = g_signal_connect (priv->entry, "changed",
431                                              G_CALLBACK (entry_changed_cb),
432                                              object);
433
434   update_label (GTK_FILE_CHOOSER_BUTTON (object));
435
436   return object;
437 }
438
439 static void
440 gtk_file_chooser_button_set_property (GObject      *object,
441                                       guint         param_id,
442                                       const GValue *value,
443                                       GParamSpec   *pspec)
444 {
445   GtkFileChooserButtonPrivate *priv;
446
447   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
448
449   switch (param_id)
450     {
451     case PROP_DIALOG:
452       /* Construct-only */
453       priv->dialog = g_value_get_object (value);
454       break;
455     case PROP_ACTIVE:
456       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
457                                     g_value_get_boolean (value));
458       break;
459     case PROP_WIDTH_CHARS:
460       gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
461                                                g_value_get_int (value));
462       break;
463
464     case GTK_FILE_CHOOSER_PROP_ACTION:
465       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
466       _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->entry),
467                                           (GtkFileChooserAction) g_value_get_enum (value));
468       update_icons (GTK_FILE_CHOOSER_BUTTON (object));
469
470       switch (g_value_get_enum (value))
471         {
472         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
473           gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
474           /* Fall through to set the widget states */
475         case GTK_FILE_CHOOSER_ACTION_OPEN:
476           gtk_widget_hide (priv->entry_box);
477           gtk_widget_show (priv->label_box);
478           gtk_box_set_child_packing (GTK_BOX (object), priv->button,
479                                      TRUE, TRUE, 0, GTK_PACK_START);
480           gtk_button_set_label (GTK_BUTTON (priv->accept_button),
481                                 GTK_STOCK_OPEN);
482           break;
483
484         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
485           gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
486           /* Fall through to set the widget states */
487         case GTK_FILE_CHOOSER_ACTION_SAVE:
488           gtk_widget_show (priv->entry_box);
489           gtk_widget_hide (priv->label_box);
490           gtk_box_set_child_packing (GTK_BOX (object), priv->button,
491                                      FALSE, FALSE, 0, GTK_PACK_START);
492           gtk_button_set_label (GTK_BUTTON (priv->accept_button),
493                                 GTK_STOCK_SAVE);
494           break;
495         }
496       break;
497
498     case PROP_TITLE:
499     case GTK_FILE_CHOOSER_PROP_FILTER:
500     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
501     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
502     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
503     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
504     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
505     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
506       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
507       break;
508
509     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
510       /* Construct-only */
511       priv->backend = g_value_dup_string (value);
512       break;
513
514     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
515       g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
516                  G_STRFUNC, G_OBJECT_TYPE_NAME (object));
517       break;
518     default:
519       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
520       break;
521     }
522 }
523
524 static void
525 gtk_file_chooser_button_get_property (GObject    *object,
526                                       guint       param_id,
527                                       GValue     *value,
528                                       GParamSpec *pspec)
529 {
530   GtkFileChooserButtonPrivate *priv;
531
532   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
533
534   switch (param_id)
535     {
536     case PROP_ACTIVE:
537       g_value_set_boolean (value,
538                            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)));
539       break;
540     case PROP_WIDTH_CHARS:
541       g_value_set_int (value,
542                        gtk_entry_get_width_chars (GTK_ENTRY (priv->entry)));
543       break;
544
545     case PROP_TITLE:
546     case GTK_FILE_CHOOSER_PROP_ACTION:
547     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
548     case GTK_FILE_CHOOSER_PROP_FILTER:
549     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
550     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
551     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
552     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
553     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
554     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
555     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
556       g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
557       break;
558
559     default:
560       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
561       break;
562     }
563 }
564
565
566 /* ********************* *
567  *  GtkObject Functions  *
568  * ********************* */
569
570 static void
571 gtk_file_chooser_button_destroy (GtkObject * object)
572 {
573   GtkFileChooserButtonPrivate *priv;
574
575   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
576
577   if (priv->update_id)
578     g_source_remove (priv->update_id);
579
580   if (priv->dialog != NULL)
581     gtk_widget_destroy (priv->dialog);
582   
583   remove_settings_signal (GTK_FILE_CHOOSER_BUTTON (object),
584                           gtk_widget_get_screen (GTK_WIDGET (object)));
585
586   if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
587     (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
588 }
589
590
591 /* ********************* *
592  *  GtkWidget Functions  *
593  * ********************* */
594
595 static void
596 gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
597                                             GdkDragContext   *context,
598                                             gint              x,
599                                             gint              y,
600                                             GtkSelectionData *data,
601                                             guint             info,
602                                             guint             drag_time)
603 {
604   GtkFileChooserButtonPrivate *priv;
605   gchar *text;
606
607   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
608     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
609                                                                                     context,
610                                                                                     x, y,
611                                                                                     data, info,
612                                                                                     drag_time);
613
614   if (widget == NULL || context == NULL || data == NULL || data->length < 0)
615     return;
616
617   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
618
619   switch (info)
620     {
621     case TEXT_URI_LIST:
622       {
623         gchar **uris;
624         guint i;
625         gboolean selected;
626
627         uris = gtk_selection_data_get_uris (data);
628
629         if (uris == NULL)
630           break;
631
632         selected = FALSE;
633         for (i = 0; !selected && uris[i] != NULL; i++)
634           {
635             GtkFileSystem *fs;
636             GtkFilePath *path, *base_path;
637
638             fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
639             path = gtk_file_system_uri_to_path (fs, uris[i]);
640
641             base_path = NULL;
642             if (path != NULL &&
643                 gtk_file_system_get_parent (fs, path, &base_path, NULL))
644               {
645                 GtkFileFolder *folder;
646                 GtkFileInfo *info;
647
648                 folder = gtk_file_system_get_folder (fs, base_path,
649                                                      GTK_FILE_INFO_IS_FOLDER,
650                                                      NULL);
651
652                 info = gtk_file_folder_get_info (folder, path, NULL);
653
654                 if (info != NULL)
655                   {
656                     GtkFileChooserAction action;
657
658                     g_object_get (priv->dialog, "action", &action, NULL);
659
660                     selected = 
661                       ((((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
662                           action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
663                          gtk_file_info_get_is_folder (info)) ||
664                         ((action == GTK_FILE_CHOOSER_ACTION_OPEN ||
665                           action == GTK_FILE_CHOOSER_ACTION_SAVE) &&
666                          !gtk_file_info_get_is_folder (info))) &&
667                         _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
668                                                        path, NULL));
669
670                     gtk_file_info_free (info);
671                   }
672                 else
673                   selected = FALSE;
674
675                 gtk_file_path_free (base_path);
676               }
677
678             gtk_file_path_free (path);
679           }
680
681         g_strfreev (uris);
682       }
683       break;
684
685     case TEXT_PLAIN:
686       text = gtk_selection_data_get_text (data);
687       gtk_entry_set_text (GTK_ENTRY (priv->entry), text);
688       g_free (text);
689       break;
690     }
691
692   gtk_drag_finish (context, TRUE, FALSE, drag_time);
693 }
694
695 static void
696 gtk_file_chooser_button_show_all (GtkWidget *widget)
697 {
698   gtk_widget_show (widget);
699 }
700
701 static void
702 gtk_file_chooser_button_hide_all (GtkWidget *widget)
703 {
704   gtk_widget_hide (widget);
705 }
706
707 static void
708 gtk_file_chooser_button_show (GtkWidget *widget)
709 {
710   GtkFileChooserButtonPrivate *priv;
711
712   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
713
714   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
715     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
716
717   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)))
718     gtk_widget_show (priv->dialog);
719 }
720
721 static void
722 gtk_file_chooser_button_hide (GtkWidget *widget)
723 {
724   GtkFileChooserButtonPrivate *priv;
725
726   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
727
728   gtk_widget_hide (priv->dialog);
729
730   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
731     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
732 }
733
734 static gboolean
735 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
736                                            gboolean   group_cycling)
737 {
738   GtkFileChooserButtonPrivate *priv;
739
740   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
741
742   switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
743     {
744     case GTK_FILE_CHOOSER_ACTION_OPEN:
745     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
746       gtk_widget_grab_focus (priv->button);
747       break;
748     case GTK_FILE_CHOOSER_ACTION_SAVE:
749     case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
750       gtk_widget_grab_focus (priv->entry);
751       break;
752     }
753
754   return TRUE;
755 }
756
757 /* Changes the icons wherever it is needed */
758 static void
759 change_icon_theme (GtkFileChooserButton *button)
760 {
761   GtkSettings *settings;
762   gint width, height;
763
764   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
765
766   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR,
767                                          &width, &height))
768     button->priv->icon_size = MAX (width, height);
769   else
770     button->priv->icon_size = FALLBACK_ICON_SIZE;
771
772   update_icons (button);
773 }
774
775 /* Callback used when a GtkSettings value changes */
776 static void
777 settings_notify_cb (GObject    *object,
778                     GParamSpec *pspec,
779                     gpointer    user_data)
780 {
781   const char *name;
782
783   name = g_param_spec_get_name (pspec);
784
785   if (strcmp (name, "gtk-icon-theme-name") == 0
786       || strcmp (name, "gtk-icon-sizes") == 0)
787     change_icon_theme (user_data);
788 }
789
790 /* Installs a signal handler for GtkSettings so that we can monitor changes in
791  * the icon theme.
792  */
793 static void
794 check_icon_theme (GtkFileChooserButton *button)
795 {
796   GtkSettings *settings;
797
798   if (button->priv->settings_signal_id)
799     return;
800
801   if (gtk_widget_has_screen (GTK_WIDGET (button)))
802     {
803       settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
804       button->priv->settings_signal_id = g_signal_connect (settings, "notify",
805                                                            G_CALLBACK (settings_notify_cb),
806                                                            button);
807
808       change_icon_theme (button);
809     }
810 }
811
812 static void
813 gtk_file_chooser_button_style_set (GtkWidget *widget,
814                                    GtkStyle  *old_style)
815 {
816   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
817     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
818                                                                            old_style);
819
820   if (gtk_widget_has_screen (widget))
821     change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
822 }
823
824 static void
825 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
826                                         GdkScreen *old_screen)
827 {
828   GtkFileChooserButton *button;
829
830   button = GTK_FILE_CHOOSER_BUTTON (widget);
831
832   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
833     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
834                                                                                 old_screen);
835
836   remove_settings_signal (button, old_screen);
837   check_icon_theme (button); 
838 }
839
840
841 /* ************************************************************************** *
842  *  Public API                                                                *
843  * ************************************************************************** */
844
845 /**
846  * gtk_file_chooser_button_new:
847  * @title: the title of the browse dialog.
848  * 
849  * Creates a new file-selecting button widget.
850  * 
851  * Returns: a new button widget.
852  * 
853  * Since: 2.6
854  **/
855 GtkWidget *
856 gtk_file_chooser_button_new (const gchar *title)
857 {
858   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
859                        "title", title,
860                        NULL);
861 }
862
863 /**
864  * gtk_file_chooser_button_new_with_backend:
865  * @title: the title of the browse dialog.
866  * @backend: the name of the #GtkFileSystem backend to use.
867  * 
868  * Creates a new file-selecting button widget using @backend.
869  * 
870  * Returns: a new button widget.
871  * 
872  * Since: 2.6
873  **/
874 GtkWidget *
875 gtk_file_chooser_button_new_with_backend (const gchar *title,
876                                           const gchar *backend)
877 {
878   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
879                        "title", title,
880                        "file-system-backend", backend,
881                        NULL);
882 }
883
884 /**
885  * gtk_file_chooser_button_new_with_dialog:
886  * @dialog: the #GtkDialog widget to use.
887  * 
888  * Creates a #GtkFileChooserButton widget which uses @dialog as it's
889  * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
890  * subclass).
891  * 
892  * Returns: a new button widget.
893  * 
894  * Since: 2.6
895  **/
896 GtkWidget *
897 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
898 {
899   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
900
901   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
902                        "dialog", dialog,
903                        NULL);
904 }
905
906 /**
907  * gtk_file_chooser_button_set_title:
908  * @button: the button widget to modify.
909  * @title: the new browse dialog title.
910  * 
911  * Modifies the @title of the browse dialog used by @button.
912  * 
913  * Since: 2.6
914  **/
915 void
916 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
917                                    const gchar          *title)
918 {
919   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
920
921   gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
922   g_object_notify (G_OBJECT (button), "title");
923 }
924
925 /**
926  * gtk_file_chooser_button_get_title:
927  * @button: the button widget to examine.
928  * 
929  * Retrieves the title of the browse dialog used by @button. The returned value
930  * should not be modified or freed.
931  * 
932  * Returns: a pointer to the browse dialog's title.
933  * 
934  * Since: 2.6
935  **/
936 G_CONST_RETURN gchar *
937 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
938 {
939   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
940
941   return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
942 }
943
944 /**
945  * gtk_file_chooser_button_set_active:
946  * @button: the button widget to modify.
947  * @is_active: whether or not the dialog is visible.
948  * 
949  * Modifies whether or not the dialog attached to @button is visible or not.
950  * 
951  * Since: 2.6
952  **/
953 void
954 gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
955                                     gboolean              is_active)
956 {
957   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
958
959   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active);
960 }
961
962 /**
963  * gtk_file_chooser_button_get_active:
964  * @button: the button widget to examine.
965  * 
966  * Retrieves whether or not the dialog attached to @button is visible.
967  * 
968  * Returns: a boolean whether the dialog is visible or not.
969  * 
970  * Since: 2.6
971  **/
972 gboolean
973 gtk_file_chooser_button_get_active (GtkFileChooserButton *button)
974 {
975   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
976
977   return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button));
978 }
979
980 /**
981  * gtk_file_chooser_button_get_width_chars:
982  * @button: the button widget to examine.
983  * 
984  * Retrieves the width in characters of the @button widget's entry and/or label.
985  * 
986  * Returns: an integer width (in characters) that the button will use to size itself.
987  * 
988  * Since: 2.6
989  **/
990 gint
991 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
992 {
993   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
994
995   return gtk_entry_get_width_chars (GTK_ENTRY (button->priv->entry));
996 }
997
998 /**
999  * gtk_file_chooser_button_set_width_chars:
1000  * @button: the button widget to examine.
1001  * @n_chars: the new width, in chracters.
1002  * 
1003  * Sets the width (in characters) that @button will use to @n_chars.
1004  * 
1005  * Since: 2.6
1006  **/
1007 void
1008 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
1009                                          gint                  n_chars)
1010 {
1011   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
1012
1013   gtk_entry_set_width_chars (GTK_ENTRY (button->priv->entry), n_chars);
1014   gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
1015   g_object_notify (G_OBJECT (button), "width-chars");
1016 }
1017
1018
1019 /* ******************* *
1020  *  Utility Functions  *
1021  * ******************* */
1022
1023 /* Removes the settings signal handler.  It's safe to call multiple times */
1024 static void
1025 remove_settings_signal (GtkFileChooserButton *button,
1026                         GdkScreen            *screen)
1027 {
1028   if (button->priv->settings_signal_id)
1029     {
1030       GtkSettings *settings;
1031
1032       settings = gtk_settings_get_for_screen (screen);
1033       g_signal_handler_disconnect (settings,
1034                                    button->priv->settings_signal_id);
1035       button->priv->settings_signal_id = 0;
1036     }
1037 }
1038
1039 static GtkIconTheme *
1040 get_icon_theme (GtkWidget *widget)
1041 {
1042   if (gtk_widget_has_screen (widget))
1043     return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1044
1045   return gtk_icon_theme_get_default ();
1046 }
1047
1048 static gboolean
1049 check_if_path_exists (GtkFileSystem     *fs,
1050                       const GtkFilePath *path)
1051 {
1052   gboolean path_exists;
1053   GtkFilePath *parent_path;
1054
1055   path_exists = FALSE;
1056   parent_path = NULL;
1057
1058   if (gtk_file_system_get_parent (fs, path, &parent_path, NULL))
1059     {
1060       GtkFileFolder *folder;
1061     
1062       folder = gtk_file_system_get_folder (fs, parent_path, 0, NULL);
1063       if (folder)
1064         {
1065           GtkFileInfo *info;
1066         
1067           info = gtk_file_folder_get_info (folder, path, NULL);
1068           if (info)
1069             {
1070               path_exists = TRUE;
1071               gtk_file_info_free (info);
1072             }
1073
1074           g_object_unref (folder);
1075         }
1076     
1077       gtk_file_path_free (parent_path);
1078     }
1079
1080   return path_exists;
1081 }
1082
1083 static void
1084 update_icons (GtkFileChooserButton *button)
1085 {
1086   GtkFileChooserButtonPrivate *priv;
1087   GdkPixbuf *pixbuf;
1088   GSList *paths;
1089
1090   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1091   pixbuf = NULL;
1092   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1093
1094   if (paths)
1095     {
1096       GtkFilePath *path;
1097       GtkFileSystem *fs;
1098
1099       path = paths->data;
1100       fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1101
1102       switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1103         {
1104         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1105           {
1106             GtkFileSystemVolume *volume;
1107       
1108             volume = gtk_file_system_get_volume_for_path (fs, path);
1109             if (volume)
1110               {
1111                 GtkFilePath *base_path;
1112
1113                 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1114                 
1115                 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1116                   pixbuf = gtk_file_system_volume_render_icon (fs, volume,
1117                                                                GTK_WIDGET (button),
1118                                                                priv->icon_size,
1119                                                                NULL);
1120
1121                 if (base_path)
1122                   gtk_file_path_free (base_path);
1123
1124                 gtk_file_system_volume_free (fs, volume);
1125               }
1126           }
1127         
1128         case GTK_FILE_CHOOSER_ACTION_OPEN:
1129           if (!pixbuf)
1130             pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button),
1131                                                   priv->icon_size, NULL);
1132           break;
1133
1134         case GTK_FILE_CHOOSER_ACTION_SAVE:
1135           if (check_if_path_exists (fs, path))
1136             pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1137                                                GTK_STOCK_DIALOG_WARNING,
1138                                                priv->icon_size, 0, NULL);
1139           else
1140             pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1141                                                NEW_FILE_ICON_NAME,
1142                                                priv->icon_size, 0, NULL);
1143           break;
1144         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1145           if (check_if_path_exists (fs, path))
1146             pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1147                                                GTK_STOCK_DIALOG_WARNING,
1148                                                priv->icon_size, 0, NULL);
1149           else
1150             pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1151                                                NEW_DIR_ICON_NAME,
1152                                                priv->icon_size, 0, NULL);
1153           break;
1154         }
1155
1156       gtk_file_paths_free (paths);
1157     }
1158
1159   if (!pixbuf)
1160     pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1161                                        FALLBACK_ICON_NAME,
1162                                        priv->icon_size, 0, NULL);
1163
1164   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->entry_image), pixbuf);
1165   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->label_image), pixbuf);
1166
1167   if (pixbuf)
1168     g_object_unref (pixbuf);
1169 }
1170
1171
1172 static void
1173 update_label (GtkFileChooserButton *button)
1174 {
1175   GtkFileChooserButtonPrivate *priv;
1176   gchar *label_text;
1177   GSList *paths;
1178
1179   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1180   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
1181   label_text = NULL;
1182
1183   if (paths)
1184     {
1185       GtkFileSystem *fs;
1186       GtkFilePath *path;
1187       GtkFileSystemVolume *volume;
1188     
1189       path = paths->data;
1190
1191       fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1192
1193       volume = gtk_file_system_get_volume_for_path (fs, path);
1194       if (volume)
1195         {
1196           GtkFilePath *base_path;
1197
1198           base_path = gtk_file_system_volume_get_base_path (fs, volume);
1199           if (base_path && gtk_file_path_compare (base_path, path) == 0)
1200             label_text = gtk_file_system_volume_get_display_name (fs, volume);
1201
1202           if (base_path)
1203             gtk_file_path_free (base_path);
1204
1205           gtk_file_system_volume_free (fs, volume);
1206
1207           if (label_text)
1208             goto out;
1209         }
1210     
1211       if (gtk_file_system_path_is_local (fs, path))
1212         {
1213           const gchar *home;
1214           gchar *tmp;
1215           gchar *filename;
1216
1217           filename = gtk_file_system_path_to_filename (fs, path);
1218
1219           if (!filename)
1220             goto out;
1221
1222           home = g_get_home_dir ();
1223
1224           /* Munging for psuedo-volumes and files in the user's home tree */
1225           if (home)
1226             {
1227               if (strcmp (filename, home) == 0)
1228                 {
1229                   label_text = g_strdup (_("Home"));
1230                   goto localout;
1231                 }
1232
1233               tmp = g_build_filename (home, "Desktop", NULL);
1234
1235               if (strcmp (filename, tmp) == 0)
1236                 label_text = g_strdup (_("Desktop"));
1237
1238               g_free (tmp);
1239
1240               if (label_text)
1241                 goto out;
1242
1243               if (g_str_has_prefix (filename, home))
1244                 {
1245                   label_text = g_strconcat ("~", filename + strlen (home), NULL);
1246                   goto localout;
1247                 }
1248             }
1249
1250           if (!label_text)
1251             label_text = g_strdup (filename);
1252
1253          localout:
1254           g_free (filename);
1255         }
1256       else
1257         {
1258           gchar *uri;
1259
1260           uri = gtk_file_system_path_to_uri (fs, path);
1261
1262           if (uri)
1263             label_text = uri;
1264         }
1265
1266      out:
1267       gtk_file_paths_free (paths);
1268     }
1269
1270   if (label_text)
1271     {
1272       gtk_label_set_text (GTK_LABEL (priv->label), label_text);
1273       g_free (label_text);
1274     }
1275   else
1276     gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
1277 }
1278
1279 static void
1280 update_entry (GtkFileChooserButton *button)
1281 {
1282   GtkFileChooserButtonPrivate *priv;
1283   GSList *paths;
1284   gchar *filename;
1285
1286   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1287
1288   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1289
1290   if (paths)
1291     {
1292       GtkFileSystem *fs;
1293       GtkFilePath *path;
1294     
1295       path = paths->data;
1296       fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1297     
1298       if (gtk_file_system_path_is_local (fs, path))
1299         {
1300           filename = gtk_file_system_path_to_filename (fs, path);
1301
1302           if (filename)
1303             {
1304               const gchar *home;
1305               gchar *tmp;
1306
1307               if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1308                 {
1309                   tmp = g_strconcat (filename, "/", NULL);
1310                   g_free (filename);
1311                   filename = tmp;
1312                 }
1313
1314               home = g_get_home_dir ();
1315
1316               if (home && g_str_has_prefix (filename, home))
1317                 {
1318                   tmp = g_strconcat ("~", filename + strlen (home), NULL);
1319                   g_free (filename);
1320                   filename = tmp;
1321                 }
1322             }
1323         }
1324       else
1325         filename = gtk_file_system_path_to_uri (fs, path);
1326     }
1327   else
1328     filename = NULL;
1329
1330   if (filename)
1331     {
1332       gchar *entry_text;
1333     
1334       entry_text = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1335       g_free (filename);
1336
1337       gtk_entry_set_text (GTK_ENTRY (priv->entry), entry_text);
1338       g_free (entry_text);
1339     }
1340   else
1341     gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
1342 }
1343
1344 static void
1345 update_dialog (GtkFileChooserButton *button)
1346 {
1347   GtkFileChooserButtonPrivate *priv;
1348   GtkFilePath *current_folder;
1349   GtkFileSystem *fs;
1350   GtkFilePath *folder_part, *full_path;
1351   gchar *file_part;
1352   const gchar *text;
1353   GtkFilePath *base_path;
1354
1355   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1356   file_part = NULL;
1357   folder_part = NULL;
1358   fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1359
1360   text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
1361
1362   base_path = gtk_file_path_new_dup ("/");
1363   gtk_file_system_parse (fs, base_path, text, &folder_part, &file_part, NULL);
1364   gtk_file_path_free (base_path);
1365
1366   switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1367     {
1368     case GTK_FILE_CHOOSER_ACTION_OPEN:
1369       gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1370       if (folder_part)
1371         {
1372           GtkFileFolder *folder;
1373           GtkFileInfo *info;
1374
1375           folder = gtk_file_system_get_folder (fs, folder_part,
1376                                                GTK_FILE_INFO_IS_FOLDER, NULL);
1377
1378           full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1379           info = gtk_file_folder_get_info (folder, full_path, NULL);
1380
1381           /* Entry contents don't exist. */
1382           if (info == NULL)
1383             _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1384                                                        folder_part, NULL);
1385           /* Entry contents are a folder */
1386           else if (gtk_file_info_get_is_folder (info))
1387             _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1388                                                        full_path, NULL);
1389           /* Entry contents must be a file. */
1390           else
1391             _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1392                                            full_path, NULL);
1393
1394           if (info)
1395             gtk_file_info_free (info);
1396
1397           gtk_file_path_free (full_path);
1398         }
1399       break;
1400     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1401       gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1402       if (folder_part)
1403         {
1404           full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1405
1406           /* Entry contents don't exist. */
1407           if (full_path)
1408             _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1409                                            full_path, NULL);
1410           else
1411             _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1412                                                        folder_part, NULL);
1413
1414           gtk_file_path_free (full_path);
1415         }
1416       break;
1417
1418     case GTK_FILE_CHOOSER_ACTION_SAVE:
1419     case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1420       gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1421       if (folder_part)
1422         {
1423           current_folder = _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog));
1424
1425           if (!current_folder ||
1426               gtk_file_path_compare (current_folder, folder_part) != 0)
1427             {
1428               _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1429                                                          folder_part, NULL);
1430               g_signal_emit_by_name (button, "current-folder-changed");
1431             }
1432
1433           if (current_folder)
1434             gtk_file_path_free (current_folder);
1435         }
1436
1437       gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog),
1438                                          file_part);
1439       g_signal_emit_by_name (button, "selection-changed");
1440       break;
1441     }
1442 }
1443
1444 /* ************************ *
1445  *  Child-Widget Callbacks  *
1446  * ************************ */
1447
1448 static void
1449 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
1450                                   gpointer        user_data)
1451 {
1452   g_signal_emit_by_name (user_data, "current-folder-changed");
1453 }
1454
1455 static void
1456 dialog_file_activated_cb (GtkFileChooser *dialog,
1457                           gpointer        user_data)
1458 {
1459   g_signal_emit_by_name (user_data, "file-activated");
1460 }
1461
1462 static void
1463 dialog_selection_changed_cb (GtkFileChooser *dialog,
1464                              gpointer        user_data)
1465 {
1466   GtkFileChooserButtonPrivate *priv;
1467
1468   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1469
1470   g_signal_handler_block (priv->entry, priv->entry_changed_id);
1471   update_entry (user_data);
1472   g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1473   update_icons (user_data);
1474   update_label (user_data);
1475 }
1476
1477 static void
1478 dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
1479                                    gpointer        user_data)
1480 {
1481   GtkFileChooserButtonPrivate *priv;
1482
1483   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1484
1485   g_signal_emit_by_name (user_data, "selection-changed");
1486 }
1487
1488 static void
1489 dialog_update_preview_cb (GtkFileChooser *dialog,
1490                           gpointer        user_data)
1491 {
1492   g_signal_emit_by_name (user_data, "update-preview");
1493 }
1494
1495 static void
1496 dialog_notify_cb (GObject    *dialog,
1497                   GParamSpec *pspec,
1498                   gpointer    user_data)
1499 {
1500   gpointer iface;
1501
1502   iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
1503                                  GTK_TYPE_FILE_CHOOSER);
1504   if (g_object_interface_find_property (iface, pspec->name))
1505     g_object_notify (user_data, pspec->name);
1506 }
1507
1508 static gboolean
1509 dialog_delete_event_cb (GtkWidget *dialog,
1510                         GdkEvent  *event,
1511                         gpointer   user_data)
1512 {
1513   g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
1514
1515   return TRUE;
1516 }
1517
1518 static void
1519 dialog_response_cb (GtkFileChooser *dialog,
1520                     gint            response,
1521                     gpointer        user_data)
1522 {
1523   GtkFileChooserButtonPrivate *priv;
1524
1525   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1526
1527   if (response == GTK_RESPONSE_ACCEPT)
1528     {
1529       g_signal_handler_block (priv->entry, priv->entry_changed_id);
1530       update_entry (user_data);
1531       g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1532       update_label (user_data);
1533       update_icons (user_data);
1534
1535       g_signal_emit_by_name (user_data, "current-folder-changed");
1536       g_signal_emit_by_name (user_data, "selection-changed");
1537     }
1538   else
1539     {
1540       update_dialog (user_data);
1541     }
1542
1543   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
1544 }
1545
1546
1547 static void
1548 button_toggled_cb (GtkToggleButton *real_button,
1549                    gpointer         user_data)
1550 {
1551   GtkFileChooserButtonPrivate *priv;
1552
1553   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1554
1555   if (gtk_toggle_button_get_active (real_button))
1556     {
1557       /* Setup the dialog parent to be chooser button's toplevel, and be modal
1558          as needed. */
1559       if (!GTK_WIDGET_VISIBLE (priv->dialog))
1560         {
1561           GtkWidget *toplevel;
1562
1563           toplevel = gtk_widget_get_toplevel (user_data);
1564
1565           if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1566             {
1567               if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1568                 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (toplevel));
1569               
1570               gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1571                                     gtk_window_get_modal (GTK_WINDOW (toplevel)));
1572             }
1573         }
1574
1575       g_signal_handler_block (priv->dialog,
1576                               priv->dialog_folder_changed_id);
1577       g_signal_handler_block (priv->dialog,
1578                               priv->dialog_file_activated_id);
1579       g_signal_handler_block (priv->dialog,
1580                               priv->dialog_selection_changed_proxy_id);
1581       gtk_widget_set_sensitive (priv->entry, FALSE);
1582       gtk_window_present (GTK_WINDOW (priv->dialog));
1583     }
1584   else
1585     {
1586       g_signal_handler_unblock (priv->dialog,
1587                                 priv->dialog_folder_changed_id);
1588       g_signal_handler_unblock (priv->dialog,
1589                                 priv->dialog_file_activated_id);
1590       g_signal_handler_unblock (priv->dialog,
1591                                 priv->dialog_selection_changed_proxy_id);
1592       gtk_widget_set_sensitive (priv->entry, TRUE);
1593       gtk_widget_hide (priv->dialog);
1594     }
1595 }
1596
1597 static void
1598 button_notify_active_cb (GObject    *real_button,
1599                          GParamSpec *pspec,
1600                          gpointer    user_data)
1601 {
1602   g_object_notify (user_data, "active");
1603 }
1604
1605
1606 static gboolean
1607 update_idler (gpointer user_data)
1608 {
1609   GtkFileChooserButtonPrivate *priv;
1610   gboolean retval;
1611
1612   GDK_THREADS_ENTER ();
1613
1614   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1615
1616   if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry),
1617                                           NULL, NULL))
1618     {
1619       g_signal_handler_block (priv->dialog,
1620                               priv->dialog_selection_changed_id);
1621       update_dialog (user_data);
1622       g_signal_handler_unblock (priv->dialog,
1623                                 priv->dialog_selection_changed_id);
1624       update_icons (user_data);
1625       update_label (user_data);
1626       priv->update_id = 0;
1627       retval = FALSE;
1628     }
1629   else
1630     retval = TRUE;
1631   
1632   GDK_THREADS_LEAVE ();
1633
1634   return retval;
1635 }
1636
1637 static void
1638 entry_changed_cb (GtkEditable *editable,
1639                   gpointer     user_data)
1640 {
1641   GtkFileChooserButtonPrivate *priv;
1642
1643   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1644
1645   if (priv->update_id)
1646     g_source_remove (priv->update_id);
1647
1648   priv->update_id = g_idle_add_full (G_PRIORITY_LOW, update_idler,
1649                                      user_data, NULL);
1650 }
1651
1652 /* Ensure the button height == entry height */
1653 static void
1654 entry_size_allocate_cb (GtkWidget     *entry,
1655                         GtkAllocation *allocation,
1656                         gpointer       user_data)
1657 {
1658   gtk_widget_set_size_request (user_data, -1, allocation->height);
1659 }