Codebase list xapp / 0e063b8
Add an XAppStackSidebar widget (#45) This is mostly a modified GtkStackSidebar that allows adding an icon. The placement of the icon is controlled by setting the orientation. JosephMcc authored 5 years ago Clement Lefebvre committed 5 years ago
4 changed file(s) with 601 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
1919 <xi:include href="xml/xapp-monitor-blanker.xml"/>
2020 <xi:include href="xml/xapp-gtk-window.xml"/>
2121 <xi:include href="xml/xapp-preferences-window.xml"/>
22 <xi:include href="xml/xapp-stack-sidebar.xml"/>
2223
2324 </chapter>
2425 <chapter id="object-tree">
1212 'xapp-gtk-window.h',
1313 'xapp-kbd-layout-controller.h',
1414 'xapp-monitor-blanker.h',
15 'xapp-preferences-window.h'
15 'xapp-preferences-window.h',
16 'xapp-stack-sidebar.h',
1617 ]
1718
1819 xapp_sources = [
2122 'xapp-kbd-layout-controller.c',
2223 'xapp-monitor-blanker.c',
2324 'xapp-preferences-window.c',
25 'xapp-stack-sidebar.c',
2426 ]
2527
2628 libxapp = library('xapp',
0 /* Based on gtkstacksidebar.c */
1
2 #include "xapp-stack-sidebar.h"
3
4 /**
5 * SECTION:xapp-stack-sidebar
6 * @Title: XAppStackSidebar
7 * @Short_description: An automatic sidebar widget
8 *
9 * A XAppStackSidebar allows you to quickly and easily provide a
10 * consistent "sidebar" object for your user interface
11 *
12 * In order to use a XAppStackSidebar, you simply use a GtkStack to
13 * organize your UI flow, and add the sidebar to your sidebar area. You
14 * can use xapp_stack_sidebar_set_stack() to connect the #XAppStackSidebar
15 * to the #GtkStack. The #XAppStackSidebar is an extended version of the
16 * the #GtkStackSidebar that allows showing an icon in addition to the text.
17 *
18 * # CSS nodes
19 *
20 * XAppStackSidebar has a single CSS node with the name stacksidebar and
21 * style class .sidebar
22 *
23 * When circumstances require it, XAppStackSidebar adds the
24 * .needs-attention style class to the widgets representing the stack
25 * pages.
26 */
27
28
29 struct _XAppStackSidebar
30 {
31 GtkBin parent_instance;
32
33 GtkOrientation orientation;
34 GtkListBox *list;
35 GtkStack *stack;
36 GHashTable *rows;
37 gboolean in_child_changed;
38 };
39
40 enum
41 {
42 PROP_0,
43 PROP_ORIENTATION,
44 PROP_STACK,
45 N_PROPERTIES
46 };
47
48 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
49
50 G_DEFINE_TYPE (XAppStackSidebar, xapp_stack_sidebar, GTK_TYPE_BIN)
51
52 static void
53 xapp_stack_sidebar_set_property (GObject *object,
54 guint prop_id,
55 const GValue *value,
56 GParamSpec *pspec)
57 {
58 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (object);
59
60 switch (prop_id)
61 {
62 case PROP_ORIENTATION:
63 sidebar->orientation = g_value_get_enum (value);
64 break;
65 case PROP_STACK:
66 xapp_stack_sidebar_set_stack (XAPP_STACK_SIDEBAR (object), g_value_get_object (value));
67 break;
68 default:
69 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
70 break;
71 }
72 }
73
74 static void
75 xapp_stack_sidebar_get_property (GObject *object,
76 guint prop_id,
77 GValue *value,
78 GParamSpec *pspec)
79 {
80 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (object);
81
82 switch (prop_id)
83 {
84 case PROP_ORIENTATION:
85 g_value_set_enum (value, sidebar->orientation);
86 break;
87 case PROP_STACK:
88 g_value_set_object (value, sidebar->stack);
89 break;
90 default:
91 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
92 break;
93 }
94 }
95
96 static void
97 update_header (GtkListBoxRow *row,
98 GtkListBoxRow *before,
99 gpointer userdata)
100 {
101 GtkWidget *ret = NULL;
102
103 if (before && !gtk_list_box_row_get_header (row))
104 {
105 ret = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
106 gtk_list_box_row_set_header (row, ret);
107 }
108 }
109
110 static gint
111 sort_list (GtkListBoxRow *row1,
112 GtkListBoxRow *row2,
113 gpointer userdata)
114 {
115 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (userdata);
116 GtkWidget *item;
117 GtkWidget *widget;
118 gint left = 0;
119 gint right = 0;
120
121
122 if (row1)
123 {
124 item = gtk_bin_get_child (GTK_BIN (row1));
125 widget = g_object_get_data (G_OBJECT (item), "stack-child");
126 gtk_container_child_get (GTK_CONTAINER (sidebar->stack), widget,
127 "position", &left,
128 NULL);
129 }
130
131 if (row2)
132 {
133 item = gtk_bin_get_child (GTK_BIN (row2));
134 widget = g_object_get_data (G_OBJECT (item), "stack-child");
135 gtk_container_child_get (GTK_CONTAINER (sidebar->stack), widget,
136 "position", &right,
137 NULL);
138 }
139
140 if (left < right)
141 {
142 return -1;
143 }
144
145 if (left == right)
146 {
147 return 0;
148 }
149
150 return 1;
151 }
152
153 static void
154 xapp_stack_sidebar_row_selected (GtkListBox *box,
155 GtkListBoxRow *row,
156 gpointer user_data)
157 {
158 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (user_data);
159 GtkWidget *item;
160 GtkWidget *widget;
161
162 if (sidebar->in_child_changed)
163 {
164 return;
165 }
166
167 if (!row)
168 {
169 return;
170 }
171
172 item = gtk_bin_get_child (GTK_BIN (row));
173 widget = g_object_get_data (G_OBJECT (item), "stack-child");
174 gtk_stack_set_visible_child (sidebar->stack, widget);
175 }
176
177 static void
178 xapp_stack_sidebar_constructed (GObject *object)
179 {
180 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (object);
181 GtkStyleContext *style;
182 GtkWidget *sw;
183
184 G_OBJECT_CLASS (xapp_stack_sidebar_parent_class)->constructed (object);
185
186 sw = gtk_scrolled_window_new (NULL, NULL);
187 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
188 GTK_POLICY_NEVER,
189 GTK_POLICY_AUTOMATIC);
190
191 gtk_container_add (GTK_CONTAINER (sidebar), sw);
192
193 sidebar->list = GTK_LIST_BOX (gtk_list_box_new ());
194
195 gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (sidebar->list));
196
197 gtk_list_box_set_header_func (sidebar->list, update_header, sidebar, NULL);
198 gtk_list_box_set_sort_func (sidebar->list, sort_list, sidebar, NULL);
199
200 g_signal_connect (sidebar->list, "row-selected",
201 G_CALLBACK (xapp_stack_sidebar_row_selected), sidebar);
202
203 style = gtk_widget_get_style_context (GTK_WIDGET (sidebar));
204 gtk_style_context_add_class (style, "sidebar");
205 }
206
207 static void
208 xapp_stack_sidebar_init (XAppStackSidebar *sidebar)
209 {
210 sidebar->rows = g_hash_table_new (NULL, NULL);
211 }
212
213 static void
214 update_row (XAppStackSidebar *sidebar,
215 GtkWidget *widget,
216 GtkWidget *row)
217 {
218 GList *children;
219 GList *list;
220 GtkWidget *item;
221 gchar *title;
222 gchar *icon_name;
223 gboolean needs_attention;
224 GtkStyleContext *context;
225
226 gtk_container_child_get (GTK_CONTAINER (sidebar->stack),
227 widget,
228 "title", &title,
229 "icon-name", &icon_name,
230 "needs-attention", &needs_attention,
231 NULL);
232
233 item = gtk_bin_get_child (GTK_BIN (row));
234
235 children = gtk_container_get_children (GTK_CONTAINER (item));
236 for (list = children; list != NULL; list = list->next)
237 {
238 GtkWidget *child = list->data;
239
240 if (GTK_IS_LABEL (child))
241 {
242 gtk_label_set_text (GTK_LABEL (child), title);
243 }
244 else if (GTK_IS_IMAGE (child))
245 {
246 if (sidebar->orientation == GTK_ORIENTATION_HORIZONTAL)
247 {
248 gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name, GTK_ICON_SIZE_MENU);
249 }
250 else
251 {
252 gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR);
253 }
254 }
255 }
256
257 gtk_widget_set_visible (row, gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL));
258
259 context = gtk_widget_get_style_context (row);
260 if (needs_attention)
261 {
262 gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
263 }
264 else
265 {
266 gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
267 }
268
269 g_free (title);
270 g_free (icon_name);
271 g_list_free (children);
272 }
273
274 static void
275 on_position_updated (GtkWidget *widget,
276 GParamSpec *pspec,
277 XAppStackSidebar *sidebar)
278 {
279 gtk_list_box_invalidate_sort (sidebar->list);
280 }
281
282 static void
283 on_child_updated (GtkWidget *widget,
284 GParamSpec *pspec,
285 XAppStackSidebar *sidebar)
286 {
287 GtkWidget *row;
288
289 row = g_hash_table_lookup (sidebar->rows, widget);
290 update_row (sidebar, widget, row);
291 }
292
293 static void
294 add_child (GtkWidget *widget,
295 XAppStackSidebar *sidebar)
296 {
297 GtkWidget *item;
298 GtkWidget *label;
299 GtkWidget *icon;
300 GtkWidget *row;
301 int spacing = 0;
302
303 /* Check we don't actually already know about this widget */
304 if (g_hash_table_lookup (sidebar->rows, widget))
305 {
306 return;
307 }
308
309 if (sidebar->orientation == GTK_ORIENTATION_HORIZONTAL)
310 {
311 spacing = 6;
312 }
313
314 /* Make a pretty item when we add children */
315 item = gtk_box_new (sidebar->orientation, spacing);
316
317 icon = gtk_image_new ();
318 gtk_widget_set_halign (icon, GTK_ALIGN_CENTER);
319 gtk_box_pack_start (GTK_BOX (item), icon, FALSE, FALSE, 0);
320
321 label = gtk_label_new ("");
322 gtk_widget_set_halign (item, GTK_ALIGN_CENTER);
323 gtk_box_pack_start (GTK_BOX (item), label, FALSE, FALSE, 0);
324
325 row = gtk_list_box_row_new ();
326 gtk_container_add (GTK_CONTAINER (row), item);
327 gtk_widget_show_all (item);
328
329 update_row (sidebar, widget, row);
330
331 /* Hook up events */
332 g_signal_connect (widget, "child-notify::title",
333 G_CALLBACK (on_child_updated), sidebar);
334 g_signal_connect (widget, "child-notify::icon-name",
335 G_CALLBACK (on_child_updated), sidebar);
336 g_signal_connect (widget, "child-notify::needs-attention",
337 G_CALLBACK (on_child_updated), sidebar);
338 g_signal_connect (widget, "notify::visible",
339 G_CALLBACK (on_child_updated), sidebar);
340 g_signal_connect (widget, "child-notify::position",
341 G_CALLBACK (on_position_updated), sidebar);
342
343 g_object_set_data (G_OBJECT (item), "stack-child", widget);
344 g_hash_table_insert (sidebar->rows, widget, row);
345 gtk_container_add (GTK_CONTAINER (sidebar->list), row);
346 }
347
348 static void
349 remove_child (GtkWidget *widget,
350 XAppStackSidebar *sidebar)
351 {
352 GtkWidget *row;
353
354 row = g_hash_table_lookup (sidebar->rows, widget);
355 if (!row)
356 {
357 return;
358 }
359
360 g_signal_handlers_disconnect_by_func (widget, on_child_updated, sidebar);
361 g_signal_handlers_disconnect_by_func (widget, on_position_updated, sidebar);
362
363 gtk_container_remove (GTK_CONTAINER (sidebar->list), row);
364 g_hash_table_remove (sidebar->rows, widget);
365 }
366
367 static void
368 populate_sidebar (XAppStackSidebar *sidebar)
369 {
370 GtkWidget *widget;
371 GtkWidget *row;
372
373 gtk_container_foreach (GTK_CONTAINER (sidebar->stack), (GtkCallback)add_child, sidebar);
374
375 widget = gtk_stack_get_visible_child (sidebar->stack);
376 if (widget)
377 {
378 row = g_hash_table_lookup (sidebar->rows, widget);
379 gtk_list_box_select_row (sidebar->list, GTK_LIST_BOX_ROW (row));
380 }
381 }
382
383 static void
384 clear_sidebar (XAppStackSidebar *sidebar)
385 {
386 gtk_container_foreach (GTK_CONTAINER (sidebar->stack), (GtkCallback)remove_child, sidebar);
387 }
388
389 static void
390 on_child_changed (GtkWidget *widget,
391 GParamSpec *pspec,
392 XAppStackSidebar *sidebar)
393 {
394 GtkWidget *child;
395 GtkWidget *row;
396
397 child = gtk_stack_get_visible_child (GTK_STACK (widget));
398 row = g_hash_table_lookup (sidebar->rows, child);
399
400 if (row != NULL)
401 {
402 sidebar->in_child_changed = TRUE;
403 gtk_list_box_select_row (sidebar->list, GTK_LIST_BOX_ROW (row));
404 sidebar->in_child_changed = FALSE;
405 }
406 }
407
408 static void
409 on_stack_child_added (GtkContainer *container,
410 GtkWidget *widget,
411 XAppStackSidebar *sidebar)
412 {
413 add_child (widget, sidebar);
414 }
415
416 static void
417 on_stack_child_removed (GtkContainer *container,
418 GtkWidget *widget,
419 XAppStackSidebar *sidebar)
420 {
421 remove_child (widget, sidebar);
422 }
423
424 static void
425 disconnect_stack_signals (XAppStackSidebar *sidebar)
426 {
427 g_signal_handlers_disconnect_by_func (sidebar->stack, on_stack_child_added, sidebar);
428 g_signal_handlers_disconnect_by_func (sidebar->stack, on_stack_child_removed, sidebar);
429 g_signal_handlers_disconnect_by_func (sidebar->stack, on_child_changed, sidebar);
430 g_signal_handlers_disconnect_by_func (sidebar->stack, disconnect_stack_signals, sidebar);
431 }
432
433 static void
434 connect_stack_signals (XAppStackSidebar *sidebar)
435 {
436 g_signal_connect_after (sidebar->stack, "add",
437 G_CALLBACK (on_stack_child_added), sidebar);
438 g_signal_connect_after (sidebar->stack, "remove",
439 G_CALLBACK (on_stack_child_removed), sidebar);
440 g_signal_connect (sidebar->stack, "notify::visible-child",
441 G_CALLBACK (on_child_changed), sidebar);
442 g_signal_connect_swapped (sidebar->stack, "destroy",
443 G_CALLBACK (disconnect_stack_signals), sidebar);
444 }
445
446 static void
447 xapp_stack_sidebar_dispose (GObject *object)
448 {
449 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (object);
450
451 xapp_stack_sidebar_set_stack (sidebar, NULL);
452
453 G_OBJECT_CLASS (xapp_stack_sidebar_parent_class)->dispose (object);
454 }
455
456 static void
457 xapp_stack_sidebar_finalize (GObject *object)
458 {
459 XAppStackSidebar *sidebar = XAPP_STACK_SIDEBAR (object);
460
461 g_hash_table_destroy (sidebar->rows);
462
463 G_OBJECT_CLASS (xapp_stack_sidebar_parent_class)->finalize (object);
464 }
465
466 static void
467 xapp_stack_sidebar_class_init (XAppStackSidebarClass *klass)
468 {
469 GObjectClass *object_class = G_OBJECT_CLASS (klass);
470 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
471
472 object_class->constructed = xapp_stack_sidebar_constructed;
473 object_class->dispose = xapp_stack_sidebar_dispose;
474 object_class->finalize = xapp_stack_sidebar_finalize;
475 object_class->set_property = xapp_stack_sidebar_set_property;
476 object_class->get_property = xapp_stack_sidebar_get_property;
477
478 obj_properties[PROP_STACK] =
479 g_param_spec_object ("stack",
480 "Stack",
481 "Associated stack for this XAppStackSidebar",
482 GTK_TYPE_STACK,
483 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
484
485 obj_properties[PROP_ORIENTATION] =
486 g_param_spec_enum ("orientation",
487 "Orientation",
488 "The orientation of the icon",
489 GTK_TYPE_ORIENTATION,
490 GTK_ORIENTATION_HORIZONTAL,
491 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
492
493 g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
494
495 gtk_widget_class_set_css_name (widget_class, "stacksidebar");
496 }
497
498 /**
499 * xapp_stack_sidebar_new:
500 * @orientation: a #GtkOrientation
501 *
502 * Creates a new sidebar.
503 *
504 * Returns: the new #XAppStackSidebar
505 */
506
507 XAppStackSidebar *
508 xapp_stack_sidebar_new (GtkOrientation orientation)
509 {
510 return g_object_new (XAPP_TYPE_STACK_SIDEBAR,
511 "orientation", orientation,
512 NULL);
513 }
514
515 /**
516 * xapp_stack_sidebar_set_stack:
517 * @sidebar: a #XAppStackSidebar
518 * @stack: a #GtkStack
519 *
520 * Set the #GtkStack associated with this #XAppStackSidebar.
521 *
522 * The sidebar widget will automatically update according to the order
523 * (packing) and items within the given #GtkStack.
524 */
525
526 void
527 xapp_stack_sidebar_set_stack (XAppStackSidebar *sidebar,
528 GtkStack *stack)
529 {
530 g_return_if_fail (XAPP_IS_STACK_SIDEBAR (sidebar));
531 g_return_if_fail (GTK_IS_STACK (stack) || stack == NULL);
532
533 if (sidebar->stack == stack)
534 {
535 return;
536 }
537
538 if (sidebar->stack)
539 {
540 disconnect_stack_signals (sidebar);
541 clear_sidebar (sidebar);
542 g_clear_object (&sidebar->stack);
543 }
544
545 if (stack)
546 {
547 sidebar->stack = g_object_ref (stack);
548 populate_sidebar (sidebar);
549 connect_stack_signals (sidebar);
550 }
551
552 gtk_widget_queue_resize (GTK_WIDGET (sidebar));
553
554 g_object_notify (G_OBJECT (sidebar), "stack");
555 }
556
557 /**
558 * xapp_stack_sidebar_get_stack:
559 * @sidebar: a #XAppStackSidebar
560 *
561 * Retrieves the stack.
562 * See xapp_stack_sidebar_set_stack().
563 *
564 * Returns: (nullable) (transfer none): the associated #GtkStack or
565 * %NULL if none has been set explicitly
566 */
567
568 GtkStack *
569 xapp_stack_sidebar_get_stack (XAppStackSidebar *sidebar)
570 {
571 g_return_val_if_fail (XAPP_IS_STACK_SIDEBAR (sidebar), NULL);
572
573 return GTK_STACK (sidebar->stack);
574 }
0 #ifndef _XAPP_STACK_SIDEBAR_H_
1 #define _XAPP_STACK_SIDEBAR_H_
2
3 #include <glib-object.h>
4 #include <gtk/gtk.h>
5
6 G_BEGIN_DECLS
7
8 #define XAPP_TYPE_STACK_SIDEBAR (xapp_stack_sidebar_get_type ())
9
10 G_DECLARE_FINAL_TYPE (XAppStackSidebar, xapp_stack_sidebar, XAPP, STACK_SIDEBAR, GtkBin)
11
12 XAppStackSidebar *xapp_stack_sidebar_new (GtkOrientation orientation);
13
14 void xapp_stack_sidebar_set_stack (XAppStackSidebar *sidebar,
15 GtkStack *stack);
16
17 GtkStack *xapp_stack_sidebar_get_stack (XAppStackSidebar *sidebar);
18
19 G_END_DECLS
20
21 #endif /*_XAPP_STACK_SIDEBAR_H_ */