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