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