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 = vf_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;
1601 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1603 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1604 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1606 valid = gtk_tree_model_get_iter_first(store, &iter);
1612 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1614 mark_val = file_data_get_mark(fd, n);
1615 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1619 case MTS_MODE_SET: selected = mark_val;
1621 case MTS_MODE_OR: selected = mark_val || selected;
1623 case MTS_MODE_AND: selected = mark_val && selected;
1625 case MTS_MODE_MINUS: selected = !mark_val && selected;
1630 gtk_tree_selection_select_iter(selection, &iter);
1632 gtk_tree_selection_unselect_iter(selection, &iter);
1634 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1638 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1640 GtkTreeModel *store;
1641 GtkTreeSelection *selection;
1646 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1648 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1649 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1653 auto tpath = static_cast<GtkTreePath *>(work->data);
1657 gtk_tree_model_get_iter(store, &iter, tpath);
1658 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1660 /* the change has a very limited range and the standard notification would trigger
1661 complete re-read of the directory - try to do only minimal update instead */
1662 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1666 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1668 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1670 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1674 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1676 vf_refresh_idle(vf);
1680 /* mark functions can have various side effects - update all columns to be sure */
1681 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1682 /* mark functions can change sidecars too */
1683 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1687 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1691 g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1695 *-----------------------------------------------------------------------------
1697 *-----------------------------------------------------------------------------
1700 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1702 GtkTreeViewColumn *column;
1703 GtkCellRenderer *cell;
1706 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1707 if (!column) return;
1709 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1711 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1713 cell = static_cast<GtkCellRenderer *>(list->data);
1716 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1717 gtk_tree_view_column_set_visible(column, thumb);
1719 if (options->show_star_rating)
1721 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1722 if (!column) return;
1723 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1724 gtk_tree_view_column_set_visible(column, TRUE);
1726 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1727 if (!column) return;
1728 gtk_tree_view_column_set_visible(column, FALSE);
1732 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1733 if (!column) return;
1734 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1735 gtk_tree_view_column_set_visible(column, TRUE);
1737 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1738 if (!column) return;
1739 gtk_tree_view_column_set_visible(column, FALSE);
1742 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1743 if (!column) return;
1744 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1746 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1747 if (!column) return;
1748 gtk_tree_view_column_set_visible(column, !multiline);
1750 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1751 if (!column) return;
1752 gtk_tree_view_column_set_visible(column, !multiline);
1755 static gboolean vflist_is_multiline(ViewFile *vf)
1757 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1761 static void vflist_populate_view(ViewFile *vf, gboolean force)
1763 GtkTreeStore *store;
1766 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1773 vflist_store_clear(vf, FALSE);
1778 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1780 selected = vflist_selection_get_list(vf);
1782 vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1784 if (selected && vflist_selection_count(vf, nullptr) == 0)
1786 /* all selected files disappeared */
1787 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1790 filelist_free(selected);
1793 vf_thumb_update(vf);
1797 gboolean vflist_refresh(ViewFile *vf)
1800 gboolean ret = TRUE;
1802 old_list = vf->list;
1805 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1808 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1810 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1812 if (vf->marks_enabled)
1814 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1815 // each time a mark is changed.
1816 file_data_lock_list(vf->list);
1820 /** @FIXME only do this when needed (aka when we just switched from */
1821 /** @FIXME marks-enabled to marks-disabled) */
1822 file_data_unlock_list(vf->list);
1825 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1826 vf->list = g_list_first(vf->list);
1827 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1829 vf->list = g_list_first(vf->list);
1830 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1832 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1834 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1835 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1838 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1840 vflist_populate_view(vf, FALSE);
1842 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1844 filelist_free(old_list);
1845 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1851 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1853 static GdkRGBA color;
1854 static GtkWidget *done = nullptr;
1860 style = gtk_widget_get_style(widget);
1861 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1863 shift_color(&color, -1, 0);
1870 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1871 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1873 auto vf = static_cast<ViewFile *>(data);
1876 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1877 g_object_set(G_OBJECT(cell),
1878 "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1879 "cell-background-set", set, NULL);
1882 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1884 GtkTreeViewColumn *column;
1885 GtkCellRenderer *renderer;
1887 column = gtk_tree_view_column_new();
1888 gtk_tree_view_column_set_title(column, title);
1889 gtk_tree_view_column_set_min_width(column, 4);
1893 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1894 renderer = gtk_cell_renderer_text_new();
1897 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1899 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1900 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1902 gtk_tree_view_column_set_expand(column, TRUE);
1906 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1907 renderer = gtk_cell_renderer_pixbuf_new();
1908 cell_renderer_height_override(renderer);
1909 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1910 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1913 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
1914 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1915 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1917 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1920 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1922 auto vf = static_cast<ViewFile *>(data);
1923 GtkTreeStore *store;
1924 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1930 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1931 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1934 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1936 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1938 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1941 /* the change has a very limited range and the standard notification would trigger
1942 complete re-read of the directory - try to do only minimal update instead */
1943 file_data_unregister_notify_func(vf_notify_cb, vf);
1944 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1945 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1947 vf_refresh_idle(vf);
1951 /* mark functions can have various side effects - update all columns to be sure */
1952 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1953 /* mark functions can change sidecars too */
1954 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1956 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1958 gtk_tree_path_free(path);
1961 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1963 GtkTreeViewColumn *column;
1964 GtkCellRenderer *renderer;
1966 renderer = gtk_cell_renderer_toggle_new();
1967 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1969 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1970 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1971 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1973 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1974 gtk_tree_view_column_set_fixed_width(column, 22);
1975 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1978 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1982 *-----------------------------------------------------------------------------
1984 *-----------------------------------------------------------------------------
1987 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1990 if (!dir_fd) return FALSE;
1991 if (vf->dir_fd == dir_fd) return TRUE;
1993 file_data_unref(vf->dir_fd);
1994 vf->dir_fd = file_data_ref(dir_fd);
1996 /* force complete reload */
1997 vflist_store_clear(vf, TRUE);
1999 filelist_free(vf->list);
2002 ret = vf_refresh(vf);
2003 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2007 void vflist_destroy_cb(ViewFile *vf)
2009 file_data_unregister_notify_func(vf_notify_cb, vf);
2011 vflist_select_idle_cancel(vf);
2012 vf_refresh_idle_cancel(vf);
2016 filelist_free(vf->list);
2019 ViewFile *vflist_new(ViewFile *vf)
2021 GtkTreeStore *store;
2022 GtkTreeSelection *selection;
2023 GType flist_types[FILE_COLUMN_COUNT];
2027 vf->info = g_new0(ViewFileInfoList, 1);
2029 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2030 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2031 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2032 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2033 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2034 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2035 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2036 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2037 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2038 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2039 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2040 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2041 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2042 flist_types[i] = G_TYPE_BOOLEAN;
2044 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2046 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2047 g_object_unref(store);
2049 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2050 G_CALLBACK(vflist_expand_cb), vf);
2052 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2053 G_CALLBACK(vflist_collapse_cb), vf);
2055 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2056 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2057 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2059 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2060 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2062 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2066 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2068 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2069 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2073 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2074 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2077 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2078 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2081 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2082 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2085 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2086 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2089 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2090 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2093 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2094 g_assert(column == FILE_VIEW_COLUMN_DATE);
2097 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2101 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2103 if (VFLIST(vf)->thumbs_enabled == enable) return;
2105 VFLIST(vf)->thumbs_enabled = enable;
2107 /* vflist_populate_view is better than vf_refresh:
2108 - no need to re-read the directory
2109 - force update because the formatted string has changed
2113 vflist_populate_view(vf, TRUE);
2114 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2118 void vflist_marks_set(ViewFile *vf, gboolean enable)
2123 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2128 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2129 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2132 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2133 gtk_tree_view_column_set_visible(column, enable);
2138 // Previously disabled, which means that vf->list is complete
2139 file_data_lock_list(vf->list);
2143 // Previously enabled, which means that vf->list is incomplete
2146 g_list_free(columns);
2149 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */