1 /* gtkshortcutssection.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 "config.h"
20 
21 #include "gtkshortcutssection.h"
22 
23 #include "gtkshortcutsgroup.h"
24 #include "gtkbutton.h"
25 #include "gtklabel.h"
26 #include "gtkstack.h"
27 #include "gtkstackswitcher.h"
28 #include "gtkstylecontext.h"
29 #include "gtkorientable.h"
30 #include "gtksizegroup.h"
31 #include "gtkwidget.h"
32 #include "gtkbindings.h"
33 #include "gtkprivate.h"
34 #include "gtkmarshalers.h"
35 #include "gtkgesturepan.h"
36 #include "gtkwidgetprivate.h"
37 #include "gtkintl.h"
38 
39 /**
40  * SECTION:gtkshortcutssection
41  * @Title: GtkShortcutsSection
42  * @Short_description: Represents an application mode in a GtkShortcutsWindow
43  *
44  * A GtkShortcutsSection collects all the keyboard shortcuts and gestures
45  * for a major application mode. If your application needs multiple sections,
46  * you should give each section a unique #GtkShortcutsSection:section-name and
47  * a #GtkShortcutsSection:title that can be shown in the section selector of
48  * the GtkShortcutsWindow.
49  *
50  * The #GtkShortcutsSection:max-height property can be used to influence how
51  * the groups in the section are distributed over pages and columns.
52  *
53  * This widget is only meant to be used with #GtkShortcutsWindow.
54  */
55 
56 struct _GtkShortcutsSection
57 {
58   GtkBox            parent_instance;
59 
60   gchar            *name;
61   gchar            *title;
62   gchar            *view_name;
63   guint             max_height;
64 
65   GtkStack         *stack;
66   GtkStackSwitcher *switcher;
67   GtkWidget        *show_all;
68   GtkWidget        *footer;
69   GList            *groups;
70 
71   gboolean          has_filtered_group;
72   gboolean          need_reflow;
73 
74   GtkGesture       *pan_gesture;
75 };
76 
77 struct _GtkShortcutsSectionClass
78 {
79   GtkBoxClass parent_class;
80 
81   gboolean (* change_current_page) (GtkShortcutsSection *self,
82                                     gint                 offset);
83 
84 };
85 
86 G_DEFINE_TYPE (GtkShortcutsSection, gtk_shortcuts_section, GTK_TYPE_BOX)
87 
88 enum {
89   PROP_0,
90   PROP_TITLE,
91   PROP_SECTION_NAME,
92   PROP_VIEW_NAME,
93   PROP_MAX_HEIGHT,
94   LAST_PROP
95 };
96 
97 enum {
98   CHANGE_CURRENT_PAGE,
99   LAST_SIGNAL
100 };
101 
102 static GParamSpec *properties[LAST_PROP];
103 static guint signals[LAST_SIGNAL];
104 
105 static void gtk_shortcuts_section_set_view_name    (GtkShortcutsSection *self,
106                                                     const gchar         *view_name);
107 static void gtk_shortcuts_section_set_max_height   (GtkShortcutsSection *self,
108                                                     guint                max_height);
109 static void gtk_shortcuts_section_add_group        (GtkShortcutsSection *self,
110                                                     GtkShortcutsGroup   *group);
111 
112 static void gtk_shortcuts_section_show_all         (GtkShortcutsSection *self);
113 static void gtk_shortcuts_section_filter_groups    (GtkShortcutsSection *self);
114 static void gtk_shortcuts_section_reflow_groups    (GtkShortcutsSection *self);
115 static void gtk_shortcuts_section_maybe_reflow     (GtkShortcutsSection *self);
116 
117 static gboolean gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
118                                                            gint                 offset);
119 
120 static void gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan       *gesture,
121                                                    GtkPanDirection      direction,
122                                                    gdouble              offset,
123                                                    GtkShortcutsSection *self);
124 
125 static void
gtk_shortcuts_section_add(GtkContainer * container,GtkWidget * child)126 gtk_shortcuts_section_add (GtkContainer *container,
127                            GtkWidget    *child)
128 {
129   GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (container);
130 
131   if (GTK_IS_SHORTCUTS_GROUP (child))
132     gtk_shortcuts_section_add_group (self, GTK_SHORTCUTS_GROUP (child));
133   else
134     g_warning ("Can't add children of type %s to %s",
135                G_OBJECT_TYPE_NAME (child),
136                G_OBJECT_TYPE_NAME (container));
137 }
138 
139 static void
gtk_shortcuts_section_remove(GtkContainer * container,GtkWidget * child)140 gtk_shortcuts_section_remove (GtkContainer *container,
141                               GtkWidget    *child)
142 {
143   GtkShortcutsSection *self = (GtkShortcutsSection *)container;
144 
145   if (GTK_IS_SHORTCUTS_GROUP (child) &&
146       gtk_widget_is_ancestor (child, GTK_WIDGET (container)))
147     {
148       self->groups = g_list_remove (self->groups, child);
149       gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (child)), child);
150     }
151   else
152     GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->remove (container, child);
153 }
154 
155 static void
gtk_shortcuts_section_forall(GtkContainer * container,gboolean include_internal,GtkCallback callback,gpointer callback_data)156 gtk_shortcuts_section_forall (GtkContainer *container,
157                               gboolean      include_internal,
158                               GtkCallback   callback,
159                               gpointer      callback_data)
160 {
161   GtkShortcutsSection *self = (GtkShortcutsSection *)container;
162   GList *l;
163 
164   if (include_internal)
165     {
166       GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->forall (container, include_internal, callback, callback_data);
167     }
168   else
169     {
170       for (l = self->groups; l; l = l->next)
171         {
172           GtkWidget *group = l->data;
173           callback (group, callback_data);
174         }
175     }
176 }
177 
178 static void
map_child(GtkWidget * child)179 map_child (GtkWidget *child)
180 {
181   if (_gtk_widget_get_visible (child) &&
182       _gtk_widget_get_child_visible (child) &&
183       !_gtk_widget_get_mapped (child))
184     gtk_widget_map (child);
185 }
186 
187 static void
gtk_shortcuts_section_map(GtkWidget * widget)188 gtk_shortcuts_section_map (GtkWidget *widget)
189 {
190   GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
191 
192   if (self->need_reflow)
193     gtk_shortcuts_section_reflow_groups (self);
194 
195   gtk_widget_set_mapped (widget, TRUE);
196 
197   map_child (GTK_WIDGET (self->stack));
198   map_child (GTK_WIDGET (self->footer));
199 }
200 
201 static void
gtk_shortcuts_section_unmap(GtkWidget * widget)202 gtk_shortcuts_section_unmap (GtkWidget *widget)
203 {
204   GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
205 
206   gtk_widget_set_mapped (widget, FALSE);
207 
208   gtk_widget_unmap (GTK_WIDGET (self->footer));
209   gtk_widget_unmap (GTK_WIDGET (self->stack));
210 }
211 
212 static void
gtk_shortcuts_section_destroy(GtkWidget * widget)213 gtk_shortcuts_section_destroy (GtkWidget *widget)
214 {
215   GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
216 
217   if (self->stack)
218     {
219       gtk_widget_destroy (GTK_WIDGET (self->stack));
220       self->stack = NULL;
221     }
222 
223   if (self->footer)
224     {
225       gtk_widget_destroy (GTK_WIDGET (self->footer));
226       self->footer = NULL;
227     }
228 
229   g_list_free (self->groups);
230   self->groups = NULL;
231 
232   GTK_WIDGET_CLASS (gtk_shortcuts_section_parent_class)->destroy (widget);
233 }
234 
235 static void
gtk_shortcuts_section_finalize(GObject * object)236 gtk_shortcuts_section_finalize (GObject *object)
237 {
238   GtkShortcutsSection *self = (GtkShortcutsSection *)object;
239 
240   g_clear_pointer (&self->name, g_free);
241   g_clear_pointer (&self->title, g_free);
242   g_clear_pointer (&self->view_name, g_free);
243   g_clear_object (&self->pan_gesture);
244 
245   G_OBJECT_CLASS (gtk_shortcuts_section_parent_class)->finalize (object);
246 }
247 
248 static void
gtk_shortcuts_section_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)249 gtk_shortcuts_section_get_property (GObject    *object,
250                                     guint       prop_id,
251                                     GValue     *value,
252                                     GParamSpec *pspec)
253 {
254   GtkShortcutsSection *self = (GtkShortcutsSection *)object;
255 
256   switch (prop_id)
257     {
258     case PROP_SECTION_NAME:
259       g_value_set_string (value, self->name);
260       break;
261 
262     case PROP_VIEW_NAME:
263       g_value_set_string (value, self->view_name);
264       break;
265 
266     case PROP_TITLE:
267       g_value_set_string (value, self->title);
268       break;
269 
270     case PROP_MAX_HEIGHT:
271       g_value_set_uint (value, self->max_height);
272       break;
273 
274     default:
275       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276     }
277 }
278 
279 static void
gtk_shortcuts_section_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)280 gtk_shortcuts_section_set_property (GObject      *object,
281                                     guint         prop_id,
282                                     const GValue *value,
283                                     GParamSpec   *pspec)
284 {
285   GtkShortcutsSection *self = (GtkShortcutsSection *)object;
286 
287   switch (prop_id)
288     {
289     case PROP_SECTION_NAME:
290       g_free (self->name);
291       self->name = g_value_dup_string (value);
292       break;
293 
294     case PROP_VIEW_NAME:
295       gtk_shortcuts_section_set_view_name (self, g_value_get_string (value));
296       break;
297 
298     case PROP_TITLE:
299       g_free (self->title);
300       self->title = g_value_dup_string (value);
301       break;
302 
303     case PROP_MAX_HEIGHT:
304       gtk_shortcuts_section_set_max_height (self, g_value_get_uint (value));
305       break;
306 
307     default:
308       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309     }
310 }
311 
312 static GType
gtk_shortcuts_section_child_type(GtkContainer * container)313 gtk_shortcuts_section_child_type (GtkContainer *container)
314 {
315   return GTK_TYPE_SHORTCUTS_GROUP;
316 }
317 
318 static void
gtk_shortcuts_section_class_init(GtkShortcutsSectionClass * klass)319 gtk_shortcuts_section_class_init (GtkShortcutsSectionClass *klass)
320 {
321   GObjectClass *object_class = G_OBJECT_CLASS (klass);
322   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
323   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
324   GtkBindingSet *binding_set;
325 
326   object_class->finalize = gtk_shortcuts_section_finalize;
327   object_class->get_property = gtk_shortcuts_section_get_property;
328   object_class->set_property = gtk_shortcuts_section_set_property;
329 
330   widget_class->map = gtk_shortcuts_section_map;
331   widget_class->unmap = gtk_shortcuts_section_unmap;
332   widget_class->destroy = gtk_shortcuts_section_destroy;
333 
334   container_class->add = gtk_shortcuts_section_add;
335   container_class->remove = gtk_shortcuts_section_remove;
336   container_class->forall = gtk_shortcuts_section_forall;
337   container_class->child_type = gtk_shortcuts_section_child_type;
338 
339   klass->change_current_page = gtk_shortcuts_section_change_current_page;
340 
341   /**
342    * GtkShortcutsSection:section-name:
343    *
344    * A unique name to identify this section among the sections
345    * added to the GtkShortcutsWindow. Setting the #GtkShortcutsWindow:section-name
346    * property to this string will make this section shown in the
347    * GtkShortcutsWindow.
348    */
349   properties[PROP_SECTION_NAME] =
350     g_param_spec_string ("section-name", P_("Section Name"), P_("Section Name"),
351                          NULL,
352                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
353 
354   /**
355    * GtkShortcutsSection:view-name:
356    *
357    * A view name to filter the groups in this section by.
358    * See #GtkShortcutsGroup:view.
359    *
360    * Applications are expected to use the #GtkShortcutsWindow:view-name
361    * property for this purpose.
362    */
363   properties[PROP_VIEW_NAME] =
364     g_param_spec_string ("view-name", P_("View Name"), P_("View Name"),
365                          NULL,
366                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
367 
368   /**
369    * GtkShortcutsSection:title:
370    *
371    * The string to show in the section selector of the GtkShortcutsWindow
372    * for this section. If there is only one section, you don't need to
373    * set a title, since the section selector will not be shown in this case.
374    */
375   properties[PROP_TITLE] =
376     g_param_spec_string ("title", P_("Title"), P_("Title"),
377                          NULL,
378                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
379 
380   /**
381    * GtkShortcutsSection:max-height:
382    *
383    * The maximum number of lines to allow per column. This property can
384    * be used to influence how the groups in this section are distributed
385    * across pages and columns. The default value of 15 should work in
386    * most cases.
387    */
388   properties[PROP_MAX_HEIGHT] =
389     g_param_spec_uint ("max-height", P_("Maximum Height"), P_("Maximum Height"),
390                        0, G_MAXUINT, 15,
391                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
392 
393   g_object_class_install_properties (object_class, LAST_PROP, properties);
394 
395   signals[CHANGE_CURRENT_PAGE] =
396     g_signal_new (I_("change-current-page"),
397                   G_TYPE_FROM_CLASS (object_class),
398                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
399                   G_STRUCT_OFFSET (GtkShortcutsSectionClass, change_current_page),
400                   NULL, NULL,
401                   _gtk_marshal_BOOLEAN__INT,
402                   G_TYPE_BOOLEAN, 1,
403                   G_TYPE_INT);
404 
405   binding_set = gtk_binding_set_by_class (klass);
406   gtk_binding_entry_add_signal (binding_set,
407                                 GDK_KEY_Page_Up, 0,
408                                 "change-current-page", 1,
409                                 G_TYPE_INT, -1);
410   gtk_binding_entry_add_signal (binding_set,
411                                 GDK_KEY_Page_Down, 0,
412                                 "change-current-page", 1,
413                                 G_TYPE_INT, 1);
414   gtk_binding_entry_add_signal (binding_set,
415                                 GDK_KEY_Page_Up, GDK_CONTROL_MASK,
416                                 "change-current-page", 1,
417                                 G_TYPE_INT, -1);
418   gtk_binding_entry_add_signal (binding_set,
419                                 GDK_KEY_Page_Down, GDK_CONTROL_MASK,
420                                 "change-current-page", 1,
421                                 G_TYPE_INT, 1);
422 }
423 
424 static void
gtk_shortcuts_section_init(GtkShortcutsSection * self)425 gtk_shortcuts_section_init (GtkShortcutsSection *self)
426 {
427   self->max_height = 15;
428 
429   gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
430   gtk_box_set_homogeneous (GTK_BOX (self), FALSE);
431   gtk_box_set_spacing (GTK_BOX (self), 22);
432   gtk_container_set_border_width (GTK_CONTAINER (self), 24);
433 
434   self->stack = g_object_new (GTK_TYPE_STACK,
435                               "homogeneous", TRUE,
436                               "transition-type", GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT,
437                               "vexpand", TRUE,
438                               "visible", TRUE,
439                               NULL);
440   GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET (self->stack));
441 
442   self->switcher = g_object_new (GTK_TYPE_STACK_SWITCHER,
443                                  "halign", GTK_ALIGN_CENTER,
444                                  "stack", self->stack,
445                                  "spacing", 12,
446                                  "no-show-all", TRUE,
447                                  NULL);
448 
449   gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (self->switcher)), GTK_STYLE_CLASS_LINKED);
450 
451   self->show_all = gtk_button_new_with_mnemonic (_("_Show All"));
452   gtk_widget_set_no_show_all (self->show_all, TRUE);
453   g_signal_connect_swapped (self->show_all, "clicked",
454                             G_CALLBACK (gtk_shortcuts_section_show_all), self);
455 
456   self->footer = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20);
457   GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->add (GTK_CONTAINER (self), self->footer);
458 
459   gtk_box_set_center_widget (GTK_BOX (self->footer), GTK_WIDGET (self->switcher));
460   gtk_box_pack_end (GTK_BOX (self->footer), self->show_all, TRUE, TRUE, 0);
461   gtk_widget_set_halign (self->show_all, GTK_ALIGN_END);
462 
463   self->pan_gesture = gtk_gesture_pan_new (GTK_WIDGET (self->stack), GTK_ORIENTATION_HORIZONTAL);
464   g_signal_connect (self->pan_gesture, "pan",
465                     G_CALLBACK (gtk_shortcuts_section_pan_gesture_pan), self);
466 }
467 
468 static void
gtk_shortcuts_section_set_view_name(GtkShortcutsSection * self,const gchar * view_name)469 gtk_shortcuts_section_set_view_name (GtkShortcutsSection *self,
470                                      const gchar         *view_name)
471 {
472   if (g_strcmp0 (self->view_name, view_name) == 0)
473     return;
474 
475   g_free (self->view_name);
476   self->view_name = g_strdup (view_name);
477 
478   gtk_shortcuts_section_filter_groups (self);
479   gtk_shortcuts_section_reflow_groups (self);
480 
481   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW_NAME]);
482 }
483 
484 static void
gtk_shortcuts_section_set_max_height(GtkShortcutsSection * self,guint max_height)485 gtk_shortcuts_section_set_max_height (GtkShortcutsSection *self,
486                                       guint                max_height)
487 {
488   if (self->max_height == max_height)
489     return;
490 
491   self->max_height = max_height;
492 
493   gtk_shortcuts_section_maybe_reflow (self);
494 
495   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_HEIGHT]);
496 }
497 
498 static void
gtk_shortcuts_section_add_group(GtkShortcutsSection * self,GtkShortcutsGroup * group)499 gtk_shortcuts_section_add_group (GtkShortcutsSection *self,
500                                  GtkShortcutsGroup   *group)
501 {
502   GList *children;
503   GtkWidget *page, *column;
504 
505   children = gtk_container_get_children (GTK_CONTAINER (self->stack));
506   if (children)
507     page = g_list_last (children)->data;
508   else
509     {
510       page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
511       gtk_stack_add_named (self->stack, page, "1");
512     }
513   g_list_free (children);
514 
515   children = gtk_container_get_children (GTK_CONTAINER (page));
516   if (children)
517     column = g_list_last (children)->data;
518   else
519     {
520       column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
521       gtk_container_add (GTK_CONTAINER (page), column);
522     }
523   g_list_free (children);
524 
525   gtk_container_add (GTK_CONTAINER (column), GTK_WIDGET (group));
526   self->groups = g_list_append (self->groups, group);
527 
528   gtk_shortcuts_section_maybe_reflow (self);
529 }
530 
531 static void
gtk_shortcuts_section_show_all(GtkShortcutsSection * self)532 gtk_shortcuts_section_show_all (GtkShortcutsSection *self)
533 {
534   gtk_shortcuts_section_set_view_name (self, NULL);
535 }
536 
537 static void
update_group_visibility(GtkWidget * child,gpointer data)538 update_group_visibility (GtkWidget *child, gpointer data)
539 {
540   GtkShortcutsSection *self = data;
541 
542   if (GTK_IS_SHORTCUTS_GROUP (child))
543     {
544       gchar *view;
545       gboolean match;
546 
547       g_object_get (child, "view", &view, NULL);
548       match = view == NULL ||
549               self->view_name == NULL ||
550               strcmp (view, self->view_name) == 0;
551 
552       gtk_widget_set_visible (child, match);
553       self->has_filtered_group |= !match;
554 
555       g_free (view);
556     }
557   else if (GTK_IS_CONTAINER (child))
558     {
559       gtk_container_foreach (GTK_CONTAINER (child), update_group_visibility, data);
560     }
561 }
562 
563 static void
gtk_shortcuts_section_filter_groups(GtkShortcutsSection * self)564 gtk_shortcuts_section_filter_groups (GtkShortcutsSection *self)
565 {
566   self->has_filtered_group = FALSE;
567 
568   gtk_container_foreach (GTK_CONTAINER (self), update_group_visibility, self);
569 
570   gtk_widget_set_visible (GTK_WIDGET (self->show_all), self->has_filtered_group);
571   gtk_widget_set_visible (gtk_widget_get_parent (GTK_WIDGET (self->show_all)),
572                           gtk_widget_get_visible (GTK_WIDGET (self->show_all)) ||
573                           gtk_widget_get_visible (GTK_WIDGET (self->switcher)));
574 }
575 
576 static void
gtk_shortcuts_section_maybe_reflow(GtkShortcutsSection * self)577 gtk_shortcuts_section_maybe_reflow (GtkShortcutsSection *self)
578 {
579   if (gtk_widget_get_mapped (GTK_WIDGET (self)))
580     gtk_shortcuts_section_reflow_groups (self);
581   else
582     self->need_reflow = TRUE;
583 }
584 
585 static void
adjust_page_buttons(GtkWidget * widget,gpointer data)586 adjust_page_buttons (GtkWidget *widget,
587                      gpointer   data)
588 {
589   GtkWidget *label;
590 
591   gtk_style_context_add_class (gtk_widget_get_style_context (widget), "circular");
592 
593   label = gtk_bin_get_child (GTK_BIN (widget));
594   gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
595 }
596 
597 static void
gtk_shortcuts_section_reflow_groups(GtkShortcutsSection * self)598 gtk_shortcuts_section_reflow_groups (GtkShortcutsSection *self)
599 {
600   GList *pages, *p;
601   GList *columns, *c;
602   GList *groups, *g;
603   GList *children;
604   guint n_rows;
605   guint n_columns;
606   guint n_pages;
607   GtkWidget *current_page, *current_column;
608 
609   /* collect all groups from the current pages */
610   groups = NULL;
611   pages = gtk_container_get_children (GTK_CONTAINER (self->stack));
612   for (p = pages; p; p = p->next)
613     {
614       columns = gtk_container_get_children (GTK_CONTAINER (p->data));
615       for (c = columns; c; c = c->next)
616         {
617           children = gtk_container_get_children (GTK_CONTAINER (c->data));
618           groups = g_list_concat (groups, children);
619         }
620       g_list_free (columns);
621     }
622   g_list_free (pages);
623 
624   /* create new pages */
625   current_page = NULL;
626   current_column = NULL;
627   pages = NULL;
628   n_rows = 0;
629   n_columns = 0;
630   n_pages = 0;
631   for (g = groups; g; g = g->next)
632     {
633       GtkShortcutsGroup *group = g->data;
634       guint height;
635       gboolean visible;
636 
637       g_object_get (group,
638                     "visible", &visible,
639                     "height", &height,
640                     NULL);
641       if (!visible)
642         height = 0;
643 
644       if (current_column == NULL || n_rows + height > self->max_height)
645         {
646           GtkWidget *column;
647           GtkSizeGroup *group;
648 
649           column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
650           gtk_widget_show (column);
651 
652           group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
653 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
654           gtk_size_group_set_ignore_hidden (group, TRUE);
655 G_GNUC_END_IGNORE_DEPRECATIONS
656           g_object_set_data_full (G_OBJECT (column), "accel-size-group", group, g_object_unref);
657 
658           group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
659 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
660           gtk_size_group_set_ignore_hidden (group, TRUE);
661 G_GNUC_END_IGNORE_DEPRECATIONS
662           g_object_set_data_full (G_OBJECT (column), "title-size-group", group, g_object_unref);
663 
664           if (n_columns % 2 == 0)
665             {
666               GtkWidget *page;
667 
668               page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
669               gtk_widget_show (page);
670 
671               pages = g_list_append (pages, page);
672               current_page = page;
673             }
674 
675           gtk_container_add (GTK_CONTAINER (current_page), column);
676           current_column = column;
677           n_columns += 1;
678           n_rows = 0;
679         }
680 
681       n_rows += height;
682 
683       g_object_set (group,
684                     "accel-size-group", g_object_get_data (G_OBJECT (current_column), "accel-size-group"),
685                     "title-size-group", g_object_get_data (G_OBJECT (current_column), "title-size-group"),
686                     NULL);
687 
688       g_object_ref (group);
689       gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (group))), GTK_WIDGET (group));
690       gtk_container_add (GTK_CONTAINER (current_column), GTK_WIDGET (group));
691       g_object_unref (group);
692     }
693 
694   /* balance the last page */
695   if (n_columns % 2 == 1)
696     {
697       GtkWidget *column;
698       GtkSizeGroup *group;
699       GList *content;
700       guint n;
701 
702       column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
703       gtk_widget_show (column);
704 
705       group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
706 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
707       gtk_size_group_set_ignore_hidden (group, TRUE);
708 G_GNUC_END_IGNORE_DEPRECATIONS
709       g_object_set_data_full (G_OBJECT (column), "accel-size-group", group, g_object_unref);
710       group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
711 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
712       gtk_size_group_set_ignore_hidden (group, TRUE);
713 G_GNUC_END_IGNORE_DEPRECATIONS
714       g_object_set_data_full (G_OBJECT (column), "title-size-group", group, g_object_unref);
715 
716       gtk_container_add (GTK_CONTAINER (current_page), column);
717 
718       content = gtk_container_get_children (GTK_CONTAINER (current_column));
719       n = 0;
720 
721       for (g = g_list_last (content); g; g = g->prev)
722         {
723           GtkShortcutsGroup *group = g->data;
724           guint height;
725           gboolean visible;
726 
727           g_object_get (group,
728                         "visible", &visible,
729                         "height", &height,
730                         NULL);
731           if (!visible)
732             height = 0;
733 
734           if (n_rows - height == 0)
735             break;
736           if (ABS (n_rows - n) < ABS ((n_rows - height) - (n + height)))
737             break;
738 
739           n_rows -= height;
740           n += height;
741         }
742 
743       for (g = g->next; g; g = g->next)
744         {
745           GtkShortcutsGroup *group = g->data;
746 
747           g_object_set (group,
748                         "accel-size-group", g_object_get_data (G_OBJECT (column), "accel-size-group"),
749                         "title-size-group", g_object_get_data (G_OBJECT (column), "title-size-group"),
750                         NULL);
751 
752           g_object_ref (group);
753           gtk_container_remove (GTK_CONTAINER (current_column), GTK_WIDGET (group));
754           gtk_container_add (GTK_CONTAINER (column), GTK_WIDGET (group));
755           g_object_unref (group);
756         }
757 
758       g_list_free (content);
759     }
760 
761   /* replace the current pages with the new pages */
762   children = gtk_container_get_children (GTK_CONTAINER (self->stack));
763   g_list_free_full (children, (GDestroyNotify)gtk_widget_destroy);
764 
765   for (p = pages, n_pages = 0; p; p = p->next, n_pages++)
766     {
767       GtkWidget *page = p->data;
768       gchar *title;
769 
770       title = g_strdup_printf ("_%u", n_pages + 1);
771       gtk_stack_add_titled (self->stack, page, title, title);
772       g_free (title);
773     }
774 
775   /* fix up stack switcher */
776   gtk_container_foreach (GTK_CONTAINER (self->switcher), adjust_page_buttons, NULL);
777   gtk_widget_set_visible (GTK_WIDGET (self->switcher), (n_pages > 1));
778   gtk_widget_set_visible (gtk_widget_get_parent (GTK_WIDGET (self->switcher)),
779                           gtk_widget_get_visible (GTK_WIDGET (self->show_all)) ||
780                           gtk_widget_get_visible (GTK_WIDGET (self->switcher)));
781 
782   /* clean up */
783   g_list_free (groups);
784   g_list_free (pages);
785 
786   self->need_reflow = FALSE;
787 }
788 
789 static gboolean
gtk_shortcuts_section_change_current_page(GtkShortcutsSection * self,gint offset)790 gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
791                                            gint                 offset)
792 {
793   GtkWidget *child;
794   GList *children, *l;
795 
796   child = gtk_stack_get_visible_child (self->stack);
797   children = gtk_container_get_children (GTK_CONTAINER (self->stack));
798   l = g_list_find (children, child);
799 
800   if (offset == 1)
801     l = l->next;
802   else if (offset == -1)
803     l = l->prev;
804   else
805     g_assert_not_reached ();
806 
807   if (l)
808     gtk_stack_set_visible_child (self->stack, GTK_WIDGET (l->data));
809   else
810     gtk_widget_error_bell (GTK_WIDGET (self));
811 
812   g_list_free (children);
813 
814   return TRUE;
815 }
816 
817 static void
gtk_shortcuts_section_pan_gesture_pan(GtkGesturePan * gesture,GtkPanDirection direction,gdouble offset,GtkShortcutsSection * self)818 gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan       *gesture,
819                                        GtkPanDirection      direction,
820                                        gdouble              offset,
821                                        GtkShortcutsSection *self)
822 {
823   if (offset < 50)
824     return;
825 
826   if (direction == GTK_PAN_DIRECTION_LEFT)
827     gtk_shortcuts_section_change_current_page (self, 1);
828   else if (direction == GTK_PAN_DIRECTION_RIGHT)
829     gtk_shortcuts_section_change_current_page (self, -1);
830   else
831     g_assert_not_reached ();
832 
833   gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
834 }
835