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