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