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 | |
58 | struct _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 *; |
71 | GList *groups; |
72 | |
73 | gboolean has_filtered_group; |
74 | }; |
75 | |
76 | struct _GtkShortcutsSectionClass |
77 | { |
78 | GtkBoxClass parent_class; |
79 | |
80 | gboolean (* change_current_page) (GtkShortcutsSection *self, |
81 | int offset); |
82 | |
83 | }; |
84 | |
85 | static void gtk_shortcuts_section_buildable_iface_init (GtkBuildableIface *iface); |
86 | |
87 | G_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 | |
91 | enum { |
92 | PROP_0, |
93 | PROP_TITLE, |
94 | PROP_SECTION_NAME, |
95 | PROP_VIEW_NAME, |
96 | PROP_MAX_HEIGHT, |
97 | LAST_PROP |
98 | }; |
99 | |
100 | enum { |
101 | CHANGE_CURRENT_PAGE, |
102 | LAST_SIGNAL |
103 | }; |
104 | |
105 | static GParamSpec *properties[LAST_PROP]; |
106 | static guint signals[LAST_SIGNAL]; |
107 | |
108 | static void gtk_shortcuts_section_set_view_name (GtkShortcutsSection *self, |
109 | const char *view_name); |
110 | static void gtk_shortcuts_section_set_max_height (GtkShortcutsSection *self, |
111 | guint max_height); |
112 | static void gtk_shortcuts_section_add_group (GtkShortcutsSection *self, |
113 | GtkShortcutsGroup *group); |
114 | |
115 | static void gtk_shortcuts_section_show_all (GtkShortcutsSection *self); |
116 | static void gtk_shortcuts_section_filter_groups (GtkShortcutsSection *self); |
117 | static void gtk_shortcuts_section_reflow_groups (GtkShortcutsSection *self); |
118 | |
119 | static gboolean gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self, |
120 | int offset); |
121 | |
122 | static void gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan *gesture, |
123 | GtkPanDirection direction, |
124 | double offset, |
125 | GtkShortcutsSection *self); |
126 | |
127 | static GtkBuildableIface *parent_buildable_iface; |
128 | |
129 | static void |
130 | gtk_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 | |
141 | static void |
142 | gtk_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 | |
149 | static void |
150 | map_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 | |
158 | static void |
159 | gtk_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 | |
169 | static void |
170 | gtk_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 | |
180 | static void |
181 | gtk_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 | |
194 | static void |
195 | gtk_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 | |
206 | static void |
207 | gtk_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 | |
237 | static void |
238 | gtk_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 | |
270 | static void |
271 | gtk_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 | |
376 | static void |
377 | gtk_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 | |
424 | static void |
425 | gtk_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 | |
440 | static void |
441 | gtk_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 | |
454 | static void |
455 | gtk_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 | |
480 | static void |
481 | gtk_shortcuts_section_show_all (GtkShortcutsSection *self) |
482 | { |
483 | gtk_shortcuts_section_set_view_name (self, NULL); |
484 | } |
485 | |
486 | static void |
487 | update_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 | |
515 | static void |
516 | gtk_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 | |
533 | static void |
534 | gtk_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 | |
736 | static gboolean |
737 | gtk_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 | |
759 | static void |
760 | gtk_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 | |