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 ¬_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