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