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(const ViewFile *vf, const 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 = nullptr;
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 list = filelist_copy(fd->sidecar_files);
337 gtk_tree_path_free(tpath);
341 return g_list_prepend(list, file_data_ref(fd));
344 GList *vflist_pop_menu_file_list(ViewFile *vf)
346 if (!VFLIST(vf)->click_fd) return nullptr;
348 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
350 return vf_selection_get_list(vf);
352 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
356 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
358 auto vf = static_cast<ViewFile *>(data);
360 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
364 list = vf_selection_get_list(vf);
365 view_window_new_from_list(list);
370 view_window_new(VFLIST(vf)->click_fd);
374 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
376 auto vf = static_cast<ViewFile *>(data);
379 list = vf_pop_menu_file_list(vf);
380 if (options->file_ops.enable_in_place_rename &&
381 list && !list->next && VFLIST(vf)->click_fd)
388 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
389 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
393 tpath = gtk_tree_model_get_path(store, &iter);
394 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
395 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
396 vflist_row_rename_cb, vf);
397 gtk_tree_path_free(tpath);
402 file_util_rename(nullptr, list, vf->listview);
405 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
407 auto vf = static_cast<ViewFile *>(data);
409 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
412 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
416 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
420 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
425 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
430 auto column = static_cast<GtkTreeViewColumn *>(work->data);
431 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
434 if (vflist_is_multiline(vf))
436 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
438 gtk_tree_view_column_set_visible(column, enable);
440 if (col_idx == FILE_COLUMN_FORMATTED)
442 gtk_tree_view_column_set_visible(column, !enable);
447 if (col_idx == FILE_COLUMN_STAR_RATING)
449 gtk_tree_view_column_set_visible(column, enable);
453 g_list_free(columns);
456 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
458 auto vf = static_cast<ViewFile *>(data);
460 options->show_star_rating = !options->show_star_rating;
462 vflist_populate_view(vf, TRUE);
464 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
465 vflist_star_rating_set(vf, options->show_star_rating);
468 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
470 auto vf = static_cast<ViewFile *>(data);
472 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
474 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
477 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
479 auto vf = static_cast<ViewFile *>(data);
480 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
481 VFLIST(vf)->click_fd = nullptr;
487 *-----------------------------------------------------------------------------
489 *-----------------------------------------------------------------------------
492 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
494 auto vf = static_cast<ViewFile *>(data);
497 if (!new_name || !new_name[0]) return FALSE;
499 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
501 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
503 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
504 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
509 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
510 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
511 file_util_rename_simple(fd, new_path, vf->listview);
521 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
523 auto vf = static_cast<ViewFile *>(data);
526 if (event->keyval != GDK_KEY_Menu) return FALSE;
528 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
534 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
535 gtk_tree_model_get_iter(store, &iter, tpath);
536 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
537 gtk_tree_path_free(tpath);
541 VFLIST(vf)->click_fd = nullptr;
544 vf->popup = vf_pop_menu(vf);
545 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
550 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
552 auto vf = static_cast<ViewFile *>(data);
555 FileData *fd = nullptr;
556 GtkTreeViewColumn *column;
558 vf->clicked_mark = 0;
560 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
561 &tpath, &column, nullptr, nullptr))
564 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
566 if (bevent->button == MOUSE_BUTTON_LEFT &&
567 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
570 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
571 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
573 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
575 gtk_tree_model_get_iter(store, &iter, tpath);
576 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
577 gtk_tree_path_free(tpath);
580 VFLIST(vf)->click_fd = fd;
582 if (bevent->button == MOUSE_BUTTON_RIGHT)
584 vf->popup = vf_pop_menu(vf);
585 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
589 if (!fd) return FALSE;
591 if (bevent->button == MOUSE_BUTTON_MIDDLE)
593 if (!vflist_row_is_selected(vf, fd))
595 vflist_color_set(vf, fd, TRUE);
601 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
602 !(bevent->state & GDK_SHIFT_MASK ) &&
603 !(bevent->state & GDK_CONTROL_MASK ) &&
604 vflist_row_is_selected(vf, fd))
606 GtkTreeSelection *selection;
608 gtk_widget_grab_focus(widget);
611 /* returning FALSE and further processing of the event is needed for
612 correct operation of the expander, to show the sidecar files.
613 It however resets the selection of multiple files. With this condition
614 it should work for both cases */
615 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
616 return (gtk_tree_selection_count_selected_rows(selection) > 1);
619 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
621 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
623 collection_window_new(VFLIST(vf)->click_fd->path);
627 if (vf->layout) layout_image_full_screen_start(vf->layout);
634 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
636 auto vf = static_cast<ViewFile *>(data);
639 FileData *fd = nullptr;
641 if (defined_mouse_buttons(widget, bevent, vf->layout))
646 if (bevent->button == MOUSE_BUTTON_MIDDLE)
648 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
651 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
656 if ((bevent->x != 0 || bevent->y != 0) &&
657 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
658 &tpath, nullptr, nullptr, nullptr))
662 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
663 gtk_tree_model_get_iter(store, &iter, tpath);
664 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
665 gtk_tree_path_free(tpath);
668 if (bevent->button == MOUSE_BUTTON_MIDDLE)
670 if (fd && VFLIST(vf)->click_fd == fd)
672 GtkTreeSelection *selection;
674 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
675 if (vflist_row_is_selected(vf, fd))
677 gtk_tree_selection_unselect_iter(selection, &iter);
681 gtk_tree_selection_select_iter(selection, &iter);
687 if (fd && VFLIST(vf)->click_fd == fd &&
688 !(bevent->state & GDK_SHIFT_MASK ) &&
689 !(bevent->state & GDK_CONTROL_MASK ) &&
690 vflist_row_is_selected(vf, fd))
692 GtkTreeSelection *selection;
694 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
695 gtk_tree_selection_unselect_all(selection);
696 gtk_tree_selection_select_iter(selection, &iter);
697 vflist_move_cursor(vf, &iter);
703 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
705 FileData *read_ahead_fd = nullptr;
711 cur_fd = layout_image_get_fd(vf->layout);
712 if (sel_fd == cur_fd) return; /* no change */
714 row = g_list_index(vf->list, sel_fd);
715 /** @FIXME sidecar data */
717 if (sel_fd && options->image.enable_read_ahead && row >= 0)
719 if (row > g_list_index(vf->list, cur_fd) &&
720 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
722 read_ahead_fd = vf_index_get_data(vf, row + 1);
726 read_ahead_fd = vf_index_get_data(vf, row - 1);
730 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
733 static gboolean vflist_select_idle_cb(gpointer data)
735 auto vf = static_cast<ViewFile *>(data);
739 VFLIST(vf)->select_idle_id = 0;
740 return G_SOURCE_REMOVE;
745 if (VFLIST(vf)->select_fd)
747 vflist_select_image(vf, VFLIST(vf)->select_fd);
748 VFLIST(vf)->select_fd = nullptr;
751 VFLIST(vf)->select_idle_id = 0;
752 return G_SOURCE_REMOVE;
755 static void vflist_select_idle_cancel(ViewFile *vf)
757 if (VFLIST(vf)->select_idle_id)
759 g_source_remove(VFLIST(vf)->select_idle_id);
760 VFLIST(vf)->select_idle_id = 0;
764 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
766 auto vf = static_cast<ViewFile *>(data);
768 GtkTreePath *cursor_path;
770 VFLIST(vf)->select_fd = nullptr;
772 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
774 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
777 gtk_tree_model_get_iter(store, &iter, cursor_path);
778 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
779 gtk_tree_path_free(cursor_path);
784 !VFLIST(vf)->select_idle_id)
786 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
792 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
794 auto vf = static_cast<ViewFile *>(data);
795 vflist_set_expanded(vf, iter, TRUE);
798 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
800 auto vf = static_cast<ViewFile *>(data);
801 vflist_set_expanded(vf, iter, FALSE);
805 *-----------------------------------------------------------------------------
807 *-----------------------------------------------------------------------------
811 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)
813 gboolean multiline = vflist_is_multiline(vf);
820 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
824 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
829 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
834 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
842 gchar *formatted_with_stars;
844 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
846 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
847 FILE_COLUMN_NAME, &name,
848 FILE_COLUMN_SIDECARS, &sidecars,
849 FILE_COLUMN_SIZE, &size,
850 FILE_COLUMN_DATE, &time,
851 FILE_COLUMN_STAR_RATING, &star_rating,
854 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
855 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
857 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
858 FILE_COLUMN_EXPANDED, expanded,
860 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
861 FILE_COLUMN_EXPANDED, expanded,
868 g_free(formatted_with_stars);
871 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
874 gchar *sidecars = nullptr;
876 const gchar *time = text_from_time(fd->date);
877 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
878 const gchar *disabled_grouping;
880 gchar *formatted_with_stars;
881 gboolean expanded = FALSE;
884 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
886 star_rating = convert_rating_to_stars(fd->rating);
890 star_rating = nullptr;
893 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
895 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
898 sidecars = file_data_sc_list_to_string(fd);
900 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
901 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
902 size = text_from_size(fd->size);
904 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
905 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
907 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
908 FILE_COLUMN_VERSION, fd->version,
909 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
910 FILE_COLUMN_FORMATTED, formatted,
911 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
912 FILE_COLUMN_SIDECARS, sidecars,
913 FILE_COLUMN_NAME, name,
914 FILE_COLUMN_STAR_RATING, star_rating,
915 FILE_COLUMN_SIZE, size,
916 FILE_COLUMN_DATE, time,
917 #define STORE_SET_IS_SLOW 1
918 #if STORE_SET_IS_SLOW
919 /* this is 3x faster on a directory with 20000 files */
920 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
921 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
922 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
923 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
924 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
925 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
926 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
927 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
928 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
929 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
930 #if FILEDATA_MARKS_SIZE != 10
931 #error this needs to be updated
934 FILE_COLUMN_COLOR, FALSE, -1);
936 #if !STORE_SET_IS_SLOW
939 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
940 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
949 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
954 gint num_ordered = 0;
955 gint num_prepended = 0;
957 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
963 auto fd = static_cast<FileData *>(work->data);
964 gboolean done = FALSE;
968 FileData *old_fd = nullptr;
969 gint old_version = 0;
973 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
974 FILE_COLUMN_POINTER, &old_fd,
975 FILE_COLUMN_VERSION, &old_version,
985 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
987 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
989 if (match == 0) g_warning("multiple fd for the same path");
1000 GtkTreeIter new_iter;
1005 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1010 here should be used gtk_tree_store_append, but this function seems to be O(n)
1011 and it seems to be much faster to add new entries to the beginning and reorder later
1014 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1017 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1018 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1020 if (g_list_find(selected, fd))
1022 /* renamed files - the same fd appears at different position - select it again*/
1023 GtkTreeSelection *selection;
1024 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1025 gtk_tree_selection_select_iter(selection, &new_iter);
1032 file_data_unref(old_fd);
1033 valid = gtk_tree_store_remove(store, &iter);
1038 if (fd->version != old_version || force)
1040 vflist_setup_iter(vf, store, &iter, fd);
1041 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1044 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1055 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1056 file_data_unref(old_fd);
1058 valid = gtk_tree_store_remove(store, &iter);
1061 /* move the prepended entries to the correct position */
1064 gint num_total = num_prepended + num_ordered;
1065 std::vector<gint> new_order;
1066 new_order.reserve(num_total);
1068 for (gint i = 0; i < num_ordered; i++)
1070 new_order.push_back(num_prepended + i);
1072 for (gint i = num_ordered; i < num_total; i++)
1074 new_order.push_back(num_total - 1 - i);
1076 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1080 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1083 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1084 GtkTreeStore *store;
1087 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1088 if (!vf->list) return;
1094 auto fd = static_cast<FileData *>(work->data);
1095 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1100 vf->sort_method = type;
1101 vf->sort_ascend = ascend;
1102 vf->sort_case = case_sensitive;
1104 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1106 std::vector<gint> new_order;
1107 new_order.reserve(i);
1112 auto fd = static_cast<FileData *>(work->data);
1113 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1117 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1118 gtk_tree_store_reorder(store, nullptr, new_order.data());
1120 g_hash_table_destroy(fd_idx_hash);
1124 *-----------------------------------------------------------------------------
1126 *-----------------------------------------------------------------------------
1130 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1135 auto fd = static_cast<FileData *>(work->data);
1138 if (fd->thumb_pixbuf) (*done)++;
1140 if (fd->sidecar_files)
1142 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1148 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1153 auto fd = static_cast<FileData *>(work->data);
1156 if (fd->metadata_in_idle_loaded) (*done)++;
1158 if (fd->sidecar_files)
1160 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1166 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1168 GtkTreeStore *store;
1171 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1173 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1174 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1177 FileData *vflist_thumb_next_fd(ViewFile *vf)
1180 FileData *fd = nullptr;
1182 /* first check the visible files */
1184 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1186 GtkTreeModel *store;
1188 gboolean valid = TRUE;
1190 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1191 gtk_tree_model_get_iter(store, &iter, tpath);
1192 gtk_tree_path_free(tpath);
1195 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1199 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1201 if (!nfd->thumb_pixbuf) fd = nfd;
1203 valid = gtk_tree_model_iter_next(store, &iter);
1207 /* then find first undone */
1211 GList *work = vf->list;
1214 auto fd_p = static_cast<FileData *>(work->data);
1215 if (!fd_p->thumb_pixbuf)
1219 GList *work2 = fd_p->sidecar_files;
1221 while (work2 && !fd)
1223 fd_p = static_cast<FileData *>(work2->data);
1224 if (!fd_p->thumb_pixbuf) fd = fd_p;
1225 work2 = work2->next;
1235 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1237 GtkTreeStore *store;
1244 gchar *formatted_with_stars;
1247 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1249 star_rating = metadata_read_rating_stars(fd);
1251 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1252 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1254 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1255 FILE_COLUMN_NAME, &name,
1256 FILE_COLUMN_SIDECARS, &sidecars,
1257 FILE_COLUMN_SIZE, &size,
1258 FILE_COLUMN_DATE, &time,
1259 FILE_COLUMN_EXPANDED, &expanded,
1262 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1264 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1265 FILE_COLUMN_EXPANDED, expanded,
1268 g_free(star_rating);
1269 g_free(formatted_with_stars);
1272 FileData *vflist_star_next_fd(ViewFile *vf)
1275 FileData *fd = nullptr;
1276 FileData *nfd = nullptr;
1278 /* first check the visible files */
1280 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1282 GtkTreeModel *store;
1284 gboolean valid = TRUE;
1286 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1287 gtk_tree_model_get_iter(store, &iter, tpath);
1288 gtk_tree_path_free(tpath);
1291 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1293 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1295 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1300 valid = gtk_tree_model_iter_next(store, &iter);
1305 vf->stars_filedata = fd;
1307 if (vf->stars_id == 0)
1309 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1314 /* then find first undone */
1318 GList *work = vf->list;
1322 auto fd_p = static_cast<FileData *>(work->data);
1324 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1338 vf->stars_filedata = fd;
1340 if (vf->stars_id == 0)
1342 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1351 *-----------------------------------------------------------------------------
1353 *-----------------------------------------------------------------------------
1356 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1365 auto list_fd = static_cast<FileData *>(work->data);
1366 if (list_fd == fd) return p;
1368 work2 = list_fd->sidecar_files;
1371 /** @FIXME return the same index also for sidecars
1372 it is sufficient for next/prev navigation but it should be rewritten
1373 without using indexes at all
1375 auto sidecar_fd = static_cast<FileData *>(work2->data);
1376 if (sidecar_fd == fd) return p;
1377 work2 = work2->next;
1388 *-----------------------------------------------------------------------------
1390 *-----------------------------------------------------------------------------
1393 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1395 GtkTreeModel *store;
1396 GtkTreeSelection *selection;
1399 gboolean found = FALSE;
1401 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1402 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1404 while (!found && work)
1406 auto tpath = static_cast<GtkTreePath *>(work->data);
1410 gtk_tree_model_get_iter(store, &iter, tpath);
1411 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1412 if (fd_n == fd) found = TRUE;
1415 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1420 #pragma GCC diagnostic push
1421 #pragma GCC diagnostic ignored "-Wunused-function"
1422 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1426 fd = vf_index_get_data(vf, row);
1427 return vflist_row_is_selected(vf, fd);
1429 #pragma GCC diagnostic pop
1431 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1433 GtkTreeModel *store;
1434 GtkTreeSelection *selection;
1438 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1439 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1449 auto tpath = static_cast<GtkTreePath *>(work->data);
1453 gtk_tree_model_get_iter(store, &iter, tpath);
1454 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1463 count = g_list_length(slist);
1464 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1469 GList *vflist_selection_get_list(ViewFile *vf)
1471 GtkTreeModel *store;
1472 GtkTreeSelection *selection;
1474 GList *list = nullptr;
1476 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1477 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1478 for (GList *work = g_list_last(slist); work; work = work->prev)
1480 auto tpath = static_cast<GtkTreePath *>(work->data);
1484 gtk_tree_model_get_iter(store, &iter, tpath);
1485 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1487 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1489 /* unexpanded - add whole group */
1490 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1493 list = g_list_prepend(list, file_data_ref(fd));
1495 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1500 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1502 GtkTreeModel *store;
1503 GtkTreeSelection *selection;
1505 GList *list = nullptr;
1508 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1509 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1513 auto tpath = static_cast<GtkTreePath *>(work->data);
1517 gtk_tree_model_get_iter(store, &iter, tpath);
1518 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1520 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1524 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1526 return g_list_reverse(list);
1529 void vflist_select_all(ViewFile *vf)
1531 GtkTreeSelection *selection;
1533 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1534 gtk_tree_selection_select_all(selection);
1536 VFLIST(vf)->select_fd = nullptr;
1539 void vflist_select_none(ViewFile *vf)
1541 GtkTreeSelection *selection;
1543 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1544 gtk_tree_selection_unselect_all(selection);
1547 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1552 tpath = gtk_tree_model_get_path(store, iter);
1553 result = gtk_tree_path_prev(tpath);
1555 gtk_tree_model_get_iter(store, iter, tpath);
1557 gtk_tree_path_free(tpath);
1562 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1564 if (!gtk_tree_model_get_iter_first(store, iter))
1569 GtkTreeIter next = *iter;
1571 if (gtk_tree_model_iter_next(store, &next))
1580 void vflist_select_invert(ViewFile *vf)
1583 GtkTreeSelection *selection;
1584 GtkTreeModel *store;
1587 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1588 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1590 /* Backward iteration prevents scrolling to the end of the list,
1591 * it scrolls to the first selected row instead. */
1592 valid = tree_model_get_iter_last(store, &iter);
1596 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1599 gtk_tree_selection_unselect_iter(selection, &iter);
1601 gtk_tree_selection_select_iter(selection, &iter);
1603 valid = tree_model_iter_prev(store, &iter);
1607 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1611 if (vflist_find_row(vf, fd, &iter) < 0) return;
1613 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1615 if (!vflist_row_is_selected(vf, fd))
1617 GtkTreeSelection *selection;
1618 GtkTreeModel *store;
1621 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1622 gtk_tree_selection_unselect_all(selection);
1623 gtk_tree_selection_select_iter(selection, &iter);
1624 vflist_move_cursor(vf, &iter);
1626 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1627 tpath = gtk_tree_model_get_path(store, &iter);
1628 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1629 gtk_tree_path_free(tpath);
1633 void vflist_select_list(ViewFile *vf, GList *list)
1644 fd = static_cast<FileData *>(work->data);
1646 if (vflist_find_row(vf, fd, &iter) < 0) return;
1647 if (!vflist_row_is_selected(vf, fd))
1649 GtkTreeSelection *selection;
1651 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1652 gtk_tree_selection_select_iter(selection, &iter);
1658 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1661 FileData *fd = nullptr;
1663 if (sel_fd->parent) sel_fd = sel_fd->parent;
1669 fd = static_cast<FileData *>(work->data);
1672 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1674 if (match >= 0) break;
1677 if (fd) vflist_select_by_fd(vf, fd);
1681 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1683 GtkTreeModel *store;
1685 GtkTreeSelection *selection;
1689 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1691 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1692 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1694 valid = gtk_tree_model_get_iter_first(store, &iter);
1700 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1702 mark_val = file_data_get_mark(fd, n);
1703 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1707 case MTS_MODE_SET: selected = mark_val;
1709 case MTS_MODE_OR: selected = mark_val || selected;
1711 case MTS_MODE_AND: selected = mark_val && selected;
1713 case MTS_MODE_MINUS: selected = !mark_val && selected;
1718 gtk_tree_selection_select_iter(selection, &iter);
1720 gtk_tree_selection_unselect_iter(selection, &iter);
1722 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1726 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1728 GtkTreeModel *store;
1729 GtkTreeSelection *selection;
1734 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1736 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1737 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1741 auto tpath = static_cast<GtkTreePath *>(work->data);
1745 gtk_tree_model_get_iter(store, &iter, tpath);
1746 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1748 /* the change has a very limited range and the standard notification would trigger
1749 complete re-read of the directory - try to do only minimal update instead */
1750 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1754 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1756 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1758 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1762 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1764 vf_refresh_idle(vf);
1768 /* mark functions can have various side effects - update all columns to be sure */
1769 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1770 /* mark functions can change sidecars too */
1771 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1775 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1779 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1783 *-----------------------------------------------------------------------------
1785 *-----------------------------------------------------------------------------
1788 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1790 GtkTreeViewColumn *column;
1791 GtkCellRenderer *cell;
1794 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1795 if (!column) return;
1797 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1799 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1801 cell = static_cast<GtkCellRenderer *>(list->data);
1804 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1805 gtk_tree_view_column_set_visible(column, thumb);
1807 if (options->show_star_rating)
1809 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1810 if (!column) return;
1811 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1812 gtk_tree_view_column_set_visible(column, TRUE);
1814 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1815 if (!column) return;
1816 gtk_tree_view_column_set_visible(column, FALSE);
1820 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1821 if (!column) return;
1822 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1823 gtk_tree_view_column_set_visible(column, TRUE);
1825 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1826 if (!column) return;
1827 gtk_tree_view_column_set_visible(column, FALSE);
1830 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1831 if (!column) return;
1832 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1834 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1835 if (!column) return;
1836 gtk_tree_view_column_set_visible(column, !multiline);
1838 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1839 if (!column) return;
1840 gtk_tree_view_column_set_visible(column, !multiline);
1843 static gboolean vflist_is_multiline(ViewFile *vf)
1845 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1849 static void vflist_populate_view(ViewFile *vf, gboolean force)
1851 GtkTreeStore *store;
1854 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1861 vflist_store_clear(vf, FALSE);
1866 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1868 selected = vflist_selection_get_list(vf);
1870 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1872 if (selected && vflist_selection_count(vf, nullptr) == 0)
1874 /* all selected files disappeared */
1875 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1878 filelist_free(selected);
1881 vf_thumb_update(vf);
1885 gboolean vflist_refresh(ViewFile *vf)
1888 gboolean ret = TRUE;
1890 old_list = vf->list;
1893 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1896 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1898 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1900 if (vf->marks_enabled)
1902 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1903 // each time a mark is changed.
1904 file_data_lock_list(vf->list);
1908 /** @FIXME only do this when needed (aka when we just switched from */
1909 /** @FIXME marks-enabled to marks-disabled) */
1910 file_data_unlock_list(vf->list);
1913 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1914 vf->list = g_list_first(vf->list);
1915 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1917 vf->list = g_list_first(vf->list);
1918 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1920 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1922 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1923 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1926 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1928 vflist_populate_view(vf, FALSE);
1930 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1932 filelist_free(old_list);
1933 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1940 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1943 CELL_HEIGHT_OVERRIDE = 512
1946 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1950 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1951 if (spec && G_IS_PARAM_SPEC_INT(spec))
1953 GParamSpecInt *spec_int;
1955 spec_int = G_PARAM_SPEC_INT(spec);
1956 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1960 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1962 static GdkRGBA color;
1963 static GdkRGBA color_style;
1964 static GtkWidget *done = nullptr;
1970 style = gtk_widget_get_style(widget);
1971 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color_style);
1973 memcpy(&color, &color_style, sizeof(color));
1974 shift_color(&color, -1, 0);
1981 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1982 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1984 auto vf = static_cast<ViewFile *>(data);
1987 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1988 g_object_set(G_OBJECT(cell),
1989 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1990 "cell-background-set", set, NULL);
1993 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1995 GtkTreeViewColumn *column;
1996 GtkCellRenderer *renderer;
1998 column = gtk_tree_view_column_new();
1999 gtk_tree_view_column_set_title(column, title);
2000 gtk_tree_view_column_set_min_width(column, 4);
2004 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2005 renderer = gtk_cell_renderer_text_new();
2008 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2010 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2011 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2013 gtk_tree_view_column_set_expand(column, TRUE);
2017 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2018 renderer = gtk_cell_renderer_pixbuf_new();
2019 cell_renderer_height_override(renderer);
2020 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2021 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2024 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2025 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2026 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2028 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2031 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2033 auto vf = static_cast<ViewFile *>(data);
2034 GtkTreeStore *store;
2035 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2041 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2042 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2045 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2047 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2049 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2052 /* the change has a very limited range and the standard notification would trigger
2053 complete re-read of the directory - try to do only minimal update instead */
2054 file_data_unregister_notify_func(vf_notify_cb, vf);
2055 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2056 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2058 vf_refresh_idle(vf);
2062 /* mark functions can have various side effects - update all columns to be sure */
2063 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2064 /* mark functions can change sidecars too */
2065 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2067 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2069 gtk_tree_path_free(path);
2072 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2074 GtkTreeViewColumn *column;
2075 GtkCellRenderer *renderer;
2077 renderer = gtk_cell_renderer_toggle_new();
2078 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2080 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2081 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2082 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2084 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2085 gtk_tree_view_column_set_fixed_width(column, 22);
2086 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2089 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2093 *-----------------------------------------------------------------------------
2095 *-----------------------------------------------------------------------------
2098 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2101 if (!dir_fd) return FALSE;
2102 if (vf->dir_fd == dir_fd) return TRUE;
2104 file_data_unref(vf->dir_fd);
2105 vf->dir_fd = file_data_ref(dir_fd);
2107 /* force complete reload */
2108 vflist_store_clear(vf, TRUE);
2110 filelist_free(vf->list);
2113 ret = vf_refresh(vf);
2114 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2118 void vflist_destroy_cb(GtkWidget *, gpointer data)
2120 auto vf = static_cast<ViewFile *>(data);
2122 file_data_unregister_notify_func(vf_notify_cb, vf);
2124 vflist_select_idle_cancel(vf);
2125 vf_refresh_idle_cancel(vf);
2129 filelist_free(vf->list);
2132 ViewFile *vflist_new(ViewFile *vf, FileData *)
2134 GtkTreeStore *store;
2135 GtkTreeSelection *selection;
2136 GType flist_types[FILE_COLUMN_COUNT];
2140 vf->info = g_new0(ViewFileInfoList, 1);
2142 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2143 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2144 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2145 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2146 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2147 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2148 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2149 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2150 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2151 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2152 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2153 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2154 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2155 flist_types[i] = G_TYPE_BOOLEAN;
2157 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2159 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2160 g_object_unref(store);
2162 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2163 G_CALLBACK(vflist_expand_cb), vf);
2165 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2166 G_CALLBACK(vflist_collapse_cb), vf);
2168 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2169 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2170 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2172 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2173 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2175 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2179 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2181 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2182 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2186 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2187 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2190 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2191 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2194 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2195 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2198 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2199 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2202 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2203 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2206 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2207 g_assert(column == FILE_VIEW_COLUMN_DATE);
2210 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2214 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2216 if (VFLIST(vf)->thumbs_enabled == enable) return;
2218 VFLIST(vf)->thumbs_enabled = enable;
2220 /* vflist_populate_view is better than vf_refresh:
2221 - no need to re-read the directory
2222 - force update because the formatted string has changed
2226 vflist_populate_view(vf, TRUE);
2227 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2231 void vflist_marks_set(ViewFile *vf, gboolean enable)
2236 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2241 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2242 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2245 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2246 gtk_tree_view_column_set_visible(column, enable);
2251 // Previously disabled, which means that vf->list is complete
2252 file_data_lock_list(vf->list);
2256 // Previously enabled, which means that vf->list is incomplete
2259 g_list_free(columns);
2262 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */