]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserbutton.c
Fixes bug #317999:
[~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 "gtkintl.h"
34 #include "gtkbutton.h"
35 #include "gtkcelllayout.h"
36 #include "gtkcellrenderertext.h"
37 #include "gtkcellrendererpixbuf.h"
38 #include "gtkcombobox.h"
39 #include "gtkdnd.h"
40 #include "gtkicontheme.h"
41 #include "gtkiconfactory.h"
42 #include "gtkimage.h"
43 #include "gtklabel.h"
44 #include "gtkliststore.h"
45 #include "gtkstock.h"
46 #include "gtktreemodelfilter.h"
47 #include "gtkvseparator.h"
48 #include "gtkfilechooserdialog.h"
49 #include "gtkfilechooserprivate.h"
50 #include "gtkfilechooserutils.h"
51
52 #include "gtkfilechooserbutton.h"
53
54 #ifdef G_OS_WIN32
55 #include "gtkfilesystemwin32.h"
56 #endif
57
58 #include "gtkprivate.h"
59 #include "gtkalias.h"
60
61 /* **************** *
62  *  Private Macros  *
63  * **************** */
64
65 #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)
66
67 #define DEFAULT_TITLE           N_("Select A File")
68 #define HOME_DISPLAY_NAME       N_("Home")
69 #define DESKTOP_DISPLAY_NAME    N_("Desktop")
70 #define FALLBACK_DISPLAY_NAME   N_("(None)")
71 #define FALLBACK_ICON_NAME      "stock_unknown"
72 #define FALLBACK_ICON_SIZE      16
73
74
75 /* ********************** *
76  *  Private Enumerations  *
77  * ********************** */
78
79 /* Property IDs */
80 enum
81 {
82   PROP_0,
83
84   PROP_DIALOG,
85   PROP_TITLE,
86   PROP_WIDTH_CHARS
87 };
88
89 /* TreeModel Columns */
90 enum
91 {
92   ICON_COLUMN,
93   DISPLAY_NAME_COLUMN,
94   TYPE_COLUMN,
95   DATA_COLUMN,
96   NUM_COLUMNS
97 };
98
99 /* TreeModel Row Types */
100 typedef enum
101 {
102   ROW_TYPE_SPECIAL,
103   ROW_TYPE_VOLUME,
104   ROW_TYPE_SHORTCUT,
105   ROW_TYPE_BOOKMARK_SEPARATOR,
106   ROW_TYPE_BOOKMARK,
107   ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
108   ROW_TYPE_CURRENT_FOLDER,
109   ROW_TYPE_OTHER_SEPARATOR,
110   ROW_TYPE_OTHER,
111
112   ROW_TYPE_INVALID = -1
113 }
114 RowType;
115
116
117 /* ******************** *
118  *  Private Structures  *
119  * ******************** */
120
121 struct _GtkFileChooserButtonPrivate
122 {
123   GtkWidget *dialog;
124   GtkWidget *button;
125   GtkWidget *image;
126   GtkWidget *label;
127   GtkWidget *combo_box;
128   GtkCellRenderer *icon_cell;
129   GtkCellRenderer *name_cell;
130
131   GtkTreeModel *model;
132   GtkTreeModel *filter_model;
133
134   gchar *backend;
135   GtkFileSystem *fs;
136   GtkFilePath *old_path;
137
138   gulong combo_box_changed_id;
139   gulong dialog_file_activated_id;
140   gulong dialog_folder_changed_id;
141   gulong dialog_selection_changed_id;
142   gulong fs_volumes_changed_id;
143   gulong fs_bookmarks_changed_id;
144
145   gint icon_size;
146
147   guint8 n_special;
148   guint8 n_volumes;
149   guint8 n_shortcuts;
150   guint8 n_bookmarks;
151   guint8 has_bookmark_separator       : 1;
152   guint8 has_current_folder_separator : 1;
153   guint8 has_current_folder           : 1;
154   guint8 has_other_separator          : 1;
155
156   /* Used for hiding/showing the dialog when the button is hidden */
157   guint8 active                       : 1;
158
159   /* Used to track whether we need to set a default current folder on ::map() */
160   guint8 folder_has_been_set          : 1;
161 };
162
163
164 /* ************* *
165  *  DnD Support  *
166  * ************* */
167
168 enum
169 {
170   TEXT_PLAIN,
171   TEXT_URI_LIST
172 };
173
174
175 /* ********************* *
176  *  Function Prototypes  *
177  * ********************* */
178
179 /* GtkFileChooserIface Functions */
180 static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
181 static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
182                                                                  const GtkFilePath   *path,
183                                                                  GError             **error);
184 static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
185                                                                  const GtkFilePath   *path,
186                                                                  GError             **error);
187
188 /* GObject Functions */
189 static GObject *gtk_file_chooser_button_constructor        (GType             type,
190                                                             guint             n_params,
191                                                             GObjectConstructParam *params);
192 static void     gtk_file_chooser_button_set_property       (GObject          *object,
193                                                             guint             param_id,
194                                                             const GValue     *value,
195                                                             GParamSpec       *pspec);
196 static void     gtk_file_chooser_button_get_property       (GObject          *object,
197                                                             guint             param_id,
198                                                             GValue           *value,
199                                                             GParamSpec       *pspec);
200 static void     gtk_file_chooser_button_finalize           (GObject          *object);
201
202 /* GtkObject Functions */
203 static void     gtk_file_chooser_button_destroy            (GtkObject        *object);
204
205 /* GtkWidget Functions */
206 static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
207                                                             GdkDragContext   *context,
208                                                             gint              x,
209                                                             gint              y,
210                                                             GtkSelectionData *data,
211                                                             guint             info,
212                                                             guint             drag_time);
213 static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
214 static void     gtk_file_chooser_button_hide_all           (GtkWidget        *widget);
215 static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
216 static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
217 static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
218 static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
219                                                             gboolean          group_cycling);
220 static void     gtk_file_chooser_button_style_set          (GtkWidget        *widget,
221                                                             GtkStyle         *old_style);
222 static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
223                                                             GdkScreen        *old_screen);
224
225 /* Utility Functions */
226 static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
227 static gchar        *get_display_name_for_path    (GtkFileSystem     *fs,
228                                                    const GtkFilePath *path);
229
230 static gint          model_get_type_position      (GtkFileChooserButton *button,
231                                                    RowType               row_type);
232 static void          model_free_row_data          (GtkFileChooserButton *button,
233                                                    GtkTreeIter          *iter);
234 static inline void   model_add_special            (GtkFileChooserButton *button);
235 static inline void   model_add_other              (GtkFileChooserButton *button);
236 static void          model_add_volumes            (GtkFileChooserButton *button,
237                                                    GSList               *volumes);
238 static void          model_add_bookmarks          (GtkFileChooserButton *button,
239                                                    GSList               *bookmarks);
240 static void          model_update_current_folder  (GtkFileChooserButton *button,
241                                                    const GtkFilePath    *path);
242 static void          model_remove_rows            (GtkFileChooserButton *button,
243                                                    gint                  pos,
244                                                    gint                  n_rows);
245
246 static gboolean      filter_model_visible_func    (GtkTreeModel         *model,
247                                                    GtkTreeIter          *iter,
248                                                    gpointer              user_data);
249
250 static gboolean      combo_box_row_separator_func (GtkTreeModel         *model,
251                                                    GtkTreeIter          *iter,
252                                                    gpointer              user_data);
253 static void          name_cell_data_func          (GtkCellLayout        *layout,
254                                                    GtkCellRenderer      *cell,
255                                                    GtkTreeModel         *model,
256                                                    GtkTreeIter          *iter,
257                                                    gpointer              user_data);
258 static void          open_dialog                  (GtkFileChooserButton *button);
259 static void          update_combo_box             (GtkFileChooserButton *button);
260 static void          update_label_and_image       (GtkFileChooserButton *button);
261
262 /* Child Object Callbacks */
263 static void     fs_volumes_changed_cb            (GtkFileSystem  *fs,
264                                                   gpointer        user_data);
265 static void     fs_bookmarks_changed_cb          (GtkFileSystem  *fs,
266                                                   gpointer        user_data);
267
268 static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
269                                                   gpointer        user_data);
270
271 static void     button_clicked_cb                (GtkButton      *real_button,
272                                                   gpointer        user_data);
273
274 static void     dialog_update_preview_cb         (GtkFileChooser *dialog,
275                                                   gpointer        user_data);
276 static void     dialog_selection_changed_cb      (GtkFileChooser *dialog,
277                                                   gpointer        user_data);
278 static void     dialog_file_activated_cb         (GtkFileChooser *dialog,
279                                                   gpointer        user_data);
280 static void     dialog_current_folder_changed_cb (GtkFileChooser *dialog,
281                                                   gpointer        user_data);
282 static void     dialog_notify_cb                 (GObject        *dialog,
283                                                   GParamSpec     *pspec,
284                                                   gpointer        user_data);
285 static gboolean dialog_delete_event_cb           (GtkWidget      *dialog,
286                                                   GdkEvent       *event,
287                                                   gpointer        user_data);
288 static void     dialog_response_cb               (GtkDialog      *dialog,
289                                                   gint            response,
290                                                   gpointer        user_data);
291
292
293 /* ******************* *
294  *  GType Declaration  *
295  * ******************* */
296
297 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
298     G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, gtk_file_chooser_button_file_chooser_iface_init) \
299 });
300
301
302 /* ***************** *
303  *  GType Functions  *
304  * ***************** */
305
306 static void
307 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
308 {
309   GObjectClass *gobject_class;
310   GtkObjectClass *gtkobject_class;
311   GtkWidgetClass *widget_class;
312
313   gobject_class = G_OBJECT_CLASS (class);
314   gtkobject_class = GTK_OBJECT_CLASS (class);
315   widget_class = GTK_WIDGET_CLASS (class);
316
317   gobject_class->constructor = gtk_file_chooser_button_constructor;
318   gobject_class->set_property = gtk_file_chooser_button_set_property;
319   gobject_class->get_property = gtk_file_chooser_button_get_property;
320   gobject_class->finalize = gtk_file_chooser_button_finalize;
321
322   gtkobject_class->destroy = gtk_file_chooser_button_destroy;
323
324   widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
325   widget_class->show_all = gtk_file_chooser_button_show_all;
326   widget_class->hide_all = gtk_file_chooser_button_hide_all;
327   widget_class->show = gtk_file_chooser_button_show;
328   widget_class->hide = gtk_file_chooser_button_hide;
329   widget_class->map = gtk_file_chooser_button_map;
330   widget_class->style_set = gtk_file_chooser_button_style_set;
331   widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
332   widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
333
334   /**
335    * GtkFileChooserButton:dialog:
336    * 
337    * Instance of the #GtkFileChooserDialog associated with the button.
338    *
339    * Since: 2.6
340    */
341   g_object_class_install_property (gobject_class, PROP_DIALOG,
342                                    g_param_spec_object ("dialog",
343                                                         P_("Dialog"),
344                                                         P_("The file chooser dialog to use."),
345                                                         GTK_TYPE_FILE_CHOOSER_DIALOG,
346                                                         (GTK_PARAM_WRITABLE |
347                                                          G_PARAM_CONSTRUCT_ONLY)));
348
349   /**
350    * GtkFileChooserButton:title:
351    * 
352    * Title to put on the #GtkFileChooserDialog associated with the button.
353    *
354    * Since: 2.6
355    */
356   g_object_class_install_property (gobject_class, PROP_TITLE,
357                                    g_param_spec_string ("title",
358                                                         P_("Title"),
359                                                         P_("The title of the file chooser dialog."),
360                                                         _(DEFAULT_TITLE),
361                                                         GTK_PARAM_READWRITE));
362
363   /**
364    * GtkFileChooserButton:width-chars:
365    * 
366    * The width of the entry and label inside the button, in characters.
367    *
368    * Since: 2.6
369    */
370   g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
371                                    g_param_spec_int ("width-chars",
372                                                      P_("Width In Characters"),
373                                                      P_("The desired width of the button widget, in characters."),
374                                                      -1, G_MAXINT, -1,
375                                                      GTK_PARAM_READWRITE));
376
377   _gtk_file_chooser_install_properties (gobject_class);
378
379   g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
380 }
381
382 static void
383 gtk_file_chooser_button_init (GtkFileChooserButton *button)
384 {
385   GtkFileChooserButtonPrivate *priv;
386   GtkWidget *box, *image, *sep;
387   GtkTargetList *target_list;
388
389   priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
390                                       GtkFileChooserButtonPrivate);
391   button->priv = priv;
392
393   priv->icon_size = FALLBACK_ICON_SIZE;
394
395   gtk_widget_push_composite_child ();
396
397   /* Button */
398   priv->button = gtk_button_new ();
399   g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked_cb),
400                     button);
401   gtk_container_add (GTK_CONTAINER (button), priv->button);
402   gtk_widget_show (priv->button);
403
404   box = gtk_hbox_new (FALSE, 4);
405   gtk_container_add (GTK_CONTAINER (priv->button), box);
406   gtk_widget_show (box);
407
408   priv->image = gtk_image_new ();
409   gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0);
410   gtk_widget_show (priv->image);
411
412   priv->label = gtk_label_new (_(FALLBACK_DISPLAY_NAME));
413   gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
414   gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
415   gtk_container_add (GTK_CONTAINER (box), priv->label);
416   gtk_widget_show (priv->label);
417
418   sep = gtk_vseparator_new ();
419   gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
420   gtk_widget_show (sep);
421
422   image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
423                                     GTK_ICON_SIZE_MENU);
424   gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
425   gtk_widget_show (image);
426
427   /* Combo Box */
428   /* Keep in sync with columns enum, line 88 */
429   priv->model =
430     GTK_TREE_MODEL (gtk_list_store_new (NUM_COLUMNS,
431                                         GDK_TYPE_PIXBUF, /* Icon */
432                                         G_TYPE_STRING,   /* Display Name */
433                                         G_TYPE_CHAR,     /* Row Type */
434                                         G_TYPE_POINTER   /* Volume || Path */));
435
436   priv->combo_box = gtk_combo_box_new ();
437   priv->combo_box_changed_id =
438     g_signal_connect (priv->combo_box, "changed",
439                       G_CALLBACK (combo_box_changed_cb), button);
440   gtk_container_add (GTK_CONTAINER (button), priv->combo_box);
441
442   priv->icon_cell = gtk_cell_renderer_pixbuf_new ();
443   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
444                               priv->icon_cell, FALSE);
445   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
446                                  priv->icon_cell, "pixbuf", ICON_COLUMN);
447
448   priv->name_cell = gtk_cell_renderer_text_new ();
449   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
450                               priv->name_cell, TRUE);
451   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
452                                  priv->name_cell, "text", DISPLAY_NAME_COLUMN);
453   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
454                                       priv->name_cell, name_cell_data_func,
455                                       NULL, NULL);
456
457   gtk_widget_pop_composite_child ();
458
459   /* DnD */
460   gtk_drag_dest_set (GTK_WIDGET (button),
461                      (GTK_DEST_DEFAULT_ALL),
462                      NULL, 0,
463                      GDK_ACTION_COPY);
464   target_list = gtk_target_list_new (NULL, 0);
465   gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
466   gtk_target_list_add_text_targets (target_list, TEXT_PLAIN);
467   gtk_drag_dest_set_target_list (GTK_WIDGET (button), target_list);
468   gtk_target_list_unref (target_list);
469 }
470
471
472 /* ******************************* *
473  *  GtkFileChooserIface Functions  *
474  * ******************************* */
475 static void
476 gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
477 {
478   _gtk_file_chooser_delegate_iface_init (iface);
479
480   iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
481   iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
482 }
483
484 static gboolean
485 gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser     *chooser,
486                                              const GtkFilePath  *path,
487                                              GError            **error)
488 {
489   GtkFileChooser *delegate;
490   gboolean retval;
491
492   delegate = g_object_get_qdata (G_OBJECT (chooser),
493                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
494   retval = _gtk_file_chooser_add_shortcut_folder (delegate, path, error);
495
496   if (retval)
497     {
498       GtkFileChooserButtonPrivate *priv;
499       GtkTreeIter iter;
500       gint pos;
501       GdkPixbuf *pixbuf;
502       gchar *display_name;
503
504       priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (chooser);
505
506       pos = model_get_type_position (GTK_FILE_CHOOSER_BUTTON (chooser),
507                                      ROW_TYPE_SHORTCUT);
508       pos += priv->n_shortcuts;
509
510       pixbuf = gtk_file_system_render_icon (priv->fs, path,
511                                             GTK_WIDGET (chooser),
512                                             priv->icon_size, NULL);
513       display_name = get_display_name_for_path (priv->fs, path);
514
515       gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos);
516       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
517                           ICON_COLUMN, pixbuf,
518                           DISPLAY_NAME_COLUMN, display_name,
519                           TYPE_COLUMN, ROW_TYPE_SHORTCUT,
520                           DATA_COLUMN, gtk_file_path_copy (path),
521                           -1);
522       priv->n_shortcuts++;
523
524       if (pixbuf)
525         g_object_unref (pixbuf);
526       g_free (display_name);
527
528       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
529     }
530
531   return retval;
532 }
533
534 static gboolean
535 gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser     *chooser,
536                                                 const GtkFilePath  *path,
537                                                 GError            **error)
538 {
539   GtkFileChooser *delegate;
540   gboolean retval;
541
542   delegate = g_object_get_qdata (G_OBJECT (chooser),
543                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
544
545   retval = _gtk_file_chooser_remove_shortcut_folder (delegate, path, error);
546
547   if (retval)
548     {
549       GtkFileChooserButtonPrivate *priv;
550       GtkTreeIter iter;
551       gint pos;
552       gchar type;
553
554       priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (chooser);
555
556       pos = model_get_type_position (GTK_FILE_CHOOSER_BUTTON (chooser),
557                                      ROW_TYPE_SHORTCUT);
558       g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos));
559
560       do
561         {
562           gpointer data;
563
564           gtk_tree_model_get (priv->model, &iter,
565                               TYPE_COLUMN, &type,
566                               DATA_COLUMN, &data,
567                               -1);
568
569           if (type == ROW_TYPE_SHORTCUT &&
570               data &&
571               gtk_file_path_compare (data, path) == 0)
572             {
573               model_free_row_data (GTK_FILE_CHOOSER_BUTTON (chooser), &iter);
574               gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter);
575               priv->n_shortcuts--;
576               gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
577               update_combo_box (GTK_FILE_CHOOSER_BUTTON (chooser));
578               break;
579             }
580         }
581       while (type == ROW_TYPE_SHORTCUT &&
582              gtk_tree_model_iter_next (priv->model, &iter));
583     }
584
585   return retval;
586 }
587
588
589 /* ******************* *
590  *  GObject Functions  *
591  * ******************* */
592
593 static GObject *
594 gtk_file_chooser_button_constructor (GType                  type,
595                                      guint                  n_params,
596                                      GObjectConstructParam *params)
597 {
598   GObject *object;
599   GtkFileChooserButtonPrivate *priv;
600   GSList *list;
601   char *current_folder;
602
603   object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
604                                                                                   n_params,
605                                                                                   params);
606   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
607
608   if (!priv->dialog)
609     {
610       if (priv->backend)
611         priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
612                                                                  GTK_FILE_CHOOSER_ACTION_OPEN,
613                                                                  priv->backend,
614                                                                  GTK_STOCK_CANCEL,
615                                                                  GTK_RESPONSE_CANCEL,
616                                                                  GTK_STOCK_OPEN,
617                                                                  GTK_RESPONSE_ACCEPT,
618                                                                  NULL);
619       else
620         priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
621                                                     GTK_FILE_CHOOSER_ACTION_OPEN,
622                                                     GTK_STOCK_CANCEL,
623                                                     GTK_RESPONSE_CANCEL,
624                                                     GTK_STOCK_OPEN,
625                                                     GTK_RESPONSE_ACCEPT,
626                                                     NULL);
627
628       gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
629                                        GTK_RESPONSE_ACCEPT);
630       gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
631                                                GTK_RESPONSE_ACCEPT,
632                                                GTK_RESPONSE_CANCEL,
633                                                -1);
634     }
635
636   current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (priv->dialog));
637   if (current_folder != NULL)
638     {
639       priv->folder_has_been_set = TRUE;
640       g_free (current_folder);
641     }
642
643   g_free (priv->backend);
644   priv->backend = NULL;
645
646   g_signal_connect (priv->dialog, "delete-event",
647                     G_CALLBACK (dialog_delete_event_cb), object);
648   g_signal_connect (priv->dialog, "response",
649                     G_CALLBACK (dialog_response_cb), object);
650
651   /* This is used, instead of the standard delegate, to ensure that signals are only
652    * delegated when the OK button is pressed. */
653   g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
654   priv->dialog_folder_changed_id =
655     g_signal_connect (priv->dialog, "current-folder-changed",
656                       G_CALLBACK (dialog_current_folder_changed_cb), object);
657   priv->dialog_file_activated_id =
658     g_signal_connect (priv->dialog, "file-activated",
659                       G_CALLBACK (dialog_file_activated_cb), object);
660   priv->dialog_selection_changed_id =
661     g_signal_connect (priv->dialog, "selection-changed",
662                       G_CALLBACK (dialog_selection_changed_cb), object);
663   g_signal_connect (priv->dialog, "update-preview",
664                     G_CALLBACK (dialog_update_preview_cb), object);
665   g_signal_connect (priv->dialog, "notify",
666                     G_CALLBACK (dialog_notify_cb), object);
667   g_object_add_weak_pointer (G_OBJECT (priv->dialog),
668                              (gpointer *) (&priv->dialog));
669
670   priv->fs =
671     g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
672
673   model_add_special (GTK_FILE_CHOOSER_BUTTON (object));
674
675   list = gtk_file_system_list_volumes (priv->fs);
676   model_add_volumes (GTK_FILE_CHOOSER_BUTTON (object), list);
677   g_slist_free (list);
678
679   list = gtk_file_system_list_bookmarks (priv->fs);
680   model_add_bookmarks (GTK_FILE_CHOOSER_BUTTON (object), list);
681   gtk_file_paths_free (list);
682
683   model_add_other (GTK_FILE_CHOOSER_BUTTON (object));
684
685   priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL);
686   gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
687                                           filter_model_visible_func,
688                                           object, NULL);
689
690   gtk_combo_box_set_model (GTK_COMBO_BOX (priv->combo_box), priv->filter_model);
691   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->combo_box),
692                                         combo_box_row_separator_func,
693                                         NULL, NULL);
694
695   /* set up the action for a user-provided dialog, this also updates
696    * the label, image and combobox
697    */
698   g_object_set (object, 
699                 "action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)),
700                 NULL);
701
702   priv->fs_volumes_changed_id =
703     g_signal_connect (priv->fs, "volumes-changed",
704                       G_CALLBACK (fs_volumes_changed_cb), object);
705   priv->fs_bookmarks_changed_id =
706     g_signal_connect (priv->fs, "bookmarks-changed",
707                       G_CALLBACK (fs_bookmarks_changed_cb), object);
708
709   return object;
710 }
711
712 static void
713 gtk_file_chooser_button_set_property (GObject      *object,
714                                       guint         param_id,
715                                       const GValue *value,
716                                       GParamSpec   *pspec)
717 {
718   GtkFileChooserButtonPrivate *priv;
719
720   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
721
722   switch (param_id)
723     {
724     case PROP_DIALOG:
725       /* Construct-only */
726       priv->dialog = g_value_get_object (value);
727       break;
728     case PROP_WIDTH_CHARS:
729       gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
730                                                g_value_get_int (value));
731       break;
732     case GTK_FILE_CHOOSER_PROP_ACTION:
733       switch (g_value_get_enum (value))
734         {
735         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
736         case GTK_FILE_CHOOSER_ACTION_SAVE:
737           {
738             GEnumClass *eclass;
739             GEnumValue *eval;
740
741             eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION);
742             eval = g_enum_get_value (eclass, g_value_get_enum (value));
743             g_warning ("%s: Choosers of type `%s' do not support `%s'.",
744                        G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);
745
746             g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
747           }
748           break;
749         }
750
751       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
752       update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
753       update_combo_box (GTK_FILE_CHOOSER_BUTTON (object));
754
755       switch (g_value_get_enum (value))
756         {
757         case GTK_FILE_CHOOSER_ACTION_OPEN:
758           gtk_widget_hide (priv->combo_box);
759           gtk_widget_show (priv->button);
760           break;
761         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
762           gtk_widget_hide (priv->button);
763           gtk_widget_show (priv->combo_box);
764           break;
765         default:
766           g_assert_not_reached ();
767           break;
768         }
769       break;
770
771     case PROP_TITLE:
772     case GTK_FILE_CHOOSER_PROP_FILTER:
773     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
774     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
775     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
776     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
777     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
778     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
779     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
780       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
781       break;
782
783     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
784       /* Construct-only */
785       priv->backend = g_value_dup_string (value);
786       break;
787
788     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
789       g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
790                  G_STRFUNC, G_OBJECT_TYPE_NAME (object));
791       break;
792     default:
793       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
794       break;
795     }
796 }
797
798 static void
799 gtk_file_chooser_button_get_property (GObject    *object,
800                                       guint       param_id,
801                                       GValue     *value,
802                                       GParamSpec *pspec)
803 {
804   GtkFileChooserButtonPrivate *priv;
805
806   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
807
808   switch (param_id)
809     {
810     case PROP_WIDTH_CHARS:
811       g_value_set_int (value,
812                        gtk_label_get_width_chars (GTK_LABEL (priv->label)));
813       break;
814
815     case PROP_TITLE:
816     case GTK_FILE_CHOOSER_PROP_ACTION:
817     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
818     case GTK_FILE_CHOOSER_PROP_FILTER:
819     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
820     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
821     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
822     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
823     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
824     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
825     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
826     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
827       g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
828       break;
829
830     default:
831       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
832       break;
833     }
834 }
835
836 static void
837 gtk_file_chooser_button_finalize (GObject *object)
838 {
839   GtkFileChooserButtonPrivate *priv;
840   GtkTreeIter iter;
841
842   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
843
844   if (priv->old_path)
845     gtk_file_path_free (priv->old_path);
846
847   g_assert (gtk_tree_model_get_iter_first (priv->model, &iter));
848
849   do
850     {
851       model_free_row_data (GTK_FILE_CHOOSER_BUTTON (object), &iter);
852     }
853   while (gtk_tree_model_iter_next (priv->model, &iter));
854
855   g_object_unref (priv->model);
856   g_object_unref (priv->filter_model);
857
858   g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id);
859   g_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id);
860   g_object_unref (priv->fs);
861
862   if (G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize != NULL)
863     (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize) (object);
864 }
865
866 /* ********************* *
867  *  GtkObject Functions  *
868  * ********************* */
869
870 static void
871 gtk_file_chooser_button_destroy (GtkObject *object)
872 {
873   GtkFileChooserButtonPrivate *priv;
874
875   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
876
877   if (priv->dialog != NULL)
878     gtk_widget_destroy (priv->dialog);
879
880   if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
881     (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
882 }
883
884
885 /* ********************* *
886  *  GtkWidget Functions  *
887  * ********************* */
888
889 static void
890 gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
891                                             GdkDragContext   *context,
892                                             gint              x,
893                                             gint              y,
894                                             GtkSelectionData *data,
895                                             guint             info,
896                                             guint             drag_time)
897 {
898   GtkFileChooserButtonPrivate *priv;
899   GtkFilePath *path;
900   gchar *text;
901
902   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
903     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
904                                                                                     context,
905                                                                                     x, y,
906                                                                                     data, info,
907                                                                                     drag_time);
908
909   if (widget == NULL || context == NULL || data == NULL || data->length < 0)
910     return;
911
912   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
913
914   switch (info)
915     {
916     case TEXT_URI_LIST:
917       {
918         gchar **uris;
919         GtkFilePath *base_path;
920         guint i;
921         gboolean selected;
922
923         uris = gtk_selection_data_get_uris (data);
924         
925         if (uris == NULL)
926           break;
927
928         selected = FALSE;
929         for (i = 0; !selected && uris[i] != NULL; i++)
930           {
931             path = gtk_file_system_uri_to_path (priv->fs, uris[i]);
932
933             base_path = NULL;
934             if (path != NULL &&
935                 gtk_file_system_get_parent (priv->fs, path, &base_path, NULL))
936               {
937                 GtkFileFolder *folder;
938                 GtkFileInfo *info;
939
940                 folder = gtk_file_system_get_folder (priv->fs, base_path,
941                                                      GTK_FILE_INFO_IS_FOLDER,
942                                                      NULL);
943
944                 info = gtk_file_folder_get_info (folder, path, NULL);
945
946                 if (info != NULL)
947                   {
948                     GtkFileChooserAction action;
949
950                     g_object_get (priv->dialog, "action", &action, NULL);
951
952                     selected = 
953                       (((action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER &&
954                          gtk_file_info_get_is_folder (info)) ||
955                         (action == GTK_FILE_CHOOSER_ACTION_OPEN &&
956                          !gtk_file_info_get_is_folder (info))) &&
957                         _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
958                                                        path, NULL));
959
960                     gtk_file_info_free (info);
961                   }
962                 else
963                   selected = FALSE;
964
965                 gtk_file_path_free (base_path);
966               }
967
968             gtk_file_path_free (path);
969           }
970
971         g_strfreev (uris);
972       }
973       break;
974
975     case TEXT_PLAIN:
976       text = gtk_selection_data_get_text (data);
977       path = gtk_file_path_new_steal (text);
978       _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), path,
979                                      NULL);
980       gtk_file_path_free (path);
981       break;
982
983     default:
984       break;
985     }
986
987   gtk_drag_finish (context, TRUE, FALSE, drag_time);
988 }
989
990 static void
991 gtk_file_chooser_button_show_all (GtkWidget *widget)
992 {
993   gtk_widget_show (widget);
994 }
995
996 static void
997 gtk_file_chooser_button_hide_all (GtkWidget *widget)
998 {
999   gtk_widget_hide (widget);
1000 }
1001
1002 static void
1003 gtk_file_chooser_button_show (GtkWidget *widget)
1004 {
1005   GtkFileChooserButtonPrivate *priv;
1006
1007   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
1008
1009   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
1010     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
1011
1012   if (priv->active)
1013     open_dialog (GTK_FILE_CHOOSER_BUTTON (widget));
1014 }
1015
1016 static void
1017 gtk_file_chooser_button_hide (GtkWidget *widget)
1018 {
1019   GtkFileChooserButtonPrivate *priv;
1020
1021   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
1022
1023   gtk_widget_hide (priv->dialog);
1024
1025   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
1026     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
1027 }
1028
1029 static void
1030 gtk_file_chooser_button_map (GtkWidget *widget)
1031 {
1032   GtkFileChooserButtonPrivate *priv;
1033
1034   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
1035
1036   if (!priv->folder_has_been_set)
1037     {
1038       char *current_working_dir;
1039
1040       current_working_dir = g_get_current_dir ();
1041       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), current_working_dir);
1042       g_free (current_working_dir);
1043
1044       priv->folder_has_been_set = TRUE;
1045     }
1046
1047   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map)
1048     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map) (widget);
1049 }
1050
1051 static gboolean
1052 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
1053                                            gboolean   group_cycling)
1054 {
1055   GtkFileChooserButtonPrivate *priv;
1056
1057   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
1058   switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1059     {
1060     case GTK_FILE_CHOOSER_ACTION_OPEN:
1061       gtk_widget_grab_focus (priv->button);
1062       break;
1063     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1064       return gtk_widget_mnemonic_activate (priv->combo_box, group_cycling);
1065       break;
1066     default:
1067       g_assert_not_reached ();
1068       break;
1069     }
1070
1071   return TRUE;
1072 }
1073
1074 /* Changes the icons wherever it is needed */
1075 static void
1076 change_icon_theme (GtkFileChooserButton *button)
1077 {
1078   GtkFileChooserButtonPrivate *priv;
1079   GtkSettings *settings;
1080   GtkIconTheme *theme;
1081   GtkTreeIter iter;
1082   gint width, height;
1083
1084   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1085
1086   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
1087
1088   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
1089                                          &width, &height))
1090     priv->icon_size = MAX (width, height);
1091   else
1092     priv->icon_size = FALLBACK_ICON_SIZE;
1093
1094   update_label_and_image (button);
1095
1096   g_assert (gtk_tree_model_get_iter_first (priv->model, &iter));
1097
1098   theme = get_icon_theme (GTK_WIDGET (button));
1099
1100   do
1101     {
1102       GdkPixbuf *pixbuf;
1103       gchar type;
1104       gpointer data;
1105
1106       type = ROW_TYPE_INVALID;
1107       gtk_tree_model_get (priv->model, &iter,
1108                           TYPE_COLUMN, &type,
1109                           DATA_COLUMN, &data,
1110                           -1);
1111
1112       switch (type)
1113         {
1114         case ROW_TYPE_SPECIAL:
1115         case ROW_TYPE_SHORTCUT:
1116         case ROW_TYPE_BOOKMARK:
1117         case ROW_TYPE_CURRENT_FOLDER:
1118           if (data)
1119             pixbuf = gtk_file_system_render_icon (priv->fs, data,
1120                                                   GTK_WIDGET (button),
1121                                                   priv->icon_size, NULL);
1122           else
1123             pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
1124                                                priv->icon_size, 0, NULL);
1125           break;
1126         case ROW_TYPE_VOLUME:
1127           if (data)
1128             pixbuf = gtk_file_system_volume_render_icon (priv->fs, data,
1129                                                          GTK_WIDGET (button),
1130                                                          priv->icon_size,
1131                                                          NULL);
1132           else
1133             pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
1134                                                priv->icon_size, 0, NULL);
1135           break;
1136         default:
1137           continue;
1138           break;
1139         }
1140
1141       if (pixbuf)
1142         width = MAX (width, gdk_pixbuf_get_width (pixbuf));
1143
1144       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
1145                           ICON_COLUMN, pixbuf,
1146                           -1);
1147
1148       if (pixbuf)
1149         g_object_unref (pixbuf);
1150     }
1151   while (gtk_tree_model_iter_next (priv->model, &iter));
1152
1153   g_object_set (button->priv->icon_cell,
1154                 "width", width,
1155                 NULL);
1156 }
1157
1158 static void
1159 gtk_file_chooser_button_style_set (GtkWidget *widget,
1160                                    GtkStyle  *old_style)
1161 {
1162   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
1163     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
1164                                                                            old_style);
1165
1166   if (gtk_widget_has_screen (widget))
1167     change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1168 }
1169
1170 static void
1171 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
1172                                         GdkScreen *old_screen)
1173 {
1174   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
1175     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
1176                                                                                 old_screen);
1177
1178   change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget)); 
1179 }
1180
1181
1182 /* ******************* *
1183  *  Utility Functions  *
1184  * ******************* */
1185
1186 /* General */
1187 static GtkIconTheme *
1188 get_icon_theme (GtkWidget *widget)
1189 {
1190   if (gtk_widget_has_screen (widget))
1191     return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1192
1193   return gtk_icon_theme_get_default ();
1194 }
1195
1196 static gchar *
1197 get_display_name_for_path (GtkFileSystem     *fs,
1198                            const GtkFilePath *path)
1199 {
1200   GtkFilePath *parent_path;
1201   GtkFileFolder *folder;
1202   gchar *retval;
1203
1204   parent_path = NULL;
1205   retval = NULL;
1206
1207   gtk_file_system_get_parent (fs, path, &parent_path, NULL);
1208
1209   folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path,
1210                                        GTK_FILE_INFO_DISPLAY_NAME, NULL);
1211
1212   if (folder)
1213     {
1214       GtkFileInfo *info;
1215
1216       info = gtk_file_folder_get_info (folder, path, NULL);
1217       g_object_unref (folder);
1218
1219       if (info)
1220         {
1221           retval = g_strdup (gtk_file_info_get_display_name (info));
1222           gtk_file_info_free (info);
1223         }
1224     }
1225
1226   if (parent_path)
1227     gtk_file_path_free (parent_path);
1228
1229   if (!retval)
1230     retval = g_strdup (_(FALLBACK_DISPLAY_NAME));
1231
1232   return retval;
1233 }
1234
1235 /* Shortcuts Model */
1236 static gint
1237 model_get_type_position (GtkFileChooserButton *button,
1238                          RowType               row_type)
1239 {
1240   gint retval = 0;
1241
1242   if (row_type == ROW_TYPE_SPECIAL)
1243     return retval;
1244
1245   retval += button->priv->n_special;
1246
1247   if (row_type == ROW_TYPE_VOLUME)
1248     return retval;
1249
1250   retval += button->priv->n_volumes;
1251
1252   if (row_type == ROW_TYPE_SHORTCUT)
1253     return retval;
1254
1255   retval += button->priv->n_shortcuts;
1256
1257   if (row_type == ROW_TYPE_BOOKMARK_SEPARATOR)
1258     return retval;
1259
1260   retval += button->priv->has_bookmark_separator;
1261
1262   if (row_type == ROW_TYPE_BOOKMARK)
1263     return retval;
1264
1265   retval += button->priv->n_bookmarks;
1266
1267   if (row_type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR)
1268     return retval;
1269
1270   retval += button->priv->has_current_folder_separator;
1271
1272   if (row_type == ROW_TYPE_CURRENT_FOLDER)
1273     return retval;
1274
1275   retval += button->priv->has_current_folder;
1276
1277   if (row_type == ROW_TYPE_OTHER_SEPARATOR)
1278     return retval;
1279
1280   retval += button->priv->has_other_separator;
1281
1282   if (row_type == ROW_TYPE_OTHER)
1283     return retval;
1284
1285   g_assert_not_reached ();
1286   return -1;
1287 }
1288
1289 static void
1290 model_free_row_data (GtkFileChooserButton *button,
1291                      GtkTreeIter          *iter)
1292 {
1293   gchar type;
1294   gpointer data;
1295
1296   gtk_tree_model_get (button->priv->model, iter,
1297                       TYPE_COLUMN, &type,
1298                       DATA_COLUMN, &data,
1299                       -1);
1300
1301   switch (type)
1302     {
1303     case ROW_TYPE_SPECIAL:
1304     case ROW_TYPE_SHORTCUT:
1305     case ROW_TYPE_BOOKMARK:
1306     case ROW_TYPE_CURRENT_FOLDER:
1307       gtk_file_path_free (data);
1308       break;
1309     case ROW_TYPE_VOLUME:
1310       gtk_file_system_volume_free (button->priv->fs, data);
1311       break;
1312     default:
1313       break;
1314     }
1315 }
1316
1317 static inline void
1318 model_add_special (GtkFileChooserButton *button)
1319 {
1320   const gchar *homedir;
1321   gchar *desktopdir = NULL;
1322   GtkListStore *store;
1323   GtkTreeIter iter;
1324   GtkFilePath *path;
1325   GdkPixbuf *pixbuf;
1326   gint pos;
1327
1328   store = GTK_LIST_STORE (button->priv->model);
1329   pos = model_get_type_position (button, ROW_TYPE_SPECIAL);
1330
1331   homedir = g_get_home_dir ();
1332
1333   if (homedir)
1334     {
1335       path = gtk_file_system_filename_to_path (button->priv->fs, homedir);
1336       pixbuf = gtk_file_system_render_icon (button->priv->fs, path,
1337                                             GTK_WIDGET (button),
1338                                             button->priv->icon_size, NULL);
1339       gtk_list_store_insert (store, &iter, pos);
1340       pos++;
1341       gtk_list_store_set (store, &iter,
1342                           ICON_COLUMN, pixbuf,
1343                           DISPLAY_NAME_COLUMN, _(HOME_DISPLAY_NAME),
1344                           TYPE_COLUMN, ROW_TYPE_SPECIAL,
1345                           DATA_COLUMN, path,
1346                           -1);
1347
1348       if (pixbuf)
1349         g_object_unref (pixbuf);
1350       button->priv->n_special++;
1351
1352 #ifndef G_OS_WIN32
1353       desktopdir = g_build_filename (homedir, DESKTOP_DISPLAY_NAME, NULL);
1354 #endif
1355     }
1356
1357 #ifdef G_OS_WIN32
1358   desktopdir = _gtk_file_system_win32_get_desktop ();
1359 #endif
1360
1361   if (desktopdir)
1362     {
1363       path = gtk_file_system_filename_to_path (button->priv->fs, desktopdir);
1364       g_free (desktopdir);
1365       pixbuf = gtk_file_system_render_icon (button->priv->fs, path,
1366                                             GTK_WIDGET (button),
1367                                             button->priv->icon_size, NULL);
1368       gtk_list_store_insert (store, &iter, pos);
1369       pos++;
1370       gtk_list_store_set (store, &iter,
1371                           TYPE_COLUMN, ROW_TYPE_SPECIAL,
1372                           ICON_COLUMN, pixbuf,
1373                           DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME),
1374                           DATA_COLUMN, path,
1375                           -1);
1376
1377       if (pixbuf)
1378         g_object_unref (pixbuf);
1379       button->priv->n_special++;
1380     }
1381 }
1382
1383 static void
1384 model_add_volumes (GtkFileChooserButton *button,
1385                    GSList               *volumes)
1386 {
1387   GtkListStore *store;
1388   gint pos;
1389
1390   if (!volumes)
1391     return;
1392
1393   store = GTK_LIST_STORE (button->priv->model);
1394   pos = model_get_type_position (button, ROW_TYPE_VOLUME);
1395
1396   do
1397     {
1398       GtkTreeIter iter;
1399       GdkPixbuf *pixbuf;
1400       gchar *display_name;
1401
1402       pixbuf = gtk_file_system_volume_render_icon (button->priv->fs,
1403                                                    volumes->data,
1404                                                    GTK_WIDGET (button),
1405                                                    button->priv->icon_size,
1406                                                    NULL);
1407       display_name = gtk_file_system_volume_get_display_name (button->priv->fs,
1408                                                               volumes->data);
1409
1410       gtk_list_store_insert (store, &iter, pos);
1411       gtk_list_store_set (store, &iter,
1412                           ICON_COLUMN, pixbuf,
1413                           DISPLAY_NAME_COLUMN, display_name,
1414                           TYPE_COLUMN, ROW_TYPE_VOLUME,
1415                           DATA_COLUMN, volumes->data,
1416                           -1);
1417
1418       if (pixbuf)
1419         g_object_unref (pixbuf);
1420       g_free (display_name);
1421
1422       button->priv->n_volumes++;
1423       pos++;
1424       volumes = volumes->next;
1425     }
1426   while (volumes);
1427 }
1428
1429 static void
1430 model_add_bookmarks (GtkFileChooserButton *button,
1431                      GSList               *bookmarks)
1432 {
1433   GtkListStore *store;
1434   GtkTreeIter iter;
1435   gint pos;
1436
1437   if (!bookmarks)
1438     return;
1439
1440   store = GTK_LIST_STORE (button->priv->model);
1441   pos = model_get_type_position (button, ROW_TYPE_BOOKMARK_SEPARATOR);
1442
1443   if (!button->priv->has_bookmark_separator)
1444     {
1445       gtk_list_store_insert (store, &iter, pos);
1446       gtk_list_store_set (store, &iter,
1447                           ICON_COLUMN, NULL,
1448                           DISPLAY_NAME_COLUMN, NULL,
1449                           TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR,
1450                           DATA_COLUMN, NULL,
1451                           -1);
1452       button->priv->has_bookmark_separator = TRUE;
1453     }
1454
1455   do
1456     {
1457       GdkPixbuf *pixbuf;
1458       gchar *display_name;
1459
1460       pos++;
1461       pixbuf = gtk_file_system_render_icon (button->priv->fs, bookmarks->data,
1462                                             GTK_WIDGET (button),
1463                                             button->priv->icon_size, NULL);
1464       display_name = get_display_name_for_path (button->priv->fs,
1465                                                 bookmarks->data);
1466
1467       gtk_list_store_insert (store, &iter, pos);
1468       gtk_list_store_set (store, &iter,
1469                           ICON_COLUMN, pixbuf,
1470                           DISPLAY_NAME_COLUMN, display_name,
1471                           TYPE_COLUMN, ROW_TYPE_BOOKMARK,
1472                           DATA_COLUMN, gtk_file_path_copy (bookmarks->data),
1473                           -1);
1474       if (pixbuf)
1475         g_object_unref (pixbuf);
1476       g_free (display_name);
1477
1478       button->priv->n_bookmarks++;
1479       bookmarks = bookmarks->next;
1480     }
1481   while (bookmarks);
1482 }
1483
1484 static void
1485 model_update_current_folder (GtkFileChooserButton *button,
1486                              const GtkFilePath    *path)
1487 {
1488   GtkListStore *store;
1489   GtkTreeIter iter;
1490   gint pos;
1491   GdkPixbuf *pixbuf;
1492   gchar *display_name;
1493
1494   if (!path) 
1495     return;
1496
1497   store = GTK_LIST_STORE (button->priv->model);
1498
1499   if (!button->priv->has_current_folder_separator)
1500     {
1501       pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER_SEPARATOR);
1502       gtk_list_store_insert (store, &iter, pos);
1503       gtk_list_store_set (store, &iter,
1504                           ICON_COLUMN, NULL,
1505                           DISPLAY_NAME_COLUMN, NULL,
1506                           TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
1507                           DATA_COLUMN, NULL,
1508                           -1);
1509       button->priv->has_current_folder_separator = TRUE;
1510     }
1511
1512   pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
1513   if (!button->priv->has_current_folder)
1514     {
1515       gtk_list_store_insert (store, &iter, pos);
1516       button->priv->has_current_folder = TRUE;
1517     }
1518   else
1519     {
1520       gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos);
1521       model_free_row_data (button, &iter);
1522     }
1523
1524   pixbuf = gtk_file_system_render_icon (button->priv->fs, path,
1525                                         GTK_WIDGET (button),
1526                                         button->priv->icon_size, NULL);
1527   display_name = get_display_name_for_path (button->priv->fs, path);
1528   gtk_list_store_set (store, &iter,
1529                       ICON_COLUMN, pixbuf,
1530                       DISPLAY_NAME_COLUMN, display_name,
1531                       TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
1532                       DATA_COLUMN, gtk_file_path_copy (path),
1533                       -1);
1534   if (pixbuf)
1535     g_object_unref (pixbuf);
1536   g_free (display_name);
1537 }
1538
1539 static inline void
1540 model_add_other (GtkFileChooserButton *button)
1541 {
1542   GtkListStore *store;
1543   GtkTreeIter iter;
1544   gint pos;
1545   
1546   store = GTK_LIST_STORE (button->priv->model);
1547   pos = model_get_type_position (button, ROW_TYPE_OTHER_SEPARATOR);
1548
1549   gtk_list_store_insert (store, &iter, pos);
1550   gtk_list_store_set (store, &iter,
1551                       ICON_COLUMN, NULL,
1552                       DISPLAY_NAME_COLUMN, NULL,
1553                       TYPE_COLUMN, ROW_TYPE_OTHER_SEPARATOR,
1554                       DATA_COLUMN, NULL,
1555                       -1);
1556   button->priv->has_other_separator = TRUE;
1557   pos++;
1558
1559   gtk_list_store_insert (store, &iter, pos);
1560   gtk_list_store_set (store, &iter,
1561                       ICON_COLUMN, NULL,
1562                       DISPLAY_NAME_COLUMN, _("Other..."),
1563                       TYPE_COLUMN, ROW_TYPE_OTHER,
1564                       DATA_COLUMN, NULL,
1565                       -1);
1566 }
1567
1568 static void
1569 model_remove_rows (GtkFileChooserButton *button,
1570                    gint                  pos,
1571                    gint                  n_rows)
1572 {
1573   GtkListStore *store;
1574
1575   if (!n_rows)
1576     return;
1577
1578   store = GTK_LIST_STORE (button->priv->model);
1579
1580   do
1581     {
1582       GtkTreeIter iter;
1583
1584       if (!gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos))
1585         g_assert_not_reached ();
1586
1587       model_free_row_data (button, &iter);
1588       gtk_list_store_remove (store, &iter);
1589       n_rows--;
1590     }
1591   while (n_rows);
1592 }
1593
1594 /* Filter Model */
1595 static inline gboolean
1596 test_if_path_is_visible (GtkFileSystem     *fs,
1597                          const GtkFilePath *path,
1598                          gboolean           local_only)
1599 {
1600   GtkFilePath *parent_path;
1601   GtkFileFolder *folder;
1602   GtkFileInfo *info;
1603
1604   if (!path)
1605     return FALSE;
1606
1607   if (local_only && !gtk_file_system_path_is_local (fs, path))
1608     return FALSE;
1609
1610   parent_path = NULL;
1611   gtk_file_system_get_parent (fs, path, &parent_path, NULL);
1612
1613   folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path,
1614                                        GTK_FILE_INFO_IS_FOLDER, NULL);
1615   gtk_file_path_free (parent_path);
1616
1617   if (folder)
1618     {
1619       info = gtk_file_folder_get_info (folder, path, NULL);
1620       g_object_unref (folder);
1621     }
1622   else
1623     info = NULL;
1624
1625   if (!info)
1626     return FALSE;
1627   else if (!gtk_file_info_get_is_folder (info))
1628     {
1629       gtk_file_info_free (info);
1630       return FALSE;
1631     }
1632
1633   gtk_file_info_free (info);
1634
1635   return TRUE;
1636 }
1637
1638 static gboolean
1639 filter_model_visible_func (GtkTreeModel *model,
1640                            GtkTreeIter  *iter,
1641                            gpointer      user_data)
1642 {
1643   GtkFileChooserButtonPrivate *priv;
1644   gchar type;
1645   gpointer data;
1646   gboolean local_only, retval;
1647
1648   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1649   type = ROW_TYPE_INVALID;
1650   data = NULL;
1651   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog));
1652
1653   gtk_tree_model_get (model, iter,
1654                       TYPE_COLUMN, &type,
1655                       DATA_COLUMN, &data,
1656                       -1);
1657
1658   switch (type)
1659     {
1660     case ROW_TYPE_CURRENT_FOLDER:
1661       retval = TRUE;
1662       break;
1663     case ROW_TYPE_SPECIAL:
1664     case ROW_TYPE_SHORTCUT:
1665     case ROW_TYPE_BOOKMARK:
1666       retval = test_if_path_is_visible (priv->fs, data, local_only);
1667       break;
1668     case ROW_TYPE_VOLUME:
1669       {
1670         GtkFilePath *base_path;
1671
1672         base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
1673         if (base_path)
1674           {
1675             retval = (!local_only ||
1676                       gtk_file_system_path_is_local (priv->fs, base_path));
1677             gtk_file_path_free (base_path);
1678           }
1679         else
1680           retval = FALSE;
1681       }
1682       break;
1683     default:
1684       retval = TRUE;
1685       break;
1686     }
1687
1688   return retval;
1689 }
1690
1691 /* Combo Box */
1692 static void
1693 name_cell_data_func (GtkCellLayout   *layout,
1694                      GtkCellRenderer *cell,
1695                      GtkTreeModel    *model,
1696                      GtkTreeIter     *iter,
1697                      gpointer         user_data)
1698 {
1699   gchar type;
1700
1701   type = 0;
1702   gtk_tree_model_get (model, iter,
1703                       TYPE_COLUMN, &type,
1704                       -1);
1705
1706   if (type == ROW_TYPE_CURRENT_FOLDER)
1707     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1708   else
1709     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
1710 }
1711
1712 static gboolean
1713 combo_box_row_separator_func (GtkTreeModel *model,
1714                               GtkTreeIter  *iter,
1715                               gpointer      user_data)
1716 {
1717   gchar type = ROW_TYPE_INVALID;
1718
1719   gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, -1);
1720
1721   return (type == ROW_TYPE_BOOKMARK_SEPARATOR ||
1722           type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR ||
1723           type == ROW_TYPE_OTHER_SEPARATOR);
1724 }                         
1725
1726 static void
1727 update_combo_box (GtkFileChooserButton *button)
1728 {
1729   GtkFileChooserButtonPrivate *priv;
1730   GSList *paths;
1731   GtkTreeIter iter;
1732   gboolean row_found;
1733
1734   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1735
1736   g_assert (gtk_tree_model_get_iter_first (priv->filter_model, &iter));
1737
1738   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1739
1740   row_found = FALSE;
1741
1742   do
1743     {
1744       gchar type;
1745       gpointer data;
1746
1747       type = ROW_TYPE_INVALID;
1748       data = NULL;
1749
1750       gtk_tree_model_get (priv->filter_model, &iter,
1751                           TYPE_COLUMN, &type,
1752                           DATA_COLUMN, &data,
1753                           -1);
1754     
1755       switch (type)
1756         {
1757         case ROW_TYPE_SPECIAL:
1758         case ROW_TYPE_SHORTCUT:
1759         case ROW_TYPE_BOOKMARK:
1760         case ROW_TYPE_CURRENT_FOLDER:
1761           row_found = (paths &&
1762                        paths->data &&
1763                        gtk_file_path_compare (data, paths->data) == 0);
1764           break;
1765         case ROW_TYPE_VOLUME:
1766           {
1767             GtkFilePath *base_path;
1768
1769             base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
1770             row_found = (paths &&
1771                          paths->data &&
1772                          gtk_file_path_compare (base_path, paths->data) == 0);
1773             gtk_file_path_free (base_path);
1774           }
1775           break;
1776         default:
1777           row_found = FALSE;
1778           break;
1779         }
1780
1781       if (row_found)
1782         {
1783           g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
1784           gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box),
1785                                          &iter);
1786           g_signal_handler_unblock (priv->combo_box,
1787                                     priv->combo_box_changed_id);
1788         }
1789     }
1790   while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter));
1791
1792   /* If it hasn't been found already, update & select the current-folder row. */
1793   if (!row_found && paths && paths->data)
1794     {
1795       GtkTreeIter filter_iter;
1796       gint pos;
1797     
1798       model_update_current_folder (button, paths->data);
1799       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
1800
1801       pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
1802       g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos));
1803
1804       gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
1805                                                         &filter_iter, &iter);
1806
1807       g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
1808       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
1809       g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id);
1810     }
1811
1812   gtk_file_paths_free (paths);
1813 }
1814
1815 /* Button */
1816 static void
1817 update_label_and_image (GtkFileChooserButton *button)
1818 {
1819   GtkFileChooserButtonPrivate *priv;
1820   GdkPixbuf *pixbuf;
1821   gchar *label_text;
1822   GSList *paths;
1823
1824   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1825   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
1826   label_text = NULL;
1827   pixbuf = NULL;
1828
1829   if (paths)
1830     {
1831       GtkFilePath *path, *parent_path;
1832       GtkFileSystemVolume *volume;
1833       GtkFileFolder *folder;
1834     
1835       path = paths->data;
1836
1837       volume = gtk_file_system_get_volume_for_path (priv->fs, path);
1838       if (volume)
1839         {
1840           GtkFilePath *base_path;
1841
1842           base_path = gtk_file_system_volume_get_base_path (priv->fs, volume);
1843           if (base_path && gtk_file_path_compare (base_path, path) == 0)
1844             {
1845               label_text = gtk_file_system_volume_get_display_name (priv->fs,
1846                                                                     volume);
1847               pixbuf = gtk_file_system_volume_render_icon (priv->fs, volume,
1848                                                            GTK_WIDGET (button),
1849                                                            priv->icon_size,
1850                                                            NULL);
1851             }
1852
1853           if (base_path)
1854             gtk_file_path_free (base_path);
1855
1856           gtk_file_system_volume_free (priv->fs, volume);
1857
1858           if (label_text)
1859             goto out;
1860         }
1861
1862       if (!pixbuf)
1863         pixbuf = gtk_file_system_render_icon (priv->fs, path,
1864                                               GTK_WIDGET (button),
1865                                               priv->icon_size, NULL);
1866
1867       parent_path = NULL;
1868       gtk_file_system_get_parent (priv->fs, path, &parent_path, NULL);
1869
1870       folder = gtk_file_system_get_folder (priv->fs,
1871                                            parent_path ? parent_path : path,
1872                                            GTK_FILE_INFO_DISPLAY_NAME, NULL);
1873       gtk_file_path_free (parent_path);
1874
1875       if (folder)
1876         {
1877           GtkFileInfo *info;
1878
1879           info = gtk_file_folder_get_info (folder, path, NULL);
1880           g_object_unref (folder);
1881
1882           if (info)
1883             {
1884               label_text = g_strdup (gtk_file_info_get_display_name (info));
1885               gtk_file_info_free (info);
1886             }
1887         }
1888
1889      out:
1890       gtk_file_paths_free (paths);
1891     }
1892
1893   if (label_text)
1894     {
1895       gtk_label_set_text (GTK_LABEL (priv->label), label_text);
1896       g_free (label_text);
1897     }
1898   else
1899     gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME));
1900   
1901   if (!pixbuf)
1902     pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1903                                        FALLBACK_ICON_NAME,
1904                                        priv->icon_size, 0, NULL);
1905
1906   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
1907   if (pixbuf)
1908     g_object_unref (pixbuf);
1909 }
1910
1911
1912 /* ************************ *
1913  *  Child Object Callbacks  *
1914  * ************************ */
1915
1916 /* File System */
1917 static void
1918 fs_volumes_changed_cb (GtkFileSystem *fs,
1919                        gpointer       user_data)
1920 {
1921   GtkFileChooserButtonPrivate *priv;
1922   GSList *volumes;
1923
1924   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1925   
1926   model_remove_rows (user_data,
1927                      model_get_type_position (user_data, ROW_TYPE_VOLUME),
1928                      priv->n_volumes);
1929
1930   priv->n_volumes = 0;
1931
1932   volumes = gtk_file_system_list_volumes (fs);
1933   model_add_volumes (user_data, volumes);
1934   g_slist_free (volumes);
1935
1936   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
1937
1938   update_label_and_image (user_data);
1939   update_combo_box (user_data);
1940 }
1941
1942 static void
1943 fs_bookmarks_changed_cb (GtkFileSystem *fs,
1944                          gpointer       user_data)
1945 {
1946   GtkFileChooserButtonPrivate *priv;
1947   GSList *bookmarks;
1948
1949   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1950
1951   bookmarks = gtk_file_system_list_bookmarks (fs);
1952   if (!bookmarks)
1953     {
1954       model_remove_rows (user_data,
1955                          model_get_type_position (user_data,
1956                                                   ROW_TYPE_BOOKMARK_SEPARATOR),
1957                          (priv->n_bookmarks + priv->has_bookmark_separator));
1958       priv->has_bookmark_separator = FALSE;
1959     }
1960   else
1961     model_remove_rows (user_data,
1962                        model_get_type_position (user_data, ROW_TYPE_BOOKMARK),
1963                        priv->n_bookmarks);
1964
1965   priv->n_bookmarks = 0;
1966   model_add_bookmarks (user_data, bookmarks);
1967   gtk_file_paths_free (bookmarks);
1968
1969   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
1970
1971   update_label_and_image (user_data);
1972   update_combo_box (user_data);
1973 }
1974
1975 /* Dialog */
1976 static void
1977 open_dialog (GtkFileChooserButton *button)
1978 {
1979   GtkFileChooserButtonPrivate *priv;
1980
1981   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1982
1983   /* Setup the dialog parent to be chooser button's toplevel, and be modal
1984      as needed. */
1985   if (!GTK_WIDGET_VISIBLE (priv->dialog))
1986     {
1987       GtkWidget *toplevel;
1988
1989       toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
1990
1991       if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1992         {
1993           if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1994             gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
1995                                           GTK_WINDOW (toplevel));
1996               
1997           gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1998                                 gtk_window_get_modal (GTK_WINDOW (toplevel)));
1999         }
2000     }
2001
2002   if (!priv->active)
2003     {
2004       GSList *paths;
2005
2006       g_signal_handler_block (priv->dialog,
2007                               priv->dialog_folder_changed_id);
2008       g_signal_handler_block (priv->dialog,
2009                               priv->dialog_file_activated_id);
2010       g_signal_handler_block (priv->dialog,
2011                               priv->dialog_selection_changed_id);
2012       paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
2013       if (paths)
2014         {
2015           if (paths->data)
2016             priv->old_path = gtk_file_path_copy (paths->data);
2017
2018           gtk_file_paths_free (paths);
2019         }
2020
2021       priv->active = TRUE;
2022     }
2023
2024   gtk_widget_set_sensitive (priv->combo_box, FALSE);
2025   gtk_window_present (GTK_WINDOW (priv->dialog));
2026 }
2027
2028 /* Combo Box */
2029 static void
2030 combo_box_changed_cb (GtkComboBox *combo_box,
2031                       gpointer     user_data)
2032 {
2033   GtkTreeIter iter;
2034
2035   if (gtk_combo_box_get_active_iter (combo_box, &iter))
2036     {
2037       GtkFileChooserButtonPrivate *priv;
2038       gchar type;
2039       gpointer data;
2040
2041       priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
2042
2043       type = ROW_TYPE_INVALID;
2044       data = NULL;
2045
2046       gtk_tree_model_get (priv->filter_model, &iter,
2047                           TYPE_COLUMN, &type,
2048                           DATA_COLUMN, &data,
2049                           -1);
2050
2051       switch (type)
2052         {
2053         case ROW_TYPE_SPECIAL:
2054         case ROW_TYPE_SHORTCUT:
2055         case ROW_TYPE_BOOKMARK:
2056         case ROW_TYPE_CURRENT_FOLDER:
2057           gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
2058           if (data)
2059             _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
2060                                                        data, NULL);
2061           break;
2062         case ROW_TYPE_VOLUME:
2063           {
2064             GtkFilePath *base_path;
2065
2066             gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
2067             base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
2068             if (base_path)
2069               {
2070                 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
2071                                                            base_path, NULL);
2072                 gtk_file_path_free (base_path);
2073               }
2074           }
2075           break;
2076         case ROW_TYPE_OTHER:
2077           open_dialog (user_data);
2078           break;
2079         default:
2080           break;
2081         }
2082     }
2083 }
2084
2085 /* Button */
2086 static void
2087 button_clicked_cb (GtkButton *real_button,
2088                    gpointer   user_data)
2089 {
2090   open_dialog (user_data);
2091 }
2092
2093 /* Dialog */
2094 static void
2095 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
2096                                   gpointer        user_data)
2097 {
2098   GtkFileChooserButton *button;
2099   GtkFileChooserButtonPrivate *priv;
2100
2101   button = GTK_FILE_CHOOSER_BUTTON (user_data);
2102   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
2103
2104   priv->folder_has_been_set = TRUE;
2105
2106   g_signal_emit_by_name (button, "current-folder-changed");
2107 }
2108
2109 static void
2110 dialog_file_activated_cb (GtkFileChooser *dialog,
2111                           gpointer        user_data)
2112 {
2113   g_signal_emit_by_name (user_data, "file-activated");
2114 }
2115
2116 static void
2117 dialog_selection_changed_cb (GtkFileChooser *dialog,
2118                              gpointer        user_data)
2119 {
2120   update_label_and_image (user_data);
2121   update_combo_box (user_data);
2122   g_signal_emit_by_name (user_data, "selection-changed");
2123 }
2124
2125 static void
2126 dialog_update_preview_cb (GtkFileChooser *dialog,
2127                           gpointer        user_data)
2128 {
2129   g_signal_emit_by_name (user_data, "update-preview");
2130 }
2131
2132 static void
2133 dialog_notify_cb (GObject    *dialog,
2134                   GParamSpec *pspec,
2135                   gpointer    user_data)
2136 {
2137   gpointer iface;
2138
2139   iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
2140                                  GTK_TYPE_FILE_CHOOSER);
2141   if (g_object_interface_find_property (iface, pspec->name))
2142     g_object_notify (user_data, pspec->name);
2143
2144   if (g_ascii_strcasecmp (pspec->name, "local-only") == 0)
2145     {
2146       GtkFileChooserButtonPrivate *priv;
2147
2148       priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
2149
2150       if (priv->has_current_folder)
2151         {
2152           GtkTreeIter iter;
2153           gint pos;
2154           gpointer data;
2155
2156           pos = model_get_type_position (user_data,
2157                                          ROW_TYPE_CURRENT_FOLDER);
2158           g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos));
2159
2160           data = NULL;
2161           gtk_tree_model_get (priv->model, &iter, DATA_COLUMN, &data, -1);
2162
2163           /* If the path isn't local but we're in local-only mode now, remove
2164            * the custom-folder row */
2165           if (data &&
2166               (!gtk_file_system_path_is_local (priv->fs, data) &&
2167                gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog))))
2168             {
2169               pos--;
2170               model_remove_rows (user_data, pos, 2);
2171             }
2172         }
2173
2174       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2175       update_combo_box (user_data);
2176     }
2177 }
2178
2179 static gboolean
2180 dialog_delete_event_cb (GtkWidget *dialog,
2181                         GdkEvent  *event,
2182                         gpointer   user_data)
2183 {
2184   g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
2185
2186   return TRUE;
2187 }
2188
2189 static void
2190 dialog_response_cb (GtkDialog *dialog,
2191                     gint       response,
2192                     gpointer   user_data)
2193 {
2194   GtkFileChooserButtonPrivate *priv;
2195
2196   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
2197
2198   if (response == GTK_RESPONSE_ACCEPT)
2199     {
2200       g_signal_emit_by_name (user_data, "current-folder-changed");
2201       g_signal_emit_by_name (user_data, "selection-changed");
2202     }
2203   else if (priv->old_path)
2204     {
2205       switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)))
2206         {
2207         case GTK_FILE_CHOOSER_ACTION_OPEN:
2208           _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (dialog), priv->old_path,
2209                                          NULL);
2210           break;
2211         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
2212           _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (dialog),
2213                                                      priv->old_path, NULL);
2214           break;
2215         default:
2216           g_assert_not_reached ();
2217           break;
2218         }
2219     }
2220   else
2221     gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (dialog));
2222
2223   if (priv->old_path)
2224     {
2225       gtk_file_path_free (priv->old_path);
2226       priv->old_path = NULL;
2227     }
2228
2229   update_label_and_image (user_data);
2230   update_combo_box (user_data);
2231   
2232   if (priv->active)
2233     {
2234       g_signal_handler_unblock (priv->dialog,
2235                                 priv->dialog_folder_changed_id);
2236       g_signal_handler_unblock (priv->dialog,
2237                                 priv->dialog_file_activated_id);
2238       g_signal_handler_unblock (priv->dialog,
2239                                 priv->dialog_selection_changed_id);
2240       priv->active = FALSE;
2241     }
2242
2243   gtk_widget_set_sensitive (priv->combo_box, TRUE);
2244   gtk_widget_hide (priv->dialog);
2245 }
2246
2247
2248 /* ************************************************************************** *
2249  *  Public API                                                                *
2250  * ************************************************************************** */
2251
2252 /**
2253  * gtk_file_chooser_button_new:
2254  * @title: the title of the browse dialog.
2255  * @action: the open mode for the widget.
2256  * 
2257  * Creates a new file-selecting button widget.
2258  * 
2259  * Returns: a new button widget.
2260  * 
2261  * Since: 2.6
2262  **/
2263 GtkWidget *
2264 gtk_file_chooser_button_new (const gchar          *title,
2265                              GtkFileChooserAction  action)
2266 {
2267   g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2268                         action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL);
2269
2270   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
2271                        "action", action,
2272                        "title", (title ? title : _(DEFAULT_TITLE)),
2273                        NULL);
2274 }
2275
2276 /**
2277  * gtk_file_chooser_button_new_with_backend:
2278  * @title: the title of the browse dialog.
2279  * @action: the open mode for the widget.
2280  * @backend: the name of the #GtkFileSystem backend to use.
2281  * 
2282  * Creates a new file-selecting button widget using @backend.
2283  * 
2284  * Returns: a new button widget.
2285  * 
2286  * Since: 2.6
2287  **/
2288 GtkWidget *
2289 gtk_file_chooser_button_new_with_backend (const gchar          *title,
2290                                           GtkFileChooserAction  action,
2291                                           const gchar          *backend)
2292 {
2293   g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2294                         action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL);
2295
2296   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
2297                        "action", action,
2298                        "title", (title ? title : _(DEFAULT_TITLE)),
2299                        "file-system-backend", backend,
2300                        NULL);
2301 }
2302
2303 /**
2304  * gtk_file_chooser_button_new_with_dialog:
2305  * @dialog: the #GtkFileChooserDialog widget to use.
2306  * 
2307  * Creates a #GtkFileChooserButton widget which uses @dialog as it's
2308  * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
2309  * subclass).
2310  * 
2311  * Returns: a new button widget.
2312  * 
2313  * Since: 2.6
2314  **/
2315 GtkWidget *
2316 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
2317 {
2318   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
2319
2320   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
2321                        "dialog", dialog,
2322                        NULL);
2323 }
2324
2325 /**
2326  * gtk_file_chooser_button_set_title:
2327  * @button: the button widget to modify.
2328  * @title: the new browse dialog title.
2329  * 
2330  * Modifies the @title of the browse dialog used by @button.
2331  * 
2332  * Since: 2.6
2333  **/
2334 void
2335 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
2336                                    const gchar          *title)
2337 {
2338   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
2339
2340   gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
2341   g_object_notify (G_OBJECT (button), "title");
2342 }
2343
2344 /**
2345  * gtk_file_chooser_button_get_title:
2346  * @button: the button widget to examine.
2347  * 
2348  * Retrieves the title of the browse dialog used by @button. The returned value
2349  * should not be modified or freed.
2350  * 
2351  * Returns: a pointer to the browse dialog's title.
2352  * 
2353  * Since: 2.6
2354  **/
2355 G_CONST_RETURN gchar *
2356 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
2357 {
2358   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
2359
2360   return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
2361 }
2362
2363 /**
2364  * gtk_file_chooser_button_get_width_chars:
2365  * @button: the button widget to examine.
2366  * 
2367  * Retrieves the width in characters of the @button widget's entry and/or label.
2368  * 
2369  * Returns: an integer width (in characters) that the button will use to size itself.
2370  * 
2371  * Since: 2.6
2372  **/
2373 gint
2374 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
2375 {
2376   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
2377
2378   return gtk_label_get_width_chars (GTK_LABEL (button->priv->label));
2379 }
2380
2381 /**
2382  * gtk_file_chooser_button_set_width_chars:
2383  * @button: the button widget to examine.
2384  * @n_chars: the new width, in characters.
2385  * 
2386  * Sets the width (in characters) that @button will use to @n_chars.
2387  * 
2388  * Since: 2.6
2389  **/
2390 void
2391 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
2392                                          gint                  n_chars)
2393 {
2394   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
2395
2396   gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
2397   g_object_notify (G_OBJECT (button), "width-chars");
2398 }
2399
2400 #define __GTK_FILE_CHOOSER_BUTTON_C__
2401 #include "gtkaliasdef.c"