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