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"
36 GtkListStore *command_store;
43 static gint sort_iter_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer UNUSED(data))
46 gchar *label1, *label2;
48 gtk_tree_model_get(model, a, SAR_LABEL, &label1, -1);
49 gtk_tree_model_get(model, b, SAR_LABEL, &label2, -1);
51 if (label1 == nullptr || label2 == nullptr)
53 if (label1 == nullptr && label2 == nullptr)
59 ret = (label1 == nullptr) ? -1 : 1;
64 ret = g_utf8_collate(label1, label2);
73 static void command_store_populate(SarData* sar)
75 GList *groups, *actions;
77 const gchar *accel_path;
80 GtkTreeSortable *sortable;
81 gchar *label, *tooltip;
82 gchar *label2, *tooltip2;
84 gchar *existing_command;
86 gboolean duplicate_command;
89 sar->command_store = GTK_LIST_STORE(gtk_builder_get_object(sar->builder, "command_store"));
90 sortable = GTK_TREE_SORTABLE(sar->command_store);
91 gtk_tree_sortable_set_sort_func(sortable, SAR_LABEL, sort_iter_compare_func, nullptr, nullptr);
93 gtk_tree_sortable_set_sort_column_id(sortable, SAR_LABEL, GTK_SORT_ASCENDING);
95 groups = gtk_ui_manager_get_action_groups(sar->lw->ui_manager);
98 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
101 action = GTK_ACTION(actions->data);
102 accel_path = gtk_action_get_accel_path(action);
103 if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
105 g_object_get(action, "tooltip", &tooltip, "label", &label, NULL);
106 accel = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
108 /* menu items with no tooltip are placeholders */
109 if (g_strrstr(accel_path, ".desktop") != nullptr || tooltip != nullptr)
111 if (pango_parse_markup(label, -1, '_', nullptr, &label2, nullptr, nullptr) && label2)
118 if (pango_parse_markup(tooltip, -1, '_', nullptr, &tooltip2, nullptr, nullptr) && label2)
125 new_command = g_string_new(nullptr);
126 if (g_strcmp0(label, tooltip) == 0)
128 g_string_append_printf(new_command, "%s : %s",label, accel);
132 g_string_append_printf(new_command, "%s - %s : %s",label, tooltip, accel);
135 iter_found = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sar->command_store), &iter);
136 duplicate_command = FALSE;
140 gtk_tree_model_get(GTK_TREE_MODEL(sar->command_store), &iter, SAR_LABEL, &existing_command, -1);
141 if (g_strcmp0(new_command->str, existing_command) == 0)
143 g_free(existing_command);
144 duplicate_command = TRUE;
147 g_free(existing_command);
148 iter_found = gtk_tree_model_iter_next(GTK_TREE_MODEL(sar->command_store), &iter);
151 if (!duplicate_command )
153 gtk_list_store_append(sar->command_store, &iter);
154 gtk_list_store_set(sar->command_store, &iter,
155 SAR_LABEL, new_command->str,
162 g_string_free(new_command, TRUE);
165 actions = actions->next;
167 groups = groups->next;
171 static gboolean search_and_run_destroy(gpointer data)
173 auto sar = static_cast<SarData *>(data);
175 sar->lw->sar_window = nullptr;
176 g_object_unref(gtk_builder_get_object(sar->builder, "completion"));
177 g_object_unref(gtk_builder_get_object(sar->builder, "command_store"));
178 gtk_widget_destroy(sar->window);
181 return G_SOURCE_REMOVE;
184 static gboolean entry_box_activate_cb(GtkWidget *UNUSED(widget), gpointer data)
186 auto sar = static_cast<SarData *>(data);
190 gtk_action_activate(sar->action);
193 search_and_run_destroy(sar);
198 static gboolean keypress_cb(GtkWidget *UNUSED(widget), GdkEventKey *event, gpointer data)
200 auto sar = static_cast<SarData *>(data);
201 gboolean ret = FALSE;
203 switch (event->keyval)
206 search_and_run_destroy(sar);
212 sar->match_found = FALSE;
213 sar->action = nullptr;
219 static gboolean match_selected_cb(GtkEntryCompletion *UNUSED(widget), GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
221 auto sar = static_cast<SarData *>(data);
223 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &sar->action, -1);
227 gtk_action_activate(sar->action);
230 g_idle_add(static_cast<GSourceFunc>(search_and_run_destroy), sar);
235 static gboolean match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer data)
237 auto sar = static_cast<SarData *>(data);
238 gboolean ret = FALSE;
243 GString *reg_exp_str;
245 GError *error = nullptr;
247 model = gtk_entry_completion_get_model(completion);
248 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_LABEL, &label, -1);
249 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &action, -1);
251 normalized = g_utf8_normalize(label, -1, G_NORMALIZE_DEFAULT);
253 reg_exp_str = g_string_new("\\b(\?=.*:)");
254 reg_exp_str = g_string_append(reg_exp_str, key);
256 reg_exp = g_regex_new(reg_exp_str->str, G_REGEX_CASELESS, static_cast<GRegexMatchFlags>(0), &error);
259 log_printf("Error: could not compile regular expression %s\n%s\n", reg_exp_str->str, error->message);
262 reg_exp = g_regex_new("", static_cast<GRegexCompileFlags>(0), static_cast<GRegexMatchFlags>(0), nullptr);
265 ret = g_regex_match(reg_exp, normalized, static_cast<GRegexMatchFlags>(0), nullptr);
267 if (sar->match_found == FALSE && ret == TRUE)
269 sar->action = action;
270 sar->match_found = TRUE;
273 g_regex_unref(reg_exp);
274 g_string_free(reg_exp_str, TRUE);
280 GtkWidget *search_and_run_new(LayoutWindow *lw)
285 sar = g_new0(SarData, 1);
288 ui_path = g_build_filename(GQ_RESOURCE_PATH_UI, "search-and-run.ui", nullptr);
290 sar->builder = gtk_builder_new_from_resource(ui_path);
291 command_store_populate(sar);
293 sar->window = GTK_WIDGET(gtk_builder_get_object(sar->builder, "search_and_run"));
294 DEBUG_NAME(sar->window);
296 gtk_entry_completion_set_match_func(GTK_ENTRY_COMPLETION(gtk_builder_get_object(sar->builder, "completion")), match_func, sar, nullptr);
298 g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "completion")), "match-selected", G_CALLBACK(match_selected_cb), sar);
299 g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "entry")), "key_press_event", G_CALLBACK(keypress_cb), sar);
300 g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "entry")), "activate", G_CALLBACK(entry_box_activate_cb), sar);
302 gtk_widget_show(sar->window);
308 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */