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"
31 #include "layout-image.h"
33 #include "main-defines.h"
36 #include "ui-fileops.h"
38 #include "ui-tree-edit.h"
39 #include "uri-utils.h"
41 #include "view-file.h"
45 /* Index to tree store */
47 FILE_COLUMN_POINTER = 0,
50 FILE_COLUMN_FORMATTED,
51 FILE_COLUMN_FORMATTED_WITH_STARS,
54 FILE_COLUMN_STAR_RATING,
60 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
65 /* Index to tree view */
67 FILE_VIEW_COLUMN_MARKS = 0,
68 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
69 FILE_VIEW_COLUMN_THUMB,
70 FILE_VIEW_COLUMN_FORMATTED,
71 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
72 FILE_VIEW_COLUMN_STAR_RATING,
73 FILE_VIEW_COLUMN_SIZE,
74 FILE_VIEW_COLUMN_DATE,
75 FILE_VIEW_COLUMN_COUNT
80 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
81 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
82 static void vflist_populate_view(ViewFile *vf, gboolean force);
83 static gboolean vflist_is_multiline(ViewFile *vf);
84 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
88 *-----------------------------------------------------------------------------
90 *-----------------------------------------------------------------------------
92 struct ViewFileFindRowData {
99 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
101 auto find = static_cast<ViewFileFindRowData *>(data);
103 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
114 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
117 ViewFileFindRowData data = {fd, iter, FALSE, 0};
119 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
120 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
130 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
133 GtkTreeViewColumn *column;
135 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
136 &tpath, &column, nullptr, nullptr))
142 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
143 gtk_tree_model_get_iter(store, &row, tpath);
144 gtk_tree_path_free(tpath);
145 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
153 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
156 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
158 /* it seems that gtk_tree_store_clear may call some callbacks
159 that use the column. Set the pointer to NULL to be safe. */
160 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
165 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
168 GList *files = nullptr;
170 if (unlock_files && vf->marks_enabled)
172 // unlock locked files in this directory
173 filelist_read(vf->dir_fd, &files, nullptr);
177 auto fd = static_cast<FileData *>(work->data);
179 file_data_unlock(fd);
180 file_data_unref(fd); // undo the ref that got added in filelist_read
185 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
186 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
187 gtk_tree_store_clear(GTK_TREE_STORE(store));
190 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
195 if (vflist_find_row(vf, fd, &iter) < 0) return;
196 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
197 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
200 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
205 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
207 tpath = gtk_tree_model_get_path(store, iter);
208 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
209 gtk_tree_path_free(tpath);
214 *-----------------------------------------------------------------------------
216 *-----------------------------------------------------------------------------
219 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
220 GtkSelectionData *selection_data, guint,
221 guint, gpointer data)
223 auto vf = static_cast<ViewFile *>(data);
224 GList *list = nullptr;
226 if (!VFLIST(vf)->click_fd) return;
228 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
230 list = vf_selection_get_list(vf);
234 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
238 uri_selection_data_set_uris_from_filelist(selection_data, list);
242 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
244 auto vf = static_cast<ViewFile *>(data);
246 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
248 if (VFLIST(vf)->thumbs_enabled &&
249 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
253 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
254 items = vf_selection_count(vf, nullptr);
258 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
262 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
264 auto vf = static_cast<ViewFile *>(data);
266 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
268 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
274 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
275 int x, int y, GtkSelectionData *selection,
276 guint info, guint, gpointer data)
278 auto vf = static_cast<ViewFile *>(data);
280 if (info == TARGET_TEXT_PLAIN) {
281 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
284 /* Add keywords to file */
285 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
286 GList *kw_list = string_to_keywords_list(str);
288 metadata_append_list(fd, KEYWORD_KEY, kw_list);
289 g_list_free_full(kw_list, g_free);
295 void vflist_dnd_init(ViewFile *vf)
297 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
298 dnd_file_drag_types, dnd_file_drag_types_count,
299 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
300 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
301 dnd_file_drag_types, dnd_file_drag_types_count,
302 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
304 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
305 G_CALLBACK(vflist_dnd_get), vf);
306 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
307 G_CALLBACK(vflist_dnd_begin), vf);
308 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
309 G_CALLBACK(vflist_dnd_end), vf);
310 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
311 G_CALLBACK(vflist_drag_data_received), vf);
315 *-----------------------------------------------------------------------------
317 *-----------------------------------------------------------------------------
320 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
322 GList *list = nullptr;
324 if (fd->sidecar_files)
326 /* check if the row is expanded */
330 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
331 if (vflist_find_row(vf, fd, &iter) >= 0)
335 tpath = gtk_tree_model_get_path(store, &iter);
336 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
338 /* unexpanded - add whole group */
339 list = filelist_copy(fd->sidecar_files);
341 gtk_tree_path_free(tpath);
345 return g_list_prepend(list, file_data_ref(fd));
348 GList *vflist_pop_menu_file_list(ViewFile *vf)
350 if (!VFLIST(vf)->click_fd) return nullptr;
352 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
354 return vf_selection_get_list(vf);
356 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
360 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
362 auto vf = static_cast<ViewFile *>(data);
364 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
368 list = vf_selection_get_list(vf);
369 view_window_new_from_list(list);
374 view_window_new(VFLIST(vf)->click_fd);
378 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
380 auto vf = static_cast<ViewFile *>(data);
383 list = vf_pop_menu_file_list(vf);
384 if (options->file_ops.enable_in_place_rename &&
385 list && !list->next && VFLIST(vf)->click_fd)
392 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
393 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
397 tpath = gtk_tree_model_get_path(store, &iter);
398 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
399 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
400 vflist_row_rename_cb, vf);
401 gtk_tree_path_free(tpath);
406 file_util_rename(nullptr, list, vf->listview);
409 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
411 auto vf = static_cast<ViewFile *>(data);
413 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
416 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
420 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
424 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
429 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
434 auto column = static_cast<GtkTreeViewColumn *>(work->data);
435 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
438 if (vflist_is_multiline(vf))
440 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
442 gtk_tree_view_column_set_visible(column, enable);
444 if (col_idx == FILE_COLUMN_FORMATTED)
446 gtk_tree_view_column_set_visible(column, !enable);
451 if (col_idx == FILE_COLUMN_STAR_RATING)
453 gtk_tree_view_column_set_visible(column, enable);
457 g_list_free(columns);
460 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
462 auto vf = static_cast<ViewFile *>(data);
464 options->show_star_rating = !options->show_star_rating;
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());
1944 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1947 CELL_HEIGHT_OVERRIDE = 512
1950 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1954 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1955 if (spec && G_IS_PARAM_SPEC_INT(spec))
1957 GParamSpecInt *spec_int;
1959 spec_int = G_PARAM_SPEC_INT(spec);
1960 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1964 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1966 static GdkRGBA color;
1967 static GtkWidget *done = nullptr;
1973 style = gtk_widget_get_style(widget);
1974 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1976 shift_color(&color, -1, 0);
1983 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1984 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1986 auto vf = static_cast<ViewFile *>(data);
1989 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1990 g_object_set(G_OBJECT(cell),
1991 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1992 "cell-background-set", set, NULL);
1995 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1997 GtkTreeViewColumn *column;
1998 GtkCellRenderer *renderer;
2000 column = gtk_tree_view_column_new();
2001 gtk_tree_view_column_set_title(column, title);
2002 gtk_tree_view_column_set_min_width(column, 4);
2006 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2007 renderer = gtk_cell_renderer_text_new();
2010 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2012 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2013 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2015 gtk_tree_view_column_set_expand(column, TRUE);
2019 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2020 renderer = gtk_cell_renderer_pixbuf_new();
2021 cell_renderer_height_override(renderer);
2022 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2023 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2026 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2027 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2028 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2030 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2033 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2035 auto vf = static_cast<ViewFile *>(data);
2036 GtkTreeStore *store;
2037 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2043 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2044 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2047 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2049 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2051 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2054 /* the change has a very limited range and the standard notification would trigger
2055 complete re-read of the directory - try to do only minimal update instead */
2056 file_data_unregister_notify_func(vf_notify_cb, vf);
2057 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2058 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2060 vf_refresh_idle(vf);
2064 /* mark functions can have various side effects - update all columns to be sure */
2065 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2066 /* mark functions can change sidecars too */
2067 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2069 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2071 gtk_tree_path_free(path);
2074 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2076 GtkTreeViewColumn *column;
2077 GtkCellRenderer *renderer;
2079 renderer = gtk_cell_renderer_toggle_new();
2080 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2082 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2083 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2084 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2086 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2087 gtk_tree_view_column_set_fixed_width(column, 22);
2088 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2091 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2095 *-----------------------------------------------------------------------------
2097 *-----------------------------------------------------------------------------
2100 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2103 if (!dir_fd) return FALSE;
2104 if (vf->dir_fd == dir_fd) return TRUE;
2106 file_data_unref(vf->dir_fd);
2107 vf->dir_fd = file_data_ref(dir_fd);
2109 /* force complete reload */
2110 vflist_store_clear(vf, TRUE);
2112 filelist_free(vf->list);
2115 ret = vf_refresh(vf);
2116 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2120 void vflist_destroy_cb(GtkWidget *, gpointer data)
2122 auto vf = static_cast<ViewFile *>(data);
2124 file_data_unregister_notify_func(vf_notify_cb, vf);
2126 vflist_select_idle_cancel(vf);
2127 vf_refresh_idle_cancel(vf);
2131 filelist_free(vf->list);
2134 ViewFile *vflist_new(ViewFile *vf, FileData *)
2136 GtkTreeStore *store;
2137 GtkTreeSelection *selection;
2138 GType flist_types[FILE_COLUMN_COUNT];
2142 vf->info = g_new0(ViewFileInfoList, 1);
2144 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2145 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2146 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2147 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2148 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2149 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2150 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2151 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2152 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2153 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2154 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2155 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2156 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2157 flist_types[i] = G_TYPE_BOOLEAN;
2159 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2161 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2162 g_object_unref(store);
2164 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2165 G_CALLBACK(vflist_expand_cb), vf);
2167 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2168 G_CALLBACK(vflist_collapse_cb), vf);
2170 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2171 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2172 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2174 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2175 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2177 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2181 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2183 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2184 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2188 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2189 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2192 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2193 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2196 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2197 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2200 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2201 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2204 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2205 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2208 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2209 g_assert(column == FILE_VIEW_COLUMN_DATE);
2212 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2216 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2218 if (VFLIST(vf)->thumbs_enabled == enable) return;
2220 VFLIST(vf)->thumbs_enabled = enable;
2222 /* vflist_populate_view is better than vf_refresh:
2223 - no need to re-read the directory
2224 - force update because the formatted string has changed
2228 vflist_populate_view(vf, TRUE);
2229 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2233 void vflist_marks_set(ViewFile *vf, gboolean enable)
2238 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2243 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2244 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2247 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2248 gtk_tree_view_column_set_visible(column, enable);
2253 // Previously disabled, which means that vf->list is complete
2254 file_data_lock_list(vf->list);
2258 // Previously enabled, which means that vf->list is incomplete
2261 g_list_free(columns);
2264 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */