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

source code of gtk/gtk/gtkshortcutssection.c