Fix #715: / for action search
authorColin Clark <colin.clark@cclark.uk>
Sun, 20 Oct 2019 16:00:50 +0000 (17:00 +0100)
committerColin Clark <colin.clark@cclark.uk>
Sun, 20 Oct 2019 16:00:50 +0000 (17:00 +0100)
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.

14 files changed:
doc/docbook/GuideMainWindowMenus.xml
doc/docbook/GuideReferenceKeyboardShortcuts.xml
plugins/camera-import/camera-import.desktop.in
plugins/export-jpeg/export-jpeg.desktop.in
plugins/image-crop/image-crop.desktop.in
plugins/random-image/random-image.desktop.in
plugins/ufraw/geeqie-ufraw-id.desktop.in
plugins/ufraw/geeqie-ufraw-recursive.desktop.in
plugins/ufraw/geeqie-ufraw.desktop.in
src/Makefile.am
src/layout_util.c
src/search_and_run.c [new file with mode: 0644]
src/search_and_run.h [new file with mode: 0644]
src/typedefs.h

index 07e9b1d..caba6ab 100644 (file)
     </variablelist>\r
     <para />\r
   </section>\r
+  <section id="Sarmenu">\r
+    <title>SaR menu</title>\r
+    <variablelist>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>/</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Search and Run Commands</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>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 <code>Enter</code> to execute the command. Pressing <code>Enter</code> without selecting from the list will execute the first action in the list. Press <code>Esc</code> to close the box.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+     </variablelist>\r
+    <para />\r
+  </section>\r
   <section id="Tearoffmenus">\r
     <title>Tear off menus</title>\r
     <para>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.</para>\r
index f2f1230..8a89e16 100644 (file)
             <entry>Right-click</entry>\r
             <entry>Context menu.</entry>\r
           </row>\r
+          <row>\r
+            <entry>\r
+              <keycap>/</keycap>\r
+            </entry>\r
+            <entry/>\r
+            <entry>Search and Run Commands.</entry>\r
+          </row>\r
         </tbody>\r
       </tgroup>\r
     </table>\r
index fff8ede..60bef21 100644 (file)
@@ -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
index 3cfdb73..3edf83d 100644 (file)
@@ -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
 
index 82d3ce9..16cb806 100644 (file)
@@ -2,6 +2,7 @@
 Version=1.0
 Type=Application
 _Name=Image crop
+_Comment=Crop image from marked rectangle
 
 # Requires ImageMagick and exiftools
 
index a9c733a..e7765f1 100644 (file)
@@ -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
 
index aadcf4d..f28e89c 100644 (file)
@@ -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
 
index 474010a..43282a4 100644 (file)
@@ -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 .
index 951c1b4..e237cb6 100644 (file)
@@ -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
index aa8695b..350f95d 100644 (file)
@@ -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     \
index 40a3253..f2551a9 100644 (file)
@@ -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"),                      "<control>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"),                    "<control>W",           N_("Close window"),                     CB(layout_menu_close_cb) },
   { "Quit",            GTK_STOCK_QUIT,         N_("_Quit"),                            "<control>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"),        "<alt>KP_0",    N_("Rating 0"),                 CB(layout_menu_rating_0_cb) },
   { "Rating1",         NULL,                   N_("_Rating 1"),        "<alt>KP_1",    N_("Rating 1"),                 CB(layout_menu_rating_1_cb) },
   { "Rating2",         NULL,                   N_("_Rating 2"),        "<alt>KP_2",    N_("Rating 2"),                 CB(layout_menu_rating_2_cb) },
@@ -1999,9 +2011,9 @@ static GtkActionEntry menu_entries[] = {
   { "Rating5",         NULL,                   N_("_Rating 5"),        "<alt>KP_5",    N_("Rating 5"),                 CB(layout_menu_rating_5_cb) },
   { "RatingM1",                NULL,                   N_("_Rating -1"),       "<alt>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°"),    "<shift>R",             N_("Rotate 180°"),                     CB(layout_menu_alter_180_cb) },
-  { "Mirror",          PIXBUF_INLINE_ICON_MIRROR,      N_("_Mirror"),  "<shift>M",             N_("Mirror"),                           CB(layout_menu_alter_mirror_cb) },
-  { "Flip",            PIXBUF_INLINE_ICON_FLIP,        N_("_Flip"),    "<shift>F",             N_("Flip"),                             CB(layout_menu_alter_flip_cb) },
+  { "Rotate180",       PIXBUF_INLINE_ICON_180, N_("Rotate 1_80°"),    "<shift>R",             N_("Image Rotate 180°"),                       CB(layout_menu_alter_180_cb) },
+  { "Mirror",          PIXBUF_INLINE_ICON_MIRROR,      N_("_Mirror"),  "<shift>M",             N_("Image Mirror"),                             CB(layout_menu_alter_mirror_cb) },
+  { "Flip",            PIXBUF_INLINE_ICON_FLIP,        N_("_Flip"),    "<shift>F",             N_("Image Flip"),                               CB(layout_menu_alter_flip_cb) },
   { "AlterNone",       PIXBUF_INLINE_ICON_ORIGINAL,    N_("_Original state"),  "<shift>O",             N_("Original state"),                   CB(layout_menu_alter_none_cb) },
   { "SelectAll",       PIXBUF_INLINE_ICON_SELECT_ALL,                  N_("Select _all"),                      "<control>A",           N_("Select all"),                       CB(layout_menu_select_all_cb) },
   { "SelectNone",      PIXBUF_INLINE_ICON_SELECT_NONE,                 N_("Select _none"),                     "<control><shift>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"),                  "<control>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"),           "<control>KP_Add",                      N_("Faster"),                   CB(layout_menu_slideshow_faster_cb) },
-  { "SlideShowSlower", GTK_STOCK_FILE, N_("Slower"),           "<control>KP_Subtract",                 N_("Slower"),                   CB(layout_menu_slideshow_slower_cb) },
+  { "SlideShowFaster", GTK_STOCK_FILE, N_("Faster"),           "<control>KP_Add",                      N_("Slideshow Faster"),                         CB(layout_menu_slideshow_faster_cb) },
+  { "SlideShowSlower", GTK_STOCK_FILE, N_("Slower"),           "<control>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"),                     "<control>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"),       "<alt>Right",                   N_("Next Pane"),        CB(layout_menu_split_pane_next_cb) },
-  { "SplitPreviousPane",       NULL,                   N_("_Previous Pane"),   "<alt>Left",                    N_("Previous Pane"),    CB(layout_menu_split_pane_prev_cb) },
-  { "SplitUpPane",     NULL,                   N_("_Up Pane"), "<alt>Up",                      N_("Up Pane"),  CB(layout_menu_split_pane_updown_cb) },
-  { "SplitDownPane",   NULL,                   N_("_Down Pane"),       "<alt>Down",                    N_("Down Pane"),        CB(layout_menu_split_pane_updown_cb) },
+  { "SplitNextPane",   NULL,                   N_("_Next Pane"),       "<alt>Right",                   N_("Next Split Pane"),  CB(layout_menu_split_pane_next_cb) },
+  { "SplitPreviousPane",       NULL,                   N_("_Previous Pane"),   "<alt>Left",                    N_("Previous Split Pane"),      CB(layout_menu_split_pane_prev_cb) },
+  { "SplitUpPane",     NULL,                   N_("_Up Pane"), "<alt>Up",                      N_("Up Split Pane"),    CB(layout_menu_split_pane_updown_cb) },
+  { "SplitDownPane",   NULL,                   N_("_Down Pane"),       "<alt>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 =
 "      <placeholder name='SlideShowSection'/>"
 "      <separator/>"
 "    </menu>"
+"    <menu action='SarMenu'>"
+"      <menuitem action='SearchAndRunCommand'/>"
+"    </menu>"
 "    <menu action='HelpMenu'>"
 "      <separator/>"
 "      <menuitem action='HelpContents'/>"
@@ -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 (file)
index 0000000..aa670eb
--- /dev/null
@@ -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 (file)
index 0000000..4bfc37a
--- /dev/null
@@ -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: */
index 307a9cd..d9d0007 100644 (file)
@@ -836,6 +836,7 @@ struct _LayoutWindow
 //     gint bar_width;
 
        GtkWidget *exif_window;
+       GtkWidget *sar_window; /* Search and Run window */
 
        AnimationData *animation;