2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "view_file_list.h"
26 #include "cache_maint.h"
32 #include "layout_image.h"
38 #include "ui_fileops.h"
41 #include "ui_tree_edit.h"
42 #include "uri_utils.h"
43 #include "view_file.h"
46 #include <gdk/gdkkeysyms.h> /* for keyboard values */
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, const gchar *new, 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 *-----------------------------------------------------------------------------
100 } ViewFileFindRowData;
102 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
104 ViewFileFindRowData *find = data;
106 gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
117 static gint vflist_find_row(ViewFile *vf, 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 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *iter)
136 GtkTreeViewColumn *column;
138 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
139 &tpath, &column, NULL, NULL))
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 *path, GtkTreeIter *iter, gpointer data)
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)
173 if (unlock_files && vf->marks_enabled)
175 // unlock locked files in this directory
176 filelist_read(vf->dir_fd, &files, NULL);
179 FileData *fd = files->data;
181 file_data_unlock(fd);
182 file_data_unref(fd); // undo the ref that got added in filelist_read
187 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
188 gtk_tree_model_foreach(store, vflist_store_clear_cb, NULL);
189 gtk_tree_store_clear(GTK_TREE_STORE(store));
192 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
197 if (vflist_find_row(vf, fd, &iter) < 0) return;
198 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
199 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
202 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
207 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
209 tpath = gtk_tree_model_get_path(store, iter);
210 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
211 gtk_tree_path_free(tpath);
216 *-----------------------------------------------------------------------------
218 *-----------------------------------------------------------------------------
221 static void vflist_dnd_get(GtkWidget *widget, GdkDragContext *context,
222 GtkSelectionData *selection_data, guint info,
223 guint time, gpointer data)
228 if (!VFLIST(vf)->click_fd) return;
230 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
232 list = vf_selection_get_list(vf);
236 list = g_list_append(NULL, file_data_ref(VFLIST(vf)->click_fd));
240 uri_selection_data_set_uris_from_filelist(selection_data, list);
244 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
248 vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
250 if (VFLIST(vf)->thumbs_enabled &&
251 VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
255 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
256 items = vf_selection_count(vf, NULL);
260 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
264 static void vflist_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
268 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
270 if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
276 static void vflist_drag_data_received(GtkWidget *entry_widget, GdkDragContext *context,
277 int x, int y, GtkSelectionData *selection,
278 guint info, guint time, gpointer data)
282 if (info == TARGET_TEXT_PLAIN) {
283 FileData *fd = vflist_find_data_by_coord(vf, x, y, NULL);
286 /* Add keywords to file */
287 gchar *str = (gchar *) gtk_selection_data_get_text(selection);
288 GList *kw_list = string_to_keywords_list(str);
290 metadata_append_list(fd, KEYWORD_KEY, kw_list);
291 string_list_free(kw_list);
297 void vflist_dnd_init(ViewFile *vf)
299 gtk_drag_source_set(vf->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
300 dnd_file_drag_types, dnd_file_drag_types_count,
301 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
302 gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
303 dnd_file_drag_types, dnd_file_drag_types_count,
304 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
306 g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
307 G_CALLBACK(vflist_dnd_get), vf);
308 g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
309 G_CALLBACK(vflist_dnd_begin), vf);
310 g_signal_connect(G_OBJECT(vf->listview), "drag_end",
311 G_CALLBACK(vflist_dnd_end), vf);
312 g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
313 G_CALLBACK(vflist_drag_data_received), vf);
317 *-----------------------------------------------------------------------------
319 *-----------------------------------------------------------------------------
322 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
324 GList *list = g_list_append(NULL, file_data_ref(fd));
326 if (fd->sidecar_files)
328 /* check if the row is expanded */
332 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
333 if (vflist_find_row(vf, fd, &iter) >= 0)
337 tpath = gtk_tree_model_get_path(store, &iter);
338 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
340 /* unexpanded - add whole group */
341 GList *work = fd->sidecar_files;
344 FileData *sfd = work->data;
345 list = g_list_prepend(list, file_data_ref(sfd));
349 gtk_tree_path_free(tpath);
351 list = g_list_reverse(list);
357 GList *vflist_pop_menu_file_list(ViewFile *vf)
359 if (!VFLIST(vf)->click_fd) return NULL;
361 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
363 return vf_selection_get_list(vf);
365 return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
369 void vflist_pop_menu_view_cb(GtkWidget *widget, gpointer data)
373 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
377 list = vf_selection_get_list(vf);
378 view_window_new_from_list(list);
383 view_window_new(VFLIST(vf)->click_fd);
387 void vflist_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
392 list = vf_pop_menu_file_list(vf);
393 if (options->file_ops.enable_in_place_rename &&
394 list && !list->next && VFLIST(vf)->click_fd)
401 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
402 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
406 tpath = gtk_tree_model_get_path(store, &iter);
407 tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
408 FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
409 vflist_row_rename_cb, vf);
410 gtk_tree_path_free(tpath);
415 file_util_rename(NULL, list, vf->listview);
418 void vflist_pop_menu_thumbs_cb(GtkWidget *widget, gpointer data)
422 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
425 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
429 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
433 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
435 GList *columns, *work;
437 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
442 GtkTreeViewColumn *column = work->data;
443 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
446 if (vflist_is_multiline(vf))
448 if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
450 gtk_tree_view_column_set_visible(column, enable);
452 if (col_idx == FILE_COLUMN_FORMATTED)
454 gtk_tree_view_column_set_visible(column, !enable);
459 if (col_idx == FILE_COLUMN_STAR_RATING)
461 gtk_tree_view_column_set_visible(column, enable);
465 g_list_free(columns);
468 void vflist_pop_menu_show_star_rating_cb(GtkWidget *widget, gpointer data)
472 options->show_star_rating = !options->show_star_rating;
474 vflist_populate_view(vf, TRUE);
476 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
477 vflist_star_rating_set(vf, options->show_star_rating);
480 void vflist_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
484 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
486 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
489 void vflist_popup_destroy_cb(GtkWidget *widget, gpointer data)
492 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
493 VFLIST(vf)->click_fd = NULL;
499 *-----------------------------------------------------------------------------
501 *-----------------------------------------------------------------------------
504 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
509 if (!new || !new[0]) return FALSE;
511 new_path = g_build_filename(vf->dir_fd->path, new, NULL);
513 if (strchr(new, G_DIR_SEPARATOR) != NULL)
515 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new);
516 file_util_warning_dialog(_("Error renaming file"), text, GTK_STOCK_DIALOG_ERROR, vf->listview);
521 gchar *old_path = g_build_filename(vf->dir_fd->path, old, NULL);
522 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
523 file_util_rename_simple(fd, new_path, vf->listview);
533 static void vflist_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
541 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) < 0) return;
542 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
543 tpath = gtk_tree_model_get_path(store, &iter);
544 tree_view_get_cell_clamped(GTK_TREE_VIEW(vf->listview), tpath, FILE_COLUMN_NAME - 1, TRUE, x, y, &cw, &ch);
545 gtk_tree_path_free(tpath);
547 popup_menu_position_clamp(menu, x, y, 0);
550 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
555 if (event->keyval != GDK_KEY_Menu) return FALSE;
557 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, NULL);
563 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
564 gtk_tree_model_get_iter(store, &iter, tpath);
565 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
566 gtk_tree_path_free(tpath);
570 VFLIST(vf)->click_fd = NULL;
573 vf->popup = vf_pop_menu(vf);
574 gtk_menu_popup(GTK_MENU(vf->popup), NULL, NULL, vflist_menu_position_cb, vf, 0, GDK_CURRENT_TIME);
579 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
585 GtkTreeViewColumn *column;
587 vf->clicked_mark = 0;
589 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
590 &tpath, &column, NULL, NULL))
593 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
595 if (bevent->button == MOUSE_BUTTON_LEFT &&
596 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
599 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
600 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
602 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
604 gtk_tree_model_get_iter(store, &iter, tpath);
605 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
606 gtk_tree_path_free(tpath);
609 VFLIST(vf)->click_fd = fd;
611 if (bevent->button == MOUSE_BUTTON_RIGHT)
613 vf->popup = vf_pop_menu(vf);
614 gtk_menu_popup(GTK_MENU(vf->popup), NULL, NULL, NULL, NULL,
615 bevent->button, bevent->time);
619 if (!fd) return FALSE;
621 if (bevent->button == MOUSE_BUTTON_MIDDLE)
623 if (!vflist_row_is_selected(vf, fd))
625 vflist_color_set(vf, fd, TRUE);
631 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
632 !(bevent->state & GDK_SHIFT_MASK ) &&
633 !(bevent->state & GDK_CONTROL_MASK ) &&
634 vflist_row_is_selected(vf, fd))
636 GtkTreeSelection *selection;
638 gtk_widget_grab_focus(widget);
641 /* returning FALSE and further processing of the event is needed for
642 correct operation of the expander, to show the sidecar files.
643 It however resets the selection of multiple files. With this condition
644 it should work for both cases */
645 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
646 return (gtk_tree_selection_count_selected_rows(selection) > 1);
649 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
651 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
653 collection_window_new(VFLIST(vf)->click_fd->path);
657 if (vf->layout) layout_image_full_screen_start(vf->layout);
664 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
671 if (defined_mouse_buttons(widget, bevent, vf->layout))
676 if (bevent->button == MOUSE_BUTTON_MIDDLE)
678 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
681 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
686 if ((bevent->x != 0 || bevent->y != 0) &&
687 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
688 &tpath, NULL, NULL, NULL))
692 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
693 gtk_tree_model_get_iter(store, &iter, tpath);
694 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
695 gtk_tree_path_free(tpath);
698 if (bevent->button == MOUSE_BUTTON_MIDDLE)
700 if (fd && VFLIST(vf)->click_fd == fd)
702 GtkTreeSelection *selection;
704 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
705 if (vflist_row_is_selected(vf, fd))
707 gtk_tree_selection_unselect_iter(selection, &iter);
711 gtk_tree_selection_select_iter(selection, &iter);
717 if (fd && VFLIST(vf)->click_fd == fd &&
718 !(bevent->state & GDK_SHIFT_MASK ) &&
719 !(bevent->state & GDK_CONTROL_MASK ) &&
720 vflist_row_is_selected(vf, fd))
722 GtkTreeSelection *selection;
724 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
725 gtk_tree_selection_unselect_all(selection);
726 gtk_tree_selection_select_iter(selection, &iter);
727 vflist_move_cursor(vf, &iter);
733 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
735 FileData *read_ahead_fd = NULL;
741 cur_fd = layout_image_get_fd(vf->layout);
742 if (sel_fd == cur_fd) return; /* no change */
744 row = g_list_index(vf->list, sel_fd);
745 /** @FIXME sidecar data */
747 if (sel_fd && options->image.enable_read_ahead && row >= 0)
749 if (row > g_list_index(vf->list, cur_fd) &&
750 (guint) (row + 1) < vf_count(vf, NULL))
752 read_ahead_fd = vf_index_get_data(vf, row + 1);
756 read_ahead_fd = vf_index_get_data(vf, row - 1);
760 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
763 static gboolean vflist_select_idle_cb(gpointer data)
769 VFLIST(vf)->select_idle_id = 0;
775 if (VFLIST(vf)->select_fd)
777 vflist_select_image(vf, VFLIST(vf)->select_fd);
778 VFLIST(vf)->select_fd = NULL;
781 VFLIST(vf)->select_idle_id = 0;
785 static void vflist_select_idle_cancel(ViewFile *vf)
787 if (VFLIST(vf)->select_idle_id)
789 g_source_remove(VFLIST(vf)->select_idle_id);
790 VFLIST(vf)->select_idle_id = 0;
794 static gboolean vflist_select_cb(GtkTreeSelection *selection, GtkTreeModel *store, GtkTreePath *tpath,
795 gboolean path_currently_selected, gpointer data)
799 GtkTreePath *cursor_path;
801 VFLIST(vf)->select_fd = NULL;
803 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
805 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, NULL);
808 gtk_tree_model_get_iter(store, &iter, cursor_path);
809 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
810 gtk_tree_path_free(cursor_path);
815 !VFLIST(vf)->select_idle_id)
817 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
823 static void vflist_expand_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
826 vflist_set_expanded(vf, iter, TRUE);
829 static void vflist_collapse_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
832 vflist_set_expanded(vf, iter, FALSE);
836 *-----------------------------------------------------------------------------
838 *-----------------------------------------------------------------------------
842 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)
844 gboolean multiline = vflist_is_multiline(vf);
851 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
855 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
860 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
865 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
873 gchar *formatted_with_stars;
875 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
877 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
878 FILE_COLUMN_NAME, &name,
879 FILE_COLUMN_SIDECARS, &sidecars,
880 FILE_COLUMN_SIZE, &size,
881 FILE_COLUMN_DATE, &time,
882 FILE_COLUMN_STAR_RATING, &star_rating,
885 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
886 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
888 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
889 FILE_COLUMN_EXPANDED, expanded,
891 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
892 FILE_COLUMN_EXPANDED, expanded,
899 g_free(formatted_with_stars);
902 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
905 gchar *sidecars = NULL;
907 const gchar *time = text_from_time(fd->date);
908 gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
909 const gchar *disabled_grouping;
911 gchar *formatted_with_stars;
912 gboolean expanded = FALSE;
915 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
917 star_rating = convert_rating_to_stars(fd->rating);
924 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
926 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
929 sidecars = file_data_sc_list_to_string(fd);
931 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
932 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
933 size = text_from_size(fd->size);
935 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
936 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
938 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
939 FILE_COLUMN_VERSION, fd->version,
940 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
941 FILE_COLUMN_FORMATTED, formatted,
942 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
943 FILE_COLUMN_SIDECARS, sidecars,
944 FILE_COLUMN_NAME, name,
945 FILE_COLUMN_STAR_RATING, star_rating,
946 FILE_COLUMN_SIZE, size,
947 FILE_COLUMN_DATE, time,
948 #define STORE_SET_IS_SLOW 1
949 #if STORE_SET_IS_SLOW
950 /* this is 3x faster on a directory with 20000 files */
951 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
952 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
953 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
954 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
955 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
956 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
957 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
958 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
959 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
960 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
961 #if FILEDATA_MARKS_SIZE != 10
962 #error this needs to be updated
965 FILE_COLUMN_COLOR, FALSE, -1);
967 #if !STORE_SET_IS_SLOW
970 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
971 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
980 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
985 gint num_ordered = 0;
986 gint num_prepended = 0;
988 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
994 FileData *fd = work->data;
995 gboolean done = FALSE;
999 FileData *old_fd = NULL;
1000 gint old_version = 0;
1004 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1005 FILE_COLUMN_POINTER, &old_fd,
1006 FILE_COLUMN_VERSION, &old_version,
1016 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
1018 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
1020 if (match == 0) g_warning("multiple fd for the same path");
1036 gtk_tree_store_insert_before(store, &new, parent_iter, &iter);
1041 here should be used gtk_tree_store_append, but this function seems to be O(n)
1042 and it seems to be much faster to add new entries to the beginning and reorder later
1045 gtk_tree_store_prepend(store, &new, parent_iter);
1048 vflist_setup_iter(vf, store, &new, file_data_ref(fd));
1049 vflist_setup_iter_recursive(vf, store, &new, fd->sidecar_files, selected, force);
1051 if (g_list_find(selected, fd))
1053 /* renamed files - the same fd appears at different position - select it again*/
1054 GtkTreeSelection *selection;
1055 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1056 gtk_tree_selection_select_iter(selection, &new);
1063 file_data_unref(old_fd);
1064 valid = gtk_tree_store_remove(store, &iter);
1069 if (fd->version != old_version || force)
1071 vflist_setup_iter(vf, store, &iter, fd);
1072 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1075 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1086 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1087 file_data_unref(old_fd);
1089 valid = gtk_tree_store_remove(store, &iter);
1092 /* move the prepended entries to the correct position */
1096 gint num_total = num_prepended + num_ordered;
1097 gint *new_order = g_malloc(num_total * sizeof(gint));
1099 for (i = 0; i < num_total; i++)
1101 if (i < num_ordered)
1102 new_order[i] = num_prepended + i;
1104 new_order[i] = num_total - 1 - i;
1106 gtk_tree_store_reorder(store, parent_iter, new_order);
1112 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1115 GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
1117 GtkTreeStore *store;
1120 if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1121 if (!vf->list) return;
1127 FileData *fd = work->data;
1128 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1133 vf->sort_method = type;
1134 vf->sort_ascend = ascend;
1136 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1138 new_order = g_malloc(i * sizeof(gint));
1144 FileData *fd = work->data;
1145 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
1150 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1151 gtk_tree_store_reorder(store, NULL, new_order);
1154 g_hash_table_destroy(fd_idx_hash);
1158 *-----------------------------------------------------------------------------
1160 *-----------------------------------------------------------------------------
1164 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1169 FileData *fd = work->data;
1172 if (fd->thumb_pixbuf) (*done)++;
1174 if (fd->sidecar_files)
1176 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1182 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1187 FileData *fd = work->data;
1190 if (fd->metadata_in_idle_loaded) (*done)++;
1192 if (fd->sidecar_files)
1194 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1200 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1202 GtkTreeStore *store;
1205 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1207 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1208 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1211 FileData *vflist_thumb_next_fd(ViewFile *vf)
1214 FileData *fd = NULL;
1216 /* first check the visible files */
1218 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1220 GtkTreeModel *store;
1222 gboolean valid = TRUE;
1224 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1225 gtk_tree_model_get_iter(store, &iter, tpath);
1226 gtk_tree_path_free(tpath);
1229 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1233 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1235 if (!nfd->thumb_pixbuf) fd = nfd;
1237 valid = gtk_tree_model_iter_next(store, &iter);
1241 /* then find first undone */
1245 GList *work = vf->list;
1248 FileData *fd_p = work->data;
1249 if (!fd_p->thumb_pixbuf)
1253 GList *work2 = fd_p->sidecar_files;
1255 while (work2 && !fd)
1258 if (!fd_p->thumb_pixbuf) fd = fd_p;
1259 work2 = work2->next;
1269 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1271 GtkTreeStore *store;
1278 gchar *formatted_with_stars;
1281 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1283 star_rating = metadata_read_rating_stars(fd);
1285 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1286 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1288 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1289 FILE_COLUMN_NAME, &name,
1290 FILE_COLUMN_SIDECARS, &sidecars,
1291 FILE_COLUMN_SIZE, &size,
1292 FILE_COLUMN_DATE, &time,
1293 FILE_COLUMN_EXPANDED, &expanded,
1296 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1298 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1299 FILE_COLUMN_EXPANDED, expanded,
1302 g_free(star_rating);
1303 g_free(formatted_with_stars);
1306 FileData *vflist_star_next_fd(ViewFile *vf)
1309 FileData *fd = NULL;
1310 FileData *nfd = NULL;
1312 /* first check the visible files */
1314 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1316 GtkTreeModel *store;
1318 gboolean valid = TRUE;
1320 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1321 gtk_tree_model_get_iter(store, &iter, tpath);
1322 gtk_tree_path_free(tpath);
1325 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1327 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1329 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1334 valid = gtk_tree_model_iter_next(store, &iter);
1339 vf->stars_filedata = fd;
1341 if (vf->stars_id == 0)
1343 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1348 /* then find first undone */
1352 GList *work = vf->list;
1356 FileData *fd_p = work->data;
1358 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1372 vf->stars_filedata = fd;
1374 if (vf->stars_id == 0)
1376 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1385 *-----------------------------------------------------------------------------
1387 *-----------------------------------------------------------------------------
1390 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1393 GList *work, *work2;
1398 FileData *list_fd = work->data;
1399 if (list_fd == fd) return p;
1401 work2 = list_fd->sidecar_files;
1404 /** @FIXME return the same index also for sidecars
1405 it is sufficient for next/prev navigation but it should be rewritten
1406 without using indexes at all
1408 FileData *sidecar_fd = work2->data;
1409 if (sidecar_fd == fd) return p;
1410 work2 = work2->next;
1421 *-----------------------------------------------------------------------------
1423 *-----------------------------------------------------------------------------
1426 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1428 GtkTreeModel *store;
1429 GtkTreeSelection *selection;
1432 gboolean found = FALSE;
1434 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1435 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1437 while (!found && work)
1439 GtkTreePath *tpath = work->data;
1443 gtk_tree_model_get_iter(store, &iter, tpath);
1444 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1445 if (fd_n == fd) found = TRUE;
1448 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1454 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1458 fd = vf_index_get_data(vf, row);
1459 return vflist_row_is_selected(vf, fd);
1462 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1464 GtkTreeModel *store;
1465 GtkTreeSelection *selection;
1469 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1470 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1480 GtkTreePath *tpath = work->data;
1484 gtk_tree_model_get_iter(store, &iter, tpath);
1485 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1494 count = g_list_length(slist);
1495 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1501 GList *vflist_selection_get_list(ViewFile *vf)
1503 GtkTreeModel *store;
1504 GtkTreeSelection *selection;
1509 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1510 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1514 GtkTreePath *tpath = work->data;
1518 gtk_tree_model_get_iter(store, &iter, tpath);
1519 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1521 list = g_list_prepend(list, file_data_ref(fd));
1523 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1525 /* unexpanded - add whole group */
1526 GList *work2 = fd->sidecar_files;
1529 FileData *sfd = work2->data;
1530 list = g_list_prepend(list, file_data_ref(sfd));
1531 work2 = work2->next;
1537 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1540 return g_list_reverse(list);
1543 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1545 GtkTreeModel *store;
1546 GtkTreeSelection *selection;
1551 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1552 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1556 GtkTreePath *tpath = work->data;
1560 gtk_tree_model_get_iter(store, &iter, tpath);
1561 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1563 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1567 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1570 return g_list_reverse(list);
1573 void vflist_select_all(ViewFile *vf)
1575 GtkTreeSelection *selection;
1577 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1578 gtk_tree_selection_select_all(selection);
1580 VFLIST(vf)->select_fd = NULL;
1583 void vflist_select_none(ViewFile *vf)
1585 GtkTreeSelection *selection;
1587 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1588 gtk_tree_selection_unselect_all(selection);
1591 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1596 tpath = gtk_tree_model_get_path(store, iter);
1597 result = gtk_tree_path_prev(tpath);
1599 gtk_tree_model_get_iter(store, iter, tpath);
1601 gtk_tree_path_free(tpath);
1606 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1608 if (!gtk_tree_model_get_iter_first(store, iter))
1613 GtkTreeIter next = *iter;
1615 if (gtk_tree_model_iter_next(store, &next))
1624 void vflist_select_invert(ViewFile *vf)
1627 GtkTreeSelection *selection;
1628 GtkTreeModel *store;
1631 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1632 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1634 /* Backward iteration prevents scrolling to the end of the list,
1635 * it scrolls to the first selected row instead. */
1636 valid = tree_model_get_iter_last(store, &iter);
1640 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1643 gtk_tree_selection_unselect_iter(selection, &iter);
1645 gtk_tree_selection_select_iter(selection, &iter);
1647 valid = tree_model_iter_prev(store, &iter);
1651 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1655 if (vflist_find_row(vf, fd, &iter) < 0) return;
1657 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1659 if (!vflist_row_is_selected(vf, fd))
1661 GtkTreeSelection *selection;
1662 GtkTreeModel *store;
1665 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1666 gtk_tree_selection_unselect_all(selection);
1667 gtk_tree_selection_select_iter(selection, &iter);
1668 vflist_move_cursor(vf, &iter);
1670 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1671 tpath = gtk_tree_model_get_path(store, &iter);
1672 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1673 gtk_tree_path_free(tpath);
1677 void vflist_select_list(ViewFile *vf, GList *list)
1690 if (vflist_find_row(vf, fd, &iter) < 0) return;
1691 if (!vflist_row_is_selected(vf, fd))
1693 GtkTreeSelection *selection;
1695 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1696 gtk_tree_selection_select_iter(selection, &iter);
1702 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1705 FileData *fd = NULL;
1707 if (sel_fd->parent) sel_fd = sel_fd->parent;
1716 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1718 if (match >= 0) break;
1721 if (fd) vflist_select_by_fd(vf, fd);
1725 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1727 GtkTreeModel *store;
1729 GtkTreeSelection *selection;
1733 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1735 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1736 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1738 valid = gtk_tree_model_get_iter_first(store, &iter);
1742 gboolean mark_val, selected;
1743 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1745 mark_val = file_data_get_mark(fd, n);
1746 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1750 case MTS_MODE_SET: selected = mark_val;
1752 case MTS_MODE_OR: selected = mark_val || selected;
1754 case MTS_MODE_AND: selected = mark_val && selected;
1756 case MTS_MODE_MINUS: selected = !mark_val && selected;
1761 gtk_tree_selection_select_iter(selection, &iter);
1763 gtk_tree_selection_unselect_iter(selection, &iter);
1765 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1769 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1771 GtkTreeModel *store;
1772 GtkTreeSelection *selection;
1777 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1779 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1780 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1784 GtkTreePath *tpath = work->data;
1788 gtk_tree_model_get_iter(store, &iter, tpath);
1789 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1791 /* the change has a very limited range and the standard notification would trigger
1792 complete re-read of the directory - try to do only minimal update instead */
1793 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1797 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1799 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1801 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1805 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1807 vf_refresh_idle(vf);
1811 /* mark functions can have various side effects - update all columns to be sure */
1812 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1813 /* mark functions can change sidecars too */
1814 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1818 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1822 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1827 *-----------------------------------------------------------------------------
1829 *-----------------------------------------------------------------------------
1832 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1834 GtkTreeViewColumn *column;
1835 GtkCellRenderer *cell;
1838 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1839 if (!column) return;
1841 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1843 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1848 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1849 gtk_tree_view_column_set_visible(column, thumb);
1851 if (options->show_star_rating)
1853 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1854 if (!column) return;
1855 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1856 gtk_tree_view_column_set_visible(column, TRUE);
1858 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1859 if (!column) return;
1860 gtk_tree_view_column_set_visible(column, FALSE);
1864 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1865 if (!column) return;
1866 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1867 gtk_tree_view_column_set_visible(column, TRUE);
1869 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1870 if (!column) return;
1871 gtk_tree_view_column_set_visible(column, FALSE);
1874 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1875 if (!column) return;
1876 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1878 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1879 if (!column) return;
1880 gtk_tree_view_column_set_visible(column, !multiline);
1882 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1883 if (!column) return;
1884 gtk_tree_view_column_set_visible(column, !multiline);
1887 static gboolean vflist_is_multiline(ViewFile *vf)
1889 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1893 static void vflist_populate_view(ViewFile *vf, gboolean force)
1895 GtkTreeStore *store;
1898 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1905 vflist_store_clear(vf, FALSE);
1910 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1912 selected = vflist_selection_get_list(vf);
1914 vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1916 if (selected && vflist_selection_count(vf, NULL) == 0)
1918 /* all selected files disappeared */
1919 vflist_select_closest(vf, selected->data);
1922 filelist_free(selected);
1925 vf_thumb_update(vf);
1929 gboolean vflist_refresh(ViewFile *vf)
1932 gboolean ret = TRUE;
1934 old_list = vf->list;
1937 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1940 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1942 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1944 if (vf->marks_enabled)
1946 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1947 // each time a mark is changed.
1948 file_data_lock_list(vf->list);
1952 /** @FIXME only do this when needed (aka when we just switched from */
1953 /** @FIXME marks-enabled to marks-disabled) */
1954 file_data_unlock_list(vf->list);
1957 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1958 vf->list = g_list_first(vf->list);
1959 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1961 vf->list = g_list_first(vf->list);
1962 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1964 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1966 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1967 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1970 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1972 vflist_populate_view(vf, FALSE);
1974 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1976 filelist_free(old_list);
1977 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1984 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1986 #define CELL_HEIGHT_OVERRIDE 512
1988 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1992 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1993 if (spec && G_IS_PARAM_SPEC_INT(spec))
1995 GParamSpecInt *spec_int;
1997 spec_int = G_PARAM_SPEC_INT(spec);
1998 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
2002 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
2004 static GdkColor color;
2005 static GtkWidget *done = NULL;
2011 style = gtk_widget_get_style(widget);
2012 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
2013 shift_color(&color, -1, 0);
2020 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
2021 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2023 ViewFile *vf = data;
2026 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
2027 g_object_set(G_OBJECT(cell),
2028 "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
2029 "cell-background-set", set, NULL);
2032 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2034 GtkTreeViewColumn *column;
2035 GtkCellRenderer *renderer;
2037 column = gtk_tree_view_column_new();
2038 gtk_tree_view_column_set_title(column, title);
2039 gtk_tree_view_column_set_min_width(column, 4);
2043 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2044 renderer = gtk_cell_renderer_text_new();
2047 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2049 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2050 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2052 gtk_tree_view_column_set_expand(column, TRUE);
2056 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2057 renderer = gtk_cell_renderer_pixbuf_new();
2058 cell_renderer_height_override(renderer);
2059 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2060 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2063 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
2064 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2065 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2067 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2070 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2072 ViewFile *vf = data;
2073 GtkTreeStore *store;
2074 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2080 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2081 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2084 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2086 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2088 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2091 /* the change has a very limited range and the standard notification would trigger
2092 complete re-read of the directory - try to do only minimal update instead */
2093 file_data_unregister_notify_func(vf_notify_cb, vf);
2094 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2095 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2097 vf_refresh_idle(vf);
2101 /* mark functions can have various side effects - update all columns to be sure */
2102 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2103 /* mark functions can change sidecars too */
2104 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
2106 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2108 gtk_tree_path_free(path);
2111 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2113 GtkTreeViewColumn *column;
2114 GtkCellRenderer *renderer;
2116 renderer = gtk_cell_renderer_toggle_new();
2117 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2119 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2120 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2121 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2123 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2124 gtk_tree_view_column_set_fixed_width(column, 22);
2125 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2128 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2132 *-----------------------------------------------------------------------------
2134 *-----------------------------------------------------------------------------
2137 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2140 if (!dir_fd) return FALSE;
2141 if (vf->dir_fd == dir_fd) return TRUE;
2143 file_data_unref(vf->dir_fd);
2144 vf->dir_fd = file_data_ref(dir_fd);
2146 /* force complete reload */
2147 vflist_store_clear(vf, TRUE);
2149 filelist_free(vf->list);
2152 ret = vf_refresh(vf);
2153 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2157 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
2159 ViewFile *vf = data;
2161 file_data_unregister_notify_func(vf_notify_cb, vf);
2163 vflist_select_idle_cancel(vf);
2164 vf_refresh_idle_cancel(vf);
2168 filelist_free(vf->list);
2171 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
2173 GtkTreeStore *store;
2174 GtkTreeSelection *selection;
2175 GType flist_types[FILE_COLUMN_COUNT];
2179 vf->info = g_new0(ViewFileInfoList, 1);
2181 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2182 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2183 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2184 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2185 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2186 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2187 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2188 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2189 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2190 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2191 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2192 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2193 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2194 flist_types[i] = G_TYPE_BOOLEAN;
2196 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2198 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2199 g_object_unref(store);
2201 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2202 G_CALLBACK(vflist_expand_cb), vf);
2204 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2205 G_CALLBACK(vflist_collapse_cb), vf);
2207 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2208 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2209 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
2211 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2212 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2214 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2218 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2220 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2221 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2225 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2226 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2229 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2230 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2233 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2234 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2237 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2238 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2241 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2242 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2245 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2246 g_assert(column == FILE_VIEW_COLUMN_DATE);
2249 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2253 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2255 if (VFLIST(vf)->thumbs_enabled == enable) return;
2257 VFLIST(vf)->thumbs_enabled = enable;
2259 /* vflist_populate_view is better than vf_refresh:
2260 - no need to re-read the directory
2261 - force update because the formatted string has changed
2265 vflist_populate_view(vf, TRUE);
2266 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2270 void vflist_marks_set(ViewFile *vf, gboolean enable)
2272 GList *columns, *work;
2274 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2279 GtkTreeViewColumn *column = work->data;
2280 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2283 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2284 gtk_tree_view_column_set_visible(column, enable);
2289 // Previously disabled, which means that vf->list is complete
2290 file_data_lock_list(vf->list);
2294 // Previously enabled, which means that vf->list is incomplete
2297 g_list_free(columns);
2300 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */