From: Tomasz Golinski Date: Thu, 7 Jun 2018 10:44:36 +0000 (+0100) Subject: Fix #612: Pan view image class filtering X-Git-Tag: v1.5~134 X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?p=geeqie.git;a=commitdiff_plain;h=ba59336f031f52cbe319c3c229ad8a3558af6c9a Fix #612: Pan view image class filtering https://github.com/BestImageViewer/geeqie/issues/612 --- diff --git a/src/pan-view/pan-calendar.c b/src/pan-view/pan-calendar.c index 0f7a4b1f..b200d315 100644 --- a/src/pan-view/pan-calendar.c +++ b/src/pan-view/pan-calendar.c @@ -204,7 +204,7 @@ void pan_calendar_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he 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) { diff --git a/src/pan-view/pan-folder.c b/src/pan-view/pan-folder.c index 95748eed..c7269347 100644 --- a/src/pan-view/pan-folder.c +++ b/src/pan-view/pan-folder.c @@ -243,7 +243,7 @@ static FlowerGroup *pan_flower_group(PanWindow *pw, FileData *dir_fd, gint x, gi 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 @@ static void pan_folder_tree_path(PanWindow *pw, FileData *dir_fd, 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)); diff --git a/src/pan-view/pan-grid.c b/src/pan-view/pan-grid.c index 5d28e4b3..b2714aa6 100644 --- a/src/pan-view/pan-grid.c +++ b/src/pan-view/pan-grid.c @@ -36,7 +36,7 @@ void pan_grid_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height 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) diff --git a/src/pan-view/pan-timeline.c b/src/pan-view/pan-timeline.c index 11627955..7cf9c15e 100644 --- a/src/pan-view/pan-timeline.c +++ b/src/pan-view/pan-timeline.c @@ -42,7 +42,7 @@ void pan_timeline_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *he 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) { diff --git a/src/pan-view/pan-view-filter.c b/src/pan-view/pan-view-filter.c index 5865d66b..fb18fb16 100644 --- a/src/pan-view/pan-view-filter.c +++ b/src/pan-view/pan-view-filter.c @@ -35,6 +35,7 @@ PanViewFilterUi *pan_filter_ui_new(PanWindow *pw) 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 @@ PanViewFilterUi *pan_filter_ui_new(PanWindow *pw) 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 @@ PanViewFilterUi *pan_filter_ui_new(PanWindow *pw) 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_visible(PanWindow *pw, gboolean enable) } } +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 @@ static gboolean pan_view_list_contains_kw_pattern(GList *haystack, PanViewFilter } } -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 @@ gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements) 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)) - { - should_reject = TRUE; - } - else if (group_kw == NULL) + 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) { - 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 @@ gboolean pan_filter_fd_list(GList **fd_list, GList *filter_elements) } } - g_hash_table_destroy(seen_kw_table); + if (filter_elements) + g_hash_table_destroy(seen_kw_table); + return modified; } diff --git a/src/pan-view/pan-view-filter.h b/src/pan-view/pan-view-filter.h index f3e3e11e..818da35d 100644 --- a/src/pan-view/pan-view-filter.h +++ b/src/pan-view/pan-view-filter.h @@ -55,15 +55,18 @@ struct _PanViewFilterUi 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 @@ PanViewFilterUi *pan_filter_ui_new(PanWindow *pw); // 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: */ diff --git a/src/pan-view/pan-view.c b/src/pan-view/pan-view.c index cad50b84..24745277 100644 --- a/src/pan-view/pan-view.c +++ b/src/pan-view/pan-view.c @@ -2219,6 +2219,13 @@ static void pan_popup_menu_destroy_cb(GtkWidget *widget, gpointer data) 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 @@ static GtkWidget *pan_popup_menu(PanWindow *pw) 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, diff --git a/src/preferences.c b/src/preferences.c index fda23228..d29660cc 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -104,7 +104,7 @@ enum { AE_ACCEL }; -static gchar *format_class_list[] = { +gchar *format_class_list[] = { N_("Unknown"), N_("Image"), N_("RAW Image"), diff --git a/src/search.c b/src/search.c index 74f1fba7..d64f5ce5 100644 --- a/src/search.c +++ b/src/search.c @@ -1044,6 +1044,13 @@ static void sr_menu_clear_cb(GtkWidget *widget, gpointer data) 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 @@ static GtkWidget *search_result_menu(SearchData *sd, gboolean on_row, gboolean e 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, diff --git a/src/typedefs.h b/src/typedefs.h index 134adb0c..33686415 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -146,6 +146,8 @@ typedef enum { FILE_FORMAT_CLASSES } FileFormatClass; +extern gchar *format_class_list[]; + typedef enum { SS_ERR_NONE = 0, SS_ERR_DISABLED, /**< secsave is disabled. */