1 /*
2  * item-data.c
3  *
4  * ItemData object: part and wire model superclass.
5  *
6  * Author:
7  *  Richard Hult <rhult@hem.passagen.se>
8  *  Ricardo Markiewicz <rmarkie@fi.uba.ar>
9  *  Andres de Barbara <adebarbara@fi.uba.ar>
10  *  Marc Lorber <lorber.marc@wanadoo.fr>
11  *  Bernhard Schuster <bernhard@ahoi.io>
12  *
13  * Web page: https://ahoi.io/project/oregano
14  *
15  * Copyright (C) 1999-2001  Richard Hult
16  * Copyright (C) 2003,2006  Ricardo Markiewicz
17  * Copyright (C) 2009-2012  Marc Lorber
18  * Copyright (C) 2013       Bernhard Schuster
19  *
20  * This program is free software; you can redistribute it and/or
21  * modify it under the terms of the GNU General Public License as
22  * published by the Free Software Foundation; either version 2 of the
23  * License, or (at your option) any later version.
24  *
25  * This program is distributed in the hope that it will be useful,
26  * but WITHOUT ANY WARRANTY; without even the implied warranty of
27  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28  * General Public License for more details.
29  *
30  * You should have received a copy of the GNU General Public
31  * License along with this program; if not, write to the
32  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
33  * Boston, MA 02110-1301, USA.
34  */
35 
36 #include <glib.h>
37 #include <goocanvas.h>
38 
39 #include "item-data.h"
40 #include "node-store.h"
41 
42 #include "debug.h"
43 
44 static void item_data_class_init (ItemDataClass *klass);
45 static void item_data_init (ItemData *item_data);
46 static void item_data_set_gproperty (GObject *object, guint prop_id, const GValue *value,
47                                      GParamSpec *spec);
48 static void item_data_get_gproperty (GObject *object, guint prop_id, GValue *value,
49                                      GParamSpec *spec);
50 static void item_data_copy (ItemData *dest, ItemData *src);
51 
52 enum { ARG_0, ARG_STORE, ARG_POS };
53 
54 enum {
55 	MOVED,
56 	ROTATED,
57 	FLIPPED,
58 	CHANGED, // used to notify the view to reset and recalc the transformation
59 	HIGHLIGHT,
60 	LAST_SIGNAL
61 };
62 
63 struct _ItemDataPriv
64 {
65 	NodeStore *store;
66 
67 	// modificator matrices
68 	cairo_matrix_t translate;
69 	cairo_matrix_t rotate;
70 	cairo_matrix_t flip;
71 
72 	// Bounding box
73 	GooCanvasBounds bounds;
74 };
75 
76 G_DEFINE_TYPE (ItemData, item_data, G_TYPE_OBJECT);
77 
78 static guint item_data_signals[LAST_SIGNAL] = {0};
79 
item_data_init(ItemData * item_data)80 static void item_data_init (ItemData *item_data)
81 {
82 	ItemDataPriv *priv;
83 
84 	priv = g_slice_new0 (ItemDataPriv);
85 
86 	priv->bounds.x1 = priv->bounds.x2 = priv->bounds.y1 = priv->bounds.y2 = 0;
87 
88 	cairo_matrix_init_identity (&(priv->translate));
89 	cairo_matrix_init_identity (&(priv->rotate));
90 	cairo_matrix_init_identity (&(priv->flip));
91 
92 	item_data->priv = priv;
93 }
94 
item_data_dispose(GObject * object)95 static void item_data_dispose (GObject *object)
96 {
97 	ItemDataPriv *priv = ITEM_DATA (object)->priv;
98 	// Remove the item from the sheet node store if there.
99 	if (priv->store) {
100 		item_data_unregister (ITEM_DATA (object));
101 	}
102 	g_slice_free (ItemDataPriv, priv);
103 	G_OBJECT_CLASS (item_data_parent_class)->dispose (object);
104 }
105 
item_data_finalize(GObject * object)106 static void item_data_finalize (GObject *object)
107 {
108 	g_return_if_fail (object != NULL);
109 	G_OBJECT_CLASS (item_data_parent_class)->finalize (object);
110 }
111 
item_data_class_init(ItemDataClass * klass)112 static void item_data_class_init (ItemDataClass *klass)
113 {
114 	GObjectClass *object_class;
115 
116 	item_data_parent_class = g_type_class_peek_parent (klass);
117 
118 	object_class = G_OBJECT_CLASS (klass);
119 
120 	// This assignment must be  performed before the call
121 	// to g_object_class_install_property
122 	object_class->set_property = item_data_set_gproperty;
123 	object_class->get_property = item_data_get_gproperty;
124 
125 	g_object_class_install_property (
126 	    object_class, ARG_STORE,
127 	    g_param_spec_pointer ("store", "ItemData::store", "the store data", G_PARAM_READWRITE));
128 
129 	g_object_class_install_property (
130 	    object_class, ARG_POS,
131 	    g_param_spec_pointer ("pos", "ItemData::pos", "the pos data", G_PARAM_READWRITE));
132 
133 	object_class->dispose = item_data_dispose;
134 	object_class->finalize = item_data_finalize;
135 	item_data_signals[MOVED] =
136 	    g_signal_new ("moved", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST,
137 	                  G_STRUCT_OFFSET (ItemDataClass, moved), NULL, NULL,
138 	                  g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
139 
140 	item_data_signals[ROTATED] =
141 	    g_signal_new ("rotated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL,
142 	                  NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
143 
144 	item_data_signals[FLIPPED] =
145 	    g_signal_new ("flipped", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL,
146 	                  NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
147 
148 	item_data_signals[CHANGED] =
149 	    g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL,
150 	                  NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
151 
152 	item_data_signals[HIGHLIGHT] =
153 	    g_signal_new ("highlight", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL,
154 	                  NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
155 
156 	// Methods.
157 	klass->clone = NULL;
158 	klass->copy = item_data_copy;
159 	klass->rotate = NULL;
160 	klass->flip = NULL;
161 	klass->reg = NULL;
162 	klass->unreg = NULL;
163 	klass->changed = NULL;
164 
165 	// Signals.
166 	klass->moved = NULL;
167 }
168 
169 ////////////////////////////////////////////////////////////////////////////////
170 // END BOILER PLATE
171 ////////////////////////////////////////////////////////////////////////////////
172 
item_data_new(void)173 ItemData *item_data_new (void)
174 {
175 	ItemData *item_data;
176 
177 	item_data = ITEM_DATA (g_object_new (item_data_get_type (), NULL));
178 
179 	return item_data;
180 }
181 
item_data_set_gproperty(GObject * object,guint prop_id,const GValue * value,GParamSpec * spec)182 static void item_data_set_gproperty (GObject *object, guint prop_id, const GValue *value,
183                                      GParamSpec *spec)
184 {
185 	ItemData *item_data = ITEM_DATA (object);
186 
187 	switch (prop_id) {
188 	case ARG_STORE:
189 		item_data->priv->store = g_value_get_pointer (value);
190 		break;
191 	default:
192 		G_OBJECT_WARN_INVALID_PROPERTY_ID (item_data, prop_id, spec);
193 	}
194 }
195 
item_data_get_gproperty(GObject * object,guint prop_id,GValue * value,GParamSpec * spec)196 static void item_data_get_gproperty (GObject *object, guint prop_id, GValue *value,
197                                      GParamSpec *spec)
198 {
199 	ItemData *item_data = ITEM_DATA (object);
200 
201 	switch (prop_id) {
202 	case ARG_STORE:
203 		g_value_set_pointer (value, item_data->priv->store);
204 		break;
205 	default:
206 		G_OBJECT_WARN_INVALID_PROPERTY_ID (item_data, prop_id, spec);
207 	}
208 }
209 
210 /**
211  * returns the top left corner of the item
212  */
item_data_get_pos(ItemData * item_data,Coords * pos)213 void item_data_get_pos (ItemData *item_data, Coords *pos)
214 {
215 	g_return_if_fail (item_data != NULL);
216 	g_return_if_fail (IS_ITEM_DATA (item_data));
217 	g_return_if_fail (pos != NULL);
218 	ItemDataPriv *priv;
219 	priv = item_data->priv;
220 	pos->x = priv->translate.x0;
221 	pos->y = priv->translate.y0;
222 }
223 
item_data_set_pos(ItemData * item_data,Coords * pos)224 void item_data_set_pos (ItemData *item_data, Coords *pos)
225 {
226 	ItemDataPriv *priv;
227 	gboolean handler_connected;
228 
229 	g_return_if_fail (pos);
230 	g_return_if_fail (item_data);
231 	g_return_if_fail (IS_ITEM_DATA (item_data));
232 
233 	priv = item_data->priv;
234 
235 	cairo_matrix_init_translate (&(priv->translate), pos->x, pos->y);
236 
237 	handler_connected =
238 	    g_signal_handler_is_connected (G_OBJECT (item_data), item_data->moved_handler_id);
239 	if (handler_connected) {
240 		g_signal_emit_by_name (G_OBJECT (item_data), "moved", pos);
241 	}
242 	handler_connected =
243 	    g_signal_handler_is_connected (G_OBJECT (item_data), item_data->changed_handler_id);
244 	if (handler_connected) {
245 		g_signal_emit_by_name (G_OBJECT (item_data), "changed");
246 	}
247 }
248 
item_data_move(ItemData * item_data,const Coords * delta)249 void item_data_move (ItemData *item_data, const Coords *delta)
250 {
251 	ItemDataPriv *priv;
252 
253 	g_return_if_fail (item_data != NULL);
254 	g_return_if_fail (IS_ITEM_DATA (item_data));
255 
256 	if (delta == NULL)
257 		return;
258 
259 	if (fabs (delta->x) < 1e-2 && fabs (delta->y) < 1e-2)
260 		return;
261 
262 	priv = item_data->priv;
263 	cairo_matrix_translate (&(priv->translate), delta->x, delta->y);
264 
265 	g_signal_emit_by_name (G_OBJECT (item_data), "changed");
266 }
267 
268 // NodeStore *
item_data_get_store(ItemData * item_data)269 gpointer item_data_get_store (ItemData *item_data)
270 {
271 	g_return_val_if_fail (item_data != NULL, NULL);
272 	g_return_val_if_fail (IS_ITEM_DATA (item_data), NULL);
273 
274 	return item_data->priv->store;
275 }
276 
item_data_clone(ItemData * src)277 ItemData *item_data_clone (ItemData *src)
278 {
279 	ItemDataClass *id_class;
280 
281 	g_return_val_if_fail (src != NULL, NULL);
282 	g_return_val_if_fail (IS_ITEM_DATA (src), NULL);
283 
284 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (src));
285 	if (id_class->clone == NULL)
286 		return NULL;
287 
288 	return id_class->clone (src);
289 }
290 
item_data_copy(ItemData * dest,ItemData * src)291 static void item_data_copy (ItemData *dest, ItemData *src)
292 {
293 	g_return_if_fail (dest != NULL);
294 	g_return_if_fail (IS_ITEM_DATA (dest));
295 	g_return_if_fail (src != NULL);
296 	g_return_if_fail (IS_ITEM_DATA (src));
297 
298 	dest->priv->translate = src->priv->translate;
299 	dest->priv->rotate = src->priv->rotate;
300 	dest->priv->flip = src->priv->flip;
301 	dest->priv->store = NULL;
302 }
303 
item_data_get_relative_bbox(ItemData * data,Coords * p1,Coords * p2)304 void item_data_get_relative_bbox (ItemData *data, Coords *p1, Coords *p2)
305 {
306 	g_return_if_fail (data != NULL);
307 	g_return_if_fail (IS_ITEM_DATA (data));
308 
309 	if (p1) {
310 		p1->x = data->priv->bounds.x1;
311 		p1->y = data->priv->bounds.y1;
312 	}
313 
314 	if (p2) {
315 		p2->x = data->priv->bounds.x2;
316 		p2->y = data->priv->bounds.y2;
317 	}
318 }
319 
item_data_get_absolute_bbox(ItemData * data,Coords * p1,Coords * p2)320 void item_data_get_absolute_bbox (ItemData *data, Coords *p1, Coords *p2)
321 {
322 	g_return_if_fail (data != NULL);
323 	g_return_if_fail (IS_ITEM_DATA (data));
324 
325 	ItemDataPriv *priv;
326 
327 	item_data_get_relative_bbox (data, p1, p2);
328 	priv = data->priv;
329 
330 	if (p1) {
331 		p1->x += priv->translate.x0;
332 		p1->y += priv->translate.y0;
333 	}
334 
335 	if (p2) {
336 		p2->x += priv->translate.x0;
337 		p2->y += priv->translate.y0;
338 	}
339 }
340 
item_data_set_relative_bbox(ItemData * data,Coords * p1,Coords * p2)341 void item_data_set_relative_bbox (ItemData *data, Coords *p1, Coords *p2)
342 {
343 	g_return_if_fail (data != NULL);
344 	g_return_if_fail (IS_ITEM_DATA (data));
345 
346 	if (p1) {
347 		data->priv->bounds.x1 = p1->x;
348 		data->priv->bounds.y1 = p1->y;
349 	}
350 
351 	if (p2) {
352 		data->priv->bounds.x2 = p2->x;
353 		data->priv->bounds.y2 = p2->y;
354 	}
355 }
356 
item_data_list_get_absolute_bbox(GList * item_data_list,Coords * p1,Coords * p2)357 void item_data_list_get_absolute_bbox (GList *item_data_list, Coords *p1, Coords *p2)
358 {
359 	GList *iter;
360 	Coords b1, b2;
361 
362 	if (item_data_list == NULL)
363 		return;
364 
365 	item_data_get_absolute_bbox (item_data_list->data, p1, p2);
366 
367 	for (iter = item_data_list; iter; iter = iter->next) {
368 		if (G_UNLIKELY (iter->data == NULL))
369 			continue;
370 		item_data_get_absolute_bbox (iter->data, &b1, &b2);
371 
372 		if (p1) {
373 			p1->x = MIN (p1->x, b1.x);
374 			p1->y = MIN (p1->y, b1.y);
375 		}
376 
377 		if (p2) {
378 			p2->x = MAX (p2->x, b2.x);
379 			p2->y = MAX (p2->y, b2.y);
380 		}
381 	}
382 }
383 
item_data_rotate(ItemData * data,int angle,Coords * center)384 void item_data_rotate (ItemData *data, int angle, Coords *center)
385 {
386 	ItemDataClass *id_class;
387 
388 	g_return_if_fail (data != NULL);
389 	g_return_if_fail (IS_ITEM_DATA (data));
390 
391 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
392 	if (id_class->rotate) {
393 		id_class->rotate (data, angle, center);
394 	}
395 }
396 
item_data_flip(ItemData * data,IDFlip direction,Coords * center)397 void item_data_flip (ItemData *data, IDFlip direction, Coords *center)
398 {
399 	ItemDataClass *id_class;
400 
401 	g_return_if_fail (data != NULL);
402 	g_return_if_fail (IS_ITEM_DATA (data));
403 
404 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
405 	if (id_class->flip) {
406 		id_class->flip (data, direction, center);
407 	}
408 }
409 
item_data_unregister(ItemData * data)410 void item_data_unregister (ItemData *data)
411 {
412 	ItemDataClass *id_class;
413 
414 	g_return_if_fail (data != NULL);
415 	g_return_if_fail (IS_ITEM_DATA (data));
416 
417 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
418 	if (id_class->unreg) {
419 		id_class->unreg (data);
420 	}
421 }
422 
item_data_register(ItemData * data)423 gboolean item_data_register (ItemData *data)
424 {
425 	ItemDataClass *id_class;
426 
427 	g_return_val_if_fail (data != NULL, FALSE);
428 	g_return_val_if_fail (IS_ITEM_DATA (data), FALSE);
429 
430 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
431 	if (id_class->reg) {
432 		return id_class->reg (data);
433 	}
434 	return FALSE;
435 }
436 
item_data_get_refdes_prefix(ItemData * data)437 char *item_data_get_refdes_prefix (ItemData *data)
438 {
439 	ItemDataClass *id_class;
440 
441 	g_return_val_if_fail (data != NULL, NULL);
442 	g_return_val_if_fail (IS_ITEM_DATA (data), NULL);
443 
444 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
445 	if (id_class->get_refdes_prefix) {
446 		return id_class->get_refdes_prefix (data);
447 	}
448 
449 	return NULL;
450 }
451 
item_data_set_property(ItemData * data,char * property,char * value)452 void item_data_set_property (ItemData *data, char *property, char *value)
453 {
454 	ItemDataClass *id_class;
455 
456 	g_return_if_fail (data != NULL);
457 	g_return_if_fail (IS_ITEM_DATA (data));
458 
459 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
460 	if (id_class->set_property) {
461 		id_class->set_property (data, property, value);
462 		return;
463 	}
464 }
465 
item_data_has_properties(ItemData * data)466 gboolean item_data_has_properties (ItemData *data)
467 {
468 	ItemDataClass *id_class;
469 
470 	g_return_val_if_fail (data != NULL, FALSE);
471 	g_return_val_if_fail (IS_ITEM_DATA (data), FALSE);
472 
473 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
474 	if (id_class->has_properties) {
475 		return id_class->has_properties (data);
476 	}
477 	return FALSE;
478 }
479 
item_data_print(ItemData * data,cairo_t * cr,SchematicPrintContext * ctx)480 void item_data_print (ItemData *data, cairo_t *cr, SchematicPrintContext *ctx)
481 {
482 	ItemDataClass *id_class;
483 
484 	g_return_if_fail (data != NULL);
485 	g_return_if_fail (IS_ITEM_DATA (data));
486 	g_return_if_fail (cr != NULL);
487 
488 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
489 	if (id_class->print) {
490 		id_class->print (data, cr, ctx);
491 	}
492 }
493 
494 /**
495  * \brief changed, forcefully emits a changed signal to recalculate the morph
496  *matrix
497  *
498  * @param data determines which item to refresh
499  *
500  * \note this function does _not_ request a redraw
501  */
item_data_changed(ItemData * data)502 void item_data_changed (ItemData *data)
503 {
504 	ItemDataClass *id_class;
505 
506 	g_return_if_fail (data != NULL);
507 	g_return_if_fail (IS_ITEM_DATA (data));
508 
509 	id_class = ITEM_DATA_CLASS (G_OBJECT_GET_CLASS (data));
510 	if (id_class->changed == NULL)
511 		return;
512 
513 	return id_class->changed (data);
514 }
515 
516 /**
517  * @param data
518  * @returns [transfer-none] pointer to cairo matrix which only includes the
519  * translation
520  */
item_data_get_translate(ItemData * data)521 cairo_matrix_t *item_data_get_translate (ItemData *data)
522 {
523 	g_return_val_if_fail (data != NULL, NULL);
524 	g_return_val_if_fail (IS_ITEM_DATA (data), NULL);
525 	return &(data->priv->translate);
526 }
527 
528 /**
529  * @param data
530  * @returns [transfer-none] pointer to cairo matrix which only includes the
531  * rotation
532  */
item_data_get_rotate(ItemData * data)533 cairo_matrix_t *item_data_get_rotate (ItemData *data)
534 {
535 	g_return_val_if_fail (data != NULL, NULL);
536 	g_return_val_if_fail (IS_ITEM_DATA (data), NULL);
537 	return &(data->priv->rotate);
538 }
539