1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* fm-list-view.c - implementation of list view of directory.
4 
5    Copyright (C) 2000 Eazel, Inc.
6    Copyright (C) 2001, 2002 Anders Carlsson <andersca@gnu.org>
7 
8    The Mate Library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12 
13    The Mate Library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public
19    License along with the Mate Library; see the file COPYING.LIB.  If not,
20    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21    Boston, MA 02110-1301, USA.
22 
23    Authors: John Sullivan <sullivan@eazel.com>
24             Anders Carlsson <andersca@gnu.org>
25 	    David Emory Watson <dwatson@cs.ucr.edu>
26 */
27 
28 #include <config.h>
29 #include <string.h>
30 
31 #include <gdk/gdk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 #include <glib-object.h>
36 
37 #include <eel/eel-vfs-extensions.h>
38 #include <eel/eel-gdk-extensions.h>
39 #include <eel/eel-glib-extensions.h>
40 #include <eel/eel-gtk-macros.h>
41 #include <eel/eel-stock-dialogs.h>
42 
43 #include <libegg/eggtreemultidnd.h>
44 
45 #include <libcaja-extension/caja-column-provider.h>
46 #include <libcaja-private/caja-clipboard-monitor.h>
47 #include <libcaja-private/caja-column-chooser.h>
48 #include <libcaja-private/caja-column-utilities.h>
49 #include <libcaja-private/caja-debug-log.h>
50 #include <libcaja-private/caja-directory-background.h>
51 #include <libcaja-private/caja-dnd.h>
52 #include <libcaja-private/caja-file-dnd.h>
53 #include <libcaja-private/caja-file-utilities.h>
54 #include <libcaja-private/caja-ui-utilities.h>
55 #include <libcaja-private/caja-global-preferences.h>
56 #include <libcaja-private/caja-icon-dnd.h>
57 #include <libcaja-private/caja-metadata.h>
58 #include <libcaja-private/caja-module.h>
59 #include <libcaja-private/caja-tree-view-drag-dest.h>
60 #include <libcaja-private/caja-view-factory.h>
61 #include <libcaja-private/caja-clipboard.h>
62 #include <libcaja-private/caja-cell-renderer-text-ellipsized.h>
63 
64 #include "fm-list-view.h"
65 #include "fm-error-reporting.h"
66 #include "fm-list-model.h"
67 
68 struct FMListViewDetails
69 {
70     GtkTreeView *tree_view;
71     FMListModel *model;
72     GtkActionGroup *list_action_group;
73     guint list_merge_id;
74 
75     GtkTreeViewColumn   *file_name_column;
76     int file_name_column_num;
77 
78     GtkCellRendererPixbuf *pixbuf_cell;
79     GtkCellRendererText   *file_name_cell;
80     GList *cells;
81     GtkCellEditable *editable_widget;
82 
83     CajaZoomLevel zoom_level;
84 
85     CajaTreeViewDragDest *drag_dest;
86 
87     GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
88 
89     GtkTreePath *new_selection_path;   /* Path of the new selection after removing a file */
90 
91     GtkTreePath *hover_path;
92 
93     guint drag_button;
94     int drag_x;
95     int drag_y;
96 
97     gboolean drag_started;
98 
99     gboolean ignore_button_release;
100 
101     gboolean row_selected_on_button_down;
102 
103     gboolean menus_ready;
104 
105     GHashTable *columns;
106     GtkWidget *column_editor;
107 
108     char *original_name;
109 
110     CajaFile *renaming_file;
111     gboolean rename_done;
112     guint renaming_file_activate_timeout;
113 
114     gulong clipboard_handler_id;
115 
116     GQuark last_sort_attr;
117 };
118 
119 struct SelectionForeachData
120 {
121     GList *list;
122     GtkTreeSelection *selection;
123 };
124 
125 /* We wait two seconds after row is collapsed to unload the subdirectory */
126 #define COLLAPSE_TO_UNLOAD_DELAY 2
127 
128 /* Wait for the rename to end when activating a file being renamed */
129 #define WAIT_FOR_RENAME_ON_ACTIVATE 200
130 
131 static int                      click_policy_auto_value;
132 static CajaFileSortType         default_sort_order_auto_value;
133 static gboolean			default_sort_reversed_auto_value;
134 static CajaZoomLevel        default_zoom_level_auto_value;
135 static char **                  default_visible_columns_auto_value;
136 static char **                  default_column_order_auto_value;
137 static GdkCursor *              hand_cursor = NULL;
138 
139 static GtkTargetList *          source_target_list = NULL;
140 
141 static GList *fm_list_view_get_selection                   (FMDirectoryView   *view);
142 static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView   *view);
143 static void   fm_list_view_set_zoom_level                  (FMListView        *view,
144         CajaZoomLevel  new_level,
145         gboolean           always_set_level);
146 static void   fm_list_view_scale_font_size                 (FMListView        *view,
147         CajaZoomLevel  new_level);
148 static void   fm_list_view_scroll_to_file                  (FMListView        *view,
149         CajaFile      *file);
150 static void   fm_list_view_iface_init                      (CajaViewIface *iface);
151 static void   fm_list_view_rename_callback                 (CajaFile      *file,
152         GFile             *result_location,
153         GError            *error,
154         gpointer           callback_data);
155 
156 
157 G_DEFINE_TYPE_WITH_CODE (FMListView, fm_list_view, FM_TYPE_DIRECTORY_VIEW,
158                          G_IMPLEMENT_INTERFACE (CAJA_TYPE_VIEW,
159                                  fm_list_view_iface_init));
160 
161 static const char * default_trash_visible_columns[] =
162 {
163     "name", "size", "type", "trashed_on", "trash_orig_path", NULL
164 };
165 
166 static const char * default_trash_columns_order[] =
167 {
168     "name", "size", "type", "trashed_on", "trash_orig_path", NULL
169 };
170 
171 /* for EEL_CALL_PARENT */
172 #define parent_class fm_list_view_parent_class
173 
174 static const gchar*
get_default_sort_order(CajaFile * file,gboolean * reversed)175 get_default_sort_order (CajaFile *file, gboolean *reversed)
176 {
177     const gchar *retval;
178     const char *attributes[] = {
179         "name", /* is really "manually" which doesn't apply to lists */
180         "name",
181         "uri",
182         "size",
183         "size_on_disk",
184         "type",
185         "date_modified",
186         "date_accessed",
187         "emblems",
188         "trashed_on",
189         NULL
190     };
191 
192     retval = caja_file_get_default_sort_attribute (file, reversed);
193 
194     if (retval == NULL)
195     {
196         retval = attributes[default_sort_order_auto_value];
197         *reversed = default_sort_reversed_auto_value;
198     }
199 
200     return retval;
201 }
202 
203 static void
list_selection_changed_callback(GtkTreeSelection * selection,gpointer user_data)204 list_selection_changed_callback (GtkTreeSelection *selection, gpointer user_data)
205 {
206     FMDirectoryView *view;
207 
208     view = FM_DIRECTORY_VIEW (user_data);
209 
210     fm_directory_view_notify_selection_changed (view);
211 }
212 
213 /* Move these to eel? */
214 
215 static void
tree_selection_foreach_set_boolean(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer callback_data)216 tree_selection_foreach_set_boolean (GtkTreeModel *model,
217                                     GtkTreePath *path,
218                                     GtkTreeIter *iter,
219                                     gpointer callback_data)
220 {
221     * (gboolean *) callback_data = TRUE;
222 }
223 
224 static gboolean
tree_selection_not_empty(GtkTreeSelection * selection)225 tree_selection_not_empty (GtkTreeSelection *selection)
226 {
227     gboolean not_empty;
228 
229     not_empty = FALSE;
230     gtk_tree_selection_selected_foreach (selection,
231                                          tree_selection_foreach_set_boolean,
232                                          &not_empty);
233     return not_empty;
234 }
235 
236 static gboolean
tree_view_has_selection(GtkTreeView * view)237 tree_view_has_selection (GtkTreeView *view)
238 {
239     return tree_selection_not_empty (gtk_tree_view_get_selection (view));
240 }
241 
242 static void
activate_selected_items(FMListView * view)243 activate_selected_items (FMListView *view)
244 {
245     GList *file_list;
246 
247     file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view));
248 
249 
250     if (view->details->renaming_file)
251     {
252         /* We're currently renaming a file, wait until the rename is
253            finished, or the activation uri will be wrong */
254         if (view->details->renaming_file_activate_timeout == 0)
255         {
256             view->details->renaming_file_activate_timeout =
257                 g_timeout_add (WAIT_FOR_RENAME_ON_ACTIVATE, (GSourceFunc) activate_selected_items, view);
258         }
259         return;
260     }
261 
262     if (view->details->renaming_file_activate_timeout != 0)
263     {
264         g_source_remove (view->details->renaming_file_activate_timeout);
265         view->details->renaming_file_activate_timeout = 0;
266     }
267 
268     fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view),
269                                       file_list,
270                                       CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
271                                       0,
272                                       TRUE);
273     caja_file_list_free (file_list);
274 
275 }
276 
277 static void
activate_selected_items_alternate(FMListView * view,CajaFile * file,gboolean open_in_tab)278 activate_selected_items_alternate (FMListView *view,
279                                    CajaFile *file,
280                                    gboolean open_in_tab)
281 {
282     GList *file_list;
283     CajaWindowOpenFlags flags;
284 
285     flags = CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
286 
287     if (open_in_tab)
288     {
289         flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB;
290     }
291     else
292     {
293         flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW;
294     }
295 
296     if (file != NULL)
297     {
298         caja_file_ref (file);
299         file_list = g_list_prepend (NULL, file);
300     }
301     else
302     {
303         file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view));
304     }
305     fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view),
306                                       file_list,
307                                       CAJA_WINDOW_OPEN_ACCORDING_TO_MODE,
308                                       flags,
309                                       TRUE);
310     caja_file_list_free (file_list);
311 
312 }
313 
314 static gboolean
button_event_modifies_selection(GdkEventButton * event)315 button_event_modifies_selection (GdkEventButton *event)
316 {
317     return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
318 }
319 
320 static void
fm_list_view_did_not_drag(FMListView * view,GdkEventButton * event)321 fm_list_view_did_not_drag (FMListView *view,
322                            GdkEventButton *event)
323 {
324     GtkTreeView *tree_view;
325     GtkTreeSelection *selection;
326     GtkTreePath *path;
327 
328     tree_view = view->details->tree_view;
329     selection = gtk_tree_view_get_selection (tree_view);
330 
331     if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
332                                        &path, NULL, NULL, NULL))
333     {
334         if ((event->button == 1 || event->button == 2)
335                 && ((event->state & GDK_CONTROL_MASK) != 0 ||
336                     (event->state & GDK_SHIFT_MASK) == 0)
337                 && view->details->row_selected_on_button_down)
338         {
339             if (!button_event_modifies_selection (event))
340             {
341                 gtk_tree_selection_unselect_all (selection);
342                 gtk_tree_selection_select_path (selection, path);
343             }
344             else
345             {
346                 gtk_tree_selection_unselect_path (selection, path);
347             }
348         }
349 
350         if ((click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
351                 && !button_event_modifies_selection(event))
352         {
353             if (event->button == 1)
354             {
355                 activate_selected_items (view);
356             }
357             else if (event->button == 2)
358             {
359                 activate_selected_items_alternate (view, NULL, TRUE);
360             }
361         }
362         gtk_tree_path_free (path);
363     }
364 
365 }
366 
367 static void
drag_data_get_callback(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)368 drag_data_get_callback (GtkWidget *widget,
369                         GdkDragContext *context,
370                         GtkSelectionData *selection_data,
371                         guint info,
372                         guint time)
373 {
374     GtkTreeView *tree_view;
375     GtkTreeModel *model;
376     GList *ref_list;
377 
378     tree_view = GTK_TREE_VIEW (widget);
379 
380     model = gtk_tree_view_get_model (tree_view);
381 
382     if (model == NULL)
383     {
384         return;
385     }
386 
387     ref_list = g_object_get_data (G_OBJECT (context), "drag-info");
388 
389     if (ref_list == NULL)
390     {
391         return;
392     }
393 
394     if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model))
395     {
396         egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
397                 ref_list,
398                 selection_data);
399     }
400 }
401 
402 static void
filtered_selection_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)403 filtered_selection_foreach (GtkTreeModel *model,
404                             GtkTreePath *path,
405                             GtkTreeIter *iter,
406                             gpointer data)
407 {
408     struct SelectionForeachData *selection_data;
409     GtkTreeIter parent;
410     GtkTreeIter child;
411 
412     selection_data = data;
413 
414     /* If the parent folder is also selected, don't include this file in the
415      * file operation, since that would copy it to the toplevel target instead
416      * of keeping it as a child of the copied folder
417      */
418     child = *iter;
419     while (gtk_tree_model_iter_parent (model, &parent, &child))
420     {
421         if (gtk_tree_selection_iter_is_selected (selection_data->selection,
422                 &parent))
423         {
424             return;
425         }
426         child = parent;
427     }
428 
429     selection_data->list = g_list_prepend (selection_data->list,
430                                            gtk_tree_row_reference_new (model, path));
431 }
432 
433 static GList *
get_filtered_selection_refs(GtkTreeView * tree_view)434 get_filtered_selection_refs (GtkTreeView *tree_view)
435 {
436     struct SelectionForeachData selection_data;
437 
438     selection_data.list = NULL;
439     selection_data.selection = gtk_tree_view_get_selection (tree_view);
440 
441     gtk_tree_selection_selected_foreach (selection_data.selection,
442                                          filtered_selection_foreach,
443                                          &selection_data);
444     return g_list_reverse (selection_data.list);
445 }
446 
447 static void
ref_list_free(GList * ref_list)448 ref_list_free (GList *ref_list)
449 {
450     g_list_free_full (ref_list, (GDestroyNotify) gtk_tree_row_reference_free);
451 }
452 
453 static void
stop_drag_check(FMListView * view)454 stop_drag_check (FMListView *view)
455 {
456     view->details->drag_button = 0;
457 }
458 
459 static cairo_surface_t *
get_drag_surface(FMListView * view)460 get_drag_surface (FMListView *view)
461 {
462     GtkTreePath *path;
463     GtkTreeIter iter;
464     cairo_surface_t *ret;
465     GdkRectangle cell_area;
466 
467     ret = NULL;
468 
469     if (gtk_tree_view_get_path_at_pos (view->details->tree_view,
470                                        view->details->drag_x,
471                                        view->details->drag_y,
472                                        &path, NULL, NULL, NULL))
473     {
474         GtkTreeModel *model;
475 
476         model = gtk_tree_view_get_model (view->details->tree_view);
477         gtk_tree_model_get_iter (model, &iter, path);
478         gtk_tree_model_get (model, &iter,
479                             fm_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
480                             &ret,
481                             -1);
482 
483         gtk_tree_view_get_cell_area (view->details->tree_view,
484                                      path,
485                                      view->details->file_name_column,
486                                      &cell_area);
487 
488         gtk_tree_path_free (path);
489     }
490 
491     return ret;
492 }
493 
494 static void
drag_begin_callback(GtkWidget * widget,GdkDragContext * context,FMListView * view)495 drag_begin_callback (GtkWidget *widget,
496                      GdkDragContext *context,
497                      FMListView *view)
498 {
499     GList *ref_list;
500     cairo_surface_t *surface;
501 
502     surface = get_drag_surface (view);
503     if (surface) {
504         gtk_drag_set_icon_surface (context, surface);
505         cairo_surface_destroy (surface);
506     }
507     else
508     {
509         gtk_drag_set_icon_default (context);
510     }
511 
512     stop_drag_check (view);
513     view->details->drag_started = TRUE;
514 
515     ref_list = get_filtered_selection_refs (GTK_TREE_VIEW (widget));
516     g_object_set_data_full (G_OBJECT (context),
517                             "drag-info",
518                             ref_list,
519                             (GDestroyNotify)ref_list_free);
520 }
521 
522 static gboolean
motion_notify_callback(GtkWidget * widget,GdkEventMotion * event,gpointer callback_data)523 motion_notify_callback (GtkWidget *widget,
524                         GdkEventMotion *event,
525                         gpointer callback_data)
526 {
527     FMListView *view;
528 
529     view = FM_LIST_VIEW (callback_data);
530 
531     if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)))
532     {
533         return FALSE;
534     }
535 
536     if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
537     {
538         GtkTreePath *old_hover_path;
539 
540         old_hover_path = view->details->hover_path;
541         gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
542                                        event->x, event->y,
543                                        &view->details->hover_path,
544                                        NULL, NULL, NULL);
545 
546         if ((old_hover_path != NULL) != (view->details->hover_path != NULL))
547         {
548             if (view->details->hover_path != NULL)
549             {
550                 gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
551             }
552             else
553             {
554                 gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
555             }
556         }
557 
558         if (old_hover_path != NULL)
559         {
560             gtk_tree_path_free (old_hover_path);
561         }
562     }
563 
564     if (view->details->drag_button != 0)
565     {
566         if (!source_target_list)
567         {
568             source_target_list = fm_list_model_get_drag_target_list ();
569         }
570 
571         if (gtk_drag_check_threshold (widget,
572                                       view->details->drag_x,
573                                       view->details->drag_y,
574                                       event->x,
575                                       event->y))
576         {
577             gtk_drag_begin_with_coordinates (widget,
578                                              source_target_list,
579                                              GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK,
580                                              view->details->drag_button,
581                                              (GdkEvent*)event,
582                                              event->x,
583                                              event->y);
584         }
585         return TRUE;
586     }
587 
588     return FALSE;
589 }
590 
591 static gboolean
leave_notify_callback(GtkWidget * widget,GdkEventCrossing * event,gpointer callback_data)592 leave_notify_callback (GtkWidget *widget,
593                        GdkEventCrossing *event,
594                        gpointer callback_data)
595 {
596     FMListView *view;
597 
598     view = FM_LIST_VIEW (callback_data);
599 
600     if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE &&
601             view->details->hover_path != NULL)
602     {
603         gtk_tree_path_free (view->details->hover_path);
604         view->details->hover_path = NULL;
605     }
606 
607     return FALSE;
608 }
609 
610 static gboolean
enter_notify_callback(GtkWidget * widget,GdkEventCrossing * event,gpointer callback_data)611 enter_notify_callback (GtkWidget *widget,
612                        GdkEventCrossing *event,
613                        gpointer callback_data)
614 {
615     FMListView *view;
616 
617     view = FM_LIST_VIEW (callback_data);
618 
619     if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
620     {
621         if (view->details->hover_path != NULL)
622         {
623             gtk_tree_path_free (view->details->hover_path);
624         }
625 
626         gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
627                                        event->x, event->y,
628                                        &view->details->hover_path,
629                                        NULL, NULL, NULL);
630 
631         if (view->details->hover_path != NULL)
632         {
633             gdk_window_set_cursor (gtk_widget_get_window (widget), hand_cursor);
634         }
635     }
636 
637     return FALSE;
638 }
639 
640 static void
do_popup_menu(GtkWidget * widget,FMListView * view,GdkEventButton * event)641 do_popup_menu (GtkWidget *widget, FMListView *view, GdkEventButton *event)
642 {
643     if (tree_view_has_selection (GTK_TREE_VIEW (widget)))
644     {
645         fm_directory_view_pop_up_selection_context_menu (FM_DIRECTORY_VIEW (view), event);
646     }
647     else
648     {
649         fm_directory_view_pop_up_background_context_menu (FM_DIRECTORY_VIEW (view), event);
650     }
651 }
652 
653 static gboolean
button_press_callback(GtkWidget * widget,GdkEventButton * event,gpointer callback_data)654 button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callback_data)
655 {
656     FMListView *view;
657     GtkTreeView *tree_view;
658     GtkTreePath *path;
659     gboolean call_parent;
660     GtkTreeSelection *selection;
661     GtkWidgetClass *tree_view_class;
662     gint64 current_time;
663     static gint64 last_click_time = 0;
664     static int click_count = 0;
665     int double_click_time;
666     int expander_size, horizontal_separator;
667     gboolean on_expander;
668 
669     view = FM_LIST_VIEW (callback_data);
670     tree_view = GTK_TREE_VIEW (widget);
671     tree_view_class = GTK_WIDGET_GET_CLASS (tree_view);
672     selection = gtk_tree_view_get_selection (tree_view);
673 
674     /* Don't handle extra mouse buttons here */
675     if (event->button > 5)
676     {
677         return FALSE;
678     }
679 
680     if (event->window != gtk_tree_view_get_bin_window (tree_view))
681     {
682         return FALSE;
683     }
684 
685     fm_list_model_set_drag_view
686     (FM_LIST_MODEL (gtk_tree_view_get_model (tree_view)),
687      tree_view,
688      event->x, event->y);
689 
690     g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
691                   "gtk-double-click-time", &double_click_time,
692                   NULL);
693 
694     /* Determine click count */
695     current_time = g_get_monotonic_time ();
696     if (current_time - last_click_time < double_click_time * 1000)
697     {
698         click_count++;
699     }
700     else
701     {
702         click_count = 0;
703     }
704 
705     /* Stash time for next compare */
706     last_click_time = current_time;
707 
708     /* Ignore double click if we are in single click mode */
709     if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE && click_count >= 2)
710     {
711         return TRUE;
712     }
713 
714     view->details->ignore_button_release = FALSE;
715 
716     call_parent = TRUE;
717     if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
718                                        &path, NULL, NULL, NULL))
719     {
720         gtk_widget_style_get (widget,
721                               "expander-size", &expander_size,
722                               "horizontal-separator", &horizontal_separator,
723                               NULL);
724         /* TODO we should not hardcode this extra padding. It is
725          * EXPANDER_EXTRA_PADDING from GtkTreeView.
726          */
727         expander_size += 4;
728         on_expander = (event->x <= horizontal_separator / 2 +
729                        gtk_tree_path_get_depth (path) * expander_size);
730 
731         /* Keep track of path of last click so double clicks only happen
732          * on the same item */
733         if ((event->button == 1 || event->button == 2)  &&
734                 event->type == GDK_BUTTON_PRESS)
735         {
736             if (view->details->double_click_path[1])
737             {
738                 gtk_tree_path_free (view->details->double_click_path[1]);
739             }
740             view->details->double_click_path[1] = view->details->double_click_path[0];
741             view->details->double_click_path[0] = gtk_tree_path_copy (path);
742         }
743         if (event->type == GDK_2BUTTON_PRESS)
744         {
745             /* Double clicking does not trigger a D&D action. */
746             view->details->drag_button = 0;
747             if (view->details->double_click_path[1] &&
748                     gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 &&
749                     !on_expander)
750             {
751                 /* NOTE: Activation can actually destroy the view if we're switching */
752                 if (!button_event_modifies_selection (event))
753                 {
754                     if ((event->button == 1 || event->button == 3))
755                     {
756                         activate_selected_items (view);
757                     }
758                     else if (event->button == 2)
759                     {
760                         activate_selected_items_alternate (view, NULL, TRUE);
761                     }
762                 }
763                 else if (event->button == 1 &&
764                          (event->state & GDK_SHIFT_MASK) != 0)
765                 {
766                     CajaFile *file;
767                     file = fm_list_model_file_for_path (view->details->model, path);
768                     if (file != NULL)
769                     {
770                         activate_selected_items_alternate (view, file, TRUE);
771                         caja_file_unref (file);
772                     }
773                 }
774             }
775             else
776             {
777                 tree_view_class->button_press_event (widget, event);
778             }
779         }
780         else
781         {
782 
783             /* We're going to filter out some situations where
784              * we can't let the default code run because all
785              * but one row would be would be deselected. We don't
786              * want that; we want the right click menu or single
787              * click to apply to everything that's currently selected. */
788 
789             if (event->button == 3 && gtk_tree_selection_path_is_selected (selection, path))
790             {
791                 call_parent = FALSE;
792             }
793 
794             if ((event->button == 1 || event->button == 2) &&
795                     ((event->state & GDK_CONTROL_MASK) != 0 ||
796                      (event->state & GDK_SHIFT_MASK) == 0))
797             {
798                 view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path);
799                 if (view->details->row_selected_on_button_down)
800                 {
801                     call_parent = on_expander;
802                     view->details->ignore_button_release = call_parent;
803                 }
804                 else if ((event->state & GDK_CONTROL_MASK) != 0)
805                 {
806                     GList *selected_rows;
807                     GList *l;
808 
809                     call_parent = FALSE;
810                     if ((event->state & GDK_SHIFT_MASK) != 0)
811                     {
812                         GtkTreePath *cursor;
813                         gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
814                         if (cursor != NULL)
815                         {
816                             gtk_tree_selection_select_range (selection, cursor, path);
817                         }
818                         else
819                         {
820                             gtk_tree_selection_select_path (selection, path);
821                         }
822                     }
823                     else
824                     {
825                         gtk_tree_selection_select_path (selection, path);
826                     }
827                     selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
828 
829                     /* This unselects everything */
830                     gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
831 
832                     /* So select it again */
833                     l = selected_rows;
834                     while (l != NULL)
835                     {
836                         GtkTreePath *p = l->data;
837                         l = l->next;
838                         gtk_tree_selection_select_path (selection, p);
839                         gtk_tree_path_free (p);
840                     }
841                     g_list_free (selected_rows);
842                 }
843                 else
844                 {
845                     view->details->ignore_button_release = on_expander;
846                 }
847             }
848 
849             if (call_parent)
850             {
851                 tree_view_class->button_press_event (widget, event);
852             }
853             else if (gtk_tree_selection_path_is_selected (selection, path))
854             {
855                 gtk_widget_grab_focus (widget);
856             }
857 
858             if ((event->button == 1 || event->button == 2) &&
859                     event->type == GDK_BUTTON_PRESS)
860             {
861                 view->details->drag_started = FALSE;
862                 view->details->drag_button = event->button;
863                 view->details->drag_x = event->x;
864                 view->details->drag_y = event->y;
865             }
866 
867             if (event->button == 3)
868             {
869                 do_popup_menu (widget, view, event);
870             }
871         }
872 
873         gtk_tree_path_free (path);
874     }
875     else
876     {
877         if ((event->button == 1 || event->button == 2)  &&
878                 event->type == GDK_BUTTON_PRESS)
879         {
880             if (view->details->double_click_path[1])
881             {
882                 gtk_tree_path_free (view->details->double_click_path[1]);
883             }
884             view->details->double_click_path[1] = view->details->double_click_path[0];
885             view->details->double_click_path[0] = NULL;
886         }
887 
888         /* Deselect if people click outside any row. It's OK to
889            let default code run; it won't reselect anything. */
890         gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
891         tree_view_class->button_press_event (widget, event);
892 
893         if (event->button == 3)
894         {
895             do_popup_menu (widget, view, event);
896         }
897     }
898 
899     /* We chained to the default handler in this method, so never
900      * let the default handler run */
901     return TRUE;
902 }
903 
904 static gboolean
button_release_callback(GtkWidget * widget,GdkEventButton * event,gpointer callback_data)905 button_release_callback (GtkWidget *widget,
906                          GdkEventButton *event,
907                          gpointer callback_data)
908 {
909     FMListView *view;
910 
911     view = FM_LIST_VIEW (callback_data);
912 
913     if (event->button == view->details->drag_button)
914     {
915         stop_drag_check (view);
916         if (!view->details->drag_started &&
917                 !view->details->ignore_button_release)
918         {
919             fm_list_view_did_not_drag (view, event);
920         }
921     }
922     return FALSE;
923 }
924 
925 static gboolean
popup_menu_callback(GtkWidget * widget,gpointer callback_data)926 popup_menu_callback (GtkWidget *widget, gpointer callback_data)
927 {
928     FMListView *view;
929 
930     view = FM_LIST_VIEW (callback_data);
931 
932     do_popup_menu (widget, view, NULL);
933 
934     return TRUE;
935 }
936 
937 static void
subdirectory_done_loading_callback(CajaDirectory * directory,FMListView * view)938 subdirectory_done_loading_callback (CajaDirectory *directory, FMListView *view)
939 {
940     fm_list_model_subdirectory_done_loading (view->details->model, directory);
941 }
942 
943 static void
row_expanded_callback(GtkTreeView * treeview,GtkTreeIter * iter,GtkTreePath * path,gpointer callback_data)944 row_expanded_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
945 {
946     FMListView *view;
947     CajaDirectory *directory;
948 
949     view = FM_LIST_VIEW (callback_data);
950 
951     if (fm_list_model_load_subdirectory (view->details->model, path, &directory))
952     {
953         char *uri;
954 
955         uri = caja_directory_get_uri (directory);
956         caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
957                         "list view row expanded window=%p: %s",
958                         fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)),
959                         uri);
960         g_free (uri);
961 
962         fm_directory_view_add_subdirectory (FM_DIRECTORY_VIEW (view), directory);
963 
964         if (caja_directory_are_all_files_seen (directory))
965         {
966             fm_list_model_subdirectory_done_loading (view->details->model,
967                     directory);
968         }
969         else
970         {
971             g_signal_connect_object (directory, "done_loading",
972                                      G_CALLBACK (subdirectory_done_loading_callback),
973                                      view, 0);
974         }
975 
976         caja_directory_unref (directory);
977     }
978 }
979 
980 struct UnloadDelayData
981 {
982     CajaFile *file;
983     CajaDirectory *directory;
984     FMListView *view;
985 };
986 
987 static gboolean
unload_file_timeout(gpointer data)988 unload_file_timeout (gpointer data)
989 {
990     struct UnloadDelayData *unload_data = data;
991     GtkTreeIter iter;
992 
993     if (unload_data->view != NULL)
994     {
995         FMListModel *model;
996 
997         model = unload_data->view->details->model;
998 
999         if (fm_list_model_get_tree_iter_from_file (model,
1000                 unload_data->file,
1001                 unload_data->directory,
1002                 &iter))
1003         {
1004             GtkTreePath *path;
1005 
1006             path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1007 
1008             if (!gtk_tree_view_row_expanded (unload_data->view->details->tree_view,
1009                                              path))
1010             {
1011                 fm_list_model_unload_subdirectory (model, &iter);
1012             }
1013             gtk_tree_path_free (path);
1014         }
1015     }
1016 
1017     eel_remove_weak_pointer (&unload_data->view);
1018 
1019     if (unload_data->directory)
1020     {
1021         caja_directory_unref (unload_data->directory);
1022     }
1023     caja_file_unref (unload_data->file);
1024     g_free (unload_data);
1025     return FALSE;
1026 }
1027 
1028 static void
row_collapsed_callback(GtkTreeView * treeview,GtkTreeIter * iter,GtkTreePath * path,gpointer callback_data)1029 row_collapsed_callback (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer callback_data)
1030 {
1031     FMListView *view;
1032     CajaFile *file;
1033     CajaDirectory *directory;
1034     GtkTreeIter parent;
1035     struct UnloadDelayData *unload_data;
1036     GtkTreeModel *model;
1037     char *uri;
1038 
1039     view = FM_LIST_VIEW (callback_data);
1040     model = GTK_TREE_MODEL (view->details->model);
1041 
1042     gtk_tree_model_get (model, iter,
1043                         FM_LIST_MODEL_FILE_COLUMN, &file,
1044                         -1);
1045 
1046     directory = NULL;
1047     if (gtk_tree_model_iter_parent (model, &parent, iter))
1048     {
1049         gtk_tree_model_get (model, &parent,
1050                             FM_LIST_MODEL_SUBDIRECTORY_COLUMN, &directory,
1051                             -1);
1052     }
1053 
1054 
1055     uri = caja_file_get_uri (file);
1056     caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
1057                     "list view row collapsed window=%p: %s",
1058                     fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view)),
1059                     uri);
1060     g_free (uri);
1061 
1062     unload_data = g_new (struct UnloadDelayData, 1);
1063     unload_data->view = view;
1064     unload_data->file = file;
1065     unload_data->directory = directory;
1066 
1067     eel_add_weak_pointer (&unload_data->view);
1068 
1069     g_timeout_add_seconds (COLLAPSE_TO_UNLOAD_DELAY,
1070                            unload_file_timeout,
1071                            unload_data);
1072 }
1073 
1074 static void
row_activated_callback(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,FMListView * view)1075 row_activated_callback (GtkTreeView *treeview, GtkTreePath *path,
1076                         GtkTreeViewColumn *column, FMListView *view)
1077 {
1078     activate_selected_items (view);
1079 }
1080 
1081 static void
subdirectory_unloaded_callback(FMListModel * model,CajaDirectory * directory,gpointer callback_data)1082 subdirectory_unloaded_callback (FMListModel *model,
1083                                 CajaDirectory *directory,
1084                                 gpointer callback_data)
1085 {
1086     FMListView *view;
1087 
1088     g_return_if_fail (FM_IS_LIST_MODEL (model));
1089     g_return_if_fail (CAJA_IS_DIRECTORY (directory));
1090 
1091     view = FM_LIST_VIEW(callback_data);
1092 
1093     g_signal_handlers_disconnect_by_func (directory,
1094                                           G_CALLBACK (subdirectory_done_loading_callback),
1095                                           view);
1096     fm_directory_view_remove_subdirectory (FM_DIRECTORY_VIEW (view), directory);
1097 }
1098 
1099 static gboolean
key_press_callback(GtkWidget * widget,GdkEventKey * event,gpointer callback_data)1100 key_press_callback (GtkWidget *widget, GdkEventKey *event, gpointer callback_data)
1101 {
1102     FMDirectoryView *view;
1103     FMListView *listview;
1104     GdkEventButton button_event = { 0 };
1105     gboolean handled;
1106     GtkTreeView *tree_view;
1107     GtkTreePath *path;
1108 
1109     tree_view = GTK_TREE_VIEW (widget);
1110 
1111     view = FM_DIRECTORY_VIEW (callback_data);
1112     listview = FM_LIST_VIEW (view);
1113     handled = FALSE;
1114 
1115     switch (event->keyval)
1116     {
1117     case GDK_KEY_F10:
1118         if (event->state & GDK_CONTROL_MASK)
1119         {
1120             fm_directory_view_pop_up_background_context_menu (view, &button_event);
1121             handled = TRUE;
1122         }
1123         break;
1124     case GDK_KEY_Right:
1125         gtk_tree_view_get_cursor (tree_view, &path, NULL);
1126         if (path)
1127         {
1128             gtk_tree_view_expand_row (tree_view, path, FALSE);
1129             gtk_tree_path_free (path);
1130         }
1131         handled = TRUE;
1132         break;
1133 	case GDK_KEY_Left:
1134         gtk_tree_view_get_cursor (tree_view, &path, NULL);
1135         if (path)
1136         {
1137             if (!gtk_tree_view_collapse_row (tree_view, path)) {
1138                 /* if the row is already collapsed or doesn't have any children,
1139                  * jump to the parent row instead.
1140                  */
1141                 if ((gtk_tree_path_get_depth (path) > 1) && gtk_tree_path_up (path)) {
1142                     gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1143                 }
1144             }
1145 
1146             gtk_tree_path_free (path);
1147         }
1148         handled = TRUE;
1149         break;
1150     case GDK_KEY_space:
1151         if (event->state & GDK_CONTROL_MASK)
1152         {
1153             handled = FALSE;
1154             break;
1155         }
1156         if (!gtk_widget_has_focus (GTK_WIDGET (FM_LIST_VIEW (view)->details->tree_view)))
1157         {
1158             handled = FALSE;
1159             break;
1160         }
1161         if ((event->state & GDK_SHIFT_MASK) != 0)
1162         {
1163             activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE);
1164         }
1165         else
1166         {
1167             activate_selected_items (FM_LIST_VIEW (view));
1168         }
1169         handled = TRUE;
1170         break;
1171     case GDK_KEY_Return:
1172     case GDK_KEY_KP_Enter:
1173 	if (GTK_IS_CELL_EDITABLE (listview->details->editable_widget) &&
1174 	    ((event->state & GDK_SHIFT_MASK) || (event->state & GDK_CONTROL_MASK)))
1175 	{
1176 		event->state = 0;
1177 		handled = FALSE;
1178 	}
1179 	else
1180 	{
1181 	        if ((event->state & GDK_SHIFT_MASK) != 0)
1182 	        {
1183 	            activate_selected_items_alternate (FM_LIST_VIEW (view), NULL, TRUE);
1184 	        }
1185 	        else
1186 	        {
1187 	            activate_selected_items (FM_LIST_VIEW (view));
1188 	        }
1189 	        handled = TRUE;
1190 	}
1191         break;
1192     case GDK_KEY_v:
1193         /* Eat Control + v to not enable type ahead */
1194         if ((event->state & GDK_CONTROL_MASK) != 0)
1195         {
1196             handled = TRUE;
1197         }
1198         break;
1199 
1200     default:
1201         handled = FALSE;
1202     }
1203 
1204     return handled;
1205 }
1206 
1207 static void
fm_list_view_reveal_selection(FMDirectoryView * view)1208 fm_list_view_reveal_selection (FMDirectoryView *view)
1209 {
1210     GList *selection;
1211 
1212     g_return_if_fail (FM_IS_LIST_VIEW (view));
1213 
1214     selection = fm_directory_view_get_selection (view);
1215 
1216     /* Make sure at least one of the selected items is scrolled into view */
1217     if (selection != NULL)
1218     {
1219         FMListView *list_view;
1220         CajaFile *file;
1221         GtkTreeIter iter;
1222 
1223         list_view = FM_LIST_VIEW (view);
1224         file = selection->data;
1225 
1226         if (fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
1227         {
1228             GtkTreePath *path;
1229 
1230             path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
1231 
1232             gtk_tree_view_scroll_to_cell (list_view->details->tree_view, path, NULL, FALSE, 0.0, 0.0);
1233 
1234             gtk_tree_path_free (path);
1235         }
1236     }
1237 
1238     caja_file_list_free (selection);
1239 }
1240 
1241 static gboolean
sort_criterion_changes_due_to_user(GtkTreeView * tree_view)1242 sort_criterion_changes_due_to_user (GtkTreeView *tree_view)
1243 {
1244     GList *columns, *p;
1245     gboolean ret;
1246     GtkTreeViewColumn *column = NULL;
1247     GSignalInvocationHint *ihint = NULL;
1248 
1249     ret = FALSE;
1250 
1251     columns = gtk_tree_view_get_columns (tree_view);
1252 
1253     for (p = columns; p != NULL; p = p->next)
1254     {
1255         column = p->data;
1256         ihint = g_signal_get_invocation_hint (column);
1257 
1258         if (ihint != NULL)
1259         {
1260             ret = TRUE;
1261             break;
1262         }
1263     }
1264     g_list_free (columns);
1265 
1266     return ret;
1267 }
1268 
1269 static void
sort_column_changed_callback(GtkTreeSortable * sortable,FMListView * view)1270 sort_column_changed_callback (GtkTreeSortable *sortable,
1271                               FMListView *view)
1272 {
1273     CajaFile *file;
1274     gint sort_column_id, default_sort_column_id;
1275     GtkSortType reversed;
1276     GQuark sort_attr, default_sort_attr;
1277     char *reversed_attr, *default_reversed_attr;
1278     gboolean default_sort_reversed;
1279 
1280     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
1281 
1282     gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &reversed);
1283     sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, sort_column_id);
1284 
1285     default_sort_column_id = fm_list_model_get_sort_column_id_from_attribute (view->details->model,
1286                              g_quark_from_string (get_default_sort_order (file, &default_sort_reversed)));
1287     default_sort_attr = fm_list_model_get_attribute_from_sort_column_id (view->details->model, default_sort_column_id);
1288     caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
1289                             g_quark_to_string (default_sort_attr), g_quark_to_string (sort_attr));
1290 
1291     default_reversed_attr = (default_sort_reversed ? "true" : "false");
1292 
1293     if (view->details->last_sort_attr != sort_attr &&
1294             sort_criterion_changes_due_to_user (view->details->tree_view))
1295     {
1296         /* at this point, the sort order is always GTK_SORT_ASCENDING, if the sort column ID
1297          * switched. Invert the sort order, if it's the default criterion with a reversed preference,
1298          * or if it makes sense for the attribute (i.e. date). */
1299         if (sort_attr == default_sort_attr)
1300         {
1301             /* use value from preferences */
1302             reversed = g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER);
1303         }
1304         else
1305         {
1306             reversed = caja_file_is_date_sort_attribute_q (sort_attr);
1307         }
1308 
1309         if (reversed)
1310         {
1311             g_signal_handlers_block_by_func (sortable, sort_column_changed_callback, view);
1312             gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (view->details->model),
1313                                                   sort_column_id,
1314                                                   GTK_SORT_DESCENDING);
1315             g_signal_handlers_unblock_by_func (sortable, sort_column_changed_callback, view);
1316         }
1317     }
1318 
1319 
1320     reversed_attr = (reversed ? "true" : "false");
1321     caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
1322                             default_reversed_attr, reversed_attr);
1323 
1324     /* Make sure selected item(s) is visible after sort */
1325     fm_list_view_reveal_selection (FM_DIRECTORY_VIEW (view));
1326 
1327     view->details->last_sort_attr = sort_attr;
1328 }
1329 
1330 static void
cell_renderer_editing_started_cb(GtkCellRenderer * renderer,GtkCellEditable * editable,const gchar * path_str,FMListView * list_view)1331 cell_renderer_editing_started_cb (GtkCellRenderer *renderer,
1332                                   GtkCellEditable *editable,
1333                                   const gchar *path_str,
1334                                   FMListView *list_view)
1335 {
1336     GtkEntry *entry;
1337 
1338     entry = GTK_ENTRY (editable);
1339     list_view->details->editable_widget = editable;
1340 
1341     /* Free a previously allocated original_name */
1342     g_free (list_view->details->original_name);
1343 
1344     list_view->details->original_name = g_strdup (gtk_entry_get_text (entry));
1345 
1346     caja_clipboard_set_up_editable
1347     (GTK_EDITABLE (entry),
1348      fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (list_view)),
1349      FALSE);
1350 }
1351 
1352 static void
cell_renderer_editing_canceled(GtkCellRendererText * cell,FMListView * view)1353 cell_renderer_editing_canceled (GtkCellRendererText *cell,
1354                                 FMListView          *view)
1355 {
1356     view->details->editable_widget = NULL;
1357 
1358     fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
1359 }
1360 
1361 static void
cell_renderer_edited(GtkCellRendererText * cell,const char * path_str,const char * new_text,FMListView * view)1362 cell_renderer_edited (GtkCellRendererText *cell,
1363                       const char          *path_str,
1364                       const char          *new_text,
1365                       FMListView          *view)
1366 {
1367     GtkTreePath *path;
1368     CajaFile *file;
1369     GtkTreeIter iter;
1370 
1371     view->details->editable_widget = NULL;
1372 
1373     /* Don't allow a rename with an empty string. Revert to original
1374      * without notifying the user.
1375      */
1376     if (new_text[0] == '\0')
1377     {
1378         g_object_set (G_OBJECT (view->details->file_name_cell),
1379                       "editable", FALSE,
1380                       NULL);
1381         fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
1382         return;
1383     }
1384 
1385     path = gtk_tree_path_new_from_string (path_str);
1386 
1387     gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
1388                              &iter, path);
1389 
1390     gtk_tree_path_free (path);
1391 
1392     gtk_tree_model_get (GTK_TREE_MODEL (view->details->model),
1393                         &iter,
1394                         FM_LIST_MODEL_FILE_COLUMN, &file,
1395                         -1);
1396 
1397     /* Only rename if name actually changed */
1398     if (strcmp (new_text, view->details->original_name) != 0)
1399     {
1400         view->details->renaming_file = caja_file_ref (file);
1401         view->details->rename_done = FALSE;
1402         fm_rename_file (file, new_text, fm_list_view_rename_callback, g_object_ref (view));
1403         g_free (view->details->original_name);
1404         view->details->original_name = g_strdup (new_text);
1405     }
1406 
1407     caja_file_unref (file);
1408 
1409     /*We're done editing - make the filename-cells readonly again.*/
1410     g_object_set (G_OBJECT (view->details->file_name_cell),
1411                   "editable", FALSE,
1412                   NULL);
1413 
1414     fm_directory_view_unfreeze_updates (FM_DIRECTORY_VIEW (view));
1415 }
1416 
1417 static char *
get_root_uri_callback(CajaTreeViewDragDest * dest,gpointer user_data)1418 get_root_uri_callback (CajaTreeViewDragDest *dest,
1419                        gpointer user_data)
1420 {
1421     FMListView *view;
1422 
1423     view = FM_LIST_VIEW (user_data);
1424 
1425     return fm_directory_view_get_uri (FM_DIRECTORY_VIEW (view));
1426 }
1427 
1428 static CajaFile *
get_file_for_path_callback(CajaTreeViewDragDest * dest,GtkTreePath * path,gpointer user_data)1429 get_file_for_path_callback (CajaTreeViewDragDest *dest,
1430                             GtkTreePath *path,
1431                             gpointer user_data)
1432 {
1433     FMListView *view;
1434 
1435     view = FM_LIST_VIEW (user_data);
1436 
1437     return fm_list_model_file_for_path (view->details->model, path);
1438 }
1439 
1440 /* Handles an URL received from Mozilla */
1441 static void
list_view_handle_netscape_url(CajaTreeViewDragDest * dest,const char * encoded_url,const char * target_uri,GdkDragAction action,int x,int y,FMListView * view)1442 list_view_handle_netscape_url (CajaTreeViewDragDest *dest, const char *encoded_url,
1443                                const char *target_uri, GdkDragAction action, int x, int y, FMListView *view)
1444 {
1445     fm_directory_view_handle_netscape_url_drop (FM_DIRECTORY_VIEW (view),
1446             encoded_url, target_uri, action, x, y);
1447 }
1448 
1449 static void
list_view_handle_uri_list(CajaTreeViewDragDest * dest,const char * item_uris,const char * target_uri,GdkDragAction action,int x,int y,FMListView * view)1450 list_view_handle_uri_list (CajaTreeViewDragDest *dest, const char *item_uris,
1451                            const char *target_uri,
1452                            GdkDragAction action, int x, int y, FMListView *view)
1453 {
1454     fm_directory_view_handle_uri_list_drop (FM_DIRECTORY_VIEW (view),
1455                                             item_uris, target_uri, action, x, y);
1456 }
1457 
1458 static void
list_view_handle_text(CajaTreeViewDragDest * dest,const char * text,const char * target_uri,GdkDragAction action,int x,int y,FMListView * view)1459 list_view_handle_text (CajaTreeViewDragDest *dest, const char *text,
1460                        const char *target_uri,
1461                        GdkDragAction action, int x, int y, FMListView *view)
1462 {
1463     fm_directory_view_handle_text_drop (FM_DIRECTORY_VIEW (view),
1464                                         text, target_uri, action, x, y);
1465 }
1466 
1467 static void
list_view_handle_raw(CajaTreeViewDragDest * dest,const char * raw_data,int length,const char * target_uri,const char * direct_save_uri,GdkDragAction action,int x,int y,FMListView * view)1468 list_view_handle_raw (CajaTreeViewDragDest *dest, const char *raw_data,
1469                       int length, const char *target_uri, const char *direct_save_uri,
1470                       GdkDragAction action, int x, int y, FMListView *view)
1471 {
1472     fm_directory_view_handle_raw_drop (FM_DIRECTORY_VIEW (view),
1473                                        raw_data, length, target_uri, direct_save_uri,
1474                                        action, x, y);
1475 }
1476 
1477 static void
move_copy_items_callback(CajaTreeViewDragDest * dest,const GList * item_uris,const char * target_uri,guint action,int x,int y,gpointer user_data)1478 move_copy_items_callback (CajaTreeViewDragDest *dest,
1479                           const GList *item_uris,
1480                           const char *target_uri,
1481                           guint action,
1482                           int x,
1483                           int y,
1484                           gpointer user_data)
1485 
1486 {
1487     FMDirectoryView *view = user_data;
1488 
1489     caja_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
1490                                             item_uris,
1491                                             fm_directory_view_get_copied_files_atom (view));
1492     fm_directory_view_move_copy_items (item_uris,
1493                                        NULL,
1494                                        target_uri,
1495                                        action,
1496                                        x, y,
1497                                        view);
1498 }
1499 
1500 static void
apply_columns_settings(FMListView * list_view,char ** column_order,char ** visible_columns)1501 apply_columns_settings (FMListView *list_view,
1502                         char **column_order,
1503                         char **visible_columns)
1504 {
1505     GList *all_columns;
1506     CajaFile *file;
1507     GList *old_view_columns, *view_columns;
1508     GHashTable *visible_columns_hash;
1509     GtkTreeViewColumn *prev_view_column;
1510     GList *l;
1511     int i;
1512 
1513     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
1514 
1515     /* prepare ordered list of view columns using column_order and visible_columns */
1516     view_columns = NULL;
1517 
1518     all_columns = caja_get_columns_for_file (file);
1519     all_columns = caja_sort_columns (all_columns, column_order);
1520 
1521     /* hash table to lookup if a given column should be visible */
1522     visible_columns_hash = g_hash_table_new_full (g_str_hash,
1523                            g_str_equal,
1524                            (GDestroyNotify) g_free,
1525                            (GDestroyNotify) g_free);
1526     for (i = 0; visible_columns[i] != NULL; ++i)
1527     {
1528         g_hash_table_insert (visible_columns_hash,
1529                              g_ascii_strdown (visible_columns[i], -1),
1530                              g_ascii_strdown (visible_columns[i], -1));
1531     }
1532 
1533     for (l = all_columns; l != NULL; l = l->next)
1534     {
1535         char *name;
1536         char *lowercase;
1537 
1538         g_object_get (G_OBJECT (l->data), "name", &name, NULL);
1539         lowercase = g_ascii_strdown (name, -1);
1540 
1541         if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL)
1542         {
1543             GtkTreeViewColumn *view_column;
1544 
1545             view_column = g_hash_table_lookup (list_view->details->columns, name);
1546             if (view_column != NULL)
1547             {
1548                 view_columns = g_list_prepend (view_columns, view_column);
1549             }
1550         }
1551 
1552         g_free (name);
1553         g_free (lowercase);
1554     }
1555 
1556     g_hash_table_destroy (visible_columns_hash);
1557     caja_column_list_free (all_columns);
1558 
1559     view_columns = g_list_reverse (view_columns);
1560 
1561     /* hide columns that are not present in the configuration */
1562     old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
1563     for (l = old_view_columns; l != NULL; l = l->next)
1564     {
1565         if (g_list_find (view_columns, l->data) == NULL)
1566         {
1567             gtk_tree_view_column_set_visible (l->data, FALSE);
1568         }
1569     }
1570     g_list_free (old_view_columns);
1571 
1572     /* show new columns from the configuration */
1573     for (l = view_columns; l != NULL; l = l->next)
1574     {
1575         gtk_tree_view_column_set_visible (l->data, TRUE);
1576     }
1577 
1578     /* place columns in the correct order */
1579     prev_view_column = NULL;
1580     for (l = view_columns; l != NULL; l = l->next)
1581     {
1582         gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column);
1583         prev_view_column = l->data;
1584     }
1585     g_list_free (view_columns);
1586 }
1587 
1588 static void
filename_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,FMListView * view)1589 filename_cell_data_func (GtkTreeViewColumn *column,
1590                          GtkCellRenderer   *renderer,
1591                          GtkTreeModel      *model,
1592                          GtkTreeIter       *iter,
1593                          FMListView        *view)
1594 {
1595     char *text;
1596     PangoUnderline underline;
1597 
1598     gtk_tree_model_get (model, iter,
1599                         view->details->file_name_column_num, &text,
1600                         -1);
1601 
1602     if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
1603     {
1604         GtkTreePath *path;
1605 
1606         path = gtk_tree_model_get_path (model, iter);
1607 
1608         if (view->details->hover_path == NULL ||
1609                 gtk_tree_path_compare (path, view->details->hover_path))
1610         {
1611             underline = PANGO_UNDERLINE_NONE;
1612         }
1613         else
1614         {
1615             underline = PANGO_UNDERLINE_SINGLE;
1616         }
1617 
1618         gtk_tree_path_free (path);
1619     }
1620     else
1621     {
1622         underline = PANGO_UNDERLINE_NONE;
1623     }
1624 
1625     g_object_set (G_OBJECT (renderer),
1626                   "text", text,
1627                   "underline", underline,
1628                   NULL);
1629     g_free (text);
1630 }
1631 
1632 static gboolean
focus_in_event_callback(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)1633 focus_in_event_callback (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
1634 {
1635     CajaWindowSlotInfo *slot_info;
1636     FMListView *list_view = FM_LIST_VIEW (user_data);
1637 
1638     /* make the corresponding slot (and the pane that contains it) active */
1639     slot_info = fm_directory_view_get_caja_window_slot (FM_DIRECTORY_VIEW (list_view));
1640     caja_window_slot_info_make_hosting_pane_active (slot_info);
1641 
1642     return FALSE;
1643 }
1644 
1645 static gint
get_icon_scale_callback(FMListModel * model,FMListView * view)1646 get_icon_scale_callback (FMListModel *model,
1647                          FMListView  *view)
1648 {
1649     return gtk_widget_get_scale_factor (GTK_WIDGET (view->details->tree_view));
1650 }
1651 
1652 static void
create_and_set_up_tree_view(FMListView * view)1653 create_and_set_up_tree_view (FMListView *view)
1654 {
1655     GtkCellRenderer *cell;
1656     GtkTreeViewColumn *column;
1657     GtkBindingSet *binding_set;
1658     AtkObject *atk_obj;
1659     GList *caja_columns;
1660     GList *l;
1661 
1662     view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1663     view->details->columns = g_hash_table_new_full (g_str_hash,
1664                              g_str_equal,
1665                              (GDestroyNotify)g_free,
1666                              NULL);
1667     gtk_tree_view_set_enable_search (view->details->tree_view, TRUE);
1668 
1669     /* Don't handle backspace key. It's used to open the parent folder. */
1670     binding_set = gtk_binding_set_by_class (GTK_WIDGET_GET_CLASS (view->details->tree_view));
1671 	gtk_binding_entry_remove (binding_set, GDK_KEY_BackSpace, 0);
1672 
1673     view->details->drag_dest =
1674         caja_tree_view_drag_dest_new (view->details->tree_view);
1675 
1676     g_signal_connect_object (view->details->drag_dest,
1677                              "get_root_uri",
1678                              G_CALLBACK (get_root_uri_callback),
1679                              view, 0);
1680     g_signal_connect_object (view->details->drag_dest,
1681                              "get_file_for_path",
1682                              G_CALLBACK (get_file_for_path_callback),
1683                              view, 0);
1684     g_signal_connect_object (view->details->drag_dest,
1685                              "move_copy_items",
1686                              G_CALLBACK (move_copy_items_callback),
1687                              view, 0);
1688     g_signal_connect_object (view->details->drag_dest, "handle_netscape_url",
1689                              G_CALLBACK (list_view_handle_netscape_url), view, 0);
1690     g_signal_connect_object (view->details->drag_dest, "handle_uri_list",
1691                              G_CALLBACK (list_view_handle_uri_list), view, 0);
1692     g_signal_connect_object (view->details->drag_dest, "handle_text",
1693                              G_CALLBACK (list_view_handle_text), view, 0);
1694     g_signal_connect_object (view->details->drag_dest, "handle_raw",
1695                              G_CALLBACK (list_view_handle_raw), view, 0);
1696 
1697     g_signal_connect_object (gtk_tree_view_get_selection (view->details->tree_view),
1698                              "changed",
1699                              G_CALLBACK (list_selection_changed_callback), view, 0);
1700 
1701     g_signal_connect_object (view->details->tree_view, "drag_begin",
1702                              G_CALLBACK (drag_begin_callback), view, 0);
1703     g_signal_connect_object (view->details->tree_view, "drag_data_get",
1704                              G_CALLBACK (drag_data_get_callback), view, 0);
1705     g_signal_connect_object (view->details->tree_view, "motion_notify_event",
1706                              G_CALLBACK (motion_notify_callback), view, 0);
1707     g_signal_connect_object (view->details->tree_view, "enter_notify_event",
1708                              G_CALLBACK (enter_notify_callback), view, 0);
1709     g_signal_connect_object (view->details->tree_view, "leave_notify_event",
1710                              G_CALLBACK (leave_notify_callback), view, 0);
1711     g_signal_connect_object (view->details->tree_view, "button_press_event",
1712                              G_CALLBACK (button_press_callback), view, 0);
1713     g_signal_connect_object (view->details->tree_view, "button_release_event",
1714                              G_CALLBACK (button_release_callback), view, 0);
1715     g_signal_connect_object (view->details->tree_view, "key_press_event",
1716                              G_CALLBACK (key_press_callback), view, 0);
1717     g_signal_connect_object (view->details->tree_view, "popup_menu",
1718                              G_CALLBACK (popup_menu_callback), view, 0);
1719     g_signal_connect_object (view->details->tree_view, "row_expanded",
1720                              G_CALLBACK (row_expanded_callback), view, 0);
1721     g_signal_connect_object (view->details->tree_view, "row_collapsed",
1722                              G_CALLBACK (row_collapsed_callback), view, 0);
1723     g_signal_connect_object (view->details->tree_view, "row-activated",
1724                              G_CALLBACK (row_activated_callback), view, 0);
1725 
1726     g_signal_connect_object (view->details->tree_view, "focus_in_event",
1727                              G_CALLBACK(focus_in_event_callback), view, 0);
1728 
1729     view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL);
1730     gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model));
1731     /* Need the model for the dnd drop icon "accept" change */
1732     fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model),
1733                                  view->details->tree_view,  0, 0);
1734 
1735     g_signal_connect_object (view->details->model, "sort_column_changed",
1736                              G_CALLBACK (sort_column_changed_callback), view, 0);
1737 
1738     g_signal_connect_object (view->details->model, "subdirectory_unloaded",
1739                              G_CALLBACK (subdirectory_unloaded_callback), view, 0);
1740 
1741     g_signal_connect_object (view->details->model, "get-icon-scale",
1742                              G_CALLBACK (get_icon_scale_callback), view, 0);
1743 
1744     gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view->details->tree_view), GTK_SELECTION_MULTIPLE);
1745 
1746     caja_columns = caja_get_all_columns ();
1747 
1748     for (l = caja_columns; l != NULL; l = l->next)
1749     {
1750         CajaColumn *caja_column;
1751         int column_num;
1752         char *name;
1753         char *label;
1754         float xalign;
1755 
1756         caja_column = CAJA_COLUMN (l->data);
1757 
1758         g_object_get (caja_column,
1759                       "name", &name,
1760                       "label", &label,
1761                       "xalign", &xalign, NULL);
1762 
1763         column_num = fm_list_model_add_column (view->details->model,
1764                                                caja_column);
1765 
1766         /* Created the name column specially, because it
1767          * has the icon in it.*/
1768         if (!strcmp (name, "name"))
1769         {
1770             int font_size;
1771 
1772             /* Create the file name column */
1773             cell = gtk_cell_renderer_pixbuf_new ();
1774             view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell;
1775 
1776             view->details->file_name_column = gtk_tree_view_column_new ();
1777             gtk_tree_view_column_set_expand (view->details->file_name_column, TRUE);
1778 
1779             GtkStyleContext *context;
1780             context = gtk_widget_get_style_context (GTK_WIDGET(view));
1781             font_size = PANGO_PIXELS (pango_font_description_get_size (
1782                 gtk_style_context_get_font (context, GTK_STATE_FLAG_NORMAL)));
1783 
1784             gtk_tree_view_column_set_min_width (view->details->file_name_column, 20*font_size);
1785             gtk_tree_view_append_column (view->details->tree_view, view->details->file_name_column);
1786             view->details->file_name_column_num = column_num;
1787 
1788             g_hash_table_insert (view->details->columns,
1789                                  g_strdup ("name"),
1790                                  view->details->file_name_column);
1791 
1792             gtk_tree_view_set_search_column (view->details->tree_view, column_num);
1793 
1794             gtk_tree_view_column_set_sort_column_id (view->details->file_name_column, column_num);
1795             gtk_tree_view_column_set_title (view->details->file_name_column, _("Name"));
1796             gtk_tree_view_column_set_resizable (view->details->file_name_column, TRUE);
1797 
1798             gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE);
1799             gtk_tree_view_column_set_attributes (view->details->file_name_column,
1800                                                  cell,
1801                                                  "surface", FM_LIST_MODEL_SMALLEST_ICON_COLUMN,
1802                                                  NULL);
1803 
1804             cell = gtk_cell_renderer_text_new ();
1805             g_object_set (cell,
1806                         "ellipsize", PANGO_ELLIPSIZE_END,
1807                         "ellipsize-set", TRUE,
1808                         NULL);
1809             view->details->file_name_cell = (GtkCellRendererText *)cell;
1810             g_signal_connect (cell, "edited", G_CALLBACK (cell_renderer_edited), view);
1811             g_signal_connect (cell, "editing-canceled", G_CALLBACK (cell_renderer_editing_canceled), view);
1812             g_signal_connect (cell, "editing-started", G_CALLBACK (cell_renderer_editing_started_cb), view);
1813 
1814             gtk_tree_view_column_pack_start (view->details->file_name_column, cell, TRUE);
1815             gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell,
1816                     (GtkTreeCellDataFunc) filename_cell_data_func,
1817                     view, NULL);
1818         }
1819         else
1820         {
1821             cell = gtk_cell_renderer_text_new ();
1822             g_object_set (cell, "xalign", xalign, NULL);
1823             view->details->cells = g_list_append (view->details->cells,
1824                                                   cell);
1825             column = gtk_tree_view_column_new_with_attributes (label,
1826                      cell,
1827                      "text", column_num,
1828                      NULL);
1829             gtk_tree_view_append_column (view->details->tree_view, column);
1830             gtk_tree_view_column_set_sort_column_id (column, column_num);
1831             g_hash_table_insert (view->details->columns,
1832                                  g_strdup (name),
1833                                  column);
1834 
1835             gtk_tree_view_column_set_resizable (column, TRUE);
1836         }
1837         g_free (name);
1838         g_free (label);
1839     }
1840     caja_column_list_free (caja_columns);
1841 
1842     /* Apply the default column order and visible columns, to get it
1843      * right most of the time. The metadata will be checked when a
1844      * folder is loaded */
1845     apply_columns_settings (view,
1846                             default_column_order_auto_value,
1847                             default_visible_columns_auto_value);
1848 
1849     gtk_widget_show (GTK_WIDGET (view->details->tree_view));
1850     gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view));
1851 
1852 
1853     atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view));
1854     atk_object_set_name (atk_obj, _("List View"));
1855 }
1856 
1857 static void
fm_list_view_add_file(FMDirectoryView * view,CajaFile * file,CajaDirectory * directory)1858 fm_list_view_add_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
1859 {
1860     FMListModel *model;
1861 
1862     model = FM_LIST_VIEW (view)->details->model;
1863     fm_list_model_add_file (model, file, directory);
1864 }
1865 
1866 static char **
get_visible_columns(FMListView * list_view)1867 get_visible_columns (FMListView *list_view)
1868 {
1869     CajaFile *file;
1870     GList *visible_columns;
1871     char **ret;
1872 
1873     ret = NULL;
1874 
1875     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
1876 
1877     visible_columns = caja_file_get_metadata_list
1878                       (file,
1879                        CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS);
1880 
1881     if (visible_columns)
1882     {
1883         GPtrArray *res;
1884         GList *l;
1885 
1886         res = g_ptr_array_new ();
1887         for (l = visible_columns; l != NULL; l = l->next)
1888         {
1889             g_ptr_array_add (res, l->data);
1890         }
1891         g_ptr_array_add (res, NULL);
1892 
1893         ret = (char **) g_ptr_array_free (res, FALSE);
1894         g_list_free (visible_columns);
1895     }
1896 
1897     if (ret != NULL)
1898     {
1899         return ret;
1900     }
1901 
1902     return caja_file_is_in_trash (file) ?
1903            g_strdupv ((gchar **) default_trash_visible_columns) :
1904            g_strdupv (default_visible_columns_auto_value);
1905 }
1906 
1907 static char **
get_column_order(FMListView * list_view)1908 get_column_order (FMListView *list_view)
1909 {
1910     CajaFile *file;
1911     GList *column_order;
1912     char **ret;
1913 
1914     ret = NULL;
1915 
1916     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
1917 
1918     column_order = caja_file_get_metadata_list
1919                    (file,
1920                     CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER);
1921 
1922     if (column_order)
1923     {
1924         GPtrArray *res;
1925         GList *l;
1926 
1927         res = g_ptr_array_new ();
1928         for (l = column_order; l != NULL; l = l->next)
1929         {
1930             g_ptr_array_add (res, l->data);
1931         }
1932         g_ptr_array_add (res, NULL);
1933 
1934         ret = (char **) g_ptr_array_free (res, FALSE);
1935         g_list_free (column_order);
1936     }
1937 
1938     if (ret != NULL)
1939     {
1940         return ret;
1941     }
1942 
1943     return caja_file_is_in_trash (file) ?
1944            g_strdupv ((gchar **) default_trash_columns_order) :
1945            g_strdupv (default_column_order_auto_value);
1946 }
1947 
1948 static void
set_columns_settings_from_metadata_and_preferences(FMListView * list_view)1949 set_columns_settings_from_metadata_and_preferences (FMListView *list_view)
1950 {
1951     char **column_order;
1952     char **visible_columns;
1953 
1954     column_order = get_column_order (list_view);
1955     visible_columns = get_visible_columns (list_view);
1956 
1957     apply_columns_settings (list_view, column_order, visible_columns);
1958 
1959     g_strfreev (column_order);
1960     g_strfreev (visible_columns);
1961 }
1962 
1963 static void
set_sort_order_from_metadata_and_preferences(FMListView * list_view)1964 set_sort_order_from_metadata_and_preferences (FMListView *list_view)
1965 {
1966     char *sort_attribute;
1967     int sort_column_id;
1968     CajaFile *file;
1969     gboolean sort_reversed, default_sort_reversed;
1970     const gchar *default_sort_order;
1971 
1972     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
1973     sort_attribute = caja_file_get_metadata (file,
1974                      CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN,
1975                      NULL);
1976     sort_column_id = fm_list_model_get_sort_column_id_from_attribute (list_view->details->model,
1977                      g_quark_from_string (sort_attribute));
1978     g_free (sort_attribute);
1979 
1980     default_sort_order = get_default_sort_order (file, &default_sort_reversed);
1981 
1982     if (sort_column_id == -1)
1983     {
1984         sort_column_id =
1985             fm_list_model_get_sort_column_id_from_attribute (list_view->details->model,
1986                     g_quark_from_string (default_sort_order));
1987     }
1988 
1989     sort_reversed = caja_file_get_boolean_metadata (file,
1990                     CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED,
1991                     default_sort_reversed);
1992 
1993     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_view->details->model),
1994                                           sort_column_id,
1995                                           sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
1996 }
1997 
1998 static gboolean
list_view_changed_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1999 list_view_changed_foreach (GtkTreeModel *model,
2000                            GtkTreePath  *path,
2001                            GtkTreeIter  *iter,
2002                            gpointer      data)
2003 {
2004     gtk_tree_model_row_changed (model, path, iter);
2005     return FALSE;
2006 }
2007 
2008 static CajaZoomLevel
get_default_zoom_level(void)2009 get_default_zoom_level (void)
2010 {
2011     CajaZoomLevel default_zoom_level;
2012 
2013     default_zoom_level = default_zoom_level_auto_value;
2014 
2015     if (default_zoom_level <  CAJA_ZOOM_LEVEL_SMALLEST
2016             || CAJA_ZOOM_LEVEL_LARGEST < default_zoom_level)
2017     {
2018         default_zoom_level = CAJA_ZOOM_LEVEL_SMALL;
2019     }
2020 
2021     return default_zoom_level;
2022 }
2023 
2024 static void
set_zoom_level_from_metadata_and_preferences(FMListView * list_view)2025 set_zoom_level_from_metadata_and_preferences (FMListView *list_view)
2026 {
2027     if (fm_directory_view_supports_zooming (FM_DIRECTORY_VIEW (list_view)))
2028     {
2029         CajaFile *file;
2030         int level;
2031 
2032         file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (list_view));
2033         level = caja_file_get_integer_metadata (file,
2034                                                 CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL,
2035                                                 get_default_zoom_level ());
2036         fm_list_view_set_zoom_level (list_view, level, TRUE);
2037 
2038         /* updated the rows after updating the font size */
2039         gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
2040                                 list_view_changed_foreach, NULL);
2041     }
2042 }
2043 
2044 static void
fm_list_view_begin_loading(FMDirectoryView * view)2045 fm_list_view_begin_loading (FMDirectoryView *view)
2046 {
2047     FMListView *list_view;
2048 
2049     list_view = FM_LIST_VIEW (view);
2050 
2051     set_sort_order_from_metadata_and_preferences (list_view);
2052     set_zoom_level_from_metadata_and_preferences (list_view);
2053     set_columns_settings_from_metadata_and_preferences (list_view);
2054 }
2055 
2056 static void
stop_cell_editing(FMListView * list_view)2057 stop_cell_editing (FMListView *list_view)
2058 {
2059     GtkTreeViewColumn *column;
2060 
2061     /* Stop an ongoing rename to commit the name changes when the user
2062      * changes directories without exiting cell edit mode. It also prevents
2063      * the edited handler from being called on the cleared list model.
2064      */
2065     column = list_view->details->file_name_column;
2066     if (column != NULL && list_view->details->editable_widget != NULL &&
2067             GTK_IS_CELL_EDITABLE (list_view->details->editable_widget))
2068     {
2069         gtk_cell_editable_editing_done (list_view->details->editable_widget);
2070     }
2071 }
2072 
2073 static void
fm_list_view_clear(FMDirectoryView * view)2074 fm_list_view_clear (FMDirectoryView *view)
2075 {
2076     FMListView *list_view;
2077 
2078     list_view = FM_LIST_VIEW (view);
2079 
2080     if (list_view->details->model != NULL)
2081     {
2082         stop_cell_editing (list_view);
2083         fm_list_model_clear (list_view->details->model);
2084     }
2085 }
2086 
2087 static void
fm_list_view_rename_callback(CajaFile * file,GFile * result_location,GError * error,gpointer callback_data)2088 fm_list_view_rename_callback (CajaFile *file,
2089                               GFile *result_location,
2090                               GError *error,
2091                               gpointer callback_data)
2092 {
2093     FMListView *view;
2094 
2095     view = FM_LIST_VIEW (callback_data);
2096 
2097     if (view->details->renaming_file)
2098     {
2099         view->details->rename_done = TRUE;
2100 
2101         if (error != NULL)
2102         {
2103             /* If the rename failed (or was cancelled), kill renaming_file.
2104              * We won't get a change event for the rename, so otherwise
2105              * it would stay around forever.
2106              */
2107             caja_file_unref (view->details->renaming_file);
2108             view->details->renaming_file = NULL;
2109         }
2110     }
2111 
2112     g_object_unref (view);
2113 }
2114 
2115 
2116 static void
fm_list_view_file_changed(FMDirectoryView * view,CajaFile * file,CajaDirectory * directory)2117 fm_list_view_file_changed (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
2118 {
2119     FMListView *listview;
2120     GtkTreeIter iter;
2121 
2122     listview = FM_LIST_VIEW (view);
2123 
2124     fm_list_model_file_changed (listview->details->model, file, directory);
2125 
2126     if (listview->details->renaming_file != NULL &&
2127             file == listview->details->renaming_file &&
2128             listview->details->rename_done)
2129     {
2130         /* This is (probably) the result of the rename operation, and
2131          * the tree-view changes above could have resorted the list, so
2132          * scroll to the new position
2133          */
2134         if (fm_list_model_get_tree_iter_from_file (listview->details->model, file, directory, &iter))
2135         {
2136             GtkTreePath *file_path;
2137 
2138             file_path = gtk_tree_model_get_path (GTK_TREE_MODEL (listview->details->model), &iter);
2139             gtk_tree_view_scroll_to_cell (listview->details->tree_view,
2140                                           file_path, NULL,
2141                                           FALSE, 0.0, 0.0);
2142             gtk_tree_path_free (file_path);
2143         }
2144 
2145         caja_file_unref (listview->details->renaming_file);
2146         listview->details->renaming_file = NULL;
2147     }
2148 }
2149 
2150 static GtkWidget *
fm_list_view_get_background_widget(FMDirectoryView * view)2151 fm_list_view_get_background_widget (FMDirectoryView *view)
2152 {
2153     return GTK_WIDGET (view);
2154 }
2155 
2156 static void
fm_list_view_get_selection_foreach_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)2157 fm_list_view_get_selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
2158 {
2159     GList **list;
2160     CajaFile *file;
2161 
2162     list = data;
2163 
2164     gtk_tree_model_get (model, iter,
2165                         FM_LIST_MODEL_FILE_COLUMN, &file,
2166                         -1);
2167 
2168     if (file != NULL)
2169     {
2170         (* list) = g_list_prepend ((* list), file);
2171     }
2172 }
2173 
2174 static GList *
fm_list_view_get_selection(FMDirectoryView * view)2175 fm_list_view_get_selection (FMDirectoryView *view)
2176 {
2177     GList *list;
2178 
2179     list = NULL;
2180 
2181     gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view),
2182                                          fm_list_view_get_selection_foreach_func, &list);
2183 
2184     return g_list_reverse (list);
2185 }
2186 
2187 static void
fm_list_view_get_selection_for_file_transfer_foreach_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)2188 fm_list_view_get_selection_for_file_transfer_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
2189 {
2190     CajaFile *file;
2191     struct SelectionForeachData *selection_data;
2192     GtkTreeIter parent, child;
2193 
2194     selection_data = data;
2195 
2196     gtk_tree_model_get (model, iter,
2197                         FM_LIST_MODEL_FILE_COLUMN, &file,
2198                         -1);
2199 
2200     if (file != NULL)
2201     {
2202         /* If the parent folder is also selected, don't include this file in the
2203          * file operation, since that would copy it to the toplevel target instead
2204          * of keeping it as a child of the copied folder
2205          */
2206         child = *iter;
2207         while (gtk_tree_model_iter_parent (model, &parent, &child))
2208         {
2209             if (gtk_tree_selection_iter_is_selected (selection_data->selection,
2210                     &parent))
2211             {
2212                 return;
2213             }
2214             child = parent;
2215         }
2216 
2217         caja_file_ref (file);
2218         selection_data->list = g_list_prepend (selection_data->list, file);
2219     }
2220 }
2221 
2222 
2223 static GList *
fm_list_view_get_selection_for_file_transfer(FMDirectoryView * view)2224 fm_list_view_get_selection_for_file_transfer (FMDirectoryView *view)
2225 {
2226     struct SelectionForeachData selection_data;
2227 
2228     selection_data.list = NULL;
2229     selection_data.selection = gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view);
2230 
2231     gtk_tree_selection_selected_foreach (selection_data.selection,
2232                                          fm_list_view_get_selection_for_file_transfer_foreach_func, &selection_data);
2233 
2234     return g_list_reverse (selection_data.list);
2235 }
2236 
2237 
2238 
2239 
2240 static guint
fm_list_view_get_item_count(FMDirectoryView * view)2241 fm_list_view_get_item_count (FMDirectoryView *view)
2242 {
2243     g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0);
2244 
2245     return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model);
2246 }
2247 
2248 static gboolean
fm_list_view_is_empty(FMDirectoryView * view)2249 fm_list_view_is_empty (FMDirectoryView *view)
2250 {
2251     return fm_list_model_is_empty (FM_LIST_VIEW (view)->details->model);
2252 }
2253 
2254 static void
fm_list_view_end_file_changes(FMDirectoryView * view)2255 fm_list_view_end_file_changes (FMDirectoryView *view)
2256 {
2257     FMListView *list_view;
2258 
2259     list_view = FM_LIST_VIEW (view);
2260 
2261     if (list_view->details->new_selection_path)
2262     {
2263         gtk_tree_view_set_cursor (list_view->details->tree_view,
2264                                   list_view->details->new_selection_path,
2265                                   NULL, FALSE);
2266         gtk_tree_path_free (list_view->details->new_selection_path);
2267         list_view->details->new_selection_path = NULL;
2268     }
2269 }
2270 
2271 static void
fm_list_view_remove_file(FMDirectoryView * view,CajaFile * file,CajaDirectory * directory)2272 fm_list_view_remove_file (FMDirectoryView *view, CajaFile *file, CajaDirectory *directory)
2273 {
2274     GtkTreePath *path;
2275     GtkTreeIter iter;
2276     GtkTreeIter temp_iter;
2277     GtkTreeRowReference* row_reference;
2278     FMListView *list_view;
2279     GtkTreeModel* tree_model;
2280 
2281     path = NULL;
2282     row_reference = NULL;
2283     list_view = FM_LIST_VIEW (view);
2284     tree_model = GTK_TREE_MODEL(list_view->details->model);
2285 
2286     if (fm_list_model_get_tree_iter_from_file (list_view->details->model, file, directory, &iter))
2287     {
2288         GtkTreePath *file_path;
2289         GtkTreeSelection *selection;
2290 
2291         selection = gtk_tree_view_get_selection (list_view->details->tree_view);
2292         file_path = gtk_tree_model_get_path (tree_model, &iter);
2293 
2294         if (gtk_tree_selection_path_is_selected (selection, file_path))
2295         {
2296             /* get reference for next element in the list view. If the element to be deleted is the
2297              * last one, get reference to previous element. If there is only one element in view
2298              * no need to select anything.
2299             */
2300             temp_iter = iter;
2301 
2302             if (gtk_tree_model_iter_next (tree_model, &iter))
2303             {
2304                 path = gtk_tree_model_get_path (tree_model, &iter);
2305                 row_reference = gtk_tree_row_reference_new (tree_model, path);
2306             }
2307             else
2308             {
2309                 path = gtk_tree_model_get_path (tree_model, &temp_iter);
2310                 if (gtk_tree_path_prev (path))
2311                 {
2312                     row_reference = gtk_tree_row_reference_new (tree_model, path);
2313                 }
2314             }
2315             gtk_tree_path_free (path);
2316         }
2317 
2318         gtk_tree_path_free (file_path);
2319 
2320         fm_list_model_remove_file (list_view->details->model, file, directory);
2321 
2322         if (gtk_tree_row_reference_valid (row_reference))
2323         {
2324             if (list_view->details->new_selection_path)
2325             {
2326                 gtk_tree_path_free (list_view->details->new_selection_path);
2327             }
2328             list_view->details->new_selection_path = gtk_tree_row_reference_get_path (row_reference);
2329         }
2330 
2331         if (row_reference)
2332         {
2333             gtk_tree_row_reference_free (row_reference);
2334         }
2335     }
2336 
2337 
2338 }
2339 
2340 static void
fm_list_view_set_selection(FMDirectoryView * view,GList * selection)2341 fm_list_view_set_selection (FMDirectoryView *view, GList *selection)
2342 {
2343     FMListView *list_view;
2344     GtkTreeSelection *tree_selection;
2345     GList *node;
2346     GList *iters, *l;
2347     CajaFile *file = NULL;
2348 
2349     list_view = FM_LIST_VIEW (view);
2350     tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
2351 
2352     g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
2353 
2354     gtk_tree_selection_unselect_all (tree_selection);
2355 
2356     for (node = selection; node != NULL; node = node->next)
2357     {
2358         file = node->data;
2359         iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file);
2360 
2361         for (l = iters; l != NULL; l = l->next)
2362         {
2363             gtk_tree_selection_select_iter (tree_selection,
2364                                             (GtkTreeIter *)l->data);
2365         }
2366     	g_list_free_full (iters, g_free);
2367     }
2368 
2369     g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
2370     fm_directory_view_notify_selection_changed (view);
2371 }
2372 
2373 static void
fm_list_view_invert_selection(FMDirectoryView * view)2374 fm_list_view_invert_selection (FMDirectoryView *view)
2375 {
2376     FMListView *list_view;
2377     GtkTreeSelection *tree_selection;
2378     GList *node;
2379     GList *iters, *l;
2380     CajaFile *file = NULL;
2381     GList *selection = NULL;
2382 
2383     list_view = FM_LIST_VIEW (view);
2384     tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view);
2385 
2386     g_signal_handlers_block_by_func (tree_selection, list_selection_changed_callback, view);
2387 
2388     gtk_tree_selection_selected_foreach (tree_selection,
2389                                          fm_list_view_get_selection_foreach_func, &selection);
2390 
2391     gtk_tree_selection_select_all (tree_selection);
2392 
2393     for (node = selection; node != NULL; node = node->next)
2394     {
2395         file = node->data;
2396         iters = fm_list_model_get_all_iters_for_file (list_view->details->model, file);
2397 
2398         for (l = iters; l != NULL; l = l->next)
2399         {
2400             gtk_tree_selection_unselect_iter (tree_selection,
2401                                               (GtkTreeIter *)l->data);
2402         }
2403     	g_list_free_full (iters, g_free);
2404     }
2405 
2406     g_list_free (selection);
2407 
2408     g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view);
2409     fm_directory_view_notify_selection_changed (view);
2410 }
2411 
2412 static void
fm_list_view_select_all(FMDirectoryView * view)2413 fm_list_view_select_all (FMDirectoryView *view)
2414 {
2415     gtk_tree_selection_select_all (gtk_tree_view_get_selection (FM_LIST_VIEW (view)->details->tree_view));
2416 }
2417 
2418 static void
column_editor_response_callback(GtkWidget * dialog,int response_id,gpointer user_data)2419 column_editor_response_callback (GtkWidget *dialog,
2420                                  int response_id,
2421                                  gpointer user_data)
2422 {
2423     gtk_widget_destroy (GTK_WIDGET (dialog));
2424 }
2425 
2426 static void
column_chooser_changed_callback(CajaColumnChooser * chooser,FMListView * view)2427 column_chooser_changed_callback (CajaColumnChooser *chooser,
2428                                  FMListView *view)
2429 {
2430     CajaFile *file;
2431     char **visible_columns;
2432     char **column_order;
2433     GList *list;
2434     int i;
2435 
2436     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
2437 
2438     caja_column_chooser_get_settings (chooser,
2439                                       &visible_columns,
2440                                       &column_order);
2441 
2442     list = NULL;
2443     for (i = 0; visible_columns[i] != NULL; ++i)
2444     {
2445         list = g_list_prepend (list, visible_columns[i]);
2446     }
2447     list = g_list_reverse (list);
2448     caja_file_set_metadata_list (file,
2449                                  CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS,
2450                                  list);
2451     g_list_free (list);
2452 
2453     list = NULL;
2454     for (i = 0; column_order[i] != NULL; ++i)
2455     {
2456         list = g_list_prepend (list, column_order[i]);
2457     }
2458     list = g_list_reverse (list);
2459     caja_file_set_metadata_list (file,
2460                                  CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER,
2461                                  list);
2462     g_list_free (list);
2463 
2464     apply_columns_settings (view, column_order, visible_columns);
2465 
2466     g_strfreev (visible_columns);
2467     g_strfreev (column_order);
2468 }
2469 
2470 static void
column_chooser_set_from_arrays(CajaColumnChooser * chooser,FMListView * view,char ** visible_columns,char ** column_order)2471 column_chooser_set_from_arrays (CajaColumnChooser *chooser,
2472                                 FMListView *view,
2473                                 char **visible_columns,
2474                                 char **column_order)
2475 {
2476     g_signal_handlers_block_by_func
2477     (chooser, G_CALLBACK (column_chooser_changed_callback), view);
2478 
2479     caja_column_chooser_set_settings (chooser,
2480                                       visible_columns,
2481                                       column_order);
2482 
2483     g_signal_handlers_unblock_by_func
2484     (chooser, G_CALLBACK (column_chooser_changed_callback), view);
2485 }
2486 
2487 static void
column_chooser_set_from_settings(CajaColumnChooser * chooser,FMListView * view)2488 column_chooser_set_from_settings (CajaColumnChooser *chooser,
2489                                   FMListView *view)
2490 {
2491     char **visible_columns;
2492     char **column_order;
2493 
2494     visible_columns = get_visible_columns (view);
2495     column_order = get_column_order (view);
2496 
2497     column_chooser_set_from_arrays (chooser, view,
2498                                     visible_columns, column_order);
2499 
2500     g_strfreev (visible_columns);
2501     g_strfreev (column_order);
2502 }
2503 
2504 static void
column_chooser_use_default_callback(CajaColumnChooser * chooser,FMListView * view)2505 column_chooser_use_default_callback (CajaColumnChooser *chooser,
2506                                      FMListView *view)
2507 {
2508     CajaFile *file;
2509     char **default_columns;
2510     char **default_order;
2511 
2512     file = fm_directory_view_get_directory_as_file
2513            (FM_DIRECTORY_VIEW (view));
2514 
2515     caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
2516     caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
2517 
2518     /* set view values ourselves, as new metadata could not have been
2519      * updated yet.
2520      */
2521     default_columns = caja_file_is_in_trash (file) ?
2522                       g_strdupv ((gchar **) default_trash_visible_columns) :
2523                       g_strdupv (default_visible_columns_auto_value);
2524 
2525     default_order = caja_file_is_in_trash (file) ?
2526                     g_strdupv ((gchar **) default_trash_columns_order) :
2527                     g_strdupv (default_column_order_auto_value);
2528 
2529     apply_columns_settings (view, default_order, default_columns);
2530     column_chooser_set_from_arrays (chooser, view,
2531                                     default_columns, default_order);
2532 
2533     g_strfreev (default_columns);
2534     g_strfreev (default_order);
2535 }
2536 
2537 static GtkWidget *
create_column_editor(FMListView * view)2538 create_column_editor (FMListView *view)
2539 {
2540     GtkWidget *window;
2541     GtkWidget *label;
2542     GtkWidget *box;
2543     GtkWidget *column_chooser;
2544     CajaFile *file;
2545     char *str;
2546     char *name;
2547     const char *label_text;
2548 
2549     file = fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view));
2550     name = caja_file_get_display_name (file);
2551     str = g_strdup_printf (_("%s Visible Columns"), name);
2552     g_free (name);
2553 
2554     window = gtk_dialog_new ();
2555     gtk_window_set_title (GTK_WINDOW (window), str);
2556     gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))));
2557     gtk_window_set_destroy_with_parent (GTK_WINDOW (window), TRUE);
2558 
2559     eel_dialog_add_button (GTK_DIALOG (window),
2560                            _("_Close"),
2561                            "window-close",
2562                            GTK_RESPONSE_CLOSE);
2563 
2564     g_free (str);
2565     g_signal_connect (window, "response",
2566                       G_CALLBACK (column_editor_response_callback), NULL);
2567 
2568     gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
2569 
2570     box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
2571     gtk_container_set_border_width (GTK_CONTAINER (box), 12);
2572     gtk_widget_show (box);
2573     gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))), box, TRUE, TRUE, 0);
2574 
2575     label_text = _("Choose the order of information to appear in this folder:");
2576     str = g_strconcat ("<b>", label_text, "</b>", NULL);
2577     label = gtk_label_new (NULL);
2578     gtk_label_set_markup (GTK_LABEL (label), str);
2579     gtk_label_set_line_wrap (GTK_LABEL (label), FALSE);
2580     gtk_label_set_xalign (GTK_LABEL (label), 0);
2581     gtk_label_set_yalign (GTK_LABEL (label), 0);
2582     gtk_widget_show (label);
2583     gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
2584 
2585     g_free (str);
2586 
2587     column_chooser = caja_column_chooser_new (file);
2588     gtk_widget_set_margin_start (column_chooser, 12);
2589     gtk_widget_show (column_chooser);
2590     gtk_box_pack_start (GTK_BOX (box), column_chooser, TRUE, TRUE, 0);
2591 
2592     g_signal_connect (column_chooser, "changed",
2593                       G_CALLBACK (column_chooser_changed_callback),
2594                       view);
2595     g_signal_connect (column_chooser, "use_default",
2596                       G_CALLBACK (column_chooser_use_default_callback),
2597                       view);
2598 
2599     column_chooser_set_from_settings
2600     (CAJA_COLUMN_CHOOSER (column_chooser), view);
2601 
2602     return window;
2603 }
2604 
2605 static void
action_visible_columns_callback(GtkAction * action,gpointer callback_data)2606 action_visible_columns_callback (GtkAction *action,
2607                                  gpointer callback_data)
2608 {
2609     FMListView *list_view;
2610 
2611     list_view = FM_LIST_VIEW (callback_data);
2612 
2613     if (list_view->details->column_editor)
2614     {
2615         gtk_window_present (GTK_WINDOW (list_view->details->column_editor));
2616     }
2617     else
2618     {
2619         list_view->details->column_editor = create_column_editor (list_view);
2620         eel_add_weak_pointer (&list_view->details->column_editor);
2621 
2622         gtk_widget_show (list_view->details->column_editor);
2623     }
2624 }
2625 
2626 static const GtkActionEntry list_view_entries[] =
2627 {
2628     /* name, stock id */     { "Visible Columns", NULL,
2629         /* label, accelerator */   N_("Visible _Columns..."), NULL,
2630         /* tooltip */              N_("Select the columns visible in this folder"),
2631         G_CALLBACK (action_visible_columns_callback)
2632     },
2633 };
2634 
2635 static void
fm_list_view_merge_menus(FMDirectoryView * view)2636 fm_list_view_merge_menus (FMDirectoryView *view)
2637 {
2638     FMListView *list_view;
2639     GtkUIManager *ui_manager;
2640     GtkActionGroup *action_group;
2641     const char *ui;
2642 
2643     EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, merge_menus, (view));
2644 
2645     list_view = FM_LIST_VIEW (view);
2646 
2647     ui_manager = fm_directory_view_get_ui_manager (view);
2648 
2649     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
2650     action_group = gtk_action_group_new ("ListViewActions");
2651 #ifdef ENABLE_NLS
2652     gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
2653 #endif /* ENABLE_NLS */
2654     list_view->details->list_action_group = action_group;
2655     gtk_action_group_add_actions (action_group,
2656                                   list_view_entries, G_N_ELEMENTS (list_view_entries),
2657                                   list_view);
2658     G_GNUC_END_IGNORE_DEPRECATIONS;
2659 
2660     gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
2661     g_object_unref (action_group); /* owned by ui manager */
2662 
2663     ui = caja_ui_string_get ("caja-list-view-ui.xml");
2664     list_view->details->list_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
2665 
2666     list_view->details->menus_ready = TRUE;
2667 }
2668 
2669 static void
fm_list_view_unmerge_menus(FMDirectoryView * view)2670 fm_list_view_unmerge_menus (FMDirectoryView *view)
2671 {
2672     FMListView *list_view;
2673     GtkUIManager *ui_manager;
2674 
2675     list_view = FM_LIST_VIEW (view);
2676 
2677     FM_DIRECTORY_VIEW_CLASS (fm_list_view_parent_class)->unmerge_menus (view);
2678 
2679     ui_manager = fm_directory_view_get_ui_manager (view);
2680     if (ui_manager != NULL)
2681     {
2682         caja_ui_unmerge_ui (ui_manager,
2683                             &list_view->details->list_merge_id,
2684                             &list_view->details->list_action_group);
2685     }
2686 }
2687 
2688 static void
fm_list_view_update_menus(FMDirectoryView * view)2689 fm_list_view_update_menus (FMDirectoryView *view)
2690 {
2691     FMListView *list_view;
2692 
2693     list_view = FM_LIST_VIEW (view);
2694 
2695     /* don't update if the menus aren't ready */
2696     if (!list_view->details->menus_ready)
2697     {
2698         return;
2699     }
2700 
2701     EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS, update_menus, (view));
2702 }
2703 
2704 /* Reset sort criteria and zoom level to match defaults */
2705 static void
fm_list_view_reset_to_defaults(FMDirectoryView * view)2706 fm_list_view_reset_to_defaults (FMDirectoryView *view)
2707 {
2708     CajaFile *file;
2709     const gchar *default_sort_order;
2710     gboolean default_sort_reversed;
2711 
2712     file = fm_directory_view_get_directory_as_file (view);
2713 
2714     caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_COLUMN, NULL, NULL);
2715     caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_SORT_REVERSED, NULL, NULL);
2716     caja_file_set_metadata (file, CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL, NULL, NULL);
2717     caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL);
2718     caja_file_set_metadata_list (file, CAJA_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL);
2719 
2720     default_sort_order = get_default_sort_order (file, &default_sort_reversed);
2721 
2722     gtk_tree_sortable_set_sort_column_id
2723     (GTK_TREE_SORTABLE (FM_LIST_VIEW (view)->details->model),
2724      fm_list_model_get_sort_column_id_from_attribute (FM_LIST_VIEW (view)->details->model,
2725              g_quark_from_string (default_sort_order)),
2726      default_sort_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
2727 
2728     fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE);
2729     set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view));
2730 }
2731 
2732 static void
fm_list_view_scale_font_size(FMListView * view,CajaZoomLevel new_level)2733 fm_list_view_scale_font_size (FMListView *view,
2734                               CajaZoomLevel new_level)
2735 {
2736     GList *l;
2737     static gboolean first_time = TRUE;
2738     static double pango_scale[7];
2739 
2740     g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
2741                       new_level <= CAJA_ZOOM_LEVEL_LARGEST);
2742 
2743     if (first_time)
2744     {
2745         int medium;
2746         int i;
2747 
2748         first_time = FALSE;
2749         medium = CAJA_ZOOM_LEVEL_SMALLER;
2750         pango_scale[medium] = PANGO_SCALE_MEDIUM;
2751 
2752         for (i = medium; i > CAJA_ZOOM_LEVEL_SMALLEST; i--)
2753         {
2754             pango_scale[i - 1] = (1 / 1.2) * pango_scale[i];
2755         }
2756         for (i = medium; i < CAJA_ZOOM_LEVEL_LARGEST; i++)
2757         {
2758             pango_scale[i + 1] = 1.2 * pango_scale[i];
2759         }
2760     }
2761 
2762     g_object_set (G_OBJECT (view->details->file_name_cell),
2763                   "scale", pango_scale[new_level],
2764                   NULL);
2765     for (l = view->details->cells; l != NULL; l = l->next)
2766     {
2767         g_object_set (G_OBJECT (l->data),
2768                       "scale", pango_scale[new_level],
2769                       NULL);
2770     }
2771 }
2772 
2773 static void
fm_list_view_set_zoom_level(FMListView * view,CajaZoomLevel new_level,gboolean always_emit)2774 fm_list_view_set_zoom_level (FMListView *view,
2775                              CajaZoomLevel new_level,
2776                              gboolean always_emit)
2777 {
2778     int icon_size;
2779     int column;
2780 
2781     g_return_if_fail (FM_IS_LIST_VIEW (view));
2782     g_return_if_fail (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
2783                       new_level <= CAJA_ZOOM_LEVEL_LARGEST);
2784 
2785     if (view->details->zoom_level == new_level)
2786     {
2787         if (always_emit)
2788         {
2789             g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed");
2790         }
2791         return;
2792     }
2793 
2794     view->details->zoom_level = new_level;
2795     g_signal_emit_by_name (FM_DIRECTORY_VIEW(view), "zoom_level_changed");
2796 
2797     caja_file_set_integer_metadata
2798     (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)),
2799      CAJA_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL,
2800      get_default_zoom_level (),
2801      new_level);
2802 
2803     /* Select correctly scaled icons. */
2804     column = fm_list_model_get_column_id_from_zoom_level (new_level);
2805     gtk_tree_view_column_set_attributes (view->details->file_name_column,
2806                                          GTK_CELL_RENDERER (view->details->pixbuf_cell),
2807                                          "surface", column,
2808                                          NULL);
2809 
2810     /* Scale text. */
2811     fm_list_view_scale_font_size (view, new_level);
2812 
2813     /* Make all rows the same size. */
2814     icon_size = caja_get_icon_size_for_zoom_level (new_level);
2815     gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (view->details->pixbuf_cell),
2816                                       -1, icon_size);
2817 
2818     /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=641518 */
2819     gtk_tree_view_columns_autosize (view->details->tree_view);
2820 
2821     fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view));
2822 
2823     gtk_tree_model_foreach (GTK_TREE_MODEL (view->details->model), list_view_changed_foreach, NULL);
2824 }
2825 
2826 static void
fm_list_view_bump_zoom_level(FMDirectoryView * view,int zoom_increment)2827 fm_list_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
2828 {
2829     FMListView *list_view;
2830     gint new_level;
2831 
2832     g_return_if_fail (FM_IS_LIST_VIEW (view));
2833 
2834     list_view = FM_LIST_VIEW (view);
2835     new_level = list_view->details->zoom_level + zoom_increment;
2836 
2837     if (new_level >= CAJA_ZOOM_LEVEL_SMALLEST &&
2838             new_level <= CAJA_ZOOM_LEVEL_LARGEST)
2839     {
2840         fm_list_view_set_zoom_level (list_view, new_level, FALSE);
2841     }
2842 }
2843 
2844 static CajaZoomLevel
fm_list_view_get_zoom_level(FMDirectoryView * view)2845 fm_list_view_get_zoom_level (FMDirectoryView *view)
2846 {
2847     FMListView *list_view;
2848 
2849     g_return_val_if_fail (FM_IS_LIST_VIEW (view), CAJA_ZOOM_LEVEL_STANDARD);
2850 
2851     list_view = FM_LIST_VIEW (view);
2852 
2853     return list_view->details->zoom_level;
2854 }
2855 
2856 static void
fm_list_view_zoom_to_level(FMDirectoryView * view,CajaZoomLevel zoom_level)2857 fm_list_view_zoom_to_level (FMDirectoryView *view,
2858                             CajaZoomLevel zoom_level)
2859 {
2860     FMListView *list_view;
2861 
2862     g_return_if_fail (FM_IS_LIST_VIEW (view));
2863 
2864     list_view = FM_LIST_VIEW (view);
2865 
2866     fm_list_view_set_zoom_level (list_view, zoom_level, FALSE);
2867 }
2868 
2869 static void
fm_list_view_restore_default_zoom_level(FMDirectoryView * view)2870 fm_list_view_restore_default_zoom_level (FMDirectoryView *view)
2871 {
2872     FMListView *list_view;
2873 
2874     g_return_if_fail (FM_IS_LIST_VIEW (view));
2875 
2876     list_view = FM_LIST_VIEW (view);
2877 
2878     fm_list_view_set_zoom_level (list_view, get_default_zoom_level (), FALSE);
2879 }
2880 
2881 static gboolean
fm_list_view_can_zoom_in(FMDirectoryView * view)2882 fm_list_view_can_zoom_in (FMDirectoryView *view)
2883 {
2884     g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
2885 
2886     return FM_LIST_VIEW (view)->details->zoom_level	< CAJA_ZOOM_LEVEL_LARGEST;
2887 }
2888 
2889 static gboolean
fm_list_view_can_zoom_out(FMDirectoryView * view)2890 fm_list_view_can_zoom_out (FMDirectoryView *view)
2891 {
2892     g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
2893 
2894     return FM_LIST_VIEW (view)->details->zoom_level > CAJA_ZOOM_LEVEL_SMALLEST;
2895 }
2896 
2897 static void
fm_list_view_start_renaming_file(FMDirectoryView * view,CajaFile * file,gboolean select_all)2898 fm_list_view_start_renaming_file (FMDirectoryView *view,
2899                                   CajaFile *file,
2900                                   gboolean select_all)
2901 {
2902     FMListView *list_view;
2903     GtkTreeIter iter;
2904     GtkTreePath *path;
2905     gint start_offset, end_offset;
2906 
2907     list_view = FM_LIST_VIEW (view);
2908 
2909     /* Select all if we are in renaming mode already */
2910     if (list_view->details->file_name_column && list_view->details->editable_widget)
2911     {
2912         gtk_editable_select_region (
2913             GTK_EDITABLE (list_view->details->editable_widget),
2914             0,
2915             -1);
2916         return;
2917     }
2918 
2919     if (!fm_list_model_get_first_iter_for_file (list_view->details->model, file, &iter))
2920     {
2921         return;
2922     }
2923 
2924     /* Freeze updates to the view to prevent losing rename focus when the tree view updates */
2925     fm_directory_view_freeze_updates (FM_DIRECTORY_VIEW (view));
2926 
2927     path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), &iter);
2928 
2929     /* Make filename-cells editable. */
2930     g_object_set (G_OBJECT (list_view->details->file_name_cell),
2931                   "editable", TRUE,
2932                   NULL);
2933 
2934     gtk_tree_view_scroll_to_cell (list_view->details->tree_view,
2935                                   NULL,
2936                                   list_view->details->file_name_column,
2937                                   TRUE, 0.0, 0.0);
2938     gtk_tree_view_set_cursor_on_cell (list_view->details->tree_view,
2939                               path,
2940                               list_view->details->file_name_column,
2941                               GTK_CELL_RENDERER (list_view->details->file_name_cell),
2942                               TRUE);
2943 
2944     /* set cursor also triggers editing-started, where we save the editable widget */
2945     if (list_view->details->editable_widget != NULL) {
2946             eel_filename_get_rename_region (list_view->details->original_name,
2947                                             &start_offset, &end_offset);
2948 
2949             gtk_editable_select_region (GTK_EDITABLE (list_view->details->editable_widget),
2950                                         start_offset, end_offset);
2951     }
2952 
2953     gtk_tree_path_free (path);
2954 }
2955 
2956 static void
fm_list_view_click_policy_changed(FMDirectoryView * directory_view)2957 fm_list_view_click_policy_changed (FMDirectoryView *directory_view)
2958 {
2959     GdkDisplay *display;
2960     FMListView *view;
2961     GtkTreeIter iter;
2962 
2963     view = FM_LIST_VIEW (directory_view);
2964     display = gtk_widget_get_display (GTK_WIDGET (view));
2965 
2966     /* ensure that we unset the hand cursor and refresh underlined rows */
2967     if (click_policy_auto_value == CAJA_CLICK_POLICY_DOUBLE)
2968     {
2969         GtkTreeView *tree;
2970 
2971         if (view->details->hover_path != NULL)
2972         {
2973             if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
2974                                          &iter, view->details->hover_path))
2975             {
2976                 gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
2977                                             view->details->hover_path, &iter);
2978             }
2979 
2980             gtk_tree_path_free (view->details->hover_path);
2981             view->details->hover_path = NULL;
2982         }
2983 
2984         tree = view->details->tree_view;
2985 
2986         if (gtk_widget_get_realized (GTK_WIDGET (tree)))
2987         {
2988             GdkWindow *win;
2989 
2990             win = gtk_widget_get_window (GTK_WIDGET (tree));
2991             gdk_window_set_cursor (win, NULL);
2992 
2993             if (display != NULL)
2994             {
2995                 gdk_display_flush (display);
2996             }
2997         }
2998 
2999         g_clear_object (&hand_cursor);
3000 
3001     }
3002     else if (click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE)
3003     {
3004         if (hand_cursor == NULL)
3005         {
3006             hand_cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
3007         }
3008     }
3009 }
3010 
3011 static void
default_sort_order_changed_callback(gpointer callback_data)3012 default_sort_order_changed_callback (gpointer callback_data)
3013 {
3014     FMListView *list_view;
3015 
3016     list_view = FM_LIST_VIEW (callback_data);
3017 
3018     set_sort_order_from_metadata_and_preferences (list_view);
3019 }
3020 
3021 static void
default_zoom_level_changed_callback(gpointer callback_data)3022 default_zoom_level_changed_callback (gpointer callback_data)
3023 {
3024     FMListView *list_view;
3025 
3026     list_view = FM_LIST_VIEW (callback_data);
3027 
3028     set_zoom_level_from_metadata_and_preferences (list_view);
3029 }
3030 
3031 static void
default_visible_columns_changed_callback(gpointer callback_data)3032 default_visible_columns_changed_callback (gpointer callback_data)
3033 {
3034     FMListView *list_view;
3035 
3036     list_view = FM_LIST_VIEW (callback_data);
3037 
3038     set_columns_settings_from_metadata_and_preferences (list_view);
3039 }
3040 
3041 static void
default_column_order_changed_callback(gpointer callback_data)3042 default_column_order_changed_callback (gpointer callback_data)
3043 {
3044     FMListView *list_view;
3045 
3046     list_view = FM_LIST_VIEW (callback_data);
3047 
3048     set_columns_settings_from_metadata_and_preferences (list_view);
3049 }
3050 
3051 static void
fm_list_view_sort_directories_first_changed(FMDirectoryView * view)3052 fm_list_view_sort_directories_first_changed (FMDirectoryView *view)
3053 {
3054     FMListView *list_view;
3055 
3056     list_view = FM_LIST_VIEW (view);
3057 
3058     fm_list_model_set_should_sort_directories_first (list_view->details->model,
3059             fm_directory_view_should_sort_directories_first (view));
3060 }
3061 
3062 static int
fm_list_view_compare_files(FMDirectoryView * view,CajaFile * file1,CajaFile * file2)3063 fm_list_view_compare_files (FMDirectoryView *view, CajaFile *file1, CajaFile *file2)
3064 {
3065     FMListView *list_view;
3066 
3067     list_view = FM_LIST_VIEW (view);
3068     return fm_list_model_compare_func (list_view->details->model, file1, file2);
3069 }
3070 
3071 static gboolean
fm_list_view_using_manual_layout(FMDirectoryView * view)3072 fm_list_view_using_manual_layout (FMDirectoryView *view)
3073 {
3074     g_return_val_if_fail (FM_IS_LIST_VIEW (view), FALSE);
3075 
3076     return FALSE;
3077 }
3078 
3079 static void
fm_list_view_dispose(GObject * object)3080 fm_list_view_dispose (GObject *object)
3081 {
3082     FMListView *list_view;
3083 
3084     list_view = FM_LIST_VIEW (object);
3085 
3086     if (list_view->details->model)
3087     {
3088         stop_cell_editing (list_view);
3089         g_object_unref (list_view->details->model);
3090         list_view->details->model = NULL;
3091     }
3092 
3093     if (list_view->details->drag_dest)
3094     {
3095         g_object_unref (list_view->details->drag_dest);
3096         list_view->details->drag_dest = NULL;
3097     }
3098 
3099     if (list_view->details->renaming_file_activate_timeout != 0)
3100     {
3101         g_source_remove (list_view->details->renaming_file_activate_timeout);
3102         list_view->details->renaming_file_activate_timeout = 0;
3103     }
3104 
3105     if (list_view->details->clipboard_handler_id != 0)
3106     {
3107         g_signal_handler_disconnect (caja_clipboard_monitor_get (),
3108                                      list_view->details->clipboard_handler_id);
3109         list_view->details->clipboard_handler_id = 0;
3110     }
3111 
3112     G_OBJECT_CLASS (parent_class)->dispose (object);
3113 }
3114 
3115 static void
fm_list_view_finalize(GObject * object)3116 fm_list_view_finalize (GObject *object)
3117 {
3118     FMListView *list_view;
3119 
3120     list_view = FM_LIST_VIEW (object);
3121 
3122     g_free (list_view->details->original_name);
3123     list_view->details->original_name = NULL;
3124 
3125     if (list_view->details->double_click_path[0])
3126     {
3127         gtk_tree_path_free (list_view->details->double_click_path[0]);
3128     }
3129     if (list_view->details->double_click_path[1])
3130     {
3131         gtk_tree_path_free (list_view->details->double_click_path[1]);
3132     }
3133     if (list_view->details->new_selection_path)
3134     {
3135         gtk_tree_path_free (list_view->details->new_selection_path);
3136     }
3137 
3138     g_list_free (list_view->details->cells);
3139     g_hash_table_destroy (list_view->details->columns);
3140 
3141     if (list_view->details->hover_path != NULL)
3142     {
3143         gtk_tree_path_free (list_view->details->hover_path);
3144     }
3145 
3146     if (list_view->details->column_editor != NULL)
3147     {
3148         gtk_widget_destroy (list_view->details->column_editor);
3149     }
3150 
3151     g_free (list_view->details);
3152 
3153     g_signal_handlers_disconnect_by_func (caja_preferences,
3154                                           default_sort_order_changed_callback,
3155                                           list_view);
3156     g_signal_handlers_disconnect_by_func (caja_list_view_preferences,
3157                                           default_zoom_level_changed_callback,
3158                                           list_view);
3159     g_signal_handlers_disconnect_by_func (caja_list_view_preferences,
3160                                           default_visible_columns_changed_callback,
3161                                           list_view);
3162     g_signal_handlers_disconnect_by_func (caja_list_view_preferences,
3163                                           default_column_order_changed_callback,
3164                                           list_view);
3165 
3166     G_OBJECT_CLASS (parent_class)->finalize (object);
3167 }
3168 
3169 static void
fm_list_view_emblems_changed(FMDirectoryView * directory_view)3170 fm_list_view_emblems_changed (FMDirectoryView *directory_view)
3171 {
3172     g_assert (FM_IS_LIST_VIEW (directory_view));
3173 
3174     /* FIXME: This needs to update the emblems of the icons, since
3175      * relative emblems may have changed.
3176      */
3177 }
3178 
3179 static char *
fm_list_view_get_first_visible_file(CajaView * view)3180 fm_list_view_get_first_visible_file (CajaView *view)
3181 {
3182     CajaFile *file;
3183     GtkTreePath *path;
3184     GtkTreeIter iter;
3185     FMListView *list_view;
3186 
3187     list_view = FM_LIST_VIEW (view);
3188 
3189     if (gtk_tree_view_get_path_at_pos (list_view->details->tree_view,
3190                                        0, 0,
3191                                        &path, NULL, NULL, NULL))
3192     {
3193         gtk_tree_model_get_iter (GTK_TREE_MODEL (list_view->details->model),
3194                                  &iter, path);
3195 
3196         gtk_tree_path_free (path);
3197 
3198         gtk_tree_model_get (GTK_TREE_MODEL (list_view->details->model),
3199                             &iter,
3200                             FM_LIST_MODEL_FILE_COLUMN, &file,
3201                             -1);
3202         if (file)
3203         {
3204             char *uri;
3205 
3206             uri = caja_file_get_uri (file);
3207 
3208             caja_file_unref (file);
3209 
3210             return uri;
3211         }
3212     }
3213 
3214     return NULL;
3215 }
3216 
3217 static void
fm_list_view_scroll_to_file(FMListView * view,CajaFile * file)3218 fm_list_view_scroll_to_file (FMListView *view,
3219                              CajaFile *file)
3220 {
3221     GtkTreePath *path;
3222     GtkTreeIter iter;
3223 
3224     if (!fm_list_model_get_first_iter_for_file (view->details->model, file, &iter))
3225     {
3226         return;
3227     }
3228 
3229     path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->model), &iter);
3230 
3231     gtk_tree_view_scroll_to_cell (view->details->tree_view,
3232                                   path, NULL,
3233                                   TRUE, 0.0, 0.0);
3234 
3235     gtk_tree_path_free (path);
3236 }
3237 
3238 static void
list_view_scroll_to_file(CajaView * view,const char * uri)3239 list_view_scroll_to_file (CajaView *view,
3240                           const char *uri)
3241 {
3242     if (uri != NULL)
3243     {
3244         CajaFile *file;
3245 
3246         /* Only if existing, since we don't want to add the file to
3247            the directory if it has been removed since then */
3248         file = caja_file_get_existing_by_uri (uri);
3249 
3250         if (file != NULL)
3251         {
3252             fm_list_view_scroll_to_file (FM_LIST_VIEW (view), file);
3253             caja_file_unref (file);
3254         }
3255     }
3256 }
3257 
3258 static void
list_view_notify_clipboard_info(CajaClipboardMonitor * monitor,CajaClipboardInfo * info,FMListView * view)3259 list_view_notify_clipboard_info (CajaClipboardMonitor *monitor,
3260                                  CajaClipboardInfo *info,
3261                                  FMListView *view)
3262 {
3263     /* this could be called as a result of _end_loading() being
3264      * called after _dispose(), where the model is cleared.
3265      */
3266     if (view->details->model == NULL)
3267     {
3268         return;
3269     }
3270 
3271     if (info != NULL && info->cut)
3272     {
3273         fm_list_model_set_highlight_for_files (view->details->model, info->files);
3274     }
3275     else
3276     {
3277         fm_list_model_set_highlight_for_files (view->details->model, NULL);
3278     }
3279 }
3280 
3281 static void
fm_list_view_end_loading(FMDirectoryView * view,gboolean all_files_seen)3282 fm_list_view_end_loading (FMDirectoryView *view,
3283                           gboolean all_files_seen)
3284 {
3285     CajaClipboardMonitor *monitor;
3286     CajaClipboardInfo *info;
3287 
3288     monitor = caja_clipboard_monitor_get ();
3289     info = caja_clipboard_monitor_get_clipboard_info (monitor);
3290 
3291     list_view_notify_clipboard_info (monitor, info, FM_LIST_VIEW (view));
3292 }
3293 
3294 static void
real_set_is_active(FMDirectoryView * view,gboolean is_active)3295 real_set_is_active (FMDirectoryView *view,
3296                     gboolean is_active)
3297 {
3298     GtkWidget *tree_view;
3299     GdkRGBA color;
3300     GdkRGBA *c;
3301 
3302     tree_view = GTK_WIDGET (fm_list_view_get_tree_view (FM_LIST_VIEW (view)));
3303 
3304     if (is_active)
3305     {
3306         gtk_widget_override_background_color (tree_view, GTK_STATE_FLAG_NORMAL, NULL);
3307     }
3308     else
3309     {
3310         GtkStyleContext *style;
3311 
3312         style = gtk_widget_get_style_context (tree_view);
3313 
3314         gtk_style_context_get (style, GTK_STATE_FLAG_INSENSITIVE,
3315                                GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
3316                                &c, NULL);
3317         color = *c;
3318         gdk_rgba_free (c);
3319 
3320         gtk_widget_override_background_color (tree_view, GTK_STATE_FLAG_NORMAL, &color);
3321     }
3322 
3323     EEL_CALL_PARENT (FM_DIRECTORY_VIEW_CLASS,
3324                      set_is_active, (view, is_active));
3325 }
3326 
3327 static void
fm_list_view_class_init(FMListViewClass * class)3328 fm_list_view_class_init (FMListViewClass *class)
3329 {
3330     FMDirectoryViewClass *fm_directory_view_class;
3331 
3332     fm_directory_view_class = FM_DIRECTORY_VIEW_CLASS (class);
3333 
3334     G_OBJECT_CLASS (class)->dispose = fm_list_view_dispose;
3335     G_OBJECT_CLASS (class)->finalize = fm_list_view_finalize;
3336 
3337     fm_directory_view_class->add_file = fm_list_view_add_file;
3338     fm_directory_view_class->begin_loading = fm_list_view_begin_loading;
3339     fm_directory_view_class->end_loading = fm_list_view_end_loading;
3340     fm_directory_view_class->bump_zoom_level = fm_list_view_bump_zoom_level;
3341     fm_directory_view_class->can_zoom_in = fm_list_view_can_zoom_in;
3342     fm_directory_view_class->can_zoom_out = fm_list_view_can_zoom_out;
3343     fm_directory_view_class->click_policy_changed = fm_list_view_click_policy_changed;
3344     fm_directory_view_class->clear = fm_list_view_clear;
3345     fm_directory_view_class->file_changed = fm_list_view_file_changed;
3346     fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget;
3347     fm_directory_view_class->get_selection = fm_list_view_get_selection;
3348     fm_directory_view_class->get_selection_for_file_transfer = fm_list_view_get_selection_for_file_transfer;
3349     fm_directory_view_class->get_item_count = fm_list_view_get_item_count;
3350     fm_directory_view_class->is_empty = fm_list_view_is_empty;
3351     fm_directory_view_class->remove_file = fm_list_view_remove_file;
3352     fm_directory_view_class->merge_menus = fm_list_view_merge_menus;
3353     fm_directory_view_class->unmerge_menus = fm_list_view_unmerge_menus;
3354     fm_directory_view_class->update_menus = fm_list_view_update_menus;
3355     fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults;
3356     fm_directory_view_class->restore_default_zoom_level = fm_list_view_restore_default_zoom_level;
3357     fm_directory_view_class->reveal_selection = fm_list_view_reveal_selection;
3358     fm_directory_view_class->select_all = fm_list_view_select_all;
3359     fm_directory_view_class->set_selection = fm_list_view_set_selection;
3360     fm_directory_view_class->invert_selection = fm_list_view_invert_selection;
3361     fm_directory_view_class->compare_files = fm_list_view_compare_files;
3362     fm_directory_view_class->sort_directories_first_changed = fm_list_view_sort_directories_first_changed;
3363     fm_directory_view_class->start_renaming_file = fm_list_view_start_renaming_file;
3364     fm_directory_view_class->get_zoom_level = fm_list_view_get_zoom_level;
3365     fm_directory_view_class->zoom_to_level = fm_list_view_zoom_to_level;
3366     fm_directory_view_class->emblems_changed = fm_list_view_emblems_changed;
3367     fm_directory_view_class->end_file_changes = fm_list_view_end_file_changes;
3368     fm_directory_view_class->using_manual_layout = fm_list_view_using_manual_layout;
3369     fm_directory_view_class->set_is_active = real_set_is_active;
3370 
3371     eel_g_settings_add_auto_enum (caja_preferences,
3372                                   CAJA_PREFERENCES_CLICK_POLICY,
3373                                   &click_policy_auto_value);
3374     eel_g_settings_add_auto_enum (caja_preferences,
3375                                   CAJA_PREFERENCES_DEFAULT_SORT_ORDER,
3376                                   (int *) &default_sort_order_auto_value);
3377     eel_g_settings_add_auto_boolean (caja_preferences,
3378                                      CAJA_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
3379                                      &default_sort_reversed_auto_value);
3380     eel_g_settings_add_auto_enum (caja_list_view_preferences,
3381                                   CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
3382                                   (int *) &default_zoom_level_auto_value);
3383     eel_g_settings_add_auto_strv (caja_list_view_preferences,
3384                                   CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
3385                                   &default_visible_columns_auto_value);
3386     eel_g_settings_add_auto_strv (caja_list_view_preferences,
3387                                   CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
3388                                   &default_column_order_auto_value);
3389 }
3390 
3391 static const char *
fm_list_view_get_id(CajaView * view)3392 fm_list_view_get_id (CajaView *view)
3393 {
3394     return FM_LIST_VIEW_ID;
3395 }
3396 
3397 
3398 static void
fm_list_view_iface_init(CajaViewIface * iface)3399 fm_list_view_iface_init (CajaViewIface *iface)
3400 {
3401     fm_directory_view_init_view_iface (iface);
3402 
3403     iface->get_view_id = fm_list_view_get_id;
3404     iface->get_first_visible_file = fm_list_view_get_first_visible_file;
3405     iface->scroll_to_file = list_view_scroll_to_file;
3406     iface->get_title = NULL;
3407 }
3408 
3409 
3410 static void
fm_list_view_init(FMListView * list_view)3411 fm_list_view_init (FMListView *list_view)
3412 {
3413     list_view->details = g_new0 (FMListViewDetails, 1);
3414 
3415     create_and_set_up_tree_view (list_view);
3416 
3417     g_signal_connect_swapped (caja_preferences,
3418                               "changed::" CAJA_PREFERENCES_DEFAULT_SORT_ORDER,
3419                               G_CALLBACK (default_sort_order_changed_callback),
3420                               list_view);
3421     g_signal_connect_swapped (caja_preferences,
3422                               "changed::" CAJA_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
3423                               G_CALLBACK (default_sort_order_changed_callback),
3424                               list_view);
3425     g_signal_connect_swapped (caja_list_view_preferences,
3426                               "changed::" CAJA_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL,
3427                               G_CALLBACK (default_zoom_level_changed_callback),
3428                               list_view);
3429     g_signal_connect_swapped (caja_list_view_preferences,
3430                               "changed::" CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
3431                               G_CALLBACK (default_visible_columns_changed_callback),
3432                               list_view);
3433     g_signal_connect_swapped (caja_list_view_preferences,
3434                               "changed::" CAJA_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER,
3435                               G_CALLBACK (default_column_order_changed_callback),
3436                               list_view);
3437 
3438     fm_list_view_click_policy_changed (FM_DIRECTORY_VIEW (list_view));
3439 
3440     fm_list_view_sort_directories_first_changed (FM_DIRECTORY_VIEW (list_view));
3441 
3442     /* ensure that the zoom level is always set in begin_loading */
3443     list_view->details->zoom_level = CAJA_ZOOM_LEVEL_SMALLEST - 1;
3444 
3445     list_view->details->hover_path = NULL;
3446     list_view->details->clipboard_handler_id =
3447         g_signal_connect (caja_clipboard_monitor_get (),
3448                           "clipboard_info",
3449                           G_CALLBACK (list_view_notify_clipboard_info), list_view);
3450 }
3451 
3452 static CajaView *
fm_list_view_create(CajaWindowSlotInfo * slot)3453 fm_list_view_create (CajaWindowSlotInfo *slot)
3454 {
3455     FMListView *view;
3456 
3457     view = g_object_new (FM_TYPE_LIST_VIEW,
3458                          "window-slot", slot,
3459                          NULL);
3460     return CAJA_VIEW (view);
3461 }
3462 
3463 static gboolean
fm_list_view_supports_uri(const char * uri,GFileType file_type,const char * mime_type)3464 fm_list_view_supports_uri (const char *uri,
3465                            GFileType file_type,
3466                            const char *mime_type)
3467 {
3468     if (file_type == G_FILE_TYPE_DIRECTORY)
3469     {
3470         return TRUE;
3471     }
3472     if (strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0)
3473     {
3474         return TRUE;
3475     }
3476     if (g_str_has_prefix (uri, "trash:"))
3477     {
3478         return TRUE;
3479     }
3480     if (g_str_has_prefix (uri, EEL_SEARCH_URI))
3481     {
3482         return TRUE;
3483     }
3484 
3485     return FALSE;
3486 }
3487 
3488 static CajaViewInfo fm_list_view =
3489 {
3490     .id = FM_LIST_VIEW_ID,
3491     /* Translators: this is used in the view selection dropdown
3492      * of navigation windows and in the preferences dialog */
3493     .view_combo_label = N_("List View"),
3494     /* Translators: this is used in the view menu */
3495     .view_menu_label_with_mnemonic = N_("_List"),
3496     .error_label = N_("The list view encountered an error."),
3497     .startup_error_label = N_("The list view encountered an error while starting up."),
3498     .display_location_label = N_("Display this location with the list view."),
3499     .create = fm_list_view_create,
3500     .supports_uri = fm_list_view_supports_uri
3501 };
3502 
3503 void
fm_list_view_register(void)3504 fm_list_view_register (void)
3505 {
3506     fm_list_view.view_combo_label = _(fm_list_view.view_combo_label);
3507     fm_list_view.view_menu_label_with_mnemonic = _(fm_list_view.view_menu_label_with_mnemonic);
3508     fm_list_view.error_label = _(fm_list_view.error_label);
3509     fm_list_view.startup_error_label = _(fm_list_view.startup_error_label);
3510     fm_list_view.display_location_label = _(fm_list_view.display_location_label);
3511 
3512     caja_view_factory_register (&fm_list_view);
3513 }
3514 
3515 GtkTreeView*
fm_list_view_get_tree_view(FMListView * list_view)3516 fm_list_view_get_tree_view (FMListView *list_view)
3517 {
3518     return list_view->details->tree_view;
3519 }
3520