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