1 /* dzl-shortcut-theme.c
2 *
3 * Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-shortcut-theme"
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include "dzl-debug.h"
26
27 #include "shortcuts/dzl-shortcut-private.h"
28 #include "shortcuts/dzl-shortcut-chord.h"
29 #include "shortcuts/dzl-shortcut-theme.h"
30 #include "util/dzl-macros.h"
31
32 typedef struct
33 {
34 gchar *name;
35 gchar *title;
36 gchar *subtitle;
37
38 /*
39 * The parent_name property can be used to inherit from another
40 * shortcut theme when dispatching operations. The controllers
41 * will use this to locate the parent theme/context pair and
42 * try after the active theme fails to dispatch.
43 */
44 gchar *parent_name;
45
46 /*
47 * A hashtable of context names to the context pointer. The hashtable
48 * owns the reference to the context.
49 */
50 GHashTable *contexts;
51
52 /*
53 * A list of additional CSS resources that should be ingreated with this
54 * theme so that everything is applied together. You might use this if
55 * some of your keytheme needs to use CSS keybinding resources.
56 */
57 GHashTable *resource_providers;
58
59 /*
60 * Commands and actions can be mapped from a context or directly from the
61 * theme for convenience (to avoid having to define them from every context).
62 */
63 DzlShortcutChordTable *actions_table;
64 DzlShortcutChordTable *commands_table;
65
66 /*
67 * Weak back-pointer to the DzlShorcutMangaer that owns this theme. A theme
68 * can only be in one manager at a time. This will be cleared when the theme
69 * is removed from the manager.
70 */
71 DzlShortcutManager *manager;
72
73 /*
74 * The theme also maintains a list of chains overridden by the theme. These
75 * are indexed by the interned string pointer (for direct pointer copmarison)
76 * of the command_id or action_id.
77 */
78 GHashTable *chains;
79 } DzlShortcutThemePrivate;
80
81 enum {
82 PROP_0,
83 PROP_NAME,
84 PROP_PARENT_NAME,
85 PROP_SUBTITLE,
86 PROP_TITLE,
87 N_PROPS
88 };
89
G_DEFINE_TYPE_WITH_PRIVATE(DzlShortcutTheme,dzl_shortcut_theme,G_TYPE_OBJECT)90 G_DEFINE_TYPE_WITH_PRIVATE (DzlShortcutTheme, dzl_shortcut_theme, G_TYPE_OBJECT)
91
92 static GParamSpec *properties [N_PROPS];
93
94 static void
95 dzl_shortcut_theme_finalize (GObject *object)
96 {
97 DzlShortcutTheme *self = (DzlShortcutTheme *)object;
98 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
99
100 g_clear_pointer (&priv->name, g_free);
101 g_clear_pointer (&priv->parent_name, g_free);
102 g_clear_pointer (&priv->title, g_free);
103 g_clear_pointer (&priv->subtitle, g_free);
104 g_clear_pointer (&priv->contexts, g_hash_table_unref);
105 g_clear_pointer (&priv->chains, g_hash_table_unref);
106 g_clear_pointer (&priv->actions_table, dzl_shortcut_chord_table_free);
107 g_clear_pointer (&priv->commands_table, dzl_shortcut_chord_table_free);
108
109 G_OBJECT_CLASS (dzl_shortcut_theme_parent_class)->finalize (object);
110 }
111
112 static void
dzl_shortcut_theme_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)113 dzl_shortcut_theme_get_property (GObject *object,
114 guint prop_id,
115 GValue *value,
116 GParamSpec *pspec)
117 {
118 DzlShortcutTheme *self = (DzlShortcutTheme *)object;
119
120 switch (prop_id)
121 {
122 case PROP_NAME:
123 g_value_set_string (value, dzl_shortcut_theme_get_name (self));
124 break;
125
126 case PROP_PARENT_NAME:
127 g_value_set_string (value, dzl_shortcut_theme_get_parent_name (self));
128 break;
129
130 case PROP_TITLE:
131 g_value_set_string (value, dzl_shortcut_theme_get_title (self));
132 break;
133
134 case PROP_SUBTITLE:
135 g_value_set_string (value, dzl_shortcut_theme_get_subtitle (self));
136 break;
137
138 default:
139 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
140 }
141 }
142
143 static void
dzl_shortcut_theme_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)144 dzl_shortcut_theme_set_property (GObject *object,
145 guint prop_id,
146 const GValue *value,
147 GParamSpec *pspec)
148 {
149 DzlShortcutTheme *self = (DzlShortcutTheme *)object;
150 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
151
152 switch (prop_id)
153 {
154 case PROP_NAME:
155 priv->name = g_value_dup_string (value);
156 break;
157
158 case PROP_PARENT_NAME:
159 dzl_shortcut_theme_set_parent_name (self, g_value_get_string (value));
160 break;
161
162 case PROP_TITLE:
163 g_free (priv->title);
164 priv->title = g_value_dup_string (value);
165 break;
166
167 case PROP_SUBTITLE:
168 g_free (priv->subtitle);
169 priv->subtitle = g_value_dup_string (value);
170 break;
171
172 default:
173 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
174 }
175 }
176
177 static void
dzl_shortcut_theme_class_init(DzlShortcutThemeClass * klass)178 dzl_shortcut_theme_class_init (DzlShortcutThemeClass *klass)
179 {
180 GObjectClass *object_class = G_OBJECT_CLASS (klass);
181
182 object_class->finalize = dzl_shortcut_theme_finalize;
183 object_class->get_property = dzl_shortcut_theme_get_property;
184 object_class->set_property = dzl_shortcut_theme_set_property;
185
186 properties [PROP_NAME] =
187 g_param_spec_string ("name",
188 "Name",
189 "The name of the theme",
190 NULL,
191 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
192
193 properties [PROP_PARENT_NAME] =
194 g_param_spec_string ("parent-name",
195 "Parent Name",
196 "The name of the parent shortcut theme",
197 NULL,
198 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
199
200 properties [PROP_TITLE] =
201 g_param_spec_string ("title",
202 "Title",
203 "The title of the theme as used for UI elements",
204 NULL,
205 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
206
207 properties [PROP_SUBTITLE] =
208 g_param_spec_string ("subtitle",
209 "Subtitle",
210 "The subtitle of the theme as used for UI elements",
211 NULL,
212 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
213
214 g_object_class_install_properties (object_class, N_PROPS, properties);
215 }
216
217 static void
dzl_shortcut_theme_init(DzlShortcutTheme * self)218 dzl_shortcut_theme_init (DzlShortcutTheme *self)
219 {
220 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
221
222 priv->commands_table = dzl_shortcut_chord_table_new ();
223 priv->actions_table = dzl_shortcut_chord_table_new ();
224 priv->contexts = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
225 priv->chains = g_hash_table_new_full (NULL, NULL, NULL,
226 (GDestroyNotify)dzl_shortcut_closure_chain_free);
227 }
228
229 const gchar *
dzl_shortcut_theme_get_name(DzlShortcutTheme * self)230 dzl_shortcut_theme_get_name (DzlShortcutTheme *self)
231 {
232 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
233
234 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
235
236 return priv->name;
237 }
238
239 /**
240 * dzl_shortcut_theme_find_context_by_name:
241 * @self: An #DzlShortcutContext
242 * @name: The name of the context
243 *
244 * Gets the context named @name. If the context does not exist, it will
245 * be created.
246 *
247 * Returns: (not nullable) (transfer none): An #DzlShortcutContext
248 */
249 DzlShortcutContext *
dzl_shortcut_theme_find_context_by_name(DzlShortcutTheme * self,const gchar * name)250 dzl_shortcut_theme_find_context_by_name (DzlShortcutTheme *self,
251 const gchar *name)
252 {
253 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
254 DzlShortcutContext *ret;
255
256 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
257 g_return_val_if_fail (name != NULL, NULL);
258
259 name = g_intern_string (name);
260
261 if (NULL == (ret = g_hash_table_lookup (priv->contexts, name)))
262 {
263 ret = dzl_shortcut_context_new (name);
264 g_hash_table_insert (priv->contexts, (gchar *)name, ret);
265 }
266
267 return ret;
268 }
269
270 /**
271 * _dzl_shortcut_theme_try_find_context_by_name:
272 * @self: a #DzlShortcutTheme
273 *
274 * This function is like dzl_shortcut_theme_find_context_by_name() but will
275 * not create the context if it does not exist.
276 *
277 * Returns: (transfer none) (nullable): A #DzlShortcutContext or %NULL.
278 */
279 DzlShortcutContext *
_dzl_shortcut_theme_try_find_context_by_name(DzlShortcutTheme * self,const gchar * name)280 _dzl_shortcut_theme_try_find_context_by_name (DzlShortcutTheme *self,
281 const gchar *name)
282 {
283 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
284 GQuark qname;
285
286 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
287 g_return_val_if_fail (name != NULL, NULL);
288
289 /* Names are interned (which are quarks) */
290 if (0 != (qname = g_quark_try_string (name)))
291 return g_hash_table_lookup (priv->contexts, g_quark_to_string (qname));
292
293 return NULL;
294 }
295
296 static DzlShortcutContext *
dzl_shortcut_theme_find_default_context_by_type(DzlShortcutTheme * self,GType type)297 dzl_shortcut_theme_find_default_context_by_type (DzlShortcutTheme *self,
298 GType type)
299 {
300 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
301 g_return_val_if_fail (g_type_is_a (type, GTK_TYPE_WIDGET), NULL);
302
303 return dzl_shortcut_theme_find_context_by_name (self, g_type_name (type));
304 }
305
306 /**
307 * dzl_shortcut_theme_find_default_context:
308 *
309 * Finds the default context in the theme for @widget.
310 *
311 * Returns: (nullable) (transfer none): An #DzlShortcutContext or %NULL.
312 */
313 DzlShortcutContext *
dzl_shortcut_theme_find_default_context(DzlShortcutTheme * self,GtkWidget * widget)314 dzl_shortcut_theme_find_default_context (DzlShortcutTheme *self,
315 GtkWidget *widget)
316 {
317 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
318 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
319
320 return dzl_shortcut_theme_find_default_context_by_type (self, G_OBJECT_TYPE (widget));
321 }
322
323 DzlShortcutContext *
_dzl_shortcut_theme_find_default_context_with_phase(DzlShortcutTheme * self,GtkWidget * widget,DzlShortcutPhase phase)324 _dzl_shortcut_theme_find_default_context_with_phase (DzlShortcutTheme *self,
325 GtkWidget *widget,
326 DzlShortcutPhase phase)
327 {
328 g_autofree gchar *free_me = NULL;
329 const gchar *name;
330
331 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
332 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
333
334 name = G_OBJECT_TYPE_NAME (widget);
335
336 if ((phase & DZL_SHORTCUT_PHASE_BUBBLE) != 0)
337 name = free_me = g_strdup_printf ("%s:bubble", name);
338 else if ((phase & DZL_SHORTCUT_PHASE_CAPTURE) != 0)
339 name = free_me = g_strdup_printf ("%s:capture", name);
340
341 return dzl_shortcut_theme_find_context_by_name (self, name);
342 }
343
344 void
dzl_shortcut_theme_add_context(DzlShortcutTheme * self,DzlShortcutContext * context)345 dzl_shortcut_theme_add_context (DzlShortcutTheme *self,
346 DzlShortcutContext *context)
347 {
348 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
349 const gchar *name;
350
351 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
352 g_return_if_fail (DZL_IS_SHORTCUT_CONTEXT (context));
353
354 name = dzl_shortcut_context_get_name (context);
355
356 g_return_if_fail (name != NULL);
357
358 g_hash_table_insert (priv->contexts, (gchar *)g_intern_string (name), g_object_ref (context));
359 }
360
361 DzlShortcutTheme *
dzl_shortcut_theme_new(const gchar * name)362 dzl_shortcut_theme_new (const gchar *name)
363 {
364 return g_object_new (DZL_TYPE_SHORTCUT_THEME,
365 "name", name,
366 NULL);
367 }
368
369 const gchar *
dzl_shortcut_theme_get_title(DzlShortcutTheme * self)370 dzl_shortcut_theme_get_title (DzlShortcutTheme *self)
371 {
372 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
373
374 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
375
376 return priv->title;
377 }
378
379 const gchar *
dzl_shortcut_theme_get_subtitle(DzlShortcutTheme * self)380 dzl_shortcut_theme_get_subtitle (DzlShortcutTheme *self)
381 {
382 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
383
384 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
385
386 return priv->subtitle;
387 }
388
389 GHashTable *
_dzl_shortcut_theme_get_contexts(DzlShortcutTheme * self)390 _dzl_shortcut_theme_get_contexts (DzlShortcutTheme *self)
391 {
392 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
393
394 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
395
396 return priv->contexts;
397 }
398
399 void
_dzl_shortcut_theme_set_name(DzlShortcutTheme * self,const gchar * name)400 _dzl_shortcut_theme_set_name (DzlShortcutTheme *self,
401 const gchar *name)
402 {
403 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
404
405 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
406
407 if (g_strcmp0 (name, priv->name) != 0)
408 {
409 g_free (priv->name);
410 priv->name = g_strdup (name);
411 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
412 }
413 }
414
415 /**
416 * dzl_shortcut_theme_get_parent_name:
417 * @self: a #DzlShortcutTheme
418 *
419 * Gets the name of the parent shortcut theme.
420 *
421 * This is used to resolve shortcuts from the parent theme without having to
422 * copy them directly into this shortcut theme. It allows for some level of
423 * copy-on-write (CoW).
424 *
425 * Returns: (nullable): The name of the parent theme, or %NULL if none is set.
426 */
427 const gchar *
dzl_shortcut_theme_get_parent_name(DzlShortcutTheme * self)428 dzl_shortcut_theme_get_parent_name (DzlShortcutTheme *self)
429 {
430 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
431
432 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
433
434 return priv->parent_name;
435 }
436
437 void
dzl_shortcut_theme_set_parent_name(DzlShortcutTheme * self,const gchar * parent_name)438 dzl_shortcut_theme_set_parent_name (DzlShortcutTheme *self,
439 const gchar *parent_name)
440 {
441 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
442
443 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
444
445 if (g_strcmp0 (parent_name, priv->parent_name) != 0)
446 {
447 g_free (priv->parent_name);
448 priv->parent_name = g_strdup (parent_name);
449 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PARENT_NAME]);
450 }
451 }
452
453 const gchar *
_dzl_shortcut_theme_lookup_action(DzlShortcutTheme * self,const DzlShortcutChord * chord)454 _dzl_shortcut_theme_lookup_action (DzlShortcutTheme *self,
455 const DzlShortcutChord *chord)
456 {
457 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
458
459 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
460 g_return_val_if_fail (chord != NULL, NULL);
461
462 if (priv->actions_table != NULL)
463 {
464 const gchar *action = NULL;
465 DzlShortcutMatch match;
466
467 match = dzl_shortcut_chord_table_lookup (priv->actions_table, chord, (gpointer *)&action);
468
469 if (match == DZL_SHORTCUT_MATCH_EQUAL)
470 return action;
471 }
472
473 return NULL;
474 }
475
476 void
dzl_shortcut_theme_set_chord_for_action(DzlShortcutTheme * self,const gchar * detailed_action_name,const DzlShortcutChord * chord,DzlShortcutPhase phase)477 dzl_shortcut_theme_set_chord_for_action (DzlShortcutTheme *self,
478 const gchar *detailed_action_name,
479 const DzlShortcutChord *chord,
480 DzlShortcutPhase phase)
481 {
482 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
483
484 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
485
486 if (detailed_action_name == NULL)
487 {
488 dzl_shortcut_chord_table_remove (priv->actions_table, chord);
489 return;
490 }
491
492 detailed_action_name = g_intern_string (detailed_action_name);
493
494 dzl_shortcut_chord_table_remove_data (priv->actions_table,
495 (gpointer)detailed_action_name);
496
497 if (chord != NULL)
498 dzl_shortcut_chord_table_add (priv->actions_table, chord,
499 (gpointer)detailed_action_name);
500
501 if (!g_hash_table_contains (priv->chains, detailed_action_name))
502 {
503 DzlShortcutClosureChain *chain;
504
505 chain = dzl_shortcut_closure_chain_append_action_string (NULL, detailed_action_name);
506
507 if (chain != NULL)
508 {
509 chain->phase = phase;
510 g_hash_table_insert (priv->chains, (gchar *)detailed_action_name, chain);
511 }
512 }
513 }
514
515 const DzlShortcutChord *
dzl_shortcut_theme_get_chord_for_action(DzlShortcutTheme * self,const gchar * detailed_action_name)516 dzl_shortcut_theme_get_chord_for_action (DzlShortcutTheme *self,
517 const gchar *detailed_action_name)
518 {
519 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
520 const DzlShortcutChord *ret;
521
522 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
523
524 if (priv->actions_table == NULL)
525 return NULL;
526
527 ret = dzl_shortcut_chord_table_lookup_data (priv->actions_table,
528 (gpointer)g_intern_string (detailed_action_name));
529
530 if (ret == NULL)
531 {
532 DzlShortcutTheme *parent = dzl_shortcut_theme_get_parent (self);
533
534 if (parent != NULL)
535 ret = dzl_shortcut_theme_get_chord_for_action (parent, detailed_action_name);
536 }
537
538 return ret;
539 }
540
541 void
dzl_shortcut_theme_set_accel_for_action(DzlShortcutTheme * self,const gchar * detailed_action_name,const gchar * accel,DzlShortcutPhase phase)542 dzl_shortcut_theme_set_accel_for_action (DzlShortcutTheme *self,
543 const gchar *detailed_action_name,
544 const gchar *accel,
545 DzlShortcutPhase phase)
546 {
547 g_autoptr(DzlShortcutChord) chord = NULL;
548
549 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
550
551 if (accel != NULL)
552 chord = dzl_shortcut_chord_new_from_string (accel);
553
554 dzl_shortcut_theme_set_chord_for_action (self, detailed_action_name, chord, phase);
555 }
556
557 /**
558 * dzl_shortcut_theme_set_chord_for_command:
559 * @self: a #DzlShortcutTheme
560 * @chord: (nullable): the chord for the command
561 * @command: (nullable): the command to be executed
562 * @phase: the phase to activate within, or 0 for the default
563 *
564 * This will set the command to execute when @chord is pressed. If command is
565 * %NULL, the accelerator will be cleared. If @chord is %NULL, all
566 * accelerators for @command will be cleared.
567 */
568 void
dzl_shortcut_theme_set_chord_for_command(DzlShortcutTheme * self,const gchar * command,const DzlShortcutChord * chord,DzlShortcutPhase phase)569 dzl_shortcut_theme_set_chord_for_command (DzlShortcutTheme *self,
570 const gchar *command,
571 const DzlShortcutChord *chord,
572 DzlShortcutPhase phase)
573 {
574 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
575
576 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
577
578 if (command == NULL)
579 {
580 dzl_shortcut_chord_table_remove (priv->commands_table, chord);
581 return;
582 }
583
584 command = g_intern_string (command);
585 dzl_shortcut_chord_table_remove_data (priv->commands_table, (gpointer)command);
586
587 if (chord != NULL)
588 dzl_shortcut_chord_table_add (priv->commands_table, chord, (gpointer)command);
589
590 if (!g_hash_table_contains (priv->chains, command))
591 {
592 DzlShortcutClosureChain *chain;
593
594 chain = dzl_shortcut_closure_chain_append_command (NULL, command);
595 chain->phase = phase;
596 g_hash_table_insert (priv->chains, (gchar *)command, chain);
597 }
598 }
599
600 const DzlShortcutChord *
dzl_shortcut_theme_get_chord_for_command(DzlShortcutTheme * self,const gchar * command)601 dzl_shortcut_theme_get_chord_for_command (DzlShortcutTheme *self,
602 const gchar *command)
603 {
604 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
605 const DzlShortcutChord *ret;
606
607 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), NULL);
608
609 if (priv->commands_table == NULL)
610 return NULL;
611
612 ret = dzl_shortcut_chord_table_lookup_data (priv->commands_table,
613 (gpointer)g_intern_string (command));
614
615 if (ret == NULL)
616 {
617 DzlShortcutTheme *parent = dzl_shortcut_theme_get_parent (self);
618
619 if (parent != NULL)
620 ret = dzl_shortcut_theme_get_chord_for_command (parent, command);
621 }
622
623 return ret;
624 }
625
626 /**
627 * dzl_shortcut_theme_set_accel_for_command:
628 * @self: a #DzlShortcutTheme
629 * @command: (nullable): the command to be executed
630 * @accel: (nullable): the shortcut accelerator
631 * @phase: the phase to activate within, or 0 for the default
632 *
633 * This will set the command to execute when @accel is pressed. If command is
634 * %NULL, the accelerator will be cleared. If accelerator is %NULL, all
635 * accelerators for @command will be cleared.
636 */
637 void
dzl_shortcut_theme_set_accel_for_command(DzlShortcutTheme * self,const gchar * command,const gchar * accel,DzlShortcutPhase phase)638 dzl_shortcut_theme_set_accel_for_command (DzlShortcutTheme *self,
639 const gchar *command,
640 const gchar *accel,
641 DzlShortcutPhase phase)
642 {
643 g_autoptr(DzlShortcutChord) chord = NULL;
644
645 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
646
647 if (accel != NULL)
648 chord = dzl_shortcut_chord_new_from_string (accel);
649
650 dzl_shortcut_theme_set_chord_for_command (self, command, chord, phase);
651 }
652
653 /**
654 * dzl_shortcut_theme_get_parent:
655 * @self: a #DzlShortcutTheme
656 *
657 * If the #DzlShortcutTheme:parent-name property has been set, this will fetch
658 * the parent #DzlShortcutTheme.
659 *
660 * Returns: (transfer none) (nullable): A #DzlShortcutTheme or %NULL.
661 */
662 DzlShortcutTheme *
dzl_shortcut_theme_get_parent(DzlShortcutTheme * self)663 dzl_shortcut_theme_get_parent (DzlShortcutTheme *self)
664 {
665 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
666
667 g_assert (DZL_IS_SHORTCUT_THEME (self));
668
669 if (g_strcmp0 (priv->name, "internal") == 0)
670 return NULL;
671
672 if (priv->manager == NULL)
673 return NULL;
674
675 if (priv->parent_name == NULL)
676 return _dzl_shortcut_manager_get_internal_theme (priv->manager);
677
678 return dzl_shortcut_manager_get_theme_by_name (priv->manager, priv->parent_name);
679 }
680
681 DzlShortcutMatch
_dzl_shortcut_theme_match(DzlShortcutTheme * self,DzlShortcutPhase phase,const DzlShortcutChord * chord,DzlShortcutClosureChain ** chain)682 _dzl_shortcut_theme_match (DzlShortcutTheme *self,
683 DzlShortcutPhase phase,
684 const DzlShortcutChord *chord,
685 DzlShortcutClosureChain **chain)
686 {
687 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
688 DzlShortcutTheme *parent;
689 DzlShortcutMatch match1;
690 DzlShortcutMatch match2;
691 DzlShortcutMatch match3 = DZL_SHORTCUT_MATCH_NONE;
692 const gchar *action_id = NULL;
693 const gchar *command_id = NULL;
694
695 g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (self), FALSE);
696 g_return_val_if_fail (chord != NULL, FALSE);
697 g_return_val_if_fail (chain != NULL, FALSE);
698
699 match1 = dzl_shortcut_chord_table_lookup (priv->actions_table, chord, (gpointer *)&action_id);
700
701 if (match1 == DZL_SHORTCUT_MATCH_EQUAL)
702 {
703 *chain = g_hash_table_lookup (priv->chains, action_id);
704 if ((*chain)->phase == phase)
705 return match1;
706 match1 = DZL_SHORTCUT_MATCH_NONE;
707 }
708
709 match2 = dzl_shortcut_chord_table_lookup (priv->commands_table, chord, (gpointer *)&command_id);
710
711 if (match2 == DZL_SHORTCUT_MATCH_EQUAL)
712 {
713 *chain = g_hash_table_lookup (priv->chains, command_id);
714 if ((*chain)->phase == phase)
715 return match2;
716 match2 = DZL_SHORTCUT_MATCH_NONE;
717 }
718
719 /*
720 * We didn't find anything in this theme, try our parent theme.
721 */
722
723 parent = dzl_shortcut_theme_get_parent (self);
724
725 if (parent != NULL)
726 {
727 match3 = _dzl_shortcut_theme_match (parent, phase, chord, chain);
728
729 if (match3 == DZL_SHORTCUT_MATCH_EQUAL)
730 return match3;
731 }
732
733 /*
734 * Nothing found, let the caller know if we found a partial match
735 * and ensure we zero out chain just to be safe.
736 */
737
738 *chain = NULL;
739
740 return (match1 || match2 || match3) ? DZL_SHORTCUT_MATCH_PARTIAL : DZL_SHORTCUT_MATCH_NONE;
741 }
742
743 void
_dzl_shortcut_theme_set_manager(DzlShortcutTheme * self,DzlShortcutManager * manager)744 _dzl_shortcut_theme_set_manager (DzlShortcutTheme *self,
745 DzlShortcutManager *manager)
746 {
747 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
748
749 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
750 g_return_if_fail (!manager || DZL_IS_SHORTCUT_MANAGER (manager));
751 g_return_if_fail (priv->manager == NULL || manager == NULL);
752
753 priv->manager = manager;
754 }
755
756 static void
copy_chord_to_table(const DzlShortcutChord * chord,gpointer data,gpointer user_data)757 copy_chord_to_table (const DzlShortcutChord *chord,
758 gpointer data,
759 gpointer user_data)
760 {
761 DzlShortcutChordTable *dest = user_data;
762 const gchar *interned_string = data;
763
764 g_assert (chord != NULL);
765 g_assert (data != NULL);
766 g_assert (dest != NULL);
767
768 dzl_shortcut_chord_table_add (dest, chord, (gpointer)interned_string);
769 }
770
771 void
_dzl_shortcut_theme_merge(DzlShortcutTheme * self,DzlShortcutTheme * layer)772 _dzl_shortcut_theme_merge (DzlShortcutTheme *self,
773 DzlShortcutTheme *layer)
774 {
775 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
776 DzlShortcutThemePrivate *layer_priv = dzl_shortcut_theme_get_instance_private (layer);
777 GHashTableIter hiter;
778 gpointer key;
779 gpointer value;
780
781 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
782 g_return_if_fail (DZL_IS_SHORTCUT_THEME (layer));
783 g_return_if_fail (self != layer);
784 g_return_if_fail (DZL_IS_SHORTCUT_MANAGER (priv->manager));
785 g_return_if_fail (DZL_IS_SHORTCUT_MANAGER (layer_priv->manager));
786 g_return_if_fail (priv->manager == layer_priv->manager);
787
788 /*
789 * This function will take the values in @layer and apply them to @self.
790 * Doing so will allow us to discard @layer afterwards. What this does for us
791 * is allow the base application and plugins all define aspects of a theme
792 * but have them merged into one.
793 *
794 * This function is destructive to @layer which is why it is private API and
795 * should only be used by the DzlShortcutManager.
796 */
797
798 if (priv->name == NULL && layer_priv->name != NULL)
799 priv->name = g_steal_pointer (&layer_priv->name);
800
801 if (priv->title == NULL && layer_priv->title != NULL)
802 priv->title = g_steal_pointer (&layer_priv->title);
803
804 if (priv->subtitle == NULL && layer_priv->subtitle != NULL)
805 priv->subtitle = g_steal_pointer (&layer_priv->subtitle);
806
807 if (priv->parent_name == NULL && layer_priv->parent_name != NULL)
808 priv->parent_name = g_steal_pointer (&layer_priv->parent_name);
809
810 /*
811 * Steal all of the closure chains from @layer and apply them to our
812 * overriden closure chains.
813 */
814
815 g_hash_table_iter_init (&hiter, layer_priv->chains);
816 while (g_hash_table_iter_next (&hiter, &key, &value))
817 {
818 DzlShortcutClosureChain *chain = value;
819 const gchar *interned_key = key;
820
821 g_hash_table_insert (priv->chains, (gpointer)interned_key, chain);
822 g_hash_table_iter_steal (&hiter);
823 }
824
825 /*
826 * Merge all of the contexts found in the upper layer and apply them
827 * to our contexts. Since there could be additions/removals to the
828 * context, we can't just steal them, but have to merge their contents.
829 */
830 g_hash_table_iter_init (&hiter, layer_priv->contexts);
831 while (g_hash_table_iter_next (&hiter, &key, &value))
832 {
833 DzlShortcutContext *context = value;
834 DzlShortcutContext *base_context;
835 const gchar *interned_key = key;
836
837 base_context = g_hash_table_lookup (priv->contexts, interned_key);
838
839 /*
840 * If we do not contain this context yet, we can cheat and just steal the
841 * whole context rather than merge them.
842 */
843 if (base_context == NULL)
844 {
845 g_hash_table_insert (priv->contexts, (gpointer)interned_key, value);
846 g_hash_table_iter_steal (&hiter);
847 continue;
848 }
849
850 /*
851 * Okay, both layers have the context, so we need to merge them.
852 */
853 _dzl_shortcut_context_merge (base_context, context);
854 }
855
856 /* Merge any associated resources. */
857 if (layer_priv->resource_providers != NULL)
858 {
859 GHashTableIter iter;
860
861 if (priv->resource_providers == NULL)
862 priv->resource_providers = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
863
864 g_hash_table_iter_init (&iter, layer_priv->resource_providers);
865 while (g_hash_table_iter_next (&iter, &key, &value))
866 {
867 g_hash_table_iter_steal (&iter);
868 g_hash_table_insert (priv->resource_providers, key, value);
869 }
870 }
871
872 /*
873 * Copy our action and commands chords over. These are all const data, so no
874 * need to be tricky about stealing data or what data we are safe to
875 * copy/steal/ref/etc.
876 */
877 dzl_shortcut_chord_table_foreach (layer_priv->actions_table, copy_chord_to_table, priv->actions_table);
878 dzl_shortcut_chord_table_foreach (layer_priv->commands_table, copy_chord_to_table, priv->commands_table);
879 }
880
881 void
dzl_shortcut_theme_add_css_resource(DzlShortcutTheme * self,const gchar * path)882 dzl_shortcut_theme_add_css_resource (DzlShortcutTheme *self,
883 const gchar *path)
884 {
885 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
886 g_autoptr(GtkCssProvider) provider = NULL;
887
888 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
889 g_return_if_fail (path != NULL);
890 g_return_if_fail (*path == '/' || g_str_has_prefix (path, "resource://"));
891
892 if (priv->resource_providers == NULL)
893 priv->resource_providers = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
894
895 path = g_intern_string (path);
896
897 provider = gtk_css_provider_new ();
898
899 if (g_str_has_prefix (path, "resource://"))
900 {
901 const gchar *adjpath = path + strlen ("resource://");
902
903 gtk_css_provider_load_from_resource (provider, adjpath);
904 g_hash_table_insert (priv->resource_providers, (gpointer)path, g_steal_pointer (&provider));
905 }
906 else
907 {
908 g_autoptr(GError) error = NULL;
909
910 if (!gtk_css_provider_load_from_path (provider, path, &error))
911 g_warning ("%s", error->message);
912 else
913 g_hash_table_insert (priv->resource_providers, (gpointer)path, g_steal_pointer (&provider));
914 }
915 }
916
917 void
dzl_shortcut_theme_remove_css_resource(DzlShortcutTheme * self,const gchar * path)918 dzl_shortcut_theme_remove_css_resource (DzlShortcutTheme *self,
919 const gchar *path)
920 {
921 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
922
923 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
924 g_return_if_fail (path != NULL);
925
926 if (priv->resource_providers != NULL)
927 g_hash_table_remove (priv->resource_providers, g_intern_string (path));
928 }
929
930 void
_dzl_shortcut_theme_attach(DzlShortcutTheme * self)931 _dzl_shortcut_theme_attach (DzlShortcutTheme *self)
932 {
933 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
934
935 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
936
937 if (priv->resource_providers != NULL)
938 {
939 GdkScreen *screen = gdk_screen_get_default ();
940 GtkStyleProvider *provider;
941 GHashTableIter iter;
942
943 g_hash_table_iter_init (&iter, priv->resource_providers);
944 while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&provider))
945 {
946 DZL_TRACE_MSG ("adding CSS provider %p", provider);
947 gtk_style_context_add_provider_for_screen (screen,
948 provider,
949 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
950 }
951 }
952 }
953
954 void
_dzl_shortcut_theme_detach(DzlShortcutTheme * self)955 _dzl_shortcut_theme_detach (DzlShortcutTheme *self)
956 {
957 DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
958
959 DZL_ENTRY;
960
961 g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
962
963 if (priv->resource_providers != NULL)
964 {
965 GdkScreen *screen = gdk_screen_get_default ();
966 GtkStyleProvider *provider;
967 GHashTableIter iter;
968
969 g_hash_table_iter_init (&iter, priv->resource_providers);
970 while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&provider))
971 {
972 DZL_TRACE_MSG ("removing CSS provider %p", provider);
973 gtk_style_context_remove_provider_for_screen (screen, provider);
974 }
975 }
976
977 DZL_EXIT;
978 }
979