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 *-----------------------------------------------------------------------------
713 static gchar* vflist_get_formatted(ViewFile *vf, const gchar *name, const gchar *sidecars, const gchar *size, const gchar *time, gboolean expanded, const gchar *star_rating)
715 gboolean multiline = vflist_is_multiline(vf);
716 GString *text = g_string_new(nullptr);
718 g_string_printf(text, "%s %s", name, expanded ? "" : sidecars);
722 g_string_append_printf(text, "\n%s\n%s", size, time);
726 g_string_append_printf(text, "\n%s", star_rating);
730 return g_string_free(text, FALSE);
733 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
741 gchar *formatted_with_stars;
743 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
745 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
746 FILE_COLUMN_NAME, &name,
747 FILE_COLUMN_SIDECARS, &sidecars,
748 FILE_COLUMN_SIZE, &size,
749 FILE_COLUMN_DATE, &time,
750 FILE_COLUMN_STAR_RATING, &star_rating,
753 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, nullptr);
754 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, star_rating);
756 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
757 FILE_COLUMN_EXPANDED, expanded,
759 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
760 FILE_COLUMN_EXPANDED, expanded,
767 g_free(formatted_with_stars);
770 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
773 gchar *sidecars = nullptr;
775 const gchar *time = text_from_time(fd->date);
776 const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
777 const gchar *disabled_grouping;
779 gchar *formatted_with_stars;
780 gboolean expanded = FALSE;
783 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
785 star_rating = convert_rating_to_stars(fd->rating);
789 star_rating = nullptr;
792 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
794 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
797 sidecars = file_data_sc_list_to_string(fd);
799 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
800 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
801 size = text_from_size(fd->size);
803 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, nullptr);
804 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, star_rating);
806 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
807 FILE_COLUMN_VERSION, fd->version,
808 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
809 FILE_COLUMN_FORMATTED, formatted,
810 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
811 FILE_COLUMN_SIDECARS, sidecars,
812 FILE_COLUMN_NAME, name,
813 FILE_COLUMN_STAR_RATING, star_rating,
814 FILE_COLUMN_SIZE, size,
815 FILE_COLUMN_DATE, time,
816 #define STORE_SET_IS_SLOW 1
817 #if STORE_SET_IS_SLOW
818 /* this is 3x faster on a directory with 20000 files */
819 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
820 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
821 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
822 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
823 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
824 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
825 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
826 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
827 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
828 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
829 #if FILEDATA_MARKS_SIZE != 10
830 #error this needs to be updated
833 FILE_COLUMN_COLOR, FALSE, -1);
835 #if !STORE_SET_IS_SLOW
838 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
839 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
848 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
853 gint num_ordered = 0;
854 gint num_prepended = 0;
856 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
862 auto fd = static_cast<FileData *>(work->data);
863 gboolean done = FALSE;
867 FileData *old_fd = nullptr;
868 gint old_version = 0;
872 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
873 FILE_COLUMN_POINTER, &old_fd,
874 FILE_COLUMN_VERSION, &old_version,
884 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
886 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
888 if (match == 0) g_warning("multiple fd for the same path");
899 GtkTreeIter new_iter;
904 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
909 here should be used gtk_tree_store_append, but this function seems to be O(n)
910 and it seems to be much faster to add new entries to the beginning and reorder later
913 gtk_tree_store_prepend(store, &new_iter, parent_iter);
916 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
917 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
919 if (g_list_find(selected, fd))
921 /* renamed files - the same fd appears at different position - select it again*/
922 GtkTreeSelection *selection;
923 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
924 gtk_tree_selection_select_iter(selection, &new_iter);
931 file_data_unref(old_fd);
932 valid = gtk_tree_store_remove(store, &iter);
937 if (fd->version != old_version || force)
939 vflist_setup_iter(vf, store, &iter, fd);
940 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
943 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
954 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
955 file_data_unref(old_fd);
957 valid = gtk_tree_store_remove(store, &iter);
960 /* move the prepended entries to the correct position */
963 gint num_total = num_prepended + num_ordered;
964 std::vector<gint> new_order;
965 new_order.reserve(num_total);
967 for (gint i = 0; i < num_ordered; i++)
969 new_order.push_back(num_prepended + i);
971 for (gint i = num_ordered; i < num_total; i++)
973 new_order.push_back(num_total - 1 - i);
975 gtk_tree_store_reorder(store, parent_iter, new_order.data());
979 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
982 GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
986 if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
987 if (!vf->list) return;
993 auto fd = static_cast<FileData *>(work->data);
994 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
999 vf->sort_method = type;
1000 vf->sort_ascend = ascend;
1001 vf->sort_case = case_sensitive;
1003 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1005 std::vector<gint> new_order;
1006 new_order.reserve(i);
1011 auto fd = static_cast<FileData *>(work->data);
1012 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1016 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1017 gtk_tree_store_reorder(store, nullptr, new_order.data());
1019 g_hash_table_destroy(fd_idx_hash);
1023 *-----------------------------------------------------------------------------
1025 *-----------------------------------------------------------------------------
1029 void vflist_thumb_progress_count(const GList *list, gint &count, gint &done)
1031 for (const GList *work = list; work; work = work->next)
1033 auto fd = static_cast<FileData *>(work->data);
1035 if (fd->thumb_pixbuf) done++;
1037 if (fd->sidecar_files)
1039 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1045 void vflist_read_metadata_progress_count(const GList *list, gint &count, gint &done)
1047 for (const GList *work = list; work; work = work->next)
1049 auto fd = static_cast<FileData *>(work->data);
1051 if (fd->metadata_in_idle_loaded) done++;
1053 if (fd->sidecar_files)
1055 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1061 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1063 GtkTreeStore *store;
1066 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1068 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1069 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1072 FileData *vflist_thumb_next_fd(ViewFile *vf)
1075 FileData *fd = nullptr;
1077 /* first check the visible files */
1079 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1081 GtkTreeModel *store;
1083 gboolean valid = TRUE;
1085 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1086 gtk_tree_model_get_iter(store, &iter, tpath);
1087 gtk_tree_path_free(tpath);
1090 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1094 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1096 if (!nfd->thumb_pixbuf) fd = nfd;
1098 valid = gtk_tree_model_iter_next(store, &iter);
1102 /* then find first undone */
1106 GList *work = vf->list;
1109 auto fd_p = static_cast<FileData *>(work->data);
1110 if (!fd_p->thumb_pixbuf)
1114 GList *work2 = fd_p->sidecar_files;
1116 while (work2 && !fd)
1118 fd_p = static_cast<FileData *>(work2->data);
1119 if (!fd_p->thumb_pixbuf) fd = fd_p;
1120 work2 = work2->next;
1130 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1132 GtkTreeStore *store;
1139 gchar *formatted_with_stars;
1142 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1144 star_rating = metadata_read_rating_stars(fd);
1146 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1147 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1149 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1150 FILE_COLUMN_NAME, &name,
1151 FILE_COLUMN_SIDECARS, &sidecars,
1152 FILE_COLUMN_SIZE, &size,
1153 FILE_COLUMN_DATE, &time,
1154 FILE_COLUMN_EXPANDED, &expanded,
1157 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, star_rating);
1159 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1160 FILE_COLUMN_EXPANDED, expanded,
1163 g_free(star_rating);
1164 g_free(formatted_with_stars);
1167 FileData *vflist_star_next_fd(ViewFile *vf)
1170 FileData *fd = nullptr;
1171 FileData *nfd = nullptr;
1173 /* first check the visible files */
1175 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1177 GtkTreeModel *store;
1179 gboolean valid = TRUE;
1181 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1182 gtk_tree_model_get_iter(store, &iter, tpath);
1183 gtk_tree_path_free(tpath);
1186 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1188 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1190 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1195 valid = gtk_tree_model_iter_next(store, &iter);
1200 vf->stars_filedata = fd;
1202 if (vf->stars_id == 0)
1204 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1209 /* then find first undone */
1213 GList *work = vf->list;
1217 auto fd_p = static_cast<FileData *>(work->data);
1219 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1233 vf->stars_filedata = fd;
1235 if (vf->stars_id == 0)
1237 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1246 *-----------------------------------------------------------------------------
1248 *-----------------------------------------------------------------------------
1251 gint vflist_index_by_fd(const ViewFile *vf, const FileData *fd)
1255 for (const GList *work = vf->list; work; work = work->next)
1257 auto list_fd = static_cast<FileData *>(work->data);
1258 if (list_fd == fd) return p;
1260 /** @FIXME return the same index also for sidecars
1261 it is sufficient for next/prev navigation but it should be rewritten
1262 without using indexes at all
1264 if (g_list_find(list_fd->sidecar_files, fd)) return p;
1273 *-----------------------------------------------------------------------------
1275 *-----------------------------------------------------------------------------
1278 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1280 GtkTreeModel *store;
1281 GtkTreeSelection *selection;
1284 gboolean found = FALSE;
1286 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1287 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1289 while (!found && work)
1291 auto tpath = static_cast<GtkTreePath *>(work->data);
1295 gtk_tree_model_get_iter(store, &iter, tpath);
1296 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1297 if (fd_n == fd) found = TRUE;
1300 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1305 #pragma GCC diagnostic push
1306 #pragma GCC diagnostic ignored "-Wunused-function"
1307 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1311 fd = vf_index_get_data(vf, row);
1312 return vflist_row_is_selected(vf, fd);
1314 #pragma GCC diagnostic pop
1316 gboolean vflist_is_selected(ViewFile *vf, FileData *fd)
1318 return vflist_row_is_selected(vf, fd);
1321 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1323 GtkTreeModel *store;
1324 GtkTreeSelection *selection;
1328 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1329 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1339 auto tpath = static_cast<GtkTreePath *>(work->data);
1343 gtk_tree_model_get_iter(store, &iter, tpath);
1344 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1353 count = g_list_length(slist);
1354 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1359 GList *vflist_selection_get_list(ViewFile *vf)
1361 GtkTreeModel *store;
1362 GtkTreeSelection *selection;
1364 GList *list = nullptr;
1366 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1367 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1368 for (GList *work = g_list_last(slist); work; work = work->prev)
1370 auto tpath = static_cast<GtkTreePath *>(work->data);
1374 gtk_tree_model_get_iter(store, &iter, tpath);
1375 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1377 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1379 /* unexpanded - add whole group */
1380 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1383 list = g_list_prepend(list, file_data_ref(fd));
1385 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1390 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1392 GtkTreeModel *store;
1393 GtkTreeSelection *selection;
1395 GList *list = nullptr;
1398 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1399 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1403 auto tpath = static_cast<GtkTreePath *>(work->data);
1407 gtk_tree_model_get_iter(store, &iter, tpath);
1408 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1410 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1414 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1416 return g_list_reverse(list);
1419 void vflist_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
1421 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1422 GtkTreeModel *store;
1426 for (GList *work = gtk_tree_selection_get_selected_rows(selection, &store); work; work = work->next)
1428 auto *tpath = static_cast<GtkTreePath *>(work->data);
1430 gtk_tree_model_get_iter(store, &iter, tpath);
1431 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1437 void vflist_select_all(ViewFile *vf)
1439 GtkTreeSelection *selection;
1441 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1442 gtk_tree_selection_select_all(selection);
1444 VFLIST(vf)->select_fd = nullptr;
1447 void vflist_select_none(ViewFile *vf)
1449 GtkTreeSelection *selection;
1451 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1452 gtk_tree_selection_unselect_all(selection);
1455 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1460 tpath = gtk_tree_model_get_path(store, iter);
1461 result = gtk_tree_path_prev(tpath);
1463 gtk_tree_model_get_iter(store, iter, tpath);
1465 gtk_tree_path_free(tpath);
1470 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1472 if (!gtk_tree_model_get_iter_first(store, iter))
1477 GtkTreeIter next = *iter;
1479 if (gtk_tree_model_iter_next(store, &next))
1488 void vflist_select_invert(ViewFile *vf)
1491 GtkTreeSelection *selection;
1492 GtkTreeModel *store;
1495 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1496 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1498 /* Backward iteration prevents scrolling to the end of the list,
1499 * it scrolls to the first selected row instead. */
1500 valid = tree_model_get_iter_last(store, &iter);
1504 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1507 gtk_tree_selection_unselect_iter(selection, &iter);
1509 gtk_tree_selection_select_iter(selection, &iter);
1511 valid = tree_model_iter_prev(store, &iter);
1515 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1519 if (vflist_find_row(vf, fd, &iter) < 0) return;
1521 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1523 if (!vflist_row_is_selected(vf, fd))
1525 GtkTreeSelection *selection;
1526 GtkTreeModel *store;
1529 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1530 gtk_tree_selection_unselect_all(selection);
1531 gtk_tree_selection_select_iter(selection, &iter);
1532 vflist_move_cursor(vf, &iter);
1534 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1535 tpath = gtk_tree_model_get_path(store, &iter);
1536 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1537 gtk_tree_path_free(tpath);
1541 void vflist_select_list(ViewFile *vf, GList *list)
1552 fd = static_cast<FileData *>(work->data);
1554 if (vflist_find_row(vf, fd, &iter) < 0) return;
1555 if (!vflist_row_is_selected(vf, fd))
1557 GtkTreeSelection *selection;
1559 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1560 gtk_tree_selection_select_iter(selection, &iter);
1566 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1569 FileData *fd = nullptr;
1571 if (sel_fd->parent) sel_fd = sel_fd->parent;
1577 fd = static_cast<FileData *>(work->data);
1580 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1582 if (match >= 0) break;
1585 if (fd) vflist_select_by_fd(vf, fd);
1589 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1591 GtkTreeModel *store;
1593 GtkTreeSelection *selection;
1596 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1598 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1599 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1601 valid = gtk_tree_model_get_iter_first(store, &iter);
1606 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1608 selected = file_data_mark_to_selection(fd, mark, mode, gtk_tree_selection_iter_is_selected(selection, &iter));
1611 gtk_tree_selection_select_iter(selection, &iter);
1613 gtk_tree_selection_unselect_iter(selection, &iter);
1615 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1619 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1621 GtkTreeModel *store;
1622 GtkTreeSelection *selection;
1625 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1627 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1628 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1629 for (GList *work = slist; work; work = work->next)
1631 auto tpath = static_cast<GtkTreePath *>(work->data);
1635 gtk_tree_model_get_iter(store, &iter, tpath);
1636 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1638 /* the change has a very limited range and the standard notification would trigger
1639 complete re-read of the directory - try to do only minimal update instead */
1640 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1642 file_data_selection_to_mark(fd, mark, mode);
1644 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1646 vf_refresh_idle(vf);
1650 /* mark functions can have various side effects - update all columns to be sure */
1651 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1652 /* mark functions can change sidecars too */
1653 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1656 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1658 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1662 *-----------------------------------------------------------------------------
1664 *-----------------------------------------------------------------------------
1667 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1669 GtkTreeViewColumn *column;
1670 GtkCellRenderer *cell;
1673 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1674 if (!column) return;
1676 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1678 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1680 cell = static_cast<GtkCellRenderer *>(list->data);
1683 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1684 gtk_tree_view_column_set_visible(column, thumb);
1686 if (options->show_star_rating)
1688 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1689 if (!column) return;
1690 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1691 gtk_tree_view_column_set_visible(column, TRUE);
1693 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1694 if (!column) return;
1695 gtk_tree_view_column_set_visible(column, FALSE);
1699 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1700 if (!column) return;
1701 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1702 gtk_tree_view_column_set_visible(column, TRUE);
1704 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1705 if (!column) return;
1706 gtk_tree_view_column_set_visible(column, FALSE);
1709 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1710 if (!column) return;
1711 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1713 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1714 if (!column) return;
1715 gtk_tree_view_column_set_visible(column, !multiline);
1717 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1718 if (!column) return;
1719 gtk_tree_view_column_set_visible(column, !multiline);
1722 static gboolean vflist_is_multiline(ViewFile *vf)
1724 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1728 static void vflist_populate_view(ViewFile *vf, gboolean force)
1730 GtkTreeStore *store;
1733 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1740 vflist_store_clear(vf, FALSE);
1745 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1747 selected = vflist_selection_get_list(vf);
1749 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1751 if (selected && vflist_selection_count(vf, nullptr) == 0)
1753 /* all selected files disappeared */
1754 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1757 filelist_free(selected);
1760 vf_thumb_update(vf);
1764 gboolean vflist_refresh(ViewFile *vf)
1767 gboolean ret = TRUE;
1769 old_list = vf->list;
1772 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1775 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1777 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1779 if (vf->marks_enabled)
1781 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1782 // each time a mark is changed.
1783 file_data_lock_list(vf->list);
1787 /** @FIXME only do this when needed (aka when we just switched from */
1788 /** @FIXME marks-enabled to marks-disabled) */
1789 file_data_unlock_list(vf->list);
1792 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1793 vf->list = g_list_first(vf->list);
1794 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1796 vf->list = g_list_first(vf->list);
1797 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1799 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1801 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1802 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1805 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1807 vflist_populate_view(vf, FALSE);
1809 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1811 filelist_free(old_list);
1812 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1818 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1820 static GdkRGBA color;
1821 static GtkWidget *done = nullptr;
1827 style = gtk_widget_get_style(widget);
1828 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1830 shift_color(&color, -1, 0);
1837 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1838 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1840 auto vf = static_cast<ViewFile *>(data);
1843 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1844 g_object_set(G_OBJECT(cell),
1845 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1846 "cell-background-set", set, NULL);
1849 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1851 GtkTreeViewColumn *column;
1852 GtkCellRenderer *renderer;
1854 column = gtk_tree_view_column_new();
1855 gtk_tree_view_column_set_title(column, title);
1856 gtk_tree_view_column_set_min_width(column, 4);
1860 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1861 renderer = gtk_cell_renderer_text_new();
1864 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1866 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1867 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1869 gtk_tree_view_column_set_expand(column, TRUE);
1873 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1874 renderer = gtk_cell_renderer_pixbuf_new();
1875 cell_renderer_height_override(renderer);
1876 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1877 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1880 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
1881 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1882 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1884 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1887 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1889 auto vf = static_cast<ViewFile *>(data);
1890 GtkTreeStore *store;
1891 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1897 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1898 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1901 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1903 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1905 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1908 /* the change has a very limited range and the standard notification would trigger
1909 complete re-read of the directory - try to do only minimal update instead */
1910 file_data_unregister_notify_func(vf_notify_cb, vf);
1911 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1912 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1914 vf_refresh_idle(vf);
1918 /* mark functions can have various side effects - update all columns to be sure */
1919 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1920 /* mark functions can change sidecars too */
1921 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1923 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1925 gtk_tree_path_free(path);
1928 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1930 GtkTreeViewColumn *column;
1931 GtkCellRenderer *renderer;
1933 renderer = gtk_cell_renderer_toggle_new();
1934 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1936 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1937 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1938 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1940 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1941 gtk_tree_view_column_set_fixed_width(column, 22);
1942 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1945 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1949 *-----------------------------------------------------------------------------
1951 *-----------------------------------------------------------------------------
1954 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1957 if (!dir_fd) return FALSE;
1958 if (vf->dir_fd == dir_fd) return TRUE;
1960 file_data_unref(vf->dir_fd);
1961 vf->dir_fd = file_data_ref(dir_fd);
1963 /* force complete reload */
1964 vflist_store_clear(vf, TRUE);
1966 filelist_free(vf->list);
1969 ret = vflist_refresh(vf);
1970 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1974 void vflist_destroy_cb(ViewFile *vf)
1976 file_data_unregister_notify_func(vf_notify_cb, vf);
1978 vflist_select_idle_cancel(vf);
1979 vf_refresh_idle_cancel(vf);
1983 filelist_free(vf->list);
1986 ViewFile *vflist_new(ViewFile *vf)
1988 GtkTreeStore *store;
1989 GtkTreeSelection *selection;
1990 GType flist_types[FILE_COLUMN_COUNT];
1994 vf->info = g_new0(ViewFileInfoList, 1);
1996 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
1997 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
1998 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
1999 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2000 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2001 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2002 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2003 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2004 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2005 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2006 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2007 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2008 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2009 flist_types[i] = G_TYPE_BOOLEAN;
2011 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2013 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2014 g_object_unref(store);
2016 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2017 G_CALLBACK(vflist_expand_cb), vf);
2019 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2020 G_CALLBACK(vflist_collapse_cb), vf);
2022 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2023 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2024 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2026 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2027 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2029 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2033 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2035 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2036 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2040 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2041 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2044 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2045 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2048 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2049 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2052 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2053 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2056 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2057 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2060 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2061 g_assert(column == FILE_VIEW_COLUMN_DATE);
2064 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2068 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2070 if (VFLIST(vf)->thumbs_enabled == enable) return;
2072 VFLIST(vf)->thumbs_enabled = enable;
2074 /* vflist_populate_view is better than vflist_refresh:
2075 - no need to re-read the directory
2076 - force update because the formatted string has changed
2080 vflist_populate_view(vf, TRUE);
2081 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2085 void vflist_marks_set(ViewFile *vf, gboolean enable)
2090 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2095 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2096 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2099 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2100 gtk_tree_view_column_set_visible(column, enable);
2105 // Previously disabled, which means that vf->list is complete
2106 file_data_lock_list(vf->list);
2110 // Previously enabled, which means that vf->list is incomplete
2113 g_list_free(columns);
2116 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */