changeset 2768:7d275582e37d

Fix #612: Pan view image class filtering https://github.com/BestImageViewer/geeqie/issues/612
author Tomasz Golinski <tomaszg@math.uwb.edu.pl>
date Thu, 07 Jun 2018 11:44:36 +0100
parents 699c2ad6a287
children ed8cc78cb9dd
files src/pan-view/pan-calendar.c src/pan-view/pan-folder.c src/pan-view/pan-grid.c src/pan-view/pan-timeline.c src/pan-view/pan-view-filter.c src/pan-view/pan-view-filter.h src/pan-view/pan-view.c src/preferences.c src/search.c src/typedefs.h
diffstat 10 files changed, 126 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/src/pan-view/pan-calendar.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-calendar.c	Thu Jun 07 11:44:36 2018 +0100
@@ -204,7 +204,7 @@
 	gint day_of_week;
 
 	list = pan_list_tree(dir_fd, SORT_NONE, TRUE, pw->ignore_symlinks);
-	pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
+	pan_filter_fd_list(&list, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
 
 	if (pw->cache_list && pw->exif_date_enable)
 		{
--- a/src/pan-view/pan-folder.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-folder.c	Thu Jun 07 11:44:36 2018 +0100
@@ -243,7 +243,7 @@
 	f = filelist_sort(f, SORT_NAME, TRUE);
 	d = filelist_sort(d, SORT_NAME, TRUE);
 
-	pan_filter_fd_list(&f, pw->filter_ui->filter_elements);
+	pan_filter_fd_list(&f, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
 
 	pi_box = pan_item_text_new(pw, x, y, dir_fd->path, PAN_TEXT_ATTR_NONE,
 				   PAN_TEXT_BORDER_SIZE,
@@ -389,7 +389,7 @@
 	f = filelist_sort(f, SORT_NAME, TRUE);
 	d = filelist_sort(d, SORT_NAME, TRUE);
 
-	pan_filter_fd_list(&f, pw->filter_ui->filter_elements);
+	pan_filter_fd_list(&f, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
 
 	*x = PAN_BOX_BORDER + ((*level) * MAX(PAN_BOX_BORDER, PAN_THUMB_GAP));
 
--- a/src/pan-view/pan-grid.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-grid.c	Thu Jun 07 11:44:36 2018 +0100
@@ -36,7 +36,7 @@
 	gint next_y;
 
 	list = pan_list_tree(dir_fd, SORT_NAME, TRUE, pw->ignore_symlinks);
-	pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
+	pan_filter_fd_list(&list, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
 
 	grid_size = (gint)sqrt((gdouble)g_list_length(list));
 	if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE)
--- a/src/pan-view/pan-timeline.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-timeline.c	Thu Jun 07 11:44:36 2018 +0100
@@ -42,7 +42,7 @@
 	gint y_height;
 
 	list = pan_list_tree(dir_fd, SORT_NONE, TRUE, pw->ignore_symlinks);
-	pan_filter_fd_list(&list, pw->filter_ui->filter_elements);
+	pan_filter_fd_list(&list, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
 
 	if (pw->cache_list && pw->exif_date_enable)
 		{
--- a/src/pan-view/pan-view-filter.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-view-filter.c	Thu Jun 07 11:44:36 2018 +0100
@@ -35,6 +35,7 @@
 	PanViewFilterUi *ui = g_new0(PanViewFilterUi, 1);
 	GtkWidget *combo;
 	GtkWidget *hbox;
+	gint i;
 
 	/* Since we're using the GHashTable as a HashSet (in which key and value pointers
 	 * are always identical), specifying key _and_ value destructor callbacks will
@@ -70,7 +71,7 @@
 	pref_spacer(ui->filter_box, 0);
 	pref_label_new(ui->filter_box, _("Keyword Filter:"));
 
-	gtk_box_pack_start(GTK_BOX(ui->filter_box), ui->filter_mode_combo, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(ui->filter_box), ui->filter_mode_combo, FALSE, FALSE, 0);
 	gtk_widget_show(ui->filter_mode_combo);
 
 	hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
@@ -106,6 +107,23 @@
 	g_signal_connect(G_OBJECT(ui->filter_button), "clicked",
 			 G_CALLBACK(pan_filter_toggle_cb), pw);
 
+	// Add check buttons for filtering by image class
+	for (i = 0; i < FILE_FORMAT_CLASSES; i++)
+	{
+		ui->filter_check_buttons[i] = gtk_check_button_new_with_label(_(format_class_list[i]));
+		gtk_box_pack_start(GTK_BOX(ui->filter_box), ui->filter_check_buttons[i], FALSE, FALSE, 0);
+		gtk_widget_show(ui->filter_check_buttons[i]);
+	}
+
+	gtk_toggle_button_set_active((GtkToggleButton *)ui->filter_check_buttons[FORMAT_CLASS_IMAGE], TRUE);
+	gtk_toggle_button_set_active((GtkToggleButton *)ui->filter_check_buttons[FORMAT_CLASS_RAWIMAGE], TRUE);
+	gtk_toggle_button_set_active((GtkToggleButton *)ui->filter_check_buttons[FORMAT_CLASS_VIDEO], TRUE);
+	ui->filter_classes = (1 << FORMAT_CLASS_IMAGE) | (1 << FORMAT_CLASS_RAWIMAGE) | (1 << FORMAT_CLASS_VIDEO);
+
+	// Connecting the signal before setting the state causes segfault as pw is not yet prepared
+	for (i = 0; i < FILE_FORMAT_CLASSES; i++)
+		g_signal_connect((GtkToggleButton *)(ui->filter_check_buttons[i]), "toggled", G_CALLBACK(pan_filter_toggle_button_cb), pw);
+
 	return ui;
 }
 
@@ -248,6 +266,23 @@
 		}
 }
 
+void pan_filter_toggle_button_cb(GtkWidget *button, gpointer data)
+{
+	PanWindow *pw = data;
+	PanViewFilterUi *ui = pw->filter_ui;
+
+	gint old_classes = ui->filter_classes;
+	ui->filter_classes = 0;
+
+	for (gint i = 0; i < FILE_FORMAT_CLASSES; i++)
+	{
+		ui->filter_classes |= gtk_toggle_button_get_active((GtkToggleButton *)ui->filter_check_buttons[i]) ? 1 << i : 0;
+	}
+
+	if (ui->filter_classes != old_classes) 
+		pan_layout_update(pw);
+}
+
 static gboolean pan_view_list_contains_kw_pattern(GList *haystack, PanViewFilterElement *filter, gchar **found_kw)
 {
 	if (filter->kw_regex)
@@ -275,16 +310,17 @@
 		}
 }
 
-gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements)
+gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements, gint filter_classes)
 {
 	GList *work;
 	gboolean modified = FALSE;
 	GHashTable *seen_kw_table = NULL;
 
-	if (!fd_list || !*fd_list || !filter_elements) return modified;
+	if (!fd_list || !*fd_list) return modified;
 
 	// seen_kw_table is only valid in this scope, so don't take ownership of any strings.
-	seen_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+	if (filter_elements)
+		seen_kw_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
 
 	work = *fd_list;
 	while (work)
@@ -293,54 +329,61 @@
 		GList *last_work = work;
 		work = work->next;
 
-		// TODO(xsdg): OPTIMIZATION Do the search inside of metadata.c to avoid a
-		// bunch of string list copies.
-		GList *img_keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
-
-		// TODO(xsdg): OPTIMIZATION Determine a heuristic for when to linear-search the
-		// keywords list, and when to build a hash table for the image's keywords.
 		gboolean should_reject = FALSE;
 		gchar *group_kw = NULL;
-		GList *filter_element = filter_elements;
-		while (filter_element)
+
+		if (!((1 << fd -> format_class) & filter_classes))
+			{
+			should_reject = TRUE;
+			}
+		else if (filter_elements)
 			{
-			PanViewFilterElement *filter = filter_element->data;
-			filter_element = filter_element->next;
-			gchar *found_kw = NULL;
-			gboolean has_kw = pan_view_list_contains_kw_pattern(img_keywords, filter, &found_kw);
+			// TODO(xsdg): OPTIMIZATION Do the search inside of metadata.c to avoid a
+			// bunch of string list copies.
+			GList *img_keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
 
-			switch (filter->mode)
+			// TODO(xsdg): OPTIMIZATION Determine a heuristic for when to linear-search the
+			// keywords list, and when to build a hash table for the image's keywords.
+			GList *filter_element = filter_elements;
+
+			while (filter_element)
 				{
-				case PAN_VIEW_FILTER_REQUIRE:
-					should_reject |= !has_kw;
-					break;
-				case PAN_VIEW_FILTER_EXCLUDE:
-					should_reject |= has_kw;
-					break;
-				case PAN_VIEW_FILTER_INCLUDE:
-					if (has_kw) should_reject = FALSE;
-					break;
-				case PAN_VIEW_FILTER_GROUP:
-					if (has_kw)
-						{
-						if (g_hash_table_contains(seen_kw_table, found_kw))
+				PanViewFilterElement *filter = filter_element->data;
+				filter_element = filter_element->next;
+				gchar *found_kw = NULL;
+				gboolean has_kw = pan_view_list_contains_kw_pattern(img_keywords, filter, &found_kw);
+
+				switch (filter->mode)
+					{
+					case PAN_VIEW_FILTER_REQUIRE:
+						should_reject |= !has_kw;
+						break;
+					case PAN_VIEW_FILTER_EXCLUDE:
+						should_reject |= has_kw;
+						break;
+					case PAN_VIEW_FILTER_INCLUDE:
+						if (has_kw) should_reject = FALSE;
+						break;
+					case PAN_VIEW_FILTER_GROUP:
+						if (has_kw)
 							{
-							should_reject = TRUE;
-							}
-						else if (group_kw == NULL)
-							{
-							group_kw = found_kw;
+							if (g_hash_table_contains(seen_kw_table, found_kw))
+								{
+								should_reject = TRUE;
+								}
+							else if (group_kw == NULL)
+								{
+								group_kw = found_kw;
+								}
 							}
-						}
-					break;
+						break;
+					}
 				}
+			string_list_free(img_keywords);
+			if (!should_reject && group_kw != NULL) g_hash_table_add(seen_kw_table, group_kw);
+			group_kw = NULL;  // group_kw references an item from img_keywords.
 			}
 
-		if (!should_reject && group_kw != NULL) g_hash_table_add(seen_kw_table, group_kw);
-
-		group_kw = NULL;  // group_kw references an item from img_keywords.
-		string_list_free(img_keywords);
-
 		if (should_reject)
 			{
 			*fd_list = g_list_delete_link(*fd_list, last_work);
@@ -348,6 +391,8 @@
 			}
 		}
 
-	g_hash_table_destroy(seen_kw_table);
+	if (filter_elements)
+		g_hash_table_destroy(seen_kw_table);
+
 	return modified;
 }
--- a/src/pan-view/pan-view-filter.h	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-view-filter.h	Thu Jun 07 11:44:36 2018 +0100
@@ -55,15 +55,18 @@
 	GtkWidget *filter_button;
 	GtkWidget *filter_button_arrow;
 	GtkWidget *filter_kw_hbox;
+	GtkWidget *filter_check_buttons[FILE_FORMAT_CLASSES];
 	GtkListStore *filter_mode_model;
 	GtkWidget *filter_mode_combo;
 	GList *filter_elements;  // List of PanViewFilterElement.
+	gint filter_classes;
 };
 
 void pan_filter_toggle_visible(PanWindow *pw, gboolean enable);
 void pan_filter_activate(PanWindow *pw);
 void pan_filter_activate_cb(const gchar *text, gpointer data);
 void pan_filter_toggle_cb(GtkWidget *button, gpointer data);
+void pan_filter_toggle_button_cb(GtkWidget *button, gpointer data);
 
 // Creates a new PanViewFilterUi instance and returns it.
 PanViewFilterUi *pan_filter_ui_new(PanWindow *pw);
@@ -71,7 +74,7 @@
 // Destroys the specified PanViewFilterUi and sets the pointer to NULL.
 void pan_filter_ui_destroy(PanViewFilterUi **ui);
 
-gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements);
+gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements, gint filter_classes);
 
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/pan-view/pan-view.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/pan-view/pan-view.c	Thu Jun 07 11:44:36 2018 +0100
@@ -2219,6 +2219,13 @@
 	filelist_free(editmenu_fd_list);
 }
 
+static void pan_play_cb(GtkWidget *widget, gpointer data)
+{
+	PanWindow *pw = data;
+
+	start_editor_from_file(options->image_l_click_video_editor, pw->click_pi->fd);
+}
+
 static GList *pan_view_get_fd_list(PanWindow *pw)
 {
 	GList *list = NULL;
@@ -2253,13 +2260,18 @@
 	GtkWidget *menu;
 	GtkWidget *submenu;
 	GtkWidget *item;
-	gboolean active;
+	gboolean active, video;
 	GList *editmenu_fd_list;
 
 	active = (pw->click_pi != NULL);
+	video = (active && pw->click_pi->fd && pw->click_pi->fd->format_class == FORMAT_CLASS_VIDEO);
 
 	menu = popup_menu_short_lived();
 
+	menu_item_add_stock_sensitive(menu, _("_Play"), GTK_STOCK_MEDIA_PLAY, video,
+			    G_CALLBACK(pan_play_cb), pw);
+	menu_item_add_divider(menu);
+
 	menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
 			    G_CALLBACK(pan_zoom_in_cb), pw);
 	menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
--- a/src/preferences.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/preferences.c	Thu Jun 07 11:44:36 2018 +0100
@@ -104,7 +104,7 @@
 	AE_ACCEL
 };
 
-static gchar *format_class_list[] = {
+gchar *format_class_list[] = {
 	N_("Unknown"),
 	N_("Image"),
 	N_("RAW Image"),
--- a/src/search.c	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/search.c	Thu Jun 07 11:44:36 2018 +0100
@@ -1044,6 +1044,13 @@
 	search_result_clear(sd);
 }
 
+static void sr_menu_play_cb(GtkWidget *widget, gpointer data)
+{
+	SearchData *sd = data;
+
+	start_editor_from_file(options->image_l_click_video_editor, sd->click_fd);
+}
+
 static void search_result_menu_destroy_cb(GtkWidget *widget, gpointer data)
 {
 	GList *editmenu_fd_list = data;
@@ -1076,9 +1083,15 @@
 	GtkWidget *item;
 	GList *editmenu_fd_list;
 	GtkWidget *submenu;
+	gboolean video;
 
 	menu = popup_menu_short_lived();
 
+	video = (on_row && sd->click_fd && sd->click_fd->format_class == FORMAT_CLASS_VIDEO);
+	menu_item_add_stock_sensitive(menu, _("_Play"), GTK_STOCK_MEDIA_PLAY, video,
+			    G_CALLBACK(sr_menu_play_cb), sd);
+	menu_item_add_divider(menu);
+
 	menu_item_add_sensitive(menu, _("_View"), on_row,
 				G_CALLBACK(sr_menu_view_cb), sd);
 	menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, on_row,
--- a/src/typedefs.h	Sun Jun 03 11:39:01 2018 +0100
+++ b/src/typedefs.h	Thu Jun 07 11:44:36 2018 +0100
@@ -146,6 +146,8 @@
 	FILE_FORMAT_CLASSES
 } FileFormatClass;
 
+extern gchar *format_class_list[];
+
 typedef enum {
 	SS_ERR_NONE = 0,
 	SS_ERR_DISABLED, /**< secsave is disabled. */