/* This file is part of Ganv. * Copyright 2007-2015 David Robillard * * 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 . */ #include "boilerplate.h" #include "color.h" #include "ganv-private.h" #include "gettext.h" #include "ganv/box.h" #include "ganv/canvas.h" #include "ganv/edge.h" #include "ganv/item.h" #include "ganv/module.h" #include "ganv/node.h" #include "ganv/port.h" #include "ganv/text.h" #include "ganv/types.h" #include #include #include #include #include #include #include #include static const double PORT_LABEL_HPAD = 4.0; static const double PORT_LABEL_VPAD = 1.0; static void ganv_port_update_control_slider(GanvPort* port, float value, gboolean force); G_DEFINE_TYPE_WITH_CODE(GanvPort, ganv_port, GANV_TYPE_BOX, G_ADD_PRIVATE(GanvPort)) static GanvBoxClass* parent_class; enum { PROP_0, PROP_IS_INPUT, PROP_IS_CONTROLLABLE }; enum { PORT_VALUE_CHANGED, PORT_LAST_SIGNAL }; static guint port_signals[PORT_LAST_SIGNAL]; static void ganv_port_init(GanvPort* port) { port->impl = (GanvPortPrivate*)ganv_port_get_instance_private(port); port->impl->control = NULL; port->impl->value_label = NULL; port->impl->is_input = TRUE; port->impl->is_controllable = FALSE; } static void ganv_port_destroy(GtkObject* object) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_PORT(object)); GanvItem* item = GANV_ITEM(object); GanvPort* port = GANV_PORT(object); GanvCanvas* canvas = ganv_item_get_canvas(item); if (canvas) { if (port->impl->is_input) { ganv_canvas_for_each_edge_to( canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL); } else { ganv_canvas_for_each_edge_from( canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL); } } if (GTK_OBJECT_CLASS(parent_class)->destroy) { (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); } } static void ganv_port_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_PORT(object)); GanvPort* port = GANV_PORT(object); switch (prop_id) { SET_CASE(IS_INPUT, boolean, port->impl->is_input) SET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable) default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void ganv_port_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_PORT(object)); GanvPort* port = GANV_PORT(object); switch (prop_id) { GET_CASE(IS_INPUT, boolean, port->impl->is_input) GET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable) default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void ganv_port_update(GanvItem* item, int flags) { GanvPort* port = GANV_PORT(item); GanvPortPrivate* impl = port->impl; if (impl->control) { ganv_item_invoke_update(GANV_ITEM(impl->control->rect), flags); } if (impl->value_label) { ganv_item_invoke_update(GANV_ITEM(port->impl->value_label), flags); } GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class); item_class->update(item, flags); } static void ganv_port_draw(GanvItem* item, cairo_t* cr, double cx, double cy, double cw, double ch) { GanvPort* port = GANV_PORT(item); GanvCanvas* canvas = ganv_item_get_canvas(item); // Draw Box GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class); item_class->draw(item, cr, cx, cy, cw, ch); if (port->impl->control) { // Clip to port boundaries (to stay within radiused borders) cairo_save(cr); const double pad = GANV_NODE(port)->impl->border_width / 2.0; GanvBoxCoords coords = GANV_BOX(port)->impl->coords; ganv_item_i2w_pair(GANV_ITEM(port), &coords.x1, &coords.y1, &coords.x2, &coords.y2); ganv_box_path(GANV_BOX(port), cr, coords.x1 + pad, coords.y1 + pad, coords.x2 - pad, coords.y2 - pad, -pad); cairo_clip(cr); GanvItem* const rect = GANV_ITEM(port->impl->control->rect); GANV_ITEM_GET_CLASS(rect)->draw(rect, cr, cx, cy, cw, ch); cairo_restore(cr); } if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN || !GANV_NODE(port)->impl->show_label) { return; } GanvItem* labels[2] = { GANV_ITEM(GANV_NODE(item)->impl->label), port->impl->value_label ? GANV_ITEM(port->impl->value_label) : NULL }; for (int i = 0; i < 2; ++i) { if (labels[i] && (labels[i]->object.flags & GANV_ITEM_VISIBLE)) { GANV_ITEM_GET_CLASS(labels[i])->draw( labels[i], cr, cx, cy, cw, ch); } } } static void ganv_port_tail_vector(const GanvNode* self, const GanvNode* head, double* x, double* y, double* dx, double* dy) { (void)head; GanvPort* port = GANV_PORT(self); GanvItem* item = &port->box.node.item; GanvCanvas* canvas = ganv_item_get_canvas(item); const double px = item->impl->x; const double py = item->impl->y; const double border_width = GANV_NODE(port)->impl->border_width; switch (ganv_canvas_get_direction(canvas)) { case GANV_DIRECTION_RIGHT: *x = px + ganv_box_get_width(&port->box) + (border_width / 2.0); *y = py + ganv_box_get_height(&port->box) / 2.0; *dx = 1.0; *dy = 0.0; break; case GANV_DIRECTION_DOWN: *x = px + ganv_box_get_width(&port->box) / 2.0; *y = py + ganv_box_get_height(&port->box) + (border_width / 2.0); *dx = 0.0; *dy = 1.0; break; } ganv_item_i2w(item->impl->parent, x, y); } static void ganv_port_head_vector(const GanvNode* self, const GanvNode* tail, double* x, double* y, double* dx, double* dy) { (void)tail; GanvPort* port = GANV_PORT(self); GanvItem* item = &port->box.node.item; GanvCanvas* canvas = ganv_item_get_canvas(item); const double px = item->impl->x; const double py = item->impl->y; const double border_width = GANV_NODE(port)->impl->border_width; switch (ganv_canvas_get_direction(canvas)) { case GANV_DIRECTION_RIGHT: *x = px - (border_width / 2.0); *y = py + ganv_box_get_height(&port->box) / 2.0; *dx = -1.0; *dy = 0.0; break; case GANV_DIRECTION_DOWN: *x = px + ganv_box_get_width(&port->box) / 2.0; *y = py - (border_width / 2.0); *dx = 0.0; *dy = -1.0; break; } ganv_item_i2w(item->impl->parent, x, y); } static void ganv_port_place_labels(GanvPort* port) { GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port)); GanvPortPrivate* impl = port->impl; GanvText* label = GANV_NODE(port)->impl->label; const double port_w = ganv_box_get_width(&port->box); const double port_h = ganv_box_get_height(&port->box); double vlabel_w = 0.0; if (impl->value_label) { const double vlabel_h = impl->value_label->impl->coords.height; vlabel_w = impl->value_label->impl->coords.width; if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) { ganv_item_set(GANV_ITEM(impl->value_label), "x", PORT_LABEL_HPAD, "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD, NULL); } else { ganv_item_set(GANV_ITEM(impl->value_label), "x", (port_w - vlabel_w) / 2.0, "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD, NULL); } vlabel_w += PORT_LABEL_HPAD; } if (label) { const double label_h = label->impl->coords.height; if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) { ganv_item_set(GANV_ITEM(label), "x", vlabel_w + PORT_LABEL_HPAD, "y", (port_h - label_h) / 2.0 - PORT_LABEL_VPAD, NULL); } } } static void ganv_port_resize(GanvNode* self) { GanvPort* port = GANV_PORT(self); GanvNode* node = GANV_NODE(self); GanvText* label = node->impl->label; GanvText* vlabel = port->impl->value_label; double label_w = 0.0; double label_h = 0.0; double vlabel_w = 0.0; double vlabel_h = 0.0; if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) { g_object_get(label, "width", &label_w, "height", &label_h, NULL); } if (vlabel && (GANV_ITEM(vlabel)->object.flags & GANV_ITEM_VISIBLE)) { g_object_get(vlabel, "width", &vlabel_w, "height", &vlabel_h, NULL); } if (label || vlabel) { double labels_w = label_w + PORT_LABEL_HPAD * 2.0; if (vlabel_w != 0.0) { labels_w += vlabel_w + PORT_LABEL_HPAD; } ganv_box_set_width(&port->box, labels_w); ganv_box_set_height(&port->box, MAX(label_h, vlabel_h) + (PORT_LABEL_VPAD * 2.0)); ganv_port_place_labels(port); } if (GANV_NODE_CLASS(parent_class)->resize) { GANV_NODE_CLASS(parent_class)->resize(self); } } static void ganv_port_redraw_text(GanvNode* node) { GanvPort* port = GANV_PORT(node); if (port->impl->value_label) { ganv_text_layout(port->impl->value_label); } if (GANV_NODE_CLASS(parent_class)->redraw_text) { (*GANV_NODE_CLASS(parent_class)->redraw_text)(node); } ganv_port_place_labels(port); } static void ganv_port_set_width(GanvBox* box, double width) { GanvPort* port = GANV_PORT(box); parent_class->set_width(box, width); if (port->impl->control) { ganv_port_update_control_slider(port, port->impl->control->value, TRUE); } ganv_port_place_labels(port); } static void ganv_port_set_height(GanvBox* box, double height) { GanvPort* port = GANV_PORT(box); parent_class->set_height(box, height); if (port->impl->control) { ganv_item_set(GANV_ITEM(port->impl->control->rect), "y1", box->impl->coords.border_width / 2.0, "y2", height - box->impl->coords.border_width / 2.0, NULL); } ganv_port_place_labels(port); } static gboolean ganv_port_event(GanvItem* item, GdkEvent* event) { GanvCanvas* canvas = ganv_item_get_canvas(item); return ganv_canvas_port_event(canvas, GANV_PORT(item), event); } static void ganv_port_class_init(GanvPortClass* klass) { GObjectClass* gobject_class = (GObjectClass*)klass; GtkObjectClass* object_class = (GtkObjectClass*)klass; GanvItemClass* item_class = (GanvItemClass*)klass; GanvNodeClass* node_class = (GanvNodeClass*)klass; GanvBoxClass* box_class = (GanvBoxClass*)klass; parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass)); gobject_class->set_property = ganv_port_set_property; gobject_class->get_property = ganv_port_get_property; g_object_class_install_property( gobject_class, PROP_IS_INPUT, g_param_spec_boolean( "is-input", _("Is input"), _("Whether this port is an input, rather than an output."), 0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_IS_CONTROLLABLE, g_param_spec_boolean( "is-controllable", _("Is controllable"), _("Whether this port can be controlled by the user."), 0, G_PARAM_READWRITE)); port_signals[PORT_VALUE_CHANGED] = g_signal_new("value-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_DOUBLE); object_class->destroy = ganv_port_destroy; item_class->update = ganv_port_update; item_class->event = ganv_port_event; item_class->draw = ganv_port_draw; node_class->tail_vector = ganv_port_tail_vector; node_class->head_vector = ganv_port_head_vector; node_class->resize = ganv_port_resize; node_class->redraw_text = ganv_port_redraw_text; box_class->set_width = ganv_port_set_width; box_class->set_height = ganv_port_set_height; } GanvPort* ganv_port_new(GanvModule* module, gboolean is_input, const char* first_prop_name, ...) { GanvPort* port = GANV_PORT(g_object_new(ganv_port_get_type(), NULL)); port->impl->is_input = is_input; GanvItem* item = GANV_ITEM(port); va_list args; va_start(args, first_prop_name); ganv_item_construct(item, GANV_ITEM(module), first_prop_name, args); va_end(args); GanvBox* box = GANV_BOX(port); box->impl->coords.border_width = 1.0; GanvNode* node = GANV_NODE(port); node->impl->can_tail = !is_input; node->impl->can_head = is_input; node->impl->draggable = FALSE; node->impl->border_width = 2.0; GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port)); ganv_port_set_direction(port, ganv_canvas_get_direction(canvas)); return port; } void ganv_port_set_direction(GanvPort* port, GanvDirection direction) { GanvNode* node = GANV_NODE(port); GanvBox* box = GANV_BOX(port); gboolean is_input = port->impl->is_input; switch (direction) { case GANV_DIRECTION_RIGHT: box->impl->radius_tl = (is_input ? 0.0 : 5.0); box->impl->radius_tr = (is_input ? 5.0 : 0.0); box->impl->radius_br = (is_input ? 5.0 : 0.0); box->impl->radius_bl = (is_input ? 0.0 : 5.0); break; case GANV_DIRECTION_DOWN: box->impl->radius_tl = (is_input ? 0.0 : 5.0); box->impl->radius_tr = (is_input ? 0.0 : 5.0); box->impl->radius_br = (is_input ? 5.0 : 0.0); box->impl->radius_bl = (is_input ? 5.0 : 0.0); break; } node->impl->must_resize = TRUE; ganv_item_request_update(GANV_ITEM(node)); } void ganv_port_show_control(GanvPort* port) { if (port->impl->control) { return; } const guint color = 0xFFFFFF66; const double border_width = GANV_NODE(port)->impl->border_width; GanvPortControl* control = (GanvPortControl*)malloc(sizeof(GanvPortControl)); port->impl->control = control; control->value = 0.0f; control->min = 0.0f; control->max = 1.0f; control->is_toggle = FALSE; control->is_integer = FALSE; control->rect = GANV_BOX( ganv_item_new(GANV_ITEM(port), ganv_box_get_type(), "x1", border_width / 2.0, "y1", border_width / 2.0, "x2", 0.0, "y2", ganv_box_get_height(&port->box) - border_width / 2.0, "fill-color", color, "border-color", color, "border-width", 0.0, "managed", TRUE, NULL)); ganv_item_show(GANV_ITEM(control->rect)); } void ganv_port_hide_control(GanvPort* port) { gtk_object_destroy(GTK_OBJECT(port->impl->control->rect)); free(port->impl->control); port->impl->control = NULL; } void ganv_port_set_value_label(GanvPort* port, const char* str) { GanvPortPrivate* impl = port->impl; if (!str || str[0] == '\0') { if (impl->value_label) { gtk_object_destroy(GTK_OBJECT(impl->value_label)); impl->value_label = NULL; } } else if (impl->value_label) { ganv_item_set(GANV_ITEM(impl->value_label), "text", str, NULL); } else { impl->value_label = GANV_TEXT(ganv_item_new(GANV_ITEM(port), ganv_text_get_type(), "text", str, "color", DIM_TEXT_COLOR, "managed", TRUE, NULL)); } } static void ganv_port_update_control_slider(GanvPort* port, float value, gboolean force) { GanvPortPrivate* impl = port->impl; if (!impl->control) { return; } // Clamp to toggle or integer value if applicable if (impl->control->is_toggle) { if (value != 0.0f) { value = impl->control->max; } else { value = impl->control->min; } } else if (impl->control->is_integer) { value = lrintf(value); } // Clamp to range if (value < impl->control->min) { value = impl->control->min; } if (value > impl->control->max) { value = impl->control->max; } if (!force && value == impl->control->value) { return; // No change, do nothing } const double span = (ganv_box_get_width(&port->box) - GANV_NODE(port)->impl->border_width); const double w = (value - impl->control->min) / (impl->control->max - impl->control->min) * span; if (isnan(w)) { return; // Shouldn't happen, but ignore crazy values } // Redraw port impl->control->value = value; ganv_box_set_width(impl->control->rect, MAX(0.0, w)); ganv_box_request_redraw( GANV_ITEM(port), &GANV_BOX(port)->impl->coords, FALSE); } void ganv_port_set_control_is_toggle(GanvPort* port, gboolean is_toggle) { if (port->impl->control) { port->impl->control->is_toggle = is_toggle; ganv_port_update_control_slider(port, port->impl->control->value, TRUE); } } void ganv_port_set_control_is_integer(GanvPort* port, gboolean is_integer) { if (port->impl->control) { port->impl->control->is_integer = is_integer; const float rounded = rintf(port->impl->control->value); ganv_port_update_control_slider(port, rounded, TRUE); } } void ganv_port_set_control_value(GanvPort* port, float value) { ganv_port_update_control_slider(port, value, FALSE); } void ganv_port_set_control_value_internal(GanvPort* port, float value) { // Update slider ganv_port_set_control_value(port, value); // Fire signal to notify user value has changed const double dvalue = port->impl->control->value; g_signal_emit(port, port_signals[PORT_VALUE_CHANGED], 0, dvalue, NULL); } void ganv_port_set_control_min(GanvPort* port, float min) { if (port->impl->control) { const gboolean force = port->impl->control->min != min; port->impl->control->min = min; if (port->impl->control->max < min) { port->impl->control->max = min; } ganv_port_update_control_slider(port, port->impl->control->value, force); } } void ganv_port_set_control_max(GanvPort* port, float max) { if (port->impl->control) { const gboolean force = port->impl->control->max != max; port->impl->control->max = max; if (port->impl->control->min > max) { port->impl->control->min = max; } ganv_port_update_control_slider(port, port->impl->control->value, force); } } double ganv_port_get_natural_width(const GanvPort* port) { GanvCanvas* const canvas = ganv_item_get_canvas(GANV_ITEM(port)); GanvText* const label = port->box.node.impl->label; double w = 0.0; if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) { w = ganv_module_get_empty_port_breadth(ganv_port_get_module(port)); } else if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) { double label_w = 0.0; g_object_get(port->box.node.impl->label, "width", &label_w, NULL); w = label_w + (PORT_LABEL_HPAD * 2.0); } else { w = ganv_module_get_empty_port_depth(ganv_port_get_module(port)); } if (port->impl->value_label && (GANV_ITEM(port->impl->value_label)->object.flags & GANV_ITEM_VISIBLE)) { double label_w = 0.0; g_object_get(port->impl->value_label, "width", &label_w, NULL); w += label_w + PORT_LABEL_HPAD; } return w; } GanvModule* ganv_port_get_module(const GanvPort* port) { return GANV_MODULE(GANV_ITEM(port)->impl->parent); } float ganv_port_get_control_value(const GanvPort* port) { return port->impl->control ? port->impl->control->value : 0.0f; } float ganv_port_get_control_min(const GanvPort* port) { return port->impl->control ? port->impl->control->min : 0.0f; } float ganv_port_get_control_max(const GanvPort* port) { return port->impl->control ? port->impl->control->max : 0.0f; } gboolean ganv_port_is_input(const GanvPort* port) { return port->impl->is_input; } gboolean ganv_port_is_output(const GanvPort* port) { return !port->impl->is_input; }