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