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 }