1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpsearchpopup.c
5 * Copyright (C) 2015 Jehan <jehan at girinstud.io>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25
26 #include <gdk/gdkkeysyms.h>
27
28 #include "libgimpbase/gimpbase.h"
29 #include "libgimpwidgets/gimpwidgets.h"
30
31 #include "widgets-types.h"
32
33 #include "core/gimp.h"
34
35 #include "gimpaction.h"
36 #include "gimppopup.h"
37 #include "gimpsearchpopup.h"
38 #include "gimptoggleaction.h"
39 #include "gimpuimanager.h"
40
41 #include "gimp-intl.h"
42
43
44 enum
45 {
46 COLUMN_ICON,
47 COLUMN_MARKUP,
48 COLUMN_TOOLTIP,
49 COLUMN_ACTION,
50 COLUMN_SENSITIVE,
51 COLUMN_SECTION,
52 N_COL
53 };
54
55 enum
56 {
57 PROP_0,
58 PROP_GIMP,
59 PROP_CALLBACK,
60 PROP_CALLBACK_DATA
61 };
62
63
64 struct _GimpSearchPopupPrivate
65 {
66 Gimp *gimp;
67 GtkWidget *keyword_entry;
68 GtkWidget *results_list;
69 GtkWidget *list_view;
70
71 GimpSearchPopupCallback build_results;
72 gpointer build_results_data;
73 };
74
75
76 static void gimp_search_popup_constructed (GObject *object);
77 static void gimp_search_popup_set_property (GObject *object,
78 guint property_id,
79 const GValue *value,
80 GParamSpec *pspec);
81 static void gimp_search_popup_get_property (GObject *object,
82 guint property_id,
83 GValue *value,
84 GParamSpec *pspec);
85
86 static void gimp_search_popup_size_allocate (GtkWidget *widget,
87 GtkAllocation *allocation);
88
89 static void gimp_search_popup_confirm (GimpPopup *popup);
90
91 /* Signal handlers on the search entry */
92 static gboolean keyword_entry_key_press_event (GtkWidget *widget,
93 GdkEventKey *event,
94 GimpSearchPopup *popup);
95 static gboolean keyword_entry_key_release_event (GtkWidget *widget,
96 GdkEventKey *event,
97 GimpSearchPopup *popup);
98
99 /* Signal handlers on the results list */
100 static gboolean results_list_key_press_event (GtkWidget *widget,
101 GdkEventKey *kevent,
102 GimpSearchPopup *popup);
103 static void results_list_row_activated (GtkTreeView *treeview,
104 GtkTreePath *path,
105 GtkTreeViewColumn *col,
106 GimpSearchPopup *popup);
107
108 /* Utils */
109 static void gimp_search_popup_run_selected (GimpSearchPopup *popup);
110 static void gimp_search_popup_setup_results (GtkWidget **results_list,
111 GtkWidget **list_view);
112
113 static gchar * gimp_search_popup_find_accel_label (GimpAction *action);
114 static gboolean gimp_search_popup_view_accel_find_func (GtkAccelKey *key,
115 GClosure *closure,
116 gpointer data);
117
118
119 G_DEFINE_TYPE_WITH_PRIVATE (GimpSearchPopup, gimp_search_popup, GIMP_TYPE_POPUP)
120
121 #define parent_class gimp_search_popup_parent_class
122
123 static gint window_height = 0;
124
125
126 static void
gimp_search_popup_class_init(GimpSearchPopupClass * klass)127 gimp_search_popup_class_init (GimpSearchPopupClass *klass)
128 {
129 GObjectClass *object_class = G_OBJECT_CLASS (klass);
130 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
131 GimpPopupClass *popup_class = GIMP_POPUP_CLASS (klass);
132
133 object_class->constructed = gimp_search_popup_constructed;
134 object_class->set_property = gimp_search_popup_set_property;
135 object_class->get_property = gimp_search_popup_get_property;
136
137 widget_class->size_allocate = gimp_search_popup_size_allocate;
138
139 popup_class->confirm = gimp_search_popup_confirm;
140
141 /**
142 * GimpSearchPopup:gimp:
143 *
144 * The #Gimp object.
145 */
146 g_object_class_install_property (object_class, PROP_GIMP,
147 g_param_spec_object ("gimp",
148 NULL, NULL,
149 G_TYPE_OBJECT,
150 G_PARAM_READWRITE |
151 G_PARAM_CONSTRUCT_ONLY));
152 /**
153 * GimpSearchPopup:callback:
154 *
155 * The #GimpSearchPopupCallback used to fill in results.
156 */
157 g_object_class_install_property (object_class, PROP_CALLBACK,
158 g_param_spec_pointer ("callback", NULL, NULL,
159 GIMP_PARAM_READWRITE |
160 G_PARAM_CONSTRUCT_ONLY));
161 /**
162 * GimpSearchPopup:callback-data:
163 *
164 * The #GPointer fed as last parameter to the #GimpSearchPopupCallback.
165 */
166 g_object_class_install_property (object_class, PROP_CALLBACK_DATA,
167 g_param_spec_pointer ("callback-data", NULL, NULL,
168 GIMP_PARAM_READWRITE |
169 G_PARAM_CONSTRUCT_ONLY));
170 }
171
172 static void
gimp_search_popup_init(GimpSearchPopup * search_popup)173 gimp_search_popup_init (GimpSearchPopup *search_popup)
174 {
175 search_popup->priv = gimp_search_popup_get_instance_private (search_popup);
176 }
177
178 /************ Public Functions ****************/
179
180 /**
181 * gimp_search_popup_new:
182 * @gimp: #Gimp object.
183 * @role: the role to give to the #GtkWindow.
184 * @title: the #GtkWindow title.
185 * @callback: the #GimpSearchPopupCallback used to fill in results.
186 * @callback_data: data fed to @callback.
187 *
188 * Returns: a new #GimpSearchPopup.
189 */
190 GtkWidget *
gimp_search_popup_new(Gimp * gimp,const gchar * role,const gchar * title,GimpSearchPopupCallback callback,gpointer callback_data)191 gimp_search_popup_new (Gimp *gimp,
192 const gchar *role,
193 const gchar *title,
194 GimpSearchPopupCallback callback,
195 gpointer callback_data)
196 {
197 GtkWidget *widget;
198
199 widget = g_object_new (GIMP_TYPE_SEARCH_POPUP,
200 "type", GTK_WINDOW_TOPLEVEL,
201 "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
202 "decorated", TRUE,
203 "modal", TRUE,
204 "role", role,
205 "title", title,
206
207 "gimp", gimp,
208 "callback", callback,
209 "callback-data", callback_data,
210 NULL);
211 gtk_window_set_modal (GTK_WINDOW (widget), FALSE);
212
213
214 return widget;
215 }
216
217 /**
218 * gimp_search_popup_add_result:
219 * @popup: the #GimpSearchPopup.
220 * @action: a #GimpAction to add in results list.
221 * @section: the section to add @action.
222 *
223 * Adds @action in the @popup's results at @section.
224 * The section only indicates relative order. If you want some items
225 * to appear before other, simply use lower @section.
226 */
227 void
gimp_search_popup_add_result(GimpSearchPopup * popup,GimpAction * action,gint section)228 gimp_search_popup_add_result (GimpSearchPopup *popup,
229 GimpAction *action,
230 gint section)
231 {
232 GtkTreeIter iter;
233 GtkTreeIter next_section;
234 GtkListStore *store;
235 GtkTreeModel *model;
236 gchar *markup;
237 gchar *action_name;
238 gchar *label;
239 gchar *escaped_label = NULL;
240 const gchar *icon_name;
241 gchar *accel_string;
242 gchar *escaped_accel = NULL;
243 gboolean has_shortcut = FALSE;
244 const gchar *tooltip;
245 gchar *escaped_tooltip = NULL;
246 gboolean has_tooltip = FALSE;
247
248 label = g_strstrip (gimp_strip_uline (gimp_action_get_label (action)));
249
250 if (! label || strlen (label) == 0)
251 {
252 g_free (label);
253 return;
254 }
255
256 escaped_label = g_markup_escape_text (label, -1);
257
258 if (GIMP_IS_TOGGLE_ACTION (action))
259 {
260 if (gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action)))
261 icon_name = "gtk-ok";
262 else
263 icon_name = "gtk-no";
264 }
265 else
266 {
267 icon_name = gimp_action_get_icon_name (action);
268 }
269
270 accel_string = gimp_search_popup_find_accel_label (action);
271 if (accel_string)
272 {
273 escaped_accel = g_markup_escape_text (accel_string, -1);
274 has_shortcut = TRUE;
275 }
276
277 tooltip = gimp_action_get_tooltip (action);
278 if (tooltip != NULL)
279 {
280 escaped_tooltip = g_markup_escape_text (tooltip, -1);
281 has_tooltip = TRUE;
282 }
283
284 markup = g_strdup_printf ("%s<small>%s%s%s<span weight='light'>%s</span></small>",
285 escaped_label,
286 has_shortcut ? " | " : "",
287 has_shortcut ? escaped_accel : "",
288 has_tooltip ? "\n" : "",
289 has_tooltip ? escaped_tooltip : "");
290
291 action_name = g_markup_escape_text (gimp_action_get_name (action), -1);
292
293 model = gtk_tree_view_get_model (GTK_TREE_VIEW (popup->priv->results_list));
294 store = GTK_LIST_STORE (model);
295 if (gtk_tree_model_get_iter_first (model, &next_section))
296 {
297 while (TRUE)
298 {
299 gint iter_section;
300
301 gtk_tree_model_get (model, &next_section,
302 COLUMN_SECTION, &iter_section, -1);
303 if (iter_section > section)
304 {
305 gtk_list_store_insert_before (store, &iter, &next_section);
306 break;
307 }
308 else if (! gtk_tree_model_iter_next (model, &next_section))
309 {
310 gtk_list_store_append (store, &iter);
311 break;
312 }
313 }
314 }
315 else
316 {
317 gtk_list_store_append (store, &iter);
318 }
319
320 gtk_list_store_set (store, &iter,
321 COLUMN_ICON, icon_name,
322 COLUMN_MARKUP, markup,
323 COLUMN_TOOLTIP, action_name,
324 COLUMN_ACTION, action,
325 COLUMN_SECTION, section,
326 COLUMN_SENSITIVE, gimp_action_is_sensitive (action),
327 -1);
328
329 g_free (accel_string);
330 g_free (markup);
331 g_free (action_name);
332 g_free (label);
333 g_free (escaped_accel);
334 g_free (escaped_label);
335 g_free (escaped_tooltip);
336 }
337
338 /************ Private Functions ****************/
339
340 static void
gimp_search_popup_constructed(GObject * object)341 gimp_search_popup_constructed (GObject *object)
342 {
343 GimpSearchPopup *popup = GIMP_SEARCH_POPUP (object);
344 GdkScreen *screen = gdk_screen_get_default ();
345 GtkWidget *main_vbox;
346
347 G_OBJECT_CLASS (parent_class)->constructed (object);
348
349 main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
350 gtk_container_add (GTK_CONTAINER (popup), main_vbox);
351 gtk_widget_show (main_vbox);
352
353 popup->priv->keyword_entry = gtk_entry_new ();
354 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popup->priv->keyword_entry),
355 GTK_ENTRY_ICON_PRIMARY, "edit-find");
356 gtk_entry_set_icon_activatable (GTK_ENTRY (popup->priv->keyword_entry),
357 GTK_ENTRY_ICON_PRIMARY, FALSE);
358 gtk_box_pack_start (GTK_BOX (main_vbox),
359 popup->priv->keyword_entry,
360 FALSE, FALSE, 0);
361 gtk_widget_show (popup->priv->keyword_entry);
362
363 gimp_search_popup_setup_results (&popup->priv->results_list,
364 &popup->priv->list_view);
365 gtk_box_pack_start (GTK_BOX (main_vbox),
366 popup->priv->list_view, TRUE, TRUE, 0);
367
368 gtk_widget_set_events (GTK_WIDGET (object),
369 GDK_KEY_RELEASE_MASK |
370 GDK_KEY_PRESS_MASK |
371 GDK_BUTTON_PRESS_MASK |
372 GDK_SCROLL_MASK);
373
374 g_signal_connect (popup->priv->keyword_entry, "key-press-event",
375 G_CALLBACK (keyword_entry_key_press_event),
376 popup);
377 g_signal_connect (popup->priv->keyword_entry, "key-release-event",
378 G_CALLBACK (keyword_entry_key_release_event),
379 popup);
380
381 g_signal_connect (popup->priv->results_list, "key-press-event",
382 G_CALLBACK (results_list_key_press_event),
383 popup);
384 g_signal_connect (popup->priv->results_list, "row-activated",
385 G_CALLBACK (results_list_row_activated),
386 popup);
387
388 /* Default size of the search popup showing the result list is half
389 * the screen. */
390 if (window_height == 0)
391 window_height = gdk_screen_get_height (screen) / 2;
392 }
393
394 static void
gimp_search_popup_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)395 gimp_search_popup_set_property (GObject *object,
396 guint property_id,
397 const GValue *value,
398 GParamSpec *pspec)
399 {
400 GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (object);
401
402 switch (property_id)
403 {
404 case PROP_GIMP:
405 search_popup->priv->gimp = g_value_get_object (value);
406 break;
407 case PROP_CALLBACK:
408 search_popup->priv->build_results = g_value_get_pointer (value);
409 break;
410 case PROP_CALLBACK_DATA:
411 search_popup->priv->build_results_data = g_value_get_pointer (value);
412 break;
413
414 default:
415 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
416 break;
417 }
418 }
419
420 static void
gimp_search_popup_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)421 gimp_search_popup_get_property (GObject *object,
422 guint property_id,
423 GValue *value,
424 GParamSpec *pspec)
425 {
426 GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (object);
427
428 switch (property_id)
429 {
430 case PROP_GIMP:
431 g_value_set_object (value, search_popup->priv->gimp);
432 break;
433 case PROP_CALLBACK:
434 g_value_set_pointer (value, search_popup->priv->build_results);
435 break;
436 case PROP_CALLBACK_DATA:
437 g_value_set_pointer (value, search_popup->priv->build_results_data);
438 break;
439
440 default:
441 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
442 break;
443 }
444 }
445
446 static void
gimp_search_popup_size_allocate(GtkWidget * widget,GtkAllocation * allocation)447 gimp_search_popup_size_allocate (GtkWidget *widget,
448 GtkAllocation *allocation)
449 {
450 GimpSearchPopup *popup = GIMP_SEARCH_POPUP (widget);
451
452 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
453
454 if (gtk_widget_get_visible (widget) &&
455 gtk_widget_get_visible (popup->priv->list_view))
456 {
457 GdkScreen *screen = gdk_screen_get_default ();
458
459 /* Save the window height when results are shown so that resizes
460 * by the user are saved across searches.
461 */
462 window_height = MAX (gdk_screen_get_height (screen) / 4,
463 allocation->height);
464 }
465 }
466
467 static void
gimp_search_popup_confirm(GimpPopup * popup)468 gimp_search_popup_confirm (GimpPopup *popup)
469 {
470 GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (popup);
471
472 gimp_search_popup_run_selected (search_popup);
473 }
474
475 static gboolean
keyword_entry_key_press_event(GtkWidget * widget,GdkEventKey * event,GimpSearchPopup * popup)476 keyword_entry_key_press_event (GtkWidget *widget,
477 GdkEventKey *event,
478 GimpSearchPopup *popup)
479 {
480 gboolean event_processed = FALSE;
481
482 if (event->keyval == GDK_KEY_Down &&
483 gtk_widget_get_visible (popup->priv->list_view))
484 {
485 GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list);
486
487 /* When hitting the down key while editing, select directly the
488 * second item, since the first could have run directly with
489 * Enter. */
490 gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view),
491 gtk_tree_path_new_from_string ("1"));
492 gtk_widget_grab_focus (GTK_WIDGET (popup->priv->results_list));
493 event_processed = TRUE;
494 }
495
496 return event_processed;
497 }
498
499 static gboolean
keyword_entry_key_release_event(GtkWidget * widget,GdkEventKey * event,GimpSearchPopup * popup)500 keyword_entry_key_release_event (GtkWidget *widget,
501 GdkEventKey *event,
502 GimpSearchPopup *popup)
503 {
504 GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list);
505 gchar *entry_text;
506 gint width;
507
508 /* These keys are already managed by key bindings. */
509 if (event->keyval == GDK_KEY_Escape ||
510 event->keyval == GDK_KEY_Return ||
511 event->keyval == GDK_KEY_KP_Enter ||
512 event->keyval == GDK_KEY_ISO_Enter)
513 {
514 return FALSE;
515 }
516
517 gtk_window_get_size (GTK_WINDOW (popup), &width, NULL);
518 entry_text = g_strstrip (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1));
519
520 if (strcmp (entry_text, "") != 0)
521 {
522 gtk_window_resize (GTK_WINDOW (popup),
523 width, window_height);
524 gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view)));
525 gtk_widget_show_all (popup->priv->list_view);
526 popup->priv->build_results (popup, entry_text,
527 popup->priv->build_results_data);
528 gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view),
529 gtk_tree_path_new_from_string ("0"));
530 }
531 else if (strcmp (entry_text, "") == 0 && (event->keyval == GDK_KEY_Down))
532 {
533 gtk_window_resize (GTK_WINDOW (popup),
534 width, window_height);
535 gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view)));
536 gtk_widget_show_all (popup->priv->list_view);
537 popup->priv->build_results (popup, NULL,
538 popup->priv->build_results_data);
539 gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view),
540 gtk_tree_path_new_from_string ("0"));
541 }
542 else
543 {
544 GtkTreeSelection *selection;
545 GtkTreeModel *model;
546 GtkTreeIter iter;
547
548 selection = gtk_tree_view_get_selection (tree_view);
549 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
550
551 if (gtk_tree_selection_get_selected (selection, &model, &iter))
552 {
553 GtkTreePath *path;
554
555 path = gtk_tree_model_get_path (model, &iter);
556 gtk_tree_selection_unselect_path (selection, path);
557
558 gtk_tree_path_free (path);
559 }
560
561 gtk_widget_hide (popup->priv->list_view);
562 gtk_window_resize (GTK_WINDOW (popup), width, 1);
563 }
564
565 g_free (entry_text);
566
567 return TRUE;
568 }
569
570 static gboolean
results_list_key_press_event(GtkWidget * widget,GdkEventKey * kevent,GimpSearchPopup * popup)571 results_list_key_press_event (GtkWidget *widget,
572 GdkEventKey *kevent,
573 GimpSearchPopup *popup)
574 {
575 /* These keys are already managed by key bindings. */
576 g_return_val_if_fail (kevent->keyval != GDK_KEY_Escape &&
577 kevent->keyval != GDK_KEY_Return &&
578 kevent->keyval != GDK_KEY_KP_Enter &&
579 kevent->keyval != GDK_KEY_ISO_Enter,
580 FALSE);
581
582 switch (kevent->keyval)
583 {
584 case GDK_KEY_Up:
585 {
586 gboolean event_processed = FALSE;
587 GtkTreeSelection *selection;
588 GtkTreeModel *model;
589 GtkTreeIter iter;
590
591 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list));
592 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
593
594 if (gtk_tree_selection_get_selected (selection, &model, &iter))
595 {
596 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
597
598 if (strcmp (gtk_tree_path_to_string (path), "0") == 0)
599 {
600 gint start_pos;
601 gint end_pos;
602
603 gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry),
604 &start_pos, &end_pos);
605 gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry)));
606 gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry),
607 start_pos, end_pos);
608
609 event_processed = TRUE;
610 }
611
612 gtk_tree_path_free (path);
613 }
614
615 return event_processed;
616 }
617 case GDK_KEY_Down:
618 {
619 return FALSE;
620 }
621 default:
622 {
623 gint start_pos;
624 gint end_pos;
625
626 gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry),
627 &start_pos, &end_pos);
628 gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry)));
629 gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry),
630 start_pos, end_pos);
631 gtk_widget_event (GTK_WIDGET (popup->priv->keyword_entry),
632 (GdkEvent *) kevent);
633 }
634 }
635
636 return FALSE;
637 }
638
639 static void
results_list_row_activated(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * col,GimpSearchPopup * popup)640 results_list_row_activated (GtkTreeView *treeview,
641 GtkTreePath *path,
642 GtkTreeViewColumn *col,
643 GimpSearchPopup *popup)
644 {
645 gimp_search_popup_run_selected (popup);
646 }
647
648 static void
gimp_search_popup_run_selected(GimpSearchPopup * popup)649 gimp_search_popup_run_selected (GimpSearchPopup *popup)
650 {
651 GtkTreeSelection *selection;
652 GtkTreeModel *model;
653 GtkTreeIter iter;
654
655 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list));
656 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
657
658 if (gtk_tree_selection_get_selected (selection, &model, &iter))
659 {
660 GimpAction *action;
661
662 gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1);
663
664 if (gimp_action_is_sensitive (action))
665 {
666 /* Close the search popup on activation. */
667 GIMP_POPUP_CLASS (parent_class)->cancel (GIMP_POPUP (popup));
668
669 gimp_action_activate (action);
670 }
671
672 g_object_unref (action);
673 }
674 }
675
676 static void
gimp_search_popup_setup_results(GtkWidget ** results_list,GtkWidget ** list_view)677 gimp_search_popup_setup_results (GtkWidget **results_list,
678 GtkWidget **list_view)
679 {
680 gint wid1 = 100;
681 GtkListStore *store;
682 GtkCellRenderer *cell;
683 GtkTreeViewColumn *column;
684
685 *list_view = gtk_scrolled_window_new (NULL, NULL);
686 store = gtk_list_store_new (N_COL,
687 G_TYPE_STRING,
688 G_TYPE_STRING,
689 G_TYPE_STRING,
690 GIMP_TYPE_ACTION,
691 G_TYPE_BOOLEAN,
692 G_TYPE_INT);
693 *results_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
694 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*results_list), FALSE);
695 #ifdef GIMP_UNSTABLE
696 gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (*results_list),
697 COLUMN_TOOLTIP);
698 #endif
699
700 cell = gtk_cell_renderer_pixbuf_new ();
701 column = gtk_tree_view_column_new_with_attributes (NULL, cell,
702 "icon-name", COLUMN_ICON,
703 "sensitive", COLUMN_SENSITIVE,
704 NULL);
705 gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column);
706 gtk_tree_view_column_set_min_width (column, 22);
707
708 cell = gtk_cell_renderer_text_new ();
709 column = gtk_tree_view_column_new_with_attributes (NULL, cell,
710 "markup", COLUMN_MARKUP,
711 "sensitive", COLUMN_SENSITIVE,
712 NULL);
713 gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column);
714 gtk_tree_view_column_set_max_width (column, wid1);
715
716 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*list_view),
717 GTK_POLICY_NEVER,
718 GTK_POLICY_AUTOMATIC);
719
720 gtk_container_add (GTK_CONTAINER (*list_view), *results_list);
721 g_object_unref (G_OBJECT (store));
722 }
723
724 static gchar *
gimp_search_popup_find_accel_label(GimpAction * action)725 gimp_search_popup_find_accel_label (GimpAction *action)
726 {
727 guint accel_key = 0;
728 GdkModifierType accel_mask = 0;
729 GClosure *accel_closure = NULL;
730 gchar *accel_string;
731 GtkAccelGroup *accel_group;
732 GimpUIManager *manager;
733
734 manager = gimp_ui_managers_from_name ("<Image>")->data;
735 accel_group = gimp_ui_manager_get_accel_group (manager);
736 accel_closure = gimp_action_get_accel_closure (action);
737
738 if (accel_closure)
739 {
740 GtkAccelKey *key;
741
742 key = gtk_accel_group_find (accel_group,
743 gimp_search_popup_view_accel_find_func,
744 accel_closure);
745 if (key &&
746 key->accel_key &&
747 key->accel_flags & GTK_ACCEL_VISIBLE)
748 {
749 accel_key = key->accel_key;
750 accel_mask = key->accel_mods;
751 }
752 }
753
754 accel_string = gtk_accelerator_get_label (accel_key, accel_mask);
755
756 if (strcmp (g_strstrip (accel_string), "") == 0)
757 {
758 /* The value returned by gtk_accelerator_get_label() must be
759 * freed after use.
760 */
761 g_clear_pointer (&accel_string, g_free);
762 }
763
764 return accel_string;
765 }
766
767 static gboolean
gimp_search_popup_view_accel_find_func(GtkAccelKey * key,GClosure * closure,gpointer data)768 gimp_search_popup_view_accel_find_func (GtkAccelKey *key,
769 GClosure *closure,
770 gpointer data)
771 {
772 return (GClosure *) data == closure;
773 }
774