2 * Copyright (C) 2019 The Geeqie Team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "search-and-run.h"
33 typedef struct _SarData SarData;
39 GtkEntryCompletion *completion;
40 GtkListStore *command_store;
46 static gint sort_iter_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer UNUSED(data))
49 gchar *label1, *label2;
51 gtk_tree_model_get(model, a, SAR_LABEL, &label1, -1);
52 gtk_tree_model_get(model, b, SAR_LABEL, &label2, -1);
54 if (label1 == NULL || label2 == NULL)
56 if (label1 == NULL && label2 == NULL)
62 ret = (label1 == NULL) ? -1 : 1;
67 ret = g_utf8_collate(label1, label2);
76 static void command_store_populate(SarData* sar)
78 GList *groups, *actions;
80 const gchar *accel_path;
83 GtkTreeSortable *sortable;
84 gchar *label, *tooltip;
85 gchar *label2, *tooltip2;
87 gchar *existing_command;
89 gboolean duplicate_command;
92 sar->command_store = gtk_list_store_new(SAR_COUNT, G_TYPE_STRING, G_TYPE_OBJECT);
94 sortable = GTK_TREE_SORTABLE(sar->command_store);
95 gtk_tree_sortable_set_sort_func(sortable, SAR_LABEL, sort_iter_compare_func, NULL, NULL);
97 gtk_tree_sortable_set_sort_column_id(sortable, SAR_LABEL, GTK_SORT_ASCENDING);
99 groups = gtk_ui_manager_get_action_groups(sar->lw->ui_manager);
102 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
105 action = GTK_ACTION(actions->data);
106 accel_path = gtk_action_get_accel_path(action);
107 if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
109 g_object_get(action, "tooltip", &tooltip, "label", &label, NULL);
110 accel = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
112 /* menu items with no tooltip are placeholders */
113 if (g_strrstr(accel_path, ".desktop") != NULL || tooltip != NULL)
115 if (pango_parse_markup(label, -1, '_', NULL, &label2, NULL, NULL) && label2)
122 if (pango_parse_markup(tooltip, -1, '_', NULL, &tooltip2, NULL, NULL) && label2)
129 new_command = g_string_new(NULL);
130 if (g_strcmp0(label, tooltip) == 0)
132 g_string_append_printf(new_command, "%s : %s",label, accel);
136 g_string_append_printf(new_command, "%s - %s : %s",label, tooltip, accel);
139 iter_found = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sar->command_store), &iter);
140 duplicate_command = FALSE;
144 gtk_tree_model_get(GTK_TREE_MODEL(sar->command_store), &iter, SAR_LABEL, &existing_command, -1);
145 if (g_strcmp0(new_command->str, existing_command) == 0)
147 g_free(existing_command);
148 duplicate_command = TRUE;
151 g_free(existing_command);
152 iter_found = gtk_tree_model_iter_next(GTK_TREE_MODEL(sar->command_store), &iter);
155 if (!duplicate_command )
157 gtk_list_store_append(sar->command_store, &iter);
158 gtk_list_store_set(sar->command_store, &iter,
159 SAR_LABEL, new_command->str,
166 g_string_free(new_command, TRUE);
169 actions = actions->next;
171 groups = groups->next;
175 static gboolean search_and_run_destroy(gpointer data)
177 SarData *sar = static_cast<SarData *>(data);
179 sar->lw->sar_window = NULL;
180 gtk_widget_destroy(sar->window);
182 return G_SOURCE_CONTINUE;
185 static gboolean entry_box_activate_cb(GtkWidget *UNUSED(widget), gpointer data)
187 SarData *sar = static_cast<SarData *>(data);
191 gtk_action_activate(sar->action);
194 search_and_run_destroy(sar);
199 static gboolean keypress_cb(GtkWidget *UNUSED(widget), GdkEventKey *event, gpointer data)
201 SarData *sar = static_cast<SarData *>(data);
202 gboolean ret = FALSE;
204 switch (event->keyval)
207 search_and_run_destroy(sar);
213 sar->match_found = FALSE;
220 static gboolean match_selected_cb(GtkEntryCompletion *UNUSED(widget), GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
222 SarData *sar = static_cast<SarData *>(data);
224 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &sar->action, -1);
228 gtk_action_activate(sar->action);
231 g_idle_add((GSourceFunc)search_and_run_destroy, sar);
236 static gboolean match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer data)
238 SarData *sar = static_cast<SarData *>(data);
239 gboolean ret = FALSE;
244 GString *reg_exp_str;
246 GError *error = NULL;
248 model = gtk_entry_completion_get_model(completion);
249 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_LABEL, &label, -1);
250 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &action, -1);
252 normalized = g_utf8_normalize(label, -1, G_NORMALIZE_DEFAULT);
254 reg_exp_str = g_string_new("\\b(\?=.*:)");
255 reg_exp_str = g_string_append(reg_exp_str, key);
257 reg_exp = g_regex_new(reg_exp_str->str, G_REGEX_CASELESS, GRegexMatchFlags(0), &error);
260 log_printf("Error: could not compile regular expression %s\n%s\n", reg_exp_str->str, error->message);
263 reg_exp = g_regex_new("", GRegexCompileFlags(0), GRegexMatchFlags(0), NULL);
266 ret = g_regex_match(reg_exp, normalized, GRegexMatchFlags(0), NULL);
268 if (sar->match_found == FALSE && ret == TRUE)
270 sar->action = action;
271 sar->match_found = TRUE;
274 g_regex_unref(reg_exp);
275 g_string_free(reg_exp_str, TRUE);
281 GtkWidget *search_and_run_new(LayoutWindow *lw)
284 GdkGeometry geometry;
286 sar = g_new0(SarData, 1);
288 sar->window = window_new(GTK_WINDOW_TOPLEVEL, "sar_window", NULL, NULL, _("Search and Run command"));
289 DEBUG_NAME(sar->window);
291 geometry.min_width = 500;
292 geometry.max_width = 1500;
293 geometry.min_height = 10;
294 geometry.max_height = 10;
295 gtk_window_set_geometry_hints(GTK_WINDOW(sar->window), NULL, &geometry, static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE));
297 gtk_window_set_resizable(GTK_WINDOW(sar->window), TRUE);
299 sar->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
300 gtk_container_add(GTK_CONTAINER(sar->window), sar->vbox);
301 gtk_widget_show(sar->vbox);
303 sar->entry_box = gtk_entry_new();
304 gtk_box_pack_start(GTK_BOX(sar->vbox), sar->entry_box, FALSE, FALSE, 0);
305 gtk_widget_show(sar->entry_box);
306 gtk_entry_set_icon_from_stock(GTK_ENTRY(sar->entry_box), GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
307 gtk_widget_show(sar->vbox);
308 gtk_widget_set_tooltip_text(sar->entry_box, "Search for commands and run them");
309 g_signal_connect(G_OBJECT(sar->entry_box), "key_press_event", G_CALLBACK(keypress_cb), sar);
310 g_signal_connect(G_OBJECT(sar->entry_box), "activate", G_CALLBACK(entry_box_activate_cb), sar);
312 command_store_populate(sar);
314 sar->completion = gtk_entry_completion_new();
315 gtk_entry_set_completion(GTK_ENTRY(sar->entry_box), sar->completion);
316 gtk_entry_completion_set_inline_completion(sar->completion, FALSE);
317 gtk_entry_completion_set_inline_selection(sar->completion, FALSE);
318 gtk_entry_completion_set_minimum_key_length(sar->completion, 1);
319 gtk_entry_completion_set_match_func(sar->completion, match_func, sar, NULL);
320 g_signal_connect(G_OBJECT(sar->completion), "match-selected", G_CALLBACK(match_selected_cb), sar);
321 gtk_entry_completion_set_model(sar->completion, GTK_TREE_MODEL(sar->command_store));
322 gtk_entry_completion_set_text_column(sar->completion, SAR_LABEL);
324 gtk_widget_show(sar->window);
327 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */