1 /* Copyright (C) 2008 Igalia
2  * Copyright (C) 2012-2021 MATE Developers
3  *
4  * This file is part of MATE Utils.
5  *
6  * MATE Utils is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * MATE Utils is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
18  *
19  * Authors:
20  *   Fabio Marzocca  <thesaltydog@gmail.com>
21  *   Paolo Borelli <pborelli@katamail.com>
22  *   Miguel Gomez <magomez@igalia.com>
23  *   Eduardo Lima Mitev <elima@igalia.com>
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29 
30 #include <math.h>
31 #include <string.h>
32 #include <gtk/gtk.h>
33 
34 #include "baobab-chart.h"
35 #include "baobab-treemap.h"
36 
37 #define ITEM_TEXT_PADDING  3
38 #define ITEM_BORDER_WIDTH  1
39 #define ITEM_PADDING       6
40 
41 #define ITEM_MIN_WIDTH    3
42 #define ITEM_MIN_HEIGHT   3
43 
44 #define ITEM_SHOW_LABEL   TRUE
45 
46 struct _BaobabTreemapPrivate
47 {
48   guint max_visible_depth;
49   gboolean more_visible_childs;
50 };
51 
52 G_DEFINE_TYPE_WITH_PRIVATE (BaobabTreemap, baobab_treemap, BAOBAB_CHART_TYPE);
53 
54 static void baobab_treemap_draw_rectangle (GtkWidget *chart,
55                                            cairo_t *cr,
56                                            gdouble x, gdouble y, gdouble width, gdouble height,
57                                            BaobabChartColor fill_color,
58                                            const char *text,
59                                            gboolean show_text);
60 static void baobab_treemap_draw_item (GtkWidget *chart,
61                                       cairo_t *cr,
62                                       BaobabChartItem *item,
63                                       gboolean highlighted);
64 static void baobab_treemap_calculate_item_geometry (GtkWidget *chart,
65                                                     BaobabChartItem *item);
66 static gboolean baobab_treemap_is_point_over_item (GtkWidget *chart,
67                                                    BaobabChartItem *item,
68                                                    gdouble x,
69                                                    gdouble y);
70 static void baobab_treemap_get_item_rectangle (GtkWidget *chart,
71                                                BaobabChartItem *item);
72 guint baobab_treemap_can_zoom_in (GtkWidget *chart);
73 guint baobab_treemap_can_zoom_out (GtkWidget *chart);
74 
75 static void
baobab_treemap_class_init(BaobabTreemapClass * class)76 baobab_treemap_class_init (BaobabTreemapClass *class)
77 {
78   BaobabChartClass *chart_class;
79 
80   chart_class = BAOBAB_CHART_CLASS (class);
81 
82   /* BaobabChart abstract methods */
83   chart_class->draw_item = baobab_treemap_draw_item;
84   chart_class->calculate_item_geometry = baobab_treemap_calculate_item_geometry;
85   chart_class->is_point_over_item = baobab_treemap_is_point_over_item;
86   chart_class->get_item_rectangle = baobab_treemap_get_item_rectangle;
87   chart_class->can_zoom_in        = baobab_treemap_can_zoom_in;
88   chart_class->can_zoom_out       = baobab_treemap_can_zoom_out;
89 }
90 
91 static void
baobab_treemap_init(BaobabTreemap * chart)92 baobab_treemap_init (BaobabTreemap *chart)
93 {
94   BaobabTreemapPrivate *priv;
95 
96   priv = baobab_treemap_get_instance_private (chart);
97 
98   chart->priv = priv;
99 }
100 
101 static void
baobab_treemap_draw_rectangle(GtkWidget * chart,cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height,BaobabChartColor fill_color,const char * text,gboolean show_text)102 baobab_treemap_draw_rectangle (GtkWidget *chart,
103                                cairo_t *cr,
104                                gdouble x, gdouble y, gdouble width, gdouble height,
105                                BaobabChartColor fill_color,
106                                const char *text,
107                                gboolean show_text)
108 {
109   guint border = ITEM_BORDER_WIDTH;
110   PangoRectangle rect;
111   PangoLayout *layout;
112 
113   cairo_stroke (cr);
114 
115   cairo_set_line_width (cr, border);
116   cairo_rectangle (cr, x + border, y + border,
117                    width - border*2,
118                    height - border*2);
119   cairo_set_source_rgb (cr, fill_color.red, fill_color.green, fill_color.blue);
120   cairo_fill_preserve (cr);
121   cairo_set_source_rgb (cr, 0, 0, 0);
122   cairo_stroke (cr);
123 
124   if ((show_text) && (ITEM_SHOW_LABEL))
125     {
126       layout = gtk_widget_create_pango_layout (chart, NULL);
127       pango_layout_set_markup (layout, text, -1);
128       pango_layout_get_pixel_extents (layout, NULL, &rect);
129 
130       if ((rect.width + ITEM_TEXT_PADDING*2 <= width) &&
131           (rect.height + ITEM_TEXT_PADDING*2 <= height))
132         {
133           cairo_move_to (cr, x + width/2 - rect.width/2,
134                          y + height/2 - rect.height/2);
135           pango_cairo_show_layout (cr, layout);
136         }
137 
138       g_object_unref (layout);
139     }
140 }
141 
142 static void
baobab_treemap_draw_item(GtkWidget * chart,cairo_t * cr,BaobabChartItem * item,gboolean highlighted)143 baobab_treemap_draw_item (GtkWidget *chart,
144                           cairo_t *cr,
145                           BaobabChartItem *item,
146                           gboolean highlighted)
147 {
148   cairo_rectangle_t * rect;
149   BaobabChartColor fill_color;
150   GtkAllocation allocation;
151   gdouble width, height;
152 
153   rect = (cairo_rectangle_t *) item->data;
154 
155   gtk_widget_get_allocation (chart, &allocation);
156 
157   if (item->depth % 2 != 0)
158     {
159       baobab_chart_get_item_color (&fill_color, rect->x/allocation.width*200,
160                                    item->depth, highlighted);
161       width = rect->width - ITEM_PADDING;
162       height = rect->height;
163     }
164   else
165     {
166       baobab_chart_get_item_color (&fill_color, rect->y/allocation.height*200,
167                                    item->depth, highlighted);
168       width = rect->width;
169       height = rect->height - ITEM_PADDING;
170     }
171 
172   baobab_treemap_draw_rectangle (chart,
173                                  cr,
174                                  rect->x,
175                                  rect->y,
176                                  width,
177                                  height,
178                                  fill_color,
179                                  item->name,
180                                  (! item->has_visible_children) );
181 }
182 
183 static void
baobab_treemap_calculate_item_geometry(GtkWidget * chart,BaobabChartItem * item)184 baobab_treemap_calculate_item_geometry (GtkWidget *chart,
185                                         BaobabChartItem *item)
186 {
187   BaobabTreemapPrivate *priv;
188   cairo_rectangle_t p_area;
189   static cairo_rectangle_t *rect;
190   gdouble width, height;
191   BaobabChartItem *parent = NULL;
192   GtkAllocation allocation;
193 
194   priv = BAOBAB_TREEMAP (chart)->priv;
195 
196   if (item->depth == 0)
197     {
198       priv->max_visible_depth = 0;
199       priv->more_visible_childs = FALSE;
200     }
201 
202   item->visible = FALSE;
203 
204   if (item->parent == NULL)
205     {
206       gtk_widget_get_allocation (chart, &allocation);
207       p_area.x = 0 - ITEM_PADDING/2;
208       p_area.y = 0 - ITEM_PADDING/2;
209       p_area.width = allocation.width + ITEM_PADDING * 2;
210       p_area.height = allocation.height + ITEM_PADDING;
211     }
212   else
213     {
214       parent = (BaobabChartItem *) item->parent->data;
215       g_memmove (&p_area, parent->data, sizeof (cairo_rectangle_t));
216     }
217 
218   if (item->data == NULL)
219     item->data = g_new (cairo_rectangle_t, 1);
220 
221   rect = (cairo_rectangle_t *) item->data;
222 
223   if (item->depth % 2 != 0)
224     {
225       width = p_area.width - ITEM_PADDING;
226 
227       rect->x = p_area.x + (item->rel_start * width / 100) + ITEM_PADDING;
228       rect->y = p_area.y + ITEM_PADDING;
229       rect->width = width * item->rel_size / 100;
230       rect->height = p_area.height - ITEM_PADDING * 3;
231     }
232   else
233     {
234       height = p_area.height - ITEM_PADDING;
235 
236       rect->x = p_area.x + ITEM_PADDING;
237       rect->y = p_area.y + (item->rel_start * height / 100) + ITEM_PADDING;
238       rect->width = p_area.width - ITEM_PADDING * 3;
239       rect->height = height * item->rel_size / 100;
240     }
241 
242   if ((rect->width - ITEM_PADDING < ITEM_MIN_WIDTH) ||
243       (rect->height - ITEM_PADDING < ITEM_MIN_HEIGHT))
244     return;
245 
246   rect->x = floor (rect->x) + 0.5;
247   rect->y = floor (rect->y) + 0.5;
248   rect->width = floor (rect->width);
249   rect->height = floor (rect->height);
250 
251   item->visible = TRUE;
252 
253   if (parent != NULL)
254     parent->has_visible_children = TRUE;
255 
256   baobab_treemap_get_item_rectangle (chart, item);
257 
258   if (item->depth == baobab_chart_get_max_depth (chart) + 1)
259     priv->more_visible_childs = TRUE;
260   else
261     priv->max_visible_depth = MAX (priv->max_visible_depth, item->depth);
262 }
263 
264 static gboolean
baobab_treemap_is_point_over_item(GtkWidget * chart,BaobabChartItem * item,gdouble x,gdouble y)265 baobab_treemap_is_point_over_item (GtkWidget *chart,
266                                    BaobabChartItem *item,
267                                    gdouble x,
268                                    gdouble y)
269 {
270   GdkRectangle *rect;
271 
272   rect = &item->rect;
273   return ((x >= rect->x) && (x <= rect->x + rect->width) &&
274           (y >= rect->y) && (y <= rect->y + rect->height));
275 }
276 
277 static void
baobab_treemap_get_item_rectangle(GtkWidget * chart,BaobabChartItem * item)278 baobab_treemap_get_item_rectangle (GtkWidget *chart,
279                                    BaobabChartItem *item)
280 {
281   cairo_rectangle_t *_rect;
282 
283   _rect = (cairo_rectangle_t *) item->data;
284 
285   item->rect.x = (int) _rect->x;
286   item->rect.y = (int) _rect->y;
287   if (item->depth % 2 != 0)
288     {
289       item->rect.width = (int) _rect->width - ITEM_PADDING;
290       item->rect.height = (int) _rect->height;
291     }
292   else
293     {
294       item->rect.width = (int) _rect->width;
295       item->rect.height = (int) _rect->height - ITEM_PADDING;
296     }
297 
298 }
299 
300 guint
baobab_treemap_can_zoom_in(GtkWidget * chart)301 baobab_treemap_can_zoom_in (GtkWidget *chart)
302 {
303   BaobabTreemapPrivate *priv;
304 
305   priv = BAOBAB_TREEMAP (chart)->priv;
306 
307   return MAX (0, (gint) (priv->max_visible_depth - 1));
308 }
309 
310 guint
baobab_treemap_can_zoom_out(GtkWidget * chart)311 baobab_treemap_can_zoom_out (GtkWidget *chart)
312 {
313   BaobabTreemapPrivate *priv;
314 
315   priv = BAOBAB_TREEMAP (chart)->priv;
316 
317   return priv->more_visible_childs ? 1 : 0;
318 }
319 
320 /* Public functions start here */
321 
322 /**
323  * baobab_treemap_new:
324  *
325  * Constructor for the baobab_treemap class
326  *
327  * Returns: a new #BaobabTreemap object
328  *
329  **/
330 GtkWidget*
baobab_treemap_new(void)331 baobab_treemap_new (void)
332 {
333   return g_object_new (BAOBAB_TREEMAP_TYPE, NULL);
334 }
335