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