1 /* This file is part of Ganv.
2 * Copyright 2007-2015 David Robillard <http://drobilla.net>
3 *
4 * Ganv is free software: you can redistribute it and/or modify it under the
5 * terms of the GNU General Public License as published by the Free Software
6 * Foundation, either version 3 of the License, or any later version.
7 *
8 * Ganv is distributed in the hope that it will be useful, but WITHOUT ANY
9 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
11 *
12 * You should have received a copy of the GNU General Public License along
13 * with Ganv. If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 /* Based on GnomeCanvasGroup, by Federico Mena <federico@nuclecu.unam.mx>
17 * and Raph Levien <raph@gimp.org>
18 * Copyright 1997-2000 Free Software Foundation
19 */
20
21 #include "ganv-private.h"
22
23 #include "ganv/group.h"
24 #include "ganv/item.h"
25
26 #include <cairo.h>
27 #include <float.h>
28 #include <glib-object.h>
29 #include <glib.h>
30 #include <gtk/gtk.h>
31
32 #include <math.h>
33 #include <stddef.h>
34
35 enum {
36 GROUP_PROP_0
37 };
38
39 G_DEFINE_TYPE_WITH_CODE(GanvGroup, ganv_group, GANV_TYPE_ITEM,
40 G_ADD_PRIVATE(GanvGroup))
41
42 static GanvItemClass* group_parent_class;
43
44 static void
ganv_group_init(GanvGroup * group)45 ganv_group_init(GanvGroup* group)
46 {
47 GanvGroupPrivate* impl =
48 (GanvGroupPrivate*)ganv_group_get_instance_private(group);
49
50 group->impl = impl;
51 group->impl->item_list = NULL;
52 group->impl->item_list_end = NULL;
53 }
54
55 static void
ganv_group_set_property(GObject * gobject,guint param_id,const GValue * value,GParamSpec * pspec)56 ganv_group_set_property(GObject* gobject, guint param_id,
57 const GValue* value, GParamSpec* pspec)
58 {
59 (void)value;
60
61 g_return_if_fail(GANV_IS_GROUP(gobject));
62
63 switch (param_id) {
64 default:
65 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, param_id, pspec);
66 break;
67 }
68 }
69
70 static void
ganv_group_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)71 ganv_group_get_property(GObject* gobject, guint param_id,
72 GValue* value, GParamSpec* pspec)
73 {
74 (void)value;
75
76 g_return_if_fail(GANV_IS_GROUP(gobject));
77
78 switch (param_id) {
79 default:
80 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, param_id, pspec);
81 break;
82 }
83 }
84
85 static void
ganv_group_destroy(GtkObject * object)86 ganv_group_destroy(GtkObject* object)
87 {
88 GanvGroup* group = NULL;
89
90 g_return_if_fail(GANV_IS_GROUP(object));
91
92 group = GANV_GROUP(object);
93
94 while (group->impl->item_list) {
95 // child is unref'ed by the child's group_remove().
96 gtk_object_destroy(GTK_OBJECT(group->impl->item_list->data));
97 }
98 if (GTK_OBJECT_CLASS(group_parent_class)->destroy) {
99 (*GTK_OBJECT_CLASS(group_parent_class)->destroy)(object);
100 }
101 }
102
103 static void
ganv_group_update(GanvItem * item,int flags)104 ganv_group_update(GanvItem* item, int flags)
105 {
106 GanvGroup* group = GANV_GROUP(item);
107
108 double min_x = 0.0;
109 double min_y = 0.0;
110 double max_x = 0.0;
111 double max_y = 0.0;
112
113 for (GList* list = group->impl->item_list; list; list = list->next) {
114 GanvItem* i = (GanvItem*)list->data;
115
116 ganv_item_invoke_update(i, flags);
117
118 min_x = fmin(min_x, fmin(i->impl->x1, i->impl->x2));
119 min_y = fmin(min_y, fmin(i->impl->y1, i->impl->y2));
120 max_x = fmax(max_x, fmax(i->impl->x1, i->impl->x2));
121 max_y = fmax(max_y, fmax(i->impl->y2, i->impl->y2));
122 }
123 item->impl->x1 = min_x;
124 item->impl->y1 = min_y;
125 item->impl->x2 = max_x;
126 item->impl->y2 = max_y;
127
128 (*group_parent_class->update)(item, flags);
129 }
130
131 static void
ganv_group_realize(GanvItem * item)132 ganv_group_realize(GanvItem* item)
133 {
134 GanvGroup* group = NULL;
135 GList* list = NULL;
136 GanvItem* i = NULL;
137
138 group = GANV_GROUP(item);
139
140 for (list = group->impl->item_list; list; list = list->next) {
141 i = (GanvItem*)list->data;
142
143 if (!(i->object.flags & GANV_ITEM_REALIZED)) {
144 (*GANV_ITEM_GET_CLASS(i)->realize)(i);
145 }
146 }
147
148 (*group_parent_class->realize)(item);
149 }
150
151 static void
ganv_group_unrealize(GanvItem * item)152 ganv_group_unrealize(GanvItem* item)
153 {
154 GanvGroup* group = NULL;
155 GList* list = NULL;
156 GanvItem* i = NULL;
157
158 group = GANV_GROUP(item);
159
160 for (list = group->impl->item_list; list; list = list->next) {
161 i = (GanvItem*)list->data;
162
163 if (i->object.flags & GANV_ITEM_REALIZED) {
164 (*GANV_ITEM_GET_CLASS(i)->unrealize)(i);
165 }
166 }
167
168 (*group_parent_class->unrealize)(item);
169 }
170
171 static void
ganv_group_map(GanvItem * item)172 ganv_group_map(GanvItem* item)
173 {
174 GanvGroup* group = NULL;
175 GList* list = NULL;
176 GanvItem* i = NULL;
177
178 group = GANV_GROUP(item);
179
180 for (list = group->impl->item_list; list; list = list->next) {
181 i = (GanvItem*)list->data;
182
183 if (!(i->object.flags & GANV_ITEM_MAPPED)) {
184 (*GANV_ITEM_GET_CLASS(i)->map)(i);
185 }
186 }
187
188 (*group_parent_class->map)(item);
189 }
190
191 static void
ganv_group_unmap(GanvItem * item)192 ganv_group_unmap(GanvItem* item)
193 {
194 GanvGroup* group = NULL;
195 GList* list = NULL;
196 GanvItem* i = NULL;
197
198 group = GANV_GROUP(item);
199
200 for (list = group->impl->item_list; list; list = list->next) {
201 i = (GanvItem*)list->data;
202
203 if (i->object.flags & GANV_ITEM_MAPPED) {
204 (*GANV_ITEM_GET_CLASS(i)->unmap)(i);
205 }
206 }
207
208 (*group_parent_class->unmap)(item);
209 }
210
211 static void
ganv_group_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)212 ganv_group_draw(GanvItem* item,
213 cairo_t* cr, double cx, double cy, double cw, double ch)
214 {
215 GanvGroup* group = GANV_GROUP(item);
216
217 // TODO: Layered drawing
218
219 for (GList* list = group->impl->item_list; list; list = list->next) {
220 GanvItem* child = (GanvItem*)list->data;
221
222 if (((child->object.flags & GANV_ITEM_VISIBLE)
223 && ((child->impl->x1 < (cx + cw))
224 && (child->impl->y1 < (cy + ch))
225 && (child->impl->x2 > cx)
226 && (child->impl->y2 > cy)))) {
227 if (GANV_ITEM_GET_CLASS(child)->draw) {
228 (*GANV_ITEM_GET_CLASS(child)->draw)(
229 child, cr, cx, cy, cw, ch);
230 }
231 }
232 }
233 }
234
235 static double
ganv_group_point(GanvItem * item,double x,double y,GanvItem ** actual_item)236 ganv_group_point(GanvItem* item, double x, double y, GanvItem** actual_item)
237 {
238 GanvGroup* group = GANV_GROUP(item);
239
240 const double x1 = x - GANV_CLOSE_ENOUGH;
241 const double y1 = y - GANV_CLOSE_ENOUGH;
242 const double x2 = x + GANV_CLOSE_ENOUGH;
243 const double y2 = y + GANV_CLOSE_ENOUGH;
244
245 double dist = 0.0;
246 double best = 0.0;
247
248 *actual_item = NULL;
249
250 for (GList* list = group->impl->item_list; list; list = list->next) {
251 GanvItem* child = (GanvItem*)list->data;
252 if ((child->impl->x1 > x2) || (child->impl->y1 > y2) || (child->impl->x2 < x1) || (child->impl->y2 < y1)) {
253 continue;
254 }
255
256 GanvItem* point_item = NULL;
257
258 int has_point = FALSE;
259 if ((child->object.flags & GANV_ITEM_VISIBLE)
260 && GANV_ITEM_GET_CLASS(child)->point) {
261 dist = GANV_ITEM_GET_CLASS(child)->point(
262 child,
263 x - child->impl->x, y - child->impl->y,
264 &point_item);
265 has_point = TRUE;
266 }
267
268 if (has_point
269 && point_item
270 && ((int)(dist + 0.5) <= GANV_CLOSE_ENOUGH)) {
271 best = dist;
272 *actual_item = point_item;
273 }
274 }
275
276 if (*actual_item) {
277 return best;
278 } else {
279 *actual_item = item;
280 return 0.0;
281 }
282 }
283
284 /* Get bounds of child item in group-relative coordinates. */
285 static void
get_child_bounds(GanvItem * child,double * x1,double * y1,double * x2,double * y2)286 get_child_bounds(GanvItem* child, double* x1, double* y1, double* x2, double* y2)
287 {
288 ganv_item_get_bounds(child, x1, y1, x2, y2);
289
290 // Make bounds relative to the item's parent coordinate system
291 *x1 -= child->impl->x;
292 *y1 -= child->impl->y;
293 *x2 -= child->impl->x;
294 *y2 -= child->impl->y;
295 }
296
297 static void
ganv_group_bounds(GanvItem * item,double * x1,double * y1,double * x2,double * y2)298 ganv_group_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2)
299 {
300 GanvGroup* group = GANV_GROUP(item);
301 GanvItem* child = NULL;
302 GList* list = NULL;
303 double tx1 = 0.0;
304 double ty1 = 0.0;
305 double tx2 = 0.0;
306 double ty2 = 0.0;
307 double minx = DBL_MAX;
308 double miny = DBL_MAX;
309 double maxx = DBL_MIN;
310 double maxy = DBL_MIN;
311 int set = FALSE;
312
313 /* Get the bounds of the first visible item */
314
315 for (list = group->impl->item_list; list; list = list->next) {
316 child = (GanvItem*)list->data;
317
318 if (child->object.flags & GANV_ITEM_VISIBLE) {
319 set = TRUE;
320 get_child_bounds(child, &minx, &miny, &maxx, &maxy);
321 break;
322 }
323 }
324
325 /* If there were no visible items, return an empty bounding box */
326
327 if (!set) {
328 *x1 = *y1 = *x2 = *y2 = 0.0;
329 return;
330 }
331
332 /* Now we can grow the bounds using the rest of the items */
333
334 list = list->next;
335
336 for (; list; list = list->next) {
337 child = (GanvItem*)list->data;
338
339 if (!(child->object.flags & GANV_ITEM_VISIBLE)) {
340 continue;
341 }
342
343 get_child_bounds(child, &tx1, &ty1, &tx2, &ty2);
344
345 if (tx1 < minx) {
346 minx = tx1;
347 }
348
349 if (ty1 < miny) {
350 miny = ty1;
351 }
352
353 if (tx2 > maxx) {
354 maxx = tx2;
355 }
356
357 if (ty2 > maxy) {
358 maxy = ty2;
359 }
360 }
361
362 *x1 = minx;
363 *y1 = miny;
364 *x2 = maxx;
365 *y2 = maxy;
366 }
367
368 static void
ganv_group_add(GanvItem * parent,GanvItem * item)369 ganv_group_add(GanvItem* parent, GanvItem* item)
370 {
371 GanvGroup* group = GANV_GROUP(parent);
372 g_object_ref_sink(G_OBJECT(item));
373
374 if (!group->impl->item_list) {
375 group->impl->item_list = g_list_append(group->impl->item_list, item);
376 group->impl->item_list_end = group->impl->item_list;
377 } else {
378 group->impl->item_list_end = g_list_append(group->impl->item_list_end, item)->next;
379 }
380
381 if (group->item.object.flags & GANV_ITEM_REALIZED) {
382 (*GANV_ITEM_GET_CLASS(item)->realize)(item);
383 }
384
385 if (group->item.object.flags & GANV_ITEM_MAPPED) {
386 (*GANV_ITEM_GET_CLASS(item)->map)(item);
387 }
388
389 g_object_notify(G_OBJECT(item), "parent");
390 }
391
392 static void
ganv_group_remove(GanvItem * parent,GanvItem * item)393 ganv_group_remove(GanvItem* parent, GanvItem* item)
394 {
395 GanvGroup* group = GANV_GROUP(parent);
396 GList* children = NULL;
397
398 g_return_if_fail(GANV_IS_GROUP(group));
399 g_return_if_fail(GANV_IS_ITEM(item));
400
401 for (children = group->impl->item_list; children; children = children->next) {
402 if (children->data == item) {
403 if (item->object.flags & GANV_ITEM_MAPPED) {
404 (*GANV_ITEM_GET_CLASS(item)->unmap)(item);
405 }
406
407 if (item->object.flags & GANV_ITEM_REALIZED) {
408 (*GANV_ITEM_GET_CLASS(item)->unrealize)(item);
409 }
410
411 /* Unparent the child */
412
413 item->impl->parent = NULL;
414 g_object_unref(G_OBJECT(item));
415
416 /* Remove it from the list */
417
418 if (children == group->impl->item_list_end) {
419 group->impl->item_list_end = children->prev;
420 }
421
422 group->impl->item_list = g_list_remove_link(group->impl->item_list, children);
423 g_list_free(children);
424 break;
425 }
426 }
427 }
428
429 static void
ganv_group_class_init(GanvGroupClass * klass)430 ganv_group_class_init(GanvGroupClass* klass)
431 {
432 GObjectClass* gobject_class = (GObjectClass*)klass;
433 GtkObjectClass* object_class = (GtkObjectClass*)klass;
434 GanvItemClass* item_class = (GanvItemClass*)klass;
435
436 group_parent_class = (GanvItemClass*)g_type_class_peek_parent(klass);
437
438 gobject_class->set_property = ganv_group_set_property;
439 gobject_class->get_property = ganv_group_get_property;
440
441 object_class->destroy = ganv_group_destroy;
442
443 item_class->add = ganv_group_add;
444 item_class->remove = ganv_group_remove;
445 item_class->update = ganv_group_update;
446 item_class->realize = ganv_group_realize;
447 item_class->unrealize = ganv_group_unrealize;
448 item_class->map = ganv_group_map;
449 item_class->unmap = ganv_group_unmap;
450 item_class->draw = ganv_group_draw;
451 item_class->point = ganv_group_point;
452 item_class->bounds = ganv_group_bounds;
453 }
454