]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserdefault.c
f2b2f7d15ae25b666eb6f16b8de204d6dfd5eee1
[~andy/gtk] / gtk / gtkfilechooserdefault.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechooserimpldefault.c: Default implementation of GtkFileChooser
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "gtkcellrendererseptext.h"
22 #include "gtkfilechooserimpldefault.h"
23 #include "gtkfilechooserentry.h"
24 #include "gtkfilechooserenums.h"
25 #include "gtkfilechooserutils.h"
26 #include "gtkfilechooser.h"
27 #include "gtkfilesystemmodel.h"
28
29 #include <gtk/gtkalignment.h>
30 #include <gtk/gtkcellrendererpixbuf.h>
31 #include <gtk/gtkcellrenderertext.h>
32 #include <gtk/gtkentry.h>
33 #include <gtk/gtkframe.h>
34 #include <gtk/gtkhbox.h>
35 #include <gtk/gtkhpaned.h>
36 #include <gtk/gtkicontheme.h>
37 #include <gtk/gtkimage.h>
38 #include <gtk/gtklabel.h>
39 #include <gtk/gtkmenuitem.h>
40 #include <gtk/gtkoptionmenu.h>
41 #include <gtk/gtkscrolledwindow.h>
42 #include <gtk/gtkstock.h>
43 #include <gtk/gtktable.h>
44 #include <gtk/gtktreeview.h>
45 #include <gtk/gtktreemodelsort.h>
46 #include <gtk/gtktreeselection.h>
47 #include <gtk/gtktreestore.h>
48 #include <gtk/gtkvbox.h>
49
50 #include <string.h>
51 #include <time.h>
52
53 typedef struct _GtkFileChooserImplDefaultClass GtkFileChooserImplDefaultClass;
54
55 #define GTK_FILE_CHOOSER_IMPL_DEFAULT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_IMPL_DEFAULT, GtkFileChooserImplDefaultClass))
56 #define GTK_IS_FILE_CHOOSER_IMPL_DEFAULT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_IMPL_DEFAULT))
57 #define GTK_FILE_CHOOSER_IMPL_DEFAULT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_IMPL_DEFAULT, GtkFileChooserImplDefaultClass))
58
59 struct _GtkFileChooserImplDefaultClass
60 {
61   GtkVBoxClass parent_class;
62 };
63
64 struct _GtkFileChooserImplDefault
65 {
66   GtkVBox parent_instance;
67
68   GtkFileSystem *file_system;
69   GtkFileSystemModel *tree_model;
70   GtkTreeStore *shortcuts_model;
71   GtkFileSystemModel *list_model;
72   GtkTreeModelSort *sort_model;
73
74   GtkFileChooserAction action;
75
76   GtkFileFilter *current_filter;
77   GSList *filters;
78
79   gboolean has_home;
80   gboolean has_desktop;
81   int num_roots;
82   int num_shortcuts;
83   int num_bookmarks;
84
85   guint bookmarks_changed_id;
86   GtkTreeIter bookmarks_iter;
87
88   GtkFilePath *current_folder;
89   GtkFilePath *preview_path;
90
91   GtkWidget *preview_frame;
92
93   GtkWidget *filter_alignment;
94   GtkWidget *filter_option_menu;
95   GtkWidget *tree_scrollwin;
96   GtkWidget *tree;
97   GtkWidget *shortcuts_scrollwin;
98   GtkWidget *shortcuts_tree;
99   GtkWidget *add_bookmark_button;
100   GtkWidget *remove_bookmark_button;
101   GtkWidget *list_scrollwin;
102   GtkWidget *list;
103   GtkWidget *entry;
104   GtkWidget *preview_widget;
105   GtkWidget *extra_widget;
106
107   guint folder_mode : 1;
108   guint local_only : 1;
109   guint preview_widget_active : 1;
110   guint select_multiple : 1;
111   guint show_hidden : 1;
112   guint changing_folder : 1;
113   guint list_sort_ascending : 1;
114   guint bookmarks_set : 1;
115 };
116
117 /* Column numbers for the shortcuts tree.  Keep these in sync with create_shortcuts_model() */
118 enum {
119   SHORTCUTS_COL_PIXBUF,
120   SHORTCUTS_COL_NAME,
121   SHORTCUTS_COL_PATH,
122   SHORTCUTS_COL_NUM_COLUMNS
123 };
124
125 /* Column numbers for the file list */
126 enum {
127   FILE_LIST_COL_NAME,
128   FILE_LIST_COL_SIZE,
129   FILE_LIST_COL_MTIME,
130   FILE_LIST_COL_NUM_COLUMNS
131 };
132
133 /* Standard icon size */
134 #define ICON_SIZE 36
135
136 static void gtk_file_chooser_impl_default_class_init   (GtkFileChooserImplDefaultClass *class);
137 static void gtk_file_chooser_impl_default_iface_init   (GtkFileChooserIface            *iface);
138 static void gtk_file_chooser_impl_default_init         (GtkFileChooserImplDefault      *impl);
139
140 static GObject* gtk_file_chooser_impl_default_constructor  (GType                  type,
141                                                             guint                  n_construct_properties,
142                                                             GObjectConstructParam *construct_params);
143 static void     gtk_file_chooser_impl_default_finalize     (GObject               *object);
144 static void     gtk_file_chooser_impl_default_set_property (GObject               *object,
145                                                             guint                  prop_id,
146                                                             const GValue          *value,
147                                                             GParamSpec            *pspec);
148 static void     gtk_file_chooser_impl_default_get_property (GObject               *object,
149                                                             guint                  prop_id,
150                                                             GValue                *value,
151                                                             GParamSpec            *pspec);
152 static void     gtk_file_chooser_impl_default_show_all     (GtkWidget             *widget);
153
154 static void           gtk_file_chooser_impl_default_set_current_folder     (GtkFileChooser    *chooser,
155                                                                             const GtkFilePath *path);
156 static GtkFilePath *  gtk_file_chooser_impl_default_get_current_folder     (GtkFileChooser    *chooser);
157 static void           gtk_file_chooser_impl_default_set_current_name       (GtkFileChooser    *chooser,
158                                                                             const gchar       *name);
159 static void           gtk_file_chooser_impl_default_select_path            (GtkFileChooser    *chooser,
160                                                                             const GtkFilePath *path);
161 static void           gtk_file_chooser_impl_default_unselect_path          (GtkFileChooser    *chooser,
162                                                                             const GtkFilePath *path);
163 static void           gtk_file_chooser_impl_default_select_all             (GtkFileChooser    *chooser);
164 static void           gtk_file_chooser_impl_default_unselect_all           (GtkFileChooser    *chooser);
165 static GSList *       gtk_file_chooser_impl_default_get_paths              (GtkFileChooser    *chooser);
166 static GtkFilePath *  gtk_file_chooser_impl_default_get_preview_path       (GtkFileChooser    *chooser);
167 static GtkFileSystem *gtk_file_chooser_impl_default_get_file_system        (GtkFileChooser    *chooser);
168 static void           gtk_file_chooser_impl_default_add_filter             (GtkFileChooser    *chooser,
169                                                                             GtkFileFilter     *filter);
170 static void           gtk_file_chooser_impl_default_remove_filter          (GtkFileChooser    *chooser,
171                                                                             GtkFileFilter     *filter);
172 static GSList *       gtk_file_chooser_impl_default_list_filters           (GtkFileChooser    *chooser);
173 static gboolean       gtk_file_chooser_impl_default_add_shortcut_folder    (GtkFileChooser    *chooser,
174                                                                             const GtkFilePath *path,
175                                                                             GError           **error);
176 static gboolean       gtk_file_chooser_impl_default_remove_shortcut_folder (GtkFileChooser    *chooser,
177                                                                             const GtkFilePath *path,
178                                                                             GError           **error);
179 static GSList *       gtk_file_chooser_impl_default_list_shortcut_folders  (GtkFileChooser    *chooser);
180
181 static void set_current_filter   (GtkFileChooserImplDefault *impl,
182                                   GtkFileFilter             *filter);
183 static void check_preview_change (GtkFileChooserImplDefault *impl);
184
185 static void filter_option_menu_changed (GtkOptionMenu             *option_menu,
186                                         GtkFileChooserImplDefault *impl);
187 static void tree_selection_changed     (GtkTreeSelection          *tree_selection,
188                                         GtkFileChooserImplDefault *impl);
189 static void shortcuts_selection_changed (GtkTreeSelection          *tree_selection,
190                                          GtkFileChooserImplDefault *impl);
191 static void list_selection_changed     (GtkTreeSelection          *tree_selection,
192                                         GtkFileChooserImplDefault *impl);
193 static void list_row_activated         (GtkTreeView               *tree_view,
194                                         GtkTreePath               *path,
195                                         GtkTreeViewColumn         *column,
196                                         GtkFileChooserImplDefault *impl);
197 static void entry_activate             (GtkEntry                  *entry,
198                                         GtkFileChooserImplDefault *impl);
199
200 static void tree_name_data_func (GtkTreeViewColumn *tree_column,
201                                  GtkCellRenderer   *cell,
202                                  GtkTreeModel      *tree_model,
203                                  GtkTreeIter       *iter,
204                                  gpointer           data);
205 static void list_icon_data_func (GtkTreeViewColumn *tree_column,
206                                  GtkCellRenderer   *cell,
207                                  GtkTreeModel      *tree_model,
208                                  GtkTreeIter       *iter,
209                                  gpointer           data);
210 static void list_name_data_func (GtkTreeViewColumn *tree_column,
211                                  GtkCellRenderer   *cell,
212                                  GtkTreeModel      *tree_model,
213                                  GtkTreeIter       *iter,
214                                  gpointer           data);
215 #if 0
216 static void list_size_data_func (GtkTreeViewColumn *tree_column,
217                                  GtkCellRenderer   *cell,
218                                  GtkTreeModel      *tree_model,
219                                  GtkTreeIter       *iter,
220                                  gpointer           data);
221 #endif
222 static void list_mtime_data_func (GtkTreeViewColumn *tree_column,
223                                   GtkCellRenderer   *cell,
224                                   GtkTreeModel      *tree_model,
225                                   GtkTreeIter       *iter,
226                                   gpointer           data);
227
228 static GObjectClass *parent_class;
229
230 GType
231 _gtk_file_chooser_impl_default_get_type (void)
232 {
233   static GType file_chooser_impl_default_type = 0;
234
235   if (!file_chooser_impl_default_type)
236     {
237       static const GTypeInfo file_chooser_impl_default_info =
238       {
239         sizeof (GtkFileChooserImplDefaultClass),
240         NULL,           /* base_init */
241         NULL,           /* base_finalize */
242         (GClassInitFunc) gtk_file_chooser_impl_default_class_init,
243         NULL,           /* class_finalize */
244         NULL,           /* class_data */
245         sizeof (GtkFileChooserImplDefault),
246         0,              /* n_preallocs */
247         (GInstanceInitFunc) gtk_file_chooser_impl_default_init,
248       };
249
250       static const GInterfaceInfo file_chooser_info =
251       {
252         (GInterfaceInitFunc) gtk_file_chooser_impl_default_iface_init, /* interface_init */
253         NULL,                                                          /* interface_finalize */
254         NULL                                                           /* interface_data */
255       };
256
257       file_chooser_impl_default_type = g_type_register_static (GTK_TYPE_VBOX, "GtkFileChooserImplDefault",
258                                                          &file_chooser_impl_default_info, 0);
259       g_type_add_interface_static (file_chooser_impl_default_type,
260                                    GTK_TYPE_FILE_CHOOSER,
261                                    &file_chooser_info);
262     }
263
264   return file_chooser_impl_default_type;
265 }
266
267 static void
268 gtk_file_chooser_impl_default_class_init (GtkFileChooserImplDefaultClass *class)
269 {
270   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
271   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
272
273   parent_class = g_type_class_peek_parent (class);
274
275   gobject_class->finalize = gtk_file_chooser_impl_default_finalize;
276   gobject_class->constructor = gtk_file_chooser_impl_default_constructor;
277   gobject_class->set_property = gtk_file_chooser_impl_default_set_property;
278   gobject_class->get_property = gtk_file_chooser_impl_default_get_property;
279
280   widget_class->show_all = gtk_file_chooser_impl_default_show_all;
281
282   _gtk_file_chooser_install_properties (gobject_class);
283 }
284
285 static void
286 gtk_file_chooser_impl_default_iface_init (GtkFileChooserIface *iface)
287 {
288   iface->select_path = gtk_file_chooser_impl_default_select_path;
289   iface->unselect_path = gtk_file_chooser_impl_default_unselect_path;
290   iface->select_all = gtk_file_chooser_impl_default_select_all;
291   iface->unselect_all = gtk_file_chooser_impl_default_unselect_all;
292   iface->get_paths = gtk_file_chooser_impl_default_get_paths;
293   iface->get_preview_path = gtk_file_chooser_impl_default_get_preview_path;
294   iface->get_file_system = gtk_file_chooser_impl_default_get_file_system;
295   iface->set_current_folder = gtk_file_chooser_impl_default_set_current_folder;
296   iface->get_current_folder = gtk_file_chooser_impl_default_get_current_folder;
297   iface->set_current_name = gtk_file_chooser_impl_default_set_current_name;
298   iface->add_filter = gtk_file_chooser_impl_default_add_filter;
299   iface->remove_filter = gtk_file_chooser_impl_default_remove_filter;
300   iface->list_filters = gtk_file_chooser_impl_default_list_filters;
301   iface->add_shortcut_folder = gtk_file_chooser_impl_default_add_shortcut_folder;
302   iface->remove_shortcut_folder = gtk_file_chooser_impl_default_remove_shortcut_folder;
303   iface->list_shortcut_folders = gtk_file_chooser_impl_default_list_shortcut_folders;
304 }
305
306 static void
307 gtk_file_chooser_impl_default_init (GtkFileChooserImplDefault *impl)
308 {
309   impl->folder_mode = FALSE;
310   impl->local_only = TRUE;
311   impl->preview_widget_active = TRUE;
312   impl->select_multiple = FALSE;
313   impl->show_hidden = FALSE;
314
315   gtk_container_set_border_width (GTK_CONTAINER (impl), 5);
316 }
317
318 static void
319 gtk_file_chooser_impl_default_finalize (GObject *object)
320 {
321   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (object);
322
323   g_signal_handler_disconnect (impl->file_system, impl->bookmarks_changed_id);
324   impl->bookmarks_changed_id = 0;
325   g_object_unref (impl->file_system);
326
327   G_OBJECT_CLASS (parent_class)->finalize (object);
328 }
329
330 static void
331 update_preview_widget_visibility (GtkFileChooserImplDefault *impl)
332 {
333   if (impl->preview_widget_active && impl->preview_widget)
334     gtk_widget_show (impl->preview_frame);
335   else
336     gtk_widget_hide (impl->preview_frame);
337 }
338
339 static void
340 set_preview_widget (GtkFileChooserImplDefault *impl,
341                     GtkWidget                 *preview_widget)
342 {
343   if (preview_widget == impl->preview_widget)
344     return;
345
346   if (impl->preview_widget)
347     gtk_container_remove (GTK_CONTAINER (impl->preview_frame),
348                           impl->preview_widget);
349
350   impl->preview_widget = preview_widget;
351   if (impl->preview_widget)
352     {
353       gtk_widget_show (impl->preview_widget);
354       gtk_container_add (GTK_CONTAINER (impl->preview_frame),
355                          impl->preview_widget);
356     }
357
358   update_preview_widget_visibility (impl);
359 }
360
361 /* Used from gtk_tree_model_foreach(); selects the item that corresponds to the
362  * current path. */
363 static gboolean
364 set_current_shortcut_foreach_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
365 {
366   GtkFileChooserImplDefault *impl;
367   GtkFilePath *model_path;
368   GtkTreeSelection *selection;
369
370   impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (data);
371
372   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->shortcuts_tree));
373
374   gtk_tree_model_get (model, iter, SHORTCUTS_COL_PATH, &model_path, -1);
375
376   if (model_path && impl->current_folder && gtk_file_path_compare (model_path, impl->current_folder) == 0)
377     {
378       gtk_tree_selection_select_path (selection, path);
379       return TRUE;
380     }
381   else
382     gtk_tree_selection_unselect_path (selection, path);
383
384   return FALSE;
385 }
386
387 /* Selects the appropriate node in the shortcuts tree based on the current folder */
388 static void
389 shortcuts_select_folder (GtkFileChooserImplDefault *impl)
390 {
391   gtk_tree_model_foreach (GTK_TREE_MODEL (impl->shortcuts_model),
392                           set_current_shortcut_foreach_cb,
393                           impl);
394 }
395
396 /* Convenience function to get the display name and icon info for a path */
397 static GtkFileInfo *
398 get_file_info (GtkFileSystem *file_system, const GtkFilePath *path, GError **error)
399 {
400   GtkFilePath *parent_path;
401   GtkFileFolder *parent_folder;
402   GtkFileInfo *info;
403
404   if (!gtk_file_system_get_parent (file_system, path, &parent_path, error))
405     return NULL;
406
407   parent_folder = gtk_file_system_get_folder (file_system, parent_path,
408                                               GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON,
409                                               error);
410   gtk_file_path_free (parent_path);
411
412   if (!parent_folder)
413     return NULL;
414
415   info = gtk_file_folder_get_info (parent_folder, path, error);
416   g_object_unref (parent_folder);
417
418   return info;
419 }
420
421 /* Inserts a path in the shortcuts tree, making a copy of it.  A position of -1
422  * indicates the end of the tree.  If the label is NULL, then the display name
423  * of a GtkFileInfo is used.
424  */
425 static gboolean
426 shortcuts_insert_path (GtkFileChooserImplDefault *impl,
427                        int                        pos,
428                        const GtkFilePath         *path,
429                        gboolean                   is_root,
430                        const char                *label,
431                        GError                   **error)
432 {
433   GtkFileInfo *info;
434   GtkFilePath *path_copy;
435   GdkPixbuf *pixbuf;
436   GtkTreeIter iter;
437
438   /* FIXME: what if someone adds a shortcut to a root?  get_file_info() will not
439    * work in that case, I think...
440    */
441
442   if (is_root)
443     info = gtk_file_system_get_root_info (impl->file_system,
444                                           path,
445                                           GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON,
446                                           error);
447   else
448     info = get_file_info (impl->file_system, path, error);
449
450   if (!info)
451     return FALSE;
452
453   pixbuf = gtk_file_info_render_icon (info, impl->shortcuts_tree, ICON_SIZE);
454
455   gtk_tree_store_insert (impl->shortcuts_model, &iter, NULL, pos);
456   path_copy = gtk_file_path_copy (path);
457
458   if (!label)
459     label = gtk_file_info_get_display_name (info);
460
461   gtk_tree_store_set (impl->shortcuts_model, &iter,
462                       SHORTCUTS_COL_PIXBUF, pixbuf,
463                       SHORTCUTS_COL_NAME, label,
464                       SHORTCUTS_COL_PATH, path_copy,
465                       -1);
466
467   gtk_file_info_free (info);
468
469   if (pixbuf)
470     g_object_unref (pixbuf);
471
472   return TRUE;
473 }
474
475 /* Appends an item for the user's home directory to the shortcuts model */
476 static void
477 shortcuts_append_home (GtkFileChooserImplDefault *impl)
478 {
479   const char *name;
480   const char *home;
481   GtkFilePath *home_path;
482   char *label;
483
484   name = g_get_user_name ();
485   label = g_strdup_printf ("%s's Home", name);
486
487   home = g_get_home_dir ();
488   home_path = gtk_file_system_filename_to_path (impl->file_system, home);
489
490   impl->has_home = shortcuts_insert_path (impl, -1, home_path, FALSE, label, NULL); /* FIXME: use GError? */
491
492   g_free (label);
493   gtk_file_path_free (home_path);
494 }
495
496 /* Appends the ~/Desktop directory to the shortcuts model */
497 static void
498 shortcuts_append_desktop (GtkFileChooserImplDefault *impl)
499 {
500   char *name;
501   GtkFilePath *path;
502
503   /* FIXME: What is the Right Way of finding the desktop directory? */
504
505   name = g_build_filename (g_get_home_dir (), "Desktop", NULL);
506   path = gtk_file_system_filename_to_path (impl->file_system, name);
507   g_free (name);
508
509   impl->has_desktop = shortcuts_insert_path (impl, -1, path, FALSE, NULL, NULL); /* FIXME: use GError? */
510   gtk_file_path_free (path);
511 }
512
513 /* Appends all the file system roots to the shortcuts model */
514 static void
515 shortcuts_append_file_system_roots (GtkFileChooserImplDefault *impl)
516 {
517   GSList *roots, *l;
518
519   roots = gtk_file_system_list_roots (impl->file_system);
520   /* FIXME: handle the roots-changed signal on the file system */
521
522   impl->num_roots = 0;
523
524   for (l = roots; l; l = l->next)
525     {
526       GtkFilePath *path;
527
528       path = l->data;
529       if (shortcuts_insert_path (impl, -1, path, TRUE, NULL, NULL)) /* FIXME: use GError? */
530         impl->num_roots++;
531     }
532
533   gtk_file_paths_free (roots);
534 }
535
536 /* Removes the bookmarks separator node and all the bookmarks from the tree
537  * model.
538  */
539 static void
540 remove_bookmark_rows (GtkFileChooserImplDefault *impl)
541 {
542   GtkTreePath *path;
543   GtkTreeIter iter;
544
545   if (!impl->bookmarks_set)
546     return;
547
548   /* Ugh.  Is there a better way to do this? */
549
550   path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &impl->bookmarks_iter);
551
552   while (gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->shortcuts_model), &iter, path))
553     gtk_tree_store_remove (impl->shortcuts_model, &impl->bookmarks_iter);
554
555   impl->bookmarks_set = FALSE;
556 }
557
558 /* Appends the bookmarks separator node and the bookmarks from the file system. */
559 static void
560 shortcuts_append_bookmarks (GtkFileChooserImplDefault *impl)
561 {
562   GSList *bookmarks, *l;
563
564   remove_bookmark_rows (impl);
565
566   gtk_tree_store_append (impl->shortcuts_model, &impl->bookmarks_iter, NULL);
567   gtk_tree_store_set (impl->shortcuts_model, &impl->bookmarks_iter,
568                       SHORTCUTS_COL_PIXBUF, NULL,
569                       SHORTCUTS_COL_NAME, NULL,
570                       SHORTCUTS_COL_PATH, NULL,
571                       -1);
572   impl->bookmarks_set = TRUE;
573   impl->num_bookmarks = 0;
574
575   bookmarks = gtk_file_system_list_bookmarks (impl->file_system);
576
577   for (l = bookmarks; l; l = l->next)
578     {
579       GtkFilePath *path;
580
581       path = l->data;
582       if (shortcuts_insert_path (impl, -1, path, FALSE, NULL, NULL)) /* FIXME: use GError? */
583         impl->num_bookmarks++;
584     }
585 }
586
587 /* Creates the GtkTreeStore used as the shortcuts model */
588 static void
589 create_shortcuts_model (GtkFileChooserImplDefault *impl)
590 {
591   if (impl->shortcuts_model)
592     g_object_unref (impl->shortcuts_model);
593
594   /* Keep this order in sync with the SHORCUTS_COL_* enum values */
595   impl->shortcuts_model = gtk_tree_store_new (SHORTCUTS_COL_NUM_COLUMNS,
596                                               GDK_TYPE_PIXBUF,  /* pixbuf */
597                                               G_TYPE_STRING,    /* name */
598                                               G_TYPE_POINTER);  /* path */
599
600   if (impl->file_system)
601     {
602       shortcuts_append_home (impl);
603       shortcuts_append_desktop (impl);
604       shortcuts_append_file_system_roots (impl);
605       shortcuts_append_bookmarks (impl);
606     }
607
608   gtk_tree_view_set_model (GTK_TREE_VIEW (impl->shortcuts_tree), GTK_TREE_MODEL (impl->shortcuts_model));
609 }
610
611 /* Creates the widgets for the filter option menu */
612 static GtkWidget *
613 create_filter (GtkFileChooserImplDefault *impl)
614 {
615   GtkWidget *hbox;
616   GtkWidget *label;
617
618   impl->filter_alignment = gtk_alignment_new (0.0, 0.5, 0.0, 1.0);
619   gtk_alignment_set_padding (GTK_ALIGNMENT (impl->filter_alignment), 0, 6, 0, 0);
620   /* Don't show filter initially -- don't gtk_widget_show() the filter_alignment here */
621
622   hbox = gtk_hbox_new (FALSE, 6);
623   gtk_container_add (GTK_CONTAINER (impl->filter_alignment), hbox);
624   gtk_widget_show (hbox);
625
626   label = gtk_label_new_with_mnemonic ("Files of _type:");
627   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
628   gtk_widget_show (label);
629
630   impl->filter_option_menu = gtk_option_menu_new ();
631   gtk_option_menu_set_menu (GTK_OPTION_MENU (impl->filter_option_menu),
632                             gtk_menu_new ());
633   gtk_box_pack_start (GTK_BOX (hbox), impl->filter_option_menu, FALSE, FALSE, 0);
634   gtk_widget_show (impl->filter_option_menu);
635
636   gtk_label_set_mnemonic_widget (GTK_LABEL (label), impl->filter_option_menu);
637
638   g_signal_connect (impl->filter_option_menu, "changed",
639                     G_CALLBACK (filter_option_menu_changed), impl);
640
641   return impl->filter_alignment;
642 }
643
644 /* Creates the widgets for the folder tree */
645 static GtkWidget *
646 create_folder_tree (GtkFileChooserImplDefault *impl)
647 {
648   GtkTreeSelection *selection;
649
650   /* Scrolled window */
651
652   impl->tree_scrollwin = gtk_scrolled_window_new (NULL, NULL);
653   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->tree_scrollwin),
654                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
655   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->tree_scrollwin),
656                                        GTK_SHADOW_IN);
657   if (impl->folder_mode)
658     gtk_widget_show (impl->tree_scrollwin);
659
660   /* Tree */
661
662   impl->tree = gtk_tree_view_new ();
663   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (impl->tree), FALSE);
664
665   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->tree));
666   g_signal_connect (selection, "changed",
667                     G_CALLBACK (tree_selection_changed), impl);
668
669   gtk_container_add (GTK_CONTAINER (impl->tree_scrollwin), impl->tree);
670   gtk_widget_show (impl->tree);
671
672   /* Model */
673
674   impl->tree_model = _gtk_file_system_model_new (impl->file_system, NULL, -1,
675                                                  GTK_FILE_INFO_DISPLAY_NAME);
676   _gtk_file_system_model_set_show_files (impl->tree_model, FALSE);
677
678   gtk_tree_view_set_model (GTK_TREE_VIEW (impl->tree),
679                            GTK_TREE_MODEL (impl->tree_model));
680
681   /* Column */
682
683   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (impl->tree), 0,
684                                               "File name",
685                                               gtk_cell_renderer_text_new (),
686                                               tree_name_data_func, impl, NULL);
687   gtk_tree_view_set_search_column (GTK_TREE_VIEW (impl->tree),
688                                    GTK_FILE_SYSTEM_MODEL_DISPLAY_NAME);
689
690   return impl->tree_scrollwin;
691 }
692
693 /* Callback used when the "Add bookmark" button is clicked */
694 static void
695 add_bookmark_button_clicked_cb (GtkButton *button,
696                                 GtkFileChooserImplDefault *impl)
697 {
698   gtk_file_system_add_bookmark (impl->file_system, impl->current_folder, NULL); /* FIXME: use GError */
699 }
700
701 /* Callback used when the "Remove bookmark" button is clicked */
702 static void
703 remove_bookmark_button_clicked_cb (GtkButton *button,
704                                    GtkFileChooserImplDefault *impl)
705 {
706   GtkTreeSelection *selection;
707   GtkTreeIter iter;
708   GtkFilePath *path;
709
710   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->shortcuts_tree));
711
712   if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
713     return;
714
715   gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_PATH, &path, -1);
716
717   gtk_file_system_remove_bookmark (impl->file_system, path, NULL); /* FIXME: use GError */
718 }
719
720 /* Sensitize the "add bookmark" button if the current folder is not in the
721  * bookmarks list, or de-sensitize it otherwise.
722  */
723 static void
724 bookmarks_check_add_sensitivity (GtkFileChooserImplDefault *impl)
725 {
726   GtkTreeIter iter;
727   gboolean exists;
728
729   exists = FALSE;
730
731   if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter))
732     do
733       {
734         GtkFilePath *path;
735
736         gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_PATH, &path, -1);
737
738         if (path && gtk_file_path_compare (path, impl->current_folder) == 0)
739           {
740             exists = TRUE;
741             break;
742           }
743       }
744     while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter));
745
746   gtk_widget_set_sensitive (impl->add_bookmark_button, !exists);
747 }
748
749 /* Sets the sensitivity of the "remove bookmark" button depending on whether a
750  * bookmark row is selected in the shortcuts tree.
751  */
752 static void
753 bookmarks_check_remove_sensitivity (GtkFileChooserImplDefault *impl)
754 {
755   GtkTreeSelection *selection;
756   GtkTreeIter iter;
757   gboolean is_bookmark;
758
759   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->shortcuts_tree));
760
761   if (gtk_tree_selection_get_selected (selection, NULL, &iter))
762     {
763       GtkTreePath *bookmarks_path;
764       GtkTreePath *sel_path;
765
766       bookmarks_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model),
767                                                 &impl->bookmarks_iter);
768       sel_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter);
769
770       is_bookmark = (gtk_tree_path_compare (bookmarks_path, sel_path) < 0);
771
772       gtk_tree_path_free (bookmarks_path);
773       gtk_tree_path_free (sel_path);
774     }
775   else
776     is_bookmark = FALSE;
777
778   gtk_widget_set_sensitive (impl->remove_bookmark_button, is_bookmark);
779 }
780
781 /* Creates the widgets for the shortcuts and bookmarks tree */
782 static GtkWidget *
783 create_shortcuts_tree (GtkFileChooserImplDefault *impl)
784 {
785   GtkWidget *vbox;
786   GtkWidget *hbox;
787   GtkTreeSelection *selection;
788   GtkTreeViewColumn *column;
789   GtkCellRenderer *renderer;
790   GtkWidget *image;
791
792   vbox = gtk_vbox_new (FALSE, 12);
793   gtk_widget_show (vbox);
794
795   /* Scrolled window */
796
797   impl->shortcuts_scrollwin = gtk_scrolled_window_new (NULL, NULL);
798   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->shortcuts_scrollwin),
799                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
800   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->shortcuts_scrollwin),
801                                        GTK_SHADOW_IN);
802   gtk_box_pack_start (GTK_BOX (vbox), impl->shortcuts_scrollwin, TRUE, TRUE, 0);
803   gtk_widget_show (impl->shortcuts_scrollwin);
804
805   /* Tree */
806
807   impl->shortcuts_tree = gtk_tree_view_new ();
808   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (impl->shortcuts_tree), FALSE);
809
810   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->shortcuts_tree));
811   g_signal_connect (selection, "changed",
812                     G_CALLBACK (shortcuts_selection_changed), impl);
813
814   gtk_container_add (GTK_CONTAINER (impl->shortcuts_scrollwin), impl->shortcuts_tree);
815   gtk_widget_show (impl->shortcuts_tree);
816
817   /* Model */
818
819   create_shortcuts_model (impl);
820
821   /* Column */
822
823   column = gtk_tree_view_column_new ();
824   gtk_tree_view_column_set_title (column, "Folder");
825
826   renderer = gtk_cell_renderer_pixbuf_new ();
827   gtk_tree_view_column_pack_start (column, renderer, FALSE);
828   gtk_tree_view_column_set_attributes (column, renderer,
829                                        "pixbuf", SHORTCUTS_COL_PIXBUF,
830                                        NULL);
831
832   renderer = _gtk_cell_renderer_sep_text_new ();
833   gtk_tree_view_column_pack_start (column, renderer, TRUE);
834   gtk_tree_view_column_set_attributes (column, renderer,
835                                        "text", SHORTCUTS_COL_NAME,
836                                        NULL);
837
838   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->shortcuts_tree), column);
839
840   /* Bookmark buttons */
841
842   hbox = gtk_hbox_new (FALSE, 12);
843   gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
844   gtk_widget_show (hbox);
845
846   impl->add_bookmark_button = gtk_button_new_with_label ("Add bookmark");
847   g_signal_connect (impl->add_bookmark_button, "clicked",
848                     G_CALLBACK (add_bookmark_button_clicked_cb), impl);
849   gtk_box_pack_start (GTK_BOX (hbox), impl->add_bookmark_button, TRUE, TRUE, 0);
850   gtk_widget_set_sensitive (impl->add_bookmark_button, FALSE);
851   gtk_widget_show (impl->add_bookmark_button);
852
853   impl->remove_bookmark_button = gtk_button_new ();
854   g_signal_connect (impl->remove_bookmark_button, "clicked",
855                     G_CALLBACK (remove_bookmark_button_clicked_cb), impl);
856   image = gtk_image_new_from_stock (GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON);
857   gtk_container_add (GTK_CONTAINER (impl->remove_bookmark_button), image);
858   gtk_widget_show (image);
859   gtk_widget_set_sensitive (impl->remove_bookmark_button, FALSE);
860   gtk_box_pack_start (GTK_BOX (hbox), impl->remove_bookmark_button, FALSE, FALSE, 0);
861   gtk_widget_show (impl->remove_bookmark_button);
862
863   shortcuts_select_folder (impl);
864
865   return vbox;
866 }
867
868 /* Creates the widgets for the file list */
869 static GtkWidget *
870 create_file_list (GtkFileChooserImplDefault *impl)
871 {
872   GtkTreeSelection *selection;
873   GtkTreeViewColumn *column;
874   GtkCellRenderer *renderer;
875
876   /* Scrolled window */
877
878   impl->list_scrollwin = gtk_scrolled_window_new (NULL, NULL);
879   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->list_scrollwin),
880                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
881   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->list_scrollwin),
882                                        GTK_SHADOW_IN);
883   if (!impl->folder_mode)
884     gtk_widget_show (impl->list_scrollwin);
885
886   /* Tree/list view */
887
888   impl->list = gtk_tree_view_new ();
889   gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (impl->list), TRUE);
890   gtk_container_add (GTK_CONTAINER (impl->list_scrollwin), impl->list);
891   g_signal_connect (impl->list, "row_activated",
892                     G_CALLBACK (list_row_activated), impl);
893   gtk_widget_show (impl->list);
894
895   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
896   g_signal_connect (selection, "changed",
897                     G_CALLBACK (list_selection_changed), impl);
898
899   /* Filename column */
900
901   column = gtk_tree_view_column_new ();
902   gtk_tree_view_column_set_title (column, "File name");
903   gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_NAME);
904
905   renderer = gtk_cell_renderer_pixbuf_new ();
906   gtk_tree_view_column_pack_start (column, renderer, TRUE);
907   gtk_tree_view_column_set_cell_data_func (column, renderer,
908                                            list_icon_data_func, impl, NULL);
909
910   renderer = gtk_cell_renderer_text_new ();
911   gtk_tree_view_column_pack_start (column, renderer, TRUE);
912   gtk_tree_view_column_set_cell_data_func (column, renderer,
913                                            list_name_data_func, impl, NULL);
914
915   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->list), column);
916 #if 0
917   /* Size column */
918
919   column = gtk_tree_view_column_new ();
920   gtk_tree_view_column_set_title (column, "Size");
921
922   renderer = gtk_cell_renderer_text_new ();
923   gtk_tree_view_column_pack_start (column, renderer, TRUE);
924   gtk_tree_view_column_set_cell_data_func (column, renderer,
925                                            list_size_data_func, impl, NULL);
926   gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_SIZE);
927   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->list), column);
928 #endif
929   /* Modification time column */
930
931   column = gtk_tree_view_column_new ();
932   gtk_tree_view_column_set_title (column, "Modified");
933
934   renderer = gtk_cell_renderer_text_new ();
935   gtk_tree_view_column_pack_start (column, renderer, TRUE);
936   gtk_tree_view_column_set_cell_data_func (column, renderer,
937                                            list_mtime_data_func, impl, NULL);
938   gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_MTIME);
939   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->list), column);
940
941   return impl->list_scrollwin;
942 }
943
944 static GtkWidget *
945 create_filename_entry (GtkFileChooserImplDefault *impl)
946 {
947   GtkWidget *hbox;
948   GtkWidget *label;
949
950   hbox = gtk_hbox_new (FALSE, 6);
951   gtk_widget_show (hbox);
952
953   label = gtk_label_new_with_mnemonic ("_Location:");
954   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
955   gtk_widget_show (label);
956
957   impl->entry = _gtk_file_chooser_entry_new ();
958   gtk_entry_set_activates_default (GTK_ENTRY (impl->entry), TRUE);
959   g_signal_connect (impl->entry, "activate",
960                     G_CALLBACK (entry_activate), impl);
961   _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (impl->entry),
962                                            impl->file_system);
963
964   gtk_box_pack_start (GTK_BOX (hbox), impl->entry, TRUE, TRUE, 0);
965   gtk_widget_show (impl->entry);
966
967   gtk_label_set_mnemonic_widget (GTK_LABEL (label), impl->entry);
968
969   return hbox;
970 }
971
972 static GObject*
973 gtk_file_chooser_impl_default_constructor (GType                  type,
974                                            guint                  n_construct_properties,
975                                            GObjectConstructParam *construct_params)
976 {
977   GtkFileChooserImplDefault *impl;
978   GObject *object;
979   GtkWidget *table;
980   GtkWidget *hpaned;
981   GtkWidget *widget;
982 #if 0
983   GList *focus_chain;
984 #endif
985   GtkWidget *hbox;
986
987   object = parent_class->constructor (type,
988                                       n_construct_properties,
989                                       construct_params);
990   impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (object);
991
992   g_assert (impl->file_system);
993
994   gtk_widget_push_composite_child ();
995
996   /* Basic table */
997
998   table = gtk_table_new (3, 2, FALSE);
999   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
1000   gtk_box_pack_start (GTK_BOX (impl), table, TRUE, TRUE, 0);
1001   gtk_widget_show (table);
1002
1003   /* Filter */
1004
1005   widget = create_filter (impl);
1006   gtk_table_attach (GTK_TABLE (table), widget,
1007                     0, 1,                   0, 1,
1008                     GTK_EXPAND | GTK_FILL,  0,
1009                     0,                      0);
1010
1011   /* Paned widget */
1012
1013   hpaned = gtk_hpaned_new ();
1014   gtk_table_attach (GTK_TABLE (table), hpaned,
1015                     0, 1,                   1, 2,
1016                     GTK_EXPAND | GTK_FILL,  GTK_EXPAND | GTK_FILL,
1017                     0,                      0);
1018   gtk_paned_set_position (GTK_PANED (hpaned), 200); /* FIXME: this sucks */
1019   gtk_widget_show (hpaned);
1020
1021   /* Shortcuts list */
1022
1023   widget = create_shortcuts_tree (impl);
1024   gtk_paned_add1 (GTK_PANED (hpaned), widget);
1025
1026   /* Folder tree */
1027
1028   hbox = gtk_hbox_new (FALSE, 12);
1029   gtk_paned_add2 (GTK_PANED (hpaned), hbox);
1030   gtk_widget_show (hbox);
1031
1032   widget = create_folder_tree (impl);
1033   gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
1034
1035   /* File list */
1036
1037   widget = create_file_list (impl);
1038   gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
1039
1040   /* Location/filename entry */
1041
1042   widget = create_filename_entry (impl);
1043   gtk_table_attach (GTK_TABLE (table), widget,
1044                     0, 2,                   2, 3,
1045                     GTK_EXPAND | GTK_FILL,  0,
1046                     0,                      6);
1047
1048   /* Preview */
1049
1050   impl->preview_frame = gtk_frame_new ("Preview");
1051   gtk_table_attach (GTK_TABLE (table), impl->preview_frame,
1052                     1, 2,                   0, 2,
1053                     0,                      GTK_EXPAND | GTK_FILL,
1054                     0,                      0);
1055   /* Don't show preview frame initially */
1056
1057 #if 0
1058   focus_chain = g_list_append (NULL, impl->entry);
1059   focus_chain = g_list_append (focus_chain, impl->tree);
1060   focus_chain = g_list_append (focus_chain, impl->list);
1061   gtk_container_set_focus_chain (GTK_CONTAINER (impl), focus_chain);
1062   g_list_free (focus_chain);
1063 #endif
1064
1065   gtk_widget_pop_composite_child ();
1066
1067   return object;
1068 }
1069
1070 /* Sets the extra_widget by packing it in the appropriate place */
1071 static void
1072 set_extra_widget (GtkFileChooserImplDefault *impl,
1073                   GtkWidget                 *extra_widget)
1074 {
1075   if (extra_widget == impl->extra_widget)
1076     return;
1077
1078   if (impl->extra_widget)
1079     gtk_container_remove (GTK_CONTAINER (impl), impl->extra_widget);
1080
1081   impl->extra_widget = extra_widget;
1082   if (impl->extra_widget)
1083     {
1084       gtk_widget_show (impl->extra_widget);
1085       gtk_box_pack_end (GTK_BOX (impl), impl->extra_widget, FALSE, FALSE, 0);
1086     }
1087 }
1088
1089 /* Callback used when the set of bookmarks changes in the file system */
1090 static void
1091 bookmarks_changed_cb (GtkFileSystem             *file_system,
1092                       GtkFileChooserImplDefault *impl)
1093 {
1094   shortcuts_append_bookmarks (impl);
1095   shortcuts_select_folder (impl);
1096
1097   bookmarks_check_add_sensitivity (impl);
1098   bookmarks_check_remove_sensitivity (impl);
1099 }
1100
1101 static void
1102 gtk_file_chooser_impl_default_set_property (GObject         *object,
1103                                             guint            prop_id,
1104                                             const GValue    *value,
1105                                             GParamSpec      *pspec)
1106
1107 {
1108   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (object);
1109
1110   switch (prop_id)
1111     {
1112     case GTK_FILE_CHOOSER_PROP_ACTION:
1113       impl->action = g_value_get_enum (value);
1114       break;
1115     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM:
1116       {
1117         GtkFileSystem *file_system = g_value_get_object (value);
1118         if (impl->file_system != file_system)
1119           {
1120             if (impl->file_system)
1121               {
1122                 g_signal_handler_disconnect (impl->file_system, impl->bookmarks_changed_id);
1123                 impl->bookmarks_changed_id = 0;
1124                 g_object_unref (impl->file_system);
1125               }
1126             impl->file_system = file_system;
1127             if (impl->file_system)
1128               {
1129                 g_object_ref (impl->file_system);
1130                 impl->bookmarks_changed_id = g_signal_connect (impl->file_system, "bookmarks-changed",
1131                                                                G_CALLBACK (bookmarks_changed_cb),
1132                                                                impl);
1133               }
1134           }
1135       }
1136       break;
1137     case GTK_FILE_CHOOSER_PROP_FILTER:
1138       set_current_filter (impl, g_value_get_object (value));
1139       break;
1140     case GTK_FILE_CHOOSER_PROP_FOLDER_MODE:
1141       {
1142         gboolean folder_mode = g_value_get_boolean (value);
1143         if (folder_mode != impl->folder_mode)
1144           {
1145             impl->folder_mode = folder_mode;
1146             if (impl->folder_mode)
1147               {
1148                 gtk_widget_hide (impl->list_scrollwin);
1149                 gtk_widget_show (impl->tree_scrollwin);
1150               }
1151             else
1152               {
1153                 gtk_widget_hide (impl->tree_scrollwin);
1154                 gtk_widget_show (impl->list_scrollwin);
1155               }
1156           }
1157       }
1158       break;
1159     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
1160       impl->local_only = g_value_get_boolean (value);
1161       break;
1162     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
1163       set_preview_widget (impl, g_value_get_object (value));
1164       break;
1165     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
1166       impl->preview_widget_active = g_value_get_boolean (value);
1167       update_preview_widget_visibility (impl);
1168       break;
1169     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
1170       set_extra_widget (impl, g_value_get_object (value));
1171       break;
1172     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
1173       {
1174         gboolean select_multiple = g_value_get_boolean (value);
1175         if (select_multiple != impl->select_multiple)
1176           {
1177             GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1178
1179             impl->select_multiple = select_multiple;
1180             gtk_tree_selection_set_mode (selection,
1181                                          (select_multiple ?
1182                                           GTK_SELECTION_MULTIPLE : GTK_SELECTION_BROWSE));
1183             /* FIXME: See note in check_preview_change() */
1184             check_preview_change (impl);
1185           }
1186       }
1187       break;
1188     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
1189       {
1190         gboolean show_hidden = g_value_get_boolean (value);
1191         if (show_hidden != impl->show_hidden)
1192           {
1193             impl->show_hidden = show_hidden;
1194             _gtk_file_system_model_set_show_hidden (impl->tree_model, show_hidden);
1195             _gtk_file_system_model_set_show_hidden (impl->list_model, show_hidden);
1196           }
1197       }
1198       break;
1199     default:
1200       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1201       break;
1202     }
1203 }
1204
1205 static void
1206 gtk_file_chooser_impl_default_get_property (GObject         *object,
1207                                             guint            prop_id,
1208                                             GValue          *value,
1209                                             GParamSpec      *pspec)
1210 {
1211   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (object);
1212
1213   switch (prop_id)
1214     {
1215     case GTK_FILE_CHOOSER_PROP_ACTION:
1216       g_value_set_enum (value, impl->action);
1217       break;
1218     case GTK_FILE_CHOOSER_PROP_FILTER:
1219       g_value_set_object (value, impl->current_filter);
1220       break;
1221     case GTK_FILE_CHOOSER_PROP_FOLDER_MODE:
1222       g_value_set_boolean (value, impl->folder_mode);
1223       break;
1224     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
1225       g_value_set_boolean (value, impl->local_only);
1226       break;
1227     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
1228       g_value_set_object (value, impl->preview_widget);
1229       break;
1230     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
1231       g_value_set_boolean (value, impl->preview_widget_active);
1232       break;
1233     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
1234       g_value_set_object (value, impl->extra_widget);
1235       break;
1236     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
1237       g_value_set_boolean (value, impl->select_multiple);
1238       break;
1239     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
1240       g_value_set_boolean (value, impl->show_hidden);
1241       break;
1242     default:
1243       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1244       break;
1245     }
1246 }
1247
1248 /* We override show-all since we have internal widgets that
1249  * shouldn't be shown when you call show_all(), like the filter
1250  * option menu.
1251  */
1252 static void
1253 gtk_file_chooser_impl_default_show_all (GtkWidget *widget)
1254 {
1255   gtk_widget_show (widget);
1256 }
1257
1258 static void
1259 expand_and_select_func (GtkFileSystemModel *model,
1260                         GtkTreePath        *path,
1261                         GtkTreeIter        *iter,
1262                         gpointer            user_data)
1263 {
1264   GtkFileChooserImplDefault *impl = user_data;
1265   GtkTreeView *tree_view;
1266
1267   if (model == impl->tree_model)
1268     tree_view = GTK_TREE_VIEW (impl->tree);
1269   else
1270     tree_view = GTK_TREE_VIEW (impl->list);
1271
1272   gtk_tree_view_expand_to_path (tree_view, path);
1273   gtk_tree_view_expand_row (tree_view, path, FALSE);
1274   gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1275   gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->tree), path, NULL, TRUE, 0.3, 0.5);
1276 }
1277
1278 static gboolean
1279 list_model_filter_func (GtkFileSystemModel *model,
1280                         GtkFilePath        *path,
1281                         const GtkFileInfo  *file_info,
1282                         gpointer            user_data)
1283 {
1284   GtkFileChooserImplDefault *impl = user_data;
1285   GtkFileFilterInfo filter_info;
1286   GtkFileFilterFlags needed;
1287   gboolean result;
1288
1289   if (!impl->current_filter)
1290     return TRUE;
1291
1292   filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE;
1293
1294   needed = gtk_file_filter_get_needed (impl->current_filter);
1295
1296   filter_info.display_name = gtk_file_info_get_display_name (file_info);
1297   filter_info.mime_type = gtk_file_info_get_mime_type (file_info);
1298
1299   if (needed & GTK_FILE_FILTER_FILENAME)
1300     {
1301       filter_info.filename = gtk_file_system_path_to_filename (impl->file_system, path);
1302       if (filter_info.filename)
1303         filter_info.contains |= GTK_FILE_FILTER_FILENAME;
1304     }
1305   else
1306     filter_info.filename = NULL;
1307
1308   if (needed & GTK_FILE_FILTER_URI)
1309     {
1310       filter_info.uri = gtk_file_system_path_to_uri (impl->file_system, path);
1311       if (filter_info.filename)
1312         filter_info.contains |= GTK_FILE_FILTER_URI;
1313     }
1314   else
1315     filter_info.uri = NULL;
1316
1317   result = gtk_file_filter_filter (impl->current_filter, &filter_info);
1318
1319   if (filter_info.filename)
1320     g_free ((gchar *)filter_info.filename);
1321   if (filter_info.uri)
1322     g_free ((gchar *)filter_info.uri);
1323
1324   return result;
1325 }
1326
1327 static void
1328 install_list_model_filter (GtkFileChooserImplDefault *impl)
1329 {
1330   if (impl->current_filter)
1331     _gtk_file_system_model_set_filter (impl->list_model,
1332                                        list_model_filter_func,
1333                                        impl);
1334 }
1335
1336 #define COMPARE_DIRECTORIES                                                                                     \
1337   GtkFileChooserImplDefault *impl = user_data;                                                                  \
1338   const GtkFileInfo *info_a = _gtk_file_system_model_get_info (impl->tree_model, a);                            \
1339   const GtkFileInfo *info_b = _gtk_file_system_model_get_info (impl->tree_model, b);                            \
1340   gboolean dir_a = gtk_file_info_get_is_folder (info_a);                                                        \
1341   gboolean dir_b = gtk_file_info_get_is_folder (info_b);                                                        \
1342                                                                                                                 \
1343   if (dir_a != dir_b)                                                                                           \
1344     return impl->list_sort_ascending ? (dir_a ? -1 : 1) : (dir_a ? 1 : -1) /* Directories *always* go first */
1345
1346 /* Sort callback for the filename column */
1347 static gint
1348 name_sort_func (GtkTreeModel *model,
1349                 GtkTreeIter  *a,
1350                 GtkTreeIter  *b,
1351                 gpointer      user_data)
1352 {
1353   COMPARE_DIRECTORIES;
1354   else
1355     return strcmp (gtk_file_info_get_display_key (info_a), gtk_file_info_get_display_key (info_b));
1356 }
1357
1358 /* Sort callback for the size column */
1359 static gint
1360 size_sort_func (GtkTreeModel *model,
1361                 GtkTreeIter  *a,
1362                 GtkTreeIter  *b,
1363                 gpointer      user_data)
1364 {
1365   COMPARE_DIRECTORIES;
1366   else
1367     {
1368       gint64 size_a = gtk_file_info_get_size (info_a);
1369       gint64 size_b = gtk_file_info_get_size (info_b);
1370
1371       return size_a > size_b ? -1 : (size_a == size_b ? 0 : 1);
1372     }
1373 }
1374
1375 /* Sort callback for the mtime column */
1376 static gint
1377 mtime_sort_func (GtkTreeModel *model,
1378                  GtkTreeIter  *a,
1379                  GtkTreeIter  *b,
1380                  gpointer      user_data)
1381 {
1382   COMPARE_DIRECTORIES;
1383   else
1384     {
1385       GtkFileTime ta = gtk_file_info_get_modification_time (info_a);
1386       GtkFileTime tb = gtk_file_info_get_modification_time (info_b);
1387
1388       return ta > tb ? -1 : (ta == tb ? 0 : 1);
1389     }
1390 }
1391
1392 /* Callback used when the sort column changes.  We cache the sort order for use
1393  * in name_sort_func().
1394  */
1395 static void
1396 list_sort_column_changed_cb (GtkTreeSortable           *sortable,
1397                              GtkFileChooserImplDefault *impl)
1398 {
1399   GtkSortType sort_type;
1400
1401   if (gtk_tree_sortable_get_sort_column_id (sortable, NULL, &sort_type))
1402     impl->list_sort_ascending = (sort_type == GTK_SORT_ASCENDING);
1403 }
1404
1405 /* Gets rid of the old list model and creates a new one for the current folder */
1406 static void
1407 set_list_model (GtkFileChooserImplDefault *impl)
1408 {
1409   if (impl->list_model)
1410     {
1411       g_object_unref (impl->list_model);
1412       impl->list_model = NULL;
1413
1414       g_object_unref (impl->sort_model);
1415       impl->sort_model = NULL;
1416     }
1417
1418   impl->list_model = _gtk_file_system_model_new (impl->file_system,
1419                                                  impl->current_folder, 0,
1420                                                  GTK_FILE_INFO_ICON |
1421                                                  GTK_FILE_INFO_DISPLAY_NAME |
1422                                                  GTK_FILE_INFO_IS_FOLDER |
1423                                                  GTK_FILE_INFO_SIZE |
1424                                                  GTK_FILE_INFO_MODIFICATION_TIME);
1425   install_list_model_filter (impl);
1426
1427   impl->sort_model = (GtkTreeModelSort *)gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (impl->list_model));
1428   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->sort_model), FILE_LIST_COL_NAME, name_sort_func, impl, NULL);
1429   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->sort_model), FILE_LIST_COL_SIZE, size_sort_func, impl, NULL);
1430   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->sort_model), FILE_LIST_COL_MTIME, mtime_sort_func, impl, NULL);
1431   gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (impl->sort_model), NULL, NULL, NULL);
1432   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->sort_model), FILE_LIST_COL_NAME, GTK_SORT_ASCENDING);
1433   impl->list_sort_ascending = TRUE;
1434
1435   g_signal_connect (impl->sort_model, "sort_column_changed",
1436                     G_CALLBACK (list_sort_column_changed_cb), impl);
1437
1438   gtk_tree_view_set_model (GTK_TREE_VIEW (impl->list),
1439                            GTK_TREE_MODEL (impl->sort_model));
1440   gtk_tree_view_columns_autosize (GTK_TREE_VIEW (impl->list));
1441   gtk_tree_view_set_search_column (GTK_TREE_VIEW (impl->list),
1442                                    GTK_FILE_SYSTEM_MODEL_DISPLAY_NAME);
1443 }
1444
1445 static void
1446 update_chooser_entry (GtkFileChooserImplDefault *impl)
1447 {
1448   GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1449   const GtkFileInfo *info;
1450   GtkTreeIter iter;
1451   GtkTreeIter child_iter;
1452
1453   /* Fixing this for multiple selection involves getting the full
1454    * selection and diffing to find out what the most recently selected
1455    * file is; there is logic in GtkFileSelection that probably can
1456    * be copied; check_preview_change() is similar.
1457    */
1458   if (impl->select_multiple ||
1459       !gtk_tree_selection_get_selected (selection, NULL, &iter))
1460     return;
1461
1462   gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
1463                                                   &child_iter,
1464                                                   &iter);
1465
1466   info = _gtk_file_system_model_get_info (impl->list_model, &child_iter);
1467
1468   _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->entry),
1469                                          gtk_file_info_get_display_name (info));
1470 }
1471
1472 static void
1473 gtk_file_chooser_impl_default_set_current_folder (GtkFileChooser    *chooser,
1474                                                   const GtkFilePath *path)
1475 {
1476   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1477
1478   _gtk_file_system_model_path_do (impl->tree_model, path,
1479                                   expand_and_select_func, impl);
1480 }
1481
1482 static GtkFilePath *
1483 gtk_file_chooser_impl_default_get_current_folder (GtkFileChooser *chooser)
1484 {
1485   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1486
1487   return gtk_file_path_copy (impl->current_folder);
1488 }
1489
1490 static void
1491 gtk_file_chooser_impl_default_set_current_name (GtkFileChooser *chooser,
1492                                                 const gchar    *name)
1493 {
1494   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1495
1496   _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->entry), name);
1497 }
1498
1499 static void
1500 select_func (GtkFileSystemModel *model,
1501              GtkTreePath        *path,
1502              GtkTreeIter        *iter,
1503              gpointer            user_data)
1504 {
1505   GtkFileChooserImplDefault *impl = user_data;
1506   GtkTreeView *tree_view = GTK_TREE_VIEW (impl->list);
1507   GtkTreePath *sorted_path;
1508
1509   sorted_path = gtk_tree_model_sort_convert_child_path_to_path (impl->sort_model, path);
1510   gtk_tree_view_set_cursor (tree_view, sorted_path, NULL, FALSE);
1511   gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->tree), sorted_path, NULL, TRUE, 0.3, 0.0);
1512   gtk_tree_path_free (sorted_path);
1513 }
1514
1515 static void
1516 gtk_file_chooser_impl_default_select_path (GtkFileChooser    *chooser,
1517                                            const GtkFilePath *path)
1518 {
1519   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1520   GtkFilePath *parent_path;
1521
1522   if (!gtk_file_system_get_parent (impl->file_system, path, &parent_path, NULL))        /* NULL-GError */
1523     return;
1524
1525   if (!parent_path)
1526     {
1527       _gtk_file_chooser_set_current_folder_path (chooser, path);
1528     }
1529   else
1530     {
1531       _gtk_file_chooser_set_current_folder_path (chooser, parent_path);
1532       gtk_file_path_free (parent_path);
1533       _gtk_file_system_model_path_do (impl->list_model, path,
1534                                       select_func, impl);
1535     }
1536 }
1537
1538 static void
1539 unselect_func (GtkFileSystemModel *model,
1540                GtkTreePath        *path,
1541                GtkTreeIter        *iter,
1542                gpointer            user_data)
1543 {
1544   GtkFileChooserImplDefault *impl = user_data;
1545   GtkTreeView *tree_view = GTK_TREE_VIEW (impl->list);
1546   GtkTreePath *sorted_path;
1547
1548   sorted_path = gtk_tree_model_sort_convert_child_path_to_path (impl->sort_model,
1549                                                                 path);
1550   gtk_tree_selection_unselect_path (gtk_tree_view_get_selection (tree_view),
1551                                     sorted_path);
1552   gtk_tree_path_free (sorted_path);
1553 }
1554
1555 static void
1556 gtk_file_chooser_impl_default_unselect_path (GtkFileChooser    *chooser,
1557                                              const GtkFilePath *path)
1558 {
1559   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1560
1561   _gtk_file_system_model_path_do (impl->list_model, path,
1562                                  unselect_func, impl);
1563 }
1564
1565 static void
1566 gtk_file_chooser_impl_default_select_all (GtkFileChooser *chooser)
1567 {
1568   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1569   if (impl->select_multiple)
1570     {
1571       GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1572       gtk_tree_selection_select_all (selection);
1573     }
1574 }
1575
1576 static void
1577 gtk_file_chooser_impl_default_unselect_all (GtkFileChooser *chooser)
1578 {
1579   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1580   GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1581
1582   gtk_tree_selection_unselect_all (selection);
1583 }
1584
1585 static void
1586 get_paths_foreach (GtkTreeModel *model,
1587                   GtkTreePath   *path,
1588                   GtkTreeIter   *iter,
1589                   gpointer       data)
1590 {
1591   GtkTreePath *child_path;
1592   GtkTreeIter child_iter;
1593   const GtkFilePath *file_path;
1594
1595   struct {
1596     GSList *result;
1597     GtkFileChooserImplDefault *impl;
1598   } *info = data;
1599
1600   child_path = gtk_tree_model_sort_convert_path_to_child_path (info->impl->sort_model, path);
1601   gtk_tree_model_get_iter (GTK_TREE_MODEL (info->impl->list_model), &child_iter, child_path);
1602   gtk_tree_path_free (child_path);
1603
1604   file_path = _gtk_file_system_model_get_path (info->impl->list_model, &child_iter);
1605   info->result = g_slist_prepend (info->result, gtk_file_path_copy (file_path));
1606 }
1607
1608 static GSList *
1609 gtk_file_chooser_impl_default_get_paths (GtkFileChooser *chooser)
1610 {
1611   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1612   GtkTreeSelection *selection;
1613
1614   struct {
1615     GSList *result;
1616     GtkFileChooserImplDefault *impl;
1617   } info = { NULL, };
1618
1619   if (!impl->sort_model)
1620     return NULL;
1621
1622   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1623
1624   info.impl = impl;
1625   gtk_tree_selection_selected_foreach (selection,
1626                                        get_paths_foreach, &info);
1627   return g_slist_reverse (info.result);
1628 }
1629
1630 static GtkFilePath *
1631 gtk_file_chooser_impl_default_get_preview_path (GtkFileChooser *chooser)
1632 {
1633   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1634
1635   if (impl->preview_path)
1636     return gtk_file_path_copy (impl->preview_path);
1637   else
1638     return NULL;
1639 }
1640
1641 static GtkFileSystem *
1642 gtk_file_chooser_impl_default_get_file_system (GtkFileChooser *chooser)
1643 {
1644   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1645
1646   return impl->file_system;
1647 }
1648
1649 static GtkWidget *
1650 find_filter_menu_item (GtkFileChooserImplDefault *impl,
1651                        GtkFileFilter             *filter,
1652                        gint                      *index_return)
1653 {
1654   GtkWidget *menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu));
1655   GList *children = gtk_container_get_children (GTK_CONTAINER (menu));
1656   GList *tmp_list;
1657   int index = 0;
1658
1659   if (index_return)
1660     *index_return = -1;
1661
1662   for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
1663     {
1664       if (g_object_get_data (tmp_list->data, "gtk-file-filter") == filter)
1665         {
1666           if (index_return)
1667             *index_return = index;
1668           return tmp_list->data;
1669         }
1670       index++;
1671     }
1672
1673   g_list_free (children);
1674
1675   return NULL;
1676 }
1677
1678 static void
1679 gtk_file_chooser_impl_default_add_filter (GtkFileChooser *chooser,
1680                                           GtkFileFilter  *filter)
1681 {
1682   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1683   GtkWidget *menu;
1684   GtkWidget *menu_item;
1685   const gchar *name;
1686
1687   if (g_slist_find (impl->filters, filter))
1688     {
1689       g_warning ("gtk_file_chooser_add_filter() called on filter already in list\n");
1690       return;
1691     }
1692
1693   g_object_ref (filter);
1694   gtk_object_sink (GTK_OBJECT (filter));
1695   impl->filters = g_slist_append (impl->filters, filter);
1696
1697   name = gtk_file_filter_get_name (filter);
1698   if (!name)
1699     name = "Untitled filter";   /* Place-holder, doesn't need to be marked for translation */
1700
1701   menu_item = gtk_menu_item_new_with_label (name);
1702   g_object_set_data (G_OBJECT (menu_item), "gtk-file-filter", filter);
1703   gtk_widget_show (menu_item);
1704
1705   menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu));
1706   gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1707   /* Option menus don't react to menu size changes properly */
1708   gtk_widget_size_request (menu, NULL);
1709
1710   if (!g_slist_find (impl->filters, impl->current_filter))
1711     set_current_filter (impl, filter);
1712
1713   gtk_widget_show (impl->filter_alignment);
1714 }
1715
1716 static void
1717 gtk_file_chooser_impl_default_remove_filter (GtkFileChooser    *chooser,
1718                                              GtkFileFilter     *filter)
1719 {
1720   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1721   GtkWidget *menu;
1722   GtkWidget *menu_item;
1723
1724   if (!g_slist_find (impl->filters, filter))
1725     {
1726       g_warning ("gtk_file_chooser_remove_filter() called on filter not in list\n");
1727       return;
1728     }
1729
1730   impl->filters = g_slist_remove (impl->filters, filter);
1731
1732   if (filter == impl->current_filter)
1733     {
1734       if (impl->filters)
1735         set_current_filter (impl, impl->filters->data);
1736       else
1737         set_current_filter (impl, NULL);
1738     }
1739
1740   menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu));
1741   menu_item = find_filter_menu_item (impl, filter, NULL);
1742   g_assert (menu_item);
1743   gtk_widget_destroy (menu_item);
1744   /* Option menus don't react to menu size changes properly */
1745   gtk_widget_size_request (menu, NULL);
1746
1747   g_object_unref (filter);
1748
1749   if (!impl->filters)
1750     gtk_widget_hide (impl->filter_alignment);
1751 }
1752
1753 static GSList *
1754 gtk_file_chooser_impl_default_list_filters (GtkFileChooser *chooser)
1755 {
1756   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1757
1758   return g_slist_copy (impl->filters);
1759 }
1760
1761 /* Returns the position in the shortcuts tree where the nth specified shortcut would appear */
1762 static int
1763 shortcuts_get_pos_for_shortcut_folder (GtkFileChooserImplDefault *impl,
1764                                        int                        pos)
1765 {
1766   return pos + ((impl->has_home ? 1 : 0)
1767                 + (impl->has_desktop ? 1 : 0)
1768                 + impl->num_roots);
1769 }
1770
1771 static gboolean
1772 gtk_file_chooser_impl_default_add_shortcut_folder (GtkFileChooser    *chooser,
1773                                                    const GtkFilePath *path,
1774                                                    GError           **error)
1775 {
1776   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1777   gboolean result;
1778   int pos;
1779
1780   pos = shortcuts_get_pos_for_shortcut_folder (impl, impl->num_shortcuts);
1781
1782   /* FIXME: how do we know if the path is a file system root? */
1783   result = shortcuts_insert_path (impl, pos, path, FALSE, NULL, error);
1784
1785   if (result)
1786     impl->num_shortcuts++;
1787
1788   return result;
1789 }
1790
1791 static gboolean
1792 gtk_file_chooser_impl_default_remove_shortcut_folder (GtkFileChooser    *chooser,
1793                                                       const GtkFilePath *path,
1794                                                       GError           **error)
1795 {
1796   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1797   int pos;
1798   GtkTreeIter iter;
1799   int i;
1800
1801   if (impl->num_shortcuts == 0)
1802     goto out;
1803
1804   pos = shortcuts_get_pos_for_shortcut_folder (impl, 0);
1805   if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (impl->shortcuts_model), &iter, NULL, pos))
1806     g_assert_not_reached ();
1807
1808   for (i = 0; i < impl->num_shortcuts; i++)
1809     {
1810       GtkFilePath *shortcut;
1811
1812       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_PATH, &shortcut, -1);
1813       g_assert (shortcut != NULL);
1814
1815       if (gtk_file_path_compare (shortcut, path) == 0)
1816         {
1817           /* The other columns are freed by the GtkTreeStore */
1818           gtk_file_path_free (shortcut);
1819           gtk_tree_store_remove (impl->shortcuts_model, &iter);
1820           impl->num_shortcuts--;
1821           return TRUE;
1822         }
1823
1824       if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter))
1825         g_assert_not_reached ();
1826     }
1827
1828  out:
1829
1830   g_set_error (error,
1831                GTK_FILE_CHOOSER_ERROR,
1832                GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
1833                "shortcut %s does not exist",
1834                gtk_file_path_get_string (path));
1835
1836   return FALSE;
1837 }
1838
1839 static GSList *
1840 gtk_file_chooser_impl_default_list_shortcut_folders (GtkFileChooser *chooser)
1841 {
1842   GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser);
1843   int pos;
1844   GtkTreeIter iter;
1845   int i;
1846   GSList *list;
1847
1848   pos = shortcuts_get_pos_for_shortcut_folder (impl, 0);
1849   if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (impl->shortcuts_model), &iter, NULL, pos))
1850     g_assert_not_reached ();
1851
1852   list = NULL;
1853
1854   for (i = 0; i < impl->num_shortcuts; i++)
1855     {
1856       GtkFilePath *shortcut;
1857
1858       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_PATH, &shortcut, -1);
1859       g_assert (shortcut != NULL);
1860
1861       list = g_slist_prepend (list, gtk_file_path_copy (shortcut));
1862
1863       if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter))
1864         g_assert_not_reached ();
1865     }
1866
1867   return g_slist_reverse (list);
1868 }
1869
1870 static void
1871 set_current_filter (GtkFileChooserImplDefault *impl,
1872                     GtkFileFilter             *filter)
1873 {
1874   if (impl->current_filter != filter)
1875     {
1876       int menu_item_index;
1877
1878       /* If we have filters, new filter must be one of them
1879        */
1880       find_filter_menu_item (impl, filter, &menu_item_index);
1881       if (impl->filters && menu_item_index < 0)
1882         return;
1883
1884       if (impl->current_filter)
1885         g_object_unref (impl->current_filter);
1886       impl->current_filter = filter;
1887       if (impl->current_filter)
1888         {
1889           g_object_ref (impl->current_filter);
1890           gtk_object_sink (GTK_OBJECT (filter));
1891         }
1892
1893       if (impl->filters)
1894         gtk_option_menu_set_history (GTK_OPTION_MENU (impl->filter_option_menu),
1895                                      menu_item_index);
1896
1897       install_list_model_filter (impl);
1898
1899       g_object_notify (G_OBJECT (impl), "filter");
1900     }
1901 }
1902
1903 static void
1904 open_and_close (GtkTreeView *tree_view,
1905                 GtkTreePath *target_path)
1906 {
1907   GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
1908   GtkTreeIter iter;
1909   GtkTreePath *path;
1910
1911   path = gtk_tree_path_new ();
1912   gtk_tree_path_append_index (path, 0);
1913
1914   gtk_tree_model_get_iter (model, &iter, path);
1915
1916   while (TRUE)
1917     {
1918       if (gtk_tree_path_is_ancestor (path, target_path) ||
1919           gtk_tree_path_compare (path, target_path) == 0)
1920         {
1921           GtkTreeIter child_iter;
1922           gtk_tree_view_expand_row (tree_view, path, FALSE);
1923           if (gtk_tree_model_iter_children (model, &child_iter, &iter))
1924             {
1925               iter = child_iter;
1926               gtk_tree_path_down (path);
1927               goto next;
1928             }
1929         }
1930       else
1931         gtk_tree_view_collapse_row (tree_view, path);
1932
1933       while (TRUE)
1934         {
1935           GtkTreeIter parent_iter;
1936           GtkTreeIter next_iter;
1937
1938           next_iter = iter;
1939           if (gtk_tree_model_iter_next (model, &next_iter))
1940             {
1941               iter = next_iter;
1942               gtk_tree_path_next (path);
1943               goto next;
1944             }
1945
1946           if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter))
1947             goto out;
1948
1949           iter = parent_iter;
1950           gtk_tree_path_up (path);
1951         }
1952     next:
1953       ;
1954     }
1955
1956  out:
1957   gtk_tree_path_free (path);
1958 }
1959
1960 static void
1961 filter_option_menu_changed (GtkOptionMenu             *option_menu,
1962                             GtkFileChooserImplDefault *impl)
1963 {
1964   gint new_index = gtk_option_menu_get_history (GTK_OPTION_MENU (option_menu));
1965   GtkFileFilter *new_filter = g_slist_nth_data (impl->filters, new_index);
1966
1967   set_current_filter (impl, new_filter);
1968 }
1969
1970 static void
1971 check_preview_change (GtkFileChooserImplDefault *impl)
1972 {
1973   const GtkFilePath *new_path = NULL;
1974
1975   /* Fixing preview for multiple selection involves getting the full
1976    * selection and diffing to find out what the most recently selected
1977    * file is; there is logic in GtkFileSelection that probably can
1978    * be copied. update_chooser_entry() is similar.
1979    */
1980   if (impl->sort_model && !impl->select_multiple)
1981     {
1982       GtkTreeSelection *selection;
1983       GtkTreeIter iter;
1984
1985       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->list));
1986       if (gtk_tree_selection_get_selected  (selection, NULL, &iter))
1987         {
1988           GtkTreeIter child_iter;
1989
1990           gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
1991                                                           &child_iter, &iter);
1992
1993           new_path = _gtk_file_system_model_get_path (impl->list_model, &child_iter);
1994         }
1995     }
1996
1997   if (new_path != impl->preview_path &&
1998       !(new_path && impl->preview_path &&
1999         gtk_file_path_compare (new_path, impl->preview_path) == 0))
2000     {
2001       if (impl->preview_path)
2002         gtk_file_path_free (impl->preview_path);
2003
2004       if (new_path)
2005         impl->preview_path = gtk_file_path_copy (new_path);
2006       else
2007         impl->preview_path = NULL;
2008
2009       g_signal_emit_by_name (impl, "update-preview");
2010     }
2011 }
2012
2013 static void
2014 tree_selection_changed (GtkTreeSelection          *selection,
2015                         GtkFileChooserImplDefault *impl)
2016 {
2017   GtkTreeIter iter;
2018   const GtkFilePath *file_path;
2019   GtkTreePath *path;
2020
2021   if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
2022     return;
2023
2024   file_path = _gtk_file_system_model_get_path (impl->tree_model, &iter);
2025   if (impl->current_folder && gtk_file_path_compare (file_path, impl->current_folder) == 0)
2026     return;
2027
2028   if (impl->current_folder)
2029     gtk_file_path_free (impl->current_folder);
2030   impl->current_folder = gtk_file_path_copy (file_path);
2031   _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (impl->entry), file_path);
2032
2033   /* Close the tree up to only the parents of the newly selected
2034    * node and it's immediate children are visible.
2035    */
2036   path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->tree_model), &iter);
2037   open_and_close (GTK_TREE_VIEW (impl->tree), path);
2038   gtk_tree_path_free (path);
2039
2040   /* Create the new list model */
2041   set_list_model (impl);
2042
2043   shortcuts_select_folder (impl);
2044
2045   g_signal_emit_by_name (impl, "current-folder-changed", 0);
2046
2047   update_chooser_entry (impl);
2048   check_preview_change (impl);
2049   bookmarks_check_add_sensitivity (impl);
2050
2051   g_signal_emit_by_name (impl, "selection-changed", 0);
2052 }
2053
2054 /* Callback used when the selection in the shortcuts list changes */
2055 static void
2056 shortcuts_selection_changed (GtkTreeSelection          *selection,
2057                              GtkFileChooserImplDefault *impl)
2058 {
2059   GtkTreeIter iter;
2060   GtkFilePath *path;
2061
2062   if (impl->changing_folder)
2063     return;
2064
2065   bookmarks_check_remove_sensitivity (impl);
2066
2067   /* Set the current folder */
2068
2069   if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
2070     return;
2071
2072   gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_PATH, &path, -1);
2073
2074   if (!path)
2075     {
2076       /* We are on the bookmarks separator node, so unselect it */
2077       shortcuts_select_folder (impl);
2078       /* FIXME: how to make this row unselectable? */
2079       return;
2080     }
2081
2082   impl->changing_folder = TRUE;
2083   _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (impl), path);
2084   impl->changing_folder = FALSE;
2085 }
2086
2087 static void
2088 list_selection_changed (GtkTreeSelection          *selection,
2089                         GtkFileChooserImplDefault *impl)
2090 {
2091   update_chooser_entry (impl);
2092   check_preview_change (impl);
2093
2094   g_signal_emit_by_name (impl, "selection-changed", 0);
2095 }
2096
2097 /* Callback used when a row in the file list is activated */
2098 static void
2099 list_row_activated (GtkTreeView               *tree_view,
2100                     GtkTreePath               *path,
2101                     GtkTreeViewColumn         *column,
2102                     GtkFileChooserImplDefault *impl)
2103 {
2104   GtkTreeIter iter, child_iter;
2105   const GtkFileInfo *info;
2106
2107   if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, path))
2108     return;
2109
2110   gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter);
2111
2112   info = _gtk_file_system_model_get_info (impl->list_model, &child_iter);
2113
2114   if (gtk_file_info_get_is_folder (info))
2115     {
2116       const GtkFilePath *file_path;
2117
2118       file_path = _gtk_file_system_model_get_path (impl->list_model, &child_iter);
2119       _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (impl), file_path);
2120
2121       return;
2122     }
2123
2124   g_signal_emit_by_name (impl, "file-activated");
2125 }
2126
2127 static void
2128 entry_activate (GtkEntry                  *entry,
2129                 GtkFileChooserImplDefault *impl)
2130 {
2131   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
2132   const GtkFilePath *folder_path = _gtk_file_chooser_entry_get_current_folder (chooser_entry);
2133   const gchar *file_part = _gtk_file_chooser_entry_get_file_part (chooser_entry);
2134   GtkFilePath *new_folder = NULL;
2135
2136   /* If the file part is non-empty, we need to figure out if it
2137    * refers to a folder within folder. We could optimize the case
2138    * here where the folder is already loaded for one of our tree models.
2139    */
2140   if (file_part[0] == '\0' && gtk_file_path_compare (impl->current_folder, folder_path) != 0)
2141     new_folder = gtk_file_path_copy (folder_path);
2142   else
2143     {
2144       GtkFileFolder *folder = NULL;
2145       GtkFilePath *subfolder_path = NULL;
2146       GtkFileInfo *info = NULL;
2147
2148       folder = gtk_file_system_get_folder (impl->file_system,
2149                                            folder_path,
2150                                            GTK_FILE_INFO_IS_FOLDER,
2151                                            NULL);       /* NULL-GError */
2152
2153       if (folder)
2154         subfolder_path = gtk_file_system_make_path (impl->file_system,
2155                                                   folder_path,
2156                                                   file_part,
2157                                                   NULL); /* NULL-GError */
2158
2159       if (subfolder_path)
2160         info = gtk_file_folder_get_info (folder,
2161                                          subfolder_path,
2162                                          NULL); /* NULL-GError */
2163
2164       if (info && gtk_file_info_get_is_folder (info))
2165         new_folder = gtk_file_path_copy (subfolder_path);
2166
2167       if (folder)
2168         g_object_unref (folder);
2169
2170       if (subfolder_path)
2171         gtk_file_path_free (subfolder_path);
2172
2173       if (info)
2174         gtk_file_info_free (info);
2175     }
2176
2177   if (new_folder)
2178     {
2179       g_signal_stop_emission_by_name (entry, "activate");
2180
2181       _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (impl), new_folder);
2182       _gtk_file_chooser_entry_set_file_part (chooser_entry, "");
2183
2184       gtk_file_path_free (new_folder);
2185     }
2186 }
2187
2188 static const GtkFileInfo *
2189 get_list_file_info (GtkFileChooserImplDefault *impl,
2190                     GtkTreeIter               *iter)
2191 {
2192   GtkTreeIter child_iter;
2193
2194   gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
2195                                                   &child_iter,
2196                                                   iter);
2197
2198   return _gtk_file_system_model_get_info (impl->tree_model, &child_iter);
2199 }
2200
2201 static void
2202 tree_name_data_func (GtkTreeViewColumn *tree_column,
2203                      GtkCellRenderer   *cell,
2204                      GtkTreeModel      *tree_model,
2205                      GtkTreeIter       *iter,
2206                      gpointer           data)
2207 {
2208   GtkFileChooserImplDefault *impl = data;
2209   const GtkFileInfo *info = _gtk_file_system_model_get_info (impl->tree_model, iter);
2210
2211   if (info)
2212     {
2213       g_object_set (cell,
2214                     "text", gtk_file_info_get_display_name (info),
2215                     NULL);
2216     }
2217 }
2218
2219 static void
2220 list_icon_data_func (GtkTreeViewColumn *tree_column,
2221                      GtkCellRenderer   *cell,
2222                      GtkTreeModel      *tree_model,
2223                      GtkTreeIter       *iter,
2224                      gpointer           data)
2225 {
2226   GtkFileChooserImplDefault *impl = data;
2227   const GtkFileInfo *info = get_list_file_info (impl, iter);
2228
2229   if (info)
2230     {
2231       GtkWidget *widget = GTK_TREE_VIEW_COLUMN (tree_column)->tree_view;
2232       GdkPixbuf *pixbuf = gtk_file_info_render_icon (info, widget, ICON_SIZE);
2233
2234       g_object_set (cell,
2235                     "pixbuf", pixbuf,
2236                     NULL);
2237
2238       if (pixbuf)
2239         g_object_unref (pixbuf);
2240     }
2241 }
2242
2243 /* Sets a cellrenderer's text, making it bold if the GtkFileInfo is a folder */
2244 static void
2245 set_cell_text_bold_if_folder (const GtkFileInfo *info, GtkCellRenderer *cell, const char *text)
2246 {
2247   g_object_set (cell,
2248                 "text", text,
2249                 "weight", gtk_file_info_get_is_folder (info) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
2250                 NULL);
2251 }
2252
2253 static void
2254 list_name_data_func (GtkTreeViewColumn *tree_column,
2255                      GtkCellRenderer   *cell,
2256                      GtkTreeModel      *tree_model,
2257                      GtkTreeIter       *iter,
2258                      gpointer           data)
2259 {
2260   GtkFileChooserImplDefault *impl = data;
2261   const GtkFileInfo *info = get_list_file_info (impl, iter);
2262
2263   if (!info)
2264     return;
2265
2266   set_cell_text_bold_if_folder (info, cell, gtk_file_info_get_display_name (info));
2267 }
2268
2269 #if 0
2270 static void
2271 list_size_data_func (GtkTreeViewColumn *tree_column,
2272                      GtkCellRenderer   *cell,
2273                      GtkTreeModel      *tree_model,
2274                      GtkTreeIter       *iter,
2275                      gpointer           data)
2276 {
2277   GtkFileChooserImplDefault *impl = data;
2278   const GtkFileInfo *info = get_list_file_info (impl, iter);
2279   gint64 size = gtk_file_info_get_size (info);
2280   gchar *str;
2281
2282   if (!info || gtk_file_info_get_is_folder (info))
2283     return;
2284
2285   if (size < (gint64)1024)
2286     str = g_strdup_printf ("%d bytes", (gint)size);
2287   else if (size < (gint64)1024*1024)
2288     str = g_strdup_printf ("%.1f K", size / (1024.));
2289   else if (size < (gint64)1024*1024*1024)
2290     str = g_strdup_printf ("%.1f M", size / (1024.*1024.));
2291   else
2292     str = g_strdup_printf ("%.1f G", size / (1024.*1024.*1024.));
2293
2294   g_object_set (cell,
2295                 "text", str,
2296                 NULL);
2297
2298   g_free (str);
2299 }
2300 #endif
2301
2302 /* Tree column data callback for the file list; fetches the mtime of a file */
2303 static void
2304 list_mtime_data_func (GtkTreeViewColumn *tree_column,
2305                       GtkCellRenderer   *cell,
2306                       GtkTreeModel      *tree_model,
2307                       GtkTreeIter       *iter,
2308                       gpointer           data)
2309 {
2310   GtkFileChooserImplDefault *impl;
2311   const GtkFileInfo *info;
2312   time_t mtime, now;
2313   struct tm tm, now_tm;
2314   char buf[256];
2315
2316   impl = data;
2317
2318   info = get_list_file_info (impl, iter);
2319   if (!info)
2320     return;
2321
2322   mtime = (time_t) gtk_file_info_get_modification_time (info);
2323   tm = *localtime (&mtime);
2324
2325   now = time (NULL);
2326   now_tm = *localtime (&now);
2327
2328   /* Today */
2329   if (tm.tm_mday == now_tm.tm_mday
2330       && tm.tm_mon == now_tm.tm_mon
2331       && tm.tm_year == now_tm.tm_year)
2332     strcpy (buf, "Today");
2333   else
2334     {
2335       int i;
2336
2337       /* Days from last week */
2338
2339       for (i = 1; i < 7; i++)
2340         {
2341           time_t then;
2342           struct tm then_tm;
2343
2344           then = now - i * 60 * 60 * 24;
2345           then_tm = *localtime (&then);
2346
2347           if (tm.tm_mday == then_tm.tm_mday
2348               && tm.tm_mon == then_tm.tm_mon
2349               && tm.tm_year == then_tm.tm_year)
2350             {
2351               if (i == 1)
2352                 strcpy (buf, "Yesterday");
2353               else
2354                 if (strftime (buf, sizeof (buf), "%A", &tm) == 0)
2355                   strcpy (buf, "Unknown");
2356
2357               break;
2358             }
2359         }
2360
2361       /* Any other date */
2362
2363       if (i == 7)
2364         {
2365           if (strftime (buf, sizeof (buf), "%d/%b/%Y", &tm) == 0)
2366             strcpy (buf, "Unknown");
2367         }
2368     }
2369
2370   set_cell_text_bold_if_folder (info, cell, buf);
2371 }
2372
2373 GtkWidget *
2374 _gtk_file_chooser_impl_default_new (GtkFileSystem *file_system)
2375 {
2376   return  g_object_new (GTK_TYPE_FILE_CHOOSER_IMPL_DEFAULT,
2377                         "file-system", file_system,
2378                         NULL);
2379 }