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