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