1 | /* |
2 | * Copyright © 2013 Canonical Limited |
3 | * Copyright © 2016 Sébastien Wilmet |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the licence, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Authors: Ryan Lortie <desrt@desrt.ca> |
19 | * Sébastien Wilmet <swilmet@gnome.org> |
20 | */ |
21 | |
22 | #include "config.h" |
23 | |
24 | #include "gtkapplicationaccelsprivate.h" |
25 | |
26 | #include "gtkactionmuxerprivate.h" |
27 | #include "gtkshortcut.h" |
28 | #include "gtkshortcutaction.h" |
29 | #include "gtkshortcuttrigger.h" |
30 | |
31 | struct _GtkApplicationAccels |
32 | { |
33 | GObject parent; |
34 | |
35 | GListModel *shortcuts; |
36 | }; |
37 | |
38 | G_DEFINE_TYPE (GtkApplicationAccels, gtk_application_accels, G_TYPE_OBJECT) |
39 | |
40 | static void |
41 | gtk_application_accels_finalize (GObject *object) |
42 | { |
43 | GtkApplicationAccels *accels = GTK_APPLICATION_ACCELS (ptr: object); |
44 | |
45 | g_list_store_remove_all (store: G_LIST_STORE (ptr: accels->shortcuts)); |
46 | g_object_unref (object: accels->shortcuts); |
47 | |
48 | G_OBJECT_CLASS (gtk_application_accels_parent_class)->finalize (object); |
49 | } |
50 | |
51 | static void |
52 | gtk_application_accels_class_init (GtkApplicationAccelsClass *klass) |
53 | { |
54 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
55 | |
56 | object_class->finalize = gtk_application_accels_finalize; |
57 | } |
58 | |
59 | static void |
60 | gtk_application_accels_init (GtkApplicationAccels *accels) |
61 | { |
62 | accels->shortcuts = G_LIST_MODEL (ptr: g_list_store_new (GTK_TYPE_SHORTCUT)); |
63 | } |
64 | |
65 | GtkApplicationAccels * |
66 | gtk_application_accels_new (void) |
67 | { |
68 | return g_object_new (GTK_TYPE_APPLICATION_ACCELS, NULL); |
69 | } |
70 | |
71 | void |
72 | gtk_application_accels_set_accels_for_action (GtkApplicationAccels *accels, |
73 | const char *detailed_action_name, |
74 | const char * const *accelerators) |
75 | { |
76 | char *action_name; |
77 | GVariant *target; |
78 | GtkShortcut *shortcut; |
79 | GtkShortcutTrigger *trigger = NULL; |
80 | GError *error = NULL; |
81 | guint i; |
82 | |
83 | if (!g_action_parse_detailed_name (detailed_name: detailed_action_name, action_name: &action_name, target_value: &target, error: &error)) |
84 | { |
85 | g_critical ("Error parsing action name: %s" , error->message); |
86 | g_error_free (error); |
87 | return; |
88 | } |
89 | |
90 | /* remove the accelerator if it already exists */ |
91 | for (i = 0; i < g_list_model_get_n_items (list: accels->shortcuts); i++) |
92 | { |
93 | GtkShortcut *shortcut_i = g_list_model_get_item (list: accels->shortcuts, position: i); |
94 | GtkShortcutAction *action = gtk_shortcut_get_action (self: shortcut_i); |
95 | GVariant *args = gtk_shortcut_get_arguments (self: shortcut_i); |
96 | |
97 | if (!GTK_IS_NAMED_ACTION (ptr: action) || |
98 | !g_str_equal (v1: gtk_named_action_get_action_name (self: GTK_NAMED_ACTION (ptr: action)), v2: action_name)) |
99 | { |
100 | g_object_unref (object: shortcut_i); |
101 | continue; |
102 | } |
103 | |
104 | if ((target == NULL && args != NULL) || |
105 | (target != NULL && (args == NULL || !g_variant_equal (one: target, two: args)))) |
106 | { |
107 | g_object_unref (object: shortcut_i); |
108 | continue; |
109 | } |
110 | |
111 | g_list_store_remove (store: G_LIST_STORE (ptr: accels->shortcuts), position: i); |
112 | g_object_unref (object: shortcut_i); |
113 | break; |
114 | } |
115 | |
116 | if (accelerators == NULL) |
117 | goto out; |
118 | |
119 | for (i = 0; accelerators[i]; i++) |
120 | { |
121 | GtkShortcutTrigger *new_trigger; |
122 | guint key, modifier; |
123 | |
124 | if (!gtk_accelerator_parse (accelerator: accelerators[i], accelerator_key: &key, accelerator_mods: &modifier)) |
125 | { |
126 | g_critical ("Unable to parse accelerator '%s': ignored request to install accelerators" , |
127 | accelerators[i]); |
128 | g_clear_object (&trigger); |
129 | goto out; |
130 | } |
131 | new_trigger = gtk_keyval_trigger_new (keyval: key, modifiers: modifier); |
132 | if (trigger) |
133 | trigger = gtk_alternative_trigger_new (first: trigger, second: new_trigger); |
134 | else |
135 | trigger = new_trigger; |
136 | } |
137 | if (trigger == NULL) |
138 | goto out; |
139 | |
140 | shortcut = gtk_shortcut_new (trigger, action: gtk_named_action_new (name: action_name)); |
141 | gtk_shortcut_set_arguments (self: shortcut, args: target); |
142 | g_list_store_append (store: G_LIST_STORE (ptr: accels->shortcuts), item: shortcut); |
143 | g_object_unref (object: shortcut); |
144 | |
145 | out: |
146 | g_free (mem: action_name); |
147 | if (target) |
148 | g_variant_unref (value: target); |
149 | } |
150 | |
151 | static void |
152 | append_accelerators (GPtrArray *accels, |
153 | GtkShortcutTrigger *trigger) |
154 | { |
155 | if (GTK_IS_KEYVAL_TRIGGER (ptr: trigger)) |
156 | { |
157 | GtkKeyvalTrigger *kt = GTK_KEYVAL_TRIGGER (ptr: trigger); |
158 | guint keyval = gtk_keyval_trigger_get_keyval (self: kt); |
159 | GdkModifierType mods = gtk_keyval_trigger_get_modifiers (self: kt); |
160 | |
161 | g_ptr_array_add (array: accels, data: gtk_accelerator_name (accelerator_key: keyval, accelerator_mods: mods)); |
162 | return; |
163 | } |
164 | else if (GTK_IS_ALTERNATIVE_TRIGGER (ptr: trigger)) |
165 | { |
166 | GtkAlternativeTrigger *at = GTK_ALTERNATIVE_TRIGGER (ptr: trigger); |
167 | GtkShortcutTrigger *first = gtk_alternative_trigger_get_first (self: at); |
168 | GtkShortcutTrigger *second = gtk_alternative_trigger_get_second (self: at); |
169 | |
170 | append_accelerators (accels, trigger: first); |
171 | append_accelerators (accels, trigger: second); |
172 | return; |
173 | } |
174 | } |
175 | |
176 | char ** |
177 | gtk_application_accels_get_accels_for_action (GtkApplicationAccels *accels, |
178 | const char *detailed_action_name) |
179 | { |
180 | GPtrArray *result; |
181 | char *action_name; |
182 | GVariant *target; |
183 | GError *error = NULL; |
184 | guint i; |
185 | |
186 | result = g_ptr_array_new (); |
187 | |
188 | if (!g_action_parse_detailed_name (detailed_name: detailed_action_name, action_name: &action_name, target_value: &target, error: &error)) |
189 | { |
190 | g_critical ("Error parsing action name: %s" , error->message); |
191 | g_error_free (error); |
192 | g_ptr_array_add (array: result, NULL); |
193 | return (char **) g_ptr_array_free (array: result, FALSE); |
194 | } |
195 | |
196 | for (i = 0; i < g_list_model_get_n_items (list: accels->shortcuts); i++) |
197 | { |
198 | GtkShortcut *shortcut = g_list_model_get_item (list: accels->shortcuts, position: i); |
199 | GtkShortcutAction *action = gtk_shortcut_get_action (self: shortcut); |
200 | GVariant *args = gtk_shortcut_get_arguments (self: shortcut); |
201 | |
202 | if (!GTK_IS_NAMED_ACTION (ptr: action) || |
203 | !g_str_equal (v1: gtk_named_action_get_action_name (self: GTK_NAMED_ACTION (ptr: action)), v2: action_name)) |
204 | { |
205 | g_object_unref (object: shortcut); |
206 | continue; |
207 | } |
208 | |
209 | if ((target == NULL && args != NULL) || |
210 | (target != NULL && (args == NULL || !g_variant_equal (one: target, two: args)))) |
211 | { |
212 | g_object_unref (object: shortcut); |
213 | continue; |
214 | } |
215 | |
216 | append_accelerators (accels: result, trigger: gtk_shortcut_get_trigger (self: shortcut)); |
217 | g_object_unref (object: shortcut); |
218 | break; |
219 | } |
220 | |
221 | g_free (mem: action_name); |
222 | if (target) |
223 | g_variant_unref (value: target); |
224 | g_ptr_array_add (array: result, NULL); |
225 | return (char **) g_ptr_array_free (array: result, FALSE); |
226 | } |
227 | |
228 | static gboolean |
229 | trigger_matches_accel (GtkShortcutTrigger *trigger, |
230 | guint keyval, |
231 | GdkModifierType modifiers) |
232 | { |
233 | if (GTK_IS_KEYVAL_TRIGGER (ptr: trigger)) |
234 | { |
235 | GtkKeyvalTrigger *kt = GTK_KEYVAL_TRIGGER (ptr: trigger); |
236 | |
237 | return gtk_keyval_trigger_get_keyval (self: kt) == keyval |
238 | && gtk_keyval_trigger_get_modifiers (self: kt) == modifiers; |
239 | } |
240 | else if (GTK_IS_ALTERNATIVE_TRIGGER (ptr: trigger)) |
241 | { |
242 | GtkAlternativeTrigger *at = GTK_ALTERNATIVE_TRIGGER (ptr: trigger); |
243 | |
244 | return trigger_matches_accel (trigger: gtk_alternative_trigger_get_first (self: at), keyval, modifiers) |
245 | || trigger_matches_accel (trigger: gtk_alternative_trigger_get_second (self: at), keyval, modifiers); |
246 | } |
247 | else |
248 | { |
249 | return FALSE; |
250 | } |
251 | } |
252 | |
253 | static char * |
254 | get_detailed_name_for_shortcut (GtkShortcut *shortcut) |
255 | { |
256 | GtkShortcutAction *action = gtk_shortcut_get_action (self: shortcut); |
257 | |
258 | if (!GTK_IS_NAMED_ACTION (ptr: action)) |
259 | return NULL; |
260 | |
261 | return g_action_print_detailed_name (action_name: gtk_named_action_get_action_name (self: GTK_NAMED_ACTION (ptr: action)), |
262 | target_value: gtk_shortcut_get_arguments (self: shortcut)); |
263 | } |
264 | |
265 | char ** |
266 | gtk_application_accels_get_actions_for_accel (GtkApplicationAccels *accels, |
267 | const char *accel) |
268 | { |
269 | GPtrArray *result; |
270 | guint key, modifiers; |
271 | guint i; |
272 | |
273 | if (!gtk_accelerator_parse (accelerator: accel, accelerator_key: &key, accelerator_mods: &modifiers)) |
274 | { |
275 | g_critical ("invalid accelerator string '%s'" , accel); |
276 | return NULL; |
277 | } |
278 | |
279 | result = g_ptr_array_new (); |
280 | |
281 | for (i = 0; i < g_list_model_get_n_items (list: accels->shortcuts); i++) |
282 | { |
283 | GtkShortcut *shortcut = g_list_model_get_item (list: accels->shortcuts, position: i); |
284 | char *detailed_name; |
285 | |
286 | if (!trigger_matches_accel (trigger: gtk_shortcut_get_trigger (self: shortcut), keyval: key, modifiers)) |
287 | { |
288 | g_object_unref (object: shortcut); |
289 | continue; |
290 | } |
291 | |
292 | detailed_name = get_detailed_name_for_shortcut (shortcut); |
293 | if (detailed_name) |
294 | g_ptr_array_add (array: result, data: detailed_name); |
295 | |
296 | g_object_unref (object: shortcut); |
297 | } |
298 | |
299 | g_ptr_array_add (array: result, NULL); |
300 | return (char **) g_ptr_array_free (array: result, FALSE); |
301 | } |
302 | |
303 | char ** |
304 | gtk_application_accels_list_action_descriptions (GtkApplicationAccels *accels) |
305 | { |
306 | GPtrArray *result; |
307 | guint i; |
308 | |
309 | result = g_ptr_array_new (); |
310 | |
311 | for (i = 0; i < g_list_model_get_n_items (list: accels->shortcuts); i++) |
312 | { |
313 | GtkShortcut *shortcut = g_list_model_get_item (list: accels->shortcuts, position: i); |
314 | char *detailed_name; |
315 | |
316 | detailed_name = get_detailed_name_for_shortcut (shortcut); |
317 | if (detailed_name) |
318 | g_ptr_array_add (array: result, data: detailed_name); |
319 | |
320 | g_object_unref (object: shortcut); |
321 | } |
322 | |
323 | g_ptr_array_add (array: result, NULL); |
324 | return (char **) g_ptr_array_free (array: result, FALSE); |
325 | } |
326 | |
327 | GListModel * |
328 | gtk_application_accels_get_shortcuts (GtkApplicationAccels *accels) |
329 | { |
330 | return accels->shortcuts; |
331 | } |
332 | |