1 /*
2  * goc-group.c :
3  *
4  * Copyright (C) 2008-2009 Jean Brefort (jean.brefort@normalesup.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program 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 this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 
25 #include <glib/gi18n-lib.h>
26 #include <gsf/gsf-impl-utils.h>
27 
28 /**
29  * GocGroupClass:
30  * @base: base class.
31  **/
32 
33 static GocItemClass *parent_klass;
34 
35 struct GocGroupPriv {
36 	unsigned frozen;
37 	GPtrArray *children;
38 };
39 
40 enum {
41 	GROUP_PROP_0,
42 	GROUP_PROP_X,
43 	GROUP_PROP_Y,
44 };
45 /**
46  * SECTION:goc-group
47  * @short_description: Group item
48  *
49  * A #GocGroup is a #GocItem which just contains other items.
50  *
51  * The contents
52  * of the canvas are stored as a tree where #GocGroup items are branches and
53  * other items are leafs.
54  **/
55 static void
goc_group_set_property(GObject * gobject,guint param_id,GValue const * value,GParamSpec * pspec)56 goc_group_set_property (GObject *gobject, guint param_id,
57 			GValue const *value, GParamSpec *pspec)
58 {
59 	GocGroup *group = GOC_GROUP (gobject);
60 
61 	switch (param_id) {
62 	case GROUP_PROP_X:
63 		group->x = g_value_get_double (value);
64 		break;
65 
66 	case GROUP_PROP_Y:
67 		group->y = g_value_get_double (value);
68 		break;
69 
70 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
71 		return; /* NOTE : RETURN */
72 	}
73 	goc_item_bounds_changed (GOC_ITEM (gobject));
74 }
75 
76 static void
goc_group_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)77 goc_group_get_property (GObject *gobject, guint param_id,
78 			GValue *value, GParamSpec *pspec)
79 {
80 	GocGroup *group = GOC_GROUP (gobject);
81 
82 	switch (param_id) {
83 	case GROUP_PROP_X:
84 		g_value_set_double (value, group->x);
85 		break;
86 
87 	case GROUP_PROP_Y:
88 		g_value_set_double (value, group->y);
89 		break;
90 
91 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
92 		return; /* NOTE : RETURN */
93 	}
94 }
95 
96 static void
goc_group_update_bounds(GocItem * item)97 goc_group_update_bounds (GocItem *item)
98 {
99 	GocGroup *group = GOC_GROUP (item);
100 	GPtrArray *children = group->priv->children;
101 	double x0, y0, x1, y1;
102 	if (group->priv->frozen)
103 		return;
104 	item->x0 = item->y0 = G_MAXDOUBLE;
105 	item->x1 = item->y1 = -G_MAXDOUBLE;
106 	if (children->len > 0) {
107 		unsigned ui;
108 		for (ui = 0; ui < children->len; ui++) {
109 			GocItem *child = g_ptr_array_index (children, ui);
110 			goc_item_get_bounds (child, &x0, &y0, &x1, &y1);
111 			if (x0 < item->x0)
112 				item->x0 = x0;
113 			if (y0 < item->y0)
114 				item->y0 = y0;
115 			if (x1 > item->x1)
116 				item->x1 = x1;
117 			if (y1 > item->y1)
118 				item->y1 = y1;
119 		}
120 		item->x0 += group->x;
121 		item->y0 += group->y;
122 		item->x1 += group->x;
123 		item->y1 += group->y;
124 	}
125 	if (group->clip_path) {
126 	}
127 }
128 
129 static gboolean
goc_group_draw_region(GocItem const * item,cairo_t * cr,double x0,double y0,double x1,double y1)130 goc_group_draw_region (GocItem const *item, cairo_t *cr,
131 		      double x0, double y0,
132 		      double x1, double y1)
133 {
134 	GocGroup *group = GOC_GROUP (item);
135 	GPtrArray *children = group->priv->children;
136 	unsigned ui;
137 	if (children->len == 0)
138 		return TRUE;
139 
140 	cairo_save (cr);
141 	if (group->clip_path) {
142 		cairo_translate (cr, group->x , group->y);
143 		cairo_set_fill_rule (cr, group->clip_rule);
144 		go_path_to_cairo (group->clip_path, GO_PATH_DIRECTION_FORWARD, cr);
145 		cairo_clip (cr);
146 	}
147 	x0 -= group->x;
148 	y0 -= group->y;
149 	x1 -= group->x;
150 	y1 -= group->y;
151 	for (ui = 0; ui < children->len; ui++) {
152 		double x, y, x_, y_;
153 		GocItem *item = g_ptr_array_index (children, ui);
154 		if (!goc_item_is_visible (item))
155 			continue;
156 
157 		goc_item_get_bounds (item, &x, &y, &x_, &y_);
158 		if (x <= x1 && x_ >= x0 && y <= y1 && y_ >= y0) {
159 			if (!goc_item_draw_region (item, cr, x0, y0, x1, y1))
160 				goc_item_draw (item, cr);
161 		}
162 	}
163 	cairo_restore (cr);
164 	return TRUE;
165 }
166 
167 /* we just need the distance method to know if an event occured on an item
168  so we don't need to know exact distances when they are large enough, to avoid
169  recalculate a lot of complex distnaces and to optimize, everything more than
170  some thershold (in pixels) will be considered at infinite */
171 
172 #define GOC_THRESHOLD   10 /* 10 pixels should be enough */
173 
174 static double
goc_group_distance(GocItem * item,double x,double y,GocItem ** near_item)175 goc_group_distance (GocItem *item, double x, double y, GocItem **near_item)
176 {
177 	GocGroup *group = GOC_GROUP (item);
178 	GPtrArray *children = group->priv->children;
179 	double result = G_MAXDOUBLE, dist;
180 	unsigned ui;
181 	GocItem *cur_item;
182 	double th = GOC_THRESHOLD / item->canvas->pixels_per_unit;
183 	x -= group->x;
184 	y -= group->y;
185 	for (ui = children->len; ui-- > 0; ) {
186 		GocItem *it = g_ptr_array_index (children, ui);
187 		if (!it->visible || it->x0 > x + th || it->x1 < x - th
188 		    || it->y0 > y + th || it->y1 < y - th)
189 			continue;
190 		dist = goc_item_distance (it, x, y, &cur_item);
191 		if (dist < result) {
192 			*near_item = cur_item;
193 			result = dist;
194 		}
195 		if (result == 0.)
196 			break;
197 	}
198 	/* check if the click is not outside the clipping region */
199 	if (group->clip_path) {
200 	}
201 	return result;
202 }
203 
204 static void
goc_group_realize(GocItem * item)205 goc_group_realize (GocItem *item)
206 {
207 	GocGroup *group = GOC_GROUP (item);
208 	GPtrArray *children = group->priv->children;
209 	unsigned ui;
210 
211 	for (ui = 0; ui < children->len; ui++) {
212 		GocItem *child = g_ptr_array_index (children, ui);
213 		_goc_item_realize (child);
214 	}
215 
216 	parent_klass->realize (item);
217 }
218 
219 static void
goc_group_unrealize(GocItem * item)220 goc_group_unrealize (GocItem *item)
221 {
222 	GocGroup *group = GOC_GROUP (item);
223 	GPtrArray *children = group->priv->children;
224 	unsigned ui;
225 
226 	parent_klass->unrealize (item);
227 
228 	for (ui = 0; ui < children->len; ui++) {
229 		GocItem *child = g_ptr_array_index (children, ui);
230 		_goc_item_unrealize (child);
231 	}
232 }
233 
234 static void
goc_group_notify_scrolled(GocItem * item)235 goc_group_notify_scrolled (GocItem *item)
236 {
237 	GocGroup *group = GOC_GROUP (item);
238 	GPtrArray *children = group->priv->children;
239 	unsigned ui;
240 
241 	for (ui = 0; ui < children->len; ui++) {
242 		GocItem *child = g_ptr_array_index (children, ui);
243 		GocItemClass *klass = GOC_ITEM_GET_CLASS (child);
244 		if (klass->notify_scrolled)
245 			klass->notify_scrolled (child);
246 	}
247 }
248 
249 static void
goc_group_dispose(GObject * obj)250 goc_group_dispose (GObject *obj)
251 {
252 	GocGroup *group = GOC_GROUP (obj);
253 	goc_group_clear (group);
254 	((GObjectClass*)parent_klass)->dispose (obj);
255 }
256 
257 static void
goc_group_finalize(GObject * obj)258 goc_group_finalize (GObject *obj)
259 {
260 	GocGroup *group = GOC_GROUP (obj);
261 	g_ptr_array_free (group->priv->children, TRUE);
262 	g_free (group->priv);
263 	((GObjectClass*)parent_klass)->finalize (obj);
264 }
265 
266 static void
goc_group_copy(GocItem * dest,GocItem * source)267 goc_group_copy (GocItem *dest, GocItem *source)
268 {
269 	GocGroup *src = GOC_GROUP (source), *dst = GOC_GROUP (dest);
270 	unsigned ui;
271 
272 	dst->x = src->x;
273 	dst->y = src->y;
274 	dst->clip_path = go_path_copy (src->clip_path);
275 	dst->clip_rule = src->clip_rule;
276 	goc_group_freeze (dst, TRUE);
277 	for (ui = 0; ui < src->priv->children->len; ui++)
278 		goc_item_duplicate (g_ptr_array_index (src->priv->children, ui), dst);
279 	goc_group_freeze (dst, FALSE);
280 }
281 
282 static void
goc_group_class_init(GocItemClass * item_klass)283 goc_group_class_init (GocItemClass *item_klass)
284 {
285 	GObjectClass *obj_klass = (GObjectClass*) item_klass;
286 	parent_klass = g_type_class_peek_parent (item_klass);
287 
288 	obj_klass->get_property = goc_group_get_property;
289 	obj_klass->set_property = goc_group_set_property;
290 	obj_klass->dispose = goc_group_dispose;
291 	obj_klass->finalize = goc_group_finalize;
292 	g_object_class_install_property (obj_klass, GROUP_PROP_X,
293 		g_param_spec_double ("x",
294 			_("x"),
295 			_("The group horizontal offset"),
296 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
297 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
298 	g_object_class_install_property (obj_klass, GROUP_PROP_Y,
299 		g_param_spec_double ("y",
300 			_("y"),
301 			_("The group vertical offset"),
302 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
303 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
304 
305 	item_klass->draw_region = goc_group_draw_region;
306 	item_klass->update_bounds = goc_group_update_bounds;
307 	item_klass->distance = goc_group_distance;
308 	item_klass->realize = goc_group_realize;
309 	item_klass->unrealize = goc_group_unrealize;
310 	item_klass->notify_scrolled = goc_group_notify_scrolled;
311 	item_klass->copy = goc_group_copy;
312 }
313 
314 static void
goc_group_init(GocGroup * group)315 goc_group_init (GocGroup *group)
316 {
317 	group->priv = g_malloc0 (sizeof (struct GocGroupPriv));
318 	group->priv->children = g_ptr_array_new ();
319 }
320 
GSF_CLASS(GocGroup,goc_group,goc_group_class_init,goc_group_init,GOC_TYPE_ITEM)321 GSF_CLASS (GocGroup, goc_group,
322 	   goc_group_class_init, goc_group_init,
323 	   GOC_TYPE_ITEM)
324 
325 /**
326  * goc_group_new:
327  * @parent: #GocGroup
328  *
329  * Creates a new #GocGroup as a child of @parent.
330  * Returns: (transfer none): the newly created #GocGroup.
331  **/
332 GocGroup*
333 goc_group_new (GocGroup *parent)
334 {
335 	GocGroup *group;
336 
337 	g_return_val_if_fail (GOC_IS_GROUP (parent), NULL);
338 
339 	group = GOC_GROUP (g_object_new (GOC_TYPE_GROUP, NULL));
340 	g_return_val_if_fail (group != NULL, NULL);
341 
342 	goc_group_add_child (parent, GOC_ITEM (group));
343 
344 	return group;
345 }
346 
347 /**
348  * goc_group_clear:
349  * @group: #GocGroup
350  *
351  * Destroys all @group children.
352  **/
353 void
goc_group_clear(GocGroup * group)354 goc_group_clear (GocGroup *group)
355 {
356 	GPtrArray *children;
357 
358 	g_return_if_fail (GOC_IS_GROUP (group));
359 
360 	goc_group_freeze (group, TRUE);
361 
362 	children = group->priv->children;
363 	while (children->len > 0) {
364 		unsigned len = children->len;
365 		GocItem *child = g_ptr_array_index (children, len - 1);
366 
367 		goc_item_destroy (child);
368 
369 		if (children->len >= len) {
370 			/* The most likely trigger of this is a dispose
371 			   method that doesn't chain up to the parent
372 			   class' dispose.  */
373 			g_warning ("Trouble clearing child %p from group %p\n",
374 				   child,
375 				   group);
376 			// Brutal:
377 			g_ptr_array_set_size (children, len - 1);
378 		}
379 	}
380 
381 	goc_group_freeze (group, TRUE);
382 }
383 
384 // Provide just enough of the old ->children fields that it can be used
385 // to test for empty and inspection of only child.
386 static void
goc_group_fake_xchildren(GocGroup * group)387 goc_group_fake_xchildren (GocGroup *group)
388 {
389 	g_list_free (group->Xchildren);
390 	group->Xchildren = group->priv->children
391 		? g_list_prepend (NULL, goc_group_get_child (group, 0))
392 		: NULL;
393 }
394 
395 
396 /**
397  * goc_group_add_child:
398  * @parent: #GocGroup
399  * @item: #GocItem
400  *
401  * Adds @item as a new child to @parent.
402  **/
403 void
goc_group_add_child(GocGroup * parent,GocItem * item)404 goc_group_add_child (GocGroup *parent, GocItem *item)
405 {
406 	GocCanvas *old_canvas;
407 
408 	g_return_if_fail (GOC_IS_GROUP (parent));
409 	g_return_if_fail (GOC_IS_ITEM (item));
410 
411 	if (item->parent == parent)
412 		return;
413 
414 	/* Remove from current group.  */
415 	if (item->parent != NULL)
416 		goc_group_remove_child (item->parent, item);
417 
418 	old_canvas = item->canvas;
419 
420 	/* Insert into new group.  */
421 	g_ptr_array_add (parent->priv->children, item);
422 	item->parent = parent;
423 	item->canvas = parent->base.canvas;
424 
425 	goc_group_fake_xchildren (parent);
426 
427 	/* Notify of changes.  */
428 	if (old_canvas && item->canvas != old_canvas)
429 		_goc_canvas_remove_item (old_canvas, item);
430 	g_object_notify (G_OBJECT (item), "parent");
431 	if (item->canvas != old_canvas)
432 		g_object_notify (G_OBJECT (item), "canvas");
433 
434 	if (GOC_ITEM (parent)->realized)
435 		_goc_item_realize (item);
436 	goc_item_bounds_changed (GOC_ITEM (parent));
437 }
438 
439 /**
440  * goc_group_remove_child:
441  * @parent: #GocGroup
442  * @item: #GocItem
443  *
444  * Removes @item from @parent. This function will fail if @item is not a
445  * child of @parent.
446  **/
447 void
goc_group_remove_child(GocGroup * parent,GocItem * item)448 goc_group_remove_child (GocGroup *parent, GocItem *item)
449 {
450 	int n;
451 
452 	g_return_if_fail (GOC_IS_GROUP (parent));
453 	g_return_if_fail (GOC_IS_ITEM (item));
454 	g_return_if_fail (item->parent == parent);
455 
456 	if (item->canvas)
457 		_goc_canvas_remove_item (item->canvas, item);
458 	if (GOC_ITEM (parent)->realized)
459 		_goc_item_unrealize (item);
460 	n = goc_group_find_child (parent, item);
461 	g_ptr_array_remove_index (parent->priv->children, n);
462 	item->parent = NULL;
463 	item->canvas = NULL;
464 	goc_group_fake_xchildren (parent);
465 	g_object_notify (G_OBJECT (item), "parent");
466 	g_object_notify (G_OBJECT (item), "canvas");
467 	goc_item_bounds_changed (GOC_ITEM (parent));
468 }
469 
470 /**
471  * goc_group_get_children:
472  * @group: #GocGroup
473  *
474  * Returns: (transfer container) (element-type GocItem): An array of
475  * the items in @group.
476  **/
477 GPtrArray *
goc_group_get_children(GocGroup * group)478 goc_group_get_children (GocGroup *group)
479 {
480 	g_return_val_if_fail (GOC_IS_GROUP (group), NULL);
481 
482 	g_ptr_array_ref (group->priv->children);
483 	return group->priv->children;
484 }
485 
486 /**
487  * goc_group_get_child:
488  * @group: #GocGroup
489  * @n: number
490  *
491  * Returns: (transfer none) (nullable): The @n'th item, zero-bases, in the
492  * group or %NULL if @n is too big.
493  **/
494 GocItem *
goc_group_get_child(GocGroup * group,unsigned n)495 goc_group_get_child (GocGroup *group, unsigned n)
496 {
497 	g_return_val_if_fail (GOC_IS_GROUP (group), NULL);
498 
499 	return n >= group->priv->children->len
500 		? NULL
501 		: g_ptr_array_index (group->priv->children, n);
502 }
503 
504 
505 /**
506  * goc_group_find_child:
507  * @group: #GocGroup
508  * @item: #GocItem
509  *
510  * Returns: The index of @item in @group, or -1 if @item is not in @group.
511  **/
512 int
goc_group_find_child(GocGroup * group,GocItem * item)513 goc_group_find_child (GocGroup *group, GocItem *item)
514 {
515 	unsigned ui;
516 	GPtrArray *children = group->priv->children;
517 
518 	if (item->parent != group)
519 		return -1;
520 
521 	if (children->len > 1 &&
522 	    item == g_ptr_array_index (children, children->len - 1)) {
523 		// Very common case for large groups
524 		return children->len - 1;
525 	}
526 
527 	for (ui = 0; ui < children->len; ui++) {
528 		if (item == g_ptr_array_index (children, ui))
529 			return ui;
530 	}
531 
532 	g_warning ("Item not in group?");
533 	return -1;
534 }
535 
536 
537 /**
538  * goc_group_adjust_bounds:
539  * @group: #GocGroup
540  * @x0: first horizontal coordinate
541  * @y0: first vertical coordinate
542  * @x1: last horizontal coordinate
543  * @y1: last vertical coordinate
544  *
545  * Adds @group horizontal offset to @x0 and @x1, and vertical offset to @y0
546  * and @y1. This function is called recursively so that when returning @x0,
547  * @y0, @x1, and @y1 are absolute coordinates in canvas space,
548  **/
549 void
goc_group_adjust_bounds(GocGroup const * group,double * x0,double * y0,double * x1,double * y1)550 goc_group_adjust_bounds (GocGroup const *group, double *x0, double *y0, double *x1, double *y1)
551 {
552 	GocGroup *parent;
553 	g_return_if_fail (GOC_IS_GROUP (group));
554 	*x0 += group->x;
555 	*y0 += group->y;
556 	*x1 += group->x;
557 	*y1 += group->y;
558 	parent = GOC_ITEM (group)->parent;
559 	if (parent)
560 		goc_group_adjust_bounds (parent, x0, y0, x1, y1);
561 }
562 
563 /**
564  * goc_group_adjust_coords:
565  * @group: #GocGroup
566  * @x: horizontal coordinate
567  * @y: vertical coordinate
568  *
569  * Adds @group horizontal offset to @x0, and vertical offset to @y0.
570  * This function is called recursively so that when returning @x0 and
571  * @y0 are absolute coordinates in canvas space,
572  **/
573 void
goc_group_adjust_coords(GocGroup const * group,double * x,double * y)574 goc_group_adjust_coords (GocGroup const *group, double *x, double *y)
575 {
576 	GocGroup *parent;
577 	g_return_if_fail (GOC_IS_GROUP (group));
578 	*x += group->x;
579 	*y += group->y;
580 	parent = GOC_ITEM (group)->parent;
581 	if (parent)
582 		goc_group_adjust_coords (parent, x, y);
583 }
584 
585 /**
586  * goc_group_cairo_transform: (skip)
587  * @group: #GocGroup
588  * @cr: #cairo_t
589  * @x: horizontal coordinate
590  * @y: vertical coordinate
591  *
592  * Translates @cr current context so that operations start at (@x,@y), which
593  * are @group relative coordinates, and is scaled according to the containing
594  * #GocCanvas current scale (see goc_canvas_get_pixels_per_unit()). The
595  * translation takes all @group ancestors into account.
596  *
597  * This function does not call cairo_save().
598  **/
599 void
goc_group_cairo_transform(GocGroup const * group,cairo_t * cr,double x,double y)600 goc_group_cairo_transform (GocGroup const *group, cairo_t *cr, double x, double y)
601 {
602 	GocGroup *parent;
603 
604 	g_return_if_fail (GOC_IS_GROUP (group));
605 	parent = GOC_ITEM (group)->parent;
606 	if (parent)
607 		goc_group_cairo_transform (parent, cr, x + group->x, y + group->y);
608 	else {
609 		GocCanvas *canvas = GOC_ITEM (group)->canvas;
610 		if (canvas) {
611 			if (canvas->direction == GOC_DIRECTION_RTL)
612 				cairo_translate (cr, canvas->width / canvas->pixels_per_unit - (x - canvas->scroll_x1), y - canvas->scroll_y1);
613 			else
614 				cairo_translate (cr, x - canvas->scroll_x1, y - canvas->scroll_y1);
615 		} else
616 			cairo_translate (cr, x, y);
617 	}
618 }
619 
620 /**
621  * goc_group_set_clip_path: (skip)
622  * @group: #GocGroup
623  * @clip_path: #GOPath
624  * @clip_rule: #cairo_fill_rule_t
625  *
626  * Clips the drawing inside @path.
627  */
628 void
goc_group_set_clip_path(GocGroup * group,GOPath * clip_path,cairo_fill_rule_t clip_rule)629 goc_group_set_clip_path (GocGroup *group, GOPath *clip_path, cairo_fill_rule_t clip_rule)
630 {
631 	g_return_if_fail (GOC_IS_GROUP (group));
632 	group->clip_path = clip_path;
633 	group->clip_rule = clip_rule;
634 	goc_item_bounds_changed (GOC_ITEM (group));
635 }
636 
637 void
goc_group_freeze(GocGroup * group,gboolean freeze)638 goc_group_freeze (GocGroup *group, gboolean freeze)
639 {
640 	g_return_if_fail (GOC_IS_GROUP (group));
641 
642 	if (freeze) {
643 		group->priv->frozen++;
644 	} else {
645 		group->priv->frozen--;
646 		if (!group->priv->frozen)
647 			goc_group_update_bounds ((GocItem*) group);
648 	}
649 }
650