1 /*
2 * Copyright (c) 2012-2013 Andrzej Radecki <andrzejr@xfce.org>
3 * Copyright (c) 2017 Viktor Odintsev <ninetls@xfce.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #endif
28
29 #include <libxfce4panel/libxfce4panel.h>
30
31 #include "sn-box.h"
32 #include "sn-button.h"
33 #include "sn-util.h"
34
35
36
37 static void sn_box_get_property (GObject *object,
38 guint prop_id,
39 GValue *value,
40 GParamSpec *pspec);
41 static void sn_box_finalize (GObject *object);
42
43 static void sn_box_collect_known_items (SnBox *box,
44 GHashTable *result);
45
46 static void sn_box_list_changed (SnBox *box,
47 SnConfig *config);
48
49 static void sn_box_add (GtkContainer *container,
50 GtkWidget *child);
51
52 static void sn_box_remove (GtkContainer *container,
53 GtkWidget *child);
54
55 static void sn_box_forall (GtkContainer *container,
56 gboolean include_internals,
57 GtkCallback callback,
58 gpointer callback_data);
59
60 static GType sn_box_child_type (GtkContainer *container);
61
62 static void sn_box_get_preferred_width (GtkWidget *widget,
63 gint *minimal_width,
64 gint *natural_width);
65
66 static void sn_box_get_preferred_height (GtkWidget *widget,
67 gint *minimal_height,
68 gint *natural_height);
69
70 static void sn_box_size_allocate (GtkWidget *widget,
71 GtkAllocation *allocation);
72
73
74
75 enum
76 {
77 PROP_0,
78 PROP_HAS_HIDDEN
79 };
80
81 struct _SnBoxClass
82 {
83 GtkContainerClass __parent__;
84 };
85
86 struct _SnBox
87 {
88 GtkContainer __parent__;
89
90 SnConfig *config;
91
92 /* in theory it's possible to have multiple items with same name */
93 GHashTable *children;
94
95 /* hidden children counter */
96 gint n_hidden_children;
97 gint n_visible_children;
98 gboolean show_hidden;
99 };
100
G_DEFINE_TYPE(SnBox,sn_box,GTK_TYPE_CONTAINER)101 G_DEFINE_TYPE (SnBox, sn_box, GTK_TYPE_CONTAINER)
102
103
104
105 static void
106 sn_box_class_init (SnBoxClass *klass)
107 {
108 GObjectClass *object_class;
109 GtkWidgetClass *widget_class;
110 GtkContainerClass *container_class;
111
112 object_class = G_OBJECT_CLASS (klass);
113 object_class->get_property = sn_box_get_property;
114 object_class->finalize = sn_box_finalize;
115
116 widget_class = GTK_WIDGET_CLASS (klass);
117 widget_class->get_preferred_width = sn_box_get_preferred_width;
118 widget_class->get_preferred_height = sn_box_get_preferred_height;
119 widget_class->size_allocate = sn_box_size_allocate;
120
121 container_class = GTK_CONTAINER_CLASS (klass);
122 container_class->add = sn_box_add;
123 container_class->remove = sn_box_remove;
124 container_class->forall = sn_box_forall;
125 container_class->child_type = sn_box_child_type;
126
127 g_object_class_install_property (object_class,
128 PROP_HAS_HIDDEN,
129 g_param_spec_boolean ("has-hidden",
130 NULL, NULL,
131 FALSE,
132 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
133 }
134
135
136
137 static void
sn_box_init(SnBox * box)138 sn_box_init (SnBox *box)
139 {
140 gtk_widget_set_has_window (GTK_WIDGET (box), FALSE);
141 gtk_widget_set_can_focus (GTK_WIDGET (box), TRUE);
142 gtk_container_set_border_width (GTK_CONTAINER (box), 0);
143
144 box->children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
145 }
146
147
148
149 static void
sn_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)150 sn_box_get_property(GObject *object,
151 guint prop_id,
152 GValue *value,
153 GParamSpec *pspec)
154 {
155 SnBox *box = XFCE_SN_BOX(object);
156
157 switch (prop_id)
158 {
159 case PROP_HAS_HIDDEN:
160 g_value_set_boolean(value, box->n_hidden_children > 0);
161 break;
162
163 default:
164 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
165 break;
166 }
167 }
168
169
170
171 static void
sn_box_finalize(GObject * object)172 sn_box_finalize (GObject *object)
173 {
174 SnBox *box = XFCE_SN_BOX (object);
175
176 g_hash_table_destroy (box->children);
177
178 G_OBJECT_CLASS (sn_box_parent_class)->finalize (object);
179 }
180
181
182
183 GtkWidget *
sn_box_new(SnConfig * config)184 sn_box_new (SnConfig *config)
185 {
186 SnBox *box = g_object_new (XFCE_TYPE_SN_BOX, NULL);
187
188 box->config = config;
189
190 sn_signal_connect_weak_swapped (G_OBJECT (box->config), "collect-known-items",
191 G_CALLBACK (sn_box_collect_known_items), box);
192 sn_signal_connect_weak_swapped (G_OBJECT (box->config), "items-list-changed",
193 G_CALLBACK (sn_box_list_changed), box);
194
195 return GTK_WIDGET (box);
196 }
197
198
199
200 static void
sn_box_collect_known_items_callback(GtkWidget * widget,gpointer user_data)201 sn_box_collect_known_items_callback (GtkWidget *widget,
202 gpointer user_data)
203 {
204 SnButton *button = XFCE_SN_BUTTON (widget);
205 GHashTable *table = user_data;
206 gchar *name;
207
208 name = g_strdup (sn_button_get_name (button));
209 g_hash_table_replace (table, name, name);
210 }
211
212
213
214 static void
sn_box_collect_known_items(SnBox * box,GHashTable * result)215 sn_box_collect_known_items (SnBox *box,
216 GHashTable *result)
217 {
218 gtk_container_foreach (GTK_CONTAINER (box),
219 sn_box_collect_known_items_callback, result);
220 }
221
222
223
224 static void
sn_box_list_changed(SnBox * box,SnConfig * config)225 sn_box_list_changed (SnBox *box,
226 SnConfig *config)
227 {
228 SnButton *button;
229 GList *known_items, *li, *li_int, *li_tmp;
230 gint n_hidden_children = 0, n_visible_children = 0;
231
232 g_return_if_fail (XFCE_IS_SN_BOX (box));
233 g_return_if_fail (XFCE_IS_SN_CONFIG (config));
234
235 known_items = sn_config_get_known_items (box->config);
236 for (li = known_items; li != NULL; li = li->next)
237 {
238 li_int = g_hash_table_lookup (box->children, li->data);
239 for (li_tmp = li_int; li_tmp != NULL; li_tmp = li_tmp->next)
240 {
241 button = li_tmp->data;
242 if (!sn_config_is_hidden (box->config,
243 sn_button_get_name (button)))
244 {
245 gtk_widget_map (GTK_WIDGET(button));
246 n_visible_children++;
247 }
248 else
249 {
250 gtk_widget_set_mapped (GTK_WIDGET (button), box->show_hidden);
251 n_hidden_children++;
252 }
253 }
254 }
255
256 box->n_visible_children = n_visible_children;
257 if (box->n_hidden_children != n_hidden_children)
258 {
259 box->n_hidden_children = n_hidden_children;
260 g_object_notify (G_OBJECT (box), "has-hidden");
261 }
262
263 gtk_widget_queue_resize (GTK_WIDGET (box));
264 }
265
266
267
268 static void
sn_box_add(GtkContainer * container,GtkWidget * child)269 sn_box_add (GtkContainer *container,
270 GtkWidget *child)
271 {
272 SnBox *box = XFCE_SN_BOX (container);
273 SnButton *button = XFCE_SN_BUTTON (child);
274 GList *li;
275 const gchar *name;
276
277 g_return_if_fail (XFCE_IS_SN_BOX (box));
278 g_return_if_fail (XFCE_IS_SN_BUTTON (button));
279 g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) == NULL);
280
281 name = sn_button_get_name (button);
282 li = g_hash_table_lookup (box->children, name);
283 li = g_list_prepend (li, button);
284 g_hash_table_replace (box->children, g_strdup (name), li);
285
286 gtk_widget_set_parent (child, GTK_WIDGET (box));
287
288 gtk_widget_queue_resize (GTK_WIDGET (container));
289 }
290
291
292
293 static void
sn_box_remove(GtkContainer * container,GtkWidget * child)294 sn_box_remove (GtkContainer *container,
295 GtkWidget *child)
296 {
297 SnBox *box = XFCE_SN_BOX (container);
298 SnButton *button = XFCE_SN_BUTTON (child);
299 GList *li, *li_tmp;
300 const gchar *name;
301
302 /* search the child */
303 name = sn_button_get_name (button);
304 li = g_hash_table_lookup (box->children, name);
305 li_tmp = g_list_find (li, button);
306 if (G_LIKELY (li_tmp != NULL))
307 {
308 /* unparent widget */
309 li = g_list_remove_link (li, li_tmp);
310 g_hash_table_replace (box->children, g_strdup (name), li);
311 gtk_widget_unparent (child);
312
313 /* resize, so we update has-hidden */
314 gtk_widget_queue_resize (GTK_WIDGET (container));
315 }
316 }
317
318
319
320 static void
sn_box_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)321 sn_box_forall (GtkContainer *container,
322 gboolean include_internals,
323 GtkCallback callback,
324 gpointer callback_data)
325 {
326 SnBox *box = XFCE_SN_BOX (container);
327 SnButton *button;
328 GList *known_items, *li, *li_int, *li_tmp;
329
330 /* run callback for all children */
331 known_items = sn_config_get_known_items (box->config);
332 for (li = known_items; li != NULL; li = li->next)
333 {
334 li_int = g_hash_table_lookup (box->children, li->data);
335 for (li_tmp = li_int; li_tmp != NULL; li_tmp = li_tmp->next)
336 {
337 button = li_tmp->data;
338 callback (GTK_WIDGET (button), callback_data);
339 }
340 }
341 }
342
343
344
345 static GType
sn_box_child_type(GtkContainer * container)346 sn_box_child_type (GtkContainer *container)
347 {
348 return XFCE_TYPE_SN_BUTTON;
349 }
350
351
352
353 static void
sn_box_measure_and_allocate(GtkWidget * widget,gint * minimum_length,gint * natural_length,gboolean allocate,gint x0,gint y0,gboolean horizontal)354 sn_box_measure_and_allocate (GtkWidget *widget,
355 gint *minimum_length,
356 gint *natural_length,
357 gboolean allocate,
358 gint x0,
359 gint y0,
360 gboolean horizontal)
361 {
362 SnBox *box = XFCE_SN_BOX (widget);
363 SnButton *button;
364 GList *known_items, *li, *li_int, *li_tmp;
365 gint panel_size, config_nrows, icon_size, hx_size, hy_size, nrows;
366 gboolean single_row, single_horizontal, square_icons, rect_child;
367 gint total_length, column_length, item_length, row;
368 GtkRequisition child_req;
369 GtkAllocation child_alloc;
370
371 gint n_hidden_children = 0, n_visible_children = 0;
372
373 panel_size = sn_config_get_panel_size (box->config);
374 config_nrows = sn_config_get_nrows (box->config);
375 icon_size = sn_config_get_icon_size (box->config);
376 single_row = sn_config_get_single_row (box->config);
377 square_icons = sn_config_get_square_icons (box->config);
378 icon_size += 2; /* additional padding */
379 if (square_icons)
380 {
381 nrows = single_row ? 1 : MAX (1, config_nrows);
382 hx_size = hy_size = panel_size / nrows;
383 }
384 else
385 {
386 hx_size = MIN (icon_size, panel_size);
387 nrows = single_row ? 1 : MAX (1, panel_size / hx_size);
388 hy_size = panel_size / nrows;
389 }
390
391 total_length = 0;
392 column_length = 0;
393 item_length = 0;
394 row = 0;
395
396 known_items = sn_config_get_known_items (box->config);
397 for (li = known_items; li != NULL; li = li->next)
398 {
399 li_int = g_hash_table_lookup (box->children, li->data);
400 for (li_tmp = li_int; li_tmp != NULL; li_tmp = li_tmp->next)
401 {
402 button = li_tmp->data;
403 if (sn_config_is_hidden (box->config,
404 sn_button_get_name (button)))
405 {
406 n_hidden_children++;
407 if (!box->show_hidden)
408 {
409 gtk_widget_unmap (GTK_WIDGET (button));
410 continue;
411 }
412 }
413 gtk_widget_map (GTK_WIDGET (button));
414 n_visible_children++;
415
416 gtk_widget_get_preferred_size (GTK_WIDGET (button), NULL, &child_req);
417
418 rect_child = child_req.width > child_req.height;
419 if (horizontal)
420 {
421 if (square_icons && (!rect_child || (config_nrows >= 2 && !single_row)))
422 item_length = hx_size;
423 else
424 item_length = MAX (hx_size, child_req.width);
425
426 column_length = MAX (column_length, item_length);
427 single_horizontal = FALSE;
428 }
429 else
430 {
431 column_length = hx_size;
432 single_horizontal = rect_child;
433
434 if (square_icons)
435 item_length = single_horizontal ? panel_size : hy_size;
436 else
437 item_length = MAX (MIN (panel_size, child_req.width), hy_size);
438 }
439
440 if (single_horizontal)
441 {
442 if (row > 0)
443 total_length += hx_size;
444 row = -1; /* will become 0 later and take the full length */
445 }
446
447 if (allocate)
448 {
449 if (horizontal)
450 {
451 child_alloc.x = x0 + total_length;
452 child_alloc.y = y0 + row * hy_size;
453 child_alloc.width = item_length;
454 child_alloc.height = hy_size;
455 }
456 else
457 {
458 child_alloc.x = x0 + (single_horizontal ? 0 : row * hy_size);
459 child_alloc.y = y0 + total_length;
460 child_alloc.width = item_length;
461 child_alloc.height = hx_size;
462 }
463
464 gtk_widget_size_allocate (GTK_WIDGET (button), &child_alloc);
465 }
466
467 row = (row + 1) % nrows;
468
469 if (row == 0)
470 {
471 total_length += column_length;
472 column_length = 0;
473 }
474 }
475 }
476
477 total_length += column_length;
478
479 if (minimum_length != NULL)
480 *minimum_length = total_length;
481
482 if (natural_length != NULL)
483 *natural_length = total_length;
484
485 box->n_visible_children = n_visible_children;
486 if (box->n_hidden_children != n_hidden_children)
487 {
488 box->n_hidden_children = n_hidden_children;
489 g_object_notify(G_OBJECT(box), "has-hidden");
490 }
491 }
492
493
494
495 static void
sn_box_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)496 sn_box_get_preferred_width (GtkWidget *widget,
497 gint *minimum_width,
498 gint *natural_width)
499 {
500 SnBox *box = XFCE_SN_BOX (widget);
501 gint panel_size;
502
503 if (sn_config_get_panel_orientation (box->config) == GTK_ORIENTATION_HORIZONTAL)
504 {
505 sn_box_measure_and_allocate (widget, minimum_width, natural_width,
506 FALSE, 0, 0, TRUE);
507 }
508 else
509 {
510 panel_size = sn_config_get_panel_size (box->config);
511 if (minimum_width != NULL)
512 *minimum_width = panel_size;
513 if (natural_width != NULL)
514 *natural_width = panel_size;
515 }
516 }
517
518
519
520 static void
sn_box_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)521 sn_box_get_preferred_height (GtkWidget *widget,
522 gint *minimum_height,
523 gint *natural_height)
524 {
525 SnBox *box = XFCE_SN_BOX (widget);
526 gint panel_size;
527
528 if (sn_config_get_panel_orientation (box->config) == GTK_ORIENTATION_VERTICAL)
529 {
530 sn_box_measure_and_allocate (widget, minimum_height, natural_height,
531 FALSE, 0, 0, FALSE);
532 }
533 else
534 {
535 panel_size = sn_config_get_panel_size (box->config);
536 if (minimum_height != NULL)
537 *minimum_height = panel_size;
538 if (natural_height != NULL)
539 *natural_height = panel_size;
540 }
541 }
542
543
544
545 static void
sn_box_size_allocate(GtkWidget * widget,GtkAllocation * allocation)546 sn_box_size_allocate (GtkWidget *widget,
547 GtkAllocation *allocation)
548 {
549 SnBox *box = XFCE_SN_BOX (widget);
550
551 gtk_widget_set_allocation (widget, allocation);
552
553 sn_box_measure_and_allocate (widget, NULL, NULL,
554 TRUE, allocation->x, allocation->y,
555 sn_config_get_panel_orientation (box->config) ==
556 GTK_ORIENTATION_HORIZONTAL);
557 }
558
559
560
561 void
sn_box_remove_item(SnBox * box,SnItem * item)562 sn_box_remove_item (SnBox *box,
563 SnItem *item)
564 {
565 SnButton *button;
566 GList *known_items, *li, *li_int, *li_tmp;
567
568 g_return_if_fail (XFCE_IS_SN_BOX (box));
569
570 known_items = sn_config_get_known_items (box->config);
571 for (li = known_items; li != NULL; li = li->next)
572 {
573 li_int = g_hash_table_lookup (box->children, li->data);
574 for (li_tmp = li_int; li_tmp != NULL; li_tmp = li_tmp->next)
575 {
576 button = li_tmp->data;
577 if (sn_button_get_item (button) == item)
578 {
579 gtk_container_remove (GTK_CONTAINER (box), GTK_WIDGET (button));
580 return;
581 }
582 }
583 }
584 }
585
586 gboolean
sn_box_has_hidden_items(SnBox * box)587 sn_box_has_hidden_items (SnBox *box)
588 {
589 g_return_val_if_fail (XFCE_IS_SN_BOX (box), FALSE);
590 return box->n_hidden_children > 0;
591 }
592
593 void
sn_box_set_show_hidden(SnBox * box,gboolean show_hidden)594 sn_box_set_show_hidden (SnBox *box,
595 gboolean show_hidden)
596 {
597 g_return_if_fail (XFCE_IS_SN_BOX (box));
598
599 if (box->show_hidden != show_hidden)
600 {
601 box->show_hidden = show_hidden;
602
603 if (box->children != NULL)
604 gtk_widget_queue_resize (GTK_WIDGET (box));
605 }
606 }