]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserbutton.c
filechooserbutton: New test for opening the Other item in the combo box and then...
[~andy/gtk] / gtk / gtkfilechooserbutton.c
1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
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, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "config.h"
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28
29 #include <string.h>
30
31 #include "gtkintl.h"
32 #include "gtkbutton.h"
33 #include "gtkcelllayout.h"
34 #include "gtkcellrenderertext.h"
35 #include "gtkcellrendererpixbuf.h"
36 #include "gtkcombobox.h"
37 #include "gtkdnd.h"
38 #include "gtkicontheme.h"
39 #include "gtkiconfactory.h"
40 #include "gtkimage.h"
41 #include "gtklabel.h"
42 #include "gtkliststore.h"
43 #include "gtkstock.h"
44 #include "gtktreemodelfilter.h"
45 #include "gtkseparator.h"
46 #include "gtkfilechooserdialog.h"
47 #include "gtkfilechooserprivate.h"
48 #include "gtkfilechooserutils.h"
49 #include "gtkmarshalers.h"
50
51 #include "gtkfilechooserbutton.h"
52
53 #include "gtkorientable.h"
54
55 #include "gtktypebuiltins.h"
56 #include "gtkprivate.h"
57 #include "gtksettings.h"
58
59
60 /**
61  * SECTION:gtkfilechooserbutton
62  * @Short_description: A button to launch a file selection dialog
63  * @Title: GtkFileChooserButton
64  * @See_also:#GtkFileChooserDialog
65  *
66  * The #GtkFileChooserButton is a widget that lets the user select a
67  * file.  It implements the #GtkFileChooser interface.  Visually, it is a
68  * file name with a button to bring up a #GtkFileChooserDialog.
69  * The user can then use that dialog to change the file associated with
70  * that button.  This widget does not support setting the
71  * #GtkFileChooser:select-multiple property to %TRUE.
72  *
73  * <example>
74  * <title>Create a button to let the user select a file in /etc</title>
75  * <programlisting>
76  * {
77  *   GtkWidget *button;
78  *
79  *   button = gtk_file_chooser_button_new (_("Select a file"),
80  *                                         GTK_FILE_CHOOSER_ACTION_OPEN);
81  *   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (button),
82  *                                        "/etc");
83  * }
84  * </programlisting>
85  * </example>
86  *
87  * The #GtkFileChooserButton supports the #GtkFileChooserAction<!-- -->s
88  * %GTK_FILE_CHOOSER_ACTION_OPEN and %GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER.
89  *
90  * <important>
91  * The #GtkFileChooserButton will ellipsize the label,
92  * and thus will thus request little horizontal space.  To give the button
93  * more space, you should call gtk_widget_get_preferred_size(),
94  * gtk_file_chooser_button_set_width_chars(), or pack the button in
95  * such a way that other interface elements give space to the widget.
96  * </important>
97  */
98
99
100 /* **************** *
101  *  Private Macros  *
102  * **************** */
103
104 #define DEFAULT_TITLE           N_("Select a File")
105 #define DESKTOP_DISPLAY_NAME    N_("Desktop")
106 #define FALLBACK_DISPLAY_NAME   N_("(None)") /* this string is used in gtk+/gtk/tests/filechooser.c - change it there if you change it here */
107 #define FALLBACK_ICON_NAME      "stock_unknown"
108 #define FALLBACK_ICON_SIZE      16
109
110
111 /* ********************** *
112  *  Private Enumerations  *
113  * ********************** */
114
115 /* Property IDs */
116 enum
117 {
118   PROP_0,
119
120   PROP_DIALOG,
121   PROP_FOCUS_ON_CLICK,
122   PROP_TITLE,
123   PROP_WIDTH_CHARS
124 };
125
126 /* Signals */
127 enum
128 {
129   FILE_SET,
130   LAST_SIGNAL
131 };
132
133 /* TreeModel Columns */
134 enum
135 {
136   ICON_COLUMN,
137   DISPLAY_NAME_COLUMN,
138   TYPE_COLUMN,
139   DATA_COLUMN,
140   IS_FOLDER_COLUMN,
141   CANCELLABLE_COLUMN,
142   NUM_COLUMNS
143 };
144
145 /* TreeModel Row Types */
146 typedef enum
147 {
148   ROW_TYPE_SPECIAL,
149   ROW_TYPE_VOLUME,
150   ROW_TYPE_SHORTCUT,
151   ROW_TYPE_BOOKMARK_SEPARATOR,
152   ROW_TYPE_BOOKMARK,
153   ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
154   ROW_TYPE_CURRENT_FOLDER,
155   ROW_TYPE_OTHER_SEPARATOR,
156   ROW_TYPE_OTHER,
157   ROW_TYPE_EMPTY_SELECTION,
158
159   ROW_TYPE_INVALID = -1
160 }
161 RowType;
162
163
164 /* ******************** *
165  *  Private Structures  *
166  * ******************** */
167
168 struct _GtkFileChooserButtonPrivate
169 {
170   GtkWidget *dialog;
171   GtkWidget *button;
172   GtkWidget *image;
173   GtkWidget *label;
174   GtkWidget *combo_box;
175   GtkCellRenderer *icon_cell;
176   GtkCellRenderer *name_cell;
177
178   GtkTreeModel *model;
179   GtkTreeModel *filter_model;
180
181   GtkFileSystem *fs;
182   GFile *selection_while_inactive;
183   GFile *current_folder_while_inactive;
184
185   gulong combo_box_changed_id;
186   gulong dialog_file_activated_id;
187   gulong dialog_folder_changed_id;
188   gulong dialog_selection_changed_id;
189   gulong fs_volumes_changed_id;
190   gulong fs_bookmarks_changed_id;
191
192   GCancellable *dnd_select_folder_cancellable;
193   GCancellable *update_button_cancellable;
194   GSList *change_icon_theme_cancellables;
195
196   gint icon_size;
197
198   guint8 n_special;
199   guint8 n_volumes;
200   guint8 n_shortcuts;
201   guint8 n_bookmarks;
202   guint  has_bookmark_separator       : 1;
203   guint  has_current_folder_separator : 1;
204   guint  has_current_folder           : 1;
205   guint  has_other_separator          : 1;
206
207   /* Used for hiding/showing the dialog when the button is hidden */
208   guint  active                       : 1;
209
210   guint  focus_on_click               : 1;
211 };
212
213
214 /* ************* *
215  *  DnD Support  *
216  * ************* */
217
218 enum
219 {
220   TEXT_PLAIN,
221   TEXT_URI_LIST
222 };
223
224
225 /* ********************* *
226  *  Function Prototypes  *
227  * ********************* */
228
229 /* GtkFileChooserIface Functions */
230 static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
231 static gboolean gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
232                                                             GFile             *file,
233                                                             GError           **error);
234 static GFile *gtk_file_chooser_button_get_current_folder (GtkFileChooser    *chooser);
235 static gboolean gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
236                                                      GFile          *file,
237                                                      GError        **error);
238 static void gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
239                                                    GFile          *file);
240 static void gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser);
241 static GSList *gtk_file_chooser_button_get_files (GtkFileChooser *chooser);
242 static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
243                                                                  GFile               *file,
244                                                                  GError             **error);
245 static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
246                                                                  GFile               *file,
247                                                                  GError             **error);
248
249 /* GObject Functions */
250 static GObject *gtk_file_chooser_button_constructor        (GType             type,
251                                                             guint             n_params,
252                                                             GObjectConstructParam *params);
253 static void     gtk_file_chooser_button_set_property       (GObject          *object,
254                                                             guint             param_id,
255                                                             const GValue     *value,
256                                                             GParamSpec       *pspec);
257 static void     gtk_file_chooser_button_get_property       (GObject          *object,
258                                                             guint             param_id,
259                                                             GValue           *value,
260                                                             GParamSpec       *pspec);
261 static void     gtk_file_chooser_button_finalize           (GObject          *object);
262
263 /* GtkWidget Functions */
264 static void     gtk_file_chooser_button_destroy            (GtkWidget        *widget);
265 static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
266                                                             GdkDragContext   *context,
267                                                             gint              x,
268                                                             gint              y,
269                                                             GtkSelectionData *data,
270                                                             guint             type,
271                                                             guint             drag_time);
272 static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
273 static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
274 static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
275 static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
276 static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
277                                                             gboolean          group_cycling);
278 static void     gtk_file_chooser_button_style_updated      (GtkWidget        *widget);
279 static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
280                                                             GdkScreen        *old_screen);
281
282 /* Utility Functions */
283 static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
284 static void          set_info_for_file_at_iter         (GtkFileChooserButton *fs,
285                                                         GFile                *file,
286                                                         GtkTreeIter          *iter);
287
288 static gint          model_get_type_position      (GtkFileChooserButton *button,
289                                                    RowType               row_type);
290 static void          model_free_row_data          (GtkFileChooserButton *button,
291                                                    GtkTreeIter          *iter);
292 static void          model_add_special            (GtkFileChooserButton *button);
293 static void          model_add_other              (GtkFileChooserButton *button);
294 static void          model_add_empty_selection    (GtkFileChooserButton *button);
295 static void          model_add_volumes            (GtkFileChooserButton *button,
296                                                    GSList               *volumes);
297 static void          model_add_bookmarks          (GtkFileChooserButton *button,
298                                                    GSList               *bookmarks);
299 static void          model_update_current_folder  (GtkFileChooserButton *button,
300                                                    GFile                *file);
301 static void          model_remove_rows            (GtkFileChooserButton *button,
302                                                    gint                  pos,
303                                                    gint                  n_rows);
304
305 static gboolean      filter_model_visible_func    (GtkTreeModel         *model,
306                                                    GtkTreeIter          *iter,
307                                                    gpointer              user_data);
308
309 static gboolean      combo_box_row_separator_func (GtkTreeModel         *model,
310                                                    GtkTreeIter          *iter,
311                                                    gpointer              user_data);
312 static void          name_cell_data_func          (GtkCellLayout        *layout,
313                                                    GtkCellRenderer      *cell,
314                                                    GtkTreeModel         *model,
315                                                    GtkTreeIter          *iter,
316                                                    gpointer              user_data);
317 static void          open_dialog                  (GtkFileChooserButton *button);
318 static void          update_combo_box             (GtkFileChooserButton *button);
319 static void          update_label_and_image       (GtkFileChooserButton *button);
320
321 /* Child Object Callbacks */
322 static void     fs_volumes_changed_cb            (GtkFileSystem  *fs,
323                                                   gpointer        user_data);
324 static void     fs_bookmarks_changed_cb          (GtkFileSystem  *fs,
325                                                   gpointer        user_data);
326
327 static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
328                                                   gpointer        user_data);
329 static void     combo_box_notify_popup_shown_cb  (GObject        *object,
330                                                   GParamSpec     *pspec,
331                                                   gpointer        user_data);
332
333 static void     button_clicked_cb                (GtkButton      *real_button,
334                                                   gpointer        user_data);
335
336 static void     dialog_update_preview_cb         (GtkFileChooser *dialog,
337                                                   gpointer        user_data);
338 static void     dialog_selection_changed_cb      (GtkFileChooser *dialog,
339                                                   gpointer        user_data);
340 static void     dialog_file_activated_cb         (GtkFileChooser *dialog,
341                                                   gpointer        user_data);
342 static void     dialog_current_folder_changed_cb (GtkFileChooser *dialog,
343                                                   gpointer        user_data);
344 static void     dialog_notify_cb                 (GObject        *dialog,
345                                                   GParamSpec     *pspec,
346                                                   gpointer        user_data);
347 static gboolean dialog_delete_event_cb           (GtkWidget      *dialog,
348                                                   GdkEvent       *event,
349                                                   gpointer        user_data);
350 static void     dialog_response_cb               (GtkDialog      *dialog,
351                                                   gint            response,
352                                                   gpointer        user_data);
353
354 static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 };
355
356 /* ******************* *
357  *  GType Declaration  *
358  * ******************* */
359
360 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_BOX, { \
361     G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, gtk_file_chooser_button_file_chooser_iface_init) \
362 })
363
364
365 /* ***************** *
366  *  GType Functions  *
367  * ***************** */
368
369 static void
370 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
371 {
372   GObjectClass *gobject_class;
373   GtkWidgetClass *widget_class;
374
375   gobject_class = G_OBJECT_CLASS (class);
376   widget_class = GTK_WIDGET_CLASS (class);
377
378   gobject_class->constructor = gtk_file_chooser_button_constructor;
379   gobject_class->set_property = gtk_file_chooser_button_set_property;
380   gobject_class->get_property = gtk_file_chooser_button_get_property;
381   gobject_class->finalize = gtk_file_chooser_button_finalize;
382
383   widget_class->destroy = gtk_file_chooser_button_destroy;
384   widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
385   widget_class->show_all = gtk_file_chooser_button_show_all;
386   widget_class->show = gtk_file_chooser_button_show;
387   widget_class->hide = gtk_file_chooser_button_hide;
388   widget_class->map = gtk_file_chooser_button_map;
389   widget_class->style_updated = gtk_file_chooser_button_style_updated;
390   widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
391   widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
392
393   /**
394    * GtkFileChooserButton::file-set:
395    * @widget: the object which received the signal.
396    *
397    * The ::file-set signal is emitted when the user selects a file.
398    *
399    * Note that this signal is only emitted when the <emphasis>user</emphasis>
400    * changes the file.
401    *
402    * Since: 2.12
403    */
404   file_chooser_button_signals[FILE_SET] =
405     g_signal_new (I_("file-set"),
406                   G_TYPE_FROM_CLASS (gobject_class),
407                   G_SIGNAL_RUN_FIRST,
408                   G_STRUCT_OFFSET (GtkFileChooserButtonClass, file_set),
409                   NULL, NULL,
410                   _gtk_marshal_VOID__VOID,
411                   G_TYPE_NONE, 0);
412
413   /**
414    * GtkFileChooserButton:dialog:
415    * 
416    * Instance of the #GtkFileChooserDialog associated with the button.
417    *
418    * Since: 2.6
419    */
420   g_object_class_install_property (gobject_class, PROP_DIALOG,
421                                    g_param_spec_object ("dialog",
422                                                         P_("Dialog"),
423                                                         P_("The file chooser dialog to use."),
424                                                         GTK_TYPE_FILE_CHOOSER,
425                                                         (GTK_PARAM_WRITABLE |
426                                                          G_PARAM_CONSTRUCT_ONLY)));
427
428   /**
429    * GtkFileChooserButton:focus-on-click:
430    * 
431    * Whether the #GtkFileChooserButton button grabs focus when it is clicked
432    * with the mouse.
433    *
434    * Since: 2.10
435    */
436   g_object_class_install_property (gobject_class,
437                                    PROP_FOCUS_ON_CLICK,
438                                    g_param_spec_boolean ("focus-on-click",
439                                                          P_("Focus on click"),
440                                                          P_("Whether the button grabs focus when it is clicked with the mouse"),
441                                                          TRUE,
442                                                          GTK_PARAM_READWRITE));
443   
444   /**
445    * GtkFileChooserButton:title:
446    * 
447    * Title to put on the #GtkFileChooserDialog associated with the button.
448    *
449    * Since: 2.6
450    */
451   g_object_class_install_property (gobject_class, PROP_TITLE,
452                                    g_param_spec_string ("title",
453                                                         P_("Title"),
454                                                         P_("The title of the file chooser dialog."),
455                                                         _(DEFAULT_TITLE),
456                                                         GTK_PARAM_READWRITE));
457
458   /**
459    * GtkFileChooserButton:width-chars:
460    * 
461    * The width of the entry and label inside the button, in characters.
462    *
463    * Since: 2.6
464    */
465   g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
466                                    g_param_spec_int ("width-chars",
467                                                      P_("Width In Characters"),
468                                                      P_("The desired width of the button widget, in characters."),
469                                                      -1, G_MAXINT, -1,
470                                                      GTK_PARAM_READWRITE));
471
472   _gtk_file_chooser_install_properties (gobject_class);
473
474   g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
475 }
476
477 static void
478 gtk_file_chooser_button_init (GtkFileChooserButton *button)
479 {
480   GtkFileChooserButtonPrivate *priv;
481   GtkWidget *box, *image, *sep;
482   GtkTargetList *target_list;
483
484   priv = button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
485                                                      GTK_TYPE_FILE_CHOOSER_BUTTON,
486                                                      GtkFileChooserButtonPrivate);
487
488   priv->icon_size = FALLBACK_ICON_SIZE;
489   priv->focus_on_click = TRUE;
490
491   gtk_widget_push_composite_child ();
492
493   /* Button */
494   priv->button = gtk_button_new ();
495   g_signal_connect (priv->button, "clicked",
496                     G_CALLBACK (button_clicked_cb), button);
497   gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
498   gtk_widget_set_halign (priv->button, GTK_ALIGN_FILL);
499   gtk_widget_show (priv->button);
500
501   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
502   gtk_container_add (GTK_CONTAINER (priv->button), box);
503   gtk_widget_show (box);
504
505   priv->image = gtk_image_new ();
506   gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0);
507   gtk_widget_show (priv->image);
508
509   priv->label = gtk_label_new (_(FALLBACK_DISPLAY_NAME));
510   gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
511   gtk_widget_set_halign (priv->label, GTK_ALIGN_START);
512   gtk_widget_set_valign (priv->label, GTK_ALIGN_CENTER);
513   gtk_box_pack_start (GTK_BOX (box), priv->label, TRUE, TRUE, 0);
514   //gtk_container_add (GTK_CONTAINER (box), priv->label);
515   gtk_widget_show (priv->label);
516
517   sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
518   gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
519   gtk_widget_show (sep);
520
521   image = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
522   gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
523   gtk_widget_show (image);
524
525   /* Combo Box */
526   /* Keep in sync with columns enum, line 88 */
527   priv->model =
528     GTK_TREE_MODEL (gtk_list_store_new (NUM_COLUMNS,
529                                         GDK_TYPE_PIXBUF, /* ICON_COLUMN */
530                                         G_TYPE_STRING,   /* DISPLAY_NAME_COLUMN */
531                                         G_TYPE_CHAR,     /* TYPE_COLUMN */
532                                         G_TYPE_POINTER   /* DATA_COLUMN (Volume || Path) */,
533                                         G_TYPE_BOOLEAN   /* IS_FOLDER_COLUMN */,
534                                         G_TYPE_POINTER   /* CANCELLABLE_COLUMN */));
535
536   priv->combo_box = gtk_combo_box_new ();
537   priv->combo_box_changed_id = g_signal_connect (priv->combo_box, "changed",
538                                                  G_CALLBACK (combo_box_changed_cb), button);
539
540   g_signal_connect (priv->combo_box, "notify::popup-shown",
541                     G_CALLBACK (combo_box_notify_popup_shown_cb), button);
542
543   gtk_box_pack_start (GTK_BOX (button), priv->combo_box, TRUE, TRUE, 0);
544   gtk_widget_set_halign (priv->combo_box, GTK_ALIGN_FILL);
545
546   priv->icon_cell = gtk_cell_renderer_pixbuf_new ();
547   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
548                               priv->icon_cell, FALSE);
549   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
550                                  priv->icon_cell, "pixbuf", ICON_COLUMN);
551
552   priv->name_cell = gtk_cell_renderer_text_new ();
553   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
554                               priv->name_cell, TRUE);
555   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
556                                  priv->name_cell, "text", DISPLAY_NAME_COLUMN);
557   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
558                                       priv->name_cell, name_cell_data_func,
559                                       NULL, NULL);
560
561   gtk_widget_pop_composite_child ();
562
563   /* DnD */
564   gtk_drag_dest_set (GTK_WIDGET (button),
565                      (GTK_DEST_DEFAULT_ALL),
566                      NULL, 0,
567                      GDK_ACTION_COPY);
568   target_list = gtk_target_list_new (NULL, 0);
569   gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
570   gtk_target_list_add_text_targets (target_list, TEXT_PLAIN);
571   gtk_drag_dest_set_target_list (GTK_WIDGET (button), target_list);
572   gtk_target_list_unref (target_list);
573 }
574
575
576 /* ******************************* *
577  *  GtkFileChooserIface Functions  *
578  * ******************************* */
579 static void
580 gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
581 {
582   _gtk_file_chooser_delegate_iface_init (iface);
583
584   iface->set_current_folder = gtk_file_chooser_button_set_current_folder;
585   iface->get_current_folder = gtk_file_chooser_button_get_current_folder;
586   iface->select_file = gtk_file_chooser_button_select_file;
587   iface->unselect_file = gtk_file_chooser_button_unselect_file;
588   iface->unselect_all = gtk_file_chooser_button_unselect_all;
589   iface->get_files = gtk_file_chooser_button_get_files;
590   iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
591   iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
592 }
593
594 static gboolean
595 gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
596                                             GFile             *file,
597                                             GError           **error)
598 {
599   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
600   GtkFileChooserButtonPrivate *priv = button->priv;
601   GtkFileChooser *delegate;
602
603   delegate = g_object_get_qdata (G_OBJECT (chooser),
604                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
605
606   if (priv->active)
607     return gtk_file_chooser_set_current_folder_file (delegate, file, error);
608   else
609     {
610       if (priv->current_folder_while_inactive)
611         g_object_unref (priv->current_folder_while_inactive);
612
613       priv->current_folder_while_inactive = g_object_ref (file);
614
615       update_combo_box (button);
616
617       g_signal_emit_by_name (button, "current-folder-changed");
618
619       return TRUE;
620     }
621 }
622
623 static GFile *
624 gtk_file_chooser_button_get_current_folder (GtkFileChooser *chooser)
625 {
626   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
627   GtkFileChooserButtonPrivate *priv = button->priv;
628   GtkFileChooser *delegate;
629
630   delegate = g_object_get_qdata (G_OBJECT (chooser),
631                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
632
633   if (priv->active)
634     return gtk_file_chooser_get_current_folder_file (delegate);
635   else
636     {
637       if (priv->current_folder_while_inactive)
638         return g_object_ref (priv->current_folder_while_inactive);
639       else
640         return NULL;
641     }
642 }
643
644 static gboolean
645 gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
646                                      GFile          *file,
647                                      GError        **error)
648 {
649   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
650   GtkFileChooserButtonPrivate *priv = button->priv;
651   GtkFileChooser *delegate;
652
653   delegate = g_object_get_qdata (G_OBJECT (chooser),
654                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
655
656   if (priv->active)
657     return gtk_file_chooser_select_file (delegate, file, error);
658   else
659     {
660       if (priv->selection_while_inactive)
661         g_object_unref (priv->selection_while_inactive);
662
663       priv->selection_while_inactive = g_object_ref (file);
664
665       update_label_and_image (button);
666       update_combo_box (button);
667
668       return TRUE;
669     }
670 }
671
672 static void
673 gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
674                                        GFile          *file)
675 {
676   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
677   GtkFileChooserButtonPrivate *priv = button->priv;
678   GtkFileChooser *delegate;
679
680   delegate = g_object_get_qdata (G_OBJECT (chooser),
681                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
682
683   if (priv->active)
684     gtk_file_chooser_unselect_file (delegate, file);
685   else
686     {
687       if (g_file_equal (priv->selection_while_inactive, file))
688         {
689           if (priv->selection_while_inactive)
690             {
691               g_object_unref (priv->selection_while_inactive);
692               priv->selection_while_inactive = NULL;
693             }
694
695           update_label_and_image (button);
696           update_combo_box (button);
697         }
698     }
699 }
700
701 static void
702 gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser)
703 {
704   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
705   GtkFileChooserButtonPrivate *priv = button->priv;
706   GtkFileChooser *delegate;
707
708   delegate = g_object_get_qdata (G_OBJECT (chooser),
709                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
710
711   if (priv->active)
712     gtk_file_chooser_unselect_all (delegate);
713   else
714     {
715       if (priv->selection_while_inactive)
716         {
717           g_object_unref (priv->selection_while_inactive);
718           priv->selection_while_inactive = NULL;
719
720           update_label_and_image (button);
721           update_combo_box (button);
722         }
723     }
724 }
725
726 static GFile *
727 get_selected_file (GtkFileChooserButton *button)
728 {
729   GtkFileChooserButtonPrivate *priv = button->priv;
730
731   if (priv->active)
732     return gtk_file_chooser_get_file (GTK_FILE_CHOOSER (priv->dialog));
733   else
734     {
735       if (priv->selection_while_inactive)
736         return g_object_ref (priv->selection_while_inactive);
737       else if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
738         {
739           /* If there is no "real" selection in SELECT_FOLDER mode, then we'll just return
740            * the current folder, since that is what GtkFileChooserDefault would do.
741            */
742           if (priv->current_folder_while_inactive)
743             return g_object_ref (priv->current_folder_while_inactive);
744         }
745     }
746
747   return NULL;
748 }
749
750 static GSList *
751 gtk_file_chooser_button_get_files (GtkFileChooser *chooser)
752 {
753   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
754
755   return g_slist_prepend (NULL, get_selected_file (button));
756 }
757
758 static gboolean
759 gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser  *chooser,
760                                              GFile           *file,
761                                              GError         **error)
762 {
763   GtkFileChooser *delegate;
764   gboolean retval;
765
766   delegate = g_object_get_qdata (G_OBJECT (chooser),
767                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
768   retval = _gtk_file_chooser_add_shortcut_folder (delegate, file, error);
769
770   if (retval)
771     {
772       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
773       GtkFileChooserButtonPrivate *priv = button->priv;
774       GtkTreeIter iter;
775       gint pos;
776
777       pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
778       pos += priv->n_shortcuts;
779
780       gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos);
781       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
782                           ICON_COLUMN, NULL,
783                           DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
784                           TYPE_COLUMN, ROW_TYPE_SHORTCUT,
785                           DATA_COLUMN, g_object_ref (file),
786                           IS_FOLDER_COLUMN, FALSE,
787                           -1);
788       set_info_for_file_at_iter (button, file, &iter);
789       priv->n_shortcuts++;
790
791       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
792     }
793
794   return retval;
795 }
796
797 static gboolean
798 gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser  *chooser,
799                                                 GFile           *file,
800                                                 GError         **error)
801 {
802   GtkFileChooser *delegate;
803   gboolean retval;
804
805   delegate = g_object_get_qdata (G_OBJECT (chooser),
806                                  GTK_FILE_CHOOSER_DELEGATE_QUARK);
807
808   retval = _gtk_file_chooser_remove_shortcut_folder (delegate, file, error);
809
810   if (retval)
811     {
812       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
813       GtkFileChooserButtonPrivate *priv = button->priv;
814       GtkTreeIter iter;
815       gint pos;
816       gchar type;
817
818       pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
819       gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
820
821       do
822         {
823           gpointer data;
824
825           gtk_tree_model_get (priv->model, &iter,
826                               TYPE_COLUMN, &type,
827                               DATA_COLUMN, &data,
828                               -1);
829
830           if (type == ROW_TYPE_SHORTCUT &&
831               data && g_file_equal (data, file))
832             {
833               model_free_row_data (GTK_FILE_CHOOSER_BUTTON (chooser), &iter);
834               gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter);
835               priv->n_shortcuts--;
836               gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
837               update_combo_box (GTK_FILE_CHOOSER_BUTTON (chooser));
838               break;
839             }
840         }
841       while (type == ROW_TYPE_SHORTCUT &&
842              gtk_tree_model_iter_next (priv->model, &iter));
843     }
844
845   return retval;
846 }
847
848
849 /* ******************* *
850  *  GObject Functions  *
851  * ******************* */
852
853 static GObject *
854 gtk_file_chooser_button_constructor (GType                  type,
855                                      guint                  n_params,
856                                      GObjectConstructParam *params)
857 {
858   GObject *object;
859   GtkFileChooserButton *button;
860   GtkFileChooserButtonPrivate *priv;
861   GSList *list;
862
863   object = G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor (type,
864                                                                                n_params,
865                                                                                params);
866   button = GTK_FILE_CHOOSER_BUTTON (object);
867   priv = button->priv;
868
869   if (!priv->dialog)
870     {
871       priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
872                                                   GTK_FILE_CHOOSER_ACTION_OPEN,
873                                                   GTK_STOCK_CANCEL,
874                                                   GTK_RESPONSE_CANCEL,
875                                                   GTK_STOCK_OPEN,
876                                                   GTK_RESPONSE_ACCEPT,
877                                                   NULL);
878
879       gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
880                                        GTK_RESPONSE_ACCEPT);
881       gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
882                                                GTK_RESPONSE_ACCEPT,
883                                                GTK_RESPONSE_CANCEL,
884                                                -1);
885
886       gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
887     }
888   else if (!gtk_window_get_title (GTK_WINDOW (priv->dialog)))
889     {
890       gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
891     }
892
893   g_signal_connect (priv->dialog, "delete-event",
894                     G_CALLBACK (dialog_delete_event_cb), object);
895   g_signal_connect (priv->dialog, "response",
896                     G_CALLBACK (dialog_response_cb), object);
897
898   /* This is used, instead of the standard delegate, to ensure that signals are only
899    * delegated when the OK button is pressed. */
900   g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
901   priv->dialog_folder_changed_id =
902     g_signal_connect (priv->dialog, "current-folder-changed",
903                       G_CALLBACK (dialog_current_folder_changed_cb), object);
904   priv->dialog_file_activated_id =
905     g_signal_connect (priv->dialog, "file-activated",
906                       G_CALLBACK (dialog_file_activated_cb), object);
907   priv->dialog_selection_changed_id =
908     g_signal_connect (priv->dialog, "selection-changed",
909                       G_CALLBACK (dialog_selection_changed_cb), object);
910   g_signal_connect (priv->dialog, "update-preview",
911                     G_CALLBACK (dialog_update_preview_cb), object);
912   g_signal_connect (priv->dialog, "notify",
913                     G_CALLBACK (dialog_notify_cb), object);
914   g_object_add_weak_pointer (G_OBJECT (priv->dialog),
915                              (gpointer) (&priv->dialog));
916
917   priv->fs =
918     g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
919
920   model_add_special (button);
921
922   list = _gtk_file_system_list_volumes (priv->fs);
923   model_add_volumes (button, list);
924   g_slist_free (list);
925
926   list = _gtk_file_system_list_bookmarks (priv->fs);
927   model_add_bookmarks (button, list);
928   g_slist_foreach (list, (GFunc) g_object_unref, NULL);
929   g_slist_free (list);
930
931   model_add_other (button);
932
933   model_add_empty_selection (button);
934
935   priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL);
936   gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
937                                           filter_model_visible_func,
938                                           object, NULL);
939
940   gtk_combo_box_set_model (GTK_COMBO_BOX (priv->combo_box), priv->filter_model);
941   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->combo_box),
942                                         combo_box_row_separator_func,
943                                         NULL, NULL);
944
945   /* set up the action for a user-provided dialog, this also updates
946    * the label, image and combobox
947    */
948   g_object_set (object, 
949                 "action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)),
950                 NULL);
951
952   priv->fs_volumes_changed_id =
953     g_signal_connect (priv->fs, "volumes-changed",
954                       G_CALLBACK (fs_volumes_changed_cb), object);
955   priv->fs_bookmarks_changed_id =
956     g_signal_connect (priv->fs, "bookmarks-changed",
957                       G_CALLBACK (fs_bookmarks_changed_cb), object);
958
959   update_label_and_image (button);
960   update_combo_box (button);
961
962   return object;
963 }
964
965 static void
966 gtk_file_chooser_button_set_property (GObject      *object,
967                                       guint         param_id,
968                                       const GValue *value,
969                                       GParamSpec   *pspec)
970 {
971   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
972   GtkFileChooserButtonPrivate *priv = button->priv;
973
974   switch (param_id)
975     {
976     case PROP_DIALOG:
977       /* Construct-only */
978       priv->dialog = g_value_get_object (value);
979       break;
980     case PROP_FOCUS_ON_CLICK:
981       gtk_file_chooser_button_set_focus_on_click (button, g_value_get_boolean (value));
982       break;
983     case PROP_WIDTH_CHARS:
984       gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
985                                                g_value_get_int (value));
986       break;
987     case GTK_FILE_CHOOSER_PROP_ACTION:
988       switch (g_value_get_enum (value))
989         {
990         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
991         case GTK_FILE_CHOOSER_ACTION_SAVE:
992           {
993             GEnumClass *eclass;
994             GEnumValue *eval;
995
996             eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION);
997             eval = g_enum_get_value (eclass, g_value_get_enum (value));
998             g_warning ("%s: Choosers of type `%s' do not support `%s'.",
999                        G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);
1000
1001             g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
1002           }
1003           break;
1004         }
1005
1006       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
1007       update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
1008       update_combo_box (GTK_FILE_CHOOSER_BUTTON (object));
1009
1010       switch (g_value_get_enum (value))
1011         {
1012         case GTK_FILE_CHOOSER_ACTION_OPEN:
1013           gtk_widget_hide (priv->combo_box);
1014           gtk_widget_show (priv->button);
1015           break;
1016         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1017           gtk_widget_hide (priv->button);
1018           gtk_widget_show (priv->combo_box);
1019           break;
1020         default:
1021           g_assert_not_reached ();
1022           break;
1023         }
1024       break;
1025
1026     case PROP_TITLE:
1027     case GTK_FILE_CHOOSER_PROP_FILTER:
1028     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
1029     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
1030     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
1031     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
1032     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
1033     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
1034     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
1035       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
1036       break;
1037
1038     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
1039       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
1040       fs_volumes_changed_cb (priv->fs, button);
1041       fs_bookmarks_changed_cb (priv->fs, button);
1042       break;
1043
1044     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
1045       g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
1046                  G_STRFUNC, G_OBJECT_TYPE_NAME (object));
1047       break;
1048     default:
1049       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1050       break;
1051     }
1052 }
1053
1054 static void
1055 gtk_file_chooser_button_get_property (GObject    *object,
1056                                       guint       param_id,
1057                                       GValue     *value,
1058                                       GParamSpec *pspec)
1059 {
1060   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
1061   GtkFileChooserButtonPrivate *priv = button->priv;
1062
1063   switch (param_id)
1064     {
1065     case PROP_WIDTH_CHARS:
1066       g_value_set_int (value,
1067                        gtk_label_get_width_chars (GTK_LABEL (priv->label)));
1068       break;
1069     case PROP_FOCUS_ON_CLICK:
1070       g_value_set_boolean (value,
1071                            gtk_file_chooser_button_get_focus_on_click (button));
1072       break;
1073
1074     case PROP_TITLE:
1075     case GTK_FILE_CHOOSER_PROP_ACTION:
1076     case GTK_FILE_CHOOSER_PROP_FILTER:
1077     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
1078     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
1079     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
1080     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
1081     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
1082     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
1083     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
1084     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
1085     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
1086       g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
1087       break;
1088
1089     default:
1090       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1091       break;
1092     }
1093 }
1094
1095 static void
1096 gtk_file_chooser_button_finalize (GObject *object)
1097 {
1098   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
1099   GtkFileChooserButtonPrivate *priv = button->priv;
1100
1101   if (priv->selection_while_inactive)
1102     g_object_unref (priv->selection_while_inactive);
1103
1104   if (priv->current_folder_while_inactive)
1105     g_object_unref (priv->current_folder_while_inactive);
1106
1107   G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize (object);
1108 }
1109
1110 /* ********************* *
1111  *  GtkWidget Functions  *
1112  * ********************* */
1113
1114 static void
1115 gtk_file_chooser_button_destroy (GtkWidget *widget)
1116 {
1117   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1118   GtkFileChooserButtonPrivate *priv = button->priv;
1119   GtkTreeIter iter;
1120   GSList *l;
1121
1122   if (priv->dialog != NULL)
1123     {
1124       gtk_widget_destroy (priv->dialog);
1125       priv->dialog = NULL;
1126     }
1127
1128   if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter)) do
1129     {
1130       model_free_row_data (button, &iter);
1131     }
1132   while (gtk_tree_model_iter_next (priv->model, &iter));
1133
1134   if (priv->dnd_select_folder_cancellable)
1135     {
1136       g_cancellable_cancel (priv->dnd_select_folder_cancellable);
1137       priv->dnd_select_folder_cancellable = NULL;
1138     }
1139
1140   if (priv->update_button_cancellable)
1141     {
1142       g_cancellable_cancel (priv->update_button_cancellable);
1143       priv->update_button_cancellable = NULL;
1144     }
1145
1146   if (priv->change_icon_theme_cancellables)
1147     {
1148       for (l = priv->change_icon_theme_cancellables; l; l = l->next)
1149         {
1150           GCancellable *cancellable = G_CANCELLABLE (l->data);
1151           g_cancellable_cancel (cancellable);
1152         }
1153       g_slist_free (priv->change_icon_theme_cancellables);
1154       priv->change_icon_theme_cancellables = NULL;
1155     }
1156
1157   if (priv->model)
1158     {
1159       g_object_unref (priv->model);
1160       priv->model = NULL;
1161     }
1162
1163   if (priv->filter_model)
1164     {
1165       g_object_unref (priv->filter_model);
1166       priv->filter_model = NULL;
1167     }
1168
1169   if (priv->fs)
1170     {
1171       g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id);
1172       g_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id);
1173       g_object_unref (priv->fs);
1174       priv->fs = NULL;
1175     }
1176
1177   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget);
1178 }
1179
1180 struct DndSelectFolderData
1181 {
1182   GtkFileSystem *file_system;
1183   GtkFileChooserButton *button;
1184   GtkFileChooserAction action;
1185   GFile *file;
1186   gchar **uris;
1187   guint i;
1188   gboolean selected;
1189 };
1190
1191 static void
1192 dnd_select_folder_get_info_cb (GCancellable *cancellable,
1193                                GFileInfo    *info,
1194                                const GError *error,
1195                                gpointer      user_data)
1196 {
1197   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1198   struct DndSelectFolderData *data = user_data;
1199
1200   if (cancellable != data->button->priv->dnd_select_folder_cancellable)
1201     {
1202       g_object_unref (data->button);
1203       g_object_unref (data->file);
1204       g_strfreev (data->uris);
1205       g_free (data);
1206
1207       g_object_unref (cancellable);
1208       return;
1209     }
1210
1211   data->button->priv->dnd_select_folder_cancellable = NULL;
1212
1213   if (!cancelled && !error && info != NULL)
1214     {
1215       gboolean is_folder;
1216
1217       is_folder = _gtk_file_info_consider_as_directory (info);
1218
1219       data->selected =
1220         (((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && is_folder) ||
1221           (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && !is_folder)) &&
1222          gtk_file_chooser_select_file (GTK_FILE_CHOOSER (data->button->priv->dialog),
1223                                        data->file, NULL));
1224     }
1225   else
1226     data->selected = FALSE;
1227
1228   if (data->selected || data->uris[++data->i] == NULL)
1229     {
1230       g_signal_emit (data->button, file_chooser_button_signals[FILE_SET], 0);
1231
1232       g_object_unref (data->button);
1233       g_object_unref (data->file);
1234       g_strfreev (data->uris);
1235       g_free (data);
1236
1237       g_object_unref (cancellable);
1238       return;
1239     }
1240
1241   if (data->file)
1242     g_object_unref (data->file);
1243
1244   data->file = g_file_new_for_uri (data->uris[data->i]);
1245
1246   data->button->priv->dnd_select_folder_cancellable =
1247     _gtk_file_system_get_info (data->file_system, data->file,
1248                                "standard::type",
1249                                dnd_select_folder_get_info_cb, user_data);
1250
1251   g_object_unref (cancellable);
1252 }
1253
1254 static void
1255 gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
1256                                             GdkDragContext   *context,
1257                                             gint              x,
1258                                             gint              y,
1259                                             GtkSelectionData *data,
1260                                             guint             type,
1261                                             guint             drag_time)
1262 {
1263   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1264   GtkFileChooserButtonPrivate *priv = button->priv;
1265   GFile *file;
1266   gchar *text;
1267
1268   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
1269     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received (widget,
1270                                                                                  context,
1271                                                                                  x, y,
1272                                                                                  data, type,
1273                                                                                  drag_time);
1274
1275   if (widget == NULL || context == NULL || data == NULL || gtk_selection_data_get_length (data) < 0)
1276     return;
1277
1278   switch (type)
1279     {
1280     case TEXT_URI_LIST:
1281       {
1282         gchar **uris;
1283         struct DndSelectFolderData *info;
1284
1285         uris = gtk_selection_data_get_uris (data);
1286         
1287         if (uris == NULL)
1288           break;
1289
1290         info = g_new0 (struct DndSelectFolderData, 1);
1291         info->button = g_object_ref (button);
1292         info->i = 0;
1293         info->uris = uris;
1294         info->selected = FALSE;
1295         info->file_system = priv->fs;
1296         g_object_get (priv->dialog, "action", &info->action, NULL);
1297
1298         info->file = g_file_new_for_uri (info->uris[info->i]);
1299
1300         if (priv->dnd_select_folder_cancellable)
1301           g_cancellable_cancel (priv->dnd_select_folder_cancellable);
1302
1303         priv->dnd_select_folder_cancellable =
1304           _gtk_file_system_get_info (priv->fs, info->file,
1305                                      "standard::type",
1306                                      dnd_select_folder_get_info_cb, info);
1307       }
1308       break;
1309
1310     case TEXT_PLAIN:
1311       text = (char*) gtk_selection_data_get_text (data);
1312       file = g_file_new_for_uri (text);
1313       gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->dialog), file,
1314                                     NULL);
1315       g_object_unref (file);
1316       g_free (text);
1317       g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
1318       break;
1319
1320     default:
1321       break;
1322     }
1323
1324   gtk_drag_finish (context, TRUE, FALSE, drag_time);
1325 }
1326
1327 static void
1328 gtk_file_chooser_button_show_all (GtkWidget *widget)
1329 {
1330   gtk_widget_show (widget);
1331 }
1332
1333 static void
1334 gtk_file_chooser_button_show (GtkWidget *widget)
1335 {
1336   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1337   GtkFileChooserButtonPrivate *priv = button->priv;
1338
1339   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
1340     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show (widget);
1341
1342   if (priv->active)
1343     open_dialog (GTK_FILE_CHOOSER_BUTTON (widget));
1344 }
1345
1346 static void
1347 gtk_file_chooser_button_hide (GtkWidget *widget)
1348 {
1349   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1350   GtkFileChooserButtonPrivate *priv = button->priv;
1351
1352   gtk_widget_hide (priv->dialog);
1353
1354   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
1355     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide (widget);
1356 }
1357
1358 static void
1359 gtk_file_chooser_button_map (GtkWidget *widget)
1360 {
1361   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map (widget);
1362 }
1363
1364 static gboolean
1365 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
1366                                            gboolean   group_cycling)
1367 {
1368   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1369   GtkFileChooserButtonPrivate *priv = button->priv;
1370
1371   switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1372     {
1373     case GTK_FILE_CHOOSER_ACTION_OPEN:
1374       gtk_widget_grab_focus (priv->button);
1375       break;
1376     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1377       return gtk_widget_mnemonic_activate (priv->combo_box, group_cycling);
1378       break;
1379     default:
1380       g_assert_not_reached ();
1381       break;
1382     }
1383
1384   return TRUE;
1385 }
1386
1387 /* Changes the icons wherever it is needed */
1388 struct ChangeIconThemeData
1389 {
1390   GtkFileChooserButton *button;
1391   GtkTreeRowReference *row_ref;
1392 };
1393
1394 static void
1395 change_icon_theme_get_info_cb (GCancellable *cancellable,
1396                                GFileInfo    *info,
1397                                const GError *error,
1398                                gpointer      user_data)
1399 {
1400   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1401   GdkPixbuf *pixbuf;
1402   struct ChangeIconThemeData *data = user_data;
1403
1404   if (!g_slist_find (data->button->priv->change_icon_theme_cancellables, cancellable))
1405     goto out;
1406
1407   data->button->priv->change_icon_theme_cancellables =
1408     g_slist_remove (data->button->priv->change_icon_theme_cancellables, cancellable);
1409
1410   if (cancelled || error)
1411     goto out;
1412
1413   pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1414
1415   if (pixbuf)
1416     {
1417       gint width = 0;
1418       GtkTreeIter iter;
1419       GtkTreePath *path;
1420
1421       width = MAX (width, gdk_pixbuf_get_width (pixbuf));
1422
1423       path = gtk_tree_row_reference_get_path (data->row_ref);
1424       if (path) 
1425         {
1426           gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1427           gtk_tree_path_free (path);
1428
1429           gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1430                               ICON_COLUMN, pixbuf,
1431                               -1);
1432
1433           g_object_set (data->button->priv->icon_cell,
1434                         "width", width,
1435                         NULL);
1436         }
1437       g_object_unref (pixbuf);
1438     }
1439
1440 out:
1441   g_object_unref (data->button);
1442   gtk_tree_row_reference_free (data->row_ref);
1443   g_free (data);
1444
1445   g_object_unref (cancellable);
1446 }
1447
1448 static void
1449 change_icon_theme (GtkFileChooserButton *button)
1450 {
1451   GtkFileChooserButtonPrivate *priv = button->priv;
1452   GtkSettings *settings;
1453   GtkIconTheme *theme;
1454   GtkTreeIter iter;
1455   GSList *l;
1456   gint width = 0, height = 0;
1457
1458   for (l = button->priv->change_icon_theme_cancellables; l; l = l->next)
1459     {
1460       GCancellable *cancellable = G_CANCELLABLE (l->data);
1461       g_cancellable_cancel (cancellable);
1462     }
1463   g_slist_free (button->priv->change_icon_theme_cancellables);
1464   button->priv->change_icon_theme_cancellables = NULL;
1465
1466   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
1467
1468   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
1469                                          &width, &height))
1470     priv->icon_size = MAX (width, height);
1471   else
1472     priv->icon_size = FALLBACK_ICON_SIZE;
1473
1474   update_label_and_image (button);
1475
1476   gtk_tree_model_get_iter_first (priv->model, &iter);
1477
1478   theme = get_icon_theme (GTK_WIDGET (button));
1479
1480   do
1481     {
1482       GdkPixbuf *pixbuf;
1483       gchar type;
1484       gpointer data;
1485
1486       type = ROW_TYPE_INVALID;
1487       gtk_tree_model_get (priv->model, &iter,
1488                           TYPE_COLUMN, &type,
1489                           DATA_COLUMN, &data,
1490                           -1);
1491
1492       switch (type)
1493         {
1494         case ROW_TYPE_SPECIAL:
1495         case ROW_TYPE_SHORTCUT:
1496         case ROW_TYPE_BOOKMARK:
1497         case ROW_TYPE_CURRENT_FOLDER:
1498           if (data)
1499             {
1500               if (g_file_is_native (G_FILE (data)))
1501                 {
1502                   GtkTreePath *path;
1503                   GCancellable *cancellable;
1504                   struct ChangeIconThemeData *info;               
1505                   
1506                   info = g_new0 (struct ChangeIconThemeData, 1);
1507                   info->button = g_object_ref (button);
1508                   path = gtk_tree_model_get_path (priv->model, &iter);
1509                   info->row_ref = gtk_tree_row_reference_new (priv->model, path);
1510                   gtk_tree_path_free (path);
1511
1512                   cancellable =
1513                     _gtk_file_system_get_info (priv->fs, data,
1514                                                "standard::icon",
1515                                                change_icon_theme_get_info_cb,
1516                                                info);
1517                   button->priv->change_icon_theme_cancellables =
1518                     g_slist_append (button->priv->change_icon_theme_cancellables, cancellable);
1519                   pixbuf = NULL;
1520                 }
1521               else
1522                 /* Don't call get_info for remote paths to avoid latency and
1523                  * auth dialogs.
1524                  * If we switch to a better bookmarks file format (XBEL), we
1525                  * should use mime info to get a better icon.
1526                  */
1527                 pixbuf = gtk_icon_theme_load_icon (theme, "folder-remote",
1528                                                    priv->icon_size, 0, NULL);
1529             }
1530           else
1531             pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
1532                                                priv->icon_size, 0, NULL);
1533           break;
1534         case ROW_TYPE_VOLUME:
1535           if (data)
1536             pixbuf = _gtk_file_system_volume_render_icon (data,
1537                                                           GTK_WIDGET (button),
1538                                                           priv->icon_size,
1539                                                           NULL);
1540           else
1541             pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
1542                                                priv->icon_size, 0, NULL);
1543           break;
1544         default:
1545           continue;
1546           break;
1547         }
1548
1549       if (pixbuf)
1550         width = MAX (width, gdk_pixbuf_get_width (pixbuf));
1551
1552       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
1553                           ICON_COLUMN, pixbuf,
1554                           -1);
1555
1556       if (pixbuf)
1557         g_object_unref (pixbuf);
1558     }
1559   while (gtk_tree_model_iter_next (priv->model, &iter));
1560
1561   g_object_set (button->priv->icon_cell,
1562                 "width", width,
1563                 NULL);
1564 }
1565
1566 static void
1567 gtk_file_chooser_button_style_updated (GtkWidget *widget)
1568 {
1569   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_updated (widget);
1570
1571   if (gtk_widget_has_screen (widget))
1572     change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1573 }
1574
1575 static void
1576 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
1577                                         GdkScreen *old_screen)
1578 {
1579   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
1580     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed (widget,
1581                                                                              old_screen);
1582
1583   change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1584 }
1585
1586
1587 /* ******************* *
1588  *  Utility Functions  *
1589  * ******************* */
1590
1591 /* General */
1592 static GtkIconTheme *
1593 get_icon_theme (GtkWidget *widget)
1594 {
1595   if (gtk_widget_has_screen (widget))
1596     return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1597
1598   return gtk_icon_theme_get_default ();
1599 }
1600
1601
1602 struct SetDisplayNameData
1603 {
1604   GtkFileChooserButton *button;
1605   char *label;
1606   GtkTreeRowReference *row_ref;
1607 };
1608
1609 static void
1610 set_info_get_info_cb (GCancellable *cancellable,
1611                       GFileInfo    *info,
1612                       const GError *error,
1613                       gpointer      callback_data)
1614 {
1615   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1616   GdkPixbuf *pixbuf;
1617   GtkTreePath *path;
1618   GtkTreeIter iter;
1619   GCancellable *model_cancellable = NULL;
1620   struct SetDisplayNameData *data = callback_data;
1621   gboolean is_folder;
1622
1623   if (!data->button->priv->model)
1624     /* button got destroyed */
1625     goto out;
1626
1627   path = gtk_tree_row_reference_get_path (data->row_ref);
1628   if (!path)
1629     /* Cancellable doesn't exist anymore in the model */
1630     goto out;
1631
1632   gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1633   gtk_tree_path_free (path);
1634
1635   /* Validate the cancellable */
1636   gtk_tree_model_get (data->button->priv->model, &iter,
1637                       CANCELLABLE_COLUMN, &model_cancellable,
1638                       -1);
1639   if (cancellable != model_cancellable)
1640     goto out;
1641
1642   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1643                       CANCELLABLE_COLUMN, NULL,
1644                       -1);
1645
1646   if (cancelled || error)
1647     /* There was an error, leave the fallback name in there */
1648     goto out;
1649
1650   pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1651
1652   if (!data->label)
1653     data->label = g_strdup (g_file_info_get_display_name (info));
1654
1655   is_folder = _gtk_file_info_consider_as_directory (info);
1656
1657   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1658                       ICON_COLUMN, pixbuf,
1659                       DISPLAY_NAME_COLUMN, data->label,
1660                       IS_FOLDER_COLUMN, is_folder,
1661                       -1);
1662
1663   if (pixbuf)
1664     g_object_unref (pixbuf);
1665
1666 out:
1667   g_object_unref (data->button);
1668   g_free (data->label);
1669   gtk_tree_row_reference_free (data->row_ref);
1670   g_free (data);
1671
1672   if (model_cancellable)
1673     g_object_unref (model_cancellable);
1674 }
1675
1676 static void
1677 set_info_for_file_at_iter (GtkFileChooserButton *button,
1678                            GFile                *file,
1679                            GtkTreeIter          *iter)
1680 {
1681   struct SetDisplayNameData *data;
1682   GtkTreePath *tree_path;
1683   GCancellable *cancellable;
1684
1685   data = g_new0 (struct SetDisplayNameData, 1);
1686   data->button = g_object_ref (button);
1687   data->label = _gtk_file_system_get_bookmark_label (button->priv->fs, file);
1688
1689   tree_path = gtk_tree_model_get_path (button->priv->model, iter);
1690   data->row_ref = gtk_tree_row_reference_new (button->priv->model, tree_path);
1691   gtk_tree_path_free (tree_path);
1692
1693   cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1694                                            "standard::type,standard::icon,standard::display-name",
1695                                            set_info_get_info_cb, data);
1696
1697   gtk_list_store_set (GTK_LIST_STORE (button->priv->model), iter,
1698                       CANCELLABLE_COLUMN, cancellable,
1699                       -1);
1700 }
1701
1702 /* Shortcuts Model */
1703 static gint
1704 model_get_type_position (GtkFileChooserButton *button,
1705                          RowType               row_type)
1706 {
1707   gint retval = 0;
1708
1709   if (row_type == ROW_TYPE_SPECIAL)
1710     return retval;
1711
1712   retval += button->priv->n_special;
1713
1714   if (row_type == ROW_TYPE_VOLUME)
1715     return retval;
1716
1717   retval += button->priv->n_volumes;
1718
1719   if (row_type == ROW_TYPE_SHORTCUT)
1720     return retval;
1721
1722   retval += button->priv->n_shortcuts;
1723
1724   if (row_type == ROW_TYPE_BOOKMARK_SEPARATOR)
1725     return retval;
1726
1727   retval += button->priv->has_bookmark_separator;
1728
1729   if (row_type == ROW_TYPE_BOOKMARK)
1730     return retval;
1731
1732   retval += button->priv->n_bookmarks;
1733
1734   if (row_type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR)
1735     return retval;
1736
1737   retval += button->priv->has_current_folder_separator;
1738
1739   if (row_type == ROW_TYPE_CURRENT_FOLDER)
1740     return retval;
1741
1742   retval += button->priv->has_current_folder;
1743
1744   if (row_type == ROW_TYPE_OTHER_SEPARATOR)
1745     return retval;
1746
1747   retval += button->priv->has_other_separator;
1748
1749   if (row_type == ROW_TYPE_OTHER)
1750     return retval;
1751
1752   retval++;
1753
1754   if (row_type == ROW_TYPE_EMPTY_SELECTION)
1755     return retval;
1756
1757   g_assert_not_reached ();
1758   return -1;
1759 }
1760
1761 static void
1762 model_free_row_data (GtkFileChooserButton *button,
1763                      GtkTreeIter          *iter)
1764 {
1765   gchar type;
1766   gpointer data;
1767   GCancellable *cancellable;
1768
1769   gtk_tree_model_get (button->priv->model, iter,
1770                       TYPE_COLUMN, &type,
1771                       DATA_COLUMN, &data,
1772                       CANCELLABLE_COLUMN, &cancellable,
1773                       -1);
1774
1775   if (cancellable)
1776     {
1777       g_cancellable_cancel (cancellable);
1778       g_object_unref (cancellable);
1779     }
1780
1781   switch (type)
1782     {
1783     case ROW_TYPE_SPECIAL:
1784     case ROW_TYPE_SHORTCUT:
1785     case ROW_TYPE_BOOKMARK:
1786     case ROW_TYPE_CURRENT_FOLDER:
1787       g_object_unref (data);
1788       break;
1789     case ROW_TYPE_VOLUME:
1790       _gtk_file_system_volume_unref (data);
1791       break;
1792     default:
1793       break;
1794     }
1795 }
1796
1797 static void
1798 model_add_special_get_info_cb (GCancellable *cancellable,
1799                                GFileInfo    *info,
1800                                const GError *error,
1801                                gpointer      user_data)
1802 {
1803   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1804   GtkTreeIter iter;
1805   GtkTreePath *path;
1806   GdkPixbuf *pixbuf;
1807   GCancellable *model_cancellable = NULL;
1808   struct ChangeIconThemeData *data = user_data;
1809   gchar *name;
1810
1811   if (!data->button->priv->model)
1812     /* button got destroyed */
1813     goto out;
1814
1815   path = gtk_tree_row_reference_get_path (data->row_ref);
1816   if (!path)
1817     /* Cancellable doesn't exist anymore in the model */
1818     goto out;
1819
1820   gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1821   gtk_tree_path_free (path);
1822
1823   gtk_tree_model_get (data->button->priv->model, &iter,
1824                       CANCELLABLE_COLUMN, &model_cancellable,
1825                       -1);
1826   if (cancellable != model_cancellable)
1827     goto out;
1828
1829   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1830                       CANCELLABLE_COLUMN, NULL,
1831                       -1);
1832
1833   if (cancelled || error)
1834     goto out;
1835
1836   pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1837
1838   if (pixbuf)
1839     {
1840       gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1841                           ICON_COLUMN, pixbuf,
1842                           -1);
1843       g_object_unref (pixbuf);
1844     }
1845
1846   gtk_tree_model_get (data->button->priv->model, &iter,
1847                       DISPLAY_NAME_COLUMN, &name,
1848                       -1);
1849   if (!name)
1850     gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1851                         DISPLAY_NAME_COLUMN, g_file_info_get_display_name (info),
1852                         -1);
1853   g_free (name);
1854    
1855 out:
1856   g_object_unref (data->button);
1857   gtk_tree_row_reference_free (data->row_ref);
1858   g_free (data);
1859
1860   if (model_cancellable)
1861     g_object_unref (model_cancellable);
1862 }
1863
1864 static void
1865 model_add_special (GtkFileChooserButton *button)
1866 {
1867   const gchar *homedir;
1868   const gchar *desktopdir;
1869   GtkListStore *store;
1870   GtkTreeIter iter;
1871   GFile *file;
1872   gint pos;
1873
1874   store = GTK_LIST_STORE (button->priv->model);
1875   pos = model_get_type_position (button, ROW_TYPE_SPECIAL);
1876
1877   homedir = g_get_home_dir ();
1878
1879   if (homedir)
1880     {
1881       GtkTreePath *tree_path;
1882       GCancellable *cancellable;
1883       struct ChangeIconThemeData *info;
1884
1885       file = g_file_new_for_path (homedir);
1886       gtk_list_store_insert (store, &iter, pos);
1887       pos++;
1888
1889       info = g_new0 (struct ChangeIconThemeData, 1);
1890       info->button = g_object_ref (button);
1891       tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
1892       info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
1893                                                   tree_path);
1894       gtk_tree_path_free (tree_path);
1895
1896       cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1897                                                "standard::icon,standard::display-name",
1898                                                model_add_special_get_info_cb, info);
1899
1900       gtk_list_store_set (store, &iter,
1901                           ICON_COLUMN, NULL,
1902                           DISPLAY_NAME_COLUMN, NULL,
1903                           TYPE_COLUMN, ROW_TYPE_SPECIAL,
1904                           DATA_COLUMN, file,
1905                           IS_FOLDER_COLUMN, TRUE,
1906                           CANCELLABLE_COLUMN, cancellable,
1907                           -1);
1908
1909       button->priv->n_special++;
1910     }
1911
1912   desktopdir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1913
1914   /* "To disable a directory, point it to the homedir."
1915    * See http://freedesktop.org/wiki/Software/xdg-user-dirs
1916    **/
1917   if (g_strcmp0 (desktopdir, g_get_home_dir ()) != 0)
1918     {
1919       GtkTreePath *tree_path;
1920       GCancellable *cancellable;
1921       struct ChangeIconThemeData *info;
1922
1923       file = g_file_new_for_path (desktopdir);
1924       gtk_list_store_insert (store, &iter, pos);
1925       pos++;
1926
1927       info = g_new0 (struct ChangeIconThemeData, 1);
1928       info->button = g_object_ref (button);
1929       tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
1930       info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
1931                                                   tree_path);
1932       gtk_tree_path_free (tree_path);
1933
1934       cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1935                                                "standard::icon,standard::display-name",
1936                                                model_add_special_get_info_cb, info);
1937
1938       gtk_list_store_set (store, &iter,
1939                           TYPE_COLUMN, ROW_TYPE_SPECIAL,
1940                           ICON_COLUMN, NULL,
1941                           DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME),
1942                           DATA_COLUMN, file,
1943                           IS_FOLDER_COLUMN, TRUE,
1944                           CANCELLABLE_COLUMN, cancellable,
1945                           -1);
1946
1947       button->priv->n_special++;
1948     }
1949 }
1950
1951 static void
1952 model_add_volumes (GtkFileChooserButton *button,
1953                    GSList               *volumes)
1954 {
1955   GtkListStore *store;
1956   gint pos;
1957   gboolean local_only;
1958   GSList *l;
1959
1960   if (!volumes)
1961     return;
1962
1963   store = GTK_LIST_STORE (button->priv->model);
1964   pos = model_get_type_position (button, ROW_TYPE_VOLUME);
1965   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog));
1966
1967   for (l = volumes; l; l = l->next)
1968     {
1969       GtkFileSystemVolume *volume;
1970       GtkTreeIter iter;
1971       GdkPixbuf *pixbuf;
1972       gchar *display_name;
1973
1974       volume = l->data;
1975
1976       if (local_only)
1977         {
1978           if (_gtk_file_system_volume_is_mounted (volume))
1979             {
1980               GFile *base_file;
1981
1982               base_file = _gtk_file_system_volume_get_root (volume);
1983               if (base_file != NULL)
1984                 {
1985                   if (!_gtk_file_has_native_path (base_file))
1986                     {
1987                       g_object_unref (base_file);
1988                       continue;
1989                     }
1990                   else
1991                     g_object_unref (base_file);
1992                 }
1993             }
1994         }
1995
1996       pixbuf = _gtk_file_system_volume_render_icon (volume,
1997                                                     GTK_WIDGET (button),
1998                                                     button->priv->icon_size,
1999                                                     NULL);
2000       display_name = _gtk_file_system_volume_get_display_name (volume);
2001
2002       gtk_list_store_insert (store, &iter, pos);
2003       gtk_list_store_set (store, &iter,
2004                           ICON_COLUMN, pixbuf,
2005                           DISPLAY_NAME_COLUMN, display_name,
2006                           TYPE_COLUMN, ROW_TYPE_VOLUME,
2007                           DATA_COLUMN, _gtk_file_system_volume_ref (volume),
2008                           IS_FOLDER_COLUMN, TRUE,
2009                           -1);
2010
2011       if (pixbuf)
2012         g_object_unref (pixbuf);
2013       g_free (display_name);
2014
2015       button->priv->n_volumes++;
2016       pos++;
2017     }
2018 }
2019
2020 extern gchar * _gtk_file_chooser_label_for_file (GFile *file);
2021
2022 static void
2023 model_add_bookmarks (GtkFileChooserButton *button,
2024                      GSList               *bookmarks)
2025 {
2026   GtkListStore *store;
2027   GtkTreeIter iter;
2028   gint pos;
2029   gboolean local_only;
2030   GSList *l;
2031
2032   if (!bookmarks)
2033     return;
2034
2035   store = GTK_LIST_STORE (button->priv->model);
2036   pos = model_get_type_position (button, ROW_TYPE_BOOKMARK);
2037   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog));
2038
2039   for (l = bookmarks; l; l = l->next)
2040     {
2041       GFile *file;
2042
2043       file = l->data;
2044
2045       if (_gtk_file_has_native_path (file))
2046         {
2047           gtk_list_store_insert (store, &iter, pos);
2048           gtk_list_store_set (store, &iter,
2049                               ICON_COLUMN, NULL,
2050                               DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
2051                               TYPE_COLUMN, ROW_TYPE_BOOKMARK,
2052                               DATA_COLUMN, g_object_ref (file),
2053                               IS_FOLDER_COLUMN, FALSE,
2054                               -1);
2055           set_info_for_file_at_iter (button, file, &iter);
2056         }
2057       else
2058         {
2059           gchar *label;
2060           GtkIconTheme *icon_theme;
2061           GdkPixbuf *pixbuf;
2062
2063           if (local_only)
2064             continue;
2065
2066           /* Don't call get_info for remote paths to avoid latency and
2067            * auth dialogs.
2068            * If we switch to a better bookmarks file format (XBEL), we
2069            * should use mime info to get a better icon.
2070            */
2071           label = _gtk_file_system_get_bookmark_label (button->priv->fs, file);
2072           if (!label)
2073             label = _gtk_file_chooser_label_for_file (file);
2074
2075           icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
2076           pixbuf = gtk_icon_theme_load_icon (icon_theme, "folder-remote", 
2077                                              button->priv->icon_size, 0, NULL);
2078
2079           gtk_list_store_insert (store, &iter, pos);
2080           gtk_list_store_set (store, &iter,
2081                               ICON_COLUMN, pixbuf,
2082                               DISPLAY_NAME_COLUMN, label,
2083                               TYPE_COLUMN, ROW_TYPE_BOOKMARK,
2084                               DATA_COLUMN, g_object_ref (file),
2085                               IS_FOLDER_COLUMN, TRUE,
2086                               -1);
2087
2088           g_free (label);
2089           g_object_unref (pixbuf);
2090         }
2091
2092       button->priv->n_bookmarks++;
2093       pos++;
2094     }
2095
2096   if (button->priv->n_bookmarks > 0 && 
2097       !button->priv->has_bookmark_separator)
2098     {
2099       pos = model_get_type_position (button, ROW_TYPE_BOOKMARK_SEPARATOR);
2100
2101       gtk_list_store_insert (store, &iter, pos);
2102       gtk_list_store_set (store, &iter,
2103                           ICON_COLUMN, NULL,
2104                           DISPLAY_NAME_COLUMN, NULL,
2105                           TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR,
2106                           DATA_COLUMN, NULL,
2107                           IS_FOLDER_COLUMN, FALSE,
2108                           -1);
2109       button->priv->has_bookmark_separator = TRUE;
2110     }
2111 }
2112
2113 static void
2114 model_update_current_folder (GtkFileChooserButton *button,
2115                              GFile                *file)
2116 {
2117   GtkListStore *store;
2118   GtkTreeIter iter;
2119   gint pos;
2120
2121   if (!file)
2122     return;
2123
2124   store = GTK_LIST_STORE (button->priv->model);
2125
2126   if (!button->priv->has_current_folder_separator)
2127     {
2128       pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER_SEPARATOR);
2129       gtk_list_store_insert (store, &iter, pos);
2130       gtk_list_store_set (store, &iter,
2131                           ICON_COLUMN, NULL,
2132                           DISPLAY_NAME_COLUMN, NULL,
2133                           TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
2134                           DATA_COLUMN, NULL,
2135                           IS_FOLDER_COLUMN, FALSE,
2136                           -1);
2137       button->priv->has_current_folder_separator = TRUE;
2138     }
2139
2140   pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
2141   if (!button->priv->has_current_folder)
2142     {
2143       gtk_list_store_insert (store, &iter, pos);
2144       button->priv->has_current_folder = TRUE;
2145     }
2146   else
2147     {
2148       gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos);
2149       model_free_row_data (button, &iter);
2150     }
2151
2152   if (g_file_is_native (file))
2153     {
2154       gtk_list_store_set (store, &iter,
2155                           ICON_COLUMN, NULL,
2156                           DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
2157                           TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
2158                           DATA_COLUMN, g_object_ref (file),
2159                           IS_FOLDER_COLUMN, FALSE,
2160                           -1);
2161       set_info_for_file_at_iter (button, file, &iter);
2162     }
2163   else
2164     {
2165       gchar *label;
2166       GtkIconTheme *icon_theme;
2167       GdkPixbuf *pixbuf;
2168
2169       /* Don't call get_info for remote paths to avoid latency and
2170        * auth dialogs.
2171        * If we switch to a better bookmarks file format (XBEL), we
2172        * should use mime info to get a better icon.
2173        */
2174       label = _gtk_file_system_get_bookmark_label (button->priv->fs, file);
2175       if (!label)
2176         label = _gtk_file_chooser_label_for_file (file);
2177
2178       icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
2179
2180       if (g_file_is_native (file))
2181           pixbuf = gtk_icon_theme_load_icon (icon_theme, "folder", 
2182                                              button->priv->icon_size, 0, NULL);
2183       else
2184           pixbuf = gtk_icon_theme_load_icon (icon_theme, "folder-remote", 
2185                                              button->priv->icon_size, 0, NULL);
2186
2187       gtk_list_store_set (store, &iter,
2188                           ICON_COLUMN, pixbuf,
2189                           DISPLAY_NAME_COLUMN, label,
2190                           TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
2191                           DATA_COLUMN, g_object_ref (file),
2192                           IS_FOLDER_COLUMN, TRUE,
2193                           -1);
2194
2195       g_free (label);
2196       g_object_unref (pixbuf);
2197     }
2198 }
2199
2200 static void
2201 model_add_other (GtkFileChooserButton *button)
2202 {
2203   GtkListStore *store;
2204   GtkTreeIter iter;
2205   gint pos;
2206   
2207   store = GTK_LIST_STORE (button->priv->model);
2208   pos = model_get_type_position (button, ROW_TYPE_OTHER_SEPARATOR);
2209
2210   gtk_list_store_insert (store, &iter, pos);
2211   gtk_list_store_set (store, &iter,
2212                       ICON_COLUMN, NULL,
2213                       DISPLAY_NAME_COLUMN, NULL,
2214                       TYPE_COLUMN, ROW_TYPE_OTHER_SEPARATOR,
2215                       DATA_COLUMN, NULL,
2216                       IS_FOLDER_COLUMN, FALSE,
2217                       -1);
2218   button->priv->has_other_separator = TRUE;
2219   pos++;
2220
2221   gtk_list_store_insert (store, &iter, pos);
2222   gtk_list_store_set (store, &iter,
2223                       ICON_COLUMN, NULL,
2224                       DISPLAY_NAME_COLUMN, _("Other…"),
2225                       TYPE_COLUMN, ROW_TYPE_OTHER,
2226                       DATA_COLUMN, NULL,
2227                       IS_FOLDER_COLUMN, FALSE,
2228                       -1);
2229 }
2230
2231 static void
2232 model_add_empty_selection (GtkFileChooserButton *button)
2233 {
2234   GtkListStore *store;
2235   GtkTreeIter iter;
2236   gint pos;
2237   
2238   store = GTK_LIST_STORE (button->priv->model);
2239   pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2240
2241   gtk_list_store_insert (store, &iter, pos);
2242   gtk_list_store_set (store, &iter,
2243                       ICON_COLUMN, NULL,
2244                       DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
2245                       TYPE_COLUMN, ROW_TYPE_EMPTY_SELECTION,
2246                       DATA_COLUMN, NULL,
2247                       IS_FOLDER_COLUMN, FALSE,
2248                       -1);
2249 }
2250
2251 static void
2252 model_remove_rows (GtkFileChooserButton *button,
2253                    gint                  pos,
2254                    gint                  n_rows)
2255 {
2256   GtkListStore *store;
2257
2258   if (!n_rows)
2259     return;
2260
2261   store = GTK_LIST_STORE (button->priv->model);
2262
2263   do
2264     {
2265       GtkTreeIter iter;
2266
2267       if (!gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos))
2268         g_assert_not_reached ();
2269
2270       model_free_row_data (button, &iter);
2271       gtk_list_store_remove (store, &iter);
2272       n_rows--;
2273     }
2274   while (n_rows);
2275 }
2276
2277 /* Filter Model */
2278 static gboolean
2279 test_if_file_is_visible (GtkFileSystem *fs,
2280                          GFile         *file,
2281                          gboolean       local_only,
2282                          gboolean       is_folder)
2283 {
2284   if (!file)
2285     return FALSE;
2286
2287   if (local_only && !_gtk_file_has_native_path (file))
2288     return FALSE;
2289
2290   if (!is_folder)
2291     return FALSE;
2292
2293   return TRUE;
2294 }
2295
2296 static gboolean
2297 filter_model_visible_func (GtkTreeModel *model,
2298                            GtkTreeIter  *iter,
2299                            gpointer      user_data)
2300 {
2301   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2302   GtkFileChooserButtonPrivate *priv = button->priv;
2303   gchar type;
2304   gpointer data;
2305   gboolean local_only, retval, is_folder;
2306
2307   type = ROW_TYPE_INVALID;
2308   data = NULL;
2309   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog));
2310
2311   gtk_tree_model_get (model, iter,
2312                       TYPE_COLUMN, &type,
2313                       DATA_COLUMN, &data,
2314                       IS_FOLDER_COLUMN, &is_folder,
2315                       -1);
2316
2317   switch (type)
2318     {
2319     case ROW_TYPE_CURRENT_FOLDER:
2320       retval = TRUE;
2321       break;
2322     case ROW_TYPE_SPECIAL:
2323     case ROW_TYPE_SHORTCUT:
2324     case ROW_TYPE_BOOKMARK:
2325       retval = test_if_file_is_visible (priv->fs, data, local_only, is_folder);
2326       break;
2327     case ROW_TYPE_VOLUME:
2328       {
2329         retval = TRUE;
2330         if (local_only)
2331           {
2332             if (_gtk_file_system_volume_is_mounted (data))
2333               {
2334                 GFile *base_file;
2335
2336                 base_file = _gtk_file_system_volume_get_root (data);
2337
2338                 if (base_file)
2339                   {
2340                     if (!_gtk_file_has_native_path (base_file))
2341                       retval = FALSE;
2342                     g_object_unref (base_file);
2343                   }
2344                 else
2345                   retval = FALSE;
2346               }
2347           }
2348       }
2349       break;
2350     case ROW_TYPE_EMPTY_SELECTION:
2351       {
2352         gboolean popup_shown;
2353
2354         g_object_get (priv->combo_box,
2355                       "popup-shown", &popup_shown,
2356                       NULL);
2357
2358         if (popup_shown)
2359           retval = FALSE;
2360         else
2361           {
2362             GFile *selected;
2363
2364             /* When the combo box is not popped up... */
2365
2366             selected = get_selected_file (button);
2367             if (selected)
2368               retval = FALSE; /* ... nonempty selection means the ROW_TYPE_EMPTY_SELECTION is *not* visible... */
2369             else
2370               retval = TRUE;  /* ... and empty selection means the ROW_TYPE_EMPTY_SELECTION *is* visible */
2371
2372             if (selected)
2373               g_object_unref (selected);
2374           }
2375
2376         break;
2377       }
2378     default:
2379       retval = TRUE;
2380       break;
2381     }
2382
2383   return retval;
2384 }
2385
2386 /* Combo Box */
2387 static void
2388 name_cell_data_func (GtkCellLayout   *layout,
2389                      GtkCellRenderer *cell,
2390                      GtkTreeModel    *model,
2391                      GtkTreeIter     *iter,
2392                      gpointer         user_data)
2393 {
2394   gchar type;
2395
2396   type = 0;
2397   gtk_tree_model_get (model, iter,
2398                       TYPE_COLUMN, &type,
2399                       -1);
2400
2401   if (type == ROW_TYPE_CURRENT_FOLDER)
2402     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2403   else
2404     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
2405 }
2406
2407 static gboolean
2408 combo_box_row_separator_func (GtkTreeModel *model,
2409                               GtkTreeIter  *iter,
2410                               gpointer      user_data)
2411 {
2412   gchar type = ROW_TYPE_INVALID;
2413
2414   gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, -1);
2415
2416   return (type == ROW_TYPE_BOOKMARK_SEPARATOR ||
2417           type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR ||
2418           type == ROW_TYPE_OTHER_SEPARATOR);
2419 }
2420
2421 static void
2422 select_combo_box_row_no_notify (GtkFileChooserButton *button, int pos)
2423 {
2424   GtkFileChooserButtonPrivate *priv = button->priv;
2425   GtkTreeIter iter, filter_iter;
2426   
2427   gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
2428   gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
2429                                                     &filter_iter, &iter);
2430
2431   g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
2432   gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
2433   g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id);
2434 }
2435
2436 static void
2437 update_combo_box (GtkFileChooserButton *button)
2438 {
2439   GtkFileChooserButtonPrivate *priv = button->priv;
2440   GFile *file;
2441   GtkTreeIter iter;
2442   gboolean row_found;
2443
2444   file = get_selected_file (button);
2445
2446   row_found = FALSE;
2447
2448   gtk_tree_model_get_iter_first (priv->filter_model, &iter);
2449
2450   do
2451     {
2452       gchar type;
2453       gpointer data;
2454
2455       type = ROW_TYPE_INVALID;
2456       data = NULL;
2457
2458       gtk_tree_model_get (priv->filter_model, &iter,
2459                           TYPE_COLUMN, &type,
2460                           DATA_COLUMN, &data,
2461                           -1);
2462     
2463       switch (type)
2464         {
2465         case ROW_TYPE_SPECIAL:
2466         case ROW_TYPE_SHORTCUT:
2467         case ROW_TYPE_BOOKMARK:
2468         case ROW_TYPE_CURRENT_FOLDER:
2469           row_found = (file && g_file_equal (data, file));
2470           break;
2471         case ROW_TYPE_VOLUME:
2472           {
2473             GFile *base_file;
2474
2475             base_file = _gtk_file_system_volume_get_root (data);
2476             if (base_file)
2477               {
2478                 row_found = (file && g_file_equal (base_file, file));
2479                 g_object_unref (base_file);
2480               }
2481           }
2482           break;
2483         default:
2484           row_found = FALSE;
2485           break;
2486         }
2487
2488       if (row_found)
2489         {
2490           g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
2491           gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box),
2492                                          &iter);
2493           g_signal_handler_unblock (priv->combo_box,
2494                                     priv->combo_box_changed_id);
2495         }
2496     }
2497   while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter));
2498
2499   if (!row_found)
2500     {
2501       gint pos;
2502
2503       /* If it hasn't been found already, update & select the current-folder row. */
2504       if (file)
2505         {
2506           model_update_current_folder (button, file);
2507           pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
2508         }
2509       else
2510         {
2511           /* No selection; switch to that row */
2512
2513           pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2514         }
2515
2516       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2517
2518       select_combo_box_row_no_notify (button, pos);
2519     }
2520
2521   if (file)
2522     g_object_unref (file);
2523 }
2524
2525 /* Button */
2526 static void
2527 update_label_get_info_cb (GCancellable *cancellable,
2528                           GFileInfo    *info,
2529                           const GError *error,
2530                           gpointer      data)
2531 {
2532   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
2533   GdkPixbuf *pixbuf;
2534   GtkFileChooserButton *button = data;
2535   GtkFileChooserButtonPrivate *priv = button->priv;
2536
2537   if (cancellable != priv->update_button_cancellable)
2538     goto out;
2539
2540   priv->update_button_cancellable = NULL;
2541
2542   if (cancelled || error)
2543     goto out;
2544
2545   gtk_label_set_text (GTK_LABEL (priv->label), g_file_info_get_display_name (info));
2546
2547   pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (priv->image), priv->icon_size);
2548
2549   if (!pixbuf)
2550     pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)),
2551                                        FALLBACK_ICON_NAME,
2552                                        priv->icon_size, 0, NULL);
2553
2554   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
2555   if (pixbuf)
2556     g_object_unref (pixbuf);
2557
2558 out:
2559   g_object_unref (button);
2560   g_object_unref (cancellable);
2561 }
2562
2563 static void
2564 update_label_and_image (GtkFileChooserButton *button)
2565 {
2566   GtkFileChooserButtonPrivate *priv = button->priv;
2567   gchar *label_text;
2568   GFile *file;
2569
2570   file = get_selected_file (button);
2571
2572   label_text = NULL;
2573
2574   if (priv->update_button_cancellable)
2575     {
2576       g_cancellable_cancel (priv->update_button_cancellable);
2577       priv->update_button_cancellable = NULL;
2578     }
2579
2580   if (file)
2581     {
2582       GtkFileSystemVolume *volume = NULL;
2583
2584       volume = _gtk_file_system_get_volume_for_file (priv->fs, file);
2585       if (volume)
2586         {
2587           GFile *base_file;
2588
2589           base_file = _gtk_file_system_volume_get_root (volume);
2590           if (base_file && g_file_equal (base_file, file))
2591             {
2592               GdkPixbuf *pixbuf;
2593
2594               label_text = _gtk_file_system_volume_get_display_name (volume);
2595               pixbuf = _gtk_file_system_volume_render_icon (volume,
2596                                                             GTK_WIDGET (button),
2597                                                             priv->icon_size,
2598                                                             NULL);
2599               gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
2600               if (pixbuf)
2601                 g_object_unref (pixbuf);
2602             }
2603
2604           if (base_file)
2605             g_object_unref (base_file);
2606
2607           _gtk_file_system_volume_unref (volume);
2608
2609           if (label_text)
2610               goto out;
2611         }
2612
2613       if (g_file_is_native (file))
2614         {
2615           priv->update_button_cancellable =
2616             _gtk_file_system_get_info (priv->fs, file,
2617                                        "standard::icon,standard::display-name",
2618                                        update_label_get_info_cb,
2619                                        g_object_ref (button));
2620         }
2621       else
2622         {
2623           GdkPixbuf *pixbuf;
2624
2625           label_text = _gtk_file_system_get_bookmark_label (button->priv->fs, file);
2626           pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)),
2627                                              "text-x-generic",
2628                                              priv->icon_size, 0, NULL);
2629           gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
2630           if (pixbuf)
2631             g_object_unref (pixbuf);
2632         }
2633     }
2634 out:
2635
2636   if (file)
2637     g_object_unref (file);
2638
2639   if (label_text)
2640     {
2641       gtk_label_set_text (GTK_LABEL (priv->label), label_text);
2642       g_free (label_text);
2643     }
2644   else
2645     {
2646       gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME));
2647       gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), NULL);
2648     }
2649 }
2650
2651
2652 /* ************************ *
2653  *  Child Object Callbacks  *
2654  * ************************ */
2655
2656 /* File System */
2657 static void
2658 fs_volumes_changed_cb (GtkFileSystem *fs,
2659                        gpointer       user_data)
2660 {
2661   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2662   GtkFileChooserButtonPrivate *priv = button->priv;
2663   GSList *volumes;
2664
2665   model_remove_rows (user_data,
2666                      model_get_type_position (user_data, ROW_TYPE_VOLUME),
2667                      priv->n_volumes);
2668
2669   priv->n_volumes = 0;
2670
2671   volumes = _gtk_file_system_list_volumes (fs);
2672   model_add_volumes (user_data, volumes);
2673   g_slist_free (volumes);
2674
2675   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2676
2677   update_label_and_image (user_data);
2678   update_combo_box (user_data);
2679 }
2680
2681 static void
2682 fs_bookmarks_changed_cb (GtkFileSystem *fs,
2683                          gpointer       user_data)
2684 {
2685   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2686   GtkFileChooserButtonPrivate *priv = button->priv;
2687   GSList *bookmarks;
2688
2689   bookmarks = _gtk_file_system_list_bookmarks (fs);
2690   model_remove_rows (user_data,
2691                      model_get_type_position (user_data,
2692                                               ROW_TYPE_BOOKMARK_SEPARATOR),
2693                      (priv->n_bookmarks + priv->has_bookmark_separator));
2694   priv->has_bookmark_separator = FALSE;
2695   priv->n_bookmarks = 0;
2696   model_add_bookmarks (user_data, bookmarks);
2697   g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL);
2698   g_slist_free (bookmarks);
2699
2700   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2701
2702   update_label_and_image (user_data);
2703   update_combo_box (user_data);
2704 }
2705
2706 static void
2707 save_inactive_state (GtkFileChooserButton *button)
2708 {
2709   GtkFileChooserButtonPrivate *priv = button->priv;
2710
2711   if (priv->current_folder_while_inactive)
2712     g_object_unref (priv->current_folder_while_inactive);
2713
2714   if (priv->selection_while_inactive)
2715     g_object_unref (priv->selection_while_inactive);
2716
2717   priv->current_folder_while_inactive = gtk_file_chooser_get_current_folder_file (GTK_FILE_CHOOSER (priv->dialog));
2718   priv->selection_while_inactive = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (priv->dialog));
2719 }
2720
2721 static void
2722 restore_inactive_state (GtkFileChooserButton *button)
2723 {
2724   GtkFileChooserButtonPrivate *priv = button->priv;
2725
2726   if (priv->current_folder_while_inactive)
2727     gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->dialog), priv->current_folder_while_inactive, NULL);
2728
2729   if (priv->selection_while_inactive)
2730     gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->dialog), priv->selection_while_inactive, NULL);
2731   else
2732     gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
2733 }
2734
2735 /* Dialog */
2736 static void
2737 open_dialog (GtkFileChooserButton *button)
2738 {
2739   GtkFileChooserButtonPrivate *priv = button->priv;
2740
2741   /* Setup the dialog parent to be chooser button's toplevel, and be modal
2742      as needed. */
2743   if (!gtk_widget_get_visible (priv->dialog))
2744     {
2745       GtkWidget *toplevel;
2746
2747       toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
2748
2749       if (gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel))
2750         {
2751           if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
2752             gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
2753                                           GTK_WINDOW (toplevel));
2754               
2755           gtk_window_set_modal (GTK_WINDOW (priv->dialog),
2756                                 gtk_window_get_modal (GTK_WINDOW (toplevel)));
2757         }
2758     }
2759
2760   if (!priv->active)
2761     {
2762       g_signal_handler_block (priv->dialog,
2763                               priv->dialog_folder_changed_id);
2764       g_signal_handler_block (priv->dialog,
2765                               priv->dialog_file_activated_id);
2766       g_signal_handler_block (priv->dialog,
2767                               priv->dialog_selection_changed_id);
2768
2769       restore_inactive_state (button);
2770
2771       priv->active = TRUE;
2772     }
2773
2774   gtk_widget_set_sensitive (priv->combo_box, FALSE);
2775   gtk_window_present (GTK_WINDOW (priv->dialog));
2776 }
2777
2778 /* Combo Box */
2779 static void
2780 combo_box_changed_cb (GtkComboBox *combo_box,
2781                       gpointer     user_data)
2782 {
2783   GtkTreeIter iter;
2784
2785   if (gtk_combo_box_get_active_iter (combo_box, &iter))
2786     {
2787       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2788       GtkFileChooserButtonPrivate *priv = button->priv;
2789       gchar type;
2790       gpointer data;
2791
2792       type = ROW_TYPE_INVALID;
2793       data = NULL;
2794
2795       gtk_tree_model_get (priv->filter_model, &iter,
2796                           TYPE_COLUMN, &type,
2797                           DATA_COLUMN, &data,
2798                           -1);
2799
2800       switch (type)
2801         {
2802         case ROW_TYPE_SPECIAL:
2803         case ROW_TYPE_SHORTCUT:
2804         case ROW_TYPE_BOOKMARK:
2805         case ROW_TYPE_CURRENT_FOLDER:
2806           if (data)
2807             gtk_file_chooser_button_set_current_folder (GTK_FILE_CHOOSER (button), data, NULL);
2808           break;
2809         case ROW_TYPE_VOLUME:
2810           {
2811             GFile *base_file;
2812
2813             base_file = _gtk_file_system_volume_get_root (data);
2814             if (base_file)
2815               {
2816                 gtk_file_chooser_button_set_current_folder (GTK_FILE_CHOOSER (button), base_file, NULL);
2817                 g_object_unref (base_file);
2818               }
2819           }
2820           break;
2821         case ROW_TYPE_OTHER:
2822           open_dialog (user_data);
2823           break;
2824         default:
2825           break;
2826         }
2827     }
2828 }
2829
2830 /* Calback for the "notify::popup-shown" signal on the combo box.
2831  * When the combo is popped up, we don't want the ROW_TYPE_EMPTY_SELECTION to be visible
2832  * at all; otherwise we would be showing a "(None)" item in the combo box's popup.
2833  *
2834  * However, when the combo box is *not* popped up, we want the empty-selection row
2835  * to be visible depending on the selection.
2836  *
2837  * Since all that is done through the filter_model_visible_func(), this means
2838  * that we need to refilter the model when the combo box pops up - hence the
2839  * present signal handler.
2840  */
2841 static void
2842 combo_box_notify_popup_shown_cb (GObject    *object,
2843                                  GParamSpec *pspec,
2844                                  gpointer    user_data)
2845 {
2846   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2847   GtkFileChooserButtonPrivate *priv = button->priv;
2848   gboolean popup_shown;
2849
2850   g_object_get (priv->combo_box,
2851                 "popup-shown", &popup_shown,
2852                 NULL);
2853
2854   /* Indicate that the ROW_TYPE_EMPTY_SELECTION will change visibility... */
2855   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2856
2857   /* If the combo box popup got dismissed, go back to showing the ROW_TYPE_EMPTY_SELECTION if needed */
2858   if (!popup_shown)
2859     {
2860       GFile *selected = get_selected_file (button);
2861
2862       if (!selected)
2863         {
2864           int pos;
2865
2866           pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2867           select_combo_box_row_no_notify (button, pos);
2868         }
2869       else
2870         g_object_unref (selected);
2871     }
2872 }
2873
2874 /* Button */
2875 static void
2876 button_clicked_cb (GtkButton *real_button,
2877                    gpointer   user_data)
2878 {
2879   open_dialog (user_data);
2880 }
2881
2882 /* Dialog */
2883 static void
2884 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
2885                                   gpointer        user_data)
2886 {
2887   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2888
2889   g_signal_emit_by_name (button, "current-folder-changed");
2890 }
2891
2892 static void
2893 dialog_file_activated_cb (GtkFileChooser *dialog,
2894                           gpointer        user_data)
2895 {
2896   g_signal_emit_by_name (user_data, "file-activated");
2897 }
2898
2899 static void
2900 dialog_selection_changed_cb (GtkFileChooser *dialog,
2901                              gpointer        user_data)
2902 {
2903   update_label_and_image (user_data);
2904   update_combo_box (user_data);
2905   g_signal_emit_by_name (user_data, "selection-changed");
2906 }
2907
2908 static void
2909 dialog_update_preview_cb (GtkFileChooser *dialog,
2910                           gpointer        user_data)
2911 {
2912   g_signal_emit_by_name (user_data, "update-preview");
2913 }
2914
2915 static void
2916 dialog_notify_cb (GObject    *dialog,
2917                   GParamSpec *pspec,
2918                   gpointer    user_data)
2919 {
2920   gpointer iface;
2921
2922   iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
2923                                  GTK_TYPE_FILE_CHOOSER);
2924   if (g_object_interface_find_property (iface, pspec->name))
2925     g_object_notify (user_data, pspec->name);
2926
2927   if (g_ascii_strcasecmp (pspec->name, "local-only") == 0)
2928     {
2929       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2930       GtkFileChooserButtonPrivate *priv = button->priv;
2931
2932       if (priv->has_current_folder)
2933         {
2934           GtkTreeIter iter;
2935           gint pos;
2936           gpointer data;
2937
2938           pos = model_get_type_position (user_data,
2939                                          ROW_TYPE_CURRENT_FOLDER);
2940           gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
2941
2942           data = NULL;
2943           gtk_tree_model_get (priv->model, &iter, DATA_COLUMN, &data, -1);
2944
2945           /* If the path isn't local but we're in local-only mode now, remove
2946            * the custom-folder row */
2947           if (data && _gtk_file_has_native_path (G_FILE (data)) &&
2948               gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog)))
2949             {
2950               pos--;
2951               model_remove_rows (user_data, pos, 2);
2952             }
2953         }
2954
2955       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2956       update_combo_box (user_data);
2957     }
2958 }
2959
2960 static gboolean
2961 dialog_delete_event_cb (GtkWidget *dialog,
2962                         GdkEvent  *event,
2963                         gpointer   user_data)
2964 {
2965   g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
2966
2967   return TRUE;
2968 }
2969
2970 static void
2971 dialog_response_cb (GtkDialog *dialog,
2972                     gint       response,
2973                     gpointer   user_data)
2974 {
2975   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2976   GtkFileChooserButtonPrivate *priv = button->priv;
2977
2978   if (response == GTK_RESPONSE_ACCEPT ||
2979       response == GTK_RESPONSE_OK)
2980     {
2981       save_inactive_state (button);
2982
2983       g_signal_emit_by_name (button, "current-folder-changed");
2984       g_signal_emit_by_name (button, "selection-changed");
2985
2986       update_label_and_image (button);
2987       update_combo_box (button);
2988     }
2989   else
2990     {
2991       restore_inactive_state (button);
2992     }
2993
2994   if (priv->active)
2995     {
2996       g_signal_handler_unblock (priv->dialog,
2997                                 priv->dialog_folder_changed_id);
2998       g_signal_handler_unblock (priv->dialog,
2999                                 priv->dialog_file_activated_id);
3000       g_signal_handler_unblock (priv->dialog,
3001                                 priv->dialog_selection_changed_id);
3002       priv->active = FALSE;
3003     }
3004
3005   gtk_widget_set_sensitive (priv->combo_box, TRUE);
3006   gtk_widget_hide (priv->dialog);
3007
3008   if (response == GTK_RESPONSE_ACCEPT ||
3009       response == GTK_RESPONSE_OK)
3010     g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
3011 }
3012
3013
3014 /* ************************************************************************** *
3015  *  Public API                                                                *
3016  * ************************************************************************** */
3017
3018 /**
3019  * gtk_file_chooser_button_new:
3020  * @title: the title of the browse dialog.
3021  * @action: the open mode for the widget.
3022  * 
3023  * Creates a new file-selecting button widget.
3024  * 
3025  * Returns: a new button widget.
3026  * 
3027  * Since: 2.6
3028  **/
3029 GtkWidget *
3030 gtk_file_chooser_button_new (const gchar          *title,
3031                              GtkFileChooserAction  action)
3032 {
3033   g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
3034                         action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL);
3035
3036   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
3037                        "action", action,
3038                        "title", (title ? title : _(DEFAULT_TITLE)),
3039                        NULL);
3040 }
3041
3042 /**
3043  * gtk_file_chooser_button_new_with_dialog:
3044  * @dialog: the widget to use as dialog
3045  *
3046  * Creates a #GtkFileChooserButton widget which uses @dialog as its
3047  * file-picking window.
3048  *
3049  * Note that @dialog must be a #GtkDialog (or subclass) which
3050  * implements the #GtkFileChooser interface and must not have
3051  * %GTK_DIALOG_DESTROY_WITH_PARENT set.
3052  *
3053  * Also note that the dialog needs to have its confirmative button
3054  * added with response %GTK_RESPONSE_ACCEPT or %GTK_RESPONSE_OK in
3055  * order for the button to take over the file selected in the dialog.
3056  *
3057  * Returns: a new button widget.
3058  *
3059  * Since: 2.6
3060  **/
3061 GtkWidget *
3062 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
3063 {
3064   g_return_val_if_fail (GTK_IS_FILE_CHOOSER (dialog) && GTK_IS_DIALOG (dialog), NULL);
3065
3066   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
3067                        "dialog", dialog,
3068                        NULL);
3069 }
3070
3071 /**
3072  * gtk_file_chooser_button_set_title:
3073  * @button: the button widget to modify.
3074  * @title: the new browse dialog title.
3075  * 
3076  * Modifies the @title of the browse dialog used by @button.
3077  * 
3078  * Since: 2.6
3079  **/
3080 void
3081 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
3082                                    const gchar          *title)
3083 {
3084   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3085
3086   gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
3087   g_object_notify (G_OBJECT (button), "title");
3088 }
3089
3090 /**
3091  * gtk_file_chooser_button_get_title:
3092  * @button: the button widget to examine.
3093  * 
3094  * Retrieves the title of the browse dialog used by @button. The returned value
3095  * should not be modified or freed.
3096  * 
3097  * Returns: a pointer to the browse dialog's title.
3098  * 
3099  * Since: 2.6
3100  **/
3101 const gchar *
3102 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
3103 {
3104   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
3105
3106   return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
3107 }
3108
3109 /**
3110  * gtk_file_chooser_button_get_width_chars:
3111  * @button: the button widget to examine.
3112  * 
3113  * Retrieves the width in characters of the @button widget's entry and/or label.
3114  * 
3115  * Returns: an integer width (in characters) that the button will use to size itself.
3116  * 
3117  * Since: 2.6
3118  **/
3119 gint
3120 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
3121 {
3122   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
3123
3124   return gtk_label_get_width_chars (GTK_LABEL (button->priv->label));
3125 }
3126
3127 /**
3128  * gtk_file_chooser_button_set_width_chars:
3129  * @button: the button widget to examine.
3130  * @n_chars: the new width, in characters.
3131  * 
3132  * Sets the width (in characters) that @button will use to @n_chars.
3133  * 
3134  * Since: 2.6
3135  **/
3136 void
3137 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
3138                                          gint                  n_chars)
3139 {
3140   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3141
3142   gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
3143   g_object_notify (G_OBJECT (button), "width-chars");
3144 }
3145
3146 /**
3147  * gtk_file_chooser_button_set_focus_on_click:
3148  * @button: a #GtkFileChooserButton
3149  * @focus_on_click: whether the button grabs focus when clicked with the mouse
3150  * 
3151  * Sets whether the button will grab focus when it is clicked with the mouse.
3152  * Making mouse clicks not grab focus is useful in places like toolbars where
3153  * you don't want the keyboard focus removed from the main area of the
3154  * application.
3155  *
3156  * Since: 2.10
3157  **/
3158 void
3159 gtk_file_chooser_button_set_focus_on_click (GtkFileChooserButton *button,
3160                                             gboolean              focus_on_click)
3161 {
3162   GtkFileChooserButtonPrivate *priv;
3163
3164   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3165
3166   priv = button->priv;
3167
3168   focus_on_click = focus_on_click != FALSE;
3169
3170   if (priv->focus_on_click != focus_on_click)
3171     {
3172       priv->focus_on_click = focus_on_click;
3173       gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), focus_on_click);
3174       gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (priv->combo_box), focus_on_click);
3175       
3176       g_object_notify (G_OBJECT (button), "focus-on-click");
3177     }
3178 }
3179
3180 /**
3181  * gtk_file_chooser_button_get_focus_on_click:
3182  * @button: a #GtkFileChooserButton
3183  * 
3184  * Returns whether the button grabs focus when it is clicked with the mouse.
3185  * See gtk_file_chooser_button_set_focus_on_click().
3186  *
3187  * Return value: %TRUE if the button grabs focus when it is clicked with
3188  *               the mouse.
3189  *
3190  * Since: 2.10
3191  **/
3192 gboolean
3193 gtk_file_chooser_button_get_focus_on_click (GtkFileChooserButton *button)
3194 {
3195   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
3196   
3197   return button->priv->focus_on_click;
3198 }