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