2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "view-file-list.h"
29 #include "layout-image.h"
33 #include "ui-fileops.h"
35 #include "ui-tree-edit.h"
36 #include "uri-utils.h"
37 #include "view-file.h"
41 /* Index to tree store */
43 FILE_COLUMN_POINTER = 0,
46 FILE_COLUMN_FORMATTED,
47 FILE_COLUMN_FORMATTED_WITH_STARS,
50 FILE_COLUMN_STAR_RATING,
56 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
61 /* Index to tree view */
63 FILE_VIEW_COLUMN_MARKS = 0,
64 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
65 FILE_VIEW_COLUMN_THUMB,
66 FILE_VIEW_COLUMN_FORMATTED,
67 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
68 FILE_VIEW_COLUMN_STAR_RATING,
69 FILE_VIEW_COLUMN_SIZE,
70 FILE_VIEW_COLUMN_DATE,
71 FILE_VIEW_COLUMN_COUNT
76 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
77 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
78 static void vflist_populate_view(ViewFile *vf, gboolean force);
79 static gboolean vflist_is_multiline(ViewFile *vf);
80 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
84 *-----------------------------------------------------------------------------
86 *-----------------------------------------------------------------------------
88 struct ViewFileFindRowData {
95 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
97 auto find = static_cast<ViewFileFindRowData *>(data);
99 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
110 static gint vflist_find_row(ViewFile *vf, FileData *fd, GtkTreeIter *iter)
113 ViewFileFindRowData data = {fd, iter, FALSE, 0};
115 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
116 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
126 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
129 GtkTreeViewColumn *column;
131 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
132 &tpath, &column, nullptr, nullptr))
138 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
139 gtk_tree_model_get_iter(store, &row, tpath);
140 gtk_tree_path_free(tpath);
141 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
149 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
152 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
154 /* it seems that gtk_tree_store_clear may call some callbacks
155 that use the column. Set the pointer to NULL to be safe. */
156 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
161 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
164 GList *files = nullptr;
166 if (unlock_files && vf->marks_enabled)
168 // unlock locked files in this directory
169 filelist_read(vf->dir_fd, &files, nullptr);
173 auto fd = static_cast<FileData *>(work->data);
175 file_data_unlock(fd);
176 file_data_unref(fd); // undo the ref that got added in filelist_read
181 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
182 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
183 gtk_tree_store_clear(GTK_TREE_STORE(store));
186 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
191 if (vflist_find_row(vf, fd, &iter) < 0) return;
192 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
193 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
196 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
201 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
203 tpath = gtk_tree_model_get_path(store, iter);
204 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
205 gtk_tree_path_free(tpath);
210 *-----------------------------------------------------------------------------
212 *-----------------------------------------------------------------------------
215 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
216 GtkSelectionData *selection_data, guint,
217 guint, gpointer data)
219 auto vf = static_cast<ViewFile *>(data);
220 GList *list = nullptr;
222 if (!VFLIST(vf)->click_fd) return;
224 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
226 list = vf_selection_get_list(vf);
230 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
234 uri_selection_data_set_uris_from_filelist(selection_data, list);
238 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
240 auto vf = static_cast<ViewFile *>(data);
242 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
244 if (VFLIST(vf)->thumbs_enabled &&
245 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
249 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
250 items = vf_selection_count(vf, nullptr);
254 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
258 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
260 auto vf = static_cast<ViewFile *>(data);
262 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
264 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
270 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
271 int x, int y, GtkSelectionData *selection,
272 guint info, guint, gpointer data)
274 auto vf = static_cast<ViewFile *>(data);
276 if (info == TARGET_TEXT_PLAIN) {
277 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
280 /* Add keywords to file */
281 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
282 GList *kw_list = string_to_keywords_list(str);
284 metadata_append_list(fd, KEYWORD_KEY, kw_list);
285 g_list_free_full(kw_list, g_free);
291 void vflist_dnd_init(ViewFile *vf)
293 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
294 dnd_file_drag_types, dnd_file_drag_types_count,
295 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
296 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
297 dnd_file_drag_types, dnd_file_drag_types_count,
298 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
300 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
301 G_CALLBACK(vflist_dnd_get), vf);
302 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
303 G_CALLBACK(vflist_dnd_begin), vf);
304 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
305 G_CALLBACK(vflist_dnd_end), vf);
306 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
307 G_CALLBACK(vflist_drag_data_received), vf);
311 *-----------------------------------------------------------------------------
313 *-----------------------------------------------------------------------------
316 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
318 GList *list = g_list_append(nullptr, file_data_ref(fd));
320 if (fd->sidecar_files)
322 /* check if the row is expanded */
326 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
327 if (vflist_find_row(vf, fd, &iter) >= 0)
331 tpath = gtk_tree_model_get_path(store, &iter);
332 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
334 /* unexpanded - add whole group */
335 GList *work = fd->sidecar_files;
338 auto sfd = static_cast<FileData *>(work->data);
339 list = g_list_prepend(list, file_data_ref(sfd));
343 gtk_tree_path_free(tpath);
345 list = g_list_reverse(list);
351 GList *vflist_pop_menu_file_list(ViewFile *vf)
353 if (!VFLIST(vf)->click_fd) return nullptr;
355 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
357 return vf_selection_get_list(vf);
359 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
363 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
365 auto vf = static_cast<ViewFile *>(data);
367 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
371 list = vf_selection_get_list(vf);
372 view_window_new_from_list(list);
377 view_window_new(VFLIST(vf)->click_fd);
381 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
383 auto vf = static_cast<ViewFile *>(data);
386 list = vf_pop_menu_file_list(vf);
387 if (options->file_ops.enable_in_place_rename &&
388 list && !list->next && VFLIST(vf)->click_fd)
395 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
396 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
400 tpath = gtk_tree_model_get_path(store, &iter);
401 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
402 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
403 vflist_row_rename_cb, vf);
404 gtk_tree_path_free(tpath);
409 file_util_rename(nullptr, list, vf->listview);
412 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
414 auto vf = static_cast<ViewFile *>(data);
416 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
419 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
423 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
427 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
429 GList *columns, *work;
431 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
436 auto column = static_cast<GtkTreeViewColumn *>(work->data);
437 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
440 if (vflist_is_multiline(vf))
442 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
444 gtk_tree_view_column_set_visible(column, enable);
446 if (col_idx == FILE_COLUMN_FORMATTED)
448 gtk_tree_view_column_set_visible(column, !enable);
453 if (col_idx == FILE_COLUMN_STAR_RATING)
455 gtk_tree_view_column_set_visible(column, enable);
459 g_list_free(columns);
462 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
464 auto vf = static_cast<ViewFile *>(data);
466 options->show_star_rating = !options->show_star_rating;
468 vflist_populate_view(vf, TRUE);
470 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
471 vflist_star_rating_set(vf, options->show_star_rating);
474 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
476 auto vf = static_cast<ViewFile *>(data);
478 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
480 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
483 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
485 auto vf = static_cast<ViewFile *>(data);
486 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
487 VFLIST(vf)->click_fd = nullptr;
493 *-----------------------------------------------------------------------------
495 *-----------------------------------------------------------------------------
498 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
500 auto vf = static_cast<ViewFile *>(data);
503 if (!new_name || !new_name[0]) return FALSE;
505 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
507 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
509 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
510 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
515 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
516 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
517 file_util_rename_simple(fd, new_path, vf->listview);
527 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
529 auto vf = static_cast<ViewFile *>(data);
532 if (event->keyval != GDK_KEY_Menu) return FALSE;
534 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
540 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
541 gtk_tree_model_get_iter(store, &iter, tpath);
542 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
543 gtk_tree_path_free(tpath);
547 VFLIST(vf)->click_fd = nullptr;
550 vf->popup = vf_pop_menu(vf);
551 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
556 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
558 auto vf = static_cast<ViewFile *>(data);
561 FileData *fd = nullptr;
562 GtkTreeViewColumn *column;
564 vf->clicked_mark = 0;
566 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
567 &tpath, &column, nullptr, nullptr))
570 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
572 if (bevent->button == MOUSE_BUTTON_LEFT &&
573 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
576 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
577 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
579 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
581 gtk_tree_model_get_iter(store, &iter, tpath);
582 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
583 gtk_tree_path_free(tpath);
586 VFLIST(vf)->click_fd = fd;
588 if (bevent->button == MOUSE_BUTTON_RIGHT)
590 vf->popup = vf_pop_menu(vf);
591 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
595 if (!fd) return FALSE;
597 if (bevent->button == MOUSE_BUTTON_MIDDLE)
599 if (!vflist_row_is_selected(vf, fd))
601 vflist_color_set(vf, fd, TRUE);
607 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
608 !(bevent->state & GDK_SHIFT_MASK ) &&
609 !(bevent->state & GDK_CONTROL_MASK ) &&
610 vflist_row_is_selected(vf, fd))
612 GtkTreeSelection *selection;
614 gtk_widget_grab_focus(widget);
617 /* returning FALSE and further processing of the event is needed for
618 correct operation of the expander, to show the sidecar files.
619 It however resets the selection of multiple files. With this condition
620 it should work for both cases */
621 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
622 return (gtk_tree_selection_count_selected_rows(selection) > 1);
625 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
627 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
629 collection_window_new(VFLIST(vf)->click_fd->path);
633 if (vf->layout) layout_image_full_screen_start(vf->layout);
640 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
642 auto vf = static_cast<ViewFile *>(data);
645 FileData *fd = nullptr;
647 if (defined_mouse_buttons(widget, bevent, vf->layout))
652 if (bevent->button == MOUSE_BUTTON_MIDDLE)
654 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
657 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
662 if ((bevent->x != 0 || bevent->y != 0) &&
663 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
664 &tpath, nullptr, nullptr, nullptr))
668 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
669 gtk_tree_model_get_iter(store, &iter, tpath);
670 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
671 gtk_tree_path_free(tpath);
674 if (bevent->button == MOUSE_BUTTON_MIDDLE)
676 if (fd && VFLIST(vf)->click_fd == fd)
678 GtkTreeSelection *selection;
680 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
681 if (vflist_row_is_selected(vf, fd))
683 gtk_tree_selection_unselect_iter(selection, &iter);
687 gtk_tree_selection_select_iter(selection, &iter);
693 if (fd && VFLIST(vf)->click_fd == fd &&
694 !(bevent->state & GDK_SHIFT_MASK ) &&
695 !(bevent->state & GDK_CONTROL_MASK ) &&
696 vflist_row_is_selected(vf, fd))
698 GtkTreeSelection *selection;
700 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
701 gtk_tree_selection_unselect_all(selection);
702 gtk_tree_selection_select_iter(selection, &iter);
703 vflist_move_cursor(vf, &iter);
709 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
711 FileData *read_ahead_fd = nullptr;
717 cur_fd = layout_image_get_fd(vf->layout);
718 if (sel_fd == cur_fd) return; /* no change */
720 row = g_list_index(vf->list, sel_fd);
721 /** @FIXME sidecar data */
723 if (sel_fd && options->image.enable_read_ahead && row >= 0)
725 if (row > g_list_index(vf->list, cur_fd) &&
726 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
728 read_ahead_fd = vf_index_get_data(vf, row + 1);
732 read_ahead_fd = vf_index_get_data(vf, row - 1);
736 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
739 static gboolean vflist_select_idle_cb(gpointer data)
741 auto vf = static_cast<ViewFile *>(data);
745 VFLIST(vf)->select_idle_id = 0;
746 return G_SOURCE_REMOVE;
751 if (VFLIST(vf)->select_fd)
753 vflist_select_image(vf, VFLIST(vf)->select_fd);
754 VFLIST(vf)->select_fd = nullptr;
757 VFLIST(vf)->select_idle_id = 0;
758 return G_SOURCE_REMOVE;
761 static void vflist_select_idle_cancel(ViewFile *vf)
763 if (VFLIST(vf)->select_idle_id)
765 g_source_remove(VFLIST(vf)->select_idle_id);
766 VFLIST(vf)->select_idle_id = 0;
770 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
772 auto vf = static_cast<ViewFile *>(data);
774 GtkTreePath *cursor_path;
776 VFLIST(vf)->select_fd = nullptr;
778 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
780 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
783 gtk_tree_model_get_iter(store, &iter, cursor_path);
784 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
785 gtk_tree_path_free(cursor_path);
790 !VFLIST(vf)->select_idle_id)
792 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
798 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
800 auto vf = static_cast<ViewFile *>(data);
801 vflist_set_expanded(vf, iter, TRUE);
804 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
806 auto vf = static_cast<ViewFile *>(data);
807 vflist_set_expanded(vf, iter, FALSE);
811 *-----------------------------------------------------------------------------
813 *-----------------------------------------------------------------------------
817 static gchar* vflist_get_formatted(ViewFile *vf, const gchar *name, const gchar *sidecars, const gchar *size, const gchar *time, gboolean expanded, gboolean with_stars, const gchar *star_rating)
819 gboolean multiline = vflist_is_multiline(vf);
826 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
830 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
835 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
840 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
848 gchar *formatted_with_stars;
850 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
852 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
853 FILE_COLUMN_NAME, &name,
854 FILE_COLUMN_SIDECARS, &sidecars,
855 FILE_COLUMN_SIZE, &size,
856 FILE_COLUMN_DATE, &time,
857 FILE_COLUMN_STAR_RATING, &star_rating,
860 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
861 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
863 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
864 FILE_COLUMN_EXPANDED, expanded,
866 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
867 FILE_COLUMN_EXPANDED, expanded,
874 g_free(formatted_with_stars);
877 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
880 gchar *sidecars = nullptr;
882 const gchar *time = text_from_time(fd->date);
883 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
884 const gchar *disabled_grouping;
886 gchar *formatted_with_stars;
887 gboolean expanded = FALSE;
890 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
892 star_rating = convert_rating_to_stars(fd->rating);
896 star_rating = nullptr;
899 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
901 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
904 sidecars = file_data_sc_list_to_string(fd);
906 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
907 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
908 size = text_from_size(fd->size);
910 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
911 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
913 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
914 FILE_COLUMN_VERSION, fd->version,
915 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
916 FILE_COLUMN_FORMATTED, formatted,
917 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
918 FILE_COLUMN_SIDECARS, sidecars,
919 FILE_COLUMN_NAME, name,
920 FILE_COLUMN_STAR_RATING, star_rating,
921 FILE_COLUMN_SIZE, size,
922 FILE_COLUMN_DATE, time,
923 #define STORE_SET_IS_SLOW 1
924 #if STORE_SET_IS_SLOW
925 /* this is 3x faster on a directory with 20000 files */
926 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
927 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
928 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
929 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
930 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
931 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
932 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
933 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
934 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
935 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
936 #if FILEDATA_MARKS_SIZE != 10
937 #error this needs to be updated
940 FILE_COLUMN_COLOR, FALSE, -1);
942 #if !STORE_SET_IS_SLOW
945 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
946 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
955 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
960 gint num_ordered = 0;
961 gint num_prepended = 0;
963 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
969 auto fd = static_cast<FileData *>(work->data);
970 gboolean done = FALSE;
974 FileData *old_fd = nullptr;
975 gint old_version = 0;
979 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
980 FILE_COLUMN_POINTER, &old_fd,
981 FILE_COLUMN_VERSION, &old_version,
991 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
993 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
995 if (match == 0) g_warning("multiple fd for the same path");
1006 GtkTreeIter new_iter;
1011 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1016 here should be used gtk_tree_store_append, but this function seems to be O(n)
1017 and it seems to be much faster to add new entries to the beginning and reorder later
1020 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1023 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1024 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1026 if (g_list_find(selected, fd))
1028 /* renamed files - the same fd appears at different position - select it again*/
1029 GtkTreeSelection *selection;
1030 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1031 gtk_tree_selection_select_iter(selection, &new_iter);
1038 file_data_unref(old_fd);
1039 valid = gtk_tree_store_remove(store, &iter);
1044 if (fd->version != old_version || force)
1046 vflist_setup_iter(vf, store, &iter, fd);
1047 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1050 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1061 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1062 file_data_unref(old_fd);
1064 valid = gtk_tree_store_remove(store, &iter);
1067 /* move the prepended entries to the correct position */
1070 gint num_total = num_prepended + num_ordered;
1071 std::vector<gint> new_order;
1072 new_order.reserve(num_total);
1074 for (gint i = 0; i < num_ordered; i++)
1076 new_order.push_back(num_prepended + i);
1078 for (gint i = num_ordered; i < num_total; i++)
1080 new_order.push_back(num_total - 1 - i);
1082 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1086 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1089 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1090 GtkTreeStore *store;
1093 if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1094 if (!vf->list) return;
1100 auto fd = static_cast<FileData *>(work->data);
1101 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1106 vf->sort_method = type;
1107 vf->sort_ascend = ascend;
1109 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1111 std::vector<gint> new_order;
1112 new_order.reserve(i);
1117 auto fd = static_cast<FileData *>(work->data);
1118 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1122 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1123 gtk_tree_store_reorder(store, nullptr, new_order.data());
1125 g_hash_table_destroy(fd_idx_hash);
1129 *-----------------------------------------------------------------------------
1131 *-----------------------------------------------------------------------------
1135 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1140 auto fd = static_cast<FileData *>(work->data);
1143 if (fd->thumb_pixbuf) (*done)++;
1145 if (fd->sidecar_files)
1147 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1153 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1158 auto fd = static_cast<FileData *>(work->data);
1161 if (fd->metadata_in_idle_loaded) (*done)++;
1163 if (fd->sidecar_files)
1165 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1171 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1173 GtkTreeStore *store;
1176 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1178 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1179 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1182 FileData *vflist_thumb_next_fd(ViewFile *vf)
1185 FileData *fd = nullptr;
1187 /* first check the visible files */
1189 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1191 GtkTreeModel *store;
1193 gboolean valid = TRUE;
1195 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1196 gtk_tree_model_get_iter(store, &iter, tpath);
1197 gtk_tree_path_free(tpath);
1200 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1204 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1206 if (!nfd->thumb_pixbuf) fd = nfd;
1208 valid = gtk_tree_model_iter_next(store, &iter);
1212 /* then find first undone */
1216 GList *work = vf->list;
1219 auto fd_p = static_cast<FileData *>(work->data);
1220 if (!fd_p->thumb_pixbuf)
1224 GList *work2 = fd_p->sidecar_files;
1226 while (work2 && !fd)
1228 fd_p = static_cast<FileData *>(work2->data);
1229 if (!fd_p->thumb_pixbuf) fd = fd_p;
1230 work2 = work2->next;
1240 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1242 GtkTreeStore *store;
1249 gchar *formatted_with_stars;
1252 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1254 star_rating = metadata_read_rating_stars(fd);
1256 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1257 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1259 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1260 FILE_COLUMN_NAME, &name,
1261 FILE_COLUMN_SIDECARS, &sidecars,
1262 FILE_COLUMN_SIZE, &size,
1263 FILE_COLUMN_DATE, &time,
1264 FILE_COLUMN_EXPANDED, &expanded,
1267 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1269 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1270 FILE_COLUMN_EXPANDED, expanded,
1273 g_free(star_rating);
1274 g_free(formatted_with_stars);
1277 FileData *vflist_star_next_fd(ViewFile *vf)
1280 FileData *fd = nullptr;
1281 FileData *nfd = nullptr;
1283 /* first check the visible files */
1285 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1287 GtkTreeModel *store;
1289 gboolean valid = TRUE;
1291 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1292 gtk_tree_model_get_iter(store, &iter, tpath);
1293 gtk_tree_path_free(tpath);
1296 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1298 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1300 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1305 valid = gtk_tree_model_iter_next(store, &iter);
1310 vf->stars_filedata = fd;
1312 if (vf->stars_id == 0)
1314 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1319 /* then find first undone */
1323 GList *work = vf->list;
1327 auto fd_p = static_cast<FileData *>(work->data);
1329 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1343 vf->stars_filedata = fd;
1345 if (vf->stars_id == 0)
1347 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1356 *-----------------------------------------------------------------------------
1358 *-----------------------------------------------------------------------------
1361 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1364 GList *work, *work2;
1369 auto list_fd = static_cast<FileData *>(work->data);
1370 if (list_fd == fd) return p;
1372 work2 = list_fd->sidecar_files;
1375 /** @FIXME return the same index also for sidecars
1376 it is sufficient for next/prev navigation but it should be rewritten
1377 without using indexes at all
1379 auto sidecar_fd = static_cast<FileData *>(work2->data);
1380 if (sidecar_fd == fd) return p;
1381 work2 = work2->next;
1392 *-----------------------------------------------------------------------------
1394 *-----------------------------------------------------------------------------
1397 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1399 GtkTreeModel *store;
1400 GtkTreeSelection *selection;
1403 gboolean found = FALSE;
1405 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1406 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1408 while (!found && work)
1410 auto tpath = static_cast<GtkTreePath *>(work->data);
1414 gtk_tree_model_get_iter(store, &iter, tpath);
1415 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1416 if (fd_n == fd) found = TRUE;
1419 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1424 #pragma GCC diagnostic push
1425 #pragma GCC diagnostic ignored "-Wunused-function"
1426 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1430 fd = vf_index_get_data(vf, row);
1431 return vflist_row_is_selected(vf, fd);
1433 #pragma GCC diagnostic pop
1435 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1437 GtkTreeModel *store;
1438 GtkTreeSelection *selection;
1442 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1443 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1453 auto tpath = static_cast<GtkTreePath *>(work->data);
1457 gtk_tree_model_get_iter(store, &iter, tpath);
1458 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1467 count = g_list_length(slist);
1468 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1473 GList *vflist_selection_get_list(ViewFile *vf)
1475 GtkTreeModel *store;
1476 GtkTreeSelection *selection;
1478 GList *list = nullptr;
1481 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1482 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1486 auto tpath = static_cast<GtkTreePath *>(work->data);
1490 gtk_tree_model_get_iter(store, &iter, tpath);
1491 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1493 list = g_list_prepend(list, file_data_ref(fd));
1495 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1497 /* unexpanded - add whole group */
1498 GList *work2 = fd->sidecar_files;
1501 auto sfd = static_cast<FileData *>(work2->data);
1502 list = g_list_prepend(list, file_data_ref(sfd));
1503 work2 = work2->next;
1509 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1511 return g_list_reverse(list);
1514 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1516 GtkTreeModel *store;
1517 GtkTreeSelection *selection;
1519 GList *list = nullptr;
1522 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1523 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1527 auto tpath = static_cast<GtkTreePath *>(work->data);
1531 gtk_tree_model_get_iter(store, &iter, tpath);
1532 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1534 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1538 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1540 return g_list_reverse(list);
1543 void vflist_select_all(ViewFile *vf)
1545 GtkTreeSelection *selection;
1547 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1548 gtk_tree_selection_select_all(selection);
1550 VFLIST(vf)->select_fd = nullptr;
1553 void vflist_select_none(ViewFile *vf)
1555 GtkTreeSelection *selection;
1557 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1558 gtk_tree_selection_unselect_all(selection);
1561 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1566 tpath = gtk_tree_model_get_path(store, iter);
1567 result = gtk_tree_path_prev(tpath);
1569 gtk_tree_model_get_iter(store, iter, tpath);
1571 gtk_tree_path_free(tpath);
1576 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1578 if (!gtk_tree_model_get_iter_first(store, iter))
1583 GtkTreeIter next = *iter;
1585 if (gtk_tree_model_iter_next(store, &next))
1594 void vflist_select_invert(ViewFile *vf)
1597 GtkTreeSelection *selection;
1598 GtkTreeModel *store;
1601 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1602 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1604 /* Backward iteration prevents scrolling to the end of the list,
1605 * it scrolls to the first selected row instead. */
1606 valid = tree_model_get_iter_last(store, &iter);
1610 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1613 gtk_tree_selection_unselect_iter(selection, &iter);
1615 gtk_tree_selection_select_iter(selection, &iter);
1617 valid = tree_model_iter_prev(store, &iter);
1621 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1625 if (vflist_find_row(vf, fd, &iter) < 0) return;
1627 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1629 if (!vflist_row_is_selected(vf, fd))
1631 GtkTreeSelection *selection;
1632 GtkTreeModel *store;
1635 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1636 gtk_tree_selection_unselect_all(selection);
1637 gtk_tree_selection_select_iter(selection, &iter);
1638 vflist_move_cursor(vf, &iter);
1640 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1641 tpath = gtk_tree_model_get_path(store, &iter);
1642 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1643 gtk_tree_path_free(tpath);
1647 void vflist_select_list(ViewFile *vf, GList *list)
1658 fd = static_cast<FileData *>(work->data);
1660 if (vflist_find_row(vf, fd, &iter) < 0) return;
1661 if (!vflist_row_is_selected(vf, fd))
1663 GtkTreeSelection *selection;
1665 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1666 gtk_tree_selection_select_iter(selection, &iter);
1672 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1675 FileData *fd = nullptr;
1677 if (sel_fd->parent) sel_fd = sel_fd->parent;
1683 fd = static_cast<FileData *>(work->data);
1686 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1688 if (match >= 0) break;
1691 if (fd) vflist_select_by_fd(vf, fd);
1695 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1697 GtkTreeModel *store;
1699 GtkTreeSelection *selection;
1703 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1705 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1706 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1708 valid = gtk_tree_model_get_iter_first(store, &iter);
1712 gboolean mark_val, selected;
1713 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1715 mark_val = file_data_get_mark(fd, n);
1716 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1720 case MTS_MODE_SET: selected = mark_val;
1722 case MTS_MODE_OR: selected = mark_val || selected;
1724 case MTS_MODE_AND: selected = mark_val && selected;
1726 case MTS_MODE_MINUS: selected = !mark_val && selected;
1731 gtk_tree_selection_select_iter(selection, &iter);
1733 gtk_tree_selection_unselect_iter(selection, &iter);
1735 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1739 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1741 GtkTreeModel *store;
1742 GtkTreeSelection *selection;
1747 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1749 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1750 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1754 auto tpath = static_cast<GtkTreePath *>(work->data);
1758 gtk_tree_model_get_iter(store, &iter, tpath);
1759 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1761 /* the change has a very limited range and the standard notification would trigger
1762 complete re-read of the directory - try to do only minimal update instead */
1763 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1767 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1769 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1771 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1775 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1777 vf_refresh_idle(vf);
1781 /* mark functions can have various side effects - update all columns to be sure */
1782 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1783 /* mark functions can change sidecars too */
1784 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1788 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1792 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1796 *-----------------------------------------------------------------------------
1798 *-----------------------------------------------------------------------------
1801 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1803 GtkTreeViewColumn *column;
1804 GtkCellRenderer *cell;
1807 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1808 if (!column) return;
1810 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1812 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1814 cell = static_cast<GtkCellRenderer *>(list->data);
1817 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1818 gtk_tree_view_column_set_visible(column, thumb);
1820 if (options->show_star_rating)
1822 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1823 if (!column) return;
1824 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1825 gtk_tree_view_column_set_visible(column, TRUE);
1827 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1828 if (!column) return;
1829 gtk_tree_view_column_set_visible(column, FALSE);
1833 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1834 if (!column) return;
1835 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1836 gtk_tree_view_column_set_visible(column, TRUE);
1838 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1839 if (!column) return;
1840 gtk_tree_view_column_set_visible(column, FALSE);
1843 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1844 if (!column) return;
1845 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1847 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1848 if (!column) return;
1849 gtk_tree_view_column_set_visible(column, !multiline);
1851 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1852 if (!column) return;
1853 gtk_tree_view_column_set_visible(column, !multiline);
1856 static gboolean vflist_is_multiline(ViewFile *vf)
1858 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1862 static void vflist_populate_view(ViewFile *vf, gboolean force)
1864 GtkTreeStore *store;
1867 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1874 vflist_store_clear(vf, FALSE);
1879 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1881 selected = vflist_selection_get_list(vf);
1883 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1885 if (selected && vflist_selection_count(vf, nullptr) == 0)
1887 /* all selected files disappeared */
1888 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1891 filelist_free(selected);
1894 vf_thumb_update(vf);
1898 gboolean vflist_refresh(ViewFile *vf)
1901 gboolean ret = TRUE;
1903 old_list = vf->list;
1906 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1909 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1911 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1913 if (vf->marks_enabled)
1915 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1916 // each time a mark is changed.
1917 file_data_lock_list(vf->list);
1921 /** @FIXME only do this when needed (aka when we just switched from */
1922 /** @FIXME marks-enabled to marks-disabled) */
1923 file_data_unlock_list(vf->list);
1926 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1927 vf->list = g_list_first(vf->list);
1928 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1930 vf->list = g_list_first(vf->list);
1931 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1933 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1935 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1936 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1939 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1941 vflist_populate_view(vf, FALSE);
1943 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1945 filelist_free(old_list);
1946 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1953 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1955 #define CELL_HEIGHT_OVERRIDE 512
1957 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1961 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1962 if (spec && G_IS_PARAM_SPEC_INT(spec))
1964 GParamSpecInt *spec_int;
1966 spec_int = G_PARAM_SPEC_INT(spec);
1967 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1971 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1973 static GdkColor color;
1974 static GtkWidget *done = nullptr;
1980 style = gtk_widget_get_style(widget);
1981 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1982 shift_color(&color, -1, 0);
1989 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1990 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1992 auto vf = static_cast<ViewFile *>(data);
1995 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1996 g_object_set(G_OBJECT(cell),
1997 "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1998 "cell-background-set", set, NULL);
2001 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2003 GtkTreeViewColumn *column;
2004 GtkCellRenderer *renderer;
2006 column = gtk_tree_view_column_new();
2007 gtk_tree_view_column_set_title(column, title);
2008 gtk_tree_view_column_set_min_width(column, 4);
2012 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2013 renderer = gtk_cell_renderer_text_new();
2016 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2018 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2019 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2021 gtk_tree_view_column_set_expand(column, TRUE);
2025 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2026 renderer = gtk_cell_renderer_pixbuf_new();
2027 cell_renderer_height_override(renderer);
2028 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2029 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2032 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2033 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2034 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2036 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2039 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2041 auto vf = static_cast<ViewFile *>(data);
2042 GtkTreeStore *store;
2043 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2049 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2050 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2053 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2055 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2057 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2060 /* the change has a very limited range and the standard notification would trigger
2061 complete re-read of the directory - try to do only minimal update instead */
2062 file_data_unregister_notify_func(vf_notify_cb, vf);
2063 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2064 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2066 vf_refresh_idle(vf);
2070 /* mark functions can have various side effects - update all columns to be sure */
2071 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2072 /* mark functions can change sidecars too */
2073 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2075 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2077 gtk_tree_path_free(path);
2080 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2082 GtkTreeViewColumn *column;
2083 GtkCellRenderer *renderer;
2085 renderer = gtk_cell_renderer_toggle_new();
2086 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2088 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2089 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2090 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2092 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2093 gtk_tree_view_column_set_fixed_width(column, 22);
2094 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2097 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2101 *-----------------------------------------------------------------------------
2103 *-----------------------------------------------------------------------------
2106 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2109 if (!dir_fd) return FALSE;
2110 if (vf->dir_fd == dir_fd) return TRUE;
2112 file_data_unref(vf->dir_fd);
2113 vf->dir_fd = file_data_ref(dir_fd);
2115 /* force complete reload */
2116 vflist_store_clear(vf, TRUE);
2118 filelist_free(vf->list);
2121 ret = vf_refresh(vf);
2122 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2126 void vflist_destroy_cb(GtkWidget *, gpointer data)
2128 auto vf = static_cast<ViewFile *>(data);
2130 file_data_unregister_notify_func(vf_notify_cb, vf);
2132 vflist_select_idle_cancel(vf);
2133 vf_refresh_idle_cancel(vf);
2137 filelist_free(vf->list);
2140 ViewFile *vflist_new(ViewFile *vf, FileData *)
2142 GtkTreeStore *store;
2143 GtkTreeSelection *selection;
2144 GType flist_types[FILE_COLUMN_COUNT];
2148 vf->info = g_new0(ViewFileInfoList, 1);
2150 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2151 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2152 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2153 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2154 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2155 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2156 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2157 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2158 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2159 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2160 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2161 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2162 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2163 flist_types[i] = G_TYPE_BOOLEAN;
2165 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2167 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2168 g_object_unref(store);
2170 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2171 G_CALLBACK(vflist_expand_cb), vf);
2173 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2174 G_CALLBACK(vflist_collapse_cb), vf);
2176 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2177 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2178 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2180 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2181 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2183 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2187 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2189 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2190 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2194 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2195 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2198 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2199 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2202 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2203 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2206 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2207 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2210 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2211 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2214 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2215 g_assert(column == FILE_VIEW_COLUMN_DATE);
2218 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2222 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2224 if (VFLIST(vf)->thumbs_enabled == enable) return;
2226 VFLIST(vf)->thumbs_enabled = enable;
2228 /* vflist_populate_view is better than vf_refresh:
2229 - no need to re-read the directory
2230 - force update because the formatted string has changed
2234 vflist_populate_view(vf, TRUE);
2235 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2239 void vflist_marks_set(ViewFile *vf, gboolean enable)
2241 GList *columns, *work;
2243 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2248 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2249 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2252 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2253 gtk_tree_view_column_set_visible(column, enable);
2258 // Previously disabled, which means that vf->list is complete
2259 file_data_lock_list(vf->list);
2263 // Previously enabled, which means that vf->list is incomplete
2266 g_list_free(columns);
2269 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */