1 /* dzl-shortcuts-group.c
2  *
3  * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Library General Public License as
7  *  published by the Free Software Foundation; either version 2 of the
8  *  License, 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  *  Library General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Library General Public
16  *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <glib/gi18n.h>
20 
21 #include "config.h"
22 
23 #include "shortcuts/dzl-shortcuts-group.h"
24 #include "shortcuts/dzl-shortcuts-shortcut.h"
25 
26 /**
27  * SECTION:dzl-shortcuts-group
28  * @Title: DzlShortcutsGroup
29  * @Short_description: Represents a group of shortcuts in a DzlShortcutsWindow
30  *
31  * A DzlShortcutsGroup represents a group of related keyboard shortcuts
32  * or gestures. The group has a title. It may optionally be associated with
33  * a view of the application, which can be used to show only relevant shortcuts
34  * depending on the application context.
35  *
36  * This widget is only meant to be used with #DzlShortcutsWindow.
37  */
38 
39 struct _DzlShortcutsGroup
40 {
41   GtkBox    parent_instance;
42 
43   GtkLabel *title;
44   gchar    *view;
45   guint     height;
46 
47   GtkSizeGroup *accel_size_group;
48   GtkSizeGroup *title_size_group;
49 };
50 
51 struct _DzlShortcutsGroupClass
52 {
53   GtkBoxClass parent_class;
54 };
55 
56 G_DEFINE_TYPE (DzlShortcutsGroup, dzl_shortcuts_group, GTK_TYPE_BOX)
57 
58 enum {
59   PROP_0,
60   PROP_TITLE,
61   PROP_VIEW,
62   PROP_ACCEL_SIZE_GROUP,
63   PROP_TITLE_SIZE_GROUP,
64   PROP_HEIGHT,
65   LAST_PROP
66 };
67 
68 static GParamSpec *properties[LAST_PROP];
69 
70 static void
dzl_shortcuts_group_apply_accel_size_group(DzlShortcutsGroup * group,GtkWidget * child)71 dzl_shortcuts_group_apply_accel_size_group (DzlShortcutsGroup *group,
72                                             GtkWidget         *child)
73 {
74   if (DZL_IS_SHORTCUTS_SHORTCUT (child))
75     g_object_set (child, "accel-size-group", group->accel_size_group, NULL);
76 }
77 
78 static void
dzl_shortcuts_group_apply_title_size_group(DzlShortcutsGroup * group,GtkWidget * child)79 dzl_shortcuts_group_apply_title_size_group (DzlShortcutsGroup *group,
80                                             GtkWidget         *child)
81 {
82   if (DZL_IS_SHORTCUTS_SHORTCUT (child))
83     g_object_set (child, "title-size-group", group->title_size_group, NULL);
84 }
85 
86 static void
dzl_shortcuts_group_set_accel_size_group(DzlShortcutsGroup * group,GtkSizeGroup * size_group)87 dzl_shortcuts_group_set_accel_size_group (DzlShortcutsGroup *group,
88                                           GtkSizeGroup      *size_group)
89 {
90   GList *children, *l;
91 
92   g_set_object (&group->accel_size_group, size_group);
93 
94   children = gtk_container_get_children (GTK_CONTAINER (group));
95   for (l = children; l; l = l->next)
96     dzl_shortcuts_group_apply_accel_size_group (group, GTK_WIDGET (l->data));
97   g_list_free (children);
98 }
99 
100 static void
dzl_shortcuts_group_set_title_size_group(DzlShortcutsGroup * group,GtkSizeGroup * size_group)101 dzl_shortcuts_group_set_title_size_group (DzlShortcutsGroup *group,
102                                           GtkSizeGroup      *size_group)
103 {
104   GList *children, *l;
105 
106   g_set_object (&group->title_size_group, size_group);
107 
108   children = gtk_container_get_children (GTK_CONTAINER (group));
109   for (l = children; l; l = l->next)
110     dzl_shortcuts_group_apply_title_size_group (group, GTK_WIDGET (l->data));
111   g_list_free (children);
112 }
113 
114 static guint
dzl_shortcuts_group_get_height(DzlShortcutsGroup * group)115 dzl_shortcuts_group_get_height (DzlShortcutsGroup *group)
116 {
117   GList *children, *l;
118   guint height;
119 
120   height = 1;
121 
122   children = gtk_container_get_children (GTK_CONTAINER (group));
123   for (l = children; l; l = l->next)
124     {
125       GtkWidget *child = l->data;
126 
127       if (!gtk_widget_get_visible (child))
128         continue;
129       else if (DZL_IS_SHORTCUTS_SHORTCUT (child))
130         height += 1;
131     }
132   g_list_free (children);
133 
134   return height;
135 }
136 
137 static void
dzl_shortcuts_group_add(GtkContainer * container,GtkWidget * widget)138 dzl_shortcuts_group_add (GtkContainer *container,
139                          GtkWidget    *widget)
140 {
141   if (DZL_IS_SHORTCUTS_SHORTCUT (widget))
142     {
143       GTK_CONTAINER_CLASS (dzl_shortcuts_group_parent_class)->add (container, widget);
144       dzl_shortcuts_group_apply_accel_size_group (DZL_SHORTCUTS_GROUP (container), widget);
145       dzl_shortcuts_group_apply_title_size_group (DZL_SHORTCUTS_GROUP (container), widget);
146     }
147   else
148     g_warning ("Can't add children of type %s to %s",
149                G_OBJECT_TYPE_NAME (widget),
150                G_OBJECT_TYPE_NAME (container));
151 }
152 
153 typedef struct {
154   GtkCallback callback;
155   gpointer data;
156   gboolean include_internal;
157 } CallbackData;
158 
159 static void
forall_cb(GtkWidget * widget,gpointer data)160 forall_cb (GtkWidget *widget, gpointer data)
161 {
162   DzlShortcutsGroup *self;
163   CallbackData *cbdata = data;
164 
165   self = DZL_SHORTCUTS_GROUP (gtk_widget_get_parent (widget));
166   if (cbdata->include_internal || widget != (GtkWidget*)self->title)
167     cbdata->callback (widget, cbdata->data);
168 }
169 
170 static void
dzl_shortcuts_group_forall(GtkContainer * container,gboolean include_internal,GtkCallback callback,gpointer callback_data)171 dzl_shortcuts_group_forall (GtkContainer *container,
172                             gboolean      include_internal,
173                             GtkCallback   callback,
174                             gpointer      callback_data)
175 {
176   CallbackData cbdata;
177 
178   cbdata.include_internal = include_internal;
179   cbdata.callback = callback;
180   cbdata.data = callback_data;
181 
182   GTK_CONTAINER_CLASS (dzl_shortcuts_group_parent_class)->forall (container, include_internal, forall_cb, &cbdata);
183 }
184 
185 static void
dzl_shortcuts_group_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)186 dzl_shortcuts_group_get_property (GObject    *object,
187                                   guint       prop_id,
188                                   GValue     *value,
189                                   GParamSpec *pspec)
190 {
191   DzlShortcutsGroup *self = DZL_SHORTCUTS_GROUP (object);
192 
193   switch (prop_id)
194     {
195     case PROP_TITLE:
196       g_value_set_string (value, gtk_label_get_label (self->title));
197       break;
198 
199     case PROP_VIEW:
200       g_value_set_string (value, self->view);
201       break;
202 
203     case PROP_HEIGHT:
204       g_value_set_uint (value, dzl_shortcuts_group_get_height (self));
205       break;
206 
207     default:
208       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
209     }
210 }
211 
212 static void
dzl_shortcuts_group_direction_changed(GtkWidget * widget,GtkTextDirection previous_dir)213 dzl_shortcuts_group_direction_changed (GtkWidget        *widget,
214                                        GtkTextDirection  previous_dir)
215 {
216   GTK_WIDGET_CLASS (dzl_shortcuts_group_parent_class)->direction_changed (widget, previous_dir);
217   g_object_notify (G_OBJECT (widget), "height");
218 }
219 
220 static void
dzl_shortcuts_group_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)221 dzl_shortcuts_group_set_property (GObject      *object,
222                                   guint         prop_id,
223                                   const GValue *value,
224                                   GParamSpec   *pspec)
225 {
226   DzlShortcutsGroup *self = DZL_SHORTCUTS_GROUP (object);
227 
228   switch (prop_id)
229     {
230     case PROP_TITLE:
231       gtk_label_set_label (self->title, g_value_get_string (value));
232       break;
233 
234     case PROP_VIEW:
235       g_free (self->view);
236       self->view = g_value_dup_string (value);
237       break;
238 
239     case PROP_ACCEL_SIZE_GROUP:
240       dzl_shortcuts_group_set_accel_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
241       break;
242 
243     case PROP_TITLE_SIZE_GROUP:
244       dzl_shortcuts_group_set_title_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
245       break;
246 
247     default:
248       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
249     }
250 }
251 
252 static void
dzl_shortcuts_group_finalize(GObject * object)253 dzl_shortcuts_group_finalize (GObject *object)
254 {
255   DzlShortcutsGroup *self = DZL_SHORTCUTS_GROUP (object);
256 
257   g_free (self->view);
258   g_set_object (&self->accel_size_group, NULL);
259   g_set_object (&self->title_size_group, NULL);
260 
261   G_OBJECT_CLASS (dzl_shortcuts_group_parent_class)->finalize (object);
262 }
263 
264 static void
dzl_shortcuts_group_dispose(GObject * object)265 dzl_shortcuts_group_dispose (GObject *object)
266 {
267   DzlShortcutsGroup *self = DZL_SHORTCUTS_GROUP (object);
268 
269   /*
270    * Since we overload forall(), the inherited destroy() won't work as normal.
271    * Remove internal widgets ourself.
272    */
273   if (self->title)
274     {
275       gtk_widget_destroy (GTK_WIDGET (self->title));
276       self->title = NULL;
277     }
278 
279   G_OBJECT_CLASS (dzl_shortcuts_group_parent_class)->dispose (object);
280 }
281 
282 static void
dzl_shortcuts_group_class_init(DzlShortcutsGroupClass * klass)283 dzl_shortcuts_group_class_init (DzlShortcutsGroupClass *klass)
284 {
285   GObjectClass *object_class = G_OBJECT_CLASS (klass);
286   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
287   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
288 
289   object_class->finalize = dzl_shortcuts_group_finalize;
290   object_class->get_property = dzl_shortcuts_group_get_property;
291   object_class->set_property = dzl_shortcuts_group_set_property;
292   object_class->dispose = dzl_shortcuts_group_dispose;
293 
294   widget_class->direction_changed = dzl_shortcuts_group_direction_changed;
295   container_class->add = dzl_shortcuts_group_add;
296   container_class->forall = dzl_shortcuts_group_forall;
297 
298   /**
299    * DzlShortcutsGroup:title:
300    *
301    * The title for this group of shortcuts.
302    */
303   properties[PROP_TITLE] =
304     g_param_spec_string ("title", _("Title"), _("Title"),
305                          "",
306                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
307 
308   /**
309    * DzlShortcutsGroup:view:
310    *
311    * An optional view that the shortcuts in this group are relevant for.
312    * The group will be hidden if the #DzlShortcutsWindow:view-name property
313    * does not match the view of this group.
314    *
315    * Set this to %NULL to make the group always visible.
316    */
317   properties[PROP_VIEW] =
318     g_param_spec_string ("view", _("View"), _("View"),
319                          NULL,
320                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
321 
322   /**
323    * DzlShortcutsGroup:accel-size-group:
324    *
325    * The size group for the accelerator portion of shortcuts in this group.
326    *
327    * This is used internally by GTK+, and must not be modified by applications.
328    */
329   properties[PROP_ACCEL_SIZE_GROUP] =
330     g_param_spec_object ("accel-size-group",
331                          _("Accelerator Size Group"),
332                          _("Accelerator Size Group"),
333                          GTK_TYPE_SIZE_GROUP,
334                          (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
335 
336   /**
337    * DzlShortcutsGroup:title-size-group:
338    *
339    * The size group for the textual portion of shortcuts in this group.
340    *
341    * This is used internally by GTK+, and must not be modified by applications.
342    */
343   properties[PROP_TITLE_SIZE_GROUP] =
344     g_param_spec_object ("title-size-group",
345                          _("Title Size Group"),
346                          _("Title Size Group"),
347                          GTK_TYPE_SIZE_GROUP,
348                          (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
349 
350   /**
351    * DzlShortcutsGroup:height:
352    *
353    * A rough measure for the number of lines in this group.
354    *
355    * This is used internally by GTK+, and is not useful for applications.
356    */
357   properties[PROP_HEIGHT] =
358     g_param_spec_uint ("height", _("Height"), _("Height"),
359                        0, G_MAXUINT, 1,
360                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
361 
362   g_object_class_install_properties (object_class, LAST_PROP, properties);
363 }
364 
365 static void
dzl_shortcuts_group_init(DzlShortcutsGroup * self)366 dzl_shortcuts_group_init (DzlShortcutsGroup *self)
367 {
368   PangoAttrList *attrs;
369 
370   gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
371   gtk_box_set_spacing (GTK_BOX (self), 10);
372 
373   attrs = pango_attr_list_new ();
374   pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
375   self->title = g_object_new (GTK_TYPE_LABEL,
376                               "attributes", attrs,
377                               "visible", TRUE,
378                               "xalign", 0.0f,
379                               NULL);
380   pango_attr_list_unref (attrs);
381 
382   GTK_CONTAINER_CLASS (dzl_shortcuts_group_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET (self->title));
383 }
384