1 /*
2  *      fm-standard-view.c
3  *
4  *      Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5  *      Copyright 2012-2015 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6  *      Copyright 2013 Mamoru TASAKA <mtasaka@fedoraproject.org>
7  *
8  *      This program is free software; you can redistribute it and/or modify
9  *      it under the terms of the GNU General Public License as published by
10  *      the Free Software Foundation; either version 2 of the License, or
11  *      (at your option) any later version.
12  *
13  *      This program 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
16  *      GNU General Public License for more details.
17  *
18  *      You should have received a copy of the GNU General Public License
19  *      along with this program; if not, write to the Free Software
20  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  *      MA 02110-1301, USA.
22  */
23 
24 /**
25  * SECTION:fm-standard-view
26  * @short_description: A folder view widget based on libexo.
27  * @title: FmStandardView
28  *
29  * @include: libfm/fm-gtk.h
30  *
31  * The #FmStandardView represents view of content of a folder with
32  * support of drag & drop and other file/directory operations.
33  */
34 
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38 
39 #include <stdlib.h>
40 #include <glib/gi18n-lib.h>
41 #include "gtk-compat.h"
42 
43 #include "fm.h"
44 #include "fm-standard-view.h"
45 #include "fm-gtk-marshal.h"
46 #include "fm-cell-renderer-text.h"
47 #include "fm-cell-renderer-pixbuf.h"
48 #include "fm-gtk-utils.h"
49 
50 #include "exo/exo-icon-view.h"
51 #include "exo/exo-tree-view.h"
52 
53 #include "fm-dnd-src.h"
54 #include "fm-dnd-dest.h"
55 #include "fm-dnd-auto-scroll.h"
56 
57 struct _FmStandardView
58 {
59     GtkScrolledWindow parent;
60 
61     FmStandardViewMode mode;
62     GtkSelectionMode sel_mode;
63 
64     gboolean show_hidden;
65 
66     GtkWidget* view; /* either ExoIconView or ExoTreeView */
67     FmFolderModel* model; /* FmStandardView doesn't use abstract GtkTreeModel! */
68     FmCellRendererPixbuf* renderer_pixbuf;
69     FmCellRendererText* renderer_text;
70     guint icon_size_changed_handler;
71     guint show_full_names_handler;
72 
73     FmDndSrc* dnd_src; /* dnd source manager */
74     FmDndDest* dnd_dest; /* dnd dest manager */
75 
76     /* for very large folder update */
77     guint sel_changed_idle;
78     gboolean sel_changed_pending;
79 
80     FmFileInfoList* cached_selected_files;
81     FmPathList* cached_selected_file_paths;
82 
83     /* callbacks to creator */
84     FmFolderViewUpdatePopup update_popup;
85     FmLaunchFolderFunc open_folders;
86 
87     /* internal switches */
88     void (*set_single_click)(GtkWidget* view, gboolean single_click);
89     void (*set_auto_selection_delay)(GtkWidget* view, gint auto_selection_delay);
90     GtkTreePath* (*get_drop_path)(FmStandardView* fv, gint x, gint y);
91     void (*set_drag_dest)(FmStandardView* fv, GtkTreePath* tp);
92     void (*select_all)(GtkWidget* view);
93     void (*unselect_all)(GtkWidget* view);
94     void (*select_invert)(FmFolderModel* model, GtkWidget* view);
95     void (*select_path)(FmFolderModel* model, GtkWidget* view, GtkTreeIter* it);
96 
97     /* for columns width handling */
98     gint updated_col;
99     gboolean name_updated;
100 };
101 
102 struct _FmStandardViewClass
103 {
104     GtkScrolledWindowClass parent_class;
105 
106     /* signal handlers */
107     /* void (*column_widths_changed)(); */
108 };
109 
110 static void fm_standard_view_dispose(GObject *object);
111 
112 static void fm_standard_view_view_init(FmFolderViewInterface* iface);
113 
114 G_DEFINE_TYPE_WITH_CODE(FmStandardView, fm_standard_view, GTK_TYPE_SCROLLED_WINDOW,
115                         G_IMPLEMENT_INTERFACE(FM_TYPE_FOLDER_VIEW, fm_standard_view_view_init))
116 
117 static GList* fm_standard_view_get_selected_tree_paths(FmStandardView* fv);
118 
119 static gboolean on_standard_view_focus_in(GtkWidget* widget, GdkEventFocus* evt);
120 
121 static gboolean on_btn_pressed(GtkWidget* view, GdkEventButton* evt, FmStandardView* fv);
122 static void on_sel_changed(GObject* obj, FmStandardView* fv);
123 
124 static void on_dnd_src_data_get(FmDndSrc* ds, FmStandardView* fv);
125 
126 static void on_single_click_changed(FmConfig* cfg, FmStandardView* fv);
127 static void on_auto_selection_delay_changed(FmConfig* cfg, FmStandardView* fv);
128 static void on_big_icon_size_changed(FmConfig* cfg, FmStandardView* fv);
129 static void on_small_icon_size_changed(FmConfig* cfg, FmStandardView* fv);
130 static void on_thumbnail_size_changed(FmConfig* cfg, FmStandardView* fv);
131 
_sv_column_info_new(FmFolderModelCol col_id)132 static FmFolderViewColumnInfo* _sv_column_info_new(FmFolderModelCol col_id)
133 {
134     FmFolderViewColumnInfo* info = g_slice_new0(FmFolderViewColumnInfo);
135     info->col_id = col_id;
136     return info;
137 }
138 
_sv_column_info_free(gpointer info)139 static void _sv_column_info_free(gpointer info)
140 {
141     g_slice_free(FmFolderViewColumnInfo, info);
142 }
143 
144 /* override for GtkScrolledWindow bug - it ignores modifiers totally */
on_standard_view_scroll_event(GtkWidget * w,GdkEventScroll * evt)145 static gboolean on_standard_view_scroll_event(GtkWidget* w, GdkEventScroll* evt)
146 {
147     if ((evt->state & gtk_accelerator_get_default_mod_mask()) == 0 &&
148         GTK_WIDGET_CLASS(fm_standard_view_parent_class)->scroll_event)
149         return GTK_WIDGET_CLASS(fm_standard_view_parent_class)->scroll_event(w, evt);
150     return FALSE;
151 }
152 
fm_standard_view_class_init(FmStandardViewClass * klass)153 static void fm_standard_view_class_init(FmStandardViewClass *klass)
154 {
155     GObjectClass *g_object_class;
156     GtkWidgetClass *widget_class;
157     g_object_class = G_OBJECT_CLASS(klass);
158     g_object_class->dispose = fm_standard_view_dispose;
159     widget_class = GTK_WIDGET_CLASS(klass);
160     widget_class->focus_in_event = on_standard_view_focus_in;
161     widget_class->scroll_event = on_standard_view_scroll_event;
162 
163     fm_standard_view_parent_class = (GtkScrolledWindowClass*)g_type_class_peek(GTK_TYPE_SCROLLED_WINDOW);
164 }
165 
on_standard_view_focus_in(GtkWidget * widget,GdkEventFocus * evt)166 static gboolean on_standard_view_focus_in(GtkWidget* widget, GdkEventFocus* evt)
167 {
168     FmStandardView* fv = FM_STANDARD_VIEW(widget);
169     if( fv->view )
170     {
171         gtk_widget_grab_focus(fv->view);
172         return TRUE;
173     }
174     return FALSE;
175 }
176 
on_single_click_changed(FmConfig * cfg,FmStandardView * fv)177 static void on_single_click_changed(FmConfig* cfg, FmStandardView* fv)
178 {
179     if(fv->set_single_click)
180         fv->set_single_click(fv->view, cfg->single_click);
181 }
182 
on_auto_selection_delay_changed(FmConfig * cfg,FmStandardView * fv)183 static void on_auto_selection_delay_changed(FmConfig* cfg, FmStandardView* fv)
184 {
185     if(fv->set_auto_selection_delay)
186         fv->set_auto_selection_delay(fv->view, cfg->auto_selection_delay);
187 }
188 
on_icon_view_item_activated(ExoIconView * iv,GtkTreePath * path,FmStandardView * fv)189 static void on_icon_view_item_activated(ExoIconView* iv, GtkTreePath* path, FmStandardView* fv)
190 {
191     fm_folder_view_item_clicked(FM_FOLDER_VIEW(fv), path, FM_FV_ACTIVATED);
192 }
193 
on_tree_view_row_activated(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,FmStandardView * fv)194 static void on_tree_view_row_activated(GtkTreeView* tv, GtkTreePath* path, GtkTreeViewColumn* col, FmStandardView* fv)
195 {
196     fm_folder_view_item_clicked(FM_FOLDER_VIEW(fv), path, FM_FV_ACTIVATED);
197 }
198 
fm_standard_view_init(FmStandardView * self)199 static void fm_standard_view_init(FmStandardView *self)
200 {
201     gtk_scrolled_window_set_hadjustment((GtkScrolledWindow*)self, NULL);
202     gtk_scrolled_window_set_vadjustment((GtkScrolledWindow*)self, NULL);
203     gtk_scrolled_window_set_policy((GtkScrolledWindow*)self, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
204 
205     /* config change notifications */
206     g_signal_connect(fm_config, "changed::single_click", G_CALLBACK(on_single_click_changed), self);
207     g_signal_connect(fm_config, "changed::auto_selection_delay", G_CALLBACK(on_auto_selection_delay_changed), self);
208 
209     /* dnd support */
210     self->dnd_src = fm_dnd_src_new(NULL);
211     g_signal_connect(self->dnd_src, "data-get", G_CALLBACK(on_dnd_src_data_get), self);
212 
213     self->dnd_dest = fm_dnd_dest_new_with_handlers(NULL);
214 
215     self->mode = -1;
216     self->updated_col = -1;
217 }
218 
219 /**
220  * fm_folder_view_new
221  * @mode: initial mode of view
222  *
223  * Returns: a new #FmFolderView widget.
224  *
225  * Since: 0.1.0
226  * Deprecated: 1.0.1: Use fm_standard_view_new() instead.
227  */
fm_folder_view_new(guint mode)228 FmFolderView* fm_folder_view_new(guint mode)
229 {
230     return (FmFolderView*)fm_standard_view_new(mode, NULL, NULL);
231 }
232 
233 /**
234  * fm_standard_view_new
235  * @mode: initial mode of view
236  * @update_popup: (allow-none): callback to update context menu for files
237  * @open_folders: (allow-none): callback to open folder on activation
238  *
239  * Creates new folder view.
240  *
241  * Returns: a new #FmStandardView widget.
242  *
243  * Since: 1.0.1
244  */
fm_standard_view_new(FmStandardViewMode mode,FmFolderViewUpdatePopup update_popup,FmLaunchFolderFunc open_folders)245 FmStandardView* fm_standard_view_new(FmStandardViewMode mode,
246                                      FmFolderViewUpdatePopup update_popup,
247                                      FmLaunchFolderFunc open_folders)
248 {
249     FmStandardView* fv = (FmStandardView*)g_object_new(FM_STANDARD_VIEW_TYPE, NULL);
250     AtkObject *obj = gtk_widget_get_accessible(GTK_WIDGET(fv));
251 
252     fm_standard_view_set_mode(fv, mode);
253     fv->update_popup = update_popup;
254     fv->open_folders = open_folders;
255     atk_object_set_description(obj, _("View of folder contents"));
256     return fv;
257 }
258 
_reset_columns_widths(GtkTreeView * view)259 static void _reset_columns_widths(GtkTreeView* view)
260 {
261     GList* cols = gtk_tree_view_get_columns(view);
262     GList* l;
263 
264     for(l = cols; l; l = l->next)
265     {
266         FmFolderViewColumnInfo* info = g_object_get_qdata(l->data, fm_qdata_id);
267         if(info)
268             info->reserved1 = 0;
269     }
270     g_list_free(cols);
271 }
272 
on_row_changed(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,FmStandardView * fv)273 static void on_row_changed(GtkTreeModel *tree_model, GtkTreePath *path,
274                            GtkTreeIter *iter, FmStandardView* fv)
275 {
276     if(fv->mode == FM_FV_LIST_VIEW)
277         _reset_columns_widths(GTK_TREE_VIEW(fv->view));
278 }
279 
on_row_deleted(GtkTreeModel * tree_model,GtkTreePath * path,FmStandardView * fv)280 static void on_row_deleted(GtkTreeModel *tree_model, GtkTreePath  *path,
281                            FmStandardView* fv)
282 {
283     if(fv->mode == FM_FV_LIST_VIEW)
284         _reset_columns_widths(GTK_TREE_VIEW(fv->view));
285     /* reset tooltip - it may stick if mouse-over item was deleted,
286        see how FmCellRendererText works on that regard */
287     g_object_set(G_OBJECT(fv->view), "tooltip-text", NULL, NULL);
288 }
289 
on_row_inserted(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,FmStandardView * fv)290 static void on_row_inserted(GtkTreeModel *tree_model, GtkTreePath *path,
291                             GtkTreeIter *iter, FmStandardView* fv)
292 {
293     if(fv->mode == FM_FV_LIST_VIEW)
294         _reset_columns_widths(GTK_TREE_VIEW(fv->view));
295 }
296 
unset_model(FmStandardView * fv)297 static void unset_model(FmStandardView* fv)
298 {
299     if(fv->model)
300     {
301         FmFolderModel* model = fv->model;
302         /* g_debug("unset_model: %p, n_ref = %d", model, G_OBJECT(model)->ref_count); */
303         g_object_unref(model);
304         g_signal_handlers_disconnect_by_func(model, on_row_inserted, fv);
305         g_signal_handlers_disconnect_by_func(model, on_row_deleted, fv);
306         g_signal_handlers_disconnect_by_func(model, on_row_changed, fv);
307         fv->model = NULL;
308     }
309 }
310 
311 static void unset_view(FmStandardView* fv);
fm_standard_view_dispose(GObject * object)312 static void fm_standard_view_dispose(GObject *object)
313 {
314     FmStandardView *self;
315     g_return_if_fail(object != NULL);
316     g_return_if_fail(FM_IS_STANDARD_VIEW(object));
317     self = (FmStandardView*)object;
318     /* g_debug("fm_standard_view_dispose: %p", self); */
319 
320     unset_model(self);
321 
322     if(G_LIKELY(self->view))
323         unset_view(self);
324 
325     if(self->renderer_pixbuf)
326     {
327         g_object_unref(self->renderer_pixbuf);
328         self->renderer_pixbuf = NULL;
329     }
330 
331     if(self->renderer_text)
332     {
333         g_object_unref(self->renderer_text);
334         self->renderer_text = NULL;
335     }
336 
337     if(self->cached_selected_files)
338     {
339         fm_file_info_list_unref(self->cached_selected_files);
340         self->cached_selected_files = NULL;
341     }
342 
343     if(self->cached_selected_file_paths)
344     {
345         fm_path_list_unref(self->cached_selected_file_paths);
346         self->cached_selected_file_paths = NULL;
347     }
348 
349     if(self->dnd_src)
350     {
351         g_signal_handlers_disconnect_by_func(self->dnd_src, on_dnd_src_data_get, self);
352         g_object_unref(self->dnd_src);
353         self->dnd_src = NULL;
354     }
355     if(self->dnd_dest)
356     {
357         g_object_unref(self->dnd_dest);
358         self->dnd_dest = NULL;
359     }
360 
361     g_signal_handlers_disconnect_by_func(fm_config, on_single_click_changed, object);
362     g_signal_handlers_disconnect_by_func(fm_config, on_auto_selection_delay_changed, object);
363 
364     if(self->sel_changed_idle)
365     {
366         g_source_remove(self->sel_changed_idle);
367         self->sel_changed_idle = 0;
368     }
369 
370     if(self->icon_size_changed_handler)
371     {
372         g_signal_handler_disconnect(fm_config, self->icon_size_changed_handler);
373         self->icon_size_changed_handler = 0;
374     }
375     if(self->show_full_names_handler)
376     {
377         g_signal_handler_disconnect(fm_config, self->show_full_names_handler);
378         self->show_full_names_handler = 0;
379     }
380     (* G_OBJECT_CLASS(fm_standard_view_parent_class)->dispose)(object);
381 }
382 
set_icon_size(FmStandardView * fv,guint icon_size)383 static void set_icon_size(FmStandardView* fv, guint icon_size)
384 {
385     FmCellRendererPixbuf* render = fv->renderer_pixbuf;
386 
387     fm_cell_renderer_pixbuf_set_fixed_size(render, icon_size, icon_size);
388 
389     if(!fv->model)
390         return;
391 
392     fm_folder_model_set_icon_size(fv->model, icon_size);
393 
394     if( fv->mode != FM_FV_LIST_VIEW ) /* this is an ExoIconView */
395     {
396         /* set row spacing in range 2...12 pixels */
397         gint c_size = MIN(12, 2 + icon_size / 8);
398         exo_icon_view_set_row_spacing(EXO_ICON_VIEW(fv->view), c_size);
399     }
400 }
401 
on_big_icon_size_changed(FmConfig * cfg,FmStandardView * fv)402 static void on_big_icon_size_changed(FmConfig* cfg, FmStandardView* fv)
403 {
404     guint item_width = cfg->big_icon_size + 40;
405     /* reset ExoIconView item text sizes */
406     g_object_set((GObject*)fv->renderer_text, "wrap-width", item_width, NULL);
407     set_icon_size(fv, cfg->big_icon_size);
408 }
409 
on_small_icon_size_changed(FmConfig * cfg,FmStandardView * fv)410 static void on_small_icon_size_changed(FmConfig* cfg, FmStandardView* fv)
411 {
412     set_icon_size(fv, cfg->small_icon_size);
413 }
414 
on_thumbnail_size_changed(FmConfig * cfg,FmStandardView * fv)415 static void on_thumbnail_size_changed(FmConfig* cfg, FmStandardView* fv)
416 {
417     guint item_width = MAX(cfg->thumbnail_size, 96);
418     /* reset ExoIconView item text sizes */
419     g_object_set((GObject*)fv->renderer_text, "wrap-width", item_width, NULL);
420     /* FIXME: thumbnail and icons should have different sizes */
421     /* maybe a separate API: fm_folder_model_set_thumbnail_size() */
422     set_icon_size(fv, cfg->thumbnail_size);
423 }
424 
on_show_full_names_changed(FmConfig * cfg,FmStandardView * fv)425 static void on_show_full_names_changed(FmConfig* cfg, FmStandardView* fv)
426 {
427     gint font_height;
428 
429     g_return_if_fail(fv->renderer_text);
430 
431     if (fm_config->show_full_names)
432         font_height = 0;
433     else
434     {
435         /* calculate text row size */
436         PangoContext *pc = gtk_widget_get_pango_context((GtkWidget*)fv);
437         PangoFontMetrics *metrics = pango_context_get_metrics(pc, NULL, NULL);
438 
439         font_height = (pango_font_metrics_get_ascent(metrics)
440                        + pango_font_metrics_get_descent(metrics)) / PANGO_SCALE + 1;
441         pango_font_metrics_unref(metrics);
442     }
443     if(fv->mode == FM_FV_ICON_VIEW)
444         font_height *= 3;
445     else /* thumbnail view */
446         font_height *= 5;
447     g_object_set((GObject*)fv->renderer_text, "max-height", font_height, NULL);
448     /* we cannot use gtk_widget_queue_resize() since ExoIconView does not
449        recalculate sizes on that, therefore we do a little trick here:
450        we reset all attributes we set before enforcing it to relayout */
451     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(fv->view),
452                                    GTK_CELL_RENDERER(fv->renderer_text),
453                                    "text", FM_FOLDER_MODEL_COL_NAME, NULL);
454 }
455 
set_drag_dest_list_item(FmStandardView * fv,GtkTreePath * tp)456 static void set_drag_dest_list_item(FmStandardView* fv, GtkTreePath* tp)
457 {
458     gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(fv->view), tp,
459                                     GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
460 }
461 
set_drag_dest_icon_item(FmStandardView * fv,GtkTreePath * tp)462 static void set_drag_dest_icon_item(FmStandardView* fv, GtkTreePath* tp)
463 {
464     exo_icon_view_set_drag_dest_item(EXO_ICON_VIEW(fv->view), tp, EXO_ICON_VIEW_DROP_INTO);
465 }
466 
get_drop_path_list_view(FmStandardView * fv,gint x,gint y)467 static GtkTreePath* get_drop_path_list_view(FmStandardView* fv, gint x, gint y)
468 {
469     GtkTreePath* tp = NULL;
470     GtkTreeViewColumn* col;
471 
472     gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(fv->view), x, y, &x, &y);
473     if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(fv->view), x, y, &tp, &col, NULL, NULL))
474     {
475         if(gtk_tree_view_column_get_sort_column_id(col)!=FM_FOLDER_MODEL_COL_NAME)
476         {
477             gtk_tree_path_free(tp);
478             tp = NULL;
479         }
480     }
481     return tp;
482 }
483 
get_drop_path_icon_view(FmStandardView * fv,gint x,gint y)484 static GtkTreePath* get_drop_path_icon_view(FmStandardView* fv, gint x, gint y)
485 {
486     GtkTreePath* tp;
487 
488     tp = exo_icon_view_get_path_at_pos(EXO_ICON_VIEW(fv->view), x, y);
489     return tp;
490 }
491 
on_drag_motion(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,guint time,FmStandardView * fv)492 static gboolean on_drag_motion(GtkWidget *dest_widget,
493                                  GdkDragContext *drag_context,
494                                  gint x,
495                                  gint y,
496                                  guint time,
497                                  FmStandardView* fv)
498 {
499     gboolean ret;
500     GdkDragAction action = 0;
501     GdkAtom target = fm_dnd_dest_find_target(fv->dnd_dest, drag_context);
502 
503     if(target == GDK_NONE)
504         return FALSE;
505 
506     ret = FALSE;
507     /* files are being dragged */
508     if(fm_dnd_dest_is_target_supported(fv->dnd_dest, target))
509     {
510         GtkTreePath* tp = fv->get_drop_path(fv, x, y);
511         if(tp)
512         {
513             GtkTreeIter it;
514             if(gtk_tree_model_get_iter(GTK_TREE_MODEL(fv->model), &it, tp))
515             {
516                 FmFileInfo* fi;
517                 gtk_tree_model_get(GTK_TREE_MODEL(fv->model), &it, FM_FOLDER_MODEL_COL_INFO, &fi, -1);
518                 fm_dnd_dest_set_dest_file(fv->dnd_dest, fi);
519             }
520         }
521         else
522         {
523             FmFolderModel* model = fv->model;
524             if (model)
525             {
526                 FmFolder* folder = fm_folder_model_get_folder(model);
527                 fm_dnd_dest_set_dest_file(fv->dnd_dest, fm_folder_get_info(folder));
528             }
529             else
530                 fm_dnd_dest_set_dest_file(fv->dnd_dest, NULL);
531         }
532         action = fm_dnd_dest_get_default_action(fv->dnd_dest, drag_context, target);
533         ret = action != 0;
534         fv->set_drag_dest(fv, ret ? tp : NULL);
535         if (tp)
536             gtk_tree_path_free(tp);
537     }
538     gdk_drag_status(drag_context, action, time);
539 
540     return ret;
541 }
542 
create_icon_view(FmStandardView * fv,GList * sels)543 static inline void create_icon_view(FmStandardView* fv, GList* sels)
544 {
545     GList *l;
546     GtkCellRenderer* render;
547     FmFolderModel* model = fv->model;
548     int icon_size = 0, item_width, font_height;
549 
550     fv->view = exo_icon_view_new();
551 
552     if(fv->renderer_pixbuf)
553         g_object_unref(fv->renderer_pixbuf);
554     fv->renderer_pixbuf = g_object_ref_sink(fm_cell_renderer_pixbuf_new());
555     render = (GtkCellRenderer*)fv->renderer_pixbuf;
556 
557     g_object_set((GObject*)render, "follow-state", TRUE, NULL );
558     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(fv->view), render, TRUE);
559     gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(fv->view), render, "pixbuf", FM_FOLDER_MODEL_COL_ICON );
560     gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(fv->view), render, "info", FM_FOLDER_MODEL_COL_INFO );
561 
562     if(fv->mode == FM_FV_COMPACT_VIEW) /* compact view */
563     {
564         fv->icon_size_changed_handler = g_signal_connect(fm_config, "changed::small_icon_size", G_CALLBACK(on_small_icon_size_changed), fv);
565         icon_size = fm_config->small_icon_size;
566         fm_cell_renderer_pixbuf_set_fixed_size(fv->renderer_pixbuf, icon_size, icon_size);
567         if(model)
568             fm_folder_model_set_icon_size(model, icon_size);
569 
570         render = fm_cell_renderer_text_new();
571         g_object_set((GObject*)render,
572                      "xalign", 1.0, /* FIXME: why this needs to be 1.0? */
573                      "yalign", 0.5,
574                      NULL );
575         exo_icon_view_set_layout_mode( (ExoIconView*)fv->view, EXO_ICON_VIEW_LAYOUT_COLS );
576         exo_icon_view_set_orientation( (ExoIconView*)fv->view, GTK_ORIENTATION_HORIZONTAL );
577     }
578     else /* big icon view or thumbnail view */
579     {
580         if(fv->show_full_names_handler == 0)
581             fv->show_full_names_handler = g_signal_connect(fm_config, "changed::show_full_names", G_CALLBACK(on_show_full_names_changed), fv);
582         if (fm_config->show_full_names)
583             font_height = 0;
584         else
585         {
586             /* calculate text row size */
587             PangoContext *pc = gtk_widget_get_pango_context((GtkWidget*)fv);
588             PangoFontMetrics *metrics = pango_context_get_metrics(pc, NULL, NULL);
589 
590             font_height = (pango_font_metrics_get_ascent(metrics)
591                            + pango_font_metrics_get_descent(metrics)) / PANGO_SCALE + 1;
592             pango_font_metrics_unref(metrics);
593         }
594         if(fv->mode == FM_FV_ICON_VIEW)
595         {
596             fv->icon_size_changed_handler = g_signal_connect(fm_config, "changed::big_icon_size", G_CALLBACK(on_big_icon_size_changed), fv);
597             icon_size = fm_config->big_icon_size;
598             fm_cell_renderer_pixbuf_set_fixed_size(fv->renderer_pixbuf, icon_size, icon_size);
599             if(model)
600                 fm_folder_model_set_icon_size(model, icon_size);
601 
602             render = fm_cell_renderer_text_new();
603             item_width = icon_size + 40;
604             g_object_set((GObject*)render,
605                          "wrap-mode", PANGO_WRAP_WORD_CHAR,
606                          "wrap-width", item_width,
607                          "max-height", font_height * 3,
608                          "alignment", PANGO_ALIGN_CENTER,
609                          "xalign", 0.5,
610                          "yalign", 0.0,
611                          NULL );
612             exo_icon_view_set_column_spacing( (ExoIconView*)fv->view, 4 );
613         }
614         else
615         {
616             fv->icon_size_changed_handler = g_signal_connect(fm_config, "changed::thumbnail_size", G_CALLBACK(on_thumbnail_size_changed), fv);
617             icon_size = fm_config->thumbnail_size;
618             fm_cell_renderer_pixbuf_set_fixed_size(fv->renderer_pixbuf, icon_size, icon_size);
619             if(model)
620                 fm_folder_model_set_icon_size(model, icon_size);
621 
622             render = fm_cell_renderer_text_new();
623             item_width = MAX(icon_size, 96);
624             g_object_set((GObject*)render,
625                          "wrap-mode", PANGO_WRAP_WORD_CHAR,
626                          "wrap-width", item_width,
627                          "max-height", font_height * 5,
628                          "alignment", PANGO_ALIGN_CENTER,
629                          "xalign", 0.5,
630                          "yalign", 0.0,
631                          NULL );
632             exo_icon_view_set_column_spacing( (ExoIconView*)fv->view, 8 );
633         }
634     }
635     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(fv->view), render, TRUE);
636     gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(fv->view), render,
637                                 "text", FM_FOLDER_MODEL_COL_NAME );
638     if(fv->renderer_text)
639         g_object_unref(fv->renderer_text);
640     fv->renderer_text = g_object_ref_sink(render);
641     exo_icon_view_set_search_column((ExoIconView*)fv->view, FM_FOLDER_MODEL_COL_NAME);
642     g_signal_connect(fv->view, "item-activated", G_CALLBACK(on_icon_view_item_activated), fv);
643     g_signal_connect(fv->view, "selection-changed", G_CALLBACK(on_sel_changed), fv);
644     exo_icon_view_set_model((ExoIconView*)fv->view, (GtkTreeModel*)fv->model);
645     exo_icon_view_set_selection_mode((ExoIconView*)fv->view, fv->sel_mode);
646     exo_icon_view_set_single_click((ExoIconView*)fv->view, fm_config->single_click);
647     exo_icon_view_set_single_click_timeout((ExoIconView*)fv->view,
648                                            fm_config->auto_selection_delay);
649 
650     for(l = sels;l;l=l->next)
651         exo_icon_view_select_path((ExoIconView*)fv->view, l->data);
652 }
653 
_update_width_sizing(GtkTreeViewColumn * col,gint width)654 static void _update_width_sizing(GtkTreeViewColumn* col, gint width)
655 {
656     if(width > 0)
657     {
658         gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
659         gtk_tree_view_column_set_fixed_width(col, width);
660     }
661     else
662     {
663         gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
664         gtk_tree_view_column_set_resizable(col, TRUE);
665     }
666     gtk_tree_view_column_queue_resize(col);
667 }
668 
669 /* Each change will generate notify for all columns from first to last.
670  * 1) on window resizing only column Name may change - the size may grow to
671  *    fill any additional space
672  * 2) on manual column resize the resized column will change; last column
673  *    will change size too if horizontal scroll bar isn't visible */
on_column_width_changed(GtkTreeViewColumn * col,GParamSpec * pspec,FmStandardView * view)674 static void on_column_width_changed(GtkTreeViewColumn* col, GParamSpec *pspec,
675                                     FmStandardView* view)
676 {
677     FmFolderViewColumnInfo *info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
678     GList *cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(view->view));
679     int width;
680     guint pos;
681 
682     pos = g_list_index(cols, col);
683     width = gtk_tree_view_column_get_width(col);
684     /* g_debug("column width changed: [%u] id %u: %d", pos, info->col_id, width); */
685     /* use info->reserved1 as 'last width' */
686     if(width != info->reserved1)
687     {
688         if(info->col_id == FM_FOLDER_MODEL_COL_NAME)
689             view->name_updated = TRUE;
690         else if(info->reserved1 && view->updated_col < 0)
691             view->updated_col = pos;
692         info->reserved1 = width;
693     }
694     if(pos == g_list_length(cols) - 1) /* got all columns, decide what we got */
695     {
696         if(!view->name_updated && view->updated_col >= 0)
697         {
698             col = g_list_nth_data(cols, view->updated_col);
699             info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
700             if(info)
701             {
702                 info->width = info->reserved1;
703                 /* g_debug("column %u changed width to %d", info->col_id, info->width); */
704                 fm_folder_view_columns_changed(FM_FOLDER_VIEW(view));
705             }
706         }
707         /* FIXME: how to detect manual change of Name mix width reliably? */
708         view->updated_col = -1;
709         view->name_updated = FALSE;
710     }
711     g_list_free(cols);
712 }
713 
on_column_hide(GtkMenuItem * menu_item,GtkTreeViewColumn * col)714 static void on_column_hide(GtkMenuItem* menu_item, GtkTreeViewColumn* col)
715 {
716     GtkWidget* view = gtk_tree_view_column_get_tree_view(col);
717     gtk_tree_view_remove_column(GTK_TREE_VIEW(view), col);
718     fm_folder_view_columns_changed(FM_FOLDER_VIEW(gtk_widget_get_parent(view)));
719 }
720 
on_column_move_left(GtkMenuItem * menu_item,GtkTreeViewColumn * col)721 static void on_column_move_left(GtkMenuItem* menu_item, GtkTreeViewColumn* col)
722 {
723     GtkTreeView* view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(col));
724     GList* list, *l;
725 
726     list = gtk_tree_view_get_columns(view);
727     l = g_list_find(list, col);
728     if(l && l->prev)
729     {
730         gtk_tree_view_move_column_after(view, col,
731                                         l->prev->prev ? l->prev->prev->data : NULL);
732         fm_folder_view_columns_changed(FM_FOLDER_VIEW(gtk_widget_get_parent(GTK_WIDGET(view))));
733     }
734     g_list_free(list);
735 }
736 
on_column_move_right(GtkMenuItem * menu_item,GtkTreeViewColumn * col)737 static void on_column_move_right(GtkMenuItem* menu_item, GtkTreeViewColumn* col)
738 {
739     GtkTreeView* view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(col));
740     GList* list, *l;
741 
742     list = gtk_tree_view_get_columns(view);
743     l = g_list_find(list, col);
744     if(l && l->next)
745     {
746         gtk_tree_view_move_column_after(view, col, l->next->data);
747         fm_folder_view_columns_changed(FM_FOLDER_VIEW(gtk_widget_get_parent(GTK_WIDGET(view))));
748     }
749     g_list_free(list);
750 }
751 
752 static GtkTreeViewColumn* create_list_view_column(FmStandardView* fv, FmFolderViewColumnInfo *set);
753 
on_column_add(GtkMenuItem * menu_item,GtkTreeViewColumn * col)754 static void on_column_add(GtkMenuItem* menu_item, GtkTreeViewColumn* col)
755 {
756     GtkWidget *view = gtk_tree_view_column_get_tree_view(col);
757     GtkWidget *fv = gtk_widget_get_parent(view);
758     GtkTreeViewColumn *new_col;
759     FmFolderViewColumnInfo info;
760     g_return_if_fail(FM_IS_STANDARD_VIEW(fv));
761     memset(&info, 0, sizeof(info));
762     info.col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "col_id"));
763     new_col = create_list_view_column((FmStandardView*)fv, &info);
764     if(new_col) /* skip it if failed */
765     {
766         gtk_tree_view_move_column_after(GTK_TREE_VIEW(view), new_col, col);
767         fm_folder_view_columns_changed(FM_FOLDER_VIEW(fv));
768     }
769 }
770 
on_column_auto_adjust(GtkMenuItem * menu_item,GtkTreeViewColumn * col)771 static void on_column_auto_adjust(GtkMenuItem* menu_item, GtkTreeViewColumn* col)
772 {
773     FmFolderViewColumnInfo* info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
774     info->width = 0;
775     info->reserved1 = 0;
776     _update_width_sizing(col, 0);
777     /* g_debug("auto sizing column %u", info->col_id); */
778     fm_folder_view_columns_changed(FM_FOLDER_VIEW(gtk_widget_get_parent(gtk_tree_view_column_get_tree_view(col))));
779 }
780 
781 /* FIXME: add support for 'list_view_size_units' config setting:
782    - on FM_FOLDER_MODEL_COL_SIZE add submenu with list of available values
783    - on click that submenu item change the setting, emit signal, queue reload */
on_column_button_press_event(GtkWidget * button,GdkEventButton * event,GtkTreeViewColumn * col)784 static gboolean on_column_button_press_event(GtkWidget *button,
785                                              GdkEventButton *event,
786                                              GtkTreeViewColumn* col)
787 {
788     if(event->button == 3)
789     {
790         GtkWidget* view = gtk_tree_view_column_get_tree_view(col);
791         GtkWidget* fv = gtk_widget_get_parent(view);
792         GList *columns, *l;
793         GSList *cols_list, *ld;
794         GtkMenuShell* menu;
795         GtkWidget* menu_item;
796         const char* label;
797         char* menu_item_label;
798         FmFolderViewColumnInfo* info;
799         guint i;
800 
801         g_return_val_if_fail(FM_IS_STANDARD_VIEW(fv), FALSE);
802 
803         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
804         l = g_list_find(columns, col);
805         if(l == NULL)
806         {
807             g_warning("column not found in GtkTreeView");
808             g_list_free(columns);
809             return FALSE;
810         }
811 
812         menu = GTK_MENU_SHELL(gtk_menu_new());
813         /* destroy the menu when selection is done. */
814         g_signal_connect(menu, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL);
815 
816         info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
817         label = fm_folder_model_col_get_title(FM_STANDARD_VIEW(fv)->model, info->col_id);
818         menu_item_label = g_strdup_printf(_("_Hide %s"), label);
819         menu_item = gtk_menu_item_new_with_mnemonic(menu_item_label);
820         g_free(menu_item_label);
821         gtk_menu_shell_append(menu, menu_item);
822         g_signal_connect(menu_item, "activate", G_CALLBACK(on_column_hide), col);
823         if(info->col_id == FM_FOLDER_MODEL_COL_NAME) /* Name is immutable */
824             gtk_widget_set_sensitive(menu_item, FALSE);
825 
826         menu_item = gtk_menu_item_new_with_mnemonic(_("_Move Left"));
827         gtk_menu_shell_append(menu, menu_item);
828         g_signal_connect(menu_item, "activate", G_CALLBACK(on_column_move_left), col);
829         if(NULL == l->prev) /* the left most column */
830             gtk_widget_set_sensitive(menu_item, FALSE);
831 
832         menu_item = gtk_menu_item_new_with_mnemonic(_("Move _Right"));
833         gtk_menu_shell_append(menu, menu_item);
834         g_signal_connect(menu_item, "activate", G_CALLBACK(on_column_move_right), col);
835         if(NULL == l->next) /* the right most column */
836             gtk_widget_set_sensitive(menu_item, FALSE);
837         g_list_free(columns);
838 
839         /* create list of missing columns for 'Add' submenu */
840         cols_list = fm_folder_view_get_columns(FM_FOLDER_VIEW(fv));
841         menu_item_label = NULL; /* mark for below */
842         for(i = 0; fm_folder_model_col_is_valid(i); i++)
843         {
844             label = fm_folder_model_col_get_title(FM_STANDARD_VIEW(fv)->model, i);
845             if(!label)
846                 continue;
847             for(ld = cols_list; ld; ld = ld->next)
848                 if(((FmFolderViewColumnInfo*)ld->data)->col_id == i)
849                     break;
850             /* if the column is already in the folder view, don't add it to the menu */
851             if(ld)
852                 continue;
853             if(menu_item_label == NULL)
854                 gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
855             menu_item_label = g_strdup_printf(_("Show %s"), label);
856             menu_item = gtk_menu_item_new_with_label(menu_item_label);
857             g_object_set_data(G_OBJECT(menu_item), "col_id", GINT_TO_POINTER(i));
858             g_signal_connect(menu_item, "activate", G_CALLBACK(on_column_add), col);
859             g_free(menu_item_label);
860             gtk_menu_shell_append(menu, menu_item);
861         }
862         g_slist_free(cols_list);
863 
864         if(info->width > 0 && info->col_id != FM_FOLDER_MODEL_COL_NAME)
865         {
866             gtk_menu_shell_append(menu, gtk_separator_menu_item_new());
867             menu_item = gtk_menu_item_new_with_mnemonic(_("_Forget Width"));
868             gtk_menu_shell_append(menu, menu_item);
869             g_signal_connect(menu_item, "activate", G_CALLBACK(on_column_auto_adjust), col);
870         }
871 
872         gtk_widget_show_all(GTK_WIDGET(menu));
873         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
874         return TRUE;
875     }
876     else if(event->button == 1)
877     {
878         GtkWidget* view = gtk_tree_view_column_get_tree_view(col);
879         GtkWidget* fv = gtk_widget_get_parent(view);
880         FmFolderViewColumnInfo* info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
881         g_return_val_if_fail(FM_IS_STANDARD_VIEW(fv), FALSE);
882         return !fm_folder_model_col_is_sortable(FM_STANDARD_VIEW(fv)->model, info->col_id);
883     }
884     return FALSE;
885 }
886 
on_column_button_released_event(GtkWidget * button,GdkEventButton * event,GtkTreeViewColumn * col)887 static gboolean on_column_button_released_event(GtkWidget *button, GdkEventButton *event,
888                                         GtkTreeViewColumn* col)
889 {
890     if(event->button == 1)
891     {
892         GtkWidget* view = gtk_tree_view_column_get_tree_view(col);
893         GtkWidget* fv = gtk_widget_get_parent(view);
894         FmFolderViewColumnInfo* info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
895         g_return_val_if_fail(FM_IS_STANDARD_VIEW(fv), FALSE);
896         return !fm_folder_model_col_is_sortable(FM_STANDARD_VIEW(fv)->model, info->col_id);
897     }
898     return FALSE;
899 }
900 
create_list_view_column(FmStandardView * fv,FmFolderViewColumnInfo * set)901 static GtkTreeViewColumn* create_list_view_column(FmStandardView* fv,
902                                                   FmFolderViewColumnInfo *set)
903 {
904     GtkTreeViewColumn* col;
905     GtkCellRenderer* render;
906     const char* title;
907     FmFolderViewColumnInfo* info;
908     GtkWidget *label;
909     FmFolderModelCol col_id;
910 
911     g_return_val_if_fail(set != NULL, NULL); /* invalid arg */
912     col_id = set->col_id;
913     title = fm_folder_model_col_get_title(fv->model, col_id);
914     g_return_val_if_fail(title != NULL, NULL); /* invalid column */
915 
916     /* g_debug("adding column id=%u", col_id); */
917     col = gtk_tree_view_column_new();
918     render = gtk_cell_renderer_text_new();
919     gtk_tree_view_column_set_title(col, title);
920     info = _sv_column_info_new(col_id);
921     info->width = set->width;
922     g_object_set_qdata_full(G_OBJECT(col), fm_qdata_id, info, _sv_column_info_free);
923 
924     switch(col_id)
925     {
926     case FM_FOLDER_MODEL_COL_NAME:
927         /* special handling for Name column */
928         gtk_tree_view_column_pack_start(col, GTK_CELL_RENDERER(fv->renderer_pixbuf), FALSE);
929         gtk_tree_view_column_set_attributes(col, GTK_CELL_RENDERER(fv->renderer_pixbuf),
930                                             "pixbuf", FM_FOLDER_MODEL_COL_ICON,
931                                             "info", FM_FOLDER_MODEL_COL_INFO, NULL);
932         g_object_set(render, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
933         gtk_tree_view_column_set_expand(col, TRUE);
934 #if GTK_CHECK_VERSION(3, 0, 0)
935         /* Workaround for column collapse issue when double-clicking separator */
936         gtk_tree_view_column_set_min_width(col, 50);
937 #endif
938         if(set->width <= 0)
939             info->width = 200;
940         break;
941     case FM_FOLDER_MODEL_COL_SIZE:
942         g_object_set(render, "xalign", 1.0, NULL);
943     default:
944         if(set->width < 0)
945             info->width = fm_folder_model_col_get_default_width(fv->model, col_id);
946     }
947     _update_width_sizing(col, info->width);
948 
949     gtk_tree_view_column_pack_start(col, render, TRUE);
950     gtk_tree_view_column_set_attributes(col, render, "text", col_id, NULL);
951     gtk_tree_view_column_set_resizable(col, TRUE);
952     /* Unfortunately if we don't set it sortable we cannot right-click it too
953     if(fm_folder_model_col_is_sortable(fv->model, col_id)) */
954         gtk_tree_view_column_set_sort_column_id(col, col_id);
955     gtk_tree_view_append_column(GTK_TREE_VIEW(fv->view), col);
956     if(G_UNLIKELY(col_id == FM_FOLDER_MODEL_COL_NAME))
957         /* only this column is activable */
958         exo_tree_view_set_activable_column((ExoTreeView*)fv->view, col);
959 
960     g_signal_connect(col, "notify::width", G_CALLBACK(on_column_width_changed), fv);
961 
962 #if GTK_CHECK_VERSION(3, 0, 0)
963     label = gtk_tree_view_column_get_button(col);
964 #else
965     /* a little trick to fetch header button, taken from KIWI python library */
966     label = gtk_label_new(title);
967     gtk_widget_show(label);
968     gtk_tree_view_column_set_widget(col, label);
969     label = gtk_tree_view_column_get_widget(col);
970     while(label && !GTK_IS_BUTTON(label))
971         label = gtk_widget_get_parent(label);
972 #endif
973     if(label)
974     {
975         /* disable left-click handling for non-sortable columns */
976         g_signal_connect(label, "button-press-event",
977                          G_CALLBACK(on_column_button_press_event), col);
978         /* handle right-click on column header */
979         g_signal_connect(label, "button-release-event",
980                          G_CALLBACK(on_column_button_released_event), col);
981         /* FIXME: how to disconnect it later? */
982     }
983 
984     return col;
985 }
986 
_check_tree_columns_defaults(FmStandardView * fv)987 static void _check_tree_columns_defaults(FmStandardView* fv)
988 {
989     const FmFolderViewColumnInfo cols[] = {
990         {FM_FOLDER_MODEL_COL_NAME},
991         {FM_FOLDER_MODEL_COL_DESC},
992         {FM_FOLDER_MODEL_COL_SIZE},
993         {FM_FOLDER_MODEL_COL_MTIME} };
994     GSList* cols_list = NULL;
995     GList* tree_columns;
996     guint i;
997 
998     tree_columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(fv->view));
999     if(tree_columns != NULL) /* already set */
1000     {
1001         g_list_free(tree_columns);
1002         return;
1003     }
1004     /* Set default columns to show in detailed list mode. */
1005     for(i = 0; i < G_N_ELEMENTS(cols); i++)
1006         cols_list = g_slist_append(cols_list, (gpointer)&cols[i]);
1007     fm_folder_view_set_columns(FM_FOLDER_VIEW(fv), cols_list);
1008     g_slist_free(cols_list);
1009 }
1010 
create_list_view(FmStandardView * fv,GList * sels)1011 static inline void create_list_view(FmStandardView* fv, GList* sels)
1012 {
1013     GtkTreeSelection* ts;
1014     GList *l;
1015     FmFolderModel* model = fv->model;
1016     int icon_size = 0;
1017 
1018     fv->view = exo_tree_view_new();
1019 
1020     if(fv->renderer_pixbuf)
1021         g_object_unref(fv->renderer_pixbuf);
1022     fv->renderer_pixbuf = g_object_ref_sink(fm_cell_renderer_pixbuf_new());
1023     fv->icon_size_changed_handler = g_signal_connect(fm_config, "changed::small_icon_size", G_CALLBACK(on_small_icon_size_changed), fv);
1024     icon_size = fm_config->small_icon_size;
1025     fm_cell_renderer_pixbuf_set_fixed_size(fv->renderer_pixbuf, icon_size, icon_size);
1026     if(model)
1027     {
1028         fm_folder_model_set_icon_size(model, icon_size);
1029         _check_tree_columns_defaults(fv);
1030         gtk_tree_view_set_search_column(GTK_TREE_VIEW(fv->view),
1031                                         FM_FOLDER_MODEL_COL_NAME);
1032     }
1033 
1034     gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(fv->view), TRUE);
1035     gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(fv->view), TRUE);
1036     exo_tree_view_set_single_click((ExoTreeView*)fv->view, fm_config->single_click);
1037     exo_tree_view_set_single_click_timeout((ExoTreeView*)fv->view,
1038                                            fm_config->auto_selection_delay);
1039 
1040     ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view));
1041     g_signal_connect(fv->view, "row-activated", G_CALLBACK(on_tree_view_row_activated), fv);
1042     g_signal_connect(ts, "changed", G_CALLBACK(on_sel_changed), fv);
1043     gtk_tree_view_set_model(GTK_TREE_VIEW(fv->view), GTK_TREE_MODEL(model));
1044     gtk_tree_selection_set_mode(ts, fv->sel_mode);
1045     for(l = sels;l;l=l->next)
1046         gtk_tree_selection_select_path(ts, (GtkTreePath*)l->data);
1047 }
1048 
unset_view(FmStandardView * fv)1049 static void unset_view(FmStandardView* fv)
1050 {
1051     /* these signals connected by view creators */
1052     if(fv->mode == FM_FV_LIST_VIEW)
1053     {
1054         GtkTreeSelection* ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view));
1055         g_signal_handlers_disconnect_by_func(ts, on_sel_changed, fv);
1056         g_signal_handlers_disconnect_by_func(fv->view, on_tree_view_row_activated, fv);
1057     }
1058     else
1059     {
1060         g_signal_handlers_disconnect_by_func(fv->view, on_sel_changed, fv);
1061         g_signal_handlers_disconnect_by_func(fv->view, on_icon_view_item_activated, fv);
1062     }
1063     /* these signals connected by fm_standard_view_set_mode() */
1064     g_signal_handlers_disconnect_by_func(fv->view, on_drag_motion, fv);
1065     g_signal_handlers_disconnect_by_func(fv->view, on_btn_pressed, fv);
1066 
1067     fm_dnd_unset_dest_auto_scroll(fv->view);
1068     gtk_widget_destroy(GTK_WIDGET(fv->view));
1069     fv->view = NULL;
1070 }
1071 
select_all_list_view(GtkWidget * view)1072 static void select_all_list_view(GtkWidget* view)
1073 {
1074     GtkTreeSelection * tree_sel;
1075     tree_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1076     gtk_tree_selection_select_all(tree_sel);
1077 }
1078 
unselect_all_list_view(GtkWidget * view)1079 static void unselect_all_list_view(GtkWidget* view)
1080 {
1081     GtkTreeSelection * tree_sel;
1082     tree_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1083     gtk_tree_selection_unselect_all(tree_sel);
1084 }
1085 
select_invert_list_view(FmFolderModel * model,GtkWidget * view)1086 static void select_invert_list_view(FmFolderModel* model, GtkWidget* view)
1087 {
1088     GtkTreeSelection *tree_sel;
1089     GtkTreeIter it;
1090     if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it))
1091         return;
1092     tree_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1093     do
1094     {
1095         if(gtk_tree_selection_iter_is_selected(tree_sel, &it))
1096             gtk_tree_selection_unselect_iter(tree_sel, &it);
1097         else
1098             gtk_tree_selection_select_iter(tree_sel, &it);
1099     }while( gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it ));
1100 }
1101 
select_invert_icon_view(FmFolderModel * model,GtkWidget * view)1102 static void select_invert_icon_view(FmFolderModel* model, GtkWidget* view)
1103 {
1104     GtkTreePath* path;
1105     int i, n;
1106     n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL);
1107     if(n == 0)
1108         return;
1109     path = gtk_tree_path_new_first();
1110     for( i=0; i<n; ++i, gtk_tree_path_next(path) )
1111     {
1112         if ( exo_icon_view_path_is_selected(EXO_ICON_VIEW(view), path))
1113             exo_icon_view_unselect_path(EXO_ICON_VIEW(view), path);
1114         else
1115             exo_icon_view_select_path(EXO_ICON_VIEW(view), path);
1116     }
1117     gtk_tree_path_free(path);
1118 }
1119 
select_path_list_view(FmFolderModel * model,GtkWidget * view,GtkTreeIter * it)1120 static void select_path_list_view(FmFolderModel* model, GtkWidget* view, GtkTreeIter* it)
1121 {
1122     GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1123     gtk_tree_selection_select_iter(sel, it);
1124 }
1125 
select_path_icon_view(FmFolderModel * model,GtkWidget * view,GtkTreeIter * it)1126 static void select_path_icon_view(FmFolderModel* model, GtkWidget* view, GtkTreeIter* it)
1127 {
1128     GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(model), it);
1129     if(tp)
1130     {
1131         exo_icon_view_select_path(EXO_ICON_VIEW(view), tp);
1132         gtk_tree_path_free(tp);
1133     }
1134 }
1135 
1136 /**
1137  * fm_folder_view_set_mode
1138  * @fv: a widget to apply
1139  * @mode: new mode of view
1140  *
1141  * Since: 0.1.0
1142  * Deprecated: 1.0.1: Use fm_standard_view_set_mode() instead.
1143  */
fm_folder_view_set_mode(FmFolderView * fv,guint mode)1144 void fm_folder_view_set_mode(FmFolderView* fv, guint mode)
1145 {
1146     g_return_if_fail(FM_IS_STANDARD_VIEW(fv));
1147 
1148     fm_standard_view_set_mode((FmStandardView*)fv, mode);
1149 }
1150 
1151 /**
1152  * fm_standard_view_set_mode
1153  * @fv: a widget to apply
1154  * @mode: new mode of view
1155  *
1156  * Before 1.0.1 this API had name fm_folder_view_set_mode.
1157  *
1158  * Changes current view mode for folder in @fv.
1159  *
1160  * Since: 0.1.0
1161  */
fm_standard_view_set_mode(FmStandardView * fv,FmStandardViewMode mode)1162 void fm_standard_view_set_mode(FmStandardView* fv, FmStandardViewMode mode)
1163 {
1164     if( mode != fv->mode )
1165     {
1166         GList *sels;
1167         gboolean has_focus;
1168 
1169         if( G_LIKELY(fv->view) )
1170         {
1171             has_focus = gtk_widget_has_focus(fv->view);
1172             /* preserve old selections */
1173             sels = fm_standard_view_get_selected_tree_paths(fv);
1174 
1175             unset_view(fv); /* it will destroy the fv->view widget */
1176 
1177             /* FIXME: compact view and icon view actually use the same
1178              * type of widget, ExoIconView. So it may be better to
1179              * reuse the widget when available. */
1180         }
1181         else
1182         {
1183             sels = NULL;
1184             has_focus = FALSE;
1185         }
1186 
1187         if(fv->icon_size_changed_handler)
1188         {
1189             g_signal_handler_disconnect(fm_config, fv->icon_size_changed_handler);
1190             fv->icon_size_changed_handler = 0;
1191         }
1192         if(fv->show_full_names_handler)
1193         {
1194             g_signal_handler_disconnect(fm_config, fv->show_full_names_handler);
1195             fv->show_full_names_handler = 0;
1196         }
1197 
1198         fv->mode = mode;
1199         switch(mode)
1200         {
1201         case FM_FV_COMPACT_VIEW:
1202         case FM_FV_ICON_VIEW:
1203         case FM_FV_THUMBNAIL_VIEW:
1204             create_icon_view(fv, sels);
1205             fv->set_single_click = (void(*)(GtkWidget*,gboolean))exo_icon_view_set_single_click;
1206             fv->set_auto_selection_delay = (void(*)(GtkWidget*,gint))exo_icon_view_set_single_click_timeout;
1207             fv->get_drop_path = get_drop_path_icon_view;
1208             fv->set_drag_dest = set_drag_dest_icon_item;
1209             fv->select_all = (void(*)(GtkWidget*))exo_icon_view_select_all;
1210             fv->unselect_all = (void(*)(GtkWidget*))exo_icon_view_unselect_all;
1211             fv->select_invert = select_invert_icon_view;
1212             fv->select_path = select_path_icon_view;
1213             break;
1214         case FM_FV_LIST_VIEW: /* detailed list view */
1215             create_list_view(fv, sels);
1216             fv->set_single_click = (void(*)(GtkWidget*,gboolean))exo_tree_view_set_single_click;
1217             fv->set_auto_selection_delay = (void(*)(GtkWidget*,gint))exo_tree_view_set_single_click_timeout;
1218             fv->get_drop_path = get_drop_path_list_view;
1219             fv->set_drag_dest = set_drag_dest_list_item;
1220             fv->select_all = select_all_list_view;
1221             fv->unselect_all = unselect_all_list_view;
1222             fv->select_invert = select_invert_list_view;
1223             fv->select_path = select_path_list_view;
1224         }
1225         g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL);
1226         g_list_free(sels);
1227 
1228         fm_dnd_src_set_widget(fv->dnd_src, fv->view);
1229         fm_dnd_dest_set_widget(fv->dnd_dest, fv->view);
1230         g_signal_connect_after(fv->view, "drag-motion", G_CALLBACK(on_drag_motion), fv);
1231         /* connecting it after sometimes conflicts with system configuration
1232            (bug #3559831) so we just hope here it will be handled in order
1233            of connecting, i.e. after ExoIconView or ExoTreeView handler */
1234         g_signal_connect(fv->view, "button-press-event", G_CALLBACK(on_btn_pressed), fv);
1235 
1236         fm_dnd_set_dest_auto_scroll(fv->view, gtk_scrolled_window_get_hadjustment((GtkScrolledWindow*)fv), gtk_scrolled_window_get_vadjustment((GtkScrolledWindow*)fv));
1237 
1238         gtk_widget_show(fv->view);
1239         gtk_container_add(GTK_CONTAINER(fv), fv->view);
1240 
1241         if(has_focus) /* restore the focus if needed. */
1242             gtk_widget_grab_focus(fv->view);
1243     }
1244     else
1245     {
1246         /* g_debug("same mode"); */
1247     }
1248 }
1249 
1250 /**
1251  * fm_folder_view_get_mode
1252  * @fv: a widget to inspect
1253  *
1254  * Returns: current mode of view.
1255  *
1256  * Since: 0.1.0
1257  * Deprecated: 1.0.1: Use fm_standard_view_get_mode() instead.
1258  */
fm_folder_view_get_mode(FmFolderView * fv)1259 guint fm_folder_view_get_mode(FmFolderView* fv)
1260 {
1261     g_return_val_if_fail(FM_IS_STANDARD_VIEW(fv), 0);
1262 
1263     return ((FmStandardView*)fv)->mode;
1264 }
1265 
1266 /**
1267  * fm_standard_view_get_mode
1268  * @fv: a widget to inspect
1269  *
1270  * Retrieves current view mode for folder in @fv.
1271  *
1272  * Before 1.0.1 this API had name fm_folder_view_get_mode.
1273  *
1274  * Returns: current mode of view.
1275  *
1276  * Since: 0.1.0
1277  */
fm_standard_view_get_mode(FmStandardView * fv)1278 FmStandardViewMode fm_standard_view_get_mode(FmStandardView* fv)
1279 {
1280     return fv->mode;
1281 }
1282 
fm_standard_view_set_selection_mode(FmFolderView * ffv,GtkSelectionMode mode)1283 static void fm_standard_view_set_selection_mode(FmFolderView* ffv, GtkSelectionMode mode)
1284 {
1285     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1286     if(fv->sel_mode != mode)
1287     {
1288         fv->sel_mode = mode;
1289         switch(fv->mode)
1290         {
1291         case FM_FV_LIST_VIEW:
1292         {
1293             GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view));
1294             gtk_tree_selection_set_mode(sel, mode);
1295             break;
1296         }
1297         case FM_FV_ICON_VIEW:
1298         case FM_FV_COMPACT_VIEW:
1299         case FM_FV_THUMBNAIL_VIEW:
1300             exo_icon_view_set_selection_mode(EXO_ICON_VIEW(fv->view), mode);
1301             break;
1302         }
1303     }
1304 }
1305 
fm_standard_view_get_selection_mode(FmFolderView * ffv)1306 static GtkSelectionMode fm_standard_view_get_selection_mode(FmFolderView* ffv)
1307 {
1308     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1309     return fv->sel_mode;
1310 }
1311 
fm_standard_view_set_show_hidden(FmFolderView * ffv,gboolean show)1312 static void fm_standard_view_set_show_hidden(FmFolderView* ffv, gboolean show)
1313 {
1314     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1315     fv->show_hidden = show;
1316 }
1317 
fm_standard_view_get_show_hidden(FmFolderView * ffv)1318 static gboolean fm_standard_view_get_show_hidden(FmFolderView* ffv)
1319 {
1320     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1321     return fv->show_hidden;
1322 }
1323 
1324 /* returned list should be freed with g_list_free_full(list, gtk_tree_path_free) */
fm_standard_view_get_selected_tree_paths(FmStandardView * fv)1325 static GList* fm_standard_view_get_selected_tree_paths(FmStandardView* fv)
1326 {
1327     GList *sels = NULL;
1328     switch(fv->mode)
1329     {
1330     case FM_FV_LIST_VIEW:
1331     {
1332         GtkTreeSelection* sel;
1333         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view));
1334         sels = gtk_tree_selection_get_selected_rows(sel, NULL);
1335         break;
1336     }
1337     case FM_FV_ICON_VIEW:
1338     case FM_FV_COMPACT_VIEW:
1339     case FM_FV_THUMBNAIL_VIEW:
1340         sels = exo_icon_view_get_selected_items(EXO_ICON_VIEW(fv->view));
1341         break;
1342     }
1343     return sels;
1344 }
1345 
fm_standard_view_get_selected_files(FmStandardView * fv)1346 static inline FmFileInfoList* fm_standard_view_get_selected_files(FmStandardView* fv)
1347 {
1348     /* don't generate the data again if we have it cached. */
1349     if(!fv->cached_selected_files)
1350     {
1351         GList* sels = fm_standard_view_get_selected_tree_paths(fv);
1352         GList *l, *next;
1353         if(sels)
1354         {
1355             fv->cached_selected_files = fm_file_info_list_new();
1356             for(l = sels;l;l=next)
1357             {
1358                 FmFileInfo* fi;
1359                 GtkTreeIter it;
1360                 GtkTreePath* tp = (GtkTreePath*)l->data;
1361                 gtk_tree_model_get_iter(GTK_TREE_MODEL(fv->model), &it, l->data);
1362                 gtk_tree_model_get(GTK_TREE_MODEL(fv->model), &it, FM_FOLDER_MODEL_COL_INFO, &fi, -1);
1363                 gtk_tree_path_free(tp);
1364                 next = l->next;
1365                 l->data = fm_file_info_ref( fi );
1366                 l->prev = l->next = NULL;
1367                 fm_file_info_list_push_tail_link(fv->cached_selected_files, l);
1368             }
1369         }
1370     }
1371     return fv->cached_selected_files;
1372 }
1373 
fm_standard_view_dup_selected_files(FmFolderView * ffv)1374 static FmFileInfoList* fm_standard_view_dup_selected_files(FmFolderView* ffv)
1375 {
1376     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1377     return fm_file_info_list_ref(fm_standard_view_get_selected_files(fv));
1378 }
1379 
fm_standard_view_dup_selected_file_paths(FmFolderView * ffv)1380 static FmPathList* fm_standard_view_dup_selected_file_paths(FmFolderView* ffv)
1381 {
1382     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1383     if(!fv->cached_selected_file_paths)
1384     {
1385         FmFileInfoList* files = fm_standard_view_get_selected_files(fv);
1386         if(files)
1387             fv->cached_selected_file_paths = fm_path_list_new_from_file_info_list(files);
1388         else
1389             fv->cached_selected_file_paths = NULL;
1390     }
1391     return fm_path_list_ref(fv->cached_selected_file_paths);
1392 }
1393 
fm_standard_view_count_selected_files(FmFolderView * ffv)1394 static gint fm_standard_view_count_selected_files(FmFolderView* ffv)
1395 {
1396     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1397     gint count = 0;
1398     switch(fv->mode)
1399     {
1400     case FM_FV_LIST_VIEW:
1401     {
1402         GtkTreeSelection* sel;
1403         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view));
1404         count = gtk_tree_selection_count_selected_rows(sel);
1405         break;
1406     }
1407     case FM_FV_ICON_VIEW:
1408     case FM_FV_COMPACT_VIEW:
1409     case FM_FV_THUMBNAIL_VIEW:
1410         count = exo_icon_view_count_selected_items(EXO_ICON_VIEW(fv->view));
1411         break;
1412     }
1413     return count;
1414 }
1415 
on_btn_pressed(GtkWidget * view,GdkEventButton * evt,FmStandardView * fv)1416 static gboolean on_btn_pressed(GtkWidget* view, GdkEventButton* evt, FmStandardView* fv)
1417 {
1418     GList* sels = NULL;
1419     FmFolderViewClickType type = 0;
1420     GtkTreePath* tp = NULL;
1421 
1422     if(!fv->model)
1423         return FALSE;
1424 
1425     /* FIXME: handle single click activation */
1426     if( evt->type == GDK_BUTTON_PRESS )
1427     {
1428         /* special handling for ExoIconView */
1429         if(evt->button != 1)
1430         {
1431             switch(fv->mode)
1432             {
1433             case FM_FV_ICON_VIEW:
1434             case FM_FV_COMPACT_VIEW:
1435             case FM_FV_THUMBNAIL_VIEW:
1436                 /* select the item on right click for ExoIconView */
1437                 if(exo_icon_view_get_item_at_pos(EXO_ICON_VIEW(view), evt->x, evt->y, &tp, NULL))
1438                 {
1439                     /* if the hit item is not currently selected */
1440                     if(!exo_icon_view_path_is_selected(EXO_ICON_VIEW(view), tp))
1441                     {
1442                         sels = exo_icon_view_get_selected_items(EXO_ICON_VIEW(view));
1443                         if( sels ) /* if there are selected items */
1444                         {
1445                             exo_icon_view_unselect_all(EXO_ICON_VIEW(view)); /* unselect all items */
1446                             g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL);
1447                             g_list_free(sels);
1448                         }
1449                         exo_icon_view_select_path(EXO_ICON_VIEW(view), tp);
1450                         exo_icon_view_set_cursor(EXO_ICON_VIEW(view), tp, NULL, FALSE);
1451                     }
1452                 }
1453                 break;
1454             case FM_FV_LIST_VIEW:
1455               if(evt->window == gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view)))
1456               {
1457                 /* special handling for ExoTreeView */
1458                 /* Fix #2986834: MAJOR PROBLEM: Deletes Wrong File Frequently. */
1459                 GtkTreeViewColumn* col;
1460                 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), evt->x, evt->y, &tp, &col, NULL, NULL))
1461                 {
1462                     GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1463                     if(!gtk_tree_selection_path_is_selected(tree_sel, tp))
1464                     {
1465                         gtk_tree_selection_unselect_all(tree_sel);
1466                         if(col == exo_tree_view_get_activable_column(EXO_TREE_VIEW(view)))
1467                         {
1468                             gtk_tree_selection_select_path(tree_sel, tp);
1469                             gtk_tree_view_set_cursor(GTK_TREE_VIEW(view), tp, NULL, FALSE);
1470                         }
1471                     }
1472                 }
1473               }
1474             }
1475         }
1476 
1477         if(evt->button == 2) /* middle click */
1478             type = FM_FV_MIDDLE_CLICK;
1479         else if(evt->button == 3) /* right click */
1480             type = FM_FV_CONTEXT_MENU;
1481     }
1482 
1483     if( type != FM_FV_CLICK_NONE )
1484     {
1485         sels = fm_standard_view_get_selected_tree_paths(fv);
1486         if( sels || type == FM_FV_CONTEXT_MENU )
1487         {
1488             fm_folder_view_item_clicked(FM_FOLDER_VIEW(fv), tp, type);
1489             if(sels)
1490             {
1491                 g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL);
1492                 g_list_free(sels);
1493             }
1494         }
1495     }
1496     if(tp)
1497         gtk_tree_path_free(tp);
1498     return FALSE;
1499 }
1500 
1501 /* TODO: select files by custom func, not yet implemented */
fm_folder_view_select_custom(FmFolderView * fv,GFunc filter,gpointer user_data)1502 void fm_folder_view_select_custom(FmFolderView* fv, GFunc filter, gpointer user_data)
1503 {
1504 }
1505 
fm_standard_view_select_all(FmFolderView * ffv)1506 static void fm_standard_view_select_all(FmFolderView* ffv)
1507 {
1508     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1509     if(fv->select_all)
1510         fv->select_all(fv->view);
1511 }
1512 
fm_standard_view_unselect_all(FmFolderView * ffv)1513 static void fm_standard_view_unselect_all(FmFolderView* ffv)
1514 {
1515     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1516     if(fv->unselect_all)
1517         fv->unselect_all(fv->view);
1518 }
1519 
on_dnd_src_data_get(FmDndSrc * ds,FmStandardView * fv)1520 static void on_dnd_src_data_get(FmDndSrc* ds, FmStandardView* fv)
1521 {
1522     FmFileInfoList* files = fm_standard_view_dup_selected_files(FM_FOLDER_VIEW(fv));
1523     fm_dnd_src_set_files(ds, files);
1524     if(files)
1525         fm_file_info_list_unref(files);
1526 }
1527 
on_sel_changed_real(FmStandardView * fv)1528 static gboolean on_sel_changed_real(FmStandardView* fv)
1529 {
1530     /* clear cached selected files */
1531     if(fv->cached_selected_files)
1532     {
1533         fm_file_info_list_unref(fv->cached_selected_files);
1534         fv->cached_selected_files = NULL;
1535     }
1536     if(fv->cached_selected_file_paths)
1537     {
1538         fm_path_list_unref(fv->cached_selected_file_paths);
1539         fv->cached_selected_file_paths = NULL;
1540     }
1541     fm_folder_view_sel_changed(NULL, FM_FOLDER_VIEW(fv));
1542     fv->sel_changed_pending = FALSE;
1543     return TRUE;
1544 }
1545 
1546 /*
1547  * We limit "sel-changed" emitting here:
1548  * - if no signal was in last 200ms then signal is emitted immidiately
1549  * - if there was < 200ms since last signal then it's marked as pending
1550  *   and signal will be emitted when that 200ms timeout ends
1551  */
on_sel_changed_idle(gpointer user_data)1552 static gboolean on_sel_changed_idle(gpointer user_data)
1553 {
1554     FmStandardView* fv = (FmStandardView*)user_data;
1555     gboolean ret = FALSE;
1556 
1557     /* check if fv is destroyed already */
1558     if(g_source_is_destroyed(g_main_current_source()))
1559         goto _end;
1560     if(fv->sel_changed_pending) /* fast changing detected! continue... */
1561         ret = on_sel_changed_real(fv);
1562     fv->sel_changed_idle = 0;
1563 _end:
1564     return ret;
1565 }
1566 
on_sel_changed(GObject * obj,FmStandardView * fv)1567 static void on_sel_changed(GObject* obj, FmStandardView* fv)
1568 {
1569     if(!fv->sel_changed_idle)
1570     {
1571         fv->sel_changed_idle = gdk_threads_add_timeout_full(G_PRIORITY_HIGH_IDLE, 200,
1572                                                             on_sel_changed_idle,
1573                                                             fv, NULL);
1574         on_sel_changed_real(fv);
1575     }
1576     else
1577         fv->sel_changed_pending = TRUE;
1578 }
1579 
fm_standard_view_select_invert(FmFolderView * ffv)1580 static void fm_standard_view_select_invert(FmFolderView* ffv)
1581 {
1582     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1583     if(fv->select_invert)
1584         fv->select_invert(fv->model, fv->view);
1585 }
1586 
fm_standard_view_get_folder(FmFolderView * ffv)1587 static FmFolder* fm_standard_view_get_folder(FmFolderView* ffv)
1588 {
1589     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1590     return fv->model ? fm_folder_model_get_folder(fv->model) : NULL;
1591 }
1592 
fm_standard_view_select_file_path(FmFolderView * ffv,FmPath * path)1593 static void fm_standard_view_select_file_path(FmFolderView* ffv, FmPath* path)
1594 {
1595     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1596     FmFolder* folder = fm_standard_view_get_folder(ffv);
1597     FmPath* cwd = folder ? fm_folder_get_path(folder) : NULL;
1598     if(cwd && fm_path_equal(fm_path_get_parent(path), cwd))
1599     {
1600         FmFolderModel* model = fv->model;
1601         GtkTreeIter it;
1602         if(fv->select_path &&
1603            fm_folder_model_find_iter_by_filename(model, &it, fm_path_get_basename(path)))
1604             fv->select_path(model, fv->view, &it);
1605     }
1606 }
1607 
fm_standard_view_get_custom_menu_callbacks(FmFolderView * ffv,FmFolderViewUpdatePopup * update_popup,FmLaunchFolderFunc * open_folders)1608 static void fm_standard_view_get_custom_menu_callbacks(FmFolderView* ffv,
1609         FmFolderViewUpdatePopup *update_popup, FmLaunchFolderFunc *open_folders)
1610 {
1611     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1612     *update_popup = fv->update_popup;
1613     *open_folders = fv->open_folders;
1614 }
1615 
fm_standard_view_get_model(FmFolderView * ffv)1616 static FmFolderModel* fm_standard_view_get_model(FmFolderView* ffv)
1617 {
1618     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1619     return fv->model;
1620 }
1621 
fm_standard_view_set_model(FmFolderView * ffv,FmFolderModel * model)1622 static void fm_standard_view_set_model(FmFolderView* ffv, FmFolderModel* model)
1623 {
1624     FmStandardView* fv = FM_STANDARD_VIEW(ffv);
1625     int icon_size;
1626     unset_model(fv);
1627     switch(fv->mode)
1628     {
1629     case FM_FV_LIST_VIEW:
1630         _check_tree_columns_defaults(fv);
1631         if(model)
1632         {
1633             icon_size = fm_config->small_icon_size;
1634             fm_folder_model_set_icon_size(model, icon_size);
1635         }
1636         gtk_tree_view_set_model(GTK_TREE_VIEW(fv->view), GTK_TREE_MODEL(model));
1637         _reset_columns_widths(GTK_TREE_VIEW(fv->view));
1638         break;
1639     case FM_FV_ICON_VIEW:
1640         icon_size = fm_config->big_icon_size;
1641         if(model)
1642             fm_folder_model_set_icon_size(model, icon_size);
1643         exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), GTK_TREE_MODEL(model));
1644         break;
1645     case FM_FV_COMPACT_VIEW:
1646         if(model)
1647         {
1648             icon_size = fm_config->small_icon_size;
1649             fm_folder_model_set_icon_size(model, icon_size);
1650         }
1651         exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), GTK_TREE_MODEL(model));
1652         break;
1653     case FM_FV_THUMBNAIL_VIEW:
1654         if(model)
1655         {
1656             icon_size = fm_config->thumbnail_size;
1657             fm_folder_model_set_icon_size(model, icon_size);
1658         }
1659         exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), GTK_TREE_MODEL(model));
1660         break;
1661     }
1662 
1663     if(model)
1664     {
1665         fv->model = (FmFolderModel*)g_object_ref(model);
1666         g_signal_connect(model, "row-inserted", G_CALLBACK(on_row_inserted), fv);
1667         g_signal_connect(model, "row-deleted", G_CALLBACK(on_row_deleted), fv);
1668         g_signal_connect(model, "row-changed", G_CALLBACK(on_row_changed), fv);
1669     }
1670     else
1671         fv->model = NULL;
1672     /* reset tooltip after changing folder, it might stick from old one,
1673        see how FmCellRendererText works on that regard */
1674     g_object_set(G_OBJECT(fv->view), "tooltip-text", NULL, NULL);
1675 }
1676 
1677 typedef struct
1678 {
1679     GtkTreeViewColumn* col;
1680     FmFolderViewColumnInfo* info;
1681 } _ColumnsCache;
1682 
_fm_standard_view_set_columns(FmFolderView * fv,const GSList * cols)1683 static gboolean _fm_standard_view_set_columns(FmFolderView* fv, const GSList* cols)
1684 {
1685     FmStandardView* view;
1686     GtkTreeViewColumn *col, *last;
1687     FmFolderViewColumnInfo* info;
1688     _ColumnsCache* old_cols = NULL; /* satisfy the compiler */
1689     const GSList* l;
1690     GList *cols_list, *ld;
1691     guint i, n_cols;
1692 
1693     if(!FM_IS_STANDARD_VIEW(fv))
1694         return FALSE;
1695     view = (FmStandardView*)fv;
1696 
1697     if(view->mode != FM_FV_LIST_VIEW) /* other modes aren't supported now */
1698         return FALSE;
1699 
1700     cols_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view->view));
1701     n_cols = g_list_length(cols_list);
1702     if(n_cols > 0)
1703     {
1704         /* create more convenient for us list of columns */
1705         old_cols = g_new(_ColumnsCache, n_cols);
1706         for(ld = cols_list, i = 0; ld; ld = ld->next, i++)
1707         {
1708             col = ld->data; /* column */
1709             info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id); /* info */
1710             old_cols[i].col = col;
1711             old_cols[i].info = info;
1712         }
1713         g_list_free(cols_list);
1714     }
1715     last = NULL;
1716     for(l = cols; l; l = l->next)
1717     {
1718         info = l->data;
1719         /* find old one and move here */
1720         for(i = 0; i < n_cols; i++)
1721             if(old_cols[i].info && old_cols[i].info->col_id == info->col_id)
1722                 break;
1723         if(i < n_cols)
1724         {
1725             /* we found it so just move it here */
1726             col = old_cols[i].col;
1727             /* update all other data - width for example */
1728             if(info->col_id != FM_FOLDER_MODEL_COL_NAME)
1729             {
1730                 old_cols[i].info->width = info->width;
1731                 if(info->width < 0)
1732                     old_cols[i].info->width = fm_folder_model_col_get_default_width(view->model, info->col_id);
1733                 old_cols[i].info->reserved1 = 0;
1734                 _update_width_sizing(col, info->width);
1735             }
1736             old_cols[i].col = NULL; /* we removed it from its place */
1737             old_cols[i].info = NULL; /* don't try to use it again */
1738         }
1739         else if(!fm_folder_model_col_is_valid(0))
1740             /* workaround for case when there is no model init yet, the most
1741                probably bug #3596550 is about this (it creates column with
1742                empty title), can g_return_val_if_fail() not fail somehow? */
1743             continue;
1744         else
1745         {
1746             /* if not found then append new one */
1747             col = create_list_view_column(view, info);
1748             if(col == NULL) /* failed! skipping it */
1749                 continue;
1750         }
1751         gtk_tree_view_move_column_after(GTK_TREE_VIEW(view->view), col, last);
1752         last = col;
1753     }
1754 
1755     /* remove abandoned columns from view */
1756     for(i = 0; i < n_cols; i++)
1757         if(old_cols[i].col != NULL)
1758             gtk_tree_view_remove_column(GTK_TREE_VIEW(view->view),
1759                                         old_cols[i].col);
1760     if(n_cols > 0)
1761         g_free(old_cols);
1762     return TRUE;
1763 }
1764 
_fm_standard_view_get_columns(FmFolderView * fv)1765 static GSList* _fm_standard_view_get_columns(FmFolderView* fv)
1766 {
1767     FmStandardView* view;
1768     GSList* list;
1769     GList *cols_list, *ld;
1770 
1771     if(!FM_IS_STANDARD_VIEW(fv))
1772         return NULL;
1773     view = (FmStandardView*)fv;
1774 
1775     if(view->mode != FM_FV_LIST_VIEW) /* other modes aren't supported now */
1776         return NULL;
1777 
1778     cols_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view->view));
1779     if(cols_list == NULL)
1780         return NULL;
1781     list = NULL;
1782     for(ld = cols_list; ld; ld = ld->next)
1783     {
1784         GtkTreeViewColumn *col = ld->data;
1785         FmFolderViewColumnInfo* info = g_object_get_qdata(G_OBJECT(col), fm_qdata_id);
1786         list = g_slist_append(list, info); /* info */
1787     }
1788     g_list_free(cols_list);
1789     return list;
1790 }
1791 
_fm_standard_view_scroll_to_path(FmFolderView * fv,FmPath * path,gboolean focus)1792 static void _fm_standard_view_scroll_to_path(FmFolderView* fv, FmPath *path, gboolean focus)
1793 {
1794     FmStandardView *view;
1795     GtkTreeIter it;
1796     GtkTreePath *tp;
1797 
1798     if (!FM_IS_STANDARD_VIEW(fv) || path == NULL)
1799         return;
1800     view = (FmStandardView*)fv;
1801     if (!fm_folder_model_find_iter_by_filename(view->model, &it,
1802                                                fm_path_get_basename(path)))
1803         return;
1804     tp = gtk_tree_model_get_path(GTK_TREE_MODEL(view->model), &it);
1805     if (tp == NULL) /* invalid child */
1806         return;
1807     switch(view->mode)
1808     {
1809     case FM_FV_LIST_VIEW:
1810         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view->view), tp, NULL, TRUE, 0.5, 0.0);
1811         if (focus)
1812             gtk_tree_view_set_cursor(GTK_TREE_VIEW(view->view), tp, NULL, FALSE);
1813         break;
1814     case FM_FV_ICON_VIEW:
1815     case FM_FV_COMPACT_VIEW:
1816     case FM_FV_THUMBNAIL_VIEW:
1817         exo_icon_view_scroll_to_path(EXO_ICON_VIEW(view->view), tp, TRUE, 0.5, 0.5);
1818         if (focus)
1819             exo_icon_view_set_cursor(EXO_ICON_VIEW(view->view), tp, NULL, FALSE);
1820         break;
1821     }
1822     gtk_tree_path_free(tp);
1823 }
1824 
fm_standard_view_view_init(FmFolderViewInterface * iface)1825 static void fm_standard_view_view_init(FmFolderViewInterface* iface)
1826 {
1827     iface->set_sel_mode = fm_standard_view_set_selection_mode;
1828     iface->get_sel_mode = fm_standard_view_get_selection_mode;
1829     iface->set_show_hidden = fm_standard_view_set_show_hidden;
1830     iface->get_show_hidden = fm_standard_view_get_show_hidden;
1831     iface->get_folder = fm_standard_view_get_folder;
1832     iface->set_model = fm_standard_view_set_model;
1833     iface->get_model = fm_standard_view_get_model;
1834     iface->count_selected_files = fm_standard_view_count_selected_files;
1835     iface->dup_selected_files = fm_standard_view_dup_selected_files;
1836     iface->dup_selected_file_paths = fm_standard_view_dup_selected_file_paths;
1837     iface->select_all = fm_standard_view_select_all;
1838     iface->unselect_all = fm_standard_view_unselect_all;
1839     iface->select_invert = fm_standard_view_select_invert;
1840     iface->select_file_path = fm_standard_view_select_file_path;
1841     iface->get_custom_menu_callbacks = fm_standard_view_get_custom_menu_callbacks;
1842     iface->set_columns = _fm_standard_view_set_columns;
1843     iface->get_columns = _fm_standard_view_get_columns;
1844     iface->scroll_to_path = _fm_standard_view_scroll_to_path;
1845 }
1846 
1847 typedef struct
1848 {
1849     const char* name;
1850     FmStandardViewMode mode;
1851     char *icon;
1852     char *label;
1853     char *tooltip;
1854     //char *shortkey;
1855 } FmStandardViewModeinfo;
1856 
1857 static const FmStandardViewModeinfo view_mode_names[] =
1858 {
1859     { "icon", FM_FV_ICON_VIEW, NULL, N_("_Icon View"), NULL },
1860     { "compact", FM_FV_COMPACT_VIEW, NULL, N_("_Compact View"), NULL },
1861     { "thumbnail", FM_FV_THUMBNAIL_VIEW, NULL, N_("_Thumbnail View"), NULL },
1862     { "list", FM_FV_LIST_VIEW, NULL, N_("Detailed _List View"), NULL }
1863 };
1864 
1865 /**
1866  * fm_standard_view_mode_to_str
1867  * @mode: mode id
1868  *
1869  * Retrieves string name of rendering @mode. That name may be used for
1870  * config save or similar purposes. Returned data are owned by the
1871  * implementation and should be not freed by caller.
1872  *
1873  * Returns: name associated with @mode.
1874  *
1875  * Since: 1.0.2
1876  */
fm_standard_view_mode_to_str(FmStandardViewMode mode)1877 const char* fm_standard_view_mode_to_str(FmStandardViewMode mode)
1878 {
1879     guint i;
1880 
1881     if(G_LIKELY(FM_STANDARD_VIEW_MODE_IS_VALID(mode)))
1882         for(i = 0; i < G_N_ELEMENTS(view_mode_names); i++)
1883             if(view_mode_names[i].mode == mode)
1884                 return view_mode_names[i].name;
1885     return NULL;
1886 }
1887 
1888 /**
1889  * fm_standard_view_mode_from_str
1890  * @str: the name of mode
1891  *
1892  * Finds mode which have an associated name equal to @str.
1893  *
1894  * Returns: mode id or (FmStandardViewMode)-1 if no such mode exists.
1895  *
1896  * Since: 1.0.2
1897  */
fm_standard_view_mode_from_str(const char * str)1898 FmStandardViewMode fm_standard_view_mode_from_str(const char* str)
1899 {
1900     guint i;
1901 
1902     for(i = 0; i < G_N_ELEMENTS(view_mode_names); i++)
1903         if(strcmp(str, view_mode_names[i].name) == 0)
1904             return view_mode_names[i].mode;
1905     return (FmStandardViewMode)-1;
1906 }
1907 
1908 /**
1909 * fm_standard_view_get_n_modes
1910 *
1911 * Tests how many view modes are known to create #FmStandardView widget.
1912 *
1913 * Returns: number of known modes for standard folder view.
1914 *
1915 * Since: 1.2.0
1916 */
fm_standard_view_get_n_modes(void)1917 gint fm_standard_view_get_n_modes(void)
1918 {
1919     /* FIXME: this is rough */
1920     return (gint)FM_FV_LIST_VIEW + 1;
1921 }
1922 
1923 /**
1924 * fm_standard_view_get_mode_label
1925 * @mode: the view mode
1926 *
1927 * Retrieves label for @mode which can be used in menus. Returned
1928 * data should not be freed by caller.
1929 *
1930 * Returns: desription or %NULL if @mode is invalid.
1931 *
1932 * Since: 1.2.0
1933 */
fm_standard_view_get_mode_label(FmStandardViewMode mode)1934 const char *fm_standard_view_get_mode_label(FmStandardViewMode mode)
1935 {
1936     guint i;
1937 
1938     if(G_LIKELY(FM_STANDARD_VIEW_MODE_IS_VALID(mode)))
1939         for(i = 0; i < G_N_ELEMENTS(view_mode_names); i++)
1940             if(view_mode_names[i].mode == mode && view_mode_names[i].label)
1941                 return _(view_mode_names[i].label);
1942     return NULL;
1943 }
1944 
1945 /**
1946 * fm_standard_view_get_mode_tooltip
1947 * @mode: the view mode
1948 *
1949 * Retrieves detailed description for @mode which can be used in tooltip.
1950 * Returned data should not be freed by caller.
1951 *
1952 * Returns: detailed description or %NULL if it is not available.
1953 *
1954 * Since: 1.2.0
1955 */
fm_standard_view_get_mode_tooltip(FmStandardViewMode mode)1956 const char *fm_standard_view_get_mode_tooltip(FmStandardViewMode mode)
1957 {
1958     guint i;
1959 
1960     if(G_LIKELY(FM_STANDARD_VIEW_MODE_IS_VALID(mode)))
1961         for(i = 0; i < G_N_ELEMENTS(view_mode_names); i++)
1962             if(view_mode_names[i].mode == mode && view_mode_names[i].tooltip)
1963                 return _(view_mode_names[i].tooltip);
1964     return NULL;
1965 }
1966 
1967 /**
1968 * fm_standard_view_get_mode_icon
1969 * @mode: the view mode
1970 *
1971 * Retrieves icon name for @mode which can be used in menus. Returned
1972 * data should not be freed by caller.
1973 *
1974 * Returns: icon name or %NULL if it is not available.
1975 *
1976 * Since: 1.2.0
1977 */
fm_standard_view_get_mode_icon(FmStandardViewMode mode)1978 const char *fm_standard_view_get_mode_icon(FmStandardViewMode mode)
1979 {
1980     guint i;
1981 
1982     if(G_LIKELY(FM_STANDARD_VIEW_MODE_IS_VALID(mode)))
1983         for(i = 0; i < G_N_ELEMENTS(view_mode_names); i++)
1984             if(view_mode_names[i].mode == mode)
1985                 return view_mode_names[i].icon;
1986     return NULL;
1987 }
1988