summaryrefslogtreecommitdiffstats
path: root/src/node.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/node.c')
-rw-r--r--src/node.c897
1 files changed, 897 insertions, 0 deletions
diff --git a/src/node.c b/src/node.c
new file mode 100644
index 0000000..4955f36
--- /dev/null
+++ b/src/node.c
@@ -0,0 +1,897 @@
+/* This file is part of Ganv.
+ * Copyright 2007-2016 David Robillard <http://drobilla.net>
+ *
+ * Ganv is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or any later version.
+ *
+ * Ganv is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Ganv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ganv/canvas.h"
+#include "ganv/node.h"
+
+#include "./boilerplate.h"
+#include "./color.h"
+#include "./ganv-marshal.h"
+#include "./ganv-private.h"
+#include "./gettext.h"
+
+guint signal_moved;
+
+G_DEFINE_TYPE_WITH_CODE(GanvNode, ganv_node, GANV_TYPE_ITEM,
+ G_ADD_PRIVATE(GanvNode))
+
+static GanvItemClass* parent_class;
+
+enum {
+ PROP_0,
+ PROP_CANVAS,
+ PROP_PARTNER,
+ PROP_LABEL,
+ PROP_SHOW_LABEL,
+ PROP_DASH_LENGTH,
+ PROP_DASH_OFFSET,
+ PROP_BORDER_WIDTH,
+ PROP_FILL_COLOR,
+ PROP_BORDER_COLOR,
+ PROP_CAN_TAIL,
+ PROP_CAN_HEAD,
+ PROP_IS_SOURCE,
+ PROP_SELECTED,
+ PROP_HIGHLIGHTED,
+ PROP_DRAGGABLE,
+ PROP_GRABBED
+};
+
+static void
+ganv_node_init(GanvNode* node)
+{
+ GanvNodePrivate* impl = ganv_node_get_instance_private(node);
+
+ node->impl = impl;
+
+ impl->partner = NULL;
+ impl->label = NULL;
+ impl->dash_length = 0.0;
+ impl->dash_offset = 0.0;
+ impl->border_width = 2.0;
+ impl->fill_color = DEFAULT_FILL_COLOR;
+ impl->border_color = DEFAULT_BORDER_COLOR;
+ impl->can_tail = FALSE;
+ impl->can_head = FALSE;
+ impl->is_source = FALSE;
+ impl->selected = FALSE;
+ impl->highlighted = FALSE;
+ impl->draggable = FALSE;
+ impl->show_label = TRUE;
+ impl->grabbed = FALSE;
+ impl->must_resize = FALSE;
+#ifdef GANV_FDGL
+ impl->force.x = 0.0;
+ impl->force.y = 0.0;
+ impl->vel.x = 0.0;
+ impl->vel.y = 0.0;
+ impl->connected = FALSE;
+#endif
+}
+
+static void
+ganv_node_realize(GanvItem* item)
+{
+ GANV_ITEM_CLASS(parent_class)->realize(item);
+ ganv_canvas_add_node(ganv_item_get_canvas(item), GANV_NODE(item));
+}
+
+static void
+ganv_node_destroy(GtkObject* object)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_NODE(object));
+
+ GanvNode* node = GANV_NODE(object);
+ GanvNodePrivate* impl = node->impl;
+ if (impl->label) {
+ g_object_unref(impl->label);
+ impl->label = NULL;
+ }
+
+ GanvItem* item = GANV_ITEM(object);
+ ganv_node_disconnect(node);
+ if (item->impl->canvas) {
+ ganv_canvas_remove_node(item->impl->canvas, node);
+ }
+
+ if (GTK_OBJECT_CLASS(parent_class)->destroy) {
+ (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
+ }
+
+ impl->partner = NULL;
+ item->impl->canvas = NULL;
+}
+
+static void
+ganv_node_update(GanvItem* item, int flags)
+{
+ GanvNode* node = GANV_NODE(item);
+ if (node->impl->must_resize) {
+ ganv_node_resize(node);
+ node->impl->must_resize = FALSE;
+ }
+
+ if (node->impl->label) {
+ ganv_item_invoke_update(GANV_ITEM(node->impl->label), flags);
+ }
+
+ GANV_ITEM_CLASS(parent_class)->update(item, flags);
+}
+
+static void
+ganv_node_draw(GanvItem* item,
+ cairo_t* cr, double cx, double cy, double cw, double ch)
+{
+ /* TODO: Label is not drawn here because ports need to draw control
+ rects then the label on top. I can't see a way of solving this since
+ there's no single time parent class draw needs to be called, so perhaps
+ label shouldn't be part of this class... */
+}
+
+static void
+ganv_node_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* pspec)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_NODE(object));
+
+ GanvNode* node = GANV_NODE(object);
+ GanvNodePrivate* impl = node->impl;
+
+ switch (prop_id) {
+ SET_CASE(DASH_LENGTH, double, impl->dash_length);
+ SET_CASE(DASH_OFFSET, double, impl->dash_offset);
+ SET_CASE(BORDER_WIDTH, double, impl->border_width);
+ SET_CASE(FILL_COLOR, uint, impl->fill_color);
+ SET_CASE(BORDER_COLOR, uint, impl->border_color);
+ SET_CASE(CAN_TAIL, boolean, impl->can_tail);
+ SET_CASE(CAN_HEAD, boolean, impl->can_head);
+ SET_CASE(IS_SOURCE, boolean, impl->is_source);
+ SET_CASE(HIGHLIGHTED, boolean, impl->highlighted);
+ SET_CASE(DRAGGABLE, boolean, impl->draggable);
+ SET_CASE(GRABBED, boolean, impl->grabbed);
+ case PROP_PARTNER:
+ impl->partner = (GanvNode*)g_value_get_object(value);
+ break;
+ case PROP_SELECTED:
+ if (impl->selected != g_value_get_boolean(value)) {
+ GanvItem* item = GANV_ITEM(object);
+ impl->selected = g_value_get_boolean(value);
+ if (item->impl->canvas) {
+ if (impl->selected) {
+ ganv_canvas_select_node(ganv_item_get_canvas(item), node);
+ } else {
+ ganv_canvas_unselect_node(ganv_item_get_canvas(item), node);
+ }
+ ganv_item_request_update(item);
+ }
+ }
+ break;
+ case PROP_CANVAS:
+ if (!GANV_ITEM(object)->impl->parent) {
+ GanvCanvas* canvas = GANV_CANVAS(g_value_get_object(value));
+ g_object_set(object, "parent", ganv_canvas_root(canvas), NULL);
+ ganv_canvas_add_node(canvas, node);
+ } else {
+ g_warning("Cannot change `canvas' property after construction");
+ }
+ break;
+ case PROP_LABEL:
+ ganv_node_set_label(node, g_value_get_string(value));
+ break;
+ case PROP_SHOW_LABEL:
+ ganv_node_set_show_label(node, g_value_get_boolean(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ganv_node_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_NODE(object));
+
+ GanvNode* node = GANV_NODE(object);
+ GanvNodePrivate* impl = node->impl;
+
+ switch (prop_id) {
+ GET_CASE(PARTNER, object, impl->partner);
+ GET_CASE(LABEL, string, impl->label ? impl->label->impl->text : NULL);
+ GET_CASE(DASH_LENGTH, double, impl->dash_length);
+ GET_CASE(DASH_OFFSET, double, impl->dash_offset);
+ GET_CASE(BORDER_WIDTH, double, impl->border_width);
+ GET_CASE(FILL_COLOR, uint, impl->fill_color);
+ GET_CASE(BORDER_COLOR, uint, impl->border_color);
+ GET_CASE(CAN_TAIL, boolean, impl->can_tail);
+ GET_CASE(CAN_HEAD, boolean, impl->can_head);
+ GET_CASE(IS_SOURCE, boolean, impl->is_source);
+ GET_CASE(SELECTED, boolean, impl->selected);
+ GET_CASE(HIGHLIGHTED, boolean, impl->highlighted);
+ GET_CASE(DRAGGABLE, boolean, impl->draggable);
+ GET_CASE(GRABBED, boolean, impl->grabbed);
+ case PROP_CANVAS:
+ g_value_set_object(value, ganv_item_get_canvas(GANV_ITEM(object)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ganv_node_default_tail_vector(const GanvNode* self,
+ const GanvNode* head,
+ double* x,
+ double* y,
+ double* dx,
+ double* dy)
+{
+ GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self));
+
+ *x = GANV_ITEM(self)->impl->x;
+ *y = GANV_ITEM(self)->impl->y;
+
+ switch (ganv_canvas_get_direction(canvas)) {
+ case GANV_DIRECTION_RIGHT:
+ *dx = 1.0;
+ *dy = 0.0;
+ break;
+ case GANV_DIRECTION_DOWN:
+ *dx = 0.0;
+ *dy = 1.0;
+ break;
+ }
+
+ ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y);
+}
+
+static void
+ganv_node_default_head_vector(const GanvNode* self,
+ const GanvNode* tail,
+ double* x,
+ double* y,
+ double* dx,
+ double* dy)
+{
+ GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self));
+
+ *x = GANV_ITEM(self)->impl->x;
+ *y = GANV_ITEM(self)->impl->y;
+
+ switch (ganv_canvas_get_direction(canvas)) {
+ case GANV_DIRECTION_RIGHT:
+ *dx = -1.0;
+ *dy = 0.0;
+ break;
+ case GANV_DIRECTION_DOWN:
+ *dx = 0.0;
+ *dy = -1.0;
+ break;
+ }
+
+ ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y);
+}
+
+void
+ganv_node_get_draw_properties(const GanvNode* node,
+ double* dash_length,
+ double* border_color,
+ double* fill_color)
+{
+ GanvNodePrivate* impl = node->impl;
+
+ *dash_length = impl->dash_length;
+ *border_color = impl->border_color;
+ *fill_color = impl->fill_color;
+
+ if (impl->selected) {
+ *dash_length = 4.0;
+ *border_color = highlight_color(impl->border_color, 0x40);
+ }
+
+ if (impl->highlighted) {
+ *border_color = highlight_color(impl->border_color, 0x40);
+ *fill_color = impl->fill_color;
+ }
+}
+
+void
+ganv_node_set_label(GanvNode* node, const char* str)
+{
+ GanvNodePrivate* impl = node->impl;
+ if (!str || str[0] == '\0') {
+ if (impl->label) {
+ gtk_object_destroy(GTK_OBJECT(impl->label));
+ impl->label = NULL;
+ }
+ } else if (impl->label) {
+ ganv_item_set(GANV_ITEM(impl->label),
+ "text", str,
+ NULL);
+ } else {
+ impl->label = GANV_TEXT(ganv_item_new(GANV_ITEM(node),
+ ganv_text_get_type(),
+ "text", str,
+ "color", DEFAULT_TEXT_COLOR,
+ "managed", TRUE,
+ NULL));
+ }
+
+ impl->must_resize = TRUE;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+void
+ganv_node_set_show_label(GanvNode* node, gboolean show)
+{
+ if (node->impl->label) {
+ if (show) {
+ ganv_item_show(GANV_ITEM(node->impl->label));
+ } else {
+ ganv_item_hide(GANV_ITEM(node->impl->label));
+ }
+ }
+ node->impl->show_label = show;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+static void
+ganv_node_default_tick(GanvNode* self,
+ double seconds)
+{
+ GanvNode* node = GANV_NODE(self);
+ node->impl->dash_offset = seconds * 8.0;
+ ganv_item_request_update(GANV_ITEM(self));
+}
+
+static void
+ganv_node_default_disconnect(GanvNode* node)
+{
+ GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
+ if (canvas) {
+ ganv_canvas_for_each_edge_on(
+ canvas, node, (GanvEdgeFunc)ganv_edge_disconnect, NULL);
+ }
+}
+
+static void
+ganv_node_default_move(GanvNode* node,
+ double dx,
+ double dy)
+{
+ GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
+ ganv_item_move(GANV_ITEM(node), dx, dy);
+ ganv_canvas_for_each_edge_on(
+ canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+static void
+ganv_node_default_move_to(GanvNode* node,
+ double x,
+ double y)
+{
+ GanvItem* item = GANV_ITEM(node);
+ GanvCanvas* canvas = ganv_item_get_canvas(item);
+ item->impl->x = x;
+ item->impl->y = y;
+ if (node->impl->can_tail) {
+ ganv_canvas_for_each_edge_from(
+ canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
+ } else if (node->impl->can_head) {
+ ganv_canvas_for_each_edge_to(
+ canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
+ }
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+static void
+ganv_node_default_resize(GanvNode* node)
+{
+ GanvItem* item = GANV_ITEM(node);
+ if (GANV_IS_NODE(item->impl->parent)) {
+ ganv_node_resize(GANV_NODE(item->impl->parent));
+ }
+ node->impl->must_resize = FALSE;
+}
+
+static void
+ganv_node_default_redraw_text(GanvNode* node)
+{
+ if (node->impl->label) {
+ ganv_text_layout(node->impl->label);
+ node->impl->must_resize = TRUE;
+ ganv_item_request_update(GANV_ITEM(node));
+ }
+}
+
+static gboolean
+ganv_node_default_event(GanvItem* item,
+ GdkEvent* event)
+{
+ GanvNode* node = GANV_NODE(item);
+ GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node));
+
+ // FIXME: put these somewhere better
+ static double last_x, last_y;
+ static double drag_start_x, drag_start_y;
+ static gboolean dragging = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ ganv_item_raise(GANV_ITEM(node));
+ node->impl->highlighted = TRUE;
+ ganv_item_request_update(item);
+ return TRUE;
+
+ case GDK_LEAVE_NOTIFY:
+ ganv_item_lower(GANV_ITEM(node));
+ node->impl->highlighted = FALSE;
+ ganv_item_request_update(item);
+ return TRUE;
+
+ case GDK_BUTTON_PRESS:
+ drag_start_x = event->button.x;
+ drag_start_y = event->button.y;
+ last_x = event->button.x;
+ last_y = event->button.y;
+ if (!ganv_canvas_get_locked(canvas) && node->impl->draggable && event->button.button == 1) {
+ ganv_canvas_grab_item(
+ GANV_ITEM(node),
+ GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK|GDK_BUTTON_PRESS_MASK,
+ ganv_canvas_get_move_cursor(canvas),
+ event->button.time);
+ node->impl->grabbed = TRUE;
+ dragging = TRUE;
+ return TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ if (dragging) {
+ gboolean selected;
+ g_object_get(G_OBJECT(node), "selected", &selected, NULL);
+ ganv_canvas_ungrab_item(GANV_ITEM(node), event->button.time);
+ node->impl->grabbed = FALSE;
+ dragging = FALSE;
+ if (event->button.x != drag_start_x || event->button.y != drag_start_y) {
+ ganv_canvas_contents_changed(canvas);
+ if (selected) {
+ ganv_canvas_selection_move_finished(canvas);
+ } else {
+ const double x = GANV_ITEM(node)->impl->x;
+ const double y = GANV_ITEM(node)->impl->y;
+ g_signal_emit(node, signal_moved, 0, x, y, NULL);
+ }
+ } else {
+ // Clicked
+ if (selected) {
+ ganv_canvas_unselect_node(canvas, node);
+ } else {
+ if (!(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
+ ganv_canvas_clear_selection(canvas);
+ }
+ ganv_canvas_select_node(canvas, node);
+ }
+ }
+ return TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if ((dragging && (event->motion.state & GDK_BUTTON1_MASK))) {
+ gboolean selected;
+ g_object_get(G_OBJECT(node), "selected", &selected, NULL);
+
+ double new_x = event->motion.x;
+ double new_y = event->motion.y;
+
+ if (event->motion.is_hint) {
+ int t_x;
+ int t_y;
+ GdkModifierType state;
+ gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state);
+ new_x = t_x;
+ new_y = t_y;
+ }
+
+ const double dx = new_x - last_x;
+ const double dy = new_y - last_y;
+ if (selected) {
+ ganv_canvas_move_selected_items(canvas, dx, dy);
+ } else {
+ ganv_node_move(node, dx, dy);
+ }
+
+ last_x = new_x;
+ last_y = new_y;
+ return TRUE;
+ }
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+ganv_node_class_init(GanvNodeClass* klass)
+{
+ GObjectClass* gobject_class = (GObjectClass*)klass;
+ GtkObjectClass* object_class = (GtkObjectClass*)klass;
+ GanvItemClass* item_class = (GanvItemClass*)klass;
+
+ parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass));
+
+ gobject_class->set_property = ganv_node_set_property;
+ gobject_class->get_property = ganv_node_get_property;
+
+ g_object_class_install_property(
+ gobject_class, PROP_CANVAS, g_param_spec_object(
+ "canvas",
+ _("Canvas"),
+ _("The canvas this node is on."),
+ GANV_TYPE_CANVAS,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_PARTNER, g_param_spec_object(
+ "partner",
+ _("Partner"),
+ _("Partners are nodes that should be visually aligned to correspond"
+ " to each other, even if they are not necessarily connected (e.g."
+ " for separate modules representing the inputs and outputs of a"
+ " single thing). When the canvas is arranged, the partner will"
+ " be aligned as if there was an edge from this node to its"
+ " partner."),
+ GANV_TYPE_NODE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_LABEL, g_param_spec_string(
+ "label",
+ _("Label"),
+ _("The text to display as a label on this node."),
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_SHOW_LABEL, g_param_spec_boolean(
+ "show-label",
+ _("Show label"),
+ _("Whether or not to show the label."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_DASH_LENGTH, g_param_spec_double(
+ "dash-length",
+ _("Border dash length"),
+ _("Length of border dashes, or zero for no dashing."),
+ 0.0, G_MAXDOUBLE,
+ 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_DASH_OFFSET, g_param_spec_double(
+ "dash-offset",
+ _("Border dash offset"),
+ _("Start offset for border dashes, used for selected animation."),
+ 0.0, G_MAXDOUBLE,
+ 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_BORDER_WIDTH, g_param_spec_double(
+ "border-width",
+ _("Border width"),
+ _("Width of the border line."),
+ 0.0, G_MAXDOUBLE,
+ 2.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_FILL_COLOR, g_param_spec_uint(
+ "fill-color",
+ _("Fill color"),
+ _("Color of internal area."),
+ 0, G_MAXUINT,
+ DEFAULT_FILL_COLOR,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_BORDER_COLOR, g_param_spec_uint(
+ "border-color",
+ _("Border color"),
+ _("Color of border line."),
+ 0, G_MAXUINT,
+ DEFAULT_BORDER_COLOR,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_CAN_TAIL, g_param_spec_boolean(
+ "can-tail",
+ _("Can tail"),
+ _("Whether this node can be the tail of an edge."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_CAN_HEAD, g_param_spec_boolean(
+ "can-head",
+ _("Can head"),
+ _("Whether this object can be the head of an edge."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_IS_SOURCE, g_param_spec_boolean(
+ "is-source",
+ _("Is source"),
+ _("Whether this object should be positioned at the start of signal flow."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_SELECTED, g_param_spec_boolean(
+ "selected",
+ _("Selected"),
+ _("Whether this object is selected."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_HIGHLIGHTED, g_param_spec_boolean(
+ "highlighted",
+ _("Highlighted"),
+ _("Whether this object is highlighted."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_DRAGGABLE, g_param_spec_boolean(
+ "draggable",
+ _("Draggable"),
+ _("Whether this object is draggable."),
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_GRABBED, g_param_spec_boolean(
+ "grabbed",
+ _("Grabbed"),
+ _("Whether this object is grabbed by the user."),
+ 0,
+ G_PARAM_READWRITE));
+
+ signal_moved = g_signal_new("moved",
+ ganv_node_get_type(),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ ganv_marshal_VOID__DOUBLE_DOUBLE,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_DOUBLE,
+ G_TYPE_DOUBLE,
+ 0);
+
+ object_class->destroy = ganv_node_destroy;
+
+ item_class->realize = ganv_node_realize;
+ item_class->event = ganv_node_default_event;
+ item_class->update = ganv_node_update;
+ item_class->draw = ganv_node_draw;
+
+ klass->disconnect = ganv_node_default_disconnect;
+ klass->move = ganv_node_default_move;
+ klass->move_to = ganv_node_default_move_to;
+ klass->resize = ganv_node_default_resize;
+ klass->redraw_text = ganv_node_default_redraw_text;
+ klass->tick = ganv_node_default_tick;
+ klass->tail_vector = ganv_node_default_tail_vector;
+ klass->head_vector = ganv_node_default_head_vector;
+}
+
+gboolean
+ganv_node_can_tail(const GanvNode* self)
+{
+ return self->impl->can_tail;
+}
+
+gboolean
+ganv_node_can_head(const GanvNode* self)
+{
+ return self->impl->can_head;
+}
+
+void
+ganv_node_set_is_source(const GanvNode* node, gboolean is_source)
+{
+ node->impl->is_source = is_source;
+}
+
+gboolean
+ganv_node_is_within(const GanvNode* node,
+ double x1,
+ double y1,
+ double x2,
+ double y2)
+{
+ return GANV_NODE_GET_CLASS(node)->is_within(node, x1, y1, x2, y2);
+}
+
+void
+ganv_node_tick(GanvNode* node,
+ double seconds)
+{
+ GanvNodeClass* klass = GANV_NODE_GET_CLASS(node);
+ if (klass->tick) {
+ klass->tick(node, seconds);
+ }
+}
+
+void
+ganv_node_tail_vector(const GanvNode* self,
+ const GanvNode* head,
+ double* x1,
+ double* y1,
+ double* x2,
+ double* y2)
+{
+ GANV_NODE_GET_CLASS(self)->tail_vector(
+ self, head, x1, y1, x2, y2);
+}
+
+void
+ganv_node_head_vector(const GanvNode* self,
+ const GanvNode* tail,
+ double* x1,
+ double* y1,
+ double* x2,
+ double* y2)
+{
+ GANV_NODE_GET_CLASS(self)->head_vector(
+ self, tail, x1, y1, x2, y2);
+}
+
+const char*
+ganv_node_get_label(const GanvNode* node)
+{
+ return node->impl->label ? node->impl->label->impl->text : NULL;
+}
+
+double
+ganv_node_get_border_width(const GanvNode* node)
+{
+ return node->impl->border_width;
+}
+
+void
+ganv_node_set_border_width(const GanvNode* node, double border_width)
+{
+ node->impl->border_width = border_width;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+double
+ganv_node_get_dash_length(const GanvNode* node)
+{
+ return node->impl->dash_length;
+}
+
+void
+ganv_node_set_dash_length(const GanvNode* node, double dash_length)
+{
+ node->impl->dash_length = dash_length;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+double
+ganv_node_get_dash_offset(const GanvNode* node)
+{
+ return node->impl->dash_offset;
+}
+
+void
+ganv_node_set_dash_offset(const GanvNode* node, double dash_offset)
+{
+ node->impl->dash_offset = dash_offset;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+guint
+ganv_node_get_fill_color(const GanvNode* node)
+{
+ return node->impl->fill_color;
+}
+
+void
+ganv_node_set_fill_color(const GanvNode* node, guint fill_color)
+{
+ node->impl->fill_color = fill_color;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+guint
+ganv_node_get_border_color(const GanvNode* node)
+{
+ return node->impl->border_color;
+}
+
+void
+ganv_node_set_border_color(const GanvNode* node, guint border_color)
+{
+ node->impl->border_color = border_color;
+ ganv_item_request_update(GANV_ITEM(node));
+}
+
+GanvNode*
+ganv_node_get_partner(const GanvNode* node)
+{
+ return node->impl->partner;
+}
+
+void
+ganv_node_move(GanvNode* node,
+ double dx,
+ double dy)
+{
+ GANV_NODE_GET_CLASS(node)->move(node, dx, dy);
+}
+
+void
+ganv_node_move_to(GanvNode* node,
+ double x,
+ double y)
+{
+ GANV_NODE_GET_CLASS(node)->move_to(node, x, y);
+}
+
+void
+ganv_node_resize(GanvNode* node)
+{
+ GANV_NODE_GET_CLASS(node)->resize(node);
+ node->impl->must_resize = FALSE;
+}
+
+void
+ganv_node_redraw_text(GanvNode* node)
+{
+ GANV_NODE_GET_CLASS(node)->redraw_text(node);
+}
+
+void
+ganv_node_disconnect(GanvNode* node)
+{
+ GANV_NODE_GET_CLASS(node)->disconnect(node);
+}
+
+gboolean
+ganv_node_is_selected(GanvNode* node)
+{
+ gboolean selected = FALSE;
+ g_object_get(node, "selected", &selected, NULL);
+ return selected;
+}