1 /* This file is part of Ganv.
2  * Copyright 2007-2016 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 #include "boilerplate.h"
17 #include "color.h"
18 #include "ganv-marshal.h"
19 #include "ganv-private.h"
20 #include "gettext.h"
21 
22 #include "ganv/canvas.h"
23 #include "ganv/edge.h"
24 #include "ganv/item.h"
25 #include "ganv/node.h"
26 #include "ganv/text.h"
27 #include "ganv/types.h"
28 
29 #include <cairo.h>
30 #include <gdk/gdk.h>
31 #include <glib-object.h>
32 #include <glib.h>
33 #include <gtk/gtk.h>
34 
35 #include <math.h>
36 #include <stddef.h>
37 
38 guint signal_moved;
39 
40 G_DEFINE_TYPE_WITH_CODE(GanvNode, ganv_node, GANV_TYPE_ITEM,
41                         G_ADD_PRIVATE(GanvNode))
42 
43 static GanvItemClass* parent_class;
44 
45 enum {
46 	PROP_0,
47 	PROP_CANVAS,
48 	PROP_PARTNER,
49 	PROP_LABEL,
50 	PROP_SHOW_LABEL,
51 	PROP_DASH_LENGTH,
52 	PROP_DASH_OFFSET,
53 	PROP_BORDER_WIDTH,
54 	PROP_FILL_COLOR,
55 	PROP_BORDER_COLOR,
56 	PROP_CAN_TAIL,
57 	PROP_CAN_HEAD,
58 	PROP_IS_SOURCE,
59 	PROP_SELECTED,
60 	PROP_HIGHLIGHTED,
61 	PROP_DRAGGABLE,
62 	PROP_GRABBED
63 };
64 
65 static void
ganv_node_init(GanvNode * node)66 ganv_node_init(GanvNode* node)
67 {
68 	GanvNodePrivate* impl =
69 	    (GanvNodePrivate*)ganv_node_get_instance_private(node);
70 
71 	node->impl = impl;
72 
73 	impl->partner      = NULL;
74 	impl->label        = NULL;
75 	impl->dash_length  = 0.0;
76 	impl->dash_offset  = 0.0;
77 	impl->border_width = 2.0;
78 	impl->fill_color   = DEFAULT_FILL_COLOR;
79 	impl->border_color = DEFAULT_BORDER_COLOR;
80 	impl->can_tail     = FALSE;
81 	impl->can_head     = FALSE;
82 	impl->is_source    = FALSE;
83 	impl->selected     = FALSE;
84 	impl->highlighted  = FALSE;
85 	impl->draggable    = FALSE;
86 	impl->show_label   = TRUE;
87 	impl->grabbed      = FALSE;
88 	impl->must_resize  = FALSE;
89 #ifdef GANV_FDGL
90 	impl->force.x       = 0.0;
91 	impl->force.y       = 0.0;
92 	impl->vel.x         = 0.0;
93 	impl->vel.y         = 0.0;
94 	impl->connected     = FALSE;
95 #endif
96 }
97 
98 static void
ganv_node_realize(GanvItem * item)99 ganv_node_realize(GanvItem* item)
100 {
101 	GANV_ITEM_CLASS(parent_class)->realize(item);
102 	ganv_canvas_add_node(ganv_item_get_canvas(item), GANV_NODE(item));
103 }
104 
105 static void
ganv_node_destroy(GtkObject * object)106 ganv_node_destroy(GtkObject* object)
107 {
108 	g_return_if_fail(object != NULL);
109 	g_return_if_fail(GANV_IS_NODE(object));
110 
111 	GanvNode*        node = GANV_NODE(object);
112 	GanvNodePrivate* impl = node->impl;
113 	if (impl->label) {
114 		g_object_unref(impl->label);
115 		impl->label = NULL;
116 	}
117 
118 	GanvItem* item = GANV_ITEM(object);
119 	ganv_node_disconnect(node);
120 	if (item->impl->canvas) {
121 		ganv_canvas_remove_node(item->impl->canvas, node);
122 	}
123 
124 	if (GTK_OBJECT_CLASS(parent_class)->destroy) {
125 		(*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
126 	}
127 
128 	impl->partner      = NULL;
129 	item->impl->canvas = NULL;
130 }
131 
132 /// Expands the canvas to contain a moved/resized node if necessary
133 static void
ganv_node_expand_canvas(GanvNode * node)134 ganv_node_expand_canvas(GanvNode* node)
135 {
136 	GanvItem*   item   = GANV_ITEM(node);
137 	GanvCanvas* canvas = ganv_item_get_canvas(item);
138 
139 	if (item->impl->parent == ganv_canvas_root(canvas)) {
140 		const double pad = 10.0;
141 
142 		double x1 = 0.0;
143 		double y1 = 0.0;
144 		double x2 = 0.0;
145 		double y2 = 0.0;
146 		ganv_item_get_bounds(item, &x1, &y1, &x2, &y2);
147 
148 		ganv_item_i2w(item, &x1, &y1);
149 		ganv_item_i2w(item, &x2, &y2);
150 
151 		double canvas_w = 0.0;
152 		double canvas_h = 0.0;
153 		ganv_canvas_get_size(canvas, &canvas_w, &canvas_h);
154 		if (x2 + pad > canvas_w || y2 + pad > canvas_h) {
155 			ganv_canvas_resize(canvas,
156 			                   MAX(x1 + pad, canvas_w),
157 			                   MAX(y2 + pad, canvas_h));
158 		}
159 	}
160 }
161 
162 static void
ganv_node_update(GanvItem * item,int flags)163 ganv_node_update(GanvItem* item, int flags)
164 {
165 	GanvNode* node = GANV_NODE(item);
166 	if (node->impl->must_resize) {
167 		ganv_node_resize(node);
168 		node->impl->must_resize = FALSE;
169 	}
170 
171 	if (node->impl->label) {
172 		ganv_item_invoke_update(GANV_ITEM(node->impl->label), flags);
173 	}
174 
175 	GANV_ITEM_CLASS(parent_class)->update(item, flags);
176 
177 	ganv_node_expand_canvas(node);
178 }
179 
180 static void
ganv_node_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)181 ganv_node_draw(GanvItem* item,
182                cairo_t* cr, double cx, double cy, double cw, double ch)
183 {
184 	(void)item;
185 	(void)cr;
186 	(void)cx;
187 	(void)cy;
188 	(void)cw;
189 	(void)ch;
190 
191 	/* TODO: Label is not drawn here because ports need to draw control
192 	   rects then the label on top.  I can't see a way of solving this since
193 	   there's no single time parent class draw needs to be called, so perhaps
194 	   label shouldn't be part of this class... */
195 }
196 
197 static void
ganv_node_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)198 ganv_node_set_property(GObject*      object,
199                        guint         prop_id,
200                        const GValue* value,
201                        GParamSpec*   pspec)
202 {
203 	g_return_if_fail(object != NULL);
204 	g_return_if_fail(GANV_IS_NODE(object));
205 
206 	GanvNode*        node = GANV_NODE(object);
207 	GanvNodePrivate* impl = node->impl;
208 
209 	switch (prop_id) {
210 		SET_CASE(DASH_LENGTH, double, impl->dash_length)
211 		SET_CASE(DASH_OFFSET, double, impl->dash_offset)
212 		SET_CASE(BORDER_WIDTH, double, impl->border_width)
213 		SET_CASE(FILL_COLOR, uint, impl->fill_color)
214 		SET_CASE(BORDER_COLOR, uint, impl->border_color)
215 		SET_CASE(CAN_TAIL, boolean, impl->can_tail)
216 		SET_CASE(CAN_HEAD, boolean, impl->can_head)
217 		SET_CASE(IS_SOURCE, boolean, impl->is_source)
218 		SET_CASE(HIGHLIGHTED, boolean, impl->highlighted)
219 		SET_CASE(DRAGGABLE, boolean, impl->draggable)
220 		SET_CASE(GRABBED, boolean, impl->grabbed)
221 	case PROP_PARTNER:
222 		impl->partner = (GanvNode*)g_value_get_object(value);
223 		break;
224 	case PROP_SELECTED:
225 		if (impl->selected != g_value_get_boolean(value)) {
226 			GanvItem* item = GANV_ITEM(object);
227 			impl->selected = g_value_get_boolean(value);
228 			if (item->impl->canvas) {
229 				if (impl->selected) {
230 					ganv_canvas_select_node(ganv_item_get_canvas(item), node);
231 				} else {
232 					ganv_canvas_unselect_node(ganv_item_get_canvas(item), node);
233 				}
234 				ganv_item_request_update(item);
235 			}
236 		}
237 		break;
238 	case PROP_CANVAS:
239 		if (!GANV_ITEM(object)->impl->parent) {
240 			GanvCanvas* canvas = GANV_CANVAS(g_value_get_object(value));
241 			g_object_set(object, "parent", ganv_canvas_root(canvas), NULL);
242 			ganv_canvas_add_node(canvas, node);
243 		} else {
244 			g_warning("Cannot change `canvas' property after construction");
245 		}
246 		break;
247 	case PROP_LABEL:
248 		ganv_node_set_label(node, g_value_get_string(value));
249 		break;
250 	case PROP_SHOW_LABEL:
251 		ganv_node_set_show_label(node, g_value_get_boolean(value));
252 		break;
253 	default:
254 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
255 		break;
256 	}
257 }
258 
259 static void
ganv_node_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)260 ganv_node_get_property(GObject*    object,
261                        guint       prop_id,
262                        GValue*     value,
263                        GParamSpec* pspec)
264 {
265 	g_return_if_fail(object != NULL);
266 	g_return_if_fail(GANV_IS_NODE(object));
267 
268 	GanvNode*        node = GANV_NODE(object);
269 	GanvNodePrivate* impl = node->impl;
270 
271 	switch (prop_id) {
272 		GET_CASE(PARTNER, object, impl->partner)
273 		GET_CASE(LABEL, string, impl->label ? impl->label->impl->text : NULL)
274 		GET_CASE(DASH_LENGTH, double, impl->dash_length)
275 		GET_CASE(DASH_OFFSET, double, impl->dash_offset)
276 		GET_CASE(BORDER_WIDTH, double, impl->border_width)
277 		GET_CASE(FILL_COLOR, uint, impl->fill_color)
278 		GET_CASE(BORDER_COLOR, uint, impl->border_color)
279 		GET_CASE(CAN_TAIL, boolean, impl->can_tail)
280 		GET_CASE(CAN_HEAD, boolean, impl->can_head)
281 		GET_CASE(IS_SOURCE, boolean, impl->is_source)
282 		GET_CASE(SELECTED, boolean, impl->selected)
283 		GET_CASE(HIGHLIGHTED, boolean, impl->highlighted)
284 		GET_CASE(DRAGGABLE, boolean, impl->draggable)
285 		GET_CASE(GRABBED, boolean, impl->grabbed)
286 	case PROP_CANVAS:
287 		g_value_set_object(value, ganv_item_get_canvas(GANV_ITEM(object)));
288 		break;
289 	default:
290 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
291 		break;
292 	}
293 }
294 
295 static void
ganv_node_default_tail_vector(const GanvNode * self,const GanvNode * head,double * x,double * y,double * dx,double * dy)296 ganv_node_default_tail_vector(const GanvNode* self,
297                               const GanvNode* head,
298                               double*         x,
299                               double*         y,
300                               double*         dx,
301                               double*         dy)
302 {
303 	(void)head;
304 
305 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self));
306 
307 	*x = GANV_ITEM(self)->impl->x;
308 	*y = GANV_ITEM(self)->impl->y;
309 
310 	switch (ganv_canvas_get_direction(canvas)) {
311 	case GANV_DIRECTION_RIGHT:
312 		*dx = 1.0;
313 		*dy = 0.0;
314 		break;
315 	case GANV_DIRECTION_DOWN:
316 		*dx = 0.0;
317 		*dy = 1.0;
318 		break;
319 	}
320 
321 	ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y);
322 }
323 
324 static void
ganv_node_default_head_vector(const GanvNode * self,const GanvNode * tail,double * x,double * y,double * dx,double * dy)325 ganv_node_default_head_vector(const GanvNode* self,
326                               const GanvNode* tail,
327                               double*         x,
328                               double*         y,
329                               double*         dx,
330                               double*         dy)
331 {
332 	(void)tail;
333 
334 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self));
335 
336 	*x = GANV_ITEM(self)->impl->x;
337 	*y = GANV_ITEM(self)->impl->y;
338 
339 	switch (ganv_canvas_get_direction(canvas)) {
340 	case GANV_DIRECTION_RIGHT:
341 		*dx = -1.0;
342 		*dy = 0.0;
343 		break;
344 	case GANV_DIRECTION_DOWN:
345 		*dx = 0.0;
346 		*dy = -1.0;
347 		break;
348 	}
349 
350 	ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y);
351 }
352 
353 void
ganv_node_get_draw_properties(const GanvNode * node,double * dash_length,double * border_color,double * fill_color)354 ganv_node_get_draw_properties(const GanvNode* node,
355                               double*         dash_length,
356                               double*         border_color,
357                               double*         fill_color)
358 {
359 	GanvNodePrivate* impl = node->impl;
360 
361 	*dash_length  = impl->dash_length;
362 	*border_color = impl->border_color;
363 	*fill_color   = impl->fill_color;
364 
365 	if (impl->selected) {
366 		*dash_length  = 4.0;
367 		*border_color = highlight_color(impl->border_color, 0x40);
368 	}
369 
370 	if (impl->highlighted) {
371 		*border_color = highlight_color(impl->border_color, 0x40);
372 		*fill_color   = impl->fill_color;
373 	}
374 }
375 
376 void
ganv_node_set_label(GanvNode * node,const char * str)377 ganv_node_set_label(GanvNode* node, const char* str)
378 {
379 	GanvNodePrivate* impl = node->impl;
380 	if (!str || str[0] == '\0') {
381 		if (impl->label) {
382 			gtk_object_destroy(GTK_OBJECT(impl->label));
383 			impl->label = NULL;
384 		}
385 	} else if (impl->label) {
386 		ganv_item_set(GANV_ITEM(impl->label),
387 		              "text", str,
388 		              NULL);
389 	} else {
390 		impl->label = GANV_TEXT(ganv_item_new(GANV_ITEM(node),
391 		                                      ganv_text_get_type(),
392 		                                      "text", str,
393 		                                      "color", DEFAULT_TEXT_COLOR,
394 		                                      "managed", TRUE,
395 		                                      NULL));
396 	}
397 
398 	impl->must_resize = TRUE;
399 	ganv_item_request_update(GANV_ITEM(node));
400 }
401 
402 void
ganv_node_set_show_label(GanvNode * node,gboolean show)403 ganv_node_set_show_label(GanvNode* node, gboolean show)
404 {
405 	if (node->impl->label) {
406 		if (show) {
407 			ganv_item_show(GANV_ITEM(node->impl->label));
408 		} else {
409 			ganv_item_hide(GANV_ITEM(node->impl->label));
410 		}
411 	}
412 	node->impl->show_label = show;
413 	ganv_item_request_update(GANV_ITEM(node));
414 }
415 
416 static void
ganv_node_default_tick(GanvNode * self,double seconds)417 ganv_node_default_tick(GanvNode* self,
418                        double    seconds)
419 {
420 	GanvNode* node = GANV_NODE(self);
421 	node->impl->dash_offset = seconds * 8.0;
422 	ganv_item_request_update(GANV_ITEM(self));
423 }
424 
425 static void
ganv_node_default_disconnect(GanvNode * node)426 ganv_node_default_disconnect(GanvNode* node)
427 {
428 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
429 	if (canvas) {
430 		ganv_canvas_for_each_edge_on(
431 			canvas, node, (GanvEdgeFunc)ganv_edge_disconnect, NULL);
432 	}
433 }
434 
435 static void
ganv_node_default_move(GanvNode * node,double dx,double dy)436 ganv_node_default_move(GanvNode* node,
437                        double    dx,
438                        double    dy)
439 {
440 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
441 	ganv_item_move(GANV_ITEM(node), dx, dy);
442 	ganv_canvas_for_each_edge_on(
443 		canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
444 	ganv_item_request_update(GANV_ITEM(node));
445 }
446 
447 static void
ganv_node_default_move_to(GanvNode * node,double x,double y)448 ganv_node_default_move_to(GanvNode* node,
449                           double    x,
450                           double    y)
451 {
452 	GanvItem*   item   = GANV_ITEM(node);
453 	GanvCanvas* canvas = ganv_item_get_canvas(item);
454 	item->impl->x = x;
455 	item->impl->y = y;
456 	if (node->impl->can_tail) {
457 		ganv_canvas_for_each_edge_from(
458 			canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
459 	} else if (node->impl->can_head) {
460 		ganv_canvas_for_each_edge_to(
461 			canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
462 	}
463 	ganv_item_request_update(GANV_ITEM(node));
464 }
465 
466 static void
ganv_node_default_resize(GanvNode * node)467 ganv_node_default_resize(GanvNode* node)
468 {
469 	GanvItem* item = GANV_ITEM(node);
470 	if (GANV_IS_NODE(item->impl->parent)) {
471 		ganv_node_resize(GANV_NODE(item->impl->parent));
472 	}
473 	node->impl->must_resize = FALSE;
474 }
475 
476 static void
ganv_node_default_redraw_text(GanvNode * node)477 ganv_node_default_redraw_text(GanvNode* node)
478 {
479 	if (node->impl->label) {
480 		ganv_text_layout(node->impl->label);
481 		node->impl->must_resize = TRUE;
482 		ganv_item_request_update(GANV_ITEM(node));
483 	}
484 }
485 
486 static gboolean
ganv_node_default_event(GanvItem * item,GdkEvent * event)487 ganv_node_default_event(GanvItem* item,
488                         GdkEvent* event)
489 {
490 	GanvNode*   node   = GANV_NODE(item);
491 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
492 
493 	// FIXME: put these somewhere better
494 	static double   last_x       = NAN;
495 	static double   last_y       = NAN;
496 	static double   drag_start_x = NAN;
497 	static double   drag_start_y = NAN;
498 	static gboolean dragging     = FALSE;
499 
500 	switch (event->type) {
501 	case GDK_ENTER_NOTIFY:
502 		ganv_item_raise(GANV_ITEM(node));
503 		node->impl->highlighted = TRUE;
504 		ganv_item_request_update(item);
505 		return TRUE;
506 
507 	case GDK_LEAVE_NOTIFY:
508 		ganv_item_lower(GANV_ITEM(node));
509 		node->impl->highlighted = FALSE;
510 		ganv_item_request_update(item);
511 		return TRUE;
512 
513 	case GDK_BUTTON_PRESS:
514 		drag_start_x = event->button.x;
515 		drag_start_y = event->button.y;
516 		last_x       = event->button.x;
517 		last_y       = event->button.y;
518 		if (!ganv_canvas_get_locked(canvas) && node->impl->draggable && event->button.button == 1) {
519 			ganv_canvas_grab_item(
520 				GANV_ITEM(node),
521 				GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK|GDK_BUTTON_PRESS_MASK,
522 				ganv_canvas_get_move_cursor(canvas),
523 				event->button.time);
524 			node->impl->grabbed = TRUE;
525 			dragging = TRUE;
526 			return TRUE;
527 		}
528 		break;
529 
530 	case GDK_BUTTON_RELEASE:
531 		if (dragging) {
532 			gboolean selected = FALSE;
533 			g_object_get(G_OBJECT(node), "selected", &selected, NULL);
534 			ganv_canvas_ungrab_item(GANV_ITEM(node), event->button.time);
535 			node->impl->grabbed = FALSE;
536 			dragging = FALSE;
537 			if (event->button.x != drag_start_x || event->button.y != drag_start_y) {
538 				ganv_canvas_contents_changed(canvas);
539 				if (selected) {
540 					ganv_canvas_selection_move_finished(canvas);
541 				} else {
542 					const double x = GANV_ITEM(node)->impl->x;
543 					const double y = GANV_ITEM(node)->impl->y;
544 					g_signal_emit(node, signal_moved, 0, x, y, NULL);
545 				}
546 			} else {
547 				// Clicked
548 				if (selected) {
549 					ganv_canvas_unselect_node(canvas, node);
550 				} else {
551 					if (!(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
552 						ganv_canvas_clear_selection(canvas);
553 					}
554 					ganv_canvas_select_node(canvas, node);
555 				}
556 			}
557 			return TRUE;
558 		}
559 		break;
560 
561 	case GDK_MOTION_NOTIFY:
562 		if ((dragging && (event->motion.state & GDK_BUTTON1_MASK))) {
563 			gboolean selected = FALSE;
564 			g_object_get(G_OBJECT(node), "selected", &selected, NULL);
565 
566 			double new_x = event->motion.x;
567 			double new_y = event->motion.y;
568 
569 			if (event->motion.is_hint) {
570 				int             t_x   = 0;
571 				int             t_y   = 0;
572 				GdkModifierType state = (GdkModifierType)0;
573 				gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state);
574 				new_x = t_x;
575 				new_y = t_y;
576 			}
577 
578 			const double dx = new_x - last_x;
579 			const double dy = new_y - last_y;
580 			if (selected) {
581 				ganv_canvas_move_selected_items(canvas, dx, dy);
582 			} else {
583 				ganv_node_move(node, dx, dy);
584 			}
585 
586 			last_x = new_x;
587 			last_y = new_y;
588 			return TRUE;
589 		}
590 
591 	default:
592 		break;
593 	}
594 
595 	return FALSE;
596 }
597 
598 static void
ganv_node_class_init(GanvNodeClass * klass)599 ganv_node_class_init(GanvNodeClass* klass)
600 {
601 	GObjectClass*   gobject_class = (GObjectClass*)klass;
602 	GtkObjectClass* object_class  = (GtkObjectClass*)klass;
603 	GanvItemClass*  item_class    = (GanvItemClass*)klass;
604 
605 	parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass));
606 
607 	gobject_class->set_property = ganv_node_set_property;
608 	gobject_class->get_property = ganv_node_get_property;
609 
610 	g_object_class_install_property(
611 		gobject_class, PROP_CANVAS, g_param_spec_object(
612 			"canvas",
613 			_("Canvas"),
614 			_("The canvas this node is on."),
615 			GANV_TYPE_CANVAS,
616 			G_PARAM_READWRITE));
617 
618 	g_object_class_install_property(
619 		gobject_class, PROP_PARTNER, g_param_spec_object(
620 			"partner",
621 			_("Partner"),
622 			_("Partners are nodes that should be visually aligned to correspond"
623 			  " to each other, even if they are not necessarily connected (e.g."
624 			  " for separate modules representing the inputs and outputs of a"
625 			  " single thing).  When the canvas is arranged, the partner will"
626 			  " be aligned as if there was an edge from this node to its"
627 			  " partner."),
628 			GANV_TYPE_NODE,
629 			G_PARAM_READWRITE));
630 
631 	g_object_class_install_property(
632 		gobject_class, PROP_LABEL, g_param_spec_string(
633 			"label",
634 			_("Label"),
635 			_("The text to display as a label on this node."),
636 			NULL,
637 			G_PARAM_READWRITE));
638 
639 	g_object_class_install_property(
640 		gobject_class, PROP_SHOW_LABEL, g_param_spec_boolean(
641 			"show-label",
642 			_("Show label"),
643 			_("Whether or not to show the label."),
644 			0,
645 			G_PARAM_READWRITE));
646 
647 	g_object_class_install_property(
648 		gobject_class, PROP_DASH_LENGTH, g_param_spec_double(
649 			"dash-length",
650 			_("Border dash length"),
651 			_("Length of border dashes, or zero for no dashing."),
652 			0.0, G_MAXDOUBLE,
653 			0.0,
654 			G_PARAM_READWRITE));
655 
656 	g_object_class_install_property(
657 		gobject_class, PROP_DASH_OFFSET, g_param_spec_double(
658 			"dash-offset",
659 			_("Border dash offset"),
660 			_("Start offset for border dashes, used for selected animation."),
661 			0.0, G_MAXDOUBLE,
662 			0.0,
663 			G_PARAM_READWRITE));
664 
665 	g_object_class_install_property(
666 		gobject_class, PROP_BORDER_WIDTH, g_param_spec_double(
667 			"border-width",
668 			_("Border width"),
669 			_("Width of the border line."),
670 			0.0, G_MAXDOUBLE,
671 			2.0,
672 			G_PARAM_READWRITE));
673 
674 	g_object_class_install_property(
675 		gobject_class, PROP_FILL_COLOR, g_param_spec_uint(
676 			"fill-color",
677 			_("Fill color"),
678 			_("Color of internal area."),
679 			0, G_MAXUINT,
680 			DEFAULT_FILL_COLOR,
681 			G_PARAM_READWRITE));
682 
683 	g_object_class_install_property(
684 		gobject_class, PROP_BORDER_COLOR, g_param_spec_uint(
685 			"border-color",
686 			_("Border color"),
687 			_("Color of border line."),
688 			0, G_MAXUINT,
689 			DEFAULT_BORDER_COLOR,
690 			G_PARAM_READWRITE));
691 
692 	g_object_class_install_property(
693 		gobject_class, PROP_CAN_TAIL, g_param_spec_boolean(
694 			"can-tail",
695 			_("Can tail"),
696 			_("Whether this node can be the tail of an edge."),
697 			0,
698 			G_PARAM_READWRITE));
699 
700 	g_object_class_install_property(
701 		gobject_class, PROP_CAN_HEAD, g_param_spec_boolean(
702 			"can-head",
703 			_("Can head"),
704 			_("Whether this object can be the head of an edge."),
705 			0,
706 			G_PARAM_READWRITE));
707 
708 	g_object_class_install_property(
709 		gobject_class, PROP_IS_SOURCE, g_param_spec_boolean(
710 			"is-source",
711 			_("Is source"),
712 			_("Whether this object should be positioned at the start of signal flow."),
713 			0,
714 			G_PARAM_READWRITE));
715 
716 	g_object_class_install_property(
717 		gobject_class, PROP_SELECTED, g_param_spec_boolean(
718 			"selected",
719 			_("Selected"),
720 			_("Whether this object is selected."),
721 			0,
722 			G_PARAM_READWRITE));
723 
724 	g_object_class_install_property(
725 		gobject_class, PROP_HIGHLIGHTED, g_param_spec_boolean(
726 			"highlighted",
727 			_("Highlighted"),
728 			_("Whether this object is highlighted."),
729 			0,
730 			G_PARAM_READWRITE));
731 
732 	g_object_class_install_property(
733 		gobject_class, PROP_DRAGGABLE, g_param_spec_boolean(
734 			"draggable",
735 			_("Draggable"),
736 			_("Whether this object is draggable."),
737 			0,
738 			G_PARAM_READWRITE));
739 
740 	g_object_class_install_property(
741 		gobject_class, PROP_GRABBED, g_param_spec_boolean(
742 			"grabbed",
743 			_("Grabbed"),
744 			_("Whether this object is grabbed by the user."),
745 			0,
746 			G_PARAM_READWRITE));
747 
748 	signal_moved = g_signal_new("moved",
749 	                            ganv_node_get_type(),
750 	                            G_SIGNAL_RUN_FIRST,
751 	                            0, NULL, NULL,
752 	                            ganv_marshal_VOID__DOUBLE_DOUBLE,
753 	                            G_TYPE_NONE,
754 	                            2,
755 	                            G_TYPE_DOUBLE,
756 	                            G_TYPE_DOUBLE,
757 	                            0);
758 
759 	object_class->destroy = ganv_node_destroy;
760 
761 	item_class->realize = ganv_node_realize;
762 	item_class->event   = ganv_node_default_event;
763 	item_class->update  = ganv_node_update;
764 	item_class->draw    = ganv_node_draw;
765 
766 	klass->disconnect  = ganv_node_default_disconnect;
767 	klass->move        = ganv_node_default_move;
768 	klass->move_to     = ganv_node_default_move_to;
769 	klass->resize      = ganv_node_default_resize;
770 	klass->redraw_text = ganv_node_default_redraw_text;
771 	klass->tick        = ganv_node_default_tick;
772 	klass->tail_vector = ganv_node_default_tail_vector;
773 	klass->head_vector = ganv_node_default_head_vector;
774 }
775 
776 gboolean
ganv_node_can_tail(const GanvNode * self)777 ganv_node_can_tail(const GanvNode* self)
778 {
779 	return self->impl->can_tail;
780 }
781 
782 gboolean
ganv_node_can_head(const GanvNode * self)783 ganv_node_can_head(const GanvNode* self)
784 {
785 	return self->impl->can_head;
786 }
787 
788 void
ganv_node_set_is_source(const GanvNode * node,gboolean is_source)789 ganv_node_set_is_source(const GanvNode* node, gboolean is_source)
790 {
791 	node->impl->is_source = is_source;
792 }
793 
794 gboolean
ganv_node_is_within(const GanvNode * node,double x1,double y1,double x2,double y2)795 ganv_node_is_within(const GanvNode* node,
796                     double          x1,
797                     double          y1,
798                     double          x2,
799                     double          y2)
800 {
801 	return GANV_NODE_GET_CLASS(node)->is_within(node, x1, y1, x2, y2);
802 }
803 
804 void
ganv_node_tick(GanvNode * node,double seconds)805 ganv_node_tick(GanvNode* node,
806                double    seconds)
807 {
808 	GanvNodeClass* klass = GANV_NODE_GET_CLASS(node);
809 	if (klass->tick) {
810 		klass->tick(node, seconds);
811 	}
812 }
813 
814 void
ganv_node_tail_vector(const GanvNode * self,const GanvNode * head,double * x1,double * y1,double * x2,double * y2)815 ganv_node_tail_vector(const GanvNode* self,
816                       const GanvNode* head,
817                       double*         x1,
818                       double*         y1,
819                       double*         x2,
820                       double*         y2)
821 {
822 	GANV_NODE_GET_CLASS(self)->tail_vector(
823 		self, head, x1, y1, x2, y2);
824 }
825 
826 void
ganv_node_head_vector(const GanvNode * self,const GanvNode * tail,double * x1,double * y1,double * x2,double * y2)827 ganv_node_head_vector(const GanvNode* self,
828                       const GanvNode* tail,
829                       double*         x1,
830                       double*         y1,
831                       double*         x2,
832                       double*         y2)
833 {
834 	GANV_NODE_GET_CLASS(self)->head_vector(
835 		self, tail, x1, y1, x2, y2);
836 }
837 
838 const char*
ganv_node_get_label(const GanvNode * node)839 ganv_node_get_label(const GanvNode* node)
840 {
841 	return node->impl->label ? node->impl->label->impl->text : NULL;
842 }
843 
844 double
ganv_node_get_border_width(const GanvNode * node)845 ganv_node_get_border_width(const GanvNode* node)
846 {
847 	return node->impl->border_width;
848 }
849 
850 void
ganv_node_set_border_width(const GanvNode * node,double border_width)851 ganv_node_set_border_width(const GanvNode* node, double border_width)
852 {
853 	node->impl->border_width = border_width;
854 	ganv_item_request_update(GANV_ITEM(node));
855 }
856 
857 double
ganv_node_get_dash_length(const GanvNode * node)858 ganv_node_get_dash_length(const GanvNode* node)
859 {
860 	return node->impl->dash_length;
861 }
862 
863 void
ganv_node_set_dash_length(const GanvNode * node,double dash_length)864 ganv_node_set_dash_length(const GanvNode* node, double dash_length)
865 {
866 	node->impl->dash_length = dash_length;
867 	ganv_item_request_update(GANV_ITEM(node));
868 }
869 
870 double
ganv_node_get_dash_offset(const GanvNode * node)871 ganv_node_get_dash_offset(const GanvNode* node)
872 {
873 	return node->impl->dash_offset;
874 }
875 
876 void
ganv_node_set_dash_offset(const GanvNode * node,double dash_offset)877 ganv_node_set_dash_offset(const GanvNode* node, double dash_offset)
878 {
879 	node->impl->dash_offset = dash_offset;
880 	ganv_item_request_update(GANV_ITEM(node));
881 }
882 
883 guint
ganv_node_get_fill_color(const GanvNode * node)884 ganv_node_get_fill_color(const GanvNode* node)
885 {
886 	return node->impl->fill_color;
887 }
888 
889 void
ganv_node_set_fill_color(const GanvNode * node,guint fill_color)890 ganv_node_set_fill_color(const GanvNode* node, guint fill_color)
891 {
892 	node->impl->fill_color = fill_color;
893 	ganv_item_request_update(GANV_ITEM(node));
894 }
895 
896 guint
ganv_node_get_border_color(const GanvNode * node)897 ganv_node_get_border_color(const GanvNode* node)
898 {
899 	return node->impl->border_color;
900 }
901 
902 void
ganv_node_set_border_color(const GanvNode * node,guint border_color)903 ganv_node_set_border_color(const GanvNode* node, guint border_color)
904 {
905 	node->impl->border_color = border_color;
906 	ganv_item_request_update(GANV_ITEM(node));
907 }
908 
909 GanvNode*
ganv_node_get_partner(const GanvNode * node)910 ganv_node_get_partner(const GanvNode* node)
911 {
912 	return node->impl->partner;
913 }
914 
915 void
ganv_node_move(GanvNode * node,double dx,double dy)916 ganv_node_move(GanvNode* node,
917                double    dx,
918                double    dy)
919 {
920 	GANV_NODE_GET_CLASS(node)->move(node, dx, dy);
921 }
922 
923 void
ganv_node_move_to(GanvNode * node,double x,double y)924 ganv_node_move_to(GanvNode* node,
925                   double    x,
926                   double    y)
927 {
928 	GANV_NODE_GET_CLASS(node)->move_to(node, x, y);
929 }
930 
931 void
ganv_node_resize(GanvNode * node)932 ganv_node_resize(GanvNode* node)
933 {
934 	GANV_NODE_GET_CLASS(node)->resize(node);
935 	node->impl->must_resize = FALSE;
936 }
937 
938 void
ganv_node_redraw_text(GanvNode * node)939 ganv_node_redraw_text(GanvNode* node)
940 {
941 	GANV_NODE_GET_CLASS(node)->redraw_text(node);
942 }
943 
944 void
ganv_node_disconnect(GanvNode * node)945 ganv_node_disconnect(GanvNode* node)
946 {
947 	GANV_NODE_GET_CLASS(node)->disconnect(node);
948 }
949 
950 gboolean
ganv_node_is_selected(GanvNode * node)951 ganv_node_is_selected(GanvNode* node)
952 {
953 	gboolean selected = FALSE;
954 	g_object_get(node, "selected", &selected, NULL);
955 	return selected;
956 }
957