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"
45 #include "ui-tree-edit.h"
46 #include "uri-utils.h"
48 #include "view-file.h"
50 /* Index to tree store */
52 FILE_COLUMN_POINTER = 0,
55 FILE_COLUMN_FORMATTED,
56 FILE_COLUMN_FORMATTED_WITH_STARS,
59 FILE_COLUMN_STAR_RATING,
65 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
70 /* Index to tree view */
72 FILE_VIEW_COLUMN_MARKS = 0,
73 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
74 FILE_VIEW_COLUMN_THUMB,
75 FILE_VIEW_COLUMN_FORMATTED,
76 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
77 FILE_VIEW_COLUMN_STAR_RATING,
78 FILE_VIEW_COLUMN_SIZE,
79 FILE_VIEW_COLUMN_DATE,
80 FILE_VIEW_COLUMN_COUNT
85 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
86 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
87 static void vflist_populate_view(ViewFile *vf, gboolean force);
88 static gboolean vflist_is_multiline(ViewFile *vf);
89 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
93 *-----------------------------------------------------------------------------
95 *-----------------------------------------------------------------------------
97 struct ViewFileFindRowData {
104 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
106 auto find = static_cast<ViewFileFindRowData *>(data);
108 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
119 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
122 ViewFileFindRowData data = {fd, iter, FALSE, 0};
124 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
125 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
135 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
138 GtkTreeViewColumn *column;
140 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
141 &tpath, &column, nullptr, nullptr))
147 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
148 gtk_tree_model_get_iter(store, &row, tpath);
149 gtk_tree_path_free(tpath);
150 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
158 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
161 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
163 /* it seems that gtk_tree_store_clear may call some callbacks
164 that use the column. Set the pointer to NULL to be safe. */
165 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
170 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
173 GList *files = nullptr;
175 if (unlock_files && vf->marks_enabled)
177 // unlock locked files in this directory
178 filelist_read(vf->dir_fd, &files, nullptr);
182 auto fd = static_cast<FileData *>(work->data);
184 file_data_unlock(fd);
185 file_data_unref(fd); // undo the ref that got added in filelist_read
190 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
191 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
192 gtk_tree_store_clear(GTK_TREE_STORE(store));
195 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
200 if (vflist_find_row(vf, fd, &iter) < 0) return;
201 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
202 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
205 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
210 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
212 tpath = gtk_tree_model_get_path(store, iter);
213 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
214 gtk_tree_path_free(tpath);
219 *-----------------------------------------------------------------------------
221 *-----------------------------------------------------------------------------
224 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
225 GtkSelectionData *selection_data, guint,
226 guint, gpointer data)
228 auto vf = static_cast<ViewFile *>(data);
229 GList *list = nullptr;
231 if (!vf->click_fd) return;
233 if (vflist_row_is_selected(vf, vf->click_fd))
235 list = vf_selection_get_list(vf);
239 list = g_list_append(nullptr, file_data_ref(vf->click_fd));
243 uri_selection_data_set_uris_from_filelist(selection_data, list);
247 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
249 auto vf = static_cast<ViewFile *>(data);
251 vflist_color_set(vf, vf->click_fd, TRUE);
253 if (VFLIST(vf)->thumbs_enabled &&
254 vf->click_fd && vf->click_fd->thumb_pixbuf)
258 if (vflist_row_is_selected(vf, vf->click_fd))
259 items = vf_selection_count(vf, nullptr);
263 dnd_set_drag_icon(widget, context, vf->click_fd->thumb_pixbuf, items);
267 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
269 auto vf = static_cast<ViewFile *>(data);
271 vflist_color_set(vf, vf->click_fd, FALSE);
273 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
279 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
280 int x, int y, GtkSelectionData *selection,
281 guint info, guint, gpointer data)
283 auto vf = static_cast<ViewFile *>(data);
285 if (info == TARGET_TEXT_PLAIN) {
286 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
289 /* Add keywords to file */
290 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
291 GList *kw_list = string_to_keywords_list(str);
293 metadata_append_list(fd, KEYWORD_KEY, kw_list);
294 g_list_free_full(kw_list, g_free);
300 void vflist_dnd_init(ViewFile *vf)
302 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
303 dnd_file_drag_types, dnd_file_drag_types_count,
304 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
305 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
306 dnd_file_drag_types, dnd_file_drag_types_count,
307 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
309 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
310 G_CALLBACK(vflist_dnd_get), vf);
311 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
312 G_CALLBACK(vflist_dnd_begin), vf);
313 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
314 G_CALLBACK(vflist_dnd_end), vf);
315 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
316 G_CALLBACK(vflist_drag_data_received), vf);
320 *-----------------------------------------------------------------------------
322 *-----------------------------------------------------------------------------
325 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
327 GList *list = nullptr;
329 if (fd->sidecar_files)
331 /* check if the row is expanded */
335 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
336 if (vflist_find_row(vf, fd, &iter) >= 0)
340 tpath = gtk_tree_model_get_path(store, &iter);
341 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
343 /* unexpanded - add whole group */
344 list = filelist_copy(fd->sidecar_files);
346 gtk_tree_path_free(tpath);
350 return g_list_prepend(list, file_data_ref(fd));
353 GList *vflist_pop_menu_file_list(ViewFile *vf)
355 if (!vf->click_fd) return nullptr;
357 if (vflist_row_is_selected(vf, vf->click_fd))
359 return vf_selection_get_list(vf);
361 return vflist_selection_get_one(vf, vf->click_fd);
365 void vflist_pop_menu_view_cb(ViewFile *vf)
367 if (vflist_row_is_selected(vf, vf->click_fd))
371 list = vf_selection_get_list(vf);
372 view_window_new_from_list(list);
377 view_window_new(vf->click_fd);
381 void vflist_pop_menu_rename_cb(ViewFile *vf)
385 list = vf_pop_menu_file_list(vf);
386 if (options->file_ops.enable_in_place_rename &&
387 list && !list->next && vf->click_fd)
394 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
395 if (vflist_find_row(vf, vf->click_fd, &iter) >= 0)
399 tpath = gtk_tree_model_get_path(store, &iter);
400 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
401 FILE_VIEW_COLUMN_FORMATTED, vf->click_fd->name,
402 vflist_row_rename_cb, vf);
403 gtk_tree_path_free(tpath);
408 file_util_rename(nullptr, list, vf->listview);
411 static void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
413 auto vf = static_cast<ViewFile *>(data);
415 vflist_color_set(vf, vf->click_fd, FALSE);
418 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
422 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
426 void vflist_pop_menu_add_items(ViewFile *vf, GtkWidget *menu)
428 menu_item_add_check(menu, _("Show _thumbnails"), VFLIST(vf)->thumbs_enabled,
429 G_CALLBACK(vflist_pop_menu_thumbs_cb), vf);
432 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
437 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
442 auto column = static_cast<GtkTreeViewColumn *>(work->data);
443 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
446 if (vflist_is_multiline(vf))
448 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
450 gtk_tree_view_column_set_visible(column, enable);
452 if (col_idx == FILE_COLUMN_FORMATTED)
454 gtk_tree_view_column_set_visible(column, !enable);
459 if (col_idx == FILE_COLUMN_STAR_RATING)
461 gtk_tree_view_column_set_visible(column, enable);
465 g_list_free(columns);
468 void vflist_pop_menu_show_star_rating_cb(ViewFile *vf)
470 vflist_populate_view(vf, TRUE);
472 vflist_color_set(vf, vf->click_fd, FALSE);
473 vflist_star_rating_set(vf, options->show_star_rating);
476 void vflist_pop_menu_refresh_cb(ViewFile *vf)
478 vflist_color_set(vf, vf->click_fd, FALSE);
480 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
483 void vflist_popup_destroy_cb(ViewFile *vf)
485 vflist_color_set(vf, vf->click_fd, FALSE);
490 *-----------------------------------------------------------------------------
492 *-----------------------------------------------------------------------------
495 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
497 auto vf = static_cast<ViewFile *>(data);
500 if (!new_name || !new_name[0]) return FALSE;
502 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
504 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
506 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
507 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
512 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
513 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
514 file_util_rename_simple(fd, new_path, vf->listview);
524 gboolean vflist_press_key_cb(ViewFile *vf, GtkWidget *widget, GdkEventKey *event)
528 if (event->keyval != GDK_KEY_Menu) return FALSE;
530 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
536 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
537 gtk_tree_model_get_iter(store, &iter, tpath);
538 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &vf->click_fd, -1);
539 gtk_tree_path_free(tpath);
543 vf->click_fd = nullptr;
546 vf->popup = vf_pop_menu(vf);
547 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
552 gboolean vflist_press_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
556 FileData *fd = nullptr;
557 GtkTreeViewColumn *column;
559 vf->clicked_mark = 0;
561 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
562 &tpath, &column, nullptr, nullptr))
565 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
567 if (bevent->button == MOUSE_BUTTON_LEFT &&
568 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
571 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
572 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
574 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
576 gtk_tree_model_get_iter(store, &iter, tpath);
577 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
578 gtk_tree_path_free(tpath);
583 if (bevent->button == MOUSE_BUTTON_RIGHT)
585 vf->popup = vf_pop_menu(vf);
586 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
590 if (!fd) return FALSE;
592 if (bevent->button == MOUSE_BUTTON_MIDDLE)
594 if (!vflist_row_is_selected(vf, fd))
596 vflist_color_set(vf, fd, TRUE);
602 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
603 !(bevent->state & GDK_SHIFT_MASK ) &&
604 !(bevent->state & GDK_CONTROL_MASK ) &&
605 vflist_row_is_selected(vf, fd))
607 GtkTreeSelection *selection;
609 gtk_widget_grab_focus(widget);
612 /* returning FALSE and further processing of the event is needed for
613 correct operation of the expander, to show the sidecar files.
614 It however resets the selection of multiple files. With this condition
615 it should work for both cases */
616 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
617 return (gtk_tree_selection_count_selected_rows(selection) > 1);
620 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
622 if (vf->click_fd->format_class == FORMAT_CLASS_COLLECTION)
624 collection_window_new(vf->click_fd->path);
628 if (vf->layout) layout_image_full_screen_start(vf->layout);
635 gboolean vflist_release_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
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, 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 && 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 && 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(const GList *list, gint &count, gint &done)
1132 for (const GList *work = list; work; work = work->next)
1134 auto fd = static_cast<FileData *>(work->data);
1136 if (fd->thumb_pixbuf) done++;
1138 if (fd->sidecar_files)
1140 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1146 void vflist_read_metadata_progress_count(const GList *list, gint &count, gint &done)
1148 for (const GList *work = list; work; work = work->next)
1150 auto fd = static_cast<FileData *>(work->data);
1152 if (fd->metadata_in_idle_loaded) done++;
1154 if (fd->sidecar_files)
1156 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1162 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1164 GtkTreeStore *store;
1167 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1169 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1170 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1173 FileData *vflist_thumb_next_fd(ViewFile *vf)
1176 FileData *fd = nullptr;
1178 /* first check the visible files */
1180 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1182 GtkTreeModel *store;
1184 gboolean valid = TRUE;
1186 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1187 gtk_tree_model_get_iter(store, &iter, tpath);
1188 gtk_tree_path_free(tpath);
1191 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1195 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1197 if (!nfd->thumb_pixbuf) fd = nfd;
1199 valid = gtk_tree_model_iter_next(store, &iter);
1203 /* then find first undone */
1207 GList *work = vf->list;
1210 auto fd_p = static_cast<FileData *>(work->data);
1211 if (!fd_p->thumb_pixbuf)
1215 GList *work2 = fd_p->sidecar_files;
1217 while (work2 && !fd)
1219 fd_p = static_cast<FileData *>(work2->data);
1220 if (!fd_p->thumb_pixbuf) fd = fd_p;
1221 work2 = work2->next;
1231 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1233 GtkTreeStore *store;
1240 gchar *formatted_with_stars;
1243 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1245 star_rating = metadata_read_rating_stars(fd);
1247 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1248 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1250 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1251 FILE_COLUMN_NAME, &name,
1252 FILE_COLUMN_SIDECARS, &sidecars,
1253 FILE_COLUMN_SIZE, &size,
1254 FILE_COLUMN_DATE, &time,
1255 FILE_COLUMN_EXPANDED, &expanded,
1258 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1260 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1261 FILE_COLUMN_EXPANDED, expanded,
1264 g_free(star_rating);
1265 g_free(formatted_with_stars);
1268 FileData *vflist_star_next_fd(ViewFile *vf)
1271 FileData *fd = nullptr;
1272 FileData *nfd = nullptr;
1274 /* first check the visible files */
1276 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1278 GtkTreeModel *store;
1280 gboolean valid = TRUE;
1282 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1283 gtk_tree_model_get_iter(store, &iter, tpath);
1284 gtk_tree_path_free(tpath);
1287 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1289 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1291 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1296 valid = gtk_tree_model_iter_next(store, &iter);
1301 vf->stars_filedata = fd;
1303 if (vf->stars_id == 0)
1305 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1310 /* then find first undone */
1314 GList *work = vf->list;
1318 auto fd_p = static_cast<FileData *>(work->data);
1320 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1334 vf->stars_filedata = fd;
1336 if (vf->stars_id == 0)
1338 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1347 *-----------------------------------------------------------------------------
1349 *-----------------------------------------------------------------------------
1352 gint vflist_index_by_fd(const ViewFile *vf, const FileData *fd)
1356 for (const GList *work = vf->list; work; work = work->next)
1358 auto list_fd = static_cast<FileData *>(work->data);
1359 if (list_fd == fd) return p;
1361 /** @FIXME return the same index also for sidecars
1362 it is sufficient for next/prev navigation but it should be rewritten
1363 without using indexes at all
1365 if (g_list_find(list_fd->sidecar_files, fd)) return p;
1374 *-----------------------------------------------------------------------------
1376 *-----------------------------------------------------------------------------
1379 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1381 GtkTreeModel *store;
1382 GtkTreeSelection *selection;
1385 gboolean found = FALSE;
1387 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1388 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1390 while (!found && work)
1392 auto tpath = static_cast<GtkTreePath *>(work->data);
1396 gtk_tree_model_get_iter(store, &iter, tpath);
1397 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1398 if (fd_n == fd) found = TRUE;
1401 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1406 #pragma GCC diagnostic push
1407 #pragma GCC diagnostic ignored "-Wunused-function"
1408 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1412 fd = vf_index_get_data(vf, row);
1413 return vflist_row_is_selected(vf, fd);
1415 #pragma GCC diagnostic pop
1417 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1419 GtkTreeModel *store;
1420 GtkTreeSelection *selection;
1424 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1425 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1435 auto tpath = static_cast<GtkTreePath *>(work->data);
1439 gtk_tree_model_get_iter(store, &iter, tpath);
1440 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1449 count = g_list_length(slist);
1450 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1455 GList *vflist_selection_get_list(ViewFile *vf)
1457 GtkTreeModel *store;
1458 GtkTreeSelection *selection;
1460 GList *list = nullptr;
1462 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1463 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1464 for (GList *work = g_list_last(slist); work; work = work->prev)
1466 auto tpath = static_cast<GtkTreePath *>(work->data);
1470 gtk_tree_model_get_iter(store, &iter, tpath);
1471 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1473 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1475 /* unexpanded - add whole group */
1476 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1479 list = g_list_prepend(list, file_data_ref(fd));
1481 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1486 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1488 GtkTreeModel *store;
1489 GtkTreeSelection *selection;
1491 GList *list = nullptr;
1494 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1495 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1499 auto tpath = static_cast<GtkTreePath *>(work->data);
1503 gtk_tree_model_get_iter(store, &iter, tpath);
1504 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1506 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1510 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1512 return g_list_reverse(list);
1515 void vflist_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
1517 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1518 GtkTreeModel *store;
1522 for (GList *work = gtk_tree_selection_get_selected_rows(selection, &store); work; work = work->next)
1524 auto *tpath = static_cast<GtkTreePath *>(work->data);
1526 gtk_tree_model_get_iter(store, &iter, tpath);
1527 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1533 void vflist_select_all(ViewFile *vf)
1535 GtkTreeSelection *selection;
1537 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1538 gtk_tree_selection_select_all(selection);
1540 VFLIST(vf)->select_fd = nullptr;
1543 void vflist_select_none(ViewFile *vf)
1545 GtkTreeSelection *selection;
1547 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1548 gtk_tree_selection_unselect_all(selection);
1551 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1556 tpath = gtk_tree_model_get_path(store, iter);
1557 result = gtk_tree_path_prev(tpath);
1559 gtk_tree_model_get_iter(store, iter, tpath);
1561 gtk_tree_path_free(tpath);
1566 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1568 if (!gtk_tree_model_get_iter_first(store, iter))
1573 GtkTreeIter next = *iter;
1575 if (gtk_tree_model_iter_next(store, &next))
1584 void vflist_select_invert(ViewFile *vf)
1587 GtkTreeSelection *selection;
1588 GtkTreeModel *store;
1591 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1592 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1594 /* Backward iteration prevents scrolling to the end of the list,
1595 * it scrolls to the first selected row instead. */
1596 valid = tree_model_get_iter_last(store, &iter);
1600 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1603 gtk_tree_selection_unselect_iter(selection, &iter);
1605 gtk_tree_selection_select_iter(selection, &iter);
1607 valid = tree_model_iter_prev(store, &iter);
1611 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1615 if (vflist_find_row(vf, fd, &iter) < 0) return;
1617 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1619 if (!vflist_row_is_selected(vf, fd))
1621 GtkTreeSelection *selection;
1622 GtkTreeModel *store;
1625 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1626 gtk_tree_selection_unselect_all(selection);
1627 gtk_tree_selection_select_iter(selection, &iter);
1628 vflist_move_cursor(vf, &iter);
1630 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1631 tpath = gtk_tree_model_get_path(store, &iter);
1632 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1633 gtk_tree_path_free(tpath);
1637 void vflist_select_list(ViewFile *vf, GList *list)
1648 fd = static_cast<FileData *>(work->data);
1650 if (vflist_find_row(vf, fd, &iter) < 0) return;
1651 if (!vflist_row_is_selected(vf, fd))
1653 GtkTreeSelection *selection;
1655 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1656 gtk_tree_selection_select_iter(selection, &iter);
1662 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1665 FileData *fd = nullptr;
1667 if (sel_fd->parent) sel_fd = sel_fd->parent;
1673 fd = static_cast<FileData *>(work->data);
1676 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1678 if (match >= 0) break;
1681 if (fd) vflist_select_by_fd(vf, fd);
1685 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1687 GtkTreeModel *store;
1689 GtkTreeSelection *selection;
1693 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1695 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1696 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1698 valid = gtk_tree_model_get_iter_first(store, &iter);
1704 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1706 mark_val = file_data_get_mark(fd, n);
1707 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1711 case MTS_MODE_SET: selected = mark_val;
1713 case MTS_MODE_OR: selected = mark_val || selected;
1715 case MTS_MODE_AND: selected = mark_val && selected;
1717 case MTS_MODE_MINUS: selected = !mark_val && selected;
1722 gtk_tree_selection_select_iter(selection, &iter);
1724 gtk_tree_selection_unselect_iter(selection, &iter);
1726 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1730 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1732 GtkTreeModel *store;
1733 GtkTreeSelection *selection;
1738 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1740 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1741 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1745 auto tpath = static_cast<GtkTreePath *>(work->data);
1749 gtk_tree_model_get_iter(store, &iter, tpath);
1750 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1752 /* the change has a very limited range and the standard notification would trigger
1753 complete re-read of the directory - try to do only minimal update instead */
1754 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1758 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1760 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1762 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1766 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1768 vf_refresh_idle(vf);
1772 /* mark functions can have various side effects - update all columns to be sure */
1773 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1774 /* mark functions can change sidecars too */
1775 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1779 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1783 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1787 *-----------------------------------------------------------------------------
1789 *-----------------------------------------------------------------------------
1792 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1794 GtkTreeViewColumn *column;
1795 GtkCellRenderer *cell;
1798 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1799 if (!column) return;
1801 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1803 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1805 cell = static_cast<GtkCellRenderer *>(list->data);
1808 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1809 gtk_tree_view_column_set_visible(column, thumb);
1811 if (options->show_star_rating)
1813 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1814 if (!column) return;
1815 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1816 gtk_tree_view_column_set_visible(column, TRUE);
1818 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1819 if (!column) return;
1820 gtk_tree_view_column_set_visible(column, FALSE);
1824 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1825 if (!column) return;
1826 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1827 gtk_tree_view_column_set_visible(column, TRUE);
1829 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1830 if (!column) return;
1831 gtk_tree_view_column_set_visible(column, FALSE);
1834 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1835 if (!column) return;
1836 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1838 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1839 if (!column) return;
1840 gtk_tree_view_column_set_visible(column, !multiline);
1842 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1843 if (!column) return;
1844 gtk_tree_view_column_set_visible(column, !multiline);
1847 static gboolean vflist_is_multiline(ViewFile *vf)
1849 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1853 static void vflist_populate_view(ViewFile *vf, gboolean force)
1855 GtkTreeStore *store;
1858 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1865 vflist_store_clear(vf, FALSE);
1870 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1872 selected = vflist_selection_get_list(vf);
1874 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1876 if (selected && vflist_selection_count(vf, nullptr) == 0)
1878 /* all selected files disappeared */
1879 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1882 filelist_free(selected);
1885 vf_thumb_update(vf);
1889 gboolean vflist_refresh(ViewFile *vf)
1892 gboolean ret = TRUE;
1894 old_list = vf->list;
1897 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1900 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1902 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1904 if (vf->marks_enabled)
1906 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1907 // each time a mark is changed.
1908 file_data_lock_list(vf->list);
1912 /** @FIXME only do this when needed (aka when we just switched from */
1913 /** @FIXME marks-enabled to marks-disabled) */
1914 file_data_unlock_list(vf->list);
1917 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1918 vf->list = g_list_first(vf->list);
1919 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1921 vf->list = g_list_first(vf->list);
1922 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1924 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1926 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1927 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1930 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1932 vflist_populate_view(vf, FALSE);
1934 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1936 filelist_free(old_list);
1937 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1943 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1945 static GdkRGBA color;
1946 static GtkWidget *done = nullptr;
1952 style = gtk_widget_get_style(widget);
1953 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1955 shift_color(&color, -1, 0);
1962 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1963 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1965 auto vf = static_cast<ViewFile *>(data);
1968 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1969 g_object_set(G_OBJECT(cell),
1970 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1971 "cell-background-set", set, NULL);
1974 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1976 GtkTreeViewColumn *column;
1977 GtkCellRenderer *renderer;
1979 column = gtk_tree_view_column_new();
1980 gtk_tree_view_column_set_title(column, title);
1981 gtk_tree_view_column_set_min_width(column, 4);
1985 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1986 renderer = gtk_cell_renderer_text_new();
1989 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1991 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1992 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1994 gtk_tree_view_column_set_expand(column, TRUE);
1998 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1999 renderer = gtk_cell_renderer_pixbuf_new();
2000 cell_renderer_height_override(renderer);
2001 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2002 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2005 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2006 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2007 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2009 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2012 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2014 auto vf = static_cast<ViewFile *>(data);
2015 GtkTreeStore *store;
2016 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2022 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2023 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2026 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2028 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2030 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2033 /* the change has a very limited range and the standard notification would trigger
2034 complete re-read of the directory - try to do only minimal update instead */
2035 file_data_unregister_notify_func(vf_notify_cb, vf);
2036 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2037 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2039 vf_refresh_idle(vf);
2043 /* mark functions can have various side effects - update all columns to be sure */
2044 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2045 /* mark functions can change sidecars too */
2046 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2048 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2050 gtk_tree_path_free(path);
2053 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2055 GtkTreeViewColumn *column;
2056 GtkCellRenderer *renderer;
2058 renderer = gtk_cell_renderer_toggle_new();
2059 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2061 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2062 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2063 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2065 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2066 gtk_tree_view_column_set_fixed_width(column, 22);
2067 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2070 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2074 *-----------------------------------------------------------------------------
2076 *-----------------------------------------------------------------------------
2079 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2082 if (!dir_fd) return FALSE;
2083 if (vf->dir_fd == dir_fd) return TRUE;
2085 file_data_unref(vf->dir_fd);
2086 vf->dir_fd = file_data_ref(dir_fd);
2088 /* force complete reload */
2089 vflist_store_clear(vf, TRUE);
2091 filelist_free(vf->list);
2094 ret = vf_refresh(vf);
2095 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2099 void vflist_destroy_cb(ViewFile *vf)
2101 file_data_unregister_notify_func(vf_notify_cb, vf);
2103 vflist_select_idle_cancel(vf);
2104 vf_refresh_idle_cancel(vf);
2108 filelist_free(vf->list);
2111 ViewFile *vflist_new(ViewFile *vf)
2113 GtkTreeStore *store;
2114 GtkTreeSelection *selection;
2115 GType flist_types[FILE_COLUMN_COUNT];
2119 vf->info = g_new0(ViewFileInfoList, 1);
2121 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2122 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2123 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2124 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2125 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2126 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2127 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2128 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2129 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2130 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2131 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2132 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2133 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2134 flist_types[i] = G_TYPE_BOOLEAN;
2136 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2138 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2139 g_object_unref(store);
2141 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2142 G_CALLBACK(vflist_expand_cb), vf);
2144 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2145 G_CALLBACK(vflist_collapse_cb), vf);
2147 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2148 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2149 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2151 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2152 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2154 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2158 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2160 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2161 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2165 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2166 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2169 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2170 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2173 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2174 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2177 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2178 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2181 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2182 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2185 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2186 g_assert(column == FILE_VIEW_COLUMN_DATE);
2189 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2193 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2195 if (VFLIST(vf)->thumbs_enabled == enable) return;
2197 VFLIST(vf)->thumbs_enabled = enable;
2199 /* vflist_populate_view is better than vf_refresh:
2200 - no need to re-read the directory
2201 - force update because the formatted string has changed
2205 vflist_populate_view(vf, TRUE);
2206 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2210 void vflist_marks_set(ViewFile *vf, gboolean enable)
2215 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2220 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2221 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2224 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2225 gtk_tree_view_column_set_visible(column, enable);
2230 // Previously disabled, which means that vf->list is complete
2231 file_data_lock_list(vf->list);
2235 // Previously enabled, which means that vf->list is incomplete
2238 g_list_free(columns);
2241 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */