1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpactionview.c
5  * Copyright (C) 2004-2005  Michael Natterer <mitch@gimp.org>
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 <string.h>
24 
25 #include <gegl.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28 
29 #include "libgimpbase/gimpbase.h"
30 #include "libgimpwidgets/gimpwidgets.h"
31 
32 #include "widgets-types.h"
33 
34 #include "core/gimp.h"
35 
36 #include "gimpaction.h"
37 #include "gimpactiongroup.h"
38 #include "gimpactionview.h"
39 #include "gimpmessagebox.h"
40 #include "gimpmessagedialog.h"
41 #include "gimpuimanager.h"
42 
43 #include "gimp-intl.h"
44 
45 
46 /*  local function prototypes  */
47 
48 static void     gimp_action_view_dispose         (GObject         *object);
49 static void     gimp_action_view_finalize        (GObject         *object);
50 
51 static void     gimp_action_view_select_path     (GimpActionView  *view,
52                                                   GtkTreePath     *path);
53 static gboolean gimp_action_view_accel_find_func (GtkAccelKey     *key,
54                                                   GClosure        *closure,
55                                                   gpointer         data);
56 static void     gimp_action_view_accel_changed   (GtkAccelGroup   *accel_group,
57                                                   guint            unused1,
58                                                   GdkModifierType  unused2,
59                                                   GClosure        *accel_closure,
60                                                   GimpActionView  *view);
61 static void     gimp_action_view_accel_edited    (GtkCellRendererAccel *accel,
62                                                   const char      *path_string,
63                                                   guint            accel_key,
64                                                   GdkModifierType  accel_mask,
65                                                   guint            hardware_keycode,
66                                                   GimpActionView  *view);
67 static void     gimp_action_view_accel_cleared   (GtkCellRendererAccel *accel,
68                                                   const char      *path_string,
69                                                   GimpActionView  *view);
70 
71 
G_DEFINE_TYPE(GimpActionView,gimp_action_view,GTK_TYPE_TREE_VIEW)72 G_DEFINE_TYPE (GimpActionView, gimp_action_view, GTK_TYPE_TREE_VIEW)
73 
74 #define parent_class gimp_action_view_parent_class
75 
76 
77 static void
78 gimp_action_view_class_init (GimpActionViewClass *klass)
79 {
80   GObjectClass *object_class = G_OBJECT_CLASS (klass);
81 
82   object_class->dispose  = gimp_action_view_dispose;
83   object_class->finalize = gimp_action_view_finalize;
84 }
85 
86 static void
gimp_action_view_init(GimpActionView * view)87 gimp_action_view_init (GimpActionView *view)
88 {
89 }
90 
91 static void
gimp_action_view_dispose(GObject * object)92 gimp_action_view_dispose (GObject *object)
93 {
94   GimpActionView *view = GIMP_ACTION_VIEW (object);
95 
96   if (view->manager)
97     {
98       if (view->show_shortcuts)
99         {
100           GtkAccelGroup *group;
101 
102           group = gimp_ui_manager_get_accel_group (view->manager);
103 
104           g_signal_handlers_disconnect_by_func (group,
105                                                 gimp_action_view_accel_changed,
106                                                 view);
107         }
108 
109       g_clear_object (&view->manager);
110     }
111 
112   G_OBJECT_CLASS (parent_class)->dispose (object);
113 }
114 
115 static void
gimp_action_view_finalize(GObject * object)116 gimp_action_view_finalize (GObject *object)
117 {
118   GimpActionView *view = GIMP_ACTION_VIEW (object);
119 
120   g_clear_pointer (&view->filter, g_free);
121 
122   G_OBJECT_CLASS (parent_class)->finalize (object);
123 }
124 
125 static gboolean
idle_start_editing(GtkTreeView * tree_view)126 idle_start_editing (GtkTreeView *tree_view)
127 {
128   GtkTreePath *path;
129 
130   path = g_object_get_data (G_OBJECT (tree_view), "start-editing-path");
131 
132   if (path)
133     {
134       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
135 
136       gtk_tree_view_set_cursor (tree_view, path,
137                                 gtk_tree_view_get_column (tree_view, 1),
138                                 TRUE);
139 
140       g_object_set_data (G_OBJECT (tree_view), "start-editing-path", NULL);
141     }
142 
143   return FALSE;
144 }
145 
146 static gboolean
gimp_action_view_button_press(GtkWidget * widget,GdkEventButton * event)147 gimp_action_view_button_press (GtkWidget      *widget,
148                                GdkEventButton *event)
149 {
150   GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
151   GtkTreePath *path;
152 
153   if (event->window != gtk_tree_view_get_bin_window (tree_view))
154     return FALSE;
155 
156   if (gtk_tree_view_get_path_at_pos (tree_view,
157                                      (gint) event->x,
158                                      (gint) event->y,
159                                      &path, NULL,
160                                      NULL, NULL))
161     {
162       GClosure *closure;
163       GSource  *source;
164 
165       if (gtk_tree_path_get_depth (path) == 1)
166         {
167           gtk_tree_path_free (path);
168           return FALSE;
169         }
170 
171       g_object_set_data_full (G_OBJECT (tree_view), "start-editing-path",
172                               path, (GDestroyNotify) gtk_tree_path_free);
173 
174       g_signal_stop_emission_by_name (tree_view, "button-press-event");
175 
176       closure = g_cclosure_new_object (G_CALLBACK (idle_start_editing),
177                                        G_OBJECT (tree_view));
178 
179       source = g_idle_source_new ();
180       g_source_set_closure (source, closure);
181       g_source_attach (source, NULL);
182       g_source_unref (source);
183     }
184 
185   return TRUE;
186 }
187 
188 GtkWidget *
gimp_action_view_new(GimpUIManager * manager,const gchar * select_action,gboolean show_shortcuts)189 gimp_action_view_new (GimpUIManager *manager,
190                       const gchar   *select_action,
191                       gboolean       show_shortcuts)
192 {
193   GtkTreeView       *view;
194   GtkTreeViewColumn *column;
195   GtkCellRenderer   *cell;
196   GtkTreeStore      *store;
197   GtkTreeModel      *filter;
198   GtkAccelGroup     *accel_group;
199   GList             *list;
200   GtkTreePath       *select_path = NULL;
201 
202   g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL);
203 
204   store = gtk_tree_store_new (GIMP_ACTION_VIEW_N_COLUMNS,
205                               G_TYPE_BOOLEAN,         /* COLUMN_VISIBLE        */
206                               GIMP_TYPE_ACTION,       /* COLUMN_ACTION         */
207                               G_TYPE_STRING,          /* COLUMN_ICON_NAME      */
208                               G_TYPE_STRING,          /* COLUMN_LABEL          */
209                               G_TYPE_STRING,          /* COLUMN_LABEL_CASEFOLD */
210                               G_TYPE_STRING,          /* COLUMN_NAME           */
211                               G_TYPE_UINT,            /* COLUMN_ACCEL_KEY      */
212                               GDK_TYPE_MODIFIER_TYPE, /* COLUMN_ACCEL_MASK     */
213                               G_TYPE_CLOSURE);        /* COLUMN_ACCEL_CLOSURE  */
214 
215   accel_group = gimp_ui_manager_get_accel_group (manager);
216 
217   for (list = gimp_ui_manager_get_action_groups (manager);
218        list;
219        list = g_list_next (list))
220     {
221       GimpActionGroup *group = list->data;
222       GList           *actions;
223       GList           *list2;
224       GtkTreeIter      group_iter;
225 
226       gtk_tree_store_append (store, &group_iter, NULL);
227 
228       gtk_tree_store_set (store, &group_iter,
229                           GIMP_ACTION_VIEW_COLUMN_ICON_NAME, group->icon_name,
230                           GIMP_ACTION_VIEW_COLUMN_LABEL,     group->label,
231                           -1);
232 
233       actions = gimp_action_group_list_actions (group);
234 
235       actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare);
236 
237       for (list2 = actions; list2; list2 = g_list_next (list2))
238         {
239           GimpAction      *action        = list2->data;
240           const gchar     *name          = gimp_action_get_name (action);
241           const gchar     *icon_name     = gimp_action_get_icon_name (action);
242           gchar           *label;
243           gchar           *label_casefold;
244           guint            accel_key     = 0;
245           GdkModifierType  accel_mask    = 0;
246           GClosure        *accel_closure = NULL;
247           GtkTreeIter      action_iter;
248 
249           if (gimp_action_is_gui_blacklisted (name))
250             continue;
251 
252           label = gimp_strip_uline (gimp_action_get_label (action));
253 
254           if (! (label && strlen (label)))
255             {
256               g_free (label);
257               label = g_strdup (name);
258             }
259 
260           label_casefold = g_utf8_casefold (label, -1);
261 
262           if (show_shortcuts)
263             {
264               accel_closure = gimp_action_get_accel_closure (action);
265 
266               if (accel_closure)
267                 {
268                   GtkAccelKey *key;
269 
270                   key = gtk_accel_group_find (accel_group,
271                                               gimp_action_view_accel_find_func,
272                                               accel_closure);
273 
274                   if (key            &&
275                       key->accel_key &&
276                       key->accel_flags & GTK_ACCEL_VISIBLE)
277                     {
278                       accel_key  = key->accel_key;
279                       accel_mask = key->accel_mods;
280                     }
281                 }
282             }
283 
284           gtk_tree_store_append (store, &action_iter, &group_iter);
285 
286           gtk_tree_store_set (store, &action_iter,
287                               GIMP_ACTION_VIEW_COLUMN_VISIBLE,        TRUE,
288                               GIMP_ACTION_VIEW_COLUMN_ACTION,         action,
289                               GIMP_ACTION_VIEW_COLUMN_ICON_NAME,      icon_name,
290                               GIMP_ACTION_VIEW_COLUMN_LABEL,          label,
291                               GIMP_ACTION_VIEW_COLUMN_LABEL_CASEFOLD, label_casefold,
292                               GIMP_ACTION_VIEW_COLUMN_NAME,           name,
293                               GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY,      accel_key,
294                               GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK,     accel_mask,
295                               GIMP_ACTION_VIEW_COLUMN_ACCEL_CLOSURE,  accel_closure,
296                               -1);
297 
298           g_free (label);
299           g_free (label_casefold);
300 
301           if (select_action && ! strcmp (select_action, name))
302             {
303               select_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store),
304                                                      &action_iter);
305             }
306         }
307 
308       g_list_free (actions);
309     }
310 
311   filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
312 
313   g_object_unref (store);
314 
315   view = g_object_new (GIMP_TYPE_ACTION_VIEW,
316                        "model",      filter,
317                        "rules-hint", TRUE,
318                        NULL);
319 
320   g_object_unref (filter);
321 
322   gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter),
323                                             GIMP_ACTION_VIEW_COLUMN_VISIBLE);
324 
325   GIMP_ACTION_VIEW (view)->manager        = g_object_ref (manager);
326   GIMP_ACTION_VIEW (view)->show_shortcuts = show_shortcuts;
327 
328   gtk_tree_view_set_search_column (GTK_TREE_VIEW (view),
329                                    GIMP_ACTION_VIEW_COLUMN_LABEL);
330 
331   column = gtk_tree_view_column_new ();
332   gtk_tree_view_column_set_title (column, _("Action"));
333 
334   cell = gtk_cell_renderer_pixbuf_new ();
335   gtk_tree_view_column_pack_start (column, cell, FALSE);
336   gtk_tree_view_column_set_attributes (column, cell,
337                                        "icon-name",
338                                        GIMP_ACTION_VIEW_COLUMN_ICON_NAME,
339                                        NULL);
340 
341   cell = gtk_cell_renderer_text_new ();
342   gtk_tree_view_column_pack_start (column, cell, TRUE);
343   gtk_tree_view_column_set_attributes (column, cell,
344                                        "text",
345                                        GIMP_ACTION_VIEW_COLUMN_LABEL,
346                                        NULL);
347 
348   gtk_tree_view_append_column (view, column);
349 
350   if (show_shortcuts)
351     {
352       g_signal_connect (view, "button-press-event",
353                         G_CALLBACK (gimp_action_view_button_press),
354                         NULL);
355 
356       g_signal_connect (accel_group, "accel-changed",
357                         G_CALLBACK (gimp_action_view_accel_changed),
358                         view);
359 
360       column = gtk_tree_view_column_new ();
361       gtk_tree_view_column_set_title (column, _("Shortcut"));
362 
363       cell = gtk_cell_renderer_accel_new ();
364       g_object_set (cell,
365                     "mode",     GTK_CELL_RENDERER_MODE_EDITABLE,
366                     "editable", TRUE,
367                     NULL);
368       gtk_tree_view_column_pack_start (column, cell, TRUE);
369       gtk_tree_view_column_set_attributes (column, cell,
370                                            "accel-key",
371                                            GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY,
372                                            "accel-mods",
373                                            GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK,
374                                            NULL);
375 
376       g_signal_connect (cell, "accel-edited",
377                         G_CALLBACK (gimp_action_view_accel_edited),
378                         view);
379       g_signal_connect (cell, "accel-cleared",
380                         G_CALLBACK (gimp_action_view_accel_cleared),
381                         view);
382 
383       gtk_tree_view_append_column (view, column);
384     }
385 
386   column = gtk_tree_view_column_new ();
387   gtk_tree_view_column_set_title (column, _("Name"));
388 
389   cell = gtk_cell_renderer_text_new ();
390   gtk_tree_view_column_pack_start (column, cell, TRUE);
391   gtk_tree_view_column_set_attributes (column, cell,
392                                        "text",
393                                        GIMP_ACTION_VIEW_COLUMN_NAME,
394                                        NULL);
395 
396   gtk_tree_view_append_column (view, column);
397 
398   if (select_path)
399     {
400       gimp_action_view_select_path (GIMP_ACTION_VIEW (view), select_path);
401       gtk_tree_path_free (select_path);
402     }
403 
404   return GTK_WIDGET (view);
405 }
406 
407 void
gimp_action_view_set_filter(GimpActionView * view,const gchar * filter)408 gimp_action_view_set_filter (GimpActionView *view,
409                              const gchar    *filter)
410 {
411   GtkTreeSelection    *sel;
412   GtkTreeModel        *filtered_model;
413   GtkTreeModel        *model;
414   GtkTreeIter          iter;
415   gboolean             iter_valid;
416   GtkTreeRowReference *selected_row = NULL;
417 
418   g_return_if_fail (GIMP_IS_ACTION_VIEW (view));
419 
420   filtered_model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
421   model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filtered_model));
422 
423   if (filter && ! strlen (filter))
424     filter = NULL;
425 
426   g_clear_pointer (&view->filter, g_free);
427 
428   if (filter)
429     view->filter = g_utf8_casefold (filter, -1);
430 
431   sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
432 
433   if (gtk_tree_selection_get_selected (sel, NULL, &iter))
434     {
435       GtkTreePath *path = gtk_tree_model_get_path (filtered_model, &iter);
436 
437       selected_row = gtk_tree_row_reference_new (filtered_model, path);
438     }
439 
440   for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
441        iter_valid;
442        iter_valid = gtk_tree_model_iter_next (model, &iter))
443     {
444       GtkTreeIter child_iter;
445       gboolean    child_valid;
446       gint        n_children = 0;
447 
448       for (child_valid = gtk_tree_model_iter_children (model, &child_iter,
449                                                        &iter);
450            child_valid;
451            child_valid = gtk_tree_model_iter_next (model, &child_iter))
452         {
453           gboolean visible = TRUE;
454 
455           if (view->filter)
456             {
457               gchar *label;
458               gchar *name;
459 
460               gtk_tree_model_get (model, &child_iter,
461                                   GIMP_ACTION_VIEW_COLUMN_LABEL_CASEFOLD, &label,
462                                   GIMP_ACTION_VIEW_COLUMN_NAME,           &name,
463                                   -1);
464 
465               visible = label && name && (strstr (label, view->filter) != NULL ||
466                                           strstr (name,  view->filter) != NULL);
467 
468               g_free (label);
469               g_free (name);
470             }
471 
472           gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter,
473                               GIMP_ACTION_VIEW_COLUMN_VISIBLE, visible,
474                               -1);
475 
476           if (visible)
477             n_children++;
478         }
479 
480       gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
481                           GIMP_ACTION_VIEW_COLUMN_VISIBLE, n_children > 0,
482                           -1);
483     }
484 
485   if (view->filter)
486     gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
487   else
488     gtk_tree_view_collapse_all (GTK_TREE_VIEW (view));
489 
490   gtk_tree_view_columns_autosize (GTK_TREE_VIEW (view));
491 
492   if (selected_row)
493     {
494       if (gtk_tree_row_reference_valid (selected_row))
495         {
496           GtkTreePath *path = gtk_tree_row_reference_get_path (selected_row);
497 
498           gimp_action_view_select_path (view, path);
499           gtk_tree_path_free (path);
500         }
501 
502       gtk_tree_row_reference_free (selected_row);
503     }
504 }
505 
506 
507 /*  private functions  */
508 
509 static void
gimp_action_view_select_path(GimpActionView * view,GtkTreePath * path)510 gimp_action_view_select_path (GimpActionView *view,
511                               GtkTreePath    *path)
512 {
513   GtkTreeView *tv = GTK_TREE_VIEW (view);
514   GtkTreePath *expand;
515 
516   expand = gtk_tree_path_copy (path);
517   gtk_tree_path_up (expand);
518   gtk_tree_view_expand_row (tv, expand, FALSE);
519   gtk_tree_path_free (expand);
520 
521   gtk_tree_view_set_cursor (tv, path, NULL, FALSE);
522   gtk_tree_view_scroll_to_cell (tv, path, NULL, TRUE, 0.5, 0.0);
523 }
524 
525 static gboolean
gimp_action_view_accel_find_func(GtkAccelKey * key,GClosure * closure,gpointer data)526 gimp_action_view_accel_find_func (GtkAccelKey *key,
527                                   GClosure    *closure,
528                                   gpointer     data)
529 {
530   return (GClosure *) data == closure;
531 }
532 
533 static void
gimp_action_view_accel_changed(GtkAccelGroup * accel_group,guint unused1,GdkModifierType unused2,GClosure * accel_closure,GimpActionView * view)534 gimp_action_view_accel_changed (GtkAccelGroup   *accel_group,
535                                 guint            unused1,
536                                 GdkModifierType  unused2,
537                                 GClosure        *accel_closure,
538                                 GimpActionView  *view)
539 {
540   GtkTreeModel *model;
541   GtkTreeIter   iter;
542   gboolean      iter_valid;
543 
544   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
545   if (! model)
546     return;
547 
548   model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
549   if (! model)
550     return;
551 
552   for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
553        iter_valid;
554        iter_valid = gtk_tree_model_iter_next (model, &iter))
555     {
556       GtkTreeIter child_iter;
557       gboolean    child_valid;
558 
559       for (child_valid = gtk_tree_model_iter_children (model, &child_iter,
560                                                        &iter);
561            child_valid;
562            child_valid = gtk_tree_model_iter_next (model, &child_iter))
563         {
564           GClosure *closure;
565 
566           gtk_tree_model_get (model, &child_iter,
567                               GIMP_ACTION_VIEW_COLUMN_ACCEL_CLOSURE, &closure,
568                               -1);
569 
570           if (closure)
571             g_closure_unref (closure);
572 
573           if (accel_closure == closure)
574             {
575               GtkAccelKey     *key;
576               guint            accel_key  = 0;
577               GdkModifierType  accel_mask = 0;
578 
579               key = gtk_accel_group_find (accel_group,
580                                           gimp_action_view_accel_find_func,
581                                           accel_closure);
582 
583               if (key            &&
584                   key->accel_key &&
585                   key->accel_flags & GTK_ACCEL_VISIBLE)
586                 {
587                   accel_key  = key->accel_key;
588                   accel_mask = key->accel_mods;
589                 }
590 
591               gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter,
592                                   GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY,  accel_key,
593                                   GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, accel_mask,
594                                   -1);
595 
596               return;
597             }
598         }
599     }
600 }
601 
602 typedef struct
603 {
604   GimpUIManager   *manager;
605   gchar           *accel_path;
606   guint            accel_key;
607   GdkModifierType  accel_mask;
608 } ConfirmData;
609 
610 static void
gimp_action_view_conflict_response(GtkWidget * dialog,gint response_id,ConfirmData * confirm_data)611 gimp_action_view_conflict_response (GtkWidget   *dialog,
612                                     gint         response_id,
613                                     ConfirmData *confirm_data)
614 {
615   gtk_widget_destroy (dialog);
616 
617   if (response_id == GTK_RESPONSE_OK)
618     {
619       if (! gtk_accel_map_change_entry (confirm_data->accel_path,
620                                         confirm_data->accel_key,
621                                         confirm_data->accel_mask,
622                                         TRUE))
623         {
624           gimp_message_literal (confirm_data->manager->gimp, G_OBJECT (dialog),
625                                 GIMP_MESSAGE_ERROR,
626                                 _("Changing shortcut failed."));
627         }
628     }
629 
630   g_free (confirm_data->accel_path);
631 
632   g_slice_free (ConfirmData, confirm_data);
633 }
634 
635 static void
gimp_action_view_conflict_confirm(GimpActionView * view,GimpAction * action,guint accel_key,GdkModifierType accel_mask,const gchar * accel_path)636 gimp_action_view_conflict_confirm (GimpActionView  *view,
637                                    GimpAction      *action,
638                                    guint            accel_key,
639                                    GdkModifierType  accel_mask,
640                                    const gchar     *accel_path)
641 {
642   GimpActionGroup *group;
643   gchar           *label;
644   gchar           *accel_string;
645   ConfirmData     *confirm_data;
646   GtkWidget       *dialog;
647   GimpMessageBox  *box;
648 
649   g_object_get (action, "action-group", &group, NULL);
650 
651   label = gimp_strip_uline (gimp_action_get_label (action));
652 
653   accel_string = gtk_accelerator_get_label (accel_key, accel_mask);
654 
655   confirm_data = g_slice_new (ConfirmData);
656 
657   confirm_data->manager    = view->manager;
658   confirm_data->accel_path = g_strdup (accel_path);
659   confirm_data->accel_key  = accel_key;
660   confirm_data->accel_mask = accel_mask;
661 
662   dialog =
663     gimp_message_dialog_new (_("Conflicting Shortcuts"),
664                              GIMP_ICON_DIALOG_WARNING,
665                              gtk_widget_get_toplevel (GTK_WIDGET (view)), 0,
666                              gimp_standard_help_func, NULL,
667 
668                              _("_Cancel"),            GTK_RESPONSE_CANCEL,
669                              _("_Reassign Shortcut"), GTK_RESPONSE_OK,
670 
671                              NULL);
672 
673   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
674                                            GTK_RESPONSE_OK,
675                                            GTK_RESPONSE_CANCEL,
676                                            -1);
677 
678   g_signal_connect (dialog, "response",
679                     G_CALLBACK (gimp_action_view_conflict_response),
680                     confirm_data);
681 
682   box = GIMP_MESSAGE_DIALOG (dialog)->box;
683 
684   gimp_message_box_set_primary_text (box,
685                                      _("Shortcut \"%s\" is already taken "
686                                        "by \"%s\" from the \"%s\" group."),
687                                      accel_string, label, group->label);
688   gimp_message_box_set_text (box,
689                              _("Reassigning the shortcut will cause it "
690                                "to be removed from \"%s\"."),
691                              label);
692 
693   g_free (label);
694   g_free (accel_string);
695 
696   g_object_unref (group);
697 
698   gtk_widget_show (dialog);
699 }
700 
701 static const gchar *
gimp_action_view_get_accel_action(GimpActionView * view,const gchar * path_string,GimpAction ** action_return,guint * action_accel_key,GdkModifierType * action_accel_mask)702 gimp_action_view_get_accel_action (GimpActionView  *view,
703                                    const gchar     *path_string,
704                                    GimpAction     **action_return,
705                                    guint           *action_accel_key,
706                                    GdkModifierType *action_accel_mask)
707 {
708   GtkTreeModel *model;
709   GtkTreePath  *path;
710   GtkTreeIter   iter;
711 
712   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
713   if (! model)
714     return NULL;
715 
716   path = gtk_tree_path_new_from_string (path_string);
717 
718   if (gtk_tree_model_get_iter (model, &iter, path))
719     {
720       GimpAction *action;
721 
722       gtk_tree_model_get (model, &iter,
723                           GIMP_ACTION_VIEW_COLUMN_ACTION,     &action,
724                           GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY,  action_accel_key,
725                           GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, action_accel_mask,
726                           -1);
727 
728       if (! action)
729         goto done;
730 
731       gtk_tree_path_free (path);
732       g_object_unref (action);
733 
734       *action_return = action;
735 
736       return gimp_action_get_accel_path (action);
737     }
738 
739  done:
740   gtk_tree_path_free (path);
741 
742   return NULL;
743 }
744 
745 static void
gimp_action_view_accel_edited(GtkCellRendererAccel * accel,const char * path_string,guint accel_key,GdkModifierType accel_mask,guint hardware_keycode,GimpActionView * view)746 gimp_action_view_accel_edited (GtkCellRendererAccel *accel,
747                                const char           *path_string,
748                                guint                 accel_key,
749                                GdkModifierType       accel_mask,
750                                guint                 hardware_keycode,
751                                GimpActionView       *view)
752 {
753   GimpAction      *action;
754   guint            action_accel_key;
755   GdkModifierType  action_accel_mask;
756   const gchar     *accel_path;
757 
758   accel_path = gimp_action_view_get_accel_action (view, path_string,
759                                                   &action,
760                                                   &action_accel_key,
761                                                   &action_accel_mask);
762 
763   if (! accel_path)
764     return;
765 
766   if (accel_key  == action_accel_key &&
767       accel_mask == action_accel_mask)
768     return;
769 
770   if (! accel_key ||
771 
772       /* Don't allow arrow keys, they are all swallowed by the canvas
773        * and cannot be invoked anyway, the same applies to space.
774        */
775       accel_key == GDK_KEY_Left  ||
776       accel_key == GDK_KEY_Right ||
777       accel_key == GDK_KEY_Up    ||
778       accel_key == GDK_KEY_Down  ||
779       accel_key == GDK_KEY_space ||
780       accel_key == GDK_KEY_KP_Space)
781     {
782       gimp_message_literal (view->manager->gimp,
783                             G_OBJECT (view), GIMP_MESSAGE_ERROR,
784                             _("Invalid shortcut."));
785     }
786   else if (accel_key        == GDK_KEY_F1 ||
787            action_accel_key == GDK_KEY_F1)
788     {
789       gimp_message_literal (view->manager->gimp,
790                             G_OBJECT (view), GIMP_MESSAGE_ERROR,
791                             _("F1 cannot be remapped."));
792     }
793   else if (accel_key  >= GDK_KEY_0 &&
794            accel_key  <= GDK_KEY_9 &&
795            accel_mask == GDK_MOD1_MASK)
796     {
797       gimp_message (view->manager->gimp,
798                     G_OBJECT (view), GIMP_MESSAGE_ERROR,
799                     _("Alt+%d is used to switch to display %d and "
800                       "cannot be remapped."),
801                     accel_key - GDK_KEY_0,
802                     accel_key == GDK_KEY_0 ? 10 : accel_key - GDK_KEY_0);
803     }
804   else if (! gtk_accel_map_change_entry (accel_path,
805                                          accel_key, accel_mask, FALSE))
806     {
807       GtkTreeModel *model;
808       GimpAction   *conflict_action = NULL;
809       GtkTreeIter   iter;
810       gboolean      iter_valid;
811 
812       model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
813       model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
814 
815       for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
816            iter_valid;
817            iter_valid = gtk_tree_model_iter_next (model, &iter))
818         {
819           GtkTreeIter child_iter;
820           gboolean    child_valid;
821 
822           for (child_valid = gtk_tree_model_iter_children (model,
823                                                            &child_iter,
824                                                            &iter);
825                child_valid;
826                child_valid = gtk_tree_model_iter_next (model, &child_iter))
827             {
828               guint           child_accel_key;
829               GdkModifierType child_accel_mask;
830 
831               gtk_tree_model_get (model, &child_iter,
832                                   GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY,
833                                   &child_accel_key,
834                                   GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK,
835                                   &child_accel_mask,
836                                   -1);
837 
838               if (accel_key  == child_accel_key &&
839                   accel_mask == child_accel_mask)
840                 {
841                   gtk_tree_model_get (model, &child_iter,
842                                       GIMP_ACTION_VIEW_COLUMN_ACTION,
843                                       &conflict_action,
844                                       -1);
845                   break;
846                 }
847             }
848 
849           if (conflict_action)
850             break;
851         }
852 
853       if (conflict_action != action)
854         {
855           if (conflict_action)
856             {
857               gimp_action_view_conflict_confirm (view, conflict_action,
858                                                  accel_key,
859                                                  accel_mask,
860                                                  accel_path);
861               g_object_unref (conflict_action);
862             }
863           else
864             {
865               gimp_message_literal (view->manager->gimp,
866                                     G_OBJECT (view), GIMP_MESSAGE_ERROR,
867                                     _("Changing shortcut failed."));
868             }
869         }
870     }
871 }
872 
873 static void
gimp_action_view_accel_cleared(GtkCellRendererAccel * accel,const char * path_string,GimpActionView * view)874 gimp_action_view_accel_cleared (GtkCellRendererAccel *accel,
875                                 const char           *path_string,
876                                 GimpActionView       *view)
877 {
878   GimpAction      *action;
879   guint            action_accel_key;
880   GdkModifierType  action_accel_mask;
881   const gchar     *accel_path;
882 
883   accel_path = gimp_action_view_get_accel_action (view, path_string,
884                                                   &action,
885                                                   &action_accel_key,
886                                                   &action_accel_mask);
887 
888   if (! accel_path)
889     return;
890 
891   if (action_accel_key == GDK_KEY_F1)
892     {
893       gimp_message_literal (view->manager->gimp,
894                             G_OBJECT (view), GIMP_MESSAGE_ERROR,
895                             _("F1 cannot be remapped."));
896       return;
897     }
898 
899   if (! gtk_accel_map_change_entry (accel_path, 0, 0, FALSE))
900     {
901       gimp_message_literal (view->manager->gimp,
902                             G_OBJECT (view), GIMP_MESSAGE_ERROR,
903                             _("Removing shortcut failed."));
904     }
905 }
906