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.
22 #include "view-file-list.h"
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 #include <glib-object.h>
36 #include "layout-image.h"
38 #include "main-defines.h"
42 #include "ui-fileops.h"
44 #include "ui-tree-edit.h"
45 #include "uri-utils.h"
47 #include "view-file.h"
49 /* Index to tree store */
51 FILE_COLUMN_POINTER = VIEW_FILE_COLUMN_POINTER,
54 FILE_COLUMN_FORMATTED,
55 FILE_COLUMN_FORMATTED_WITH_STARS,
58 FILE_COLUMN_STAR_RATING,
64 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
69 /* Index to tree view */
71 FILE_VIEW_COLUMN_MARKS = 0,
72 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
73 FILE_VIEW_COLUMN_THUMB,
74 FILE_VIEW_COLUMN_FORMATTED,
75 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
76 FILE_VIEW_COLUMN_STAR_RATING,
77 FILE_VIEW_COLUMN_SIZE,
78 FILE_VIEW_COLUMN_DATE,
79 FILE_VIEW_COLUMN_COUNT
84 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
85 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
86 static void vflist_populate_view(ViewFile *vf, gboolean force);
87 static gboolean vflist_is_multiline(ViewFile *vf);
88 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
92 *-----------------------------------------------------------------------------
94 *-----------------------------------------------------------------------------
96 struct ViewFileFindRowData {
103 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
105 auto find = static_cast<ViewFileFindRowData *>(data);
107 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
118 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
121 ViewFileFindRowData data = {fd, iter, FALSE, 0};
123 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
124 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
134 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
137 GtkTreeViewColumn *column;
139 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
140 &tpath, &column, nullptr, nullptr))
146 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
147 gtk_tree_model_get_iter(store, &row, tpath);
148 gtk_tree_path_free(tpath);
149 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
157 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
160 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
162 /* it seems that gtk_tree_store_clear may call some callbacks
163 that use the column. Set the pointer to NULL to be safe. */
164 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
169 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
172 GList *files = nullptr;
174 if (unlock_files && vf->marks_enabled)
176 // unlock locked files in this directory
177 filelist_read(vf->dir_fd, &files, nullptr);
181 auto fd = static_cast<FileData *>(work->data);
183 file_data_unlock(fd);
184 file_data_unref(fd); // undo the ref that got added in filelist_read
189 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
190 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
191 gtk_tree_store_clear(GTK_TREE_STORE(store));
194 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
199 if (vflist_find_row(vf, fd, &iter) < 0) return;
200 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
201 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
204 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
209 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
211 tpath = gtk_tree_model_get_path(store, iter);
212 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
213 gtk_tree_path_free(tpath);
218 *-----------------------------------------------------------------------------
220 *-----------------------------------------------------------------------------
223 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
224 GtkSelectionData *selection_data, guint,
225 guint, gpointer data)
227 auto vf = static_cast<ViewFile *>(data);
228 GList *list = nullptr;
230 if (!VFLIST(vf)->click_fd) return;
232 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
234 list = vf_selection_get_list(vf);
238 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
242 uri_selection_data_set_uris_from_filelist(selection_data, list);
246 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
248 auto vf = static_cast<ViewFile *>(data);
250 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
252 if (VFLIST(vf)->thumbs_enabled &&
253 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
257 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
258 items = vf_selection_count(vf, nullptr);
262 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
266 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
268 auto vf = static_cast<ViewFile *>(data);
270 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
272 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
278 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
279 int x, int y, GtkSelectionData *selection,
280 guint info, guint, gpointer data)
282 auto vf = static_cast<ViewFile *>(data);
284 if (info == TARGET_TEXT_PLAIN) {
285 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
288 /* Add keywords to file */
289 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
290 GList *kw_list = string_to_keywords_list(str);
292 metadata_append_list(fd, KEYWORD_KEY, kw_list);
293 g_list_free_full(kw_list, g_free);
299 void vflist_dnd_init(ViewFile *vf)
301 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
302 dnd_file_drag_types, dnd_file_drag_types_count,
303 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
304 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
305 dnd_file_drag_types, dnd_file_drag_types_count,
306 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
308 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
309 G_CALLBACK(vflist_dnd_get), vf);
310 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
311 G_CALLBACK(vflist_dnd_begin), vf);
312 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
313 G_CALLBACK(vflist_dnd_end), vf);
314 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
315 G_CALLBACK(vflist_drag_data_received), vf);
319 *-----------------------------------------------------------------------------
321 *-----------------------------------------------------------------------------
324 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
326 GList *list = nullptr;
328 if (fd->sidecar_files)
330 /* check if the row is expanded */
334 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
335 if (vflist_find_row(vf, fd, &iter) >= 0)
339 tpath = gtk_tree_model_get_path(store, &iter);
340 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
342 /* unexpanded - add whole group */
343 list = filelist_copy(fd->sidecar_files);
345 gtk_tree_path_free(tpath);
349 return g_list_prepend(list, file_data_ref(fd));
352 GList *vflist_pop_menu_file_list(ViewFile *vf)
354 if (!VFLIST(vf)->click_fd) return nullptr;
356 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
358 return vf_selection_get_list(vf);
360 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
364 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
366 auto vf = static_cast<ViewFile *>(data);
368 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
372 list = vf_selection_get_list(vf);
373 view_window_new_from_list(list);
378 view_window_new(VFLIST(vf)->click_fd);
382 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
384 auto vf = static_cast<ViewFile *>(data);
387 list = vf_pop_menu_file_list(vf);
388 if (options->file_ops.enable_in_place_rename &&
389 list && !list->next && VFLIST(vf)->click_fd)
396 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
397 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
401 tpath = gtk_tree_model_get_path(store, &iter);
402 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
403 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
404 vflist_row_rename_cb, vf);
405 gtk_tree_path_free(tpath);
410 file_util_rename(nullptr, list, vf->listview);
413 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
415 auto vf = static_cast<ViewFile *>(data);
417 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
420 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
424 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
428 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
433 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
438 auto column = static_cast<GtkTreeViewColumn *>(work->data);
439 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
442 if (vflist_is_multiline(vf))
444 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
446 gtk_tree_view_column_set_visible(column, enable);
448 if (col_idx == FILE_COLUMN_FORMATTED)
450 gtk_tree_view_column_set_visible(column, !enable);
455 if (col_idx == FILE_COLUMN_STAR_RATING)
457 gtk_tree_view_column_set_visible(column, enable);
461 g_list_free(columns);
464 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
466 auto vf = static_cast<ViewFile *>(data);
468 options->show_star_rating = !options->show_star_rating;
470 vflist_populate_view(vf, TRUE);
472 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
473 vflist_star_rating_set(vf, options->show_star_rating);
476 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
478 auto vf = static_cast<ViewFile *>(data);
480 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
482 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
485 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
487 auto vf = static_cast<ViewFile *>(data);
488 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
489 VFLIST(vf)->click_fd = nullptr;
495 *-----------------------------------------------------------------------------
497 *-----------------------------------------------------------------------------
500 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
502 auto vf = static_cast<ViewFile *>(data);
505 if (!new_name || !new_name[0]) return FALSE;
507 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
509 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
511 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
512 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
517 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
518 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
519 file_util_rename_simple(fd, new_path, vf->listview);
529 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
531 auto vf = static_cast<ViewFile *>(data);
534 if (event->keyval != GDK_KEY_Menu) return FALSE;
536 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
542 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
543 gtk_tree_model_get_iter(store, &iter, tpath);
544 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
545 gtk_tree_path_free(tpath);
549 VFLIST(vf)->click_fd = nullptr;
552 vf->popup = vf_pop_menu(vf);
553 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
558 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
560 auto vf = static_cast<ViewFile *>(data);
563 FileData *fd = nullptr;
564 GtkTreeViewColumn *column;
566 vf->clicked_mark = 0;
568 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
569 &tpath, &column, nullptr, nullptr))
572 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
574 if (bevent->button == MOUSE_BUTTON_LEFT &&
575 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
578 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
579 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
581 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
583 gtk_tree_model_get_iter(store, &iter, tpath);
584 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
585 gtk_tree_path_free(tpath);
588 VFLIST(vf)->click_fd = fd;
590 if (bevent->button == MOUSE_BUTTON_RIGHT)
592 vf->popup = vf_pop_menu(vf);
593 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
597 if (!fd) return FALSE;
599 if (bevent->button == MOUSE_BUTTON_MIDDLE)
601 if (!vflist_row_is_selected(vf, fd))
603 vflist_color_set(vf, fd, TRUE);
609 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
610 !(bevent->state & GDK_SHIFT_MASK ) &&
611 !(bevent->state & GDK_CONTROL_MASK ) &&
612 vflist_row_is_selected(vf, fd))
614 GtkTreeSelection *selection;
616 gtk_widget_grab_focus(widget);
619 /* returning FALSE and further processing of the event is needed for
620 correct operation of the expander, to show the sidecar files.
621 It however resets the selection of multiple files. With this condition
622 it should work for both cases */
623 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
624 return (gtk_tree_selection_count_selected_rows(selection) > 1);
627 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
629 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
631 collection_window_new(VFLIST(vf)->click_fd->path);
635 if (vf->layout) layout_image_full_screen_start(vf->layout);
642 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
644 auto vf = static_cast<ViewFile *>(data);
647 FileData *fd = nullptr;
649 if (defined_mouse_buttons(widget, bevent, vf->layout))
654 if (bevent->button == MOUSE_BUTTON_MIDDLE)
656 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
659 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
664 if ((bevent->x != 0 || bevent->y != 0) &&
665 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
666 &tpath, nullptr, nullptr, nullptr))
670 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
671 gtk_tree_model_get_iter(store, &iter, tpath);
672 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
673 gtk_tree_path_free(tpath);
676 if (bevent->button == MOUSE_BUTTON_MIDDLE)
678 if (fd && VFLIST(vf)->click_fd == fd)
680 GtkTreeSelection *selection;
682 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
683 if (vflist_row_is_selected(vf, fd))
685 gtk_tree_selection_unselect_iter(selection, &iter);
689 gtk_tree_selection_select_iter(selection, &iter);
695 if (fd && VFLIST(vf)->click_fd == fd &&
696 !(bevent->state & GDK_SHIFT_MASK ) &&
697 !(bevent->state & GDK_CONTROL_MASK ) &&
698 vflist_row_is_selected(vf, fd))
700 GtkTreeSelection *selection;
702 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
703 gtk_tree_selection_unselect_all(selection);
704 gtk_tree_selection_select_iter(selection, &iter);
705 vflist_move_cursor(vf, &iter);
711 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
713 FileData *read_ahead_fd = nullptr;
719 cur_fd = layout_image_get_fd(vf->layout);
720 if (sel_fd == cur_fd) return; /* no change */
722 row = g_list_index(vf->list, sel_fd);
723 /** @FIXME sidecar data */
725 if (sel_fd && options->image.enable_read_ahead && row >= 0)
727 if (row > g_list_index(vf->list, cur_fd) &&
728 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
730 read_ahead_fd = vf_index_get_data(vf, row + 1);
734 read_ahead_fd = vf_index_get_data(vf, row - 1);
738 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
741 static gboolean vflist_select_idle_cb(gpointer data)
743 auto vf = static_cast<ViewFile *>(data);
747 VFLIST(vf)->select_idle_id = 0;
748 return G_SOURCE_REMOVE;
753 if (VFLIST(vf)->select_fd)
755 vflist_select_image(vf, VFLIST(vf)->select_fd);
756 VFLIST(vf)->select_fd = nullptr;
759 VFLIST(vf)->select_idle_id = 0;
760 return G_SOURCE_REMOVE;
763 static void vflist_select_idle_cancel(ViewFile *vf)
765 if (VFLIST(vf)->select_idle_id)
767 g_source_remove(VFLIST(vf)->select_idle_id);
768 VFLIST(vf)->select_idle_id = 0;
772 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
774 auto vf = static_cast<ViewFile *>(data);
776 GtkTreePath *cursor_path;
778 VFLIST(vf)->select_fd = nullptr;
780 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
782 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
785 gtk_tree_model_get_iter(store, &iter, cursor_path);
786 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
787 gtk_tree_path_free(cursor_path);
792 !VFLIST(vf)->select_idle_id)
794 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
800 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
802 auto vf = static_cast<ViewFile *>(data);
803 vflist_set_expanded(vf, iter, TRUE);
806 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
808 auto vf = static_cast<ViewFile *>(data);
809 vflist_set_expanded(vf, iter, FALSE);
813 *-----------------------------------------------------------------------------
815 *-----------------------------------------------------------------------------
819 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)
821 gboolean multiline = vflist_is_multiline(vf);
828 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
832 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
837 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
842 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
850 gchar *formatted_with_stars;
852 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
854 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
855 FILE_COLUMN_NAME, &name,
856 FILE_COLUMN_SIDECARS, &sidecars,
857 FILE_COLUMN_SIZE, &size,
858 FILE_COLUMN_DATE, &time,
859 FILE_COLUMN_STAR_RATING, &star_rating,
862 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
863 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
865 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
866 FILE_COLUMN_EXPANDED, expanded,
868 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
869 FILE_COLUMN_EXPANDED, expanded,
876 g_free(formatted_with_stars);
879 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
882 gchar *sidecars = nullptr;
884 const gchar *time = text_from_time(fd->date);
885 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
886 const gchar *disabled_grouping;
888 gchar *formatted_with_stars;
889 gboolean expanded = FALSE;
892 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
894 star_rating = convert_rating_to_stars(fd->rating);
898 star_rating = nullptr;
901 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
903 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
906 sidecars = file_data_sc_list_to_string(fd);
908 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
909 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
910 size = text_from_size(fd->size);
912 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
913 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
915 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
916 FILE_COLUMN_VERSION, fd->version,
917 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
918 FILE_COLUMN_FORMATTED, formatted,
919 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
920 FILE_COLUMN_SIDECARS, sidecars,
921 FILE_COLUMN_NAME, name,
922 FILE_COLUMN_STAR_RATING, star_rating,
923 FILE_COLUMN_SIZE, size,
924 FILE_COLUMN_DATE, time,
925 #define STORE_SET_IS_SLOW 1
926 #if STORE_SET_IS_SLOW
927 /* this is 3x faster on a directory with 20000 files */
928 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
929 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
930 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
931 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
932 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
933 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
934 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
935 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
936 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
937 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
938 #if FILEDATA_MARKS_SIZE != 10
939 #error this needs to be updated
942 FILE_COLUMN_COLOR, FALSE, -1);
944 #if !STORE_SET_IS_SLOW
947 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
948 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
957 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
962 gint num_ordered = 0;
963 gint num_prepended = 0;
965 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
971 auto fd = static_cast<FileData *>(work->data);
972 gboolean done = FALSE;
976 FileData *old_fd = nullptr;
977 gint old_version = 0;
981 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
982 FILE_COLUMN_POINTER, &old_fd,
983 FILE_COLUMN_VERSION, &old_version,
993 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
995 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
997 if (match == 0) g_warning("multiple fd for the same path");
1008 GtkTreeIter new_iter;
1013 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1018 here should be used gtk_tree_store_append, but this function seems to be O(n)
1019 and it seems to be much faster to add new entries to the beginning and reorder later
1022 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1025 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1026 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1028 if (g_list_find(selected, fd))
1030 /* renamed files - the same fd appears at different position - select it again*/
1031 GtkTreeSelection *selection;
1032 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1033 gtk_tree_selection_select_iter(selection, &new_iter);
1040 file_data_unref(old_fd);
1041 valid = gtk_tree_store_remove(store, &iter);
1046 if (fd->version != old_version || force)
1048 vflist_setup_iter(vf, store, &iter, fd);
1049 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1052 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1063 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1064 file_data_unref(old_fd);
1066 valid = gtk_tree_store_remove(store, &iter);
1069 /* move the prepended entries to the correct position */
1072 gint num_total = num_prepended + num_ordered;
1073 std::vector<gint> new_order;
1074 new_order.reserve(num_total);
1076 for (gint i = 0; i < num_ordered; i++)
1078 new_order.push_back(num_prepended + i);
1080 for (gint i = num_ordered; i < num_total; i++)
1082 new_order.push_back(num_total - 1 - i);
1084 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1088 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1091 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1092 GtkTreeStore *store;
1095 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1096 if (!vf->list) return;
1102 auto fd = static_cast<FileData *>(work->data);
1103 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1108 vf->sort_method = type;
1109 vf->sort_ascend = ascend;
1110 vf->sort_case = case_sensitive;
1112 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1114 std::vector<gint> new_order;
1115 new_order.reserve(i);
1120 auto fd = static_cast<FileData *>(work->data);
1121 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1125 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1126 gtk_tree_store_reorder(store, nullptr, new_order.data());
1128 g_hash_table_destroy(fd_idx_hash);
1132 *-----------------------------------------------------------------------------
1134 *-----------------------------------------------------------------------------
1138 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1143 auto fd = static_cast<FileData *>(work->data);
1146 if (fd->thumb_pixbuf) (*done)++;
1148 if (fd->sidecar_files)
1150 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1156 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1161 auto fd = static_cast<FileData *>(work->data);
1164 if (fd->metadata_in_idle_loaded) (*done)++;
1166 if (fd->sidecar_files)
1168 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1174 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1176 GtkTreeStore *store;
1179 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1181 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1182 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1185 FileData *vflist_thumb_next_fd(ViewFile *vf)
1188 FileData *fd = nullptr;
1190 /* first check the visible files */
1192 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1194 GtkTreeModel *store;
1196 gboolean valid = TRUE;
1198 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1199 gtk_tree_model_get_iter(store, &iter, tpath);
1200 gtk_tree_path_free(tpath);
1203 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1207 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1209 if (!nfd->thumb_pixbuf) fd = nfd;
1211 valid = gtk_tree_model_iter_next(store, &iter);
1215 /* then find first undone */
1219 GList *work = vf->list;
1222 auto fd_p = static_cast<FileData *>(work->data);
1223 if (!fd_p->thumb_pixbuf)
1227 GList *work2 = fd_p->sidecar_files;
1229 while (work2 && !fd)
1231 fd_p = static_cast<FileData *>(work2->data);
1232 if (!fd_p->thumb_pixbuf) fd = fd_p;
1233 work2 = work2->next;
1243 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1245 GtkTreeStore *store;
1252 gchar *formatted_with_stars;
1255 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1257 star_rating = metadata_read_rating_stars(fd);
1259 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1260 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1262 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1263 FILE_COLUMN_NAME, &name,
1264 FILE_COLUMN_SIDECARS, &sidecars,
1265 FILE_COLUMN_SIZE, &size,
1266 FILE_COLUMN_DATE, &time,
1267 FILE_COLUMN_EXPANDED, &expanded,
1270 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1272 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1273 FILE_COLUMN_EXPANDED, expanded,
1276 g_free(star_rating);
1277 g_free(formatted_with_stars);
1280 FileData *vflist_star_next_fd(ViewFile *vf)
1283 FileData *fd = nullptr;
1284 FileData *nfd = nullptr;
1286 /* first check the visible files */
1288 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1290 GtkTreeModel *store;
1292 gboolean valid = TRUE;
1294 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1295 gtk_tree_model_get_iter(store, &iter, tpath);
1296 gtk_tree_path_free(tpath);
1299 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1301 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1303 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1308 valid = gtk_tree_model_iter_next(store, &iter);
1313 vf->stars_filedata = fd;
1315 if (vf->stars_id == 0)
1317 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1322 /* then find first undone */
1326 GList *work = vf->list;
1330 auto fd_p = static_cast<FileData *>(work->data);
1332 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1346 vf->stars_filedata = fd;
1348 if (vf->stars_id == 0)
1350 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1359 *-----------------------------------------------------------------------------
1361 *-----------------------------------------------------------------------------
1364 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1373 auto list_fd = static_cast<FileData *>(work->data);
1374 if (list_fd == fd) return p;
1376 work2 = list_fd->sidecar_files;
1379 /** @FIXME return the same index also for sidecars
1380 it is sufficient for next/prev navigation but it should be rewritten
1381 without using indexes at all
1383 auto sidecar_fd = static_cast<FileData *>(work2->data);
1384 if (sidecar_fd == fd) return p;
1385 work2 = work2->next;
1396 *-----------------------------------------------------------------------------
1398 *-----------------------------------------------------------------------------
1401 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1403 GtkTreeModel *store;
1404 GtkTreeSelection *selection;
1407 gboolean found = FALSE;
1409 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1410 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1412 while (!found && work)
1414 auto tpath = static_cast<GtkTreePath *>(work->data);
1418 gtk_tree_model_get_iter(store, &iter, tpath);
1419 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1420 if (fd_n == fd) found = TRUE;
1423 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1428 #pragma GCC diagnostic push
1429 #pragma GCC diagnostic ignored "-Wunused-function"
1430 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1434 fd = vf_index_get_data(vf, row);
1435 return vflist_row_is_selected(vf, fd);
1437 #pragma GCC diagnostic pop
1439 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1441 GtkTreeModel *store;
1442 GtkTreeSelection *selection;
1446 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1447 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1457 auto tpath = static_cast<GtkTreePath *>(work->data);
1461 gtk_tree_model_get_iter(store, &iter, tpath);
1462 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1471 count = g_list_length(slist);
1472 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1477 GList *vflist_selection_get_list(ViewFile *vf)
1479 GtkTreeModel *store;
1480 GtkTreeSelection *selection;
1482 GList *list = nullptr;
1484 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1485 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1486 for (GList *work = g_list_last(slist); work; work = work->prev)
1488 auto tpath = static_cast<GtkTreePath *>(work->data);
1492 gtk_tree_model_get_iter(store, &iter, tpath);
1493 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1495 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1497 /* unexpanded - add whole group */
1498 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1501 list = g_list_prepend(list, file_data_ref(fd));
1503 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1508 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1510 GtkTreeModel *store;
1511 GtkTreeSelection *selection;
1513 GList *list = nullptr;
1516 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1517 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1521 auto tpath = static_cast<GtkTreePath *>(work->data);
1525 gtk_tree_model_get_iter(store, &iter, tpath);
1526 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1528 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1532 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1534 return g_list_reverse(list);
1537 void vflist_select_all(ViewFile *vf)
1539 GtkTreeSelection *selection;
1541 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1542 gtk_tree_selection_select_all(selection);
1544 VFLIST(vf)->select_fd = nullptr;
1547 void vflist_select_none(ViewFile *vf)
1549 GtkTreeSelection *selection;
1551 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1552 gtk_tree_selection_unselect_all(selection);
1555 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1560 tpath = gtk_tree_model_get_path(store, iter);
1561 result = gtk_tree_path_prev(tpath);
1563 gtk_tree_model_get_iter(store, iter, tpath);
1565 gtk_tree_path_free(tpath);
1570 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1572 if (!gtk_tree_model_get_iter_first(store, iter))
1577 GtkTreeIter next = *iter;
1579 if (gtk_tree_model_iter_next(store, &next))
1588 void vflist_select_invert(ViewFile *vf)
1591 GtkTreeSelection *selection;
1592 GtkTreeModel *store;
1595 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1596 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1598 /* Backward iteration prevents scrolling to the end of the list,
1599 * it scrolls to the first selected row instead. */
1600 valid = tree_model_get_iter_last(store, &iter);
1604 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1607 gtk_tree_selection_unselect_iter(selection, &iter);
1609 gtk_tree_selection_select_iter(selection, &iter);
1611 valid = tree_model_iter_prev(store, &iter);
1615 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1619 if (vflist_find_row(vf, fd, &iter) < 0) return;
1621 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1623 if (!vflist_row_is_selected(vf, fd))
1625 GtkTreeSelection *selection;
1626 GtkTreeModel *store;
1629 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1630 gtk_tree_selection_unselect_all(selection);
1631 gtk_tree_selection_select_iter(selection, &iter);
1632 vflist_move_cursor(vf, &iter);
1634 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1635 tpath = gtk_tree_model_get_path(store, &iter);
1636 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1637 gtk_tree_path_free(tpath);
1641 void vflist_select_list(ViewFile *vf, GList *list)
1652 fd = static_cast<FileData *>(work->data);
1654 if (vflist_find_row(vf, fd, &iter) < 0) return;
1655 if (!vflist_row_is_selected(vf, fd))
1657 GtkTreeSelection *selection;
1659 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1660 gtk_tree_selection_select_iter(selection, &iter);
1666 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1669 FileData *fd = nullptr;
1671 if (sel_fd->parent) sel_fd = sel_fd->parent;
1677 fd = static_cast<FileData *>(work->data);
1680 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1682 if (match >= 0) break;
1685 if (fd) vflist_select_by_fd(vf, fd);
1689 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1691 GtkTreeModel *store;
1693 GtkTreeSelection *selection;
1697 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1699 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1700 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1702 valid = gtk_tree_model_get_iter_first(store, &iter);
1708 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1710 mark_val = file_data_get_mark(fd, n);
1711 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1715 case MTS_MODE_SET: selected = mark_val;
1717 case MTS_MODE_OR: selected = mark_val || selected;
1719 case MTS_MODE_AND: selected = mark_val && selected;
1721 case MTS_MODE_MINUS: selected = !mark_val && selected;
1726 gtk_tree_selection_select_iter(selection, &iter);
1728 gtk_tree_selection_unselect_iter(selection, &iter);
1730 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1734 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1736 GtkTreeModel *store;
1737 GtkTreeSelection *selection;
1742 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1744 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1745 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1749 auto tpath = static_cast<GtkTreePath *>(work->data);
1753 gtk_tree_model_get_iter(store, &iter, tpath);
1754 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1756 /* the change has a very limited range and the standard notification would trigger
1757 complete re-read of the directory - try to do only minimal update instead */
1758 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1762 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1764 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1766 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1770 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1772 vf_refresh_idle(vf);
1776 /* mark functions can have various side effects - update all columns to be sure */
1777 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1778 /* mark functions can change sidecars too */
1779 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1783 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1787 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1791 *-----------------------------------------------------------------------------
1793 *-----------------------------------------------------------------------------
1796 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1798 GtkTreeViewColumn *column;
1799 GtkCellRenderer *cell;
1802 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1803 if (!column) return;
1805 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1807 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1809 cell = static_cast<GtkCellRenderer *>(list->data);
1812 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1813 gtk_tree_view_column_set_visible(column, thumb);
1815 if (options->show_star_rating)
1817 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1818 if (!column) return;
1819 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1820 gtk_tree_view_column_set_visible(column, TRUE);
1822 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1823 if (!column) return;
1824 gtk_tree_view_column_set_visible(column, FALSE);
1828 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1829 if (!column) return;
1830 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1831 gtk_tree_view_column_set_visible(column, TRUE);
1833 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1834 if (!column) return;
1835 gtk_tree_view_column_set_visible(column, FALSE);
1838 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1839 if (!column) return;
1840 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1842 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1843 if (!column) return;
1844 gtk_tree_view_column_set_visible(column, !multiline);
1846 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1847 if (!column) return;
1848 gtk_tree_view_column_set_visible(column, !multiline);
1851 static gboolean vflist_is_multiline(ViewFile *vf)
1853 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1857 static void vflist_populate_view(ViewFile *vf, gboolean force)
1859 GtkTreeStore *store;
1862 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1869 vflist_store_clear(vf, FALSE);
1874 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1876 selected = vflist_selection_get_list(vf);
1878 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1880 if (selected && vflist_selection_count(vf, nullptr) == 0)
1882 /* all selected files disappeared */
1883 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1886 filelist_free(selected);
1889 vf_thumb_update(vf);
1893 gboolean vflist_refresh(ViewFile *vf)
1896 gboolean ret = TRUE;
1898 old_list = vf->list;
1901 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1904 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1906 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1908 if (vf->marks_enabled)
1910 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1911 // each time a mark is changed.
1912 file_data_lock_list(vf->list);
1916 /** @FIXME only do this when needed (aka when we just switched from */
1917 /** @FIXME marks-enabled to marks-disabled) */
1918 file_data_unlock_list(vf->list);
1921 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1922 vf->list = g_list_first(vf->list);
1923 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1925 vf->list = g_list_first(vf->list);
1926 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1928 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1930 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1931 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1934 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1936 vflist_populate_view(vf, FALSE);
1938 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1940 filelist_free(old_list);
1941 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1947 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1949 static GdkRGBA color;
1950 static GtkWidget *done = nullptr;
1956 style = gtk_widget_get_style(widget);
1957 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1959 shift_color(&color, -1, 0);
1966 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1967 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1969 auto vf = static_cast<ViewFile *>(data);
1972 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1973 g_object_set(G_OBJECT(cell),
1974 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1975 "cell-background-set", set, NULL);
1978 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1980 GtkTreeViewColumn *column;
1981 GtkCellRenderer *renderer;
1983 column = gtk_tree_view_column_new();
1984 gtk_tree_view_column_set_title(column, title);
1985 gtk_tree_view_column_set_min_width(column, 4);
1989 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1990 renderer = gtk_cell_renderer_text_new();
1993 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1995 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1996 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1998 gtk_tree_view_column_set_expand(column, TRUE);
2002 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2003 renderer = gtk_cell_renderer_pixbuf_new();
2004 cell_renderer_height_override(renderer);
2005 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2006 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2009 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2010 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2011 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2013 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2016 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2018 auto vf = static_cast<ViewFile *>(data);
2019 GtkTreeStore *store;
2020 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2026 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2027 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2030 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2032 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2034 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2037 /* the change has a very limited range and the standard notification would trigger
2038 complete re-read of the directory - try to do only minimal update instead */
2039 file_data_unregister_notify_func(vf_notify_cb, vf);
2040 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2041 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2043 vf_refresh_idle(vf);
2047 /* mark functions can have various side effects - update all columns to be sure */
2048 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2049 /* mark functions can change sidecars too */
2050 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2052 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2054 gtk_tree_path_free(path);
2057 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2059 GtkTreeViewColumn *column;
2060 GtkCellRenderer *renderer;
2062 renderer = gtk_cell_renderer_toggle_new();
2063 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2065 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2066 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2067 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2069 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2070 gtk_tree_view_column_set_fixed_width(column, 22);
2071 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2074 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2078 *-----------------------------------------------------------------------------
2080 *-----------------------------------------------------------------------------
2083 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2086 if (!dir_fd) return FALSE;
2087 if (vf->dir_fd == dir_fd) return TRUE;
2089 file_data_unref(vf->dir_fd);
2090 vf->dir_fd = file_data_ref(dir_fd);
2092 /* force complete reload */
2093 vflist_store_clear(vf, TRUE);
2095 filelist_free(vf->list);
2098 ret = vf_refresh(vf);
2099 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2103 void vflist_destroy_cb(GtkWidget *, gpointer data)
2105 auto vf = static_cast<ViewFile *>(data);
2107 file_data_unregister_notify_func(vf_notify_cb, vf);
2109 vflist_select_idle_cancel(vf);
2110 vf_refresh_idle_cancel(vf);
2114 filelist_free(vf->list);
2117 ViewFile *vflist_new(ViewFile *vf, FileData *)
2119 GtkTreeStore *store;
2120 GtkTreeSelection *selection;
2121 GType flist_types[FILE_COLUMN_COUNT];
2125 vf->info = g_new0(ViewFileInfoList, 1);
2127 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2128 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2129 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2130 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2131 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2132 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2133 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2134 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2135 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2136 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2137 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2138 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2139 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2140 flist_types[i] = G_TYPE_BOOLEAN;
2142 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2144 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2145 g_object_unref(store);
2147 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2148 G_CALLBACK(vflist_expand_cb), vf);
2150 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2151 G_CALLBACK(vflist_collapse_cb), vf);
2153 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2154 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2155 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2157 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2158 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2160 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2164 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2166 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2167 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2171 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2172 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2175 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2176 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2179 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2180 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2183 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2184 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2187 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2188 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2191 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2192 g_assert(column == FILE_VIEW_COLUMN_DATE);
2195 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2199 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2201 if (VFLIST(vf)->thumbs_enabled == enable) return;
2203 VFLIST(vf)->thumbs_enabled = enable;
2205 /* vflist_populate_view is better than vf_refresh:
2206 - no need to re-read the directory
2207 - force update because the formatted string has changed
2211 vflist_populate_view(vf, TRUE);
2212 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2216 void vflist_marks_set(ViewFile *vf, gboolean enable)
2221 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2226 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2227 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2230 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2231 gtk_tree_view_column_set_visible(column, enable);
2236 // Previously disabled, which means that vf->list is complete
2237 file_data_lock_list(vf->list);
2241 // Previously enabled, which means that vf->list is incomplete
2244 g_list_free(columns);
2247 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */