2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "view-file-list.h"
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 #include <glib-object.h>
36 #include "layout-image.h"
38 #include "main-defines.h"
42 #include "ui-fileops.h"
44 #include "ui-tree-edit.h"
45 #include "uri-utils.h"
47 #include "view-file.h"
49 /* Index to tree store */
51 FILE_COLUMN_POINTER = VIEW_FILE_COLUMN_POINTER,
54 FILE_COLUMN_FORMATTED,
55 FILE_COLUMN_FORMATTED_WITH_STARS,
58 FILE_COLUMN_STAR_RATING,
64 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
69 /* Index to tree view */
71 FILE_VIEW_COLUMN_MARKS = 0,
72 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
73 FILE_VIEW_COLUMN_THUMB,
74 FILE_VIEW_COLUMN_FORMATTED,
75 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
76 FILE_VIEW_COLUMN_STAR_RATING,
77 FILE_VIEW_COLUMN_SIZE,
78 FILE_VIEW_COLUMN_DATE,
79 FILE_VIEW_COLUMN_COUNT
84 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
85 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
86 static void vflist_populate_view(ViewFile *vf, gboolean force);
87 static gboolean vflist_is_multiline(ViewFile *vf);
88 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
92 *-----------------------------------------------------------------------------
94 *-----------------------------------------------------------------------------
96 struct ViewFileFindRowData {
103 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
105 auto find = static_cast<ViewFileFindRowData *>(data);
107 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
118 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
121 ViewFileFindRowData data = {fd, iter, FALSE, 0};
123 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
124 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
134 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
137 GtkTreeViewColumn *column;
139 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
140 &tpath, &column, nullptr, nullptr))
146 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
147 gtk_tree_model_get_iter(store, &row, tpath);
148 gtk_tree_path_free(tpath);
149 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
157 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
160 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
162 /* it seems that gtk_tree_store_clear may call some callbacks
163 that use the column. Set the pointer to NULL to be safe. */
164 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
169 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
172 GList *files = nullptr;
174 if (unlock_files && vf->marks_enabled)
176 // unlock locked files in this directory
177 filelist_read(vf->dir_fd, &files, nullptr);
181 auto fd = static_cast<FileData *>(work->data);
183 file_data_unlock(fd);
184 file_data_unref(fd); // undo the ref that got added in filelist_read
189 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
190 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
191 gtk_tree_store_clear(GTK_TREE_STORE(store));
194 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
199 if (vflist_find_row(vf, fd, &iter) < 0) return;
200 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
201 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
204 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
209 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
211 tpath = gtk_tree_model_get_path(store, iter);
212 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
213 gtk_tree_path_free(tpath);
218 *-----------------------------------------------------------------------------
220 *-----------------------------------------------------------------------------
223 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
224 GtkSelectionData *selection_data, guint,
225 guint, gpointer data)
227 auto vf = static_cast<ViewFile *>(data);
228 GList *list = nullptr;
230 if (!VFLIST(vf)->click_fd) return;
232 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
234 list = vf_selection_get_list(vf);
238 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
242 uri_selection_data_set_uris_from_filelist(selection_data, list);
246 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
248 auto vf = static_cast<ViewFile *>(data);
250 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
252 if (VFLIST(vf)->thumbs_enabled &&
253 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
257 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
258 items = vf_selection_count(vf, nullptr);
262 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
266 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
268 auto vf = static_cast<ViewFile *>(data);
270 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
272 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
278 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
279 int x, int y, GtkSelectionData *selection,
280 guint info, guint, gpointer data)
282 auto vf = static_cast<ViewFile *>(data);
284 if (info == TARGET_TEXT_PLAIN) {
285 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
288 /* Add keywords to file */
289 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
290 GList *kw_list = string_to_keywords_list(str);
292 metadata_append_list(fd, KEYWORD_KEY, kw_list);
293 g_list_free_full(kw_list, g_free);
299 void vflist_dnd_init(ViewFile *vf)
301 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
302 dnd_file_drag_types, dnd_file_drag_types_count,
303 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
304 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
305 dnd_file_drag_types, dnd_file_drag_types_count,
306 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
308 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
309 G_CALLBACK(vflist_dnd_get), vf);
310 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
311 G_CALLBACK(vflist_dnd_begin), vf);
312 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
313 G_CALLBACK(vflist_dnd_end), vf);
314 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
315 G_CALLBACK(vflist_drag_data_received), vf);
319 *-----------------------------------------------------------------------------
321 *-----------------------------------------------------------------------------
324 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
326 GList *list = nullptr;
328 if (fd->sidecar_files)
330 /* check if the row is expanded */
334 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
335 if (vflist_find_row(vf, fd, &iter) >= 0)
339 tpath = gtk_tree_model_get_path(store, &iter);
340 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
342 /* unexpanded - add whole group */
343 list = filelist_copy(fd->sidecar_files);
345 gtk_tree_path_free(tpath);
349 return g_list_prepend(list, file_data_ref(fd));
352 GList *vflist_pop_menu_file_list(ViewFile *vf)
354 if (!VFLIST(vf)->click_fd) return nullptr;
356 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
358 return vf_selection_get_list(vf);
360 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
364 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
366 auto vf = static_cast<ViewFile *>(data);
368 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
372 list = vf_selection_get_list(vf);
373 view_window_new_from_list(list);
378 view_window_new(VFLIST(vf)->click_fd);
382 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
384 auto vf = static_cast<ViewFile *>(data);
387 list = vf_pop_menu_file_list(vf);
388 if (options->file_ops.enable_in_place_rename &&
389 list && !list->next && VFLIST(vf)->click_fd)
396 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
397 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
401 tpath = gtk_tree_model_get_path(store, &iter);
402 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
403 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
404 vflist_row_rename_cb, vf);
405 gtk_tree_path_free(tpath);
410 file_util_rename(nullptr, list, vf->listview);
413 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
415 auto vf = static_cast<ViewFile *>(data);
417 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
420 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
424 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
428 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
433 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
438 auto column = static_cast<GtkTreeViewColumn *>(work->data);
439 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
442 if (vflist_is_multiline(vf))
444 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
446 gtk_tree_view_column_set_visible(column, enable);
448 if (col_idx == FILE_COLUMN_FORMATTED)
450 gtk_tree_view_column_set_visible(column, !enable);
455 if (col_idx == FILE_COLUMN_STAR_RATING)
457 gtk_tree_view_column_set_visible(column, enable);
461 g_list_free(columns);
464 void vflist_pop_menu_show_star_rating_cb(ViewFile *vf)
466 vflist_populate_view(vf, TRUE);
468 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
469 vflist_star_rating_set(vf, options->show_star_rating);
472 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
474 auto vf = static_cast<ViewFile *>(data);
476 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
478 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
481 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
483 auto vf = static_cast<ViewFile *>(data);
484 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
485 VFLIST(vf)->click_fd = nullptr;
491 *-----------------------------------------------------------------------------
493 *-----------------------------------------------------------------------------
496 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
498 auto vf = static_cast<ViewFile *>(data);
501 if (!new_name || !new_name[0]) return FALSE;
503 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
505 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
507 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
508 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
513 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
514 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
515 file_util_rename_simple(fd, new_path, vf->listview);
525 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
527 auto vf = static_cast<ViewFile *>(data);
530 if (event->keyval != GDK_KEY_Menu) return FALSE;
532 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
538 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
539 gtk_tree_model_get_iter(store, &iter, tpath);
540 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
541 gtk_tree_path_free(tpath);
545 VFLIST(vf)->click_fd = nullptr;
548 vf->popup = vf_pop_menu(vf);
549 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
554 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
556 auto vf = static_cast<ViewFile *>(data);
559 FileData *fd = nullptr;
560 GtkTreeViewColumn *column;
562 vf->clicked_mark = 0;
564 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
565 &tpath, &column, nullptr, nullptr))
568 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
570 if (bevent->button == MOUSE_BUTTON_LEFT &&
571 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
574 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
575 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
577 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
579 gtk_tree_model_get_iter(store, &iter, tpath);
580 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
581 gtk_tree_path_free(tpath);
584 VFLIST(vf)->click_fd = fd;
586 if (bevent->button == MOUSE_BUTTON_RIGHT)
588 vf->popup = vf_pop_menu(vf);
589 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
593 if (!fd) return FALSE;
595 if (bevent->button == MOUSE_BUTTON_MIDDLE)
597 if (!vflist_row_is_selected(vf, fd))
599 vflist_color_set(vf, fd, TRUE);
605 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
606 !(bevent->state & GDK_SHIFT_MASK ) &&
607 !(bevent->state & GDK_CONTROL_MASK ) &&
608 vflist_row_is_selected(vf, fd))
610 GtkTreeSelection *selection;
612 gtk_widget_grab_focus(widget);
615 /* returning FALSE and further processing of the event is needed for
616 correct operation of the expander, to show the sidecar files.
617 It however resets the selection of multiple files. With this condition
618 it should work for both cases */
619 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
620 return (gtk_tree_selection_count_selected_rows(selection) > 1);
623 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
625 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
627 collection_window_new(VFLIST(vf)->click_fd->path);
631 if (vf->layout) layout_image_full_screen_start(vf->layout);
638 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
640 auto vf = static_cast<ViewFile *>(data);
643 FileData *fd = nullptr;
645 if (defined_mouse_buttons(widget, bevent, vf->layout))
650 if (bevent->button == MOUSE_BUTTON_MIDDLE)
652 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
655 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
660 if ((bevent->x != 0 || bevent->y != 0) &&
661 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
662 &tpath, nullptr, nullptr, nullptr))
666 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
667 gtk_tree_model_get_iter(store, &iter, tpath);
668 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
669 gtk_tree_path_free(tpath);
672 if (bevent->button == MOUSE_BUTTON_MIDDLE)
674 if (fd && VFLIST(vf)->click_fd == fd)
676 GtkTreeSelection *selection;
678 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
679 if (vflist_row_is_selected(vf, fd))
681 gtk_tree_selection_unselect_iter(selection, &iter);
685 gtk_tree_selection_select_iter(selection, &iter);
691 if (fd && VFLIST(vf)->click_fd == fd &&
692 !(bevent->state & GDK_SHIFT_MASK ) &&
693 !(bevent->state & GDK_CONTROL_MASK ) &&
694 vflist_row_is_selected(vf, fd))
696 GtkTreeSelection *selection;
698 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
699 gtk_tree_selection_unselect_all(selection);
700 gtk_tree_selection_select_iter(selection, &iter);
701 vflist_move_cursor(vf, &iter);
707 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
709 FileData *read_ahead_fd = nullptr;
715 cur_fd = layout_image_get_fd(vf->layout);
716 if (sel_fd == cur_fd) return; /* no change */
718 row = g_list_index(vf->list, sel_fd);
719 /** @FIXME sidecar data */
721 if (sel_fd && options->image.enable_read_ahead && row >= 0)
723 if (row > g_list_index(vf->list, cur_fd) &&
724 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
726 read_ahead_fd = vf_index_get_data(vf, row + 1);
730 read_ahead_fd = vf_index_get_data(vf, row - 1);
734 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
737 static gboolean vflist_select_idle_cb(gpointer data)
739 auto vf = static_cast<ViewFile *>(data);
743 VFLIST(vf)->select_idle_id = 0;
744 return G_SOURCE_REMOVE;
749 if (VFLIST(vf)->select_fd)
751 vflist_select_image(vf, VFLIST(vf)->select_fd);
752 VFLIST(vf)->select_fd = nullptr;
755 VFLIST(vf)->select_idle_id = 0;
756 return G_SOURCE_REMOVE;
759 static void vflist_select_idle_cancel(ViewFile *vf)
761 if (VFLIST(vf)->select_idle_id)
763 g_source_remove(VFLIST(vf)->select_idle_id);
764 VFLIST(vf)->select_idle_id = 0;
768 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
770 auto vf = static_cast<ViewFile *>(data);
772 GtkTreePath *cursor_path;
774 VFLIST(vf)->select_fd = nullptr;
776 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
778 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
781 gtk_tree_model_get_iter(store, &iter, cursor_path);
782 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
783 gtk_tree_path_free(cursor_path);
788 !VFLIST(vf)->select_idle_id)
790 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
796 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
798 auto vf = static_cast<ViewFile *>(data);
799 vflist_set_expanded(vf, iter, TRUE);
802 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
804 auto vf = static_cast<ViewFile *>(data);
805 vflist_set_expanded(vf, iter, FALSE);
809 *-----------------------------------------------------------------------------
811 *-----------------------------------------------------------------------------
815 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)
817 gboolean multiline = vflist_is_multiline(vf);
824 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
828 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
833 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
838 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
846 gchar *formatted_with_stars;
848 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
850 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
851 FILE_COLUMN_NAME, &name,
852 FILE_COLUMN_SIDECARS, &sidecars,
853 FILE_COLUMN_SIZE, &size,
854 FILE_COLUMN_DATE, &time,
855 FILE_COLUMN_STAR_RATING, &star_rating,
858 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
859 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
861 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
862 FILE_COLUMN_EXPANDED, expanded,
864 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
865 FILE_COLUMN_EXPANDED, expanded,
872 g_free(formatted_with_stars);
875 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
878 gchar *sidecars = nullptr;
880 const gchar *time = text_from_time(fd->date);
881 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
882 const gchar *disabled_grouping;
884 gchar *formatted_with_stars;
885 gboolean expanded = FALSE;
888 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
890 star_rating = convert_rating_to_stars(fd->rating);
894 star_rating = nullptr;
897 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
899 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
902 sidecars = file_data_sc_list_to_string(fd);
904 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
905 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
906 size = text_from_size(fd->size);
908 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
909 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
911 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
912 FILE_COLUMN_VERSION, fd->version,
913 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
914 FILE_COLUMN_FORMATTED, formatted,
915 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
916 FILE_COLUMN_SIDECARS, sidecars,
917 FILE_COLUMN_NAME, name,
918 FILE_COLUMN_STAR_RATING, star_rating,
919 FILE_COLUMN_SIZE, size,
920 FILE_COLUMN_DATE, time,
921 #define STORE_SET_IS_SLOW 1
922 #if STORE_SET_IS_SLOW
923 /* this is 3x faster on a directory with 20000 files */
924 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
925 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
926 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
927 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
928 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
929 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
930 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
931 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
932 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
933 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
934 #if FILEDATA_MARKS_SIZE != 10
935 #error this needs to be updated
938 FILE_COLUMN_COLOR, FALSE, -1);
940 #if !STORE_SET_IS_SLOW
943 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
944 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
953 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
958 gint num_ordered = 0;
959 gint num_prepended = 0;
961 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
967 auto fd = static_cast<FileData *>(work->data);
968 gboolean done = FALSE;
972 FileData *old_fd = nullptr;
973 gint old_version = 0;
977 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
978 FILE_COLUMN_POINTER, &old_fd,
979 FILE_COLUMN_VERSION, &old_version,
989 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
991 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
993 if (match == 0) g_warning("multiple fd for the same path");
1004 GtkTreeIter new_iter;
1009 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1014 here should be used gtk_tree_store_append, but this function seems to be O(n)
1015 and it seems to be much faster to add new entries to the beginning and reorder later
1018 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1021 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1022 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1024 if (g_list_find(selected, fd))
1026 /* renamed files - the same fd appears at different position - select it again*/
1027 GtkTreeSelection *selection;
1028 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1029 gtk_tree_selection_select_iter(selection, &new_iter);
1036 file_data_unref(old_fd);
1037 valid = gtk_tree_store_remove(store, &iter);
1042 if (fd->version != old_version || force)
1044 vflist_setup_iter(vf, store, &iter, fd);
1045 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1048 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1059 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1060 file_data_unref(old_fd);
1062 valid = gtk_tree_store_remove(store, &iter);
1065 /* move the prepended entries to the correct position */
1068 gint num_total = num_prepended + num_ordered;
1069 std::vector<gint> new_order;
1070 new_order.reserve(num_total);
1072 for (gint i = 0; i < num_ordered; i++)
1074 new_order.push_back(num_prepended + i);
1076 for (gint i = num_ordered; i < num_total; i++)
1078 new_order.push_back(num_total - 1 - i);
1080 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1084 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1087 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1088 GtkTreeStore *store;
1091 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1092 if (!vf->list) return;
1098 auto fd = static_cast<FileData *>(work->data);
1099 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1104 vf->sort_method = type;
1105 vf->sort_ascend = ascend;
1106 vf->sort_case = case_sensitive;
1108 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1110 std::vector<gint> new_order;
1111 new_order.reserve(i);
1116 auto fd = static_cast<FileData *>(work->data);
1117 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1121 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1122 gtk_tree_store_reorder(store, nullptr, new_order.data());
1124 g_hash_table_destroy(fd_idx_hash);
1128 *-----------------------------------------------------------------------------
1130 *-----------------------------------------------------------------------------
1134 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1139 auto fd = static_cast<FileData *>(work->data);
1142 if (fd->thumb_pixbuf) (*done)++;
1144 if (fd->sidecar_files)
1146 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1152 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1157 auto fd = static_cast<FileData *>(work->data);
1160 if (fd->metadata_in_idle_loaded) (*done)++;
1162 if (fd->sidecar_files)
1164 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1170 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1172 GtkTreeStore *store;
1175 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1177 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1178 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1181 FileData *vflist_thumb_next_fd(ViewFile *vf)
1184 FileData *fd = nullptr;
1186 /* first check the visible files */
1188 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1190 GtkTreeModel *store;
1192 gboolean valid = TRUE;
1194 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1195 gtk_tree_model_get_iter(store, &iter, tpath);
1196 gtk_tree_path_free(tpath);
1199 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1203 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1205 if (!nfd->thumb_pixbuf) fd = nfd;
1207 valid = gtk_tree_model_iter_next(store, &iter);
1211 /* then find first undone */
1215 GList *work = vf->list;
1218 auto fd_p = static_cast<FileData *>(work->data);
1219 if (!fd_p->thumb_pixbuf)
1223 GList *work2 = fd_p->sidecar_files;
1225 while (work2 && !fd)
1227 fd_p = static_cast<FileData *>(work2->data);
1228 if (!fd_p->thumb_pixbuf) fd = fd_p;
1229 work2 = work2->next;
1239 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1241 GtkTreeStore *store;
1248 gchar *formatted_with_stars;
1251 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1253 star_rating = metadata_read_rating_stars(fd);
1255 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1256 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1258 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1259 FILE_COLUMN_NAME, &name,
1260 FILE_COLUMN_SIDECARS, &sidecars,
1261 FILE_COLUMN_SIZE, &size,
1262 FILE_COLUMN_DATE, &time,
1263 FILE_COLUMN_EXPANDED, &expanded,
1266 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1268 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1269 FILE_COLUMN_EXPANDED, expanded,
1272 g_free(star_rating);
1273 g_free(formatted_with_stars);
1276 FileData *vflist_star_next_fd(ViewFile *vf)
1279 FileData *fd = nullptr;
1280 FileData *nfd = nullptr;
1282 /* first check the visible files */
1284 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1286 GtkTreeModel *store;
1288 gboolean valid = TRUE;
1290 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1291 gtk_tree_model_get_iter(store, &iter, tpath);
1292 gtk_tree_path_free(tpath);
1295 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1297 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1299 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1304 valid = gtk_tree_model_iter_next(store, &iter);
1309 vf->stars_filedata = fd;
1311 if (vf->stars_id == 0)
1313 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1318 /* then find first undone */
1322 GList *work = vf->list;
1326 auto fd_p = static_cast<FileData *>(work->data);
1328 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1342 vf->stars_filedata = fd;
1344 if (vf->stars_id == 0)
1346 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1355 *-----------------------------------------------------------------------------
1357 *-----------------------------------------------------------------------------
1360 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1369 auto list_fd = static_cast<FileData *>(work->data);
1370 if (list_fd == fd) return p;
1372 work2 = list_fd->sidecar_files;
1375 /** @FIXME return the same index also for sidecars
1376 it is sufficient for next/prev navigation but it should be rewritten
1377 without using indexes at all
1379 auto sidecar_fd = static_cast<FileData *>(work2->data);
1380 if (sidecar_fd == fd) return p;
1381 work2 = work2->next;
1392 *-----------------------------------------------------------------------------
1394 *-----------------------------------------------------------------------------
1397 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1399 GtkTreeModel *store;
1400 GtkTreeSelection *selection;
1403 gboolean found = FALSE;
1405 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1406 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1408 while (!found && work)
1410 auto tpath = static_cast<GtkTreePath *>(work->data);
1414 gtk_tree_model_get_iter(store, &iter, tpath);
1415 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1416 if (fd_n == fd) found = TRUE;
1419 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1424 #pragma GCC diagnostic push
1425 #pragma GCC diagnostic ignored "-Wunused-function"
1426 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1430 fd = vf_index_get_data(vf, row);
1431 return vflist_row_is_selected(vf, fd);
1433 #pragma GCC diagnostic pop
1435 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1437 GtkTreeModel *store;
1438 GtkTreeSelection *selection;
1442 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1443 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1453 auto tpath = static_cast<GtkTreePath *>(work->data);
1457 gtk_tree_model_get_iter(store, &iter, tpath);
1458 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1467 count = g_list_length(slist);
1468 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1473 GList *vflist_selection_get_list(ViewFile *vf)
1475 GtkTreeModel *store;
1476 GtkTreeSelection *selection;
1478 GList *list = nullptr;
1480 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1481 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1482 for (GList *work = g_list_last(slist); work; work = work->prev)
1484 auto tpath = static_cast<GtkTreePath *>(work->data);
1488 gtk_tree_model_get_iter(store, &iter, tpath);
1489 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1491 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1493 /* unexpanded - add whole group */
1494 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1497 list = g_list_prepend(list, file_data_ref(fd));
1499 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1504 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1506 GtkTreeModel *store;
1507 GtkTreeSelection *selection;
1509 GList *list = nullptr;
1512 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1513 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1517 auto tpath = static_cast<GtkTreePath *>(work->data);
1521 gtk_tree_model_get_iter(store, &iter, tpath);
1522 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1524 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1528 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1530 return g_list_reverse(list);
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(GtkWidget *, gpointer data)
2101 auto vf = static_cast<ViewFile *>(data);
2103 file_data_unregister_notify_func(vf_notify_cb, vf);
2105 vflist_select_idle_cancel(vf);
2106 vf_refresh_idle_cancel(vf);
2110 filelist_free(vf->list);
2113 ViewFile *vflist_new(ViewFile *vf, FileData *)
2115 GtkTreeStore *store;
2116 GtkTreeSelection *selection;
2117 GType flist_types[FILE_COLUMN_COUNT];
2121 vf->info = g_new0(ViewFileInfoList, 1);
2123 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2124 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2125 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2126 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2127 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2128 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2129 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2130 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2131 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2132 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2133 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2134 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2135 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2136 flist_types[i] = G_TYPE_BOOLEAN;
2138 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2140 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2141 g_object_unref(store);
2143 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2144 G_CALLBACK(vflist_expand_cb), vf);
2146 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2147 G_CALLBACK(vflist_collapse_cb), vf);
2149 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2150 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2151 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2153 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2154 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2156 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2160 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2162 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2163 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2167 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2168 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2171 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2172 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2175 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2176 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2179 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2180 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2183 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2184 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2187 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2188 g_assert(column == FILE_VIEW_COLUMN_DATE);
2191 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2195 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2197 if (VFLIST(vf)->thumbs_enabled == enable) return;
2199 VFLIST(vf)->thumbs_enabled = enable;
2201 /* vflist_populate_view is better than vf_refresh:
2202 - no need to re-read the directory
2203 - force update because the formatted string has changed
2207 vflist_populate_view(vf, TRUE);
2208 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2212 void vflist_marks_set(ViewFile *vf, gboolean enable)
2217 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2222 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2223 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2226 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2227 gtk_tree_view_column_set_visible(column, enable);
2232 // Previously disabled, which means that vf->list is complete
2233 file_data_lock_list(vf->list);
2237 // Previously enabled, which means that vf->list is incomplete
2240 g_list_free(columns);
2243 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */