2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "view-file-list.h"
29 #include "layout-image.h"
33 #include "ui-fileops.h"
35 #include "ui-tree-edit.h"
36 #include "uri-utils.h"
37 #include "view-file.h"
41 /* Index to tree store */
43 FILE_COLUMN_POINTER = 0,
46 FILE_COLUMN_FORMATTED,
47 FILE_COLUMN_FORMATTED_WITH_STARS,
50 FILE_COLUMN_STAR_RATING,
56 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
61 /* Index to tree view */
63 FILE_VIEW_COLUMN_MARKS = 0,
64 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
65 FILE_VIEW_COLUMN_THUMB,
66 FILE_VIEW_COLUMN_FORMATTED,
67 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
68 FILE_VIEW_COLUMN_STAR_RATING,
69 FILE_VIEW_COLUMN_SIZE,
70 FILE_VIEW_COLUMN_DATE,
71 FILE_VIEW_COLUMN_COUNT
76 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
77 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
78 static void vflist_populate_view(ViewFile *vf, gboolean force);
79 static gboolean vflist_is_multiline(ViewFile *vf);
80 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
84 *-----------------------------------------------------------------------------
86 *-----------------------------------------------------------------------------
88 struct ViewFileFindRowData {
95 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
97 auto find = static_cast<ViewFileFindRowData *>(data);
99 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
110 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
113 ViewFileFindRowData data = {fd, iter, FALSE, 0};
115 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
116 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
126 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
129 GtkTreeViewColumn *column;
131 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
132 &tpath, &column, nullptr, nullptr))
138 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
139 gtk_tree_model_get_iter(store, &row, tpath);
140 gtk_tree_path_free(tpath);
141 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
149 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
152 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
154 /* it seems that gtk_tree_store_clear may call some callbacks
155 that use the column. Set the pointer to NULL to be safe. */
156 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
161 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
164 GList *files = nullptr;
166 if (unlock_files && vf->marks_enabled)
168 // unlock locked files in this directory
169 filelist_read(vf->dir_fd, &files, nullptr);
173 auto fd = static_cast<FileData *>(work->data);
175 file_data_unlock(fd);
176 file_data_unref(fd); // undo the ref that got added in filelist_read
181 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
182 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
183 gtk_tree_store_clear(GTK_TREE_STORE(store));
186 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
191 if (vflist_find_row(vf, fd, &iter) < 0) return;
192 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
193 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
196 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
201 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
203 tpath = gtk_tree_model_get_path(store, iter);
204 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
205 gtk_tree_path_free(tpath);
210 *-----------------------------------------------------------------------------
212 *-----------------------------------------------------------------------------
215 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
216 GtkSelectionData *selection_data, guint,
217 guint, gpointer data)
219 auto vf = static_cast<ViewFile *>(data);
220 GList *list = nullptr;
222 if (!VFLIST(vf)->click_fd) return;
224 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
226 list = vf_selection_get_list(vf);
230 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
234 uri_selection_data_set_uris_from_filelist(selection_data, list);
238 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
240 auto vf = static_cast<ViewFile *>(data);
242 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
244 if (VFLIST(vf)->thumbs_enabled &&
245 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
249 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
250 items = vf_selection_count(vf, nullptr);
254 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
258 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
260 auto vf = static_cast<ViewFile *>(data);
262 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
264 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
270 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
271 int x, int y, GtkSelectionData *selection,
272 guint info, guint, gpointer data)
274 auto vf = static_cast<ViewFile *>(data);
276 if (info == TARGET_TEXT_PLAIN) {
277 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
280 /* Add keywords to file */
281 auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
282 GList *kw_list = string_to_keywords_list(str);
284 metadata_append_list(fd, KEYWORD_KEY, kw_list);
285 g_list_free_full(kw_list, g_free);
291 void vflist_dnd_init(ViewFile *vf)
293 gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
294 dnd_file_drag_types, dnd_file_drag_types_count,
295 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
296 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
297 dnd_file_drag_types, dnd_file_drag_types_count,
298 static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
300 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
301 G_CALLBACK(vflist_dnd_get), vf);
302 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
303 G_CALLBACK(vflist_dnd_begin), vf);
304 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
305 G_CALLBACK(vflist_dnd_end), vf);
306 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
307 G_CALLBACK(vflist_drag_data_received), vf);
311 *-----------------------------------------------------------------------------
313 *-----------------------------------------------------------------------------
316 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
318 GList *list = nullptr;
320 if (fd->sidecar_files)
322 /* check if the row is expanded */
326 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
327 if (vflist_find_row(vf, fd, &iter) >= 0)
331 tpath = gtk_tree_model_get_path(store, &iter);
332 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
334 /* unexpanded - add whole group */
335 list = filelist_copy(fd->sidecar_files);
337 gtk_tree_path_free(tpath);
341 return g_list_prepend(list, file_data_ref(fd));
344 GList *vflist_pop_menu_file_list(ViewFile *vf)
346 if (!VFLIST(vf)->click_fd) return nullptr;
348 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
350 return vf_selection_get_list(vf);
352 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
356 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
358 auto vf = static_cast<ViewFile *>(data);
360 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
364 list = vf_selection_get_list(vf);
365 view_window_new_from_list(list);
370 view_window_new(VFLIST(vf)->click_fd);
374 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
376 auto vf = static_cast<ViewFile *>(data);
379 list = vf_pop_menu_file_list(vf);
380 if (options->file_ops.enable_in_place_rename &&
381 list && !list->next && VFLIST(vf)->click_fd)
388 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
389 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
393 tpath = gtk_tree_model_get_path(store, &iter);
394 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
395 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
396 vflist_row_rename_cb, vf);
397 gtk_tree_path_free(tpath);
402 file_util_rename(nullptr, list, vf->listview);
405 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
407 auto vf = static_cast<ViewFile *>(data);
409 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
412 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
416 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
420 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
422 GList *columns, *work;
424 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
429 auto column = static_cast<GtkTreeViewColumn *>(work->data);
430 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
433 if (vflist_is_multiline(vf))
435 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
437 gtk_tree_view_column_set_visible(column, enable);
439 if (col_idx == FILE_COLUMN_FORMATTED)
441 gtk_tree_view_column_set_visible(column, !enable);
446 if (col_idx == FILE_COLUMN_STAR_RATING)
448 gtk_tree_view_column_set_visible(column, enable);
452 g_list_free(columns);
455 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
457 auto vf = static_cast<ViewFile *>(data);
459 options->show_star_rating = !options->show_star_rating;
461 vflist_populate_view(vf, TRUE);
463 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
464 vflist_star_rating_set(vf, options->show_star_rating);
467 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
469 auto vf = static_cast<ViewFile *>(data);
471 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
473 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
476 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
478 auto vf = static_cast<ViewFile *>(data);
479 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
480 VFLIST(vf)->click_fd = nullptr;
486 *-----------------------------------------------------------------------------
488 *-----------------------------------------------------------------------------
491 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
493 auto vf = static_cast<ViewFile *>(data);
496 if (!new_name || !new_name[0]) return FALSE;
498 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
500 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
502 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
503 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
508 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
509 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
510 file_util_rename_simple(fd, new_path, vf->listview);
520 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
522 auto vf = static_cast<ViewFile *>(data);
525 if (event->keyval != GDK_KEY_Menu) return FALSE;
527 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
533 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
534 gtk_tree_model_get_iter(store, &iter, tpath);
535 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
536 gtk_tree_path_free(tpath);
540 VFLIST(vf)->click_fd = nullptr;
543 vf->popup = vf_pop_menu(vf);
544 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
549 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
551 auto vf = static_cast<ViewFile *>(data);
554 FileData *fd = nullptr;
555 GtkTreeViewColumn *column;
557 vf->clicked_mark = 0;
559 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
560 &tpath, &column, nullptr, nullptr))
563 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
565 if (bevent->button == MOUSE_BUTTON_LEFT &&
566 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
569 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
570 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
572 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
574 gtk_tree_model_get_iter(store, &iter, tpath);
575 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
576 gtk_tree_path_free(tpath);
579 VFLIST(vf)->click_fd = fd;
581 if (bevent->button == MOUSE_BUTTON_RIGHT)
583 vf->popup = vf_pop_menu(vf);
584 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
588 if (!fd) return FALSE;
590 if (bevent->button == MOUSE_BUTTON_MIDDLE)
592 if (!vflist_row_is_selected(vf, fd))
594 vflist_color_set(vf, fd, TRUE);
600 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
601 !(bevent->state & GDK_SHIFT_MASK ) &&
602 !(bevent->state & GDK_CONTROL_MASK ) &&
603 vflist_row_is_selected(vf, fd))
605 GtkTreeSelection *selection;
607 gtk_widget_grab_focus(widget);
610 /* returning FALSE and further processing of the event is needed for
611 correct operation of the expander, to show the sidecar files.
612 It however resets the selection of multiple files. With this condition
613 it should work for both cases */
614 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
615 return (gtk_tree_selection_count_selected_rows(selection) > 1);
618 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
620 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
622 collection_window_new(VFLIST(vf)->click_fd->path);
626 if (vf->layout) layout_image_full_screen_start(vf->layout);
633 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
635 auto vf = static_cast<ViewFile *>(data);
638 FileData *fd = nullptr;
640 if (defined_mouse_buttons(widget, bevent, vf->layout))
645 if (bevent->button == MOUSE_BUTTON_MIDDLE)
647 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
650 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
655 if ((bevent->x != 0 || bevent->y != 0) &&
656 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
657 &tpath, nullptr, nullptr, nullptr))
661 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
662 gtk_tree_model_get_iter(store, &iter, tpath);
663 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
664 gtk_tree_path_free(tpath);
667 if (bevent->button == MOUSE_BUTTON_MIDDLE)
669 if (fd && VFLIST(vf)->click_fd == fd)
671 GtkTreeSelection *selection;
673 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
674 if (vflist_row_is_selected(vf, fd))
676 gtk_tree_selection_unselect_iter(selection, &iter);
680 gtk_tree_selection_select_iter(selection, &iter);
686 if (fd && VFLIST(vf)->click_fd == fd &&
687 !(bevent->state & GDK_SHIFT_MASK ) &&
688 !(bevent->state & GDK_CONTROL_MASK ) &&
689 vflist_row_is_selected(vf, fd))
691 GtkTreeSelection *selection;
693 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
694 gtk_tree_selection_unselect_all(selection);
695 gtk_tree_selection_select_iter(selection, &iter);
696 vflist_move_cursor(vf, &iter);
702 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
704 FileData *read_ahead_fd = nullptr;
710 cur_fd = layout_image_get_fd(vf->layout);
711 if (sel_fd == cur_fd) return; /* no change */
713 row = g_list_index(vf->list, sel_fd);
714 /** @FIXME sidecar data */
716 if (sel_fd && options->image.enable_read_ahead && row >= 0)
718 if (row > g_list_index(vf->list, cur_fd) &&
719 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
721 read_ahead_fd = vf_index_get_data(vf, row + 1);
725 read_ahead_fd = vf_index_get_data(vf, row - 1);
729 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
732 static gboolean vflist_select_idle_cb(gpointer data)
734 auto vf = static_cast<ViewFile *>(data);
738 VFLIST(vf)->select_idle_id = 0;
739 return G_SOURCE_REMOVE;
744 if (VFLIST(vf)->select_fd)
746 vflist_select_image(vf, VFLIST(vf)->select_fd);
747 VFLIST(vf)->select_fd = nullptr;
750 VFLIST(vf)->select_idle_id = 0;
751 return G_SOURCE_REMOVE;
754 static void vflist_select_idle_cancel(ViewFile *vf)
756 if (VFLIST(vf)->select_idle_id)
758 g_source_remove(VFLIST(vf)->select_idle_id);
759 VFLIST(vf)->select_idle_id = 0;
763 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
765 auto vf = static_cast<ViewFile *>(data);
767 GtkTreePath *cursor_path;
769 VFLIST(vf)->select_fd = nullptr;
771 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
773 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
776 gtk_tree_model_get_iter(store, &iter, cursor_path);
777 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
778 gtk_tree_path_free(cursor_path);
783 !VFLIST(vf)->select_idle_id)
785 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
791 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
793 auto vf = static_cast<ViewFile *>(data);
794 vflist_set_expanded(vf, iter, TRUE);
797 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
799 auto vf = static_cast<ViewFile *>(data);
800 vflist_set_expanded(vf, iter, FALSE);
804 *-----------------------------------------------------------------------------
806 *-----------------------------------------------------------------------------
810 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)
812 gboolean multiline = vflist_is_multiline(vf);
819 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
823 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
828 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
833 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
841 gchar *formatted_with_stars;
843 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
845 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
846 FILE_COLUMN_NAME, &name,
847 FILE_COLUMN_SIDECARS, &sidecars,
848 FILE_COLUMN_SIZE, &size,
849 FILE_COLUMN_DATE, &time,
850 FILE_COLUMN_STAR_RATING, &star_rating,
853 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
854 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
856 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
857 FILE_COLUMN_EXPANDED, expanded,
859 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
860 FILE_COLUMN_EXPANDED, expanded,
867 g_free(formatted_with_stars);
870 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
873 gchar *sidecars = nullptr;
875 const gchar *time = text_from_time(fd->date);
876 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
877 const gchar *disabled_grouping;
879 gchar *formatted_with_stars;
880 gboolean expanded = FALSE;
883 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
885 star_rating = convert_rating_to_stars(fd->rating);
889 star_rating = nullptr;
892 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
894 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
897 sidecars = file_data_sc_list_to_string(fd);
899 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
900 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
901 size = text_from_size(fd->size);
903 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
904 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
906 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
907 FILE_COLUMN_VERSION, fd->version,
908 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
909 FILE_COLUMN_FORMATTED, formatted,
910 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
911 FILE_COLUMN_SIDECARS, sidecars,
912 FILE_COLUMN_NAME, name,
913 FILE_COLUMN_STAR_RATING, star_rating,
914 FILE_COLUMN_SIZE, size,
915 FILE_COLUMN_DATE, time,
916 #define STORE_SET_IS_SLOW 1
917 #if STORE_SET_IS_SLOW
918 /* this is 3x faster on a directory with 20000 files */
919 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
920 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
921 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
922 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
923 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
924 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
925 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
926 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
927 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
928 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
929 #if FILEDATA_MARKS_SIZE != 10
930 #error this needs to be updated
933 FILE_COLUMN_COLOR, FALSE, -1);
935 #if !STORE_SET_IS_SLOW
938 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
939 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
948 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
953 gint num_ordered = 0;
954 gint num_prepended = 0;
956 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
962 auto fd = static_cast<FileData *>(work->data);
963 gboolean done = FALSE;
967 FileData *old_fd = nullptr;
968 gint old_version = 0;
972 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
973 FILE_COLUMN_POINTER, &old_fd,
974 FILE_COLUMN_VERSION, &old_version,
984 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
986 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
988 if (match == 0) g_warning("multiple fd for the same path");
999 GtkTreeIter new_iter;
1004 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1009 here should be used gtk_tree_store_append, but this function seems to be O(n)
1010 and it seems to be much faster to add new entries to the beginning and reorder later
1013 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1016 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1017 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1019 if (g_list_find(selected, fd))
1021 /* renamed files - the same fd appears at different position - select it again*/
1022 GtkTreeSelection *selection;
1023 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1024 gtk_tree_selection_select_iter(selection, &new_iter);
1031 file_data_unref(old_fd);
1032 valid = gtk_tree_store_remove(store, &iter);
1037 if (fd->version != old_version || force)
1039 vflist_setup_iter(vf, store, &iter, fd);
1040 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1043 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1054 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1055 file_data_unref(old_fd);
1057 valid = gtk_tree_store_remove(store, &iter);
1060 /* move the prepended entries to the correct position */
1063 gint num_total = num_prepended + num_ordered;
1064 std::vector<gint> new_order;
1065 new_order.reserve(num_total);
1067 for (gint i = 0; i < num_ordered; i++)
1069 new_order.push_back(num_prepended + i);
1071 for (gint i = num_ordered; i < num_total; i++)
1073 new_order.push_back(num_total - 1 - i);
1075 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1079 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1082 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1083 GtkTreeStore *store;
1086 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1087 if (!vf->list) return;
1093 auto fd = static_cast<FileData *>(work->data);
1094 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1099 vf->sort_method = type;
1100 vf->sort_ascend = ascend;
1101 vf->sort_case = case_sensitive;
1103 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1105 std::vector<gint> new_order;
1106 new_order.reserve(i);
1111 auto fd = static_cast<FileData *>(work->data);
1112 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1116 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1117 gtk_tree_store_reorder(store, nullptr, new_order.data());
1119 g_hash_table_destroy(fd_idx_hash);
1123 *-----------------------------------------------------------------------------
1125 *-----------------------------------------------------------------------------
1129 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1134 auto fd = static_cast<FileData *>(work->data);
1137 if (fd->thumb_pixbuf) (*done)++;
1139 if (fd->sidecar_files)
1141 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1147 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1152 auto fd = static_cast<FileData *>(work->data);
1155 if (fd->metadata_in_idle_loaded) (*done)++;
1157 if (fd->sidecar_files)
1159 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1165 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1167 GtkTreeStore *store;
1170 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1172 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1173 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1176 FileData *vflist_thumb_next_fd(ViewFile *vf)
1179 FileData *fd = nullptr;
1181 /* first check the visible files */
1183 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1185 GtkTreeModel *store;
1187 gboolean valid = TRUE;
1189 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1190 gtk_tree_model_get_iter(store, &iter, tpath);
1191 gtk_tree_path_free(tpath);
1194 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1198 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1200 if (!nfd->thumb_pixbuf) fd = nfd;
1202 valid = gtk_tree_model_iter_next(store, &iter);
1206 /* then find first undone */
1210 GList *work = vf->list;
1213 auto fd_p = static_cast<FileData *>(work->data);
1214 if (!fd_p->thumb_pixbuf)
1218 GList *work2 = fd_p->sidecar_files;
1220 while (work2 && !fd)
1222 fd_p = static_cast<FileData *>(work2->data);
1223 if (!fd_p->thumb_pixbuf) fd = fd_p;
1224 work2 = work2->next;
1234 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1236 GtkTreeStore *store;
1243 gchar *formatted_with_stars;
1246 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1248 star_rating = metadata_read_rating_stars(fd);
1250 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1251 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1253 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1254 FILE_COLUMN_NAME, &name,
1255 FILE_COLUMN_SIDECARS, &sidecars,
1256 FILE_COLUMN_SIZE, &size,
1257 FILE_COLUMN_DATE, &time,
1258 FILE_COLUMN_EXPANDED, &expanded,
1261 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1263 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1264 FILE_COLUMN_EXPANDED, expanded,
1267 g_free(star_rating);
1268 g_free(formatted_with_stars);
1271 FileData *vflist_star_next_fd(ViewFile *vf)
1274 FileData *fd = nullptr;
1275 FileData *nfd = nullptr;
1277 /* first check the visible files */
1279 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1281 GtkTreeModel *store;
1283 gboolean valid = TRUE;
1285 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1286 gtk_tree_model_get_iter(store, &iter, tpath);
1287 gtk_tree_path_free(tpath);
1290 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1292 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1294 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1299 valid = gtk_tree_model_iter_next(store, &iter);
1304 vf->stars_filedata = fd;
1306 if (vf->stars_id == 0)
1308 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1313 /* then find first undone */
1317 GList *work = vf->list;
1321 auto fd_p = static_cast<FileData *>(work->data);
1323 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1337 vf->stars_filedata = fd;
1339 if (vf->stars_id == 0)
1341 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1350 *-----------------------------------------------------------------------------
1352 *-----------------------------------------------------------------------------
1355 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1358 GList *work, *work2;
1363 auto list_fd = static_cast<FileData *>(work->data);
1364 if (list_fd == fd) return p;
1366 work2 = list_fd->sidecar_files;
1369 /** @FIXME return the same index also for sidecars
1370 it is sufficient for next/prev navigation but it should be rewritten
1371 without using indexes at all
1373 auto sidecar_fd = static_cast<FileData *>(work2->data);
1374 if (sidecar_fd == fd) return p;
1375 work2 = work2->next;
1386 *-----------------------------------------------------------------------------
1388 *-----------------------------------------------------------------------------
1391 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1393 GtkTreeModel *store;
1394 GtkTreeSelection *selection;
1397 gboolean found = FALSE;
1399 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1400 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1402 while (!found && work)
1404 auto tpath = static_cast<GtkTreePath *>(work->data);
1408 gtk_tree_model_get_iter(store, &iter, tpath);
1409 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1410 if (fd_n == fd) found = TRUE;
1413 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1418 #pragma GCC diagnostic push
1419 #pragma GCC diagnostic ignored "-Wunused-function"
1420 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1424 fd = vf_index_get_data(vf, row);
1425 return vflist_row_is_selected(vf, fd);
1427 #pragma GCC diagnostic pop
1429 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1431 GtkTreeModel *store;
1432 GtkTreeSelection *selection;
1436 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1437 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1447 auto tpath = static_cast<GtkTreePath *>(work->data);
1451 gtk_tree_model_get_iter(store, &iter, tpath);
1452 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1461 count = g_list_length(slist);
1462 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1467 GList *vflist_selection_get_list(ViewFile *vf)
1469 GtkTreeModel *store;
1470 GtkTreeSelection *selection;
1472 GList *list = nullptr;
1474 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1475 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1476 for (GList *work = g_list_last(slist); work; work = work->prev)
1478 auto tpath = static_cast<GtkTreePath *>(work->data);
1482 gtk_tree_model_get_iter(store, &iter, tpath);
1483 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1485 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1487 /* unexpanded - add whole group */
1488 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1491 list = g_list_prepend(list, file_data_ref(fd));
1493 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1498 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1500 GtkTreeModel *store;
1501 GtkTreeSelection *selection;
1503 GList *list = nullptr;
1506 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1507 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1511 auto tpath = static_cast<GtkTreePath *>(work->data);
1515 gtk_tree_model_get_iter(store, &iter, tpath);
1516 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1518 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1522 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1524 return g_list_reverse(list);
1527 void vflist_select_all(ViewFile *vf)
1529 GtkTreeSelection *selection;
1531 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1532 gtk_tree_selection_select_all(selection);
1534 VFLIST(vf)->select_fd = nullptr;
1537 void vflist_select_none(ViewFile *vf)
1539 GtkTreeSelection *selection;
1541 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1542 gtk_tree_selection_unselect_all(selection);
1545 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1550 tpath = gtk_tree_model_get_path(store, iter);
1551 result = gtk_tree_path_prev(tpath);
1553 gtk_tree_model_get_iter(store, iter, tpath);
1555 gtk_tree_path_free(tpath);
1560 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1562 if (!gtk_tree_model_get_iter_first(store, iter))
1567 GtkTreeIter next = *iter;
1569 if (gtk_tree_model_iter_next(store, &next))
1578 void vflist_select_invert(ViewFile *vf)
1581 GtkTreeSelection *selection;
1582 GtkTreeModel *store;
1585 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1586 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1588 /* Backward iteration prevents scrolling to the end of the list,
1589 * it scrolls to the first selected row instead. */
1590 valid = tree_model_get_iter_last(store, &iter);
1594 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1597 gtk_tree_selection_unselect_iter(selection, &iter);
1599 gtk_tree_selection_select_iter(selection, &iter);
1601 valid = tree_model_iter_prev(store, &iter);
1605 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1609 if (vflist_find_row(vf, fd, &iter) < 0) return;
1611 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1613 if (!vflist_row_is_selected(vf, fd))
1615 GtkTreeSelection *selection;
1616 GtkTreeModel *store;
1619 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1620 gtk_tree_selection_unselect_all(selection);
1621 gtk_tree_selection_select_iter(selection, &iter);
1622 vflist_move_cursor(vf, &iter);
1624 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1625 tpath = gtk_tree_model_get_path(store, &iter);
1626 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1627 gtk_tree_path_free(tpath);
1631 void vflist_select_list(ViewFile *vf, GList *list)
1642 fd = static_cast<FileData *>(work->data);
1644 if (vflist_find_row(vf, fd, &iter) < 0) return;
1645 if (!vflist_row_is_selected(vf, fd))
1647 GtkTreeSelection *selection;
1649 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1650 gtk_tree_selection_select_iter(selection, &iter);
1656 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1659 FileData *fd = nullptr;
1661 if (sel_fd->parent) sel_fd = sel_fd->parent;
1667 fd = static_cast<FileData *>(work->data);
1670 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1672 if (match >= 0) break;
1675 if (fd) vflist_select_by_fd(vf, fd);
1679 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1681 GtkTreeModel *store;
1683 GtkTreeSelection *selection;
1687 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1689 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1690 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1692 valid = gtk_tree_model_get_iter_first(store, &iter);
1696 gboolean mark_val, selected;
1697 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1699 mark_val = file_data_get_mark(fd, n);
1700 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1704 case MTS_MODE_SET: selected = mark_val;
1706 case MTS_MODE_OR: selected = mark_val || selected;
1708 case MTS_MODE_AND: selected = mark_val && selected;
1710 case MTS_MODE_MINUS: selected = !mark_val && selected;
1715 gtk_tree_selection_select_iter(selection, &iter);
1717 gtk_tree_selection_unselect_iter(selection, &iter);
1719 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1723 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1725 GtkTreeModel *store;
1726 GtkTreeSelection *selection;
1731 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1733 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1734 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1738 auto tpath = static_cast<GtkTreePath *>(work->data);
1742 gtk_tree_model_get_iter(store, &iter, tpath);
1743 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1745 /* the change has a very limited range and the standard notification would trigger
1746 complete re-read of the directory - try to do only minimal update instead */
1747 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1751 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1753 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1755 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1759 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1761 vf_refresh_idle(vf);
1765 /* mark functions can have various side effects - update all columns to be sure */
1766 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1767 /* mark functions can change sidecars too */
1768 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1772 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1776 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1780 *-----------------------------------------------------------------------------
1782 *-----------------------------------------------------------------------------
1785 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1787 GtkTreeViewColumn *column;
1788 GtkCellRenderer *cell;
1791 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1792 if (!column) return;
1794 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1796 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1798 cell = static_cast<GtkCellRenderer *>(list->data);
1801 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1802 gtk_tree_view_column_set_visible(column, thumb);
1804 if (options->show_star_rating)
1806 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1807 if (!column) return;
1808 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1809 gtk_tree_view_column_set_visible(column, TRUE);
1811 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1812 if (!column) return;
1813 gtk_tree_view_column_set_visible(column, FALSE);
1817 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1818 if (!column) return;
1819 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1820 gtk_tree_view_column_set_visible(column, TRUE);
1822 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1823 if (!column) return;
1824 gtk_tree_view_column_set_visible(column, FALSE);
1827 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1828 if (!column) return;
1829 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1831 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1832 if (!column) return;
1833 gtk_tree_view_column_set_visible(column, !multiline);
1835 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1836 if (!column) return;
1837 gtk_tree_view_column_set_visible(column, !multiline);
1840 static gboolean vflist_is_multiline(ViewFile *vf)
1842 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1846 static void vflist_populate_view(ViewFile *vf, gboolean force)
1848 GtkTreeStore *store;
1851 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1858 vflist_store_clear(vf, FALSE);
1863 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1865 selected = vflist_selection_get_list(vf);
1867 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1869 if (selected && vflist_selection_count(vf, nullptr) == 0)
1871 /* all selected files disappeared */
1872 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1875 filelist_free(selected);
1878 vf_thumb_update(vf);
1882 gboolean vflist_refresh(ViewFile *vf)
1885 gboolean ret = TRUE;
1887 old_list = vf->list;
1890 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1893 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1895 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1897 if (vf->marks_enabled)
1899 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1900 // each time a mark is changed.
1901 file_data_lock_list(vf->list);
1905 /** @FIXME only do this when needed (aka when we just switched from */
1906 /** @FIXME marks-enabled to marks-disabled) */
1907 file_data_unlock_list(vf->list);
1910 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1911 vf->list = g_list_first(vf->list);
1912 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1914 vf->list = g_list_first(vf->list);
1915 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1917 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1919 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1920 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1923 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1925 vflist_populate_view(vf, FALSE);
1927 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1929 filelist_free(old_list);
1930 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1937 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1940 CELL_HEIGHT_OVERRIDE = 512
1943 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1947 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1948 if (spec && G_IS_PARAM_SPEC_INT(spec))
1950 GParamSpecInt *spec_int;
1952 spec_int = G_PARAM_SPEC_INT(spec);
1953 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1957 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1959 static GdkRGBA color;
1960 static GdkRGBA color_style;
1961 static GtkWidget *done = nullptr;
1967 style = gtk_widget_get_style(widget);
1968 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color_style);
1970 memcpy(&color, &color_style, sizeof(color));
1971 shift_color(&color, -1, 0);
1978 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1979 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1981 auto vf = static_cast<ViewFile *>(data);
1984 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1985 g_object_set(G_OBJECT(cell),
1986 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1987 "cell-background-set", set, NULL);
1990 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1992 GtkTreeViewColumn *column;
1993 GtkCellRenderer *renderer;
1995 column = gtk_tree_view_column_new();
1996 gtk_tree_view_column_set_title(column, title);
1997 gtk_tree_view_column_set_min_width(column, 4);
2001 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2002 renderer = gtk_cell_renderer_text_new();
2005 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2007 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2008 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2010 gtk_tree_view_column_set_expand(column, TRUE);
2014 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2015 renderer = gtk_cell_renderer_pixbuf_new();
2016 cell_renderer_height_override(renderer);
2017 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2018 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2021 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2022 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2023 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2025 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2028 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2030 auto vf = static_cast<ViewFile *>(data);
2031 GtkTreeStore *store;
2032 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2038 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2039 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2042 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2044 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2046 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2049 /* the change has a very limited range and the standard notification would trigger
2050 complete re-read of the directory - try to do only minimal update instead */
2051 file_data_unregister_notify_func(vf_notify_cb, vf);
2052 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2053 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2055 vf_refresh_idle(vf);
2059 /* mark functions can have various side effects - update all columns to be sure */
2060 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2061 /* mark functions can change sidecars too */
2062 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2064 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2066 gtk_tree_path_free(path);
2069 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2071 GtkTreeViewColumn *column;
2072 GtkCellRenderer *renderer;
2074 renderer = gtk_cell_renderer_toggle_new();
2075 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2077 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2078 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2079 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2081 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2082 gtk_tree_view_column_set_fixed_width(column, 22);
2083 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2086 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2090 *-----------------------------------------------------------------------------
2092 *-----------------------------------------------------------------------------
2095 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2098 if (!dir_fd) return FALSE;
2099 if (vf->dir_fd == dir_fd) return TRUE;
2101 file_data_unref(vf->dir_fd);
2102 vf->dir_fd = file_data_ref(dir_fd);
2104 /* force complete reload */
2105 vflist_store_clear(vf, TRUE);
2107 filelist_free(vf->list);
2110 ret = vf_refresh(vf);
2111 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2115 void vflist_destroy_cb(GtkWidget *, gpointer data)
2117 auto vf = static_cast<ViewFile *>(data);
2119 file_data_unregister_notify_func(vf_notify_cb, vf);
2121 vflist_select_idle_cancel(vf);
2122 vf_refresh_idle_cancel(vf);
2126 filelist_free(vf->list);
2129 ViewFile *vflist_new(ViewFile *vf, FileData *)
2131 GtkTreeStore *store;
2132 GtkTreeSelection *selection;
2133 GType flist_types[FILE_COLUMN_COUNT];
2137 vf->info = g_new0(ViewFileInfoList, 1);
2139 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2140 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2141 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2142 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2143 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2144 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2145 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2146 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2147 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2148 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2149 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2150 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2151 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2152 flist_types[i] = G_TYPE_BOOLEAN;
2154 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2156 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2157 g_object_unref(store);
2159 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2160 G_CALLBACK(vflist_expand_cb), vf);
2162 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2163 G_CALLBACK(vflist_collapse_cb), vf);
2165 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2166 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2167 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2169 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2170 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2172 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2176 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2178 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2179 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2183 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2184 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2187 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2188 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2191 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2192 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2195 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2196 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2199 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2200 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2203 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2204 g_assert(column == FILE_VIEW_COLUMN_DATE);
2207 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2211 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2213 if (VFLIST(vf)->thumbs_enabled == enable) return;
2215 VFLIST(vf)->thumbs_enabled = enable;
2217 /* vflist_populate_view is better than vf_refresh:
2218 - no need to re-read the directory
2219 - force update because the formatted string has changed
2223 vflist_populate_view(vf, TRUE);
2224 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2228 void vflist_marks_set(ViewFile *vf, gboolean enable)
2230 GList *columns, *work;
2232 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2237 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2238 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2241 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2242 gtk_tree_view_column_set_visible(column, enable);
2247 // Previously disabled, which means that vf->list is complete
2248 file_data_lock_list(vf->list);
2252 // Previously enabled, which means that vf->list is incomplete
2255 g_list_free(columns);
2258 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */