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