029bca1bc8116c6bd56a0ecca21e074aad8a3828
[geeqie.git] / src / search-and-run.cc
1 /*
2  * Copyright (C) 2019 The Geeqie Team
3  *
4  * Author: Colin Clark
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 #include "main.h"
22 #include "search-and-run.h"
23
24 #include "layout.h"
25 #include "ui-misc.h"
26 #include "window.h"
27
28 enum {
29         SAR_LABEL,
30         SAR_ACTION,
31         SAR_COUNT
32 };
33
34 struct SarData
35 {
36         GtkWidget *window;
37         GtkListStore *command_store;
38         GtkAction *action;
39         LayoutWindow *lw;
40         gboolean match_found;
41         GtkBuilder *builder;
42 };
43
44 static gint sort_iter_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer)
45 {
46         gint ret = 0;
47         gchar *label1;
48         gchar *label2;
49
50         gtk_tree_model_get(model, a, SAR_LABEL, &label1, -1);
51         gtk_tree_model_get(model, b, SAR_LABEL, &label2, -1);
52
53         if (label1 == nullptr || label2 == nullptr)
54                 {
55                 if (label1 == nullptr && label2 == nullptr)
56                         {
57                         ret = 0;
58                         }
59                 else
60                         {
61                         ret = (label1 == nullptr) ? -1 : 1;
62                         }
63                 }
64         else
65                 {
66                 ret = g_utf8_collate(label1, label2);
67                 }
68
69         g_free(label1);
70         g_free(label2);
71
72         return ret;
73 }
74
75 static void command_store_populate(SarData* sar)
76 {
77         GList *groups;
78         GList *actions;
79         GtkAction *action;
80         const gchar *accel_path;
81         GtkAccelKey key;
82         GtkTreeIter iter;
83         GtkTreeSortable *sortable;
84         gchar *label;
85         gchar *tooltip;
86         gchar *label2;
87         gchar *tooltip2;
88         GString *new_command;
89         gchar *existing_command;
90         gboolean iter_found;
91         gboolean duplicate_command;
92         gchar *accel;
93
94         sar->command_store = GTK_LIST_STORE(gtk_builder_get_object(sar->builder, "command_store"));
95         sortable = GTK_TREE_SORTABLE(sar->command_store);
96         gtk_tree_sortable_set_sort_func(sortable, SAR_LABEL, sort_iter_compare_func, nullptr, nullptr);
97
98         gtk_tree_sortable_set_sort_column_id(sortable, SAR_LABEL, GTK_SORT_ASCENDING);
99
100         groups = gtk_ui_manager_get_action_groups(sar->lw->ui_manager);
101         while (groups)
102                 {
103                 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
104                 while (actions)
105                         {
106                         action = GTK_ACTION(actions->data);
107                         accel_path = gtk_action_get_accel_path(action);
108                         if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
109                                 {
110                                 g_object_get(action, "tooltip", &tooltip, "label", &label, NULL);
111                                 accel = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
112
113                                 /* menu items with no tooltip are placeholders */
114                                 if (g_strrstr(accel_path, ".desktop") != nullptr || tooltip != nullptr)
115                                         {
116                                         if (pango_parse_markup(label, -1, '_', nullptr, &label2, nullptr, nullptr) && label2)
117                                                 {
118                                                 g_free(label);
119                                                 label = label2;
120                                                 }
121                                         if (tooltip)
122                                                 {
123                                                 if (pango_parse_markup(tooltip, -1, '_', nullptr, &tooltip2, nullptr, nullptr) && label2)
124                                                         {
125                                                         g_free(tooltip);
126                                                         tooltip = tooltip2;
127                                                         }
128                                                 }
129
130                                         new_command = g_string_new(nullptr);
131                                         if (g_strcmp0(label, tooltip) == 0)
132                                                 {
133                                                 g_string_append_printf(new_command, "%s : %s",label, accel);
134                                                 }
135                                         else
136                                                 {
137                                                 g_string_append_printf(new_command, "%s - %s : %s",label, tooltip, accel);
138                                                 }
139
140                                         iter_found = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sar->command_store), &iter);
141                                         duplicate_command = FALSE;
142
143                                         while (iter_found)
144                                                 {
145                                                 gtk_tree_model_get(GTK_TREE_MODEL(sar->command_store), &iter, SAR_LABEL, &existing_command, -1);
146                                                 if (g_strcmp0(new_command->str, existing_command) == 0)
147                                                         {
148                                                         g_free(existing_command);
149                                                         duplicate_command = TRUE;
150                                                         break;
151                                                         }
152                                                 g_free(existing_command);
153                                                 iter_found = gtk_tree_model_iter_next(GTK_TREE_MODEL(sar->command_store), &iter);
154                                                 }
155
156                                         if (!duplicate_command )
157                                                 {
158                                                 gtk_list_store_append(sar->command_store, &iter);
159                                                 gtk_list_store_set(sar->command_store, &iter,
160                                                                 SAR_LABEL, new_command->str,
161                                                                 SAR_ACTION, action,
162                                                                 -1);
163                                                 }
164                                         g_free(label);
165                                         g_free(tooltip);
166                                         g_free(accel);
167                                         g_string_free(new_command, TRUE);
168                                         }
169                                 }
170                         actions = actions->next;
171                         }
172                 groups = groups->next;
173                 }
174 }
175
176 static gboolean search_and_run_destroy(gpointer data)
177 {
178         auto sar = static_cast<SarData *>(data);
179
180         sar->lw->sar_window = nullptr;
181         g_object_unref(gtk_builder_get_object(sar->builder, "completion"));
182         g_object_unref(gtk_builder_get_object(sar->builder, "command_store"));
183         gq_gtk_widget_destroy(sar->window);
184         g_free(sar);
185
186         return G_SOURCE_REMOVE;
187 }
188
189 static gboolean entry_box_activate_cb(GtkWidget *, gpointer data)
190 {
191         auto sar = static_cast<SarData *>(data);
192
193         if (sar->action)
194                 {
195                 gtk_action_activate(sar->action);
196                 }
197
198         search_and_run_destroy(sar);
199
200         return TRUE;
201 }
202
203 static gboolean keypress_cb(GtkWidget *, GdkEventKey *event, gpointer data)
204 {
205         auto sar = static_cast<SarData *>(data);
206         gboolean ret = FALSE;
207
208         switch (event->keyval)
209                 {
210                 case GDK_KEY_Escape:
211                         search_and_run_destroy(sar);
212                         ret = TRUE;
213                         break;
214                 case GDK_KEY_Return:
215                         break;
216                 default:
217                         sar->match_found = FALSE;
218                         sar->action = nullptr;
219                 }
220
221         return ret;
222 }
223
224 static gboolean match_selected_cb(GtkEntryCompletion *, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
225 {
226         auto sar = static_cast<SarData *>(data);
227
228         gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &sar->action, -1);
229
230         if (sar->action)
231                 {
232                 gtk_action_activate(sar->action);
233                 }
234
235         g_idle_add(static_cast<GSourceFunc>(search_and_run_destroy), sar);
236
237         return TRUE;
238 }
239
240 static gboolean match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer data)
241 {
242         auto sar = static_cast<SarData *>(data);
243         gboolean ret = FALSE;
244         gchar *normalized;
245         GtkTreeModel *model;
246         GtkAction *action;
247         gchar *label;
248         GString *reg_exp_str;
249         GRegex *reg_exp;
250         GError *error = nullptr;
251
252         model = gtk_entry_completion_get_model(completion);
253         gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_LABEL, &label, -1);
254         gtk_tree_model_get(GTK_TREE_MODEL(model), iter, SAR_ACTION, &action, -1);
255
256         normalized = g_utf8_normalize(label, -1, G_NORMALIZE_DEFAULT);
257
258         reg_exp_str = g_string_new("\\b(\?=.*:)");
259         reg_exp_str = g_string_append(reg_exp_str, key);
260
261         reg_exp = g_regex_new(reg_exp_str->str, G_REGEX_CASELESS, static_cast<GRegexMatchFlags>(0), &error);
262         if (error)
263                 {
264                 log_printf("Error: could not compile regular expression %s\n%s\n", reg_exp_str->str, error->message);
265                 g_error_free(error);
266                 error = nullptr;
267                 reg_exp = g_regex_new("", static_cast<GRegexCompileFlags>(0), static_cast<GRegexMatchFlags>(0), nullptr);
268                 }
269
270         ret = g_regex_match(reg_exp, normalized, static_cast<GRegexMatchFlags>(0), nullptr);
271
272         if (sar->match_found == FALSE && ret == TRUE)
273                 {
274                 sar->action = action;
275                 sar->match_found = TRUE;
276                 }
277
278         g_regex_unref(reg_exp);
279         g_string_free(reg_exp_str, TRUE);
280         g_free(normalized);
281
282         return ret;
283 }
284
285 GtkWidget *search_and_run_new(LayoutWindow *lw)
286 {
287         SarData *sar;
288
289         sar = g_new0(SarData, 1);
290         sar->lw = lw;
291
292         sar->builder = gtk_builder_new_from_resource(GQ_RESOURCE_PATH_UI "/search-and-run.ui");
293         command_store_populate(sar);
294
295         sar->window = GTK_WIDGET(gtk_builder_get_object(sar->builder, "search_and_run"));
296         DEBUG_NAME(sar->window);
297
298         gtk_entry_completion_set_match_func(GTK_ENTRY_COMPLETION(gtk_builder_get_object(sar->builder, "completion")), match_func, sar, nullptr);
299
300         g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "completion")), "match-selected", G_CALLBACK(match_selected_cb), sar);
301         g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "entry")), "key_press_event", G_CALLBACK(keypress_cb), sar);
302         g_signal_connect(G_OBJECT(gtk_builder_get_object(sar->builder, "entry")), "activate", G_CALLBACK(entry_box_activate_cb), sar);
303
304         gtk_widget_show(sar->window);
305
306         return sar->window;
307 }
308
309 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */