From: Colin Clark Date: Sun, 20 Oct 2019 16:00:50 +0000 (+0100) Subject: Fix #715: / for action search X-Git-Tag: v1.6~120 X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?p=geeqie.git;a=commitdiff_plain;h=20c128644acbf9e2f75a120bd43482bb9f5f5551 Fix #715: / for action search https://github.com/BestImageViewer/geeqie/issues/715 A search box is opened with the default key "/". Characters typed are matched to commands - both menu and plugins. The matching commands, with tooltip amd accel key if present , are listed in a pop-up box. --- diff --git a/doc/docbook/GuideMainWindowMenus.xml b/doc/docbook/GuideMainWindowMenus.xml index 07e9b1d6..caba6abe 100644 --- a/doc/docbook/GuideMainWindowMenus.xml +++ b/doc/docbook/GuideMainWindowMenus.xml @@ -1614,6 +1614,27 @@ +
+ SaR menu + + + + + + + / + + + Search and Run Commands + + + + Opens a text box in which you may type characters to find a command. The characters you type will be matched to the first characters of any word in all the commands in Geeqie - both built-in commands and plugins - and matching actions will be displayed in a pop-up. You may scroll to any of the matching commands and press Enter to execute the command. Pressing Enter without selecting from the list will execute the first action in the list. Press Esc to close the box. + + + + +
Tear off menus Menus can be permanently displayed by selecting the dotted line item at the top of each menu category. A tear off menu will behave the same as an ordinary window. To close a tear off menu, select the dotted line item at the top of the menu. diff --git a/doc/docbook/GuideReferenceKeyboardShortcuts.xml b/doc/docbook/GuideReferenceKeyboardShortcuts.xml index f2f12303..8a89e167 100644 --- a/doc/docbook/GuideReferenceKeyboardShortcuts.xml +++ b/doc/docbook/GuideReferenceKeyboardShortcuts.xml @@ -40,6 +40,13 @@ Right-click Context menu. + + + / + + + Search and Run Commands. + diff --git a/plugins/camera-import/camera-import.desktop.in b/plugins/camera-import/camera-import.desktop.in index fff8ede8..60bef21b 100644 --- a/plugins/camera-import/camera-import.desktop.in +++ b/plugins/camera-import/camera-import.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=Camera import +_Comment=Import all images from camera # Requires gphoto2 # The gphoto2 file $HOME/.gphoto2/settings can diff --git a/plugins/export-jpeg/export-jpeg.desktop.in b/plugins/export-jpeg/export-jpeg.desktop.in index 3cfdb738..3edf83db 100644 --- a/plugins/export-jpeg/export-jpeg.desktop.in +++ b/plugins/export-jpeg/export-jpeg.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=Export jpeg +_Comment=Extract emdedded jpegs from a raw file Exec=geeqie-export-jpeg %f diff --git a/plugins/image-crop/image-crop.desktop.in b/plugins/image-crop/image-crop.desktop.in index 82d3ce9d..16cb806e 100644 --- a/plugins/image-crop/image-crop.desktop.in +++ b/plugins/image-crop/image-crop.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=Image crop +_Comment=Crop image from marked rectangle # Requires ImageMagick and exiftools diff --git a/plugins/random-image/random-image.desktop.in b/plugins/random-image/random-image.desktop.in index a9c733ab..e7765f10 100644 --- a/plugins/random-image/random-image.desktop.in +++ b/plugins/random-image/random-image.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=Random image +_Comment=Display random image from Collections and current folder Exec=geeqie-random-image diff --git a/plugins/ufraw/geeqie-ufraw-id.desktop.in b/plugins/ufraw/geeqie-ufraw-id.desktop.in index aadcf4d1..f28e89c0 100644 --- a/plugins/ufraw/geeqie-ufraw-id.desktop.in +++ b/plugins/ufraw/geeqie-ufraw-id.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=Edit UFRaw ID file +_Comment=Create UFRaw ID files for RAW files Exec=ufraw --create-id=only %U diff --git a/plugins/ufraw/geeqie-ufraw-recursive.desktop.in b/plugins/ufraw/geeqie-ufraw-recursive.desktop.in index 474010a7..43282a48 100644 --- a/plugins/ufraw/geeqie-ufraw-recursive.desktop.in +++ b/plugins/ufraw/geeqie-ufraw-recursive.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=UFRaw Batch recursive +_Comment=Search for new RAW files or modified UFRaw ID files recursively # call the helper script with current directory as an argument Exec=geeqie-ufraw --recursive . diff --git a/plugins/ufraw/geeqie-ufraw.desktop.in b/plugins/ufraw/geeqie-ufraw.desktop.in index 951c1b45..e237cb67 100644 --- a/plugins/ufraw/geeqie-ufraw.desktop.in +++ b/plugins/ufraw/geeqie-ufraw.desktop.in @@ -2,6 +2,7 @@ Version=1.0 Type=Application _Name=UFRaw Batch +_Comment=Search for new RAW files or modified UFRaw ID files # call the helper script on each file individually # - this allows a better controll over processing diff --git a/src/Makefile.am b/src/Makefile.am index aa8695bc..350f95d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -259,6 +259,8 @@ geeqie_SOURCES = \ rcfile.h \ search.c \ search.h \ + search_and_run.c \ + search_and_run.h \ secure_save.c \ secure_save.h \ shortcuts.c \ diff --git a/src/layout_util.c b/src/layout_util.c index 40a32535..f2551a9c 100644 --- a/src/layout_util.c +++ b/src/layout_util.c @@ -47,6 +47,7 @@ #include "print.h" #include "rcfile.h" #include "search.h" +#include "search_and_run.h" #include "slideshow.h" #include "ui_fileops.h" #include "ui_menu.h" @@ -69,6 +70,7 @@ static gboolean layout_bar_enabled(LayoutWindow *lw); static gboolean layout_bar_sort_enabled(LayoutWindow *lw); static void layout_bars_hide_toggle(LayoutWindow *lw); static void layout_util_sync_views(LayoutWindow *lw); +static void layout_search_and_run_window_new(LayoutWindow *lw); /* *----------------------------------------------------------------------------- @@ -1058,6 +1060,15 @@ static void layout_menu_bar_exif_cb(GtkAction *action, gpointer data) layout_exif_window_new(lw); } +static void layout_menu_search_and_run_cb(GtkAction *action, gpointer data) +{ + LayoutWindow *lw = data; + + layout_exit_fullscreen(lw); + layout_search_and_run_window_new(lw); +} + + static void layout_menu_float_cb(GtkToggleAction *action, gpointer data) { LayoutWindow *lw = data; @@ -1943,6 +1954,7 @@ static GtkActionEntry menu_entries[] = { { "StereoMenu", NULL, N_("Stere_o"), NULL, NULL, NULL }, { "OverlayMenu", NULL, N_("Image _Overlay"), NULL, NULL, NULL }, { "PluginsMenu", NULL, N_("_Plugins"), NULL, NULL, NULL }, + { "SarMenu", NULL, N_("S_aR"), NULL, NULL, NULL }, { "HelpMenu", NULL, N_("_Help"), NULL, NULL, NULL }, { "FirstImage", GTK_STOCK_GOTO_TOP, N_("_First Image"), "Home", N_("First Image"), CB(layout_menu_image_first_cb) }, @@ -1952,21 +1964,21 @@ static GtkActionEntry menu_entries[] = { { "NextImage", GTK_STOCK_GO_DOWN, N_("_Next Image"), "space", N_("Next Image"), CB(layout_menu_image_next_cb) }, { "NextImageAlt1", GTK_STOCK_GO_DOWN, N_("_Next Image"), "Page_Down", N_("Next Image"), CB(layout_menu_image_next_cb) }, - { "ImageForward", GTK_STOCK_GOTO_LAST, N_("Image Forward"), NULL, N_("Image Forward"), CB(layout_menu_image_forward_cb) }, - { "ImageBack", GTK_STOCK_GOTO_FIRST, N_("Image Back"), NULL, N_("Image Back"), CB(layout_menu_image_back_cb) }, + { "ImageForward", GTK_STOCK_GOTO_LAST, N_("Image Forward"), NULL, N_("Forward in image history"), CB(layout_menu_image_forward_cb) }, + { "ImageBack", GTK_STOCK_GOTO_FIRST, N_("Image Back"), NULL, N_("Back in image history"), CB(layout_menu_image_back_cb) }, - { "FirstPage", GTK_STOCK_MEDIA_PREVIOUS, N_("_First Page"), NULL, N_( "First Page"), CB(layout_menu_page_first_cb) }, - { "LastPage", GTK_STOCK_MEDIA_NEXT, N_("_Last Page"), NULL, N_("Last Page"), CB(layout_menu_page_last_cb) }, - { "NextPage", GTK_STOCK_MEDIA_FORWARD, N_("_Next Page"), NULL, N_("Next Page"), CB(layout_menu_page_next_cb) }, - { "PrevPage", GTK_STOCK_MEDIA_REWIND, N_("_Previous Page"), NULL, N_("Previous Page"), CB(layout_menu_page_previous_cb) }, + { "FirstPage",GTK_STOCK_MEDIA_PREVIOUS, N_("_First Page"), NULL, N_( "First Page of multi-page image"), CB(layout_menu_page_first_cb) }, + { "LastPage", GTK_STOCK_MEDIA_NEXT, N_("_Last Page"), NULL, N_("Last Page of multi-page image"), CB(layout_menu_page_last_cb) }, + { "NextPage", GTK_STOCK_MEDIA_FORWARD, N_("_Next Page"), NULL, N_("Next Page of multi-page image"), CB(layout_menu_page_next_cb) }, + { "PrevPage", GTK_STOCK_MEDIA_REWIND, N_("_Previous Page"), NULL, N_("Previous Page of multi-page image"), CB(layout_menu_page_previous_cb) }, - { "NextImageAlt2", GTK_STOCK_GO_DOWN, N_("_Next Image"), "KP_Page_Down", N_("Next Image"), CB(layout_menu_image_next_cb) }, + { "NextImageAlt2", GTK_STOCK_GO_DOWN, N_("_Next Image"), "KP_Page_Down", N_("Next Image"), CB(layout_menu_image_next_cb) }, { "LastImage", GTK_STOCK_GOTO_BOTTOM, N_("_Last Image"), "End", N_("Last Image"), CB(layout_menu_image_last_cb) }, - { "Back", GTK_STOCK_GO_BACK, N_("_Back"), NULL, N_("Back"), CB(layout_menu_back_cb) }, - { "Forward", GTK_STOCK_GO_FORWARD, N_("_Forward"), NULL, N_("Forward"), CB(layout_menu_forward_cb) }, - { "Home", GTK_STOCK_HOME, N_("_Home"), NULL, N_("Home"), CB(layout_menu_home_cb) }, - { "Up", GTK_STOCK_GO_UP, N_("_Up"), NULL, N_("Up"), CB(layout_menu_up_cb) }, + { "Back", GTK_STOCK_GO_BACK, N_("_Back"), NULL, N_("Back in folder history"), CB(layout_menu_back_cb) }, + { "Forward", GTK_STOCK_GO_FORWARD, N_("_Forward"), NULL, N_("Forward in folder history"), CB(layout_menu_forward_cb) }, + { "Home", GTK_STOCK_HOME, N_("_Home"), NULL, N_("Home"), CB(layout_menu_home_cb) }, + { "Up", GTK_STOCK_GO_UP, N_("_Up"), NULL, N_("Up one folder"), CB(layout_menu_up_cb) }, { "NewWindow", GTK_STOCK_NEW, N_("New _window"), "N", N_("New window"), CB(layout_menu_new_window_cb) }, { "NewCollection", GTK_STOCK_INDEX, N_("_New collection"), "C", N_("New collection"), CB(layout_menu_new_cb) }, @@ -1990,7 +2002,7 @@ static GtkActionEntry menu_entries[] = { { "CopyPathUnquoted", NULL, N_("_Copy path unquoted to clipboard"), NULL, N_("Copy path unquoted to clipboard"), CB(layout_menu_copy_path_unquoted_cb) }, { "CloseWindow", GTK_STOCK_CLOSE, N_("C_lose window"), "W", N_("Close window"), CB(layout_menu_close_cb) }, { "Quit", GTK_STOCK_QUIT, N_("_Quit"), "Q", N_("Quit"), CB(layout_menu_exit_cb) }, - { "RotateCW", PIXBUF_INLINE_ICON_CW, N_("_Rotate clockwise 90°"), "bracketright", N_("Rotate clockwise 90°"), CB(layout_menu_alter_90_cb) }, + { "RotateCW", PIXBUF_INLINE_ICON_CW, N_("_Rotate clockwise 90°"), "bracketright", N_("Image Rotate clockwise 90°"), CB(layout_menu_alter_90_cb) }, { "Rating0", NULL, N_("_Rating 0"), "KP_0", N_("Rating 0"), CB(layout_menu_rating_0_cb) }, { "Rating1", NULL, N_("_Rating 1"), "KP_1", N_("Rating 1"), CB(layout_menu_rating_1_cb) }, { "Rating2", NULL, N_("_Rating 2"), "KP_2", N_("Rating 2"), CB(layout_menu_rating_2_cb) }, @@ -1999,9 +2011,9 @@ static GtkActionEntry menu_entries[] = { { "Rating5", NULL, N_("_Rating 5"), "KP_5", N_("Rating 5"), CB(layout_menu_rating_5_cb) }, { "RatingM1", NULL, N_("_Rating -1"), "KP_Subtract", N_("Rating -1"), CB(layout_menu_rating_m1_cb) }, { "RotateCCW", PIXBUF_INLINE_ICON_CCW, N_("Rotate _counterclockwise 90°"), "bracketleft", N_("Rotate counterclockwise 90°"), CB(layout_menu_alter_90cc_cb) }, - { "Rotate180", PIXBUF_INLINE_ICON_180, N_("Rotate 1_80°"), "R", N_("Rotate 180°"), CB(layout_menu_alter_180_cb) }, - { "Mirror", PIXBUF_INLINE_ICON_MIRROR, N_("_Mirror"), "M", N_("Mirror"), CB(layout_menu_alter_mirror_cb) }, - { "Flip", PIXBUF_INLINE_ICON_FLIP, N_("_Flip"), "F", N_("Flip"), CB(layout_menu_alter_flip_cb) }, + { "Rotate180", PIXBUF_INLINE_ICON_180, N_("Rotate 1_80°"), "R", N_("Image Rotate 180°"), CB(layout_menu_alter_180_cb) }, + { "Mirror", PIXBUF_INLINE_ICON_MIRROR, N_("_Mirror"), "M", N_("Image Mirror"), CB(layout_menu_alter_mirror_cb) }, + { "Flip", PIXBUF_INLINE_ICON_FLIP, N_("_Flip"), "F", N_("Image Flip"), CB(layout_menu_alter_flip_cb) }, { "AlterNone", PIXBUF_INLINE_ICON_ORIGINAL, N_("_Original state"), "O", N_("Original state"), CB(layout_menu_alter_none_cb) }, { "SelectAll", PIXBUF_INLINE_ICON_SELECT_ALL, N_("Select _all"), "A", N_("Select all"), CB(layout_menu_select_all_cb) }, { "SelectNone", PIXBUF_INLINE_ICON_SELECT_NONE, N_("Select _none"), "A", N_("Select none"), CB(layout_menu_unselect_all_cb) }, @@ -2056,8 +2068,8 @@ static GtkActionEntry menu_entries[] = { { "HistogramModeCycle",NULL, N_("Cycle through histogram mo_des"), "J", N_("Cycle through histogram modes"), CB(layout_menu_histogram_toggle_mode_cb) }, { "HideTools", PIXBUF_INLINE_ICON_HIDETOOLS, N_("_Hide file list"), "H", N_("Hide file list"), CB(layout_menu_hide_cb) }, { "SlideShowPause", GTK_STOCK_MEDIA_PAUSE, N_("_Pause slideshow"), "P", N_("Pause slideshow"), CB(layout_menu_slideshow_pause_cb) }, - { "SlideShowFaster", GTK_STOCK_FILE, N_("Faster"), "KP_Add", N_("Faster"), CB(layout_menu_slideshow_faster_cb) }, - { "SlideShowSlower", GTK_STOCK_FILE, N_("Slower"), "KP_Subtract", N_("Slower"), CB(layout_menu_slideshow_slower_cb) }, + { "SlideShowFaster", GTK_STOCK_FILE, N_("Faster"), "KP_Add", N_("Slideshow Faster"), CB(layout_menu_slideshow_faster_cb) }, + { "SlideShowSlower", GTK_STOCK_FILE, N_("Slower"), "KP_Subtract", N_("Slideshow Slower"), CB(layout_menu_slideshow_slower_cb) }, { "Refresh", GTK_STOCK_REFRESH, N_("_Refresh"), "R", N_("Refresh"), CB(layout_menu_refresh_cb) }, { "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1", N_("Contents"), CB(layout_menu_help_cb) }, { "HelpSearch", NULL, N_("On-line help search"), NULL, N_("On-line help search"), CB(layout_menu_help_search_cb) }, @@ -2065,14 +2077,15 @@ static GtkActionEntry menu_entries[] = { { "HelpKbd", NULL, N_("_Keyboard map"), NULL, N_("Keyboard map"), CB(layout_menu_kbd_map_cb) }, { "HelpNotes", NULL, N_("_Release notes"), NULL, N_("Release notes"), CB(layout_menu_notes_cb) }, { "HelpChangeLog", NULL, N_("_ChangeLog"), NULL, N_("ChangeLog notes"), CB(layout_menu_changelog_cb) }, + { "SearchAndRunCommand", GTK_STOCK_FIND, N_("Search and Run command"), "slash", N_("Search commands by keyword and run them"), CB(layout_menu_search_and_run_cb) }, { "About", GTK_STOCK_ABOUT, N_("_About"), NULL, N_("About"), CB(layout_menu_about_cb) }, { "LogWindow", NULL, N_("_Log Window"), NULL, N_("Log Window"), CB(layout_menu_log_window_cb) }, { "ExifWin", PIXBUF_INLINE_ICON_EXIF, N_("_Exif window"), "E", N_("Exif window"), CB(layout_menu_bar_exif_cb) }, { "StereoCycle", NULL, N_("_Cycle through stereo modes"), NULL, N_("Cycle through stereo modes"), CB(layout_menu_stereo_mode_next_cb) }, - { "SplitNextPane", NULL, N_("_Next Pane"), "Right", N_("Next Pane"), CB(layout_menu_split_pane_next_cb) }, - { "SplitPreviousPane", NULL, N_("_Previous Pane"), "Left", N_("Previous Pane"), CB(layout_menu_split_pane_prev_cb) }, - { "SplitUpPane", NULL, N_("_Up Pane"), "Up", N_("Up Pane"), CB(layout_menu_split_pane_updown_cb) }, - { "SplitDownPane", NULL, N_("_Down Pane"), "Down", N_("Down Pane"), CB(layout_menu_split_pane_updown_cb) }, + { "SplitNextPane", NULL, N_("_Next Pane"), "Right", N_("Next Split Pane"), CB(layout_menu_split_pane_next_cb) }, + { "SplitPreviousPane", NULL, N_("_Previous Pane"), "Left", N_("Previous Split Pane"), CB(layout_menu_split_pane_prev_cb) }, + { "SplitUpPane", NULL, N_("_Up Pane"), "Up", N_("Up Split Pane"), CB(layout_menu_split_pane_updown_cb) }, + { "SplitDownPane", NULL, N_("_Down Pane"), "Down", N_("Down Split Pane"), CB(layout_menu_split_pane_updown_cb) }, { "WriteRotation", NULL, N_("_Write orientation to file"), NULL, N_("Write orientation to file"), CB(layout_menu_write_rotate_cb) }, { "WriteRotationKeepDate", NULL, N_("_Write orientation to file (preserve timestamp)"), NULL, N_("Write orientation to file (preserve timestamp)"), CB(layout_menu_write_rotate_keep_date_cb) }, { "ClearMarks", NULL, N_("Clear Marks..."), NULL, N_("Clear Marks"), CB(layout_menu_clear_marks_cb) }, @@ -2380,6 +2393,9 @@ static const gchar *menu_ui_description = " " " " " " +" " +" " +" " " " " " " " @@ -3638,4 +3654,15 @@ void layout_exif_window_new(LayoutWindow *lw) advanced_exif_set_fd(lw->exif_window, layout_image_get_fd(lw)); } +static void layout_search_and_run_window_new(LayoutWindow *lw) +{ + if (lw->sar_window) + { + gtk_window_present(GTK_WINDOW(lw->sar_window)); + return; + } + + lw->sar_window = search_and_run_new(lw); +} + /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/search_and_run.c b/src/search_and_run.c new file mode 100644 index 00000000..aa670eb1 --- /dev/null +++ b/src/search_and_run.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019 The Geeqie Team + * + * Author: Colin Clark + * + * 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 "main.h" +#include "search_and_run.h" + +#include "layout_util.h" +#include "ui_misc.h" +#include "window.h" + +enum { + SAR_LABEL, + SAR_ACTION, + SAR_COUNT +}; + +typedef struct _SarData SarData; +struct _SarData +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *entry_box; + GtkEntryCompletion *completion; + GtkListStore *command_store; + GtkAction *action; + LayoutWindow *lw; + gboolean match_found; +}; + +static gint sort_iter_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) +{ + gint ret = 0; + gchar *label1, *label2; + + gtk_tree_model_get(model, a, SAR_LABEL, &label1, -1); + gtk_tree_model_get(model, b, SAR_LABEL, &label2, -1); + + if (label1 == NULL || label2 == NULL) + { + if (label1 == NULL && label2 == NULL) + { + ret = 0; + } + else + { + ret = (label1 == NULL) ? -1 : 1; + } + } + else + { + ret = g_utf8_collate(label1, label2); + } + + g_free(label1); + g_free(label2); + + return ret; +} + +static void command_store_populate(SarData* sar) +{ + GList *groups, *actions; + GtkAction *action; + const gchar *accel_path; + GtkAccelKey key; + GtkTreeIter iter; + GtkTreeSortable *sortable; + gchar *label, *tooltip; + gchar *label2, *tooltip2; + GString *new_command; + gchar *existing_command; + gboolean iter_found; + gboolean duplicate_command; + gchar *accel; + + sar->command_store = gtk_list_store_new(SAR_COUNT, G_TYPE_STRING, G_TYPE_OBJECT); + + sortable = GTK_TREE_SORTABLE(sar->command_store); + gtk_tree_sortable_set_sort_func(sortable, SAR_LABEL, sort_iter_compare_func, NULL, NULL); + + gtk_tree_sortable_set_sort_column_id(sortable, SAR_LABEL, GTK_SORT_ASCENDING); + + groups = gtk_ui_manager_get_action_groups(sar->lw->ui_manager); + while (groups) + { + actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data)); + while (actions) + { + action = GTK_ACTION(actions->data); + accel_path = gtk_action_get_accel_path(action); + if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key)) + { + g_object_get(action, "tooltip", &tooltip, "label", &label, NULL); + accel = gtk_accelerator_name(key.accel_key, key.accel_mods); + + /* menu items with no tooltip are placeholders */ + if (g_strrstr(accel_path, ".desktop") != NULL || tooltip != NULL) + { + if (pango_parse_markup(label, -1, '_', NULL, &label2, NULL, NULL) && label2) + { + g_free(label); + label = label2; + } + if (tooltip) + { + if (pango_parse_markup(tooltip, -1, '_', NULL, &tooltip2, NULL, NULL) && label2) + { + g_free(tooltip); + tooltip = tooltip2; + } + } + + new_command = g_string_new(NULL); + if (g_strcmp0(label, tooltip) == 0) + { + g_string_append_printf(new_command, "%s : %s",label, accel); + } + else + { + g_string_append_printf(new_command, "%s - %s : %s",label, tooltip, accel); + } + + iter_found = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sar->command_store), &iter); + duplicate_command = FALSE; + + while (iter_found) + { + gtk_tree_model_get(GTK_TREE_MODEL(sar->command_store), &iter, SAR_LABEL, &existing_command, -1); + if (g_strcmp0(new_command->str, existing_command) == 0) + { + g_free(existing_command); + duplicate_command = TRUE; + break; + } + g_free(existing_command); + iter_found = gtk_tree_model_iter_next(GTK_TREE_MODEL(sar->command_store), &iter); + } + + if (!duplicate_command ) + { + gtk_list_store_append(sar->command_store, &iter); + gtk_list_store_set(sar->command_store, &iter, + SAR_LABEL, new_command->str, + SAR_ACTION, action, + -1); + } + g_free(label); + g_free(tooltip); + g_free(accel); + g_string_free(new_command, TRUE); + } + } + actions = actions->next; + } + groups = groups->next; + } +} + +static void search_and_run_destroy(gpointer data) +{ + SarData *sar = data; + + sar->lw->sar_window = NULL; + gtk_widget_destroy(sar->window); +} + +static gboolean entry_box_activate_cb(GtkWidget *widget, gpointer data) +{ + SarData *sar = data; + + if (sar->action) + { + gtk_action_activate(sar->action); + } + + search_and_run_destroy(sar); + + return TRUE; +} + +static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + SarData *sar = data; + gboolean ret = FALSE; + + switch (event->keyval) + { + case GDK_KEY_Escape: + search_and_run_destroy(sar); + ret = TRUE; + break; + case GDK_KEY_Return: + break; + default: + sar->match_found = FALSE; + sar->action = NULL; + } + + return ret; +} + +static gboolean match_selected_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + SarData *sar = data; + + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &sar->action, -1); + + if (sar->action) + { + gtk_action_activate(sar->action); + } + + g_idle_add((GSourceFunc)search_and_run_destroy, sar); + + return TRUE; +} + +static gboolean match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer data) +{ + SarData *sar = data; + gboolean ret = FALSE; + gchar *normalized; + GtkTreeModel *model; + GtkAction *action; + gchar *label; + GString *reg_exp_str; + GRegex *reg_exp; + GError *error = NULL; + + model = gtk_entry_completion_get_model(completion); + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_LABEL, &label, -1); + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &action, -1); + + normalized = g_utf8_normalize(label, -1, G_NORMALIZE_DEFAULT); + + reg_exp_str = g_string_new("\\b\(\?=.*:)"); + reg_exp_str = g_string_append(reg_exp_str, key); + + reg_exp = g_regex_new(reg_exp_str->str, G_REGEX_CASELESS, 0, &error); + if (error) + { + log_printf("Error: could not compile regular expression %s\n%s\n", reg_exp_str->str, error->message); + g_error_free(error); + error = NULL; + reg_exp = g_regex_new("", 0, 0, NULL); + } + + ret = g_regex_match(reg_exp, normalized, 0, NULL); + + if (sar->match_found == FALSE && ret == TRUE) + { + sar->action = action; + sar->match_found = TRUE; + } + + g_regex_unref(reg_exp); + g_string_free(reg_exp_str, TRUE); + g_free(normalized); + + return ret; +} + +GtkWidget *search_and_run_new(LayoutWindow *lw) +{ + SarData *sar; + GdkGeometry geometry; + + sar = g_new0(SarData, 1); + sar->lw = lw; + sar->window = window_new(GTK_WINDOW_TOPLEVEL, "sar_window", NULL, NULL, _("Search and Run command")); + DEBUG_NAME(sar->window); + + geometry.min_width = 500; + geometry.min_height = 0; + gtk_window_set_geometry_hints(GTK_WINDOW(sar->window), NULL, &geometry, GDK_HINT_MIN_SIZE); + + gtk_window_set_resizable(GTK_WINDOW(sar->window), TRUE); + + sar->vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP); + gtk_container_add(GTK_CONTAINER(sar->window), sar->vbox); + gtk_widget_show(sar->vbox); + + sar->entry_box = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(sar->vbox), sar->entry_box, FALSE, FALSE, 0); + gtk_widget_show(sar->entry_box); + gtk_widget_show(sar->vbox); + gtk_widget_set_tooltip_text(sar->entry_box, "Search for commands and run them"); + g_signal_connect(G_OBJECT(sar->entry_box), "key_press_event", G_CALLBACK(keypress_cb), sar); + g_signal_connect(G_OBJECT(sar->entry_box), "activate", G_CALLBACK(entry_box_activate_cb), sar); + + command_store_populate(sar); + + sar->completion = gtk_entry_completion_new(); + gtk_entry_set_completion(GTK_ENTRY(sar->entry_box), sar->completion); + gtk_entry_completion_set_inline_completion(sar->completion, FALSE); + gtk_entry_completion_set_inline_selection(sar->completion, FALSE); + gtk_entry_completion_set_minimum_key_length(sar->completion, 1); + gtk_entry_completion_set_match_func(sar->completion, match_func, sar, NULL); + g_signal_connect(G_OBJECT(sar->completion), "match-selected", G_CALLBACK(match_selected_cb), sar); + gtk_entry_completion_set_model(sar->completion, GTK_TREE_MODEL(sar->command_store)); + gtk_entry_completion_set_text_column(sar->completion, SAR_LABEL); + + gtk_widget_show(sar->window); + return sar->window; +} +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/search_and_run.h b/src/search_and_run.h new file mode 100644 index 00000000..4bfc37af --- /dev/null +++ b/src/search_and_run.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 The Geeqie Team + * + * Author: Colin Clark + * + * 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 SEARCH_AND_RUN_H +#define SEARCH_AND_RUN_H + + +GtkWidget *search_and_run_new(LayoutWindow *lw); + +#endif +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/typedefs.h b/src/typedefs.h index 307a9cd9..d9d00079 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -836,6 +836,7 @@ struct _LayoutWindow // gint bar_width; GtkWidget *exif_window; + GtkWidget *sar_window; /* Search and Run window */ AnimationData *animation;