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_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 *-----------------------------------------------------------------------------
100 } ViewFileFindRowData;
102 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *UNUSED(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 *UNUSED(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 *UNUSED(path), GtkTreeIter *iter, gpointer UNUSED(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 *UNUSED(widget), GdkDragContext *UNUSED(context),
222 GtkSelectionData *selection_data, guint UNUSED(info),
223 guint UNUSED(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 *UNUSED(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 *UNUSED(entry_widget), GdkDragContext *UNUSED(context),
277 int x, int y, GtkSelectionData *selection,
278 guint info, guint UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(td), const gchar *old_name, const gchar *new_name, gpointer data)
509 if (!new_name || !new_name[0]) return FALSE;
511 new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
513 if (strchr(new_name, G_DIR_SEPARATOR) != NULL)
515 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
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_name, 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 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
538 if (event->keyval != GDK_KEY_Menu) return FALSE;
540 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, NULL);
546 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
547 gtk_tree_model_get_iter(store, &iter, tpath);
548 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
549 gtk_tree_path_free(tpath);
553 VFLIST(vf)->click_fd = NULL;
556 vf->popup = vf_pop_menu(vf);
557 gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, NULL);
562 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
568 GtkTreeViewColumn *column;
570 vf->clicked_mark = 0;
572 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
573 &tpath, &column, NULL, NULL))
576 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
578 if (bevent->button == MOUSE_BUTTON_LEFT &&
579 col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
582 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
583 vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
585 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
587 gtk_tree_model_get_iter(store, &iter, tpath);
588 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
589 gtk_tree_path_free(tpath);
592 VFLIST(vf)->click_fd = fd;
594 if (bevent->button == MOUSE_BUTTON_RIGHT)
596 vf->popup = vf_pop_menu(vf);
597 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), NULL);
601 if (!fd) return FALSE;
603 if (bevent->button == MOUSE_BUTTON_MIDDLE)
605 if (!vflist_row_is_selected(vf, fd))
607 vflist_color_set(vf, fd, TRUE);
613 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
614 !(bevent->state & GDK_SHIFT_MASK ) &&
615 !(bevent->state & GDK_CONTROL_MASK ) &&
616 vflist_row_is_selected(vf, fd))
618 GtkTreeSelection *selection;
620 gtk_widget_grab_focus(widget);
623 /* returning FALSE and further processing of the event is needed for
624 correct operation of the expander, to show the sidecar files.
625 It however resets the selection of multiple files. With this condition
626 it should work for both cases */
627 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
628 return (gtk_tree_selection_count_selected_rows(selection) > 1);
631 if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
633 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
635 collection_window_new(VFLIST(vf)->click_fd->path);
639 if (vf->layout) layout_image_full_screen_start(vf->layout);
646 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
653 if (defined_mouse_buttons(widget, bevent, vf->layout))
658 if (bevent->button == MOUSE_BUTTON_MIDDLE)
660 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
663 if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
668 if ((bevent->x != 0 || bevent->y != 0) &&
669 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
670 &tpath, NULL, NULL, NULL))
674 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
675 gtk_tree_model_get_iter(store, &iter, tpath);
676 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
677 gtk_tree_path_free(tpath);
680 if (bevent->button == MOUSE_BUTTON_MIDDLE)
682 if (fd && VFLIST(vf)->click_fd == fd)
684 GtkTreeSelection *selection;
686 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
687 if (vflist_row_is_selected(vf, fd))
689 gtk_tree_selection_unselect_iter(selection, &iter);
693 gtk_tree_selection_select_iter(selection, &iter);
699 if (fd && VFLIST(vf)->click_fd == fd &&
700 !(bevent->state & GDK_SHIFT_MASK ) &&
701 !(bevent->state & GDK_CONTROL_MASK ) &&
702 vflist_row_is_selected(vf, fd))
704 GtkTreeSelection *selection;
706 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
707 gtk_tree_selection_unselect_all(selection);
708 gtk_tree_selection_select_iter(selection, &iter);
709 vflist_move_cursor(vf, &iter);
715 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
717 FileData *read_ahead_fd = NULL;
723 cur_fd = layout_image_get_fd(vf->layout);
724 if (sel_fd == cur_fd) return; /* no change */
726 row = g_list_index(vf->list, sel_fd);
727 /** @FIXME sidecar data */
729 if (sel_fd && options->image.enable_read_ahead && row >= 0)
731 if (row > g_list_index(vf->list, cur_fd) &&
732 (guint) (row + 1) < vf_count(vf, NULL))
734 read_ahead_fd = vf_index_get_data(vf, row + 1);
738 read_ahead_fd = vf_index_get_data(vf, row - 1);
742 layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
745 static gboolean vflist_select_idle_cb(gpointer data)
751 VFLIST(vf)->select_idle_id = 0;
757 if (VFLIST(vf)->select_fd)
759 vflist_select_image(vf, VFLIST(vf)->select_fd);
760 VFLIST(vf)->select_fd = NULL;
763 VFLIST(vf)->select_idle_id = 0;
767 static void vflist_select_idle_cancel(ViewFile *vf)
769 if (VFLIST(vf)->select_idle_id)
771 g_source_remove(VFLIST(vf)->select_idle_id);
772 VFLIST(vf)->select_idle_id = 0;
776 static gboolean vflist_select_cb(GtkTreeSelection *UNUSED(selection), GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
780 GtkTreePath *cursor_path;
782 VFLIST(vf)->select_fd = NULL;
784 if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
786 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, NULL);
789 gtk_tree_model_get_iter(store, &iter, cursor_path);
790 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
791 gtk_tree_path_free(cursor_path);
796 !VFLIST(vf)->select_idle_id)
798 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
804 static void vflist_expand_cb(GtkTreeView *UNUSED(tree_view), GtkTreeIter *iter, GtkTreePath *UNUSED(path), gpointer data)
807 vflist_set_expanded(vf, iter, TRUE);
810 static void vflist_collapse_cb(GtkTreeView *UNUSED(tree_view), GtkTreeIter *iter, GtkTreePath *UNUSED(path), gpointer data)
813 vflist_set_expanded(vf, iter, FALSE);
817 *-----------------------------------------------------------------------------
819 *-----------------------------------------------------------------------------
823 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)
825 gboolean multiline = vflist_is_multiline(vf);
832 text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
836 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
841 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
846 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
854 gchar *formatted_with_stars;
856 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
858 gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
859 FILE_COLUMN_NAME, &name,
860 FILE_COLUMN_SIDECARS, &sidecars,
861 FILE_COLUMN_SIZE, &size,
862 FILE_COLUMN_DATE, &time,
863 FILE_COLUMN_STAR_RATING, &star_rating,
866 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
867 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
869 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
870 FILE_COLUMN_EXPANDED, expanded,
872 gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
873 FILE_COLUMN_EXPANDED, expanded,
880 g_free(formatted_with_stars);
883 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
886 gchar *sidecars = NULL;
888 const gchar *time = text_from_time(fd->date);
889 gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
890 const gchar *disabled_grouping;
892 gchar *formatted_with_stars;
893 gboolean expanded = FALSE;
896 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
898 star_rating = convert_rating_to_stars(fd->rating);
905 if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
907 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
910 sidecars = file_data_sc_list_to_string(fd);
912 disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
913 name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
914 size = text_from_size(fd->size);
916 formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
917 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
919 gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
920 FILE_COLUMN_VERSION, fd->version,
921 FILE_COLUMN_THUMB, fd->thumb_pixbuf,
922 FILE_COLUMN_FORMATTED, formatted,
923 FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
924 FILE_COLUMN_SIDECARS, sidecars,
925 FILE_COLUMN_NAME, name,
926 FILE_COLUMN_STAR_RATING, star_rating,
927 FILE_COLUMN_SIZE, size,
928 FILE_COLUMN_DATE, time,
929 #define STORE_SET_IS_SLOW 1
930 #if STORE_SET_IS_SLOW
931 /* this is 3x faster on a directory with 20000 files */
932 FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
933 FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
934 FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
935 FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
936 FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
937 FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
938 FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
939 FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
940 FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
941 FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
942 #if FILEDATA_MARKS_SIZE != 10
943 #error this needs to be updated
946 FILE_COLUMN_COLOR, FALSE, -1);
948 #if !STORE_SET_IS_SLOW
951 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
952 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
961 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
966 gint num_ordered = 0;
967 gint num_prepended = 0;
969 valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
975 FileData *fd = work->data;
976 gboolean done = FALSE;
980 FileData *old_fd = NULL;
981 gint old_version = 0;
985 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
986 FILE_COLUMN_POINTER, &old_fd,
987 FILE_COLUMN_VERSION, &old_version,
997 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
999 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
1001 if (match == 0) g_warning("multiple fd for the same path");
1012 GtkTreeIter new_iter;
1017 gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1022 here should be used gtk_tree_store_append, but this function seems to be O(n)
1023 and it seems to be much faster to add new entries to the beginning and reorder later
1026 gtk_tree_store_prepend(store, &new_iter, parent_iter);
1029 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1030 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1032 if (g_list_find(selected, fd))
1034 /* renamed files - the same fd appears at different position - select it again*/
1035 GtkTreeSelection *selection;
1036 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1037 gtk_tree_selection_select_iter(selection, &new_iter);
1044 file_data_unref(old_fd);
1045 valid = gtk_tree_store_remove(store, &iter);
1050 if (fd->version != old_version || force)
1052 vflist_setup_iter(vf, store, &iter, fd);
1053 vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1056 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1067 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1068 file_data_unref(old_fd);
1070 valid = gtk_tree_store_remove(store, &iter);
1073 /* move the prepended entries to the correct position */
1077 gint num_total = num_prepended + num_ordered;
1078 gint *new_order = g_malloc(num_total * sizeof(gint));
1080 for (i = 0; i < num_total; i++)
1082 if (i < num_ordered)
1083 new_order[i] = num_prepended + i;
1085 new_order[i] = num_total - 1 - i;
1087 gtk_tree_store_reorder(store, parent_iter, new_order);
1093 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1096 GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
1098 GtkTreeStore *store;
1101 if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1102 if (!vf->list) return;
1108 FileData *fd = work->data;
1109 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1114 vf->sort_method = type;
1115 vf->sort_ascend = ascend;
1117 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1119 new_order = g_malloc(i * sizeof(gint));
1125 FileData *fd = work->data;
1126 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
1131 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1132 gtk_tree_store_reorder(store, NULL, new_order);
1135 g_hash_table_destroy(fd_idx_hash);
1139 *-----------------------------------------------------------------------------
1141 *-----------------------------------------------------------------------------
1145 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1150 FileData *fd = work->data;
1153 if (fd->thumb_pixbuf) (*done)++;
1155 if (fd->sidecar_files)
1157 vflist_thumb_progress_count(fd->sidecar_files, count, done);
1163 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1168 FileData *fd = work->data;
1171 if (fd->metadata_in_idle_loaded) (*done)++;
1173 if (fd->sidecar_files)
1175 vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1181 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1183 GtkTreeStore *store;
1186 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1188 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1189 gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1192 FileData *vflist_thumb_next_fd(ViewFile *vf)
1195 FileData *fd = NULL;
1197 /* first check the visible files */
1199 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1201 GtkTreeModel *store;
1203 gboolean valid = TRUE;
1205 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1206 gtk_tree_model_get_iter(store, &iter, tpath);
1207 gtk_tree_path_free(tpath);
1210 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1214 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1216 if (!nfd->thumb_pixbuf) fd = nfd;
1218 valid = gtk_tree_model_iter_next(store, &iter);
1222 /* then find first undone */
1226 GList *work = vf->list;
1229 FileData *fd_p = work->data;
1230 if (!fd_p->thumb_pixbuf)
1234 GList *work2 = fd_p->sidecar_files;
1236 while (work2 && !fd)
1239 if (!fd_p->thumb_pixbuf) fd = fd_p;
1240 work2 = work2->next;
1250 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1252 GtkTreeStore *store;
1259 gchar *formatted_with_stars;
1262 if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1264 star_rating = metadata_read_rating_stars(fd);
1266 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1267 gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1269 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1270 FILE_COLUMN_NAME, &name,
1271 FILE_COLUMN_SIDECARS, &sidecars,
1272 FILE_COLUMN_SIZE, &size,
1273 FILE_COLUMN_DATE, &time,
1274 FILE_COLUMN_EXPANDED, &expanded,
1277 formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1279 gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1280 FILE_COLUMN_EXPANDED, expanded,
1283 g_free(star_rating);
1284 g_free(formatted_with_stars);
1287 FileData *vflist_star_next_fd(ViewFile *vf)
1290 FileData *fd = NULL;
1291 FileData *nfd = NULL;
1293 /* first check the visible files */
1295 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1297 GtkTreeModel *store;
1299 gboolean valid = TRUE;
1301 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1302 gtk_tree_model_get_iter(store, &iter, tpath);
1303 gtk_tree_path_free(tpath);
1306 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1308 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1310 if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1315 valid = gtk_tree_model_iter_next(store, &iter);
1320 vf->stars_filedata = fd;
1322 if (vf->stars_id == 0)
1324 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1329 /* then find first undone */
1333 GList *work = vf->list;
1337 FileData *fd_p = work->data;
1339 if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1353 vf->stars_filedata = fd;
1355 if (vf->stars_id == 0)
1357 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1366 *-----------------------------------------------------------------------------
1368 *-----------------------------------------------------------------------------
1371 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1374 GList *work, *work2;
1379 FileData *list_fd = work->data;
1380 if (list_fd == fd) return p;
1382 work2 = list_fd->sidecar_files;
1385 /** @FIXME return the same index also for sidecars
1386 it is sufficient for next/prev navigation but it should be rewritten
1387 without using indexes at all
1389 FileData *sidecar_fd = work2->data;
1390 if (sidecar_fd == fd) return p;
1391 work2 = work2->next;
1402 *-----------------------------------------------------------------------------
1404 *-----------------------------------------------------------------------------
1407 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1409 GtkTreeModel *store;
1410 GtkTreeSelection *selection;
1413 gboolean found = FALSE;
1415 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1416 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1418 while (!found && work)
1420 GtkTreePath *tpath = work->data;
1424 gtk_tree_model_get_iter(store, &iter, tpath);
1425 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1426 if (fd_n == fd) found = TRUE;
1429 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1435 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1439 fd = vf_index_get_data(vf, row);
1440 return vflist_row_is_selected(vf, fd);
1443 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1445 GtkTreeModel *store;
1446 GtkTreeSelection *selection;
1450 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1451 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1461 GtkTreePath *tpath = work->data;
1465 gtk_tree_model_get_iter(store, &iter, tpath);
1466 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1475 count = g_list_length(slist);
1476 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1482 GList *vflist_selection_get_list(ViewFile *vf)
1484 GtkTreeModel *store;
1485 GtkTreeSelection *selection;
1490 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1491 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1495 GtkTreePath *tpath = work->data;
1499 gtk_tree_model_get_iter(store, &iter, tpath);
1500 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1502 list = g_list_prepend(list, file_data_ref(fd));
1504 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1506 /* unexpanded - add whole group */
1507 GList *work2 = fd->sidecar_files;
1510 FileData *sfd = work2->data;
1511 list = g_list_prepend(list, file_data_ref(sfd));
1512 work2 = work2->next;
1518 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1521 return g_list_reverse(list);
1524 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1526 GtkTreeModel *store;
1527 GtkTreeSelection *selection;
1532 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1533 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1537 GtkTreePath *tpath = work->data;
1541 gtk_tree_model_get_iter(store, &iter, tpath);
1542 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1544 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1548 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1551 return g_list_reverse(list);
1554 void vflist_select_all(ViewFile *vf)
1556 GtkTreeSelection *selection;
1558 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1559 gtk_tree_selection_select_all(selection);
1561 VFLIST(vf)->select_fd = NULL;
1564 void vflist_select_none(ViewFile *vf)
1566 GtkTreeSelection *selection;
1568 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1569 gtk_tree_selection_unselect_all(selection);
1572 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1577 tpath = gtk_tree_model_get_path(store, iter);
1578 result = gtk_tree_path_prev(tpath);
1580 gtk_tree_model_get_iter(store, iter, tpath);
1582 gtk_tree_path_free(tpath);
1587 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1589 if (!gtk_tree_model_get_iter_first(store, iter))
1594 GtkTreeIter next = *iter;
1596 if (gtk_tree_model_iter_next(store, &next))
1605 void vflist_select_invert(ViewFile *vf)
1608 GtkTreeSelection *selection;
1609 GtkTreeModel *store;
1612 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1613 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1615 /* Backward iteration prevents scrolling to the end of the list,
1616 * it scrolls to the first selected row instead. */
1617 valid = tree_model_get_iter_last(store, &iter);
1621 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1624 gtk_tree_selection_unselect_iter(selection, &iter);
1626 gtk_tree_selection_select_iter(selection, &iter);
1628 valid = tree_model_iter_prev(store, &iter);
1632 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1636 if (vflist_find_row(vf, fd, &iter) < 0) return;
1638 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1640 if (!vflist_row_is_selected(vf, fd))
1642 GtkTreeSelection *selection;
1643 GtkTreeModel *store;
1646 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1647 gtk_tree_selection_unselect_all(selection);
1648 gtk_tree_selection_select_iter(selection, &iter);
1649 vflist_move_cursor(vf, &iter);
1651 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1652 tpath = gtk_tree_model_get_path(store, &iter);
1653 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1654 gtk_tree_path_free(tpath);
1658 void vflist_select_list(ViewFile *vf, GList *list)
1671 if (vflist_find_row(vf, fd, &iter) < 0) return;
1672 if (!vflist_row_is_selected(vf, fd))
1674 GtkTreeSelection *selection;
1676 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1677 gtk_tree_selection_select_iter(selection, &iter);
1683 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1686 FileData *fd = NULL;
1688 if (sel_fd->parent) sel_fd = sel_fd->parent;
1697 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1699 if (match >= 0) break;
1702 if (fd) vflist_select_by_fd(vf, fd);
1706 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1708 GtkTreeModel *store;
1710 GtkTreeSelection *selection;
1714 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1716 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1717 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1719 valid = gtk_tree_model_get_iter_first(store, &iter);
1723 gboolean mark_val, selected;
1724 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1726 mark_val = file_data_get_mark(fd, n);
1727 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1731 case MTS_MODE_SET: selected = mark_val;
1733 case MTS_MODE_OR: selected = mark_val || selected;
1735 case MTS_MODE_AND: selected = mark_val && selected;
1737 case MTS_MODE_MINUS: selected = !mark_val && selected;
1742 gtk_tree_selection_select_iter(selection, &iter);
1744 gtk_tree_selection_unselect_iter(selection, &iter);
1746 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1750 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1752 GtkTreeModel *store;
1753 GtkTreeSelection *selection;
1758 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1760 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1761 slist = gtk_tree_selection_get_selected_rows(selection, &store);
1765 GtkTreePath *tpath = work->data;
1769 gtk_tree_model_get_iter(store, &iter, tpath);
1770 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1772 /* the change has a very limited range and the standard notification would trigger
1773 complete re-read of the directory - try to do only minimal update instead */
1774 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1778 case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1780 case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1782 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1786 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1788 vf_refresh_idle(vf);
1792 /* mark functions can have various side effects - update all columns to be sure */
1793 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1794 /* mark functions can change sidecars too */
1795 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1799 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1803 g_list_foreach(slist, (GFunc)tree_path_free_wrapper, NULL);
1808 *-----------------------------------------------------------------------------
1810 *-----------------------------------------------------------------------------
1813 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1815 GtkTreeViewColumn *column;
1816 GtkCellRenderer *cell;
1819 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1820 if (!column) return;
1822 gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1824 list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1829 g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1830 gtk_tree_view_column_set_visible(column, thumb);
1832 if (options->show_star_rating)
1834 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1835 if (!column) return;
1836 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1837 gtk_tree_view_column_set_visible(column, TRUE);
1839 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1840 if (!column) return;
1841 gtk_tree_view_column_set_visible(column, FALSE);
1845 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1846 if (!column) return;
1847 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1848 gtk_tree_view_column_set_visible(column, TRUE);
1850 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1851 if (!column) return;
1852 gtk_tree_view_column_set_visible(column, FALSE);
1855 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1856 if (!column) return;
1857 gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1859 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1860 if (!column) return;
1861 gtk_tree_view_column_set_visible(column, !multiline);
1863 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1864 if (!column) return;
1865 gtk_tree_view_column_set_visible(column, !multiline);
1868 static gboolean vflist_is_multiline(ViewFile *vf)
1870 return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1874 static void vflist_populate_view(ViewFile *vf, gboolean force)
1876 GtkTreeStore *store;
1879 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1886 vflist_store_clear(vf, FALSE);
1891 vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1893 selected = vflist_selection_get_list(vf);
1895 vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1897 if (selected && vflist_selection_count(vf, NULL) == 0)
1899 /* all selected files disappeared */
1900 vflist_select_closest(vf, selected->data);
1903 filelist_free(selected);
1906 vf_thumb_update(vf);
1910 gboolean vflist_refresh(ViewFile *vf)
1913 gboolean ret = TRUE;
1915 old_list = vf->list;
1918 DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1921 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1923 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1925 if (vf->marks_enabled)
1927 // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1928 // each time a mark is changed.
1929 file_data_lock_list(vf->list);
1933 /** @FIXME only do this when needed (aka when we just switched from */
1934 /** @FIXME marks-enabled to marks-disabled) */
1935 file_data_unlock_list(vf->list);
1938 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1939 vf->list = g_list_first(vf->list);
1940 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1942 vf->list = g_list_first(vf->list);
1943 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1945 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1947 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1948 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1951 DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1953 vflist_populate_view(vf, FALSE);
1955 DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1957 filelist_free(old_list);
1958 DEBUG_1("%s vflist_refresh: done", get_exec_time());
1965 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1967 #define CELL_HEIGHT_OVERRIDE 512
1969 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1973 spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1974 if (spec && G_IS_PARAM_SPEC_INT(spec))
1976 GParamSpecInt *spec_int;
1978 spec_int = G_PARAM_SPEC_INT(spec);
1979 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1983 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1985 static GdkColor color;
1986 static GtkWidget *done = NULL;
1992 style = gtk_widget_get_style(widget);
1993 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1994 shift_color(&color, -1, 0);
2001 static void vflist_listview_color_cb(GtkTreeViewColumn *UNUSED(tree_column), GtkCellRenderer *cell,
2002 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2004 ViewFile *vf = data;
2007 gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
2008 g_object_set(G_OBJECT(cell),
2009 "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
2010 "cell-background-set", set, NULL);
2013 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2015 GtkTreeViewColumn *column;
2016 GtkCellRenderer *renderer;
2018 column = gtk_tree_view_column_new();
2019 gtk_tree_view_column_set_title(column, title);
2020 gtk_tree_view_column_set_min_width(column, 4);
2024 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2025 renderer = gtk_cell_renderer_text_new();
2028 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2030 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2031 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2033 gtk_tree_view_column_set_expand(column, TRUE);
2037 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2038 renderer = gtk_cell_renderer_pixbuf_new();
2039 cell_renderer_height_override(renderer);
2040 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2041 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2044 gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
2045 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2046 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2048 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2051 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2053 ViewFile *vf = data;
2054 GtkTreeStore *store;
2055 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2061 store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2062 if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2065 col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2067 g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2069 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2072 /* the change has a very limited range and the standard notification would trigger
2073 complete re-read of the directory - try to do only minimal update instead */
2074 file_data_unregister_notify_func(vf_notify_cb, vf);
2075 file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2076 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2078 vf_refresh_idle(vf);
2082 /* mark functions can have various side effects - update all columns to be sure */
2083 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2084 /* mark functions can change sidecars too */
2085 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
2087 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2089 gtk_tree_path_free(path);
2092 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2094 GtkTreeViewColumn *column;
2095 GtkCellRenderer *renderer;
2097 renderer = gtk_cell_renderer_toggle_new();
2098 column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2100 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2101 g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2102 g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2104 gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2105 gtk_tree_view_column_set_fixed_width(column, 22);
2106 gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2109 g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2113 *-----------------------------------------------------------------------------
2115 *-----------------------------------------------------------------------------
2118 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2121 if (!dir_fd) return FALSE;
2122 if (vf->dir_fd == dir_fd) return TRUE;
2124 file_data_unref(vf->dir_fd);
2125 vf->dir_fd = file_data_ref(dir_fd);
2127 /* force complete reload */
2128 vflist_store_clear(vf, TRUE);
2130 filelist_free(vf->list);
2133 ret = vf_refresh(vf);
2134 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2138 void vflist_destroy_cb(GtkWidget *UNUSED(widget), gpointer data)
2140 ViewFile *vf = data;
2142 file_data_unregister_notify_func(vf_notify_cb, vf);
2144 vflist_select_idle_cancel(vf);
2145 vf_refresh_idle_cancel(vf);
2149 filelist_free(vf->list);
2152 ViewFile *vflist_new(ViewFile *vf, FileData *UNUSED(dir_fd))
2154 GtkTreeStore *store;
2155 GtkTreeSelection *selection;
2156 GType flist_types[FILE_COLUMN_COUNT];
2160 vf->info = g_new0(ViewFileInfoList, 1);
2162 flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2163 flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2164 flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2165 flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2166 flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2167 flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2168 flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2169 flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2170 flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2171 flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2172 flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2173 flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2174 for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2175 flist_types[i] = G_TYPE_BOOLEAN;
2177 store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2179 vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2180 g_object_unref(store);
2182 g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2183 G_CALLBACK(vflist_expand_cb), vf);
2185 g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2186 G_CALLBACK(vflist_collapse_cb), vf);
2188 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2189 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2190 gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
2192 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2193 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2195 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2199 for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2201 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2202 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2206 vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2207 g_assert(column == FILE_VIEW_COLUMN_THUMB);
2210 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2211 g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2214 vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2215 g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2218 vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2219 g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2222 vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2223 g_assert(column == FILE_VIEW_COLUMN_SIZE);
2226 vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2227 g_assert(column == FILE_VIEW_COLUMN_DATE);
2230 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2234 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2236 if (VFLIST(vf)->thumbs_enabled == enable) return;
2238 VFLIST(vf)->thumbs_enabled = enable;
2240 /* vflist_populate_view is better than vf_refresh:
2241 - no need to re-read the directory
2242 - force update because the formatted string has changed
2246 vflist_populate_view(vf, TRUE);
2247 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2251 void vflist_marks_set(ViewFile *vf, gboolean enable)
2253 GList *columns, *work;
2255 columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2260 GtkTreeViewColumn *column = work->data;
2261 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2264 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2265 gtk_tree_view_column_set_visible(column, enable);
2270 // Previously disabled, which means that vf->list is complete
2271 file_data_lock_list(vf->list);
2275 // Previously enabled, which means that vf->list is incomplete
2278 g_list_free(columns);
2281 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */