2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "view-file-list.h"
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 #include <glib-object.h>
35 #include "layout-image.h"
37 #include "main-defines.h"
41 #include "ui-fileops.h"
44 #include "ui-tree-edit.h"
46 #include "view-file.h"
48 /* Index to tree store */
50 FILE_COLUMN_POINTER = 0,
53 FILE_COLUMN_FORMATTED,
54 FILE_COLUMN_FORMATTED_WITH_STARS,
57 FILE_COLUMN_STAR_RATING,
63 FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
68 /* Index to tree view */
70 FILE_VIEW_COLUMN_MARKS = 0,
71 FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
72 FILE_VIEW_COLUMN_THUMB,
73 FILE_VIEW_COLUMN_FORMATTED,
74 FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
75 FILE_VIEW_COLUMN_STAR_RATING,
76 FILE_VIEW_COLUMN_SIZE,
77 FILE_VIEW_COLUMN_DATE,
78 FILE_VIEW_COLUMN_COUNT
83 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
84 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
85 static void vflist_populate_view(ViewFile *vf, gboolean force);
86 static gboolean vflist_is_multiline(ViewFile *vf);
87 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
91 *-----------------------------------------------------------------------------
93 *-----------------------------------------------------------------------------
95 struct ViewFileFindRowData {
102 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
104 auto find = static_cast<ViewFileFindRowData *>(data);
106 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
117 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
120 ViewFileFindRowData data = {fd, iter, FALSE, 0};
122 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
123 gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
133 FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
136 GtkTreeViewColumn *column;
138 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
139 &tpath, &column, nullptr, nullptr))
145 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
146 gtk_tree_model_get_iter(store, &row, tpath);
147 gtk_tree_path_free(tpath);
148 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
156 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
159 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
161 /* it seems that gtk_tree_store_clear may call some callbacks
162 that use the column. Set the pointer to NULL to be safe. */
163 gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
168 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
171 GList *files = nullptr;
173 if (unlock_files && vf->marks_enabled)
175 // unlock locked files in this directory
176 filelist_read(vf->dir_fd, &files, nullptr);
180 auto fd = static_cast<FileData *>(work->data);
182 file_data_unlock(fd);
183 file_data_unref(fd); // undo the ref that got added in filelist_read
188 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
189 gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
190 gtk_tree_store_clear(GTK_TREE_STORE(store));
193 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
198 if (vflist_find_row(vf, fd, &iter) < 0) return;
199 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
200 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
203 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
208 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
210 tpath = gtk_tree_model_get_path(store, iter);
211 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
212 gtk_tree_path_free(tpath);
217 *-----------------------------------------------------------------------------
219 *-----------------------------------------------------------------------------
222 void vflist_dnd_begin(ViewFile *vf, GtkWidget *widget, GdkDragContext *context)
224 vflist_color_set(vf, vf->click_fd, TRUE);
226 if (VFLIST(vf)->thumbs_enabled &&
227 vf->click_fd && vf->click_fd->thumb_pixbuf)
231 if (vflist_row_is_selected(vf, vf->click_fd))
232 items = vflist_selection_count(vf, nullptr);
236 dnd_set_drag_icon(widget, context, vf->click_fd->thumb_pixbuf, items);
240 void vflist_dnd_end(ViewFile *vf, GdkDragContext *context)
242 vflist_color_set(vf, vf->click_fd, FALSE);
244 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
251 *-----------------------------------------------------------------------------
253 *-----------------------------------------------------------------------------
256 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
258 GList *list = nullptr;
260 if (fd->sidecar_files)
262 /* check if the row is expanded */
266 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
267 if (vflist_find_row(vf, fd, &iter) >= 0)
271 tpath = gtk_tree_model_get_path(store, &iter);
272 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
274 /* unexpanded - add whole group */
275 list = filelist_copy(fd->sidecar_files);
277 gtk_tree_path_free(tpath);
281 return g_list_prepend(list, file_data_ref(fd));
284 void vflist_pop_menu_rename_cb(ViewFile *vf)
288 list = vf_pop_menu_file_list(vf);
289 if (options->file_ops.enable_in_place_rename &&
290 list && !list->next && vf->click_fd)
297 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
298 if (vflist_find_row(vf, vf->click_fd, &iter) >= 0)
302 tpath = gtk_tree_model_get_path(store, &iter);
303 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
304 FILE_VIEW_COLUMN_FORMATTED, vf->click_fd->name,
305 vflist_row_rename_cb, vf);
306 gtk_tree_path_free(tpath);
311 file_util_rename(nullptr, list, vf->listview);
314 static void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
316 auto vf = static_cast<ViewFile *>(data);
318 vflist_color_set(vf, vf->click_fd, FALSE);
321 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
325 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
329 void vflist_pop_menu_add_items(ViewFile *vf, GtkWidget *menu)
331 menu_item_add_check(menu, _("Show _thumbnails"), VFLIST(vf)->thumbs_enabled,
332 G_CALLBACK(vflist_pop_menu_thumbs_cb), vf);
335 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
340 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
345 auto column = static_cast<GtkTreeViewColumn *>(work->data);
346 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
349 if (vflist_is_multiline(vf))
351 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
353 gtk_tree_view_column_set_visible(column, enable);
355 if (col_idx == FILE_COLUMN_FORMATTED)
357 gtk_tree_view_column_set_visible(column, !enable);
362 if (col_idx == FILE_COLUMN_STAR_RATING)
364 gtk_tree_view_column_set_visible(column, enable);
368 g_list_free(columns);
371 void vflist_pop_menu_show_star_rating_cb(ViewFile *vf)
373 vflist_populate_view(vf, TRUE);
375 vflist_color_set(vf, vf->click_fd, FALSE);
376 vflist_star_rating_set(vf, options->show_star_rating);
379 void vflist_pop_menu_refresh_cb(ViewFile *vf)
381 vflist_color_set(vf, vf->click_fd, FALSE);
383 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
386 void vflist_popup_destroy_cb(ViewFile *vf)
388 vflist_color_set(vf, vf->click_fd, FALSE);
393 *-----------------------------------------------------------------------------
395 *-----------------------------------------------------------------------------
398 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
400 auto vf = static_cast<ViewFile *>(data);
403 if (!new_name || !new_name[0]) return FALSE;
405 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
407 if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
409 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
410 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
415 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
416 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
417 file_util_rename_simple(fd, new_path, vf->listview);
427 gboolean vflist_press_key_cb(ViewFile *vf, GtkWidget *widget, GdkEventKey *event)
431 if (event->keyval != GDK_KEY_Menu) return FALSE;
433 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
439 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
440 gtk_tree_model_get_iter(store, &iter, tpath);
441 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &vf->click_fd, -1);
442 gtk_tree_path_free(tpath);
446 vf->click_fd = nullptr;
449 vf->popup = vf_pop_menu(vf);
450 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
455 gboolean vflist_press_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
459 FileData *fd = nullptr;
460 GtkTreeViewColumn *column;
462 vf->clicked_mark = 0;
464 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
465 &tpath, &column, nullptr, nullptr))
468 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
470 if (bevent->button == MOUSE_BUTTON_LEFT &&
471 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
474 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
475 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
477 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
479 gtk_tree_model_get_iter(store, &iter, tpath);
480 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
481 gtk_tree_path_free(tpath);
486 if (bevent->button == MOUSE_BUTTON_RIGHT)
488 vf->popup = vf_pop_menu(vf);
489 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
493 if (!fd) return FALSE;
495 if (bevent->button == MOUSE_BUTTON_MIDDLE)
497 if (!vflist_row_is_selected(vf, fd))
499 vflist_color_set(vf, fd, TRUE);
505 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
506 !(bevent->state & GDK_SHIFT_MASK ) &&
507 !(bevent->state & GDK_CONTROL_MASK ) &&
508 vflist_row_is_selected(vf, fd))
510 GtkTreeSelection *selection;
512 gtk_widget_grab_focus(widget);
515 /* returning FALSE and further processing of the event is needed for
516 correct operation of the expander, to show the sidecar files.
517 It however resets the selection of multiple files. With this condition
518 it should work for both cases */
519 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
520 return (gtk_tree_selection_count_selected_rows(selection) > 1);
523 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
525 if (vf->click_fd->format_class == FORMAT_CLASS_COLLECTION)
527 collection_window_new(vf->click_fd->path);
531 if (vf->layout) layout_image_full_screen_start(vf->layout);
538 gboolean vflist_release_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
542 FileData *fd = nullptr;
544 if (defined_mouse_buttons(widget, bevent, vf->layout))
549 if (bevent->button == MOUSE_BUTTON_MIDDLE)
551 vflist_color_set(vf, vf->click_fd, FALSE);
554 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
559 if ((bevent->x != 0 || bevent->y != 0) &&
560 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
561 &tpath, nullptr, nullptr, nullptr))
565 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
566 gtk_tree_model_get_iter(store, &iter, tpath);
567 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
568 gtk_tree_path_free(tpath);
571 if (bevent->button == MOUSE_BUTTON_MIDDLE)
573 if (fd && vf->click_fd == fd)
575 GtkTreeSelection *selection;
577 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
578 if (vflist_row_is_selected(vf, fd))
580 gtk_tree_selection_unselect_iter(selection, &iter);
584 gtk_tree_selection_select_iter(selection, &iter);
590 if (fd && vf->click_fd == fd &&
591 !(bevent->state & GDK_SHIFT_MASK ) &&
592 !(bevent->state & GDK_CONTROL_MASK ) &&
593 vflist_row_is_selected(vf, fd))
595 GtkTreeSelection *selection;
597 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
598 gtk_tree_selection_unselect_all(selection);
599 gtk_tree_selection_select_iter(selection, &iter);
600 vflist_move_cursor(vf, &iter);
606 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
608 FileData *read_ahead_fd = nullptr;
614 cur_fd = layout_image_get_fd(vf->layout);
615 if (sel_fd == cur_fd) return; /* no change */
617 row = g_list_index(vf->list, sel_fd);
618 /** @FIXME sidecar data */
620 if (sel_fd && options->image.enable_read_ahead && row >= 0)
622 if (row > g_list_index(vf->list, cur_fd) &&
623 static_cast<guint>(row + 1) < vf_count(vf, nullptr))
625 read_ahead_fd = vf_index_get_data(vf, row + 1);
629 read_ahead_fd = vf_index_get_data(vf, row - 1);
633 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
636 static gboolean vflist_select_idle_cb(gpointer data)
638 auto vf = static_cast<ViewFile *>(data);
642 VFLIST(vf)->select_idle_id = 0;
643 return G_SOURCE_REMOVE;
648 if (VFLIST(vf)->select_fd)
650 vflist_select_image(vf, VFLIST(vf)->select_fd);
651 VFLIST(vf)->select_fd = nullptr;
654 VFLIST(vf)->select_idle_id = 0;
655 return G_SOURCE_REMOVE;
658 static void vflist_select_idle_cancel(ViewFile *vf)
660 if (VFLIST(vf)->select_idle_id)
662 g_source_remove(VFLIST(vf)->select_idle_id);
663 VFLIST(vf)->select_idle_id = 0;
667 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
669 auto vf = static_cast<ViewFile *>(data);
671 GtkTreePath *cursor_path;
673 VFLIST(vf)->select_fd = nullptr;
675 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
677 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
680 gtk_tree_model_get_iter(store, &iter, cursor_path);
681 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
682 gtk_tree_path_free(cursor_path);
687 !VFLIST(vf)->select_idle_id)
689 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
695 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
697 auto vf = static_cast<ViewFile *>(data);
698 vflist_set_expanded(vf, iter, TRUE);
701 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
703 auto vf = static_cast<ViewFile *>(data);
704 vflist_set_expanded(vf, iter, FALSE);
708 *-----------------------------------------------------------------------------
710 *-----------------------------------------------------------------------------
714 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)
716 gboolean multiline = vflist_is_multiline(vf);
723 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
727 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
732 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
737 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
745 gchar *formatted_with_stars;
747 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
749 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
750 FILE_COLUMN_NAME, &name,
751 FILE_COLUMN_SIDECARS, &sidecars,
752 FILE_COLUMN_SIZE, &size,
753 FILE_COLUMN_DATE, &time,
754 FILE_COLUMN_STAR_RATING, &star_rating,
757 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
758 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
760 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
761 FILE_COLUMN_EXPANDED, expanded,
763 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
764 FILE_COLUMN_EXPANDED, expanded,
771 g_free(formatted_with_stars);
774 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
777 gchar *sidecars = nullptr;
779 const gchar *time = text_from_time(fd->date);
780 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
781 const gchar *disabled_grouping;
783 gchar *formatted_with_stars;
784 gboolean expanded = FALSE;
787 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
789 star_rating = convert_rating_to_stars(fd->rating);
793 star_rating = nullptr;
796 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
798 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
801 sidecars = file_data_sc_list_to_string(fd);
803 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
804 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
805 size = text_from_size(fd->size);
807 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
808 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
810 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
811 FILE_COLUMN_VERSION, fd->version,
812 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
813 FILE_COLUMN_FORMATTED, formatted,
814 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
815 FILE_COLUMN_SIDECARS, sidecars,
816 FILE_COLUMN_NAME, name,
817 FILE_COLUMN_STAR_RATING, star_rating,
818 FILE_COLUMN_SIZE, size,
819 FILE_COLUMN_DATE, time,
820 #define STORE_SET_IS_SLOW 1
821 #if STORE_SET_IS_SLOW
822 /* this is 3x faster on a directory with 20000 files */
823 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
824 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
825 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
826 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
827 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
828 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
829 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
830 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
831 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
832 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
833 #if FILEDATA_MARKS_SIZE != 10
834 #error this needs to be updated
837 FILE_COLUMN_COLOR, FALSE, -1);
839 #if !STORE_SET_IS_SLOW
842 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
843 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
852 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
857 gint num_ordered = 0;
858 gint num_prepended = 0;
860 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
866 auto fd = static_cast<FileData *>(work->data);
867 gboolean done = FALSE;
871 FileData *old_fd = nullptr;
872 gint old_version = 0;
876 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
877 FILE_COLUMN_POINTER, &old_fd,
878 FILE_COLUMN_VERSION, &old_version,
888 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
890 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
892 if (match == 0) g_warning("multiple fd for the same path");
903 GtkTreeIter new_iter;
908 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
913 here should be used gtk_tree_store_append, but this function seems to be O(n)
914 and it seems to be much faster to add new entries to the beginning and reorder later
917 gtk_tree_store_prepend(store, &new_iter, parent_iter);
920 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
921 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
923 if (g_list_find(selected, fd))
925 /* renamed files - the same fd appears at different position - select it again*/
926 GtkTreeSelection *selection;
927 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
928 gtk_tree_selection_select_iter(selection, &new_iter);
935 file_data_unref(old_fd);
936 valid = gtk_tree_store_remove(store, &iter);
941 if (fd->version != old_version || force)
943 vflist_setup_iter(vf, store, &iter, fd);
944 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
947 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
958 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
959 file_data_unref(old_fd);
961 valid = gtk_tree_store_remove(store, &iter);
964 /* move the prepended entries to the correct position */
967 gint num_total = num_prepended + num_ordered;
968 std::vector<gint> new_order;
969 new_order.reserve(num_total);
971 for (gint i = 0; i < num_ordered; i++)
973 new_order.push_back(num_prepended + i);
975 for (gint i = num_ordered; i < num_total; i++)
977 new_order.push_back(num_total - 1 - i);
979 gtk_tree_store_reorder(store, parent_iter, new_order.data());
983 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
986 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
990 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
991 if (!vf->list) return;
997 auto fd = static_cast<FileData *>(work->data);
998 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1003 vf->sort_method = type;
1004 vf->sort_ascend = ascend;
1005 vf->sort_case = case_sensitive;
1007 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1009 std::vector<gint> new_order;
1010 new_order.reserve(i);
1015 auto fd = static_cast<FileData *>(work->data);
1016 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1020 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1021 gtk_tree_store_reorder(store, nullptr, new_order.data());
1023 g_hash_table_destroy(fd_idx_hash);
1027 *-----------------------------------------------------------------------------
1029 *-----------------------------------------------------------------------------
1033 void vflist_thumb_progress_count(const GList *list, gint &count, gint &done)
1035 for (const GList *work = list; work; work = work->next)
1037 auto fd = static_cast<FileData *>(work->data);
1039 if (fd->thumb_pixbuf) done++;
1041 if (fd->sidecar_files)
1043 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1049 void vflist_read_metadata_progress_count(const GList *list, gint &count, gint &done)
1051 for (const GList *work = list; work; work = work->next)
1053 auto fd = static_cast<FileData *>(work->data);
1055 if (fd->metadata_in_idle_loaded) done++;
1057 if (fd->sidecar_files)
1059 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1065 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1067 GtkTreeStore *store;
1070 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1072 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1073 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1076 FileData *vflist_thumb_next_fd(ViewFile *vf)
1079 FileData *fd = nullptr;
1081 /* first check the visible files */
1083 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1085 GtkTreeModel *store;
1087 gboolean valid = TRUE;
1089 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1090 gtk_tree_model_get_iter(store, &iter, tpath);
1091 gtk_tree_path_free(tpath);
1094 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1098 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1100 if (!nfd->thumb_pixbuf) fd = nfd;
1102 valid = gtk_tree_model_iter_next(store, &iter);
1106 /* then find first undone */
1110 GList *work = vf->list;
1113 auto fd_p = static_cast<FileData *>(work->data);
1114 if (!fd_p->thumb_pixbuf)
1118 GList *work2 = fd_p->sidecar_files;
1120 while (work2 && !fd)
1122 fd_p = static_cast<FileData *>(work2->data);
1123 if (!fd_p->thumb_pixbuf) fd = fd_p;
1124 work2 = work2->next;
1134 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1136 GtkTreeStore *store;
1143 gchar *formatted_with_stars;
1146 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1148 star_rating = metadata_read_rating_stars(fd);
1150 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1151 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1153 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1154 FILE_COLUMN_NAME, &name,
1155 FILE_COLUMN_SIDECARS, &sidecars,
1156 FILE_COLUMN_SIZE, &size,
1157 FILE_COLUMN_DATE, &time,
1158 FILE_COLUMN_EXPANDED, &expanded,
1161 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1163 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1164 FILE_COLUMN_EXPANDED, expanded,
1167 g_free(star_rating);
1168 g_free(formatted_with_stars);
1171 FileData *vflist_star_next_fd(ViewFile *vf)
1174 FileData *fd = nullptr;
1175 FileData *nfd = nullptr;
1177 /* first check the visible files */
1179 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1181 GtkTreeModel *store;
1183 gboolean valid = TRUE;
1185 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1186 gtk_tree_model_get_iter(store, &iter, tpath);
1187 gtk_tree_path_free(tpath);
1190 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1192 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1194 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1199 valid = gtk_tree_model_iter_next(store, &iter);
1204 vf->stars_filedata = fd;
1206 if (vf->stars_id == 0)
1208 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1213 /* then find first undone */
1217 GList *work = vf->list;
1221 auto fd_p = static_cast<FileData *>(work->data);
1223 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1237 vf->stars_filedata = fd;
1239 if (vf->stars_id == 0)
1241 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1250 *-----------------------------------------------------------------------------
1252 *-----------------------------------------------------------------------------
1255 gint vflist_index_by_fd(const ViewFile *vf, const FileData *fd)
1259 for (const GList *work = vf->list; work; work = work->next)
1261 auto list_fd = static_cast<FileData *>(work->data);
1262 if (list_fd == fd) return p;
1264 /** @FIXME return the same index also for sidecars
1265 it is sufficient for next/prev navigation but it should be rewritten
1266 without using indexes at all
1268 if (g_list_find(list_fd->sidecar_files, fd)) return p;
1277 *-----------------------------------------------------------------------------
1279 *-----------------------------------------------------------------------------
1282 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1284 GtkTreeModel *store;
1285 GtkTreeSelection *selection;
1288 gboolean found = FALSE;
1290 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1291 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1293 while (!found && work)
1295 auto tpath = static_cast<GtkTreePath *>(work->data);
1299 gtk_tree_model_get_iter(store, &iter, tpath);
1300 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1301 if (fd_n == fd) found = TRUE;
1304 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1309 #pragma GCC diagnostic push
1310 #pragma GCC diagnostic ignored "-Wunused-function"
1311 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1315 fd = vf_index_get_data(vf, row);
1316 return vflist_row_is_selected(vf, fd);
1318 #pragma GCC diagnostic pop
1320 gboolean vflist_is_selected(ViewFile *vf, FileData *fd)
1322 return vflist_row_is_selected(vf, fd);
1325 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1327 GtkTreeModel *store;
1328 GtkTreeSelection *selection;
1332 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1333 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1343 auto tpath = static_cast<GtkTreePath *>(work->data);
1347 gtk_tree_model_get_iter(store, &iter, tpath);
1348 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1357 count = g_list_length(slist);
1358 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1363 GList *vflist_selection_get_list(ViewFile *vf)
1365 GtkTreeModel *store;
1366 GtkTreeSelection *selection;
1368 GList *list = nullptr;
1370 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1371 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1372 for (GList *work = g_list_last(slist); work; work = work->prev)
1374 auto tpath = static_cast<GtkTreePath *>(work->data);
1378 gtk_tree_model_get_iter(store, &iter, tpath);
1379 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1381 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1383 /* unexpanded - add whole group */
1384 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1387 list = g_list_prepend(list, file_data_ref(fd));
1389 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1394 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1396 GtkTreeModel *store;
1397 GtkTreeSelection *selection;
1399 GList *list = nullptr;
1402 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1403 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1407 auto tpath = static_cast<GtkTreePath *>(work->data);
1411 gtk_tree_model_get_iter(store, &iter, tpath);
1412 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1414 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1418 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1420 return g_list_reverse(list);
1423 void vflist_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
1425 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1426 GtkTreeModel *store;
1430 for (GList *work = gtk_tree_selection_get_selected_rows(selection, &store); work; work = work->next)
1432 auto *tpath = static_cast<GtkTreePath *>(work->data);
1434 gtk_tree_model_get_iter(store, &iter, tpath);
1435 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1441 void vflist_select_all(ViewFile *vf)
1443 GtkTreeSelection *selection;
1445 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1446 gtk_tree_selection_select_all(selection);
1448 VFLIST(vf)->select_fd = nullptr;
1451 void vflist_select_none(ViewFile *vf)
1453 GtkTreeSelection *selection;
1455 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1456 gtk_tree_selection_unselect_all(selection);
1459 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1464 tpath = gtk_tree_model_get_path(store, iter);
1465 result = gtk_tree_path_prev(tpath);
1467 gtk_tree_model_get_iter(store, iter, tpath);
1469 gtk_tree_path_free(tpath);
1474 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1476 if (!gtk_tree_model_get_iter_first(store, iter))
1481 GtkTreeIter next = *iter;
1483 if (gtk_tree_model_iter_next(store, &next))
1492 void vflist_select_invert(ViewFile *vf)
1495 GtkTreeSelection *selection;
1496 GtkTreeModel *store;
1499 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1500 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1502 /* Backward iteration prevents scrolling to the end of the list,
1503 * it scrolls to the first selected row instead. */
1504 valid = tree_model_get_iter_last(store, &iter);
1508 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1511 gtk_tree_selection_unselect_iter(selection, &iter);
1513 gtk_tree_selection_select_iter(selection, &iter);
1515 valid = tree_model_iter_prev(store, &iter);
1519 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1523 if (vflist_find_row(vf, fd, &iter) < 0) return;
1525 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1527 if (!vflist_row_is_selected(vf, fd))
1529 GtkTreeSelection *selection;
1530 GtkTreeModel *store;
1533 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1534 gtk_tree_selection_unselect_all(selection);
1535 gtk_tree_selection_select_iter(selection, &iter);
1536 vflist_move_cursor(vf, &iter);
1538 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1539 tpath = gtk_tree_model_get_path(store, &iter);
1540 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1541 gtk_tree_path_free(tpath);
1545 void vflist_select_list(ViewFile *vf, GList *list)
1556 fd = static_cast<FileData *>(work->data);
1558 if (vflist_find_row(vf, fd, &iter) < 0) return;
1559 if (!vflist_row_is_selected(vf, fd))
1561 GtkTreeSelection *selection;
1563 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1564 gtk_tree_selection_select_iter(selection, &iter);
1570 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1573 FileData *fd = nullptr;
1575 if (sel_fd->parent) sel_fd = sel_fd->parent;
1581 fd = static_cast<FileData *>(work->data);
1584 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1586 if (match >= 0) break;
1589 if (fd) vflist_select_by_fd(vf, fd);
1593 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1595 GtkTreeModel *store;
1597 GtkTreeSelection *selection;
1600 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1602 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1603 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1605 valid = gtk_tree_model_get_iter_first(store, &iter);
1610 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1612 selected = file_data_mark_to_selection(fd, mark, mode, gtk_tree_selection_iter_is_selected(selection, &iter));
1615 gtk_tree_selection_select_iter(selection, &iter);
1617 gtk_tree_selection_unselect_iter(selection, &iter);
1619 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1623 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1625 GtkTreeModel *store;
1626 GtkTreeSelection *selection;
1629 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1631 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1632 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1633 for (GList *work = slist; work; work = work->next)
1635 auto tpath = static_cast<GtkTreePath *>(work->data);
1639 gtk_tree_model_get_iter(store, &iter, tpath);
1640 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1642 /* the change has a very limited range and the standard notification would trigger
1643 complete re-read of the directory - try to do only minimal update instead */
1644 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1646 file_data_selection_to_mark(fd, mark, mode);
1648 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1650 vf_refresh_idle(vf);
1654 /* mark functions can have various side effects - update all columns to be sure */
1655 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1656 /* mark functions can change sidecars too */
1657 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1660 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1662 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1666 *-----------------------------------------------------------------------------
1668 *-----------------------------------------------------------------------------
1671 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1673 GtkTreeViewColumn *column;
1674 GtkCellRenderer *cell;
1677 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1678 if (!column) return;
1680 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1682 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1684 cell = static_cast<GtkCellRenderer *>(list->data);
1687 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1688 gtk_tree_view_column_set_visible(column, thumb);
1690 if (options->show_star_rating)
1692 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1693 if (!column) return;
1694 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1695 gtk_tree_view_column_set_visible(column, TRUE);
1697 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1698 if (!column) return;
1699 gtk_tree_view_column_set_visible(column, FALSE);
1703 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1704 if (!column) return;
1705 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1706 gtk_tree_view_column_set_visible(column, TRUE);
1708 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1709 if (!column) return;
1710 gtk_tree_view_column_set_visible(column, FALSE);
1713 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1714 if (!column) return;
1715 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1717 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1718 if (!column) return;
1719 gtk_tree_view_column_set_visible(column, !multiline);
1721 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1722 if (!column) return;
1723 gtk_tree_view_column_set_visible(column, !multiline);
1726 static gboolean vflist_is_multiline(ViewFile *vf)
1728 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1732 static void vflist_populate_view(ViewFile *vf, gboolean force)
1734 GtkTreeStore *store;
1737 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1744 vflist_store_clear(vf, FALSE);
1749 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1751 selected = vflist_selection_get_list(vf);
1753 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1755 if (selected && vflist_selection_count(vf, nullptr) == 0)
1757 /* all selected files disappeared */
1758 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1761 filelist_free(selected);
1764 vf_thumb_update(vf);
1768 gboolean vflist_refresh(ViewFile *vf)
1771 gboolean ret = TRUE;
1773 old_list = vf->list;
1776 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1779 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1781 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1783 if (vf->marks_enabled)
1785 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1786 // each time a mark is changed.
1787 file_data_lock_list(vf->list);
1791 /** @FIXME only do this when needed (aka when we just switched from */
1792 /** @FIXME marks-enabled to marks-disabled) */
1793 file_data_unlock_list(vf->list);
1796 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1797 vf->list = g_list_first(vf->list);
1798 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1800 vf->list = g_list_first(vf->list);
1801 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1803 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1805 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1806 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1809 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1811 vflist_populate_view(vf, FALSE);
1813 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1815 filelist_free(old_list);
1816 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1822 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1824 static GdkRGBA color;
1825 static GtkWidget *done = nullptr;
1831 style = gtk_widget_get_style(widget);
1832 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1834 shift_color(&color, -1, 0);
1841 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1842 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1844 auto vf = static_cast<ViewFile *>(data);
1847 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1848 g_object_set(G_OBJECT(cell),
1849 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1850 "cell-background-set", set, NULL);
1853 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1855 GtkTreeViewColumn *column;
1856 GtkCellRenderer *renderer;
1858 column = gtk_tree_view_column_new();
1859 gtk_tree_view_column_set_title(column, title);
1860 gtk_tree_view_column_set_min_width(column, 4);
1864 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1865 renderer = gtk_cell_renderer_text_new();
1868 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1870 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1871 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1873 gtk_tree_view_column_set_expand(column, TRUE);
1877 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1878 renderer = gtk_cell_renderer_pixbuf_new();
1879 cell_renderer_height_override(renderer);
1880 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1881 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1884 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
1885 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1886 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1888 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1891 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1893 auto vf = static_cast<ViewFile *>(data);
1894 GtkTreeStore *store;
1895 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1901 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1902 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1905 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1907 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1909 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1912 /* the change has a very limited range and the standard notification would trigger
1913 complete re-read of the directory - try to do only minimal update instead */
1914 file_data_unregister_notify_func(vf_notify_cb, vf);
1915 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1916 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1918 vf_refresh_idle(vf);
1922 /* mark functions can have various side effects - update all columns to be sure */
1923 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1924 /* mark functions can change sidecars too */
1925 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1927 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1929 gtk_tree_path_free(path);
1932 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1934 GtkTreeViewColumn *column;
1935 GtkCellRenderer *renderer;
1937 renderer = gtk_cell_renderer_toggle_new();
1938 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1940 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1941 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1942 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1944 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1945 gtk_tree_view_column_set_fixed_width(column, 22);
1946 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1949 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1953 *-----------------------------------------------------------------------------
1955 *-----------------------------------------------------------------------------
1958 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1961 if (!dir_fd) return FALSE;
1962 if (vf->dir_fd == dir_fd) return TRUE;
1964 file_data_unref(vf->dir_fd);
1965 vf->dir_fd = file_data_ref(dir_fd);
1967 /* force complete reload */
1968 vflist_store_clear(vf, TRUE);
1970 filelist_free(vf->list);
1973 ret = vflist_refresh(vf);
1974 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1978 void vflist_destroy_cb(ViewFile *vf)
1980 file_data_unregister_notify_func(vf_notify_cb, vf);
1982 vflist_select_idle_cancel(vf);
1983 vf_refresh_idle_cancel(vf);
1987 filelist_free(vf->list);
1990 ViewFile *vflist_new(ViewFile *vf)
1992 GtkTreeStore *store;
1993 GtkTreeSelection *selection;
1994 GType flist_types[FILE_COLUMN_COUNT];
1998 vf->info = g_new0(ViewFileInfoList, 1);
2000 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2001 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2002 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2003 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2004 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2005 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2006 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2007 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2008 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2009 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2010 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2011 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2012 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2013 flist_types[i] = G_TYPE_BOOLEAN;
2015 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2017 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2018 g_object_unref(store);
2020 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2021 G_CALLBACK(vflist_expand_cb), vf);
2023 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2024 G_CALLBACK(vflist_collapse_cb), vf);
2026 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2027 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2028 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2030 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2031 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2033 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2037 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2039 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2040 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2044 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2045 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2048 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2049 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2052 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2053 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2056 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2057 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2060 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2061 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2064 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2065 g_assert(column == FILE_VIEW_COLUMN_DATE);
2068 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2072 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2074 if (VFLIST(vf)->thumbs_enabled == enable) return;
2076 VFLIST(vf)->thumbs_enabled = enable;
2078 /* vflist_populate_view is better than vflist_refresh:
2079 - no need to re-read the directory
2080 - force update because the formatted string has changed
2084 vflist_populate_view(vf, TRUE);
2085 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2089 void vflist_marks_set(ViewFile *vf, gboolean enable)
2094 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2099 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2100 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2103 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2104 gtk_tree_view_column_set_visible(column, enable);
2109 // Previously disabled, which means that vf->list is complete
2110 file_data_lock_list(vf->list);
2114 // Previously enabled, which means that vf->list is incomplete
2117 g_list_free(columns);
2120 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */