exiv2.cc \
filecache.c \
filecache.h \
+ filecluster.c \
+ filecluster.h \
filedata.c \
filedata.h \
filefilter.c \
--- /dev/null
+/*
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "filecluster.h"
+
+#include "filedata.h"
+
+static gboolean check_list_contains_sublist(GList *haystack, GList *needle)
+{
+ // TODO(xsdg): Optimize this! Sort, then scan?
+ GList *h_work, *n_work;
+ for (n_work = needle; n_work; n_work = n_work->next)
+ {
+ gboolean found = FALSE;
+ for (h_work = haystack; h_work; h_work = h_work->next)
+ {
+ if (n_work == h_work)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean filecluster_fd_equal(gconstpointer ptr_a, gconstpointer ptr_b)
+{
+ // TODO(xsdg): Is there anything we can/should do to validate inputs?
+ FileData *fd_a = (FileData *)ptr_a;
+ FileData *fd_b = (FileData *)ptr_b;
+ return !filelist_sort_compare_filedata(fd_a, fd_b);
+}
+
+// TODO(xsdg): Move this into filedata.h
+static guint filecluster_fd_hash(gconstpointer ptr)
+{
+ if (!ptr) return 1;
+ FileData *fd = (FileData *)ptr;
+ return 7 * g_str_hash(fd->original_path);
+}
+
+FileClusterList *fileclusterlist_new()
+{
+ FileClusterList *fcl = g_new0(FileClusterList, 1);
+ fcl->clusters = g_hash_table_new(&filecluster_fd_hash, &filecluster_fd_equal);
+ return fcl;
+}
+
+FileCluster *filecluster_new()
+{
+ FileCluster *fc = g_new0(FileCluster, 1);
+ fc->show_children = FALSE;
+ return fc;
+}
+
+void fileclusterlist_free(FileClusterList *fcl)
+{
+ // TODO(xsdg): don't leak stuff
+ // if (fcl->fd_list) g_list_free_full(fcl->fd_list, (GDestroyNotify)&filecluster_free);
+ g_hash_table_destroy(fcl->clusters);
+ g_free(fcl);
+}
+
+void filecluster_free(FileCluster *fc)
+{
+ filelist_free(fc->items);
+ g_free(fc);
+}
+
+gboolean filecluster_toggle_show_children(FileCluster *fc)
+{
+ fc->show_children = !fc->show_children;
+ return fc->show_children;
+}
+
+FileCluster *fileclusterlist_create_cluster(FileClusterList *fcl, GList *fd_items)
+{
+ GList *work;
+
+ // Check preconditions.
+ if (!fd_items) return NULL;
+ for (work = fd_items; work; work = work->next)
+ {
+ FileData *fd = work->data;
+ if (g_hash_table_contains(fcl->clusters, fd))
+ {
+ // TODO(xsdg): Show this warning in the UI.
+ g_warning("Tried to create a cluster with a file that is already clustered.");
+ return NULL;
+ }
+ }
+
+ FileCluster *new_fc = filecluster_new();
+ new_fc->items = filelist_copy(fd_items);
+ new_fc->head = new_fc->items;
+
+ for (GList *item = new_fc->items; item; item = item->next)
+ {
+ FileData *fd = item->data;
+ g_hash_table_insert(fcl->clusters, fd, new_fc);
+ }
+
+ return new_fc;
+}
+
+gboolean filecluster_has_head(FileCluster *fc, FileData *fd)
+{
+ if (!fd) return FALSE;
+ return filecluster_fd_equal(fc->head->data, fd);
+}
+
+gboolean filecluster_has_child(FileCluster *fc, FileData *fd)
+{
+ if (!fd) return FALSE;
+ return !filecluster_fd_equal(fc->head->data, fd);
+}
+
+gboolean fileclusterlist_has_head(FileClusterList *fcl, FileData *fd)
+{
+ FileCluster *fc = g_hash_table_lookup(fcl->clusters, fd);
+ if (!fc) return FALSE;
+ return filecluster_has_head(fc, fd);
+}
+
+gboolean fileclusterlist_has_child(FileClusterList *fcl, FileData *fd)
+{
+ FileCluster *fc = g_hash_table_lookup(fcl->clusters, fd);
+ if (!fc) return FALSE;
+ return filecluster_has_child(fc, fd);
+}
+
+static gboolean fileclusterlist_should_hide(FileClusterList *fcl, FileData *fd)
+{
+ FileCluster *fc = g_hash_table_lookup(fcl->clusters, fd);
+ if (!fc) return FALSE;
+ // Only difference vs. fileclusterlist_has_child. Basically, if the node is a child, but
+ // we're showing children, then don't hide.
+ if (fc->show_children) return FALSE;
+ return filecluster_has_child(fc, fd);
+}
+
+// TODO(xsdg): pick a better name for this function
+GList *fileclusterlist_next_non_child(FileClusterList *fcl, GList *list)
+{
+ // Check for no-ops
+ if (!list || !g_hash_table_size(fcl->clusters)) return list;
+
+ // Clusters are being used, so we have to actually check things.
+ for (; list; list = list->next)
+ {
+ FileData *fd = list->data;
+ if (!fileclusterlist_has_child(fcl, fd)) return list;
+ }
+
+ return NULL;
+}
+
+GList *fileclusterlist_remove_children_from_list(FileClusterList *fcl, GList *list)
+{
+ GList *work = list;
+
+ while (work)
+ {
+ FileData *fd = work->data;
+ GList *link = work;
+ // Advance early in case link needs to be removed/freed.
+ work = work->next;
+
+ if (fileclusterlist_should_hide(fcl, fd))
+ {
+ list = g_list_remove_link(list, link);
+ file_data_unref(fd);
+ g_list_free(link);
+ }
+ }
+
+ return list;
+}
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- /dev/null
+/*
+ * Copyright (C) 2008 - 2016 The Geeqie Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FILECLUSTER_H
+#define FILECLUSTER_H
+
+#include "main.h"
+
+// FileCluster methods.
+FileCluster *filecluster_new(); // internal?
+void filecluster_free(FileCluster *fc);
+
+gboolean filecluster_toggle_show_children(FileCluster *fc);
+gboolean filecluster_has_head(FileCluster *fc, FileData *fd);
+gboolean filecluster_has_child(FileCluster *fc, FileData *fd);
+
+
+// FileClusterList methods.
+
+FileClusterList *fileclusterlist_new();
+void fileclusterlist_free(FileClusterList *fcl);
+
+// Creates a cluster with items that must already be in the cluster list. Will fail (and make no
+// changes) if any of the specified items isn't in the list, or if any of the items is already
+// part of a different cluster.
+FileCluster *fileclusterlist_create_cluster(FileClusterList *fcl, GList *fd_items);
+
+gboolean fileclusterlist_has_head(FileClusterList *fcl, FileData *fd);
+gboolean fileclusterlist_has_child(FileClusterList *fcl, FileData *fd);
+
+GList *fileclusterlist_next_non_child(FileClusterList *fcl, GList *list);
+GList *fileclusterlist_remove_children_from_list(FileClusterList *fcl, GList *list);
+
+#endif // FILECLUSTER_H
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
SELECTION_NONE = 0,
SELECTION_SELECTED = 1 << 0,
SELECTION_PRELIGHT = 1 << 1,
- SELECTION_FOCUS = 1 << 2
+ SELECTION_FOCUS = 1 << 2,
+ SELECTION_CLUSTER_HEAD = 1 << 3,
+ SELECTION_CLUSTER_CHILD = 1 << 4
} SelectionType;
typedef struct _ImageLoader ImageLoader;
typedef struct _ImageWindow ImageWindow;
+typedef struct _FileCluster FileCluster;
+typedef struct _FileClusterList FileClusterList;
+
typedef struct _FileData FileData;
typedef struct _FileDataChangeInfo FileDataChangeInfo;
gboolean mouse_wheel_mode;
};
+// A FileCluster is a GList with HashTable access to each node (to perform contains() checks quickly).
+struct _FileCluster
+{
+ GList *head;
+ GList *items;
+ gboolean show_children;
+};
+
+struct _FileClusterList
+{
+ // A map from any clustered FileData to the FileCluster object that describes the cluster.
+ GHashTable *clusters;
+};
+
#define FILEDATA_MARKS_SIZE 6
struct _FileDataChangeInfo {
FileData *dir_fd;
GList *list;
+ FileClusterList *cluster_list;
SortType sort_method;
gboolean sort_ascend;
void shift_color(GdkColor *src, gshort val, gint direction)
{
gshort cs;
+ static gshort COLOR_MAX = 0xffff;
if (val == -1)
{
{
val = CLAMP(val, 1, 100);
}
- cs = 0xffff / 100 * val;
+ cs = COLOR_MAX / 100 * val;
/* up or down ? */
if (direction < 0 ||
- (direction == 0 &&((gint)src->red + (gint)src->green + (gint)src->blue) / 3 > 0xffff / 2))
+ (direction == 0 &&((gint)src->red + (gint)src->green + (gint)src->blue) / 3 > COLOR_MAX / 2))
{
src->red = MAX(0 , src->red - cs);
src->green = MAX(0 , src->green - cs);
}
else
{
- src->red = MIN(0xffff, src->red + cs);
- src->green = MIN(0xffff, src->green + cs);
- src->blue = MIN(0xffff, src->blue + cs);
+ src->red = MIN(COLOR_MAX, src->red + cs);
+ src->green = MIN(COLOR_MAX, src->green + cs);
+ src->blue = MIN(COLOR_MAX, src->blue + cs);
}
}
#include "collect.h"
#include "collect-table.h"
#include "editors.h"
+#include "filecluster.h"
#include "layout.h"
#include "menu.h"
#include "thumb.h"
gtk_widget_destroy(vf->popup);
}
+ fileclusterlist_free(vf->cluster_list);
file_data_unref(vf->dir_fd);
g_free(vf->info);
g_free(vf);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vf->scrolled),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ vf->cluster_list = fileclusterlist_new();
vf->filter = vf_marks_filter_init(vf);
vf->widget = gtk_vbox_new(FALSE, 0);
#include "dnd.h"
#include "editors.h"
#include "img-view.h"
+#include "filecluster.h"
#include "filedata.h"
#include "layout.h"
#include "layout_image.h"
vf->popup = vf_pop_menu(vf);
gtk_menu_popup(GTK_MENU(vf->popup), NULL, NULL, vfi_menu_position_cb, vf, 0, GDK_CURRENT_TIME);
break;
+ case GDK_KEY_Insert:
+ // DO NOT SUBMIT
+ // TODO(xsdg): make an actual UX for this.
+ g_warning("Starting a cluster!");
+ fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, NULL);
+ if (fd)
+ {
+ // Make a cluster out of the entire selection
+ if (VFICON(vf)->selection && VFICON(vf)->selection->next)
+ {
+ FileCluster *fc;
+ // At least two items selected; go for it.
+ g_warning("Had requisite number of selection items; going for it!");
+ fc = fileclusterlist_create_cluster(vf->cluster_list, VFICON(vf)->selection);
+ if (fc)
+ {
+ vficon_selection_add(vf, VFICON(vf)->selection->data, SELECTION_CLUSTER_HEAD, NULL);
+ vf_refresh(vf);
+ }
+ }
+ else
+ {
+ if (VFICON(vf)->selection)
+ {
+ g_warning("Only one item selected; need at least two.");
+ }
+ else
+ {
+ g_warning("No items selected; need at least two.");
+ }
+ }
+ }
+ break;
+ case GDK_KEY_F2:
+ g_warning("Flipping show_children!");
+ fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, NULL);
+ if (fd)
+ {
+ FileCluster *fc = g_hash_table_lookup(vf->cluster_list->clusters, fd);
+ if (fc)
+ {
+ if (filecluster_toggle_show_children(fc))
+ {
+ for (GList *work = fc->items; work; work = work->next)
+ {
+ // TODO(xsdg): This is broken because the FileData pointer stored in the
+ // cluster is different from the one just added to vf->list, even though
+ // they are equivalent.
+ FileData *fd = work->data;
+ if (work == fc->head) continue;
+ vficon_selection_add(vf, fd, SELECTION_CLUSTER_CHILD, NULL);
+ }
+ }
+ vf_refresh(vf);
+ }
+ }
+ break;
default:
stop_signal = FALSE;
break;
{
ret = filelist_read(vf->dir_fd, &new_filelist, NULL);
new_filelist = file_data_filter_marks_list(new_filelist, vf_marks_get_filter(vf));
+ new_filelist = fileclusterlist_remove_children_from_list(vf->cluster_list, new_filelist);
}
- vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend); /* the list might not be sorted if there were renames */
+ /* the list might not be sorted if there were renames */
+ vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
new_filelist = filelist_sort(new_filelist, vf->sort_method, vf->sort_ascend);
if (VFICON(vf)->selection)
memcpy(&color_fg, &style->text[state], sizeof(color_fg));
memcpy(&color_bg, &style->base[state], sizeof(color_bg));
+ if (fd->selected & SELECTION_CLUSTER_HEAD)
+ {
+ // TODO(xsdg): Cluster coloration should be part of the style.
+ color_bg.blue = 0x4000;
+ color_bg.green = 0x4000;
+ color_bg.red = 0xFFFF;
+ }
+ else if (fd->selected & SELECTION_CLUSTER_CHILD)
+ {
+ // TODO(xsdg): Cluster coloration should be part of the style.
+ color_bg.blue = 0x8000;
+ color_bg.green = 0x8000;
+ color_bg.red = 0xFFFF;
+ }
+
if (fd->selected & SELECTION_PRELIGHT)
{
shift_color(&color_bg, -1, 0);
#include "dnd.h"
#include "editors.h"
#include "img-view.h"
+#include "filecluster.h"
#include "layout.h"
#include "layout_image.h"
#include "menu.h"
ViewFile *vf = data;
GtkTreePath *tpath;
+ // DO NOT SUBMIT
+ // TODO(xsdg): these key combos should be handled by the standard, configurable mechanism.
+
+ if (event->keyval == GDK_KEY_Insert || event->keyval == GDK_KEY_F2)
+ {
+ // First off, get the selected FDs
+ GList *selected_fds = NULL;
+ {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
+ if (event->keyval == GDK_KEY_Insert)
+ {
+ if (gtk_tree_selection_count_selected_rows(selection) < 2)
+ {
+ g_warning("Need at least two items selected to create a cluster.");
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (gtk_tree_selection_count_selected_rows(selection) < 1)
+ {
+ g_warning("Must have a node selected to flip show_children.");
+ return TRUE;
+ }
+ }
+
+ // List of GtkTreePath
+ GList *selected_rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+ GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
+ GtkTreeIter iter;
+ for (GList *work = selected_rows; work; work = work->next)
+ {
+ FileData *fd;
+ GtkTreePath *select_path = work->data;
+ gtk_tree_model_get_iter(store, &iter, select_path);
+ gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
+ selected_fds = g_list_prepend(selected_fds, file_data_ref(fd));
+ }
+
+ selected_fds = g_list_reverse(selected_fds);
+ g_list_free_full(selected_rows, (GDestroyNotify)gtk_tree_path_free);
+ }
+
+ if (event->keyval == GDK_KEY_Insert)
+ {
+ g_warning("Starting a cluster!");
+ FileCluster *fc = fileclusterlist_create_cluster(vf->cluster_list, selected_fds);
+ if (fc)
+ {
+ // TODO(xsdg): mark as in a cluster somehow?
+ vf_refresh(vf);
+ }
+ }
+ else if (event->keyval == GDK_KEY_F2)
+ {
+ FileData *fd = selected_fds->data;
+ if (fd)
+ {
+ g_warning("Flipping show_children!");
+ FileCluster *fc = g_hash_table_lookup(vf->cluster_list->clusters, fd);
+ if (fc)
+ {
+ filecluster_toggle_show_children(fc);
+ vf_refresh(vf);
+ }
+ }
+ }
+
+ return TRUE; // Handled event; stop propagating.
+ }
+
if (event->keyval != GDK_KEY_Menu) return FALSE;
gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, NULL);
else
{
if (parent_iter)
- match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
+ {
+ /* always sort sidecars by name */
+ match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE);
+ }
else
+ {
match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
+ }
if (match == 0) g_warning("multiple fd for the same path");
}
}
vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
+ vf->list = fileclusterlist_remove_children_from_list(vf->cluster_list, vf->list);
file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
DEBUG_1("%s vflist_refresh: sort", get_exec_time());
{
ViewFile *vf = data;
gboolean set;
+ FileData *fd;
gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
+ gtk_tree_model_get(tree_model, iter, FILE_COLUMN_POINTER, &fd, -1);
+ // TODO(xsdg): optimize!
+ if (fd)
+ {
+ FileCluster *fc = g_hash_table_lookup(vf->cluster_list->clusters, fd);
+ if (fc)
+ {
+ if (filecluster_has_head(fc, fd))
+ {
+ GdkColor *color_bg = g_new0(GdkColor, 1);
+ color_bg->blue = 0x4000;
+ color_bg->green = 0x4000;
+ color_bg->red = 0xFFFF;
+
+ g_object_set(G_OBJECT(cell),
+ "cell-background-gdk", color_bg,
+ "cell-background-set", TRUE, NULL);
+ return;
+ }
+ else if (filecluster_has_child(fc, fd))
+ {
+ GdkColor *color_bg = g_new0(GdkColor, 1);
+ color_bg->blue = 0x8000;
+ color_bg->green = 0x8000;
+ color_bg->red = 0xFFFF;
+
+ g_object_set(G_OBJECT(cell),
+ "cell-background-gdk", color_bg,
+ "cell-background-set", TRUE, NULL);
+ return;
+ }
+ }
+ }
+
g_object_set(G_OBJECT(cell),
"cell-background-gdk", vflist_listview_color_shifted(vf->listview),
"cell-background-set", set, NULL);