2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "view-file-list.h"
29 #include "layout-image.h"
33 #include "ui-fileops.h"
35 #include "ui-tree-edit.h"
36 #include "uri-utils.h"
37 #include "view-file.h"
41 /* Index to tree store */
43 FILE_COLUMN_POINTER = 0,
46 FILE_COLUMN_FORMATTED,
47 FILE_COLUMN_FORMATTED_WITH_STARS,
50 FILE_COLUMN_STAR_RATING,
56 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
61 /* Index to tree view */
63 FILE_VIEW_COLUMN_MARKS = 0,
64 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
65 FILE_VIEW_COLUMN_THUMB,
66 FILE_VIEW_COLUMN_FORMATTED,
67 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
68 FILE_VIEW_COLUMN_STAR_RATING,
69 FILE_VIEW_COLUMN_SIZE,
70 FILE_VIEW_COLUMN_DATE,
71 FILE_VIEW_COLUMN_COUNT
76 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
77 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
78 static void vflist_populate_view(ViewFile *vf, gboolean force);
79 static gboolean vflist_is_multiline(ViewFile *vf);
80 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
84 *-----------------------------------------------------------------------------
86 *-----------------------------------------------------------------------------
88 struct ViewFileFindRowData {
95 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
97 auto find = static_cast<ViewFileFindRowData *>(data);
99 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
110 static gint vflist_find_row(ViewFile *vf, FileData *fd, GtkTreeIter *iter)
113 ViewFileFindRowData data = {fd, iter, FALSE, 0};
115 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
116 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
126 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
129 GtkTreeViewColumn *column;
131 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
132 &tpath, &column, nullptr, nullptr))
138 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
139 gtk_tree_model_get_iter(store, &row, tpath);
140 gtk_tree_path_free(tpath);
141 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
149 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
152 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
154 /* it seems that gtk_tree_store_clear may call some callbacks
155 that use the column. Set the pointer to NULL to be safe. */
156 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
161 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
164 GList *files = nullptr;
166 if (unlock_files && vf->marks_enabled)
168 // unlock locked files in this directory
169 filelist_read(vf->dir_fd, &files, nullptr);
173 auto fd = static_cast<FileData *>(work->data);
175 file_data_unlock(fd);
176 file_data_unref(fd); // undo the ref that got added in filelist_read
181 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
182 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
183 gtk_tree_store_clear(GTK_TREE_STORE(store));
186 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
191 if (vflist_find_row(vf, fd, &iter) < 0) return;
192 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
193 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
196 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
201 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
203 tpath = gtk_tree_model_get_path(store, iter);
204 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
205 gtk_tree_path_free(tpath);
210 *-----------------------------------------------------------------------------
212 *-----------------------------------------------------------------------------
215 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
216 GtkSelectionData *selection_data, guint,
217 guint, gpointer data)
219 auto vf = static_cast<ViewFile *>(data);
220 GList *list = nullptr;
222 if (!VFLIST(vf)->click_fd) return;
224 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
226 list = vf_selection_get_list(vf);
230 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
234 uri_selection_data_set_uris_from_filelist(selection_data, list);
238 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
240 auto vf = static_cast<ViewFile *>(data);
242 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
244 if (VFLIST(vf)->thumbs_enabled &&
245 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
249 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
250 items = vf_selection_count(vf, nullptr);
254 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
258 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
260 auto vf = static_cast<ViewFile *>(data);
262 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
264 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
270 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
271 int x, int y, GtkSelectionData *selection,
272 guint info, guint, gpointer data)
274 auto vf = static_cast<ViewFile *>(data);
276 if (info == TARGET_TEXT_PLAIN) {
277 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
280 /* Add keywords to file */
281 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
282 GList *kw_list = string_to_keywords_list(str);
284 metadata_append_list(fd, KEYWORD_KEY, kw_list);
285 g_list_free_full(kw_list, g_free);
291 void vflist_dnd_init(ViewFile *vf)
293 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
294 dnd_file_drag_types, dnd_file_drag_types_count,
295 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
296 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
297 dnd_file_drag_types, dnd_file_drag_types_count,
298 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
300 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
301 G_CALLBACK(vflist_dnd_get), vf);
302 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
303 G_CALLBACK(vflist_dnd_begin), vf);
304 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
305 G_CALLBACK(vflist_dnd_end), vf);
306 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
307 G_CALLBACK(vflist_drag_data_received), vf);
311 *-----------------------------------------------------------------------------
313 *-----------------------------------------------------------------------------
316 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
318 GList *list = g_list_append(nullptr, file_data_ref(fd));
320 if (fd->sidecar_files)
322 /* check if the row is expanded */
326 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
327 if (vflist_find_row(vf, fd, &iter) >= 0)
331 tpath = gtk_tree_model_get_path(store, &iter);
332 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
334 /* unexpanded - add whole group */
335 GList *work = fd->sidecar_files;
338 auto sfd = static_cast<FileData *>(work->data);
339 list = g_list_prepend(list, file_data_ref(sfd));
343 gtk_tree_path_free(tpath);
345 list = g_list_reverse(list);
351 GList *vflist_pop_menu_file_list(ViewFile *vf)
353 if (!VFLIST(vf)->click_fd) return nullptr;
355 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
357 return vf_selection_get_list(vf);
359 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
363 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
365 auto vf = static_cast<ViewFile *>(data);
367 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
371 list = vf_selection_get_list(vf);
372 view_window_new_from_list(list);
377 view_window_new(VFLIST(vf)->click_fd);
381 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
383 auto vf = static_cast<ViewFile *>(data);
386 list = vf_pop_menu_file_list(vf);
387 if (options->file_ops.enable_in_place_rename &&
388 list && !list->next && VFLIST(vf)->click_fd)
395 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
396 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
400 tpath = gtk_tree_model_get_path(store, &iter);
401 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
402 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
403 vflist_row_rename_cb, vf);
404 gtk_tree_path_free(tpath);
409 file_util_rename(nullptr, list, vf->listview);
412 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
414 auto vf = static_cast<ViewFile *>(data);
416 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
419 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
423 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
427 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
429 GList *columns, *work;
431 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
436 auto column = static_cast<GtkTreeViewColumn *>(work->data);
437 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
440 if (vflist_is_multiline(vf))
442 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
444 gtk_tree_view_column_set_visible(column, enable);
446 if (col_idx == FILE_COLUMN_FORMATTED)
448 gtk_tree_view_column_set_visible(column, !enable);
453 if (col_idx == FILE_COLUMN_STAR_RATING)
455 gtk_tree_view_column_set_visible(column, enable);
459 g_list_free(columns);
462 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
464 auto vf = static_cast<ViewFile *>(data);
466 options->show_star_rating = !options->show_star_rating;
468 vflist_populate_view(vf, TRUE);
470 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
471 vflist_star_rating_set(vf, options->show_star_rating);
474 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
476 auto vf = static_cast<ViewFile *>(data);
478 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
480 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
483 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
485 auto vf = static_cast<ViewFile *>(data);
486 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
487 VFLIST(vf)->click_fd = nullptr;
493 *-----------------------------------------------------------------------------
495 *-----------------------------------------------------------------------------
498 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
500 auto vf = static_cast<ViewFile *>(data);
503 if (!new_name || !new_name[0]) return FALSE;
505 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
507 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
509 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
510 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
515 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
516 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
517 file_util_rename_simple(fd, new_path, vf->listview);
527 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
529 auto vf = static_cast<ViewFile *>(data);
532 if (event->keyval != GDK_KEY_Menu) return FALSE;
534 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
540 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
541 gtk_tree_model_get_iter(store, &iter, tpath);
542 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
543 gtk_tree_path_free(tpath);
547 VFLIST(vf)->click_fd = nullptr;
550 vf->popup = vf_pop_menu(vf);
551 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
556 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
558 auto vf = static_cast<ViewFile *>(data);
561 FileData *fd = nullptr;
562 GtkTreeViewColumn *column;
564 vf->clicked_mark = 0;
566 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
567 &tpath, &column, nullptr, nullptr))
570 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
572 if (bevent->button == MOUSE_BUTTON_LEFT &&
573 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
576 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
577 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
579 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
581 gtk_tree_model_get_iter(store, &iter, tpath);
582 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
583 gtk_tree_path_free(tpath);
586 VFLIST(vf)->click_fd = fd;
588 if (bevent->button == MOUSE_BUTTON_RIGHT)
590 vf->popup = vf_pop_menu(vf);
591 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
595 if (!fd) return FALSE;
597 if (bevent->button == MOUSE_BUTTON_MIDDLE)
599 if (!vflist_row_is_selected(vf, fd))
601 vflist_color_set(vf, fd, TRUE);
607 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
608 !(bevent->state & GDK_SHIFT_MASK ) &&
609 !(bevent->state & GDK_CONTROL_MASK ) &&
610 vflist_row_is_selected(vf, fd))
612 GtkTreeSelection *selection;
614 gtk_widget_grab_focus(widget);
617 /* returning FALSE and further processing of the event is needed for
618 correct operation of the expander, to show the sidecar files.
619 It however resets the selection of multiple files. With this condition
620 it should work for both cases */
621 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
622 return (gtk_tree_selection_count_selected_rows(selection) > 1);
625 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
627 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
629 collection_window_new(VFLIST(vf)->click_fd->path);
633 if (vf->layout) layout_image_full_screen_start(vf->layout);
640 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
642 auto vf = static_cast<ViewFile *>(data);
645 FileData *fd = nullptr;
647 if (defined_mouse_buttons(widget, bevent, vf->layout))
652 if (bevent->button == MOUSE_BUTTON_MIDDLE)
654 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
657 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
662 if ((bevent->x != 0 || bevent->y != 0) &&
663 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
664 &tpath, nullptr, nullptr, nullptr))
668 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
669 gtk_tree_model_get_iter(store, &iter, tpath);
670 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
671 gtk_tree_path_free(tpath);
674 if (bevent->button == MOUSE_BUTTON_MIDDLE)
676 if (fd && VFLIST(vf)->click_fd == fd)
678 GtkTreeSelection *selection;
680 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
681 if (vflist_row_is_selected(vf, fd))
683 gtk_tree_selection_unselect_iter(selection, &iter);
687 gtk_tree_selection_select_iter(selection, &iter);
693 if (fd && VFLIST(vf)->click_fd == fd &&
694 !(bevent->state & GDK_SHIFT_MASK ) &&
695 !(bevent->state & GDK_CONTROL_MASK ) &&
696 vflist_row_is_selected(vf, fd))
698 GtkTreeSelection *selection;
700 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
701 gtk_tree_selection_unselect_all(selection);
702 gtk_tree_selection_select_iter(selection, &iter);
703 vflist_move_cursor(vf, &iter);
709 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
711 FileData *read_ahead_fd = nullptr;
717 cur_fd = layout_image_get_fd(vf->layout);
718 if (sel_fd == cur_fd) return; /* no change */
720 row = g_list_index(vf->list, sel_fd);
721 /** @FIXME sidecar data */
723 if (sel_fd && options->image.enable_read_ahead && row >= 0)
725 if (row > g_list_index(vf->list, cur_fd) &&
726 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
728 read_ahead_fd = vf_index_get_data(vf, row + 1);
732 read_ahead_fd = vf_index_get_data(vf, row - 1);
736 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
739 static gboolean vflist_select_idle_cb(gpointer data)
741 auto vf = static_cast<ViewFile *>(data);
745 VFLIST(vf)->select_idle_id = 0;
746 return G_SOURCE_REMOVE;
751 if (VFLIST(vf)->select_fd)
753 vflist_select_image(vf, VFLIST(vf)->select_fd);
754 VFLIST(vf)->select_fd = nullptr;
757 VFLIST(vf)->select_idle_id = 0;
758 return G_SOURCE_REMOVE;
761 static void vflist_select_idle_cancel(ViewFile *vf)
763 if (VFLIST(vf)->select_idle_id)
765 g_source_remove(VFLIST(vf)->select_idle_id);
766 VFLIST(vf)->select_idle_id = 0;
770 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
772 auto vf = static_cast<ViewFile *>(data);
774 GtkTreePath *cursor_path;
776 VFLIST(vf)->select_fd = nullptr;
778 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
780 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
783 gtk_tree_model_get_iter(store, &iter, cursor_path);
784 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
785 gtk_tree_path_free(cursor_path);
790 !VFLIST(vf)->select_idle_id)
792 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
798 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
800 auto vf = static_cast<ViewFile *>(data);
801 vflist_set_expanded(vf, iter, TRUE);
804 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
806 auto vf = static_cast<ViewFile *>(data);
807 vflist_set_expanded(vf, iter, FALSE);
811 *-----------------------------------------------------------------------------
813 *-----------------------------------------------------------------------------
817 static gchar* vflist_get_formatted(ViewFile *vf, const gchar *name, const gchar *sidecars, const gchar *size, const gchar *time, gboolean expanded, gboolean with_stars, const gchar *star_rating)
819 gboolean multiline = vflist_is_multiline(vf);
826 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
830 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
835 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
840 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
848 gchar *formatted_with_stars;
850 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
852 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
853 FILE_COLUMN_NAME, &name,
854 FILE_COLUMN_SIDECARS, &sidecars,
855 FILE_COLUMN_SIZE, &size,
856 FILE_COLUMN_DATE, &time,
857 FILE_COLUMN_STAR_RATING, &star_rating,
860 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
861 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
863 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
864 FILE_COLUMN_EXPANDED, expanded,
866 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
867 FILE_COLUMN_EXPANDED, expanded,
874 g_free(formatted_with_stars);
877 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
880 gchar *sidecars = nullptr;
882 const gchar *time = text_from_time(fd->date);
883 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
884 const gchar *disabled_grouping;
886 gchar *formatted_with_stars;
887 gboolean expanded = FALSE;
890 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
892 star_rating = convert_rating_to_stars(fd->rating);
896 star_rating = nullptr;
899 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
901 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
904 sidecars = file_data_sc_list_to_string(fd);
906 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
907 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
908 size = text_from_size(fd->size);
910 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
911 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
913 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
914 FILE_COLUMN_VERSION, fd->version,
915 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
916 FILE_COLUMN_FORMATTED, formatted,
917 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
918 FILE_COLUMN_SIDECARS, sidecars,
919 FILE_COLUMN_NAME, name,
920 FILE_COLUMN_STAR_RATING, star_rating,
921 FILE_COLUMN_SIZE, size,
922 FILE_COLUMN_DATE, time,
923 #define STORE_SET_IS_SLOW 1
924 #if STORE_SET_IS_SLOW
925 /* this is 3x faster on a directory with 20000 files */
926 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
927 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
928 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
929 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
930 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
931 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
932 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
933 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
934 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
935 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
936 #if FILEDATA_MARKS_SIZE != 10
937 #error this needs to be updated
940 FILE_COLUMN_COLOR, FALSE, -1);
942 #if !STORE_SET_IS_SLOW
945 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
946 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
955 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
960 gint num_ordered = 0;
961 gint num_prepended = 0;
963 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
969 auto fd = static_cast<FileData *>(work->data);
970 gboolean done = FALSE;
974 FileData *old_fd = nullptr;
975 gint old_version = 0;
979 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
980 FILE_COLUMN_POINTER, &old_fd,
981 FILE_COLUMN_VERSION, &old_version,
991 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
993 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
995 if (match == 0) g_warning("multiple fd for the same path");
1006 GtkTreeIter new_iter;
1011 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1016 here should be used gtk_tree_store_append, but this function seems to be O(n)
1017 and it seems to be much faster to add new entries to the beginning and reorder later
1020 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1023 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1024 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1026 if (g_list_find(selected, fd))
1028 /* renamed files - the same fd appears at different position - select it again*/
1029 GtkTreeSelection *selection;
1030 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1031 gtk_tree_selection_select_iter(selection, &new_iter);
1038 file_data_unref(old_fd);
1039 valid = gtk_tree_store_remove(store, &iter);
1044 if (fd->version != old_version || force)
1046 vflist_setup_iter(vf, store, &iter, fd);
1047 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1050 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1061 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1062 file_data_unref(old_fd);
1064 valid = gtk_tree_store_remove(store, &iter);
1067 /* move the prepended entries to the correct position */
1070 gint num_total = num_prepended + num_ordered;
1071 std::vector<gint> new_order;
1072 new_order.reserve(num_total);
1074 for (gint i = 0; i < num_ordered; i++)
1076 new_order.push_back(num_prepended + i);
1078 for (gint i = num_ordered; i < num_total; i++)
1080 new_order.push_back(num_total - 1 - i);
1082 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1086 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1089 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1090 GtkTreeStore *store;
1093 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1094 if (!vf->list) return;
1100 auto fd = static_cast<FileData *>(work->data);
1101 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1106 vf->sort_method = type;
1107 vf->sort_ascend = ascend;
1108 vf->sort_case = case_sensitive;
1110 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1112 std::vector<gint> new_order;
1113 new_order.reserve(i);
1118 auto fd = static_cast<FileData *>(work->data);
1119 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1123 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1124 gtk_tree_store_reorder(store, nullptr, new_order.data());
1126 g_hash_table_destroy(fd_idx_hash);
1130 *-----------------------------------------------------------------------------
1132 *-----------------------------------------------------------------------------
1136 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1141 auto fd = static_cast<FileData *>(work->data);
1144 if (fd->thumb_pixbuf) (*done)++;
1146 if (fd->sidecar_files)
1148 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1154 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1159 auto fd = static_cast<FileData *>(work->data);
1162 if (fd->metadata_in_idle_loaded) (*done)++;
1164 if (fd->sidecar_files)
1166 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1172 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1174 GtkTreeStore *store;
1177 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1179 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1180 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1183 FileData *vflist_thumb_next_fd(ViewFile *vf)
1186 FileData *fd = nullptr;
1188 /* first check the visible files */
1190 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1192 GtkTreeModel *store;
1194 gboolean valid = TRUE;
1196 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1197 gtk_tree_model_get_iter(store, &iter, tpath);
1198 gtk_tree_path_free(tpath);
1201 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1205 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1207 if (!nfd->thumb_pixbuf) fd = nfd;
1209 valid = gtk_tree_model_iter_next(store, &iter);
1213 /* then find first undone */
1217 GList *work = vf->list;
1220 auto fd_p = static_cast<FileData *>(work->data);
1221 if (!fd_p->thumb_pixbuf)
1225 GList *work2 = fd_p->sidecar_files;
1227 while (work2 && !fd)
1229 fd_p = static_cast<FileData *>(work2->data);
1230 if (!fd_p->thumb_pixbuf) fd = fd_p;
1231 work2 = work2->next;
1241 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1243 GtkTreeStore *store;
1250 gchar *formatted_with_stars;
1253 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1255 star_rating = metadata_read_rating_stars(fd);
1257 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1258 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1260 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1261 FILE_COLUMN_NAME, &name,
1262 FILE_COLUMN_SIDECARS, &sidecars,
1263 FILE_COLUMN_SIZE, &size,
1264 FILE_COLUMN_DATE, &time,
1265 FILE_COLUMN_EXPANDED, &expanded,
1268 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1270 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1271 FILE_COLUMN_EXPANDED, expanded,
1274 g_free(star_rating);
1275 g_free(formatted_with_stars);
1278 FileData *vflist_star_next_fd(ViewFile *vf)
1281 FileData *fd = nullptr;
1282 FileData *nfd = nullptr;
1284 /* first check the visible files */
1286 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1288 GtkTreeModel *store;
1290 gboolean valid = TRUE;
1292 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1293 gtk_tree_model_get_iter(store, &iter, tpath);
1294 gtk_tree_path_free(tpath);
1297 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1299 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1301 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1306 valid = gtk_tree_model_iter_next(store, &iter);
1311 vf->stars_filedata = fd;
1313 if (vf->stars_id == 0)
1315 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1320 /* then find first undone */
1324 GList *work = vf->list;
1328 auto fd_p = static_cast<FileData *>(work->data);
1330 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1344 vf->stars_filedata = fd;
1346 if (vf->stars_id == 0)
1348 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1357 *-----------------------------------------------------------------------------
1359 *-----------------------------------------------------------------------------
1362 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1365 GList *work, *work2;
1370 auto list_fd = static_cast<FileData *>(work->data);
1371 if (list_fd == fd) return p;
1373 work2 = list_fd->sidecar_files;
1376 /** @FIXME return the same index also for sidecars
1377 it is sufficient for next/prev navigation but it should be rewritten
1378 without using indexes at all
1380 auto sidecar_fd = static_cast<FileData *>(work2->data);
1381 if (sidecar_fd == fd) return p;
1382 work2 = work2->next;
1393 *-----------------------------------------------------------------------------
1395 *-----------------------------------------------------------------------------
1398 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1400 GtkTreeModel *store;
1401 GtkTreeSelection *selection;
1404 gboolean found = FALSE;
1406 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1407 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1409 while (!found && work)
1411 auto tpath = static_cast<GtkTreePath *>(work->data);
1415 gtk_tree_model_get_iter(store, &iter, tpath);
1416 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1417 if (fd_n == fd) found = TRUE;
1420 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1425 #pragma GCC diagnostic push
1426 #pragma GCC diagnostic ignored "-Wunused-function"
1427 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1431 fd = vf_index_get_data(vf, row);
1432 return vflist_row_is_selected(vf, fd);
1434 #pragma GCC diagnostic pop
1436 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1438 GtkTreeModel *store;
1439 GtkTreeSelection *selection;
1443 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1444 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1454 auto tpath = static_cast<GtkTreePath *>(work->data);
1458 gtk_tree_model_get_iter(store, &iter, tpath);
1459 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1468 count = g_list_length(slist);
1469 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1474 GList *vflist_selection_get_list(ViewFile *vf)
1476 GtkTreeModel *store;
1477 GtkTreeSelection *selection;
1479 GList *list = nullptr;
1482 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1483 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1487 auto tpath = static_cast<GtkTreePath *>(work->data);
1491 gtk_tree_model_get_iter(store, &iter, tpath);
1492 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1494 list = g_list_prepend(list, file_data_ref(fd));
1496 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1498 /* unexpanded - add whole group */
1499 GList *work2 = fd->sidecar_files;
1502 auto sfd = static_cast<FileData *>(work2->data);
1503 list = g_list_prepend(list, file_data_ref(sfd));
1504 work2 = work2->next;
1510 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1512 return g_list_reverse(list);
1515 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1517 GtkTreeModel *store;
1518 GtkTreeSelection *selection;
1520 GList *list = nullptr;
1523 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1524 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1528 auto tpath = static_cast<GtkTreePath *>(work->data);
1532 gtk_tree_model_get_iter(store, &iter, tpath);
1533 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1535 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1539 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1541 return g_list_reverse(list);
1544 void vflist_select_all(ViewFile *vf)
1546 GtkTreeSelection *selection;
1548 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1549 gtk_tree_selection_select_all(selection);
1551 VFLIST(vf)->select_fd = nullptr;
1554 void vflist_select_none(ViewFile *vf)
1556 GtkTreeSelection *selection;
1558 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1559 gtk_tree_selection_unselect_all(selection);
1562 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1567 tpath = gtk_tree_model_get_path(store, iter);
1568 result = gtk_tree_path_prev(tpath);
1570 gtk_tree_model_get_iter(store, iter, tpath);
1572 gtk_tree_path_free(tpath);
1577 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1579 if (!gtk_tree_model_get_iter_first(store, iter))
1584 GtkTreeIter next = *iter;
1586 if (gtk_tree_model_iter_next(store, &next))
1595 void vflist_select_invert(ViewFile *vf)
1598 GtkTreeSelection *selection;
1599 GtkTreeModel *store;
1602 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1603 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1605 /* Backward iteration prevents scrolling to the end of the list,
1606 * it scrolls to the first selected row instead. */
1607 valid = tree_model_get_iter_last(store, &iter);
1611 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1614 gtk_tree_selection_unselect_iter(selection, &iter);
1616 gtk_tree_selection_select_iter(selection, &iter);
1618 valid = tree_model_iter_prev(store, &iter);
1622 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1626 if (vflist_find_row(vf, fd, &iter) < 0) return;
1628 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1630 if (!vflist_row_is_selected(vf, fd))
1632 GtkTreeSelection *selection;
1633 GtkTreeModel *store;
1636 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1637 gtk_tree_selection_unselect_all(selection);
1638 gtk_tree_selection_select_iter(selection, &iter);
1639 vflist_move_cursor(vf, &iter);
1641 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1642 tpath = gtk_tree_model_get_path(store, &iter);
1643 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1644 gtk_tree_path_free(tpath);
1648 void vflist_select_list(ViewFile *vf, GList *list)
1659 fd = static_cast<FileData *>(work->data);
1661 if (vflist_find_row(vf, fd, &iter) < 0) return;
1662 if (!vflist_row_is_selected(vf, fd))
1664 GtkTreeSelection *selection;
1666 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1667 gtk_tree_selection_select_iter(selection, &iter);
1673 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1676 FileData *fd = nullptr;
1678 if (sel_fd->parent) sel_fd = sel_fd->parent;
1684 fd = static_cast<FileData *>(work->data);
1687 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1689 if (match >= 0) break;
1692 if (fd) vflist_select_by_fd(vf, fd);
1696 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1698 GtkTreeModel *store;
1700 GtkTreeSelection *selection;
1704 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1706 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1707 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1709 valid = gtk_tree_model_get_iter_first(store, &iter);
1713 gboolean mark_val, selected;
1714 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1716 mark_val = file_data_get_mark(fd, n);
1717 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1721 case MTS_MODE_SET: selected = mark_val;
1723 case MTS_MODE_OR: selected = mark_val || selected;
1725 case MTS_MODE_AND: selected = mark_val && selected;
1727 case MTS_MODE_MINUS: selected = !mark_val && selected;
1732 gtk_tree_selection_select_iter(selection, &iter);
1734 gtk_tree_selection_unselect_iter(selection, &iter);
1736 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1740 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1742 GtkTreeModel *store;
1743 GtkTreeSelection *selection;
1748 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1750 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1751 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1755 auto tpath = static_cast<GtkTreePath *>(work->data);
1759 gtk_tree_model_get_iter(store, &iter, tpath);
1760 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1762 /* the change has a very limited range and the standard notification would trigger
1763 complete re-read of the directory - try to do only minimal update instead */
1764 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1768 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1770 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1772 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1776 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1778 vf_refresh_idle(vf);
1782 /* mark functions can have various side effects - update all columns to be sure */
1783 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1784 /* mark functions can change sidecars too */
1785 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1789 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1793 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1797 *-----------------------------------------------------------------------------
1799 *-----------------------------------------------------------------------------
1802 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1804 GtkTreeViewColumn *column;
1805 GtkCellRenderer *cell;
1808 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1809 if (!column) return;
1811 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1813 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1815 cell = static_cast<GtkCellRenderer *>(list->data);
1818 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1819 gtk_tree_view_column_set_visible(column, thumb);
1821 if (options->show_star_rating)
1823 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1824 if (!column) return;
1825 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1826 gtk_tree_view_column_set_visible(column, TRUE);
1828 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1829 if (!column) return;
1830 gtk_tree_view_column_set_visible(column, FALSE);
1834 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1835 if (!column) return;
1836 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1837 gtk_tree_view_column_set_visible(column, TRUE);
1839 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1840 if (!column) return;
1841 gtk_tree_view_column_set_visible(column, FALSE);
1844 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1845 if (!column) return;
1846 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1848 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1849 if (!column) return;
1850 gtk_tree_view_column_set_visible(column, !multiline);
1852 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1853 if (!column) return;
1854 gtk_tree_view_column_set_visible(column, !multiline);
1857 static gboolean vflist_is_multiline(ViewFile *vf)
1859 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1863 static void vflist_populate_view(ViewFile *vf, gboolean force)
1865 GtkTreeStore *store;
1868 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1875 vflist_store_clear(vf, FALSE);
1880 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1882 selected = vflist_selection_get_list(vf);
1884 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1886 if (selected && vflist_selection_count(vf, nullptr) == 0)
1888 /* all selected files disappeared */
1889 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1892 filelist_free(selected);
1895 vf_thumb_update(vf);
1899 gboolean vflist_refresh(ViewFile *vf)
1902 gboolean ret = TRUE;
1904 old_list = vf->list;
1907 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1910 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1912 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1914 if (vf->marks_enabled)
1916 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1917 // each time a mark is changed.
1918 file_data_lock_list(vf->list);
1922 /** @FIXME only do this when needed (aka when we just switched from */
1923 /** @FIXME marks-enabled to marks-disabled) */
1924 file_data_unlock_list(vf->list);
1927 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1928 vf->list = g_list_first(vf->list);
1929 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1931 vf->list = g_list_first(vf->list);
1932 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1934 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1936 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1937 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1940 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1942 vflist_populate_view(vf, FALSE);
1944 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1946 filelist_free(old_list);
1947 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1954 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1956 #define CELL_HEIGHT_OVERRIDE 512
1958 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1962 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1963 if (spec && G_IS_PARAM_SPEC_INT(spec))
1965 GParamSpecInt *spec_int;
1967 spec_int = G_PARAM_SPEC_INT(spec);
1968 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1972 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1974 static GdkColor color;
1975 static GtkWidget *done = nullptr;
1981 style = gtk_widget_get_style(widget);
1982 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1983 shift_color(&color, -1, 0);
1990 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1991 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1993 auto vf = static_cast<ViewFile *>(data);
1996 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1997 g_object_set(G_OBJECT(cell),
1998 "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1999 "cell-background-set", set, NULL);
2002 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2004 GtkTreeViewColumn *column;
2005 GtkCellRenderer *renderer;
2007 column = gtk_tree_view_column_new();
2008 gtk_tree_view_column_set_title(column, title);
2009 gtk_tree_view_column_set_min_width(column, 4);
2013 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2014 renderer = gtk_cell_renderer_text_new();
2017 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2019 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2020 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2022 gtk_tree_view_column_set_expand(column, TRUE);
2026 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2027 renderer = gtk_cell_renderer_pixbuf_new();
2028 cell_renderer_height_override(renderer);
2029 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2030 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2033 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2034 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2035 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2037 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2040 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2042 auto vf = static_cast<ViewFile *>(data);
2043 GtkTreeStore *store;
2044 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2050 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2051 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2054 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2056 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2058 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2061 /* the change has a very limited range and the standard notification would trigger
2062 complete re-read of the directory - try to do only minimal update instead */
2063 file_data_unregister_notify_func(vf_notify_cb, vf);
2064 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2065 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2067 vf_refresh_idle(vf);
2071 /* mark functions can have various side effects - update all columns to be sure */
2072 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2073 /* mark functions can change sidecars too */
2074 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2076 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2078 gtk_tree_path_free(path);
2081 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2083 GtkTreeViewColumn *column;
2084 GtkCellRenderer *renderer;
2086 renderer = gtk_cell_renderer_toggle_new();
2087 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2089 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2090 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2091 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2093 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2094 gtk_tree_view_column_set_fixed_width(column, 22);
2095 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2098 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2102 *-----------------------------------------------------------------------------
2104 *-----------------------------------------------------------------------------
2107 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2110 if (!dir_fd) return FALSE;
2111 if (vf->dir_fd == dir_fd) return TRUE;
2113 file_data_unref(vf->dir_fd);
2114 vf->dir_fd = file_data_ref(dir_fd);
2116 /* force complete reload */
2117 vflist_store_clear(vf, TRUE);
2119 filelist_free(vf->list);
2122 ret = vf_refresh(vf);
2123 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2127 void vflist_destroy_cb(GtkWidget *, gpointer data)
2129 auto vf = static_cast<ViewFile *>(data);
2131 file_data_unregister_notify_func(vf_notify_cb, vf);
2133 vflist_select_idle_cancel(vf);
2134 vf_refresh_idle_cancel(vf);
2138 filelist_free(vf->list);
2141 ViewFile *vflist_new(ViewFile *vf, FileData *)
2143 GtkTreeStore *store;
2144 GtkTreeSelection *selection;
2145 GType flist_types[FILE_COLUMN_COUNT];
2149 vf->info = g_new0(ViewFileInfoList, 1);
2151 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2152 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2153 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2154 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2155 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2156 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2157 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2158 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2159 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2160 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2161 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2162 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2163 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2164 flist_types[i] = G_TYPE_BOOLEAN;
2166 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2168 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2169 g_object_unref(store);
2171 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2172 G_CALLBACK(vflist_expand_cb), vf);
2174 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2175 G_CALLBACK(vflist_collapse_cb), vf);
2177 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2178 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2179 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2181 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2182 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2184 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2188 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2190 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2191 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2195 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2196 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2199 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2200 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2203 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2204 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2207 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2208 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2211 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2212 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2215 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2216 g_assert(column == FILE_VIEW_COLUMN_DATE);
2219 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2223 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2225 if (VFLIST(vf)->thumbs_enabled == enable) return;
2227 VFLIST(vf)->thumbs_enabled = enable;
2229 /* vflist_populate_view is better than vf_refresh:
2230 - no need to re-read the directory
2231 - force update because the formatted string has changed
2235 vflist_populate_view(vf, TRUE);
2236 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2240 void vflist_marks_set(ViewFile *vf, gboolean enable)
2242 GList *columns, *work;
2244 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2249 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2250 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2253 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2254 gtk_tree_view_column_set_visible(column, enable);
2259 // Previously disabled, which means that vf->list is complete
2260 file_data_lock_list(vf->list);
2264 // Previously enabled, which means that vf->list is incomplete
2267 g_list_free(columns);
2270 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */