/* This file is part of Ganv. * Copyright 2007-2013 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 #include #include "ganv/canvas.h" #include "ganv/port.h" #include "ganv/module.h" #include "./boilerplate.h" #include "./color.h" #include "./ganv-private.h" #include "./gettext.h" static const double PORT_LABEL_HPAD = 4.0; static const double PORT_LABEL_VPAD = 1.0; static const guchar check_off[] = { 0xE2, 0x98, 0x90, 0 }; static const guchar check_on[] = { 0xE2, 0x98, 0x91, 0 }; static void ganv_port_update_control_slider(GanvPort* port, float value, gboolean force); G_DEFINE_TYPE(GanvPort, ganv_port, GANV_TYPE_BOX) 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 = G_TYPE_INSTANCE_GET_PRIVATE( port, GANV_TYPE_PORT, GanvPortImpl); 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_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) { GanvItem* const rect = GANV_ITEM(port->impl->control->rect); GANV_ITEM_GET_CLASS(rect)->draw(rect, cr, cx, cy, cw, ch); } 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->control ? GANV_ITEM(port->impl->control->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) { 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; switch (ganv_canvas_get_direction(canvas)) { case GANV_DIRECTION_RIGHT: *x = px + ganv_box_get_width(&port->box); *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); *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) { 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; switch (ganv_canvas_get_direction(canvas)) { case GANV_DIRECTION_RIGHT: *x = px; *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 = 0.0; *dx = 0.0; *dy = -1.0; break; } ganv_item_i2w(item->impl->parent, x, y); } static void ganv_port_place_value_label(GanvPort* port) { GanvPortControl* control = port->impl->control; if (control && control->label) { GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port)); const double port_w = ganv_box_get_width(&port->box); const double label_w = control->label->impl->coords.width; if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) { ganv_item_set(GANV_ITEM(control->label), "x", port_w - label_w - PORT_LABEL_HPAD, "y", PORT_LABEL_VPAD, NULL); } else { const double port_h = ganv_box_get_height(&port->box); const double label_h = control->label->impl->coords.height; ganv_item_set(GANV_ITEM(control->label), "x", (port_w - label_w) / 2.0, "y", (port_h - label_h) / 2.0, 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->control ? port->impl->control->label : NULL; 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) { ganv_box_set_width(&port->box, label_w + vlabel_w + (PORT_LABEL_HPAD * 2.0)); ganv_box_set_height(&port->box, MAX(label_h, vlabel_h) + (PORT_LABEL_VPAD * 2.0)); ganv_item_set(GANV_ITEM(node->impl->label), "x", PORT_LABEL_HPAD, "y", PORT_LABEL_VPAD, NULL); } if (parent_class->parent_class.resize) { parent_class->parent_class.resize(self); } } static void ganv_port_redraw_text(GanvNode* node) { GanvPort* port = GANV_PORT(node); if (port->impl->control && port->impl->control->label) { ganv_text_layout(port->impl->control->label); ganv_port_place_value_label(port); } if (GANV_NODE_CLASS(parent_class)->redraw_text) { (*GANV_NODE_CLASS(parent_class)->redraw_text)(node); } } 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_value_label(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) { double control_y1; g_object_get(port->impl->control->rect, "y1", &control_y1, NULL); ganv_item_set(GANV_ITEM(port->impl->control->rect), "y2", control_y1 + height, NULL); ganv_port_place_value_label(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)); g_type_class_add_private(klass, sizeof(GanvPortImpl)); 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->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 = 1.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 : 4.0); box->impl->radius_tr = (is_input ? 4.0 : 0.0); box->impl->radius_br = (is_input ? 4.0 : 0.0); box->impl->radius_bl = (is_input ? 0.0 : 4.0); break; case GANV_DIRECTION_DOWN: box->impl->radius_tl = (is_input ? 0.0 : 4.0); box->impl->radius_tr = (is_input ? 0.0 : 4.0); box->impl->radius_br = (is_input ? 4.0 : 0.0); box->impl->radius_bl = (is_input ? 4.0 : 0.0); break; } ganv_node_resize(node); } void ganv_port_show_control(GanvPort* port) { GanvPortControl* control = (GanvPortControl*)malloc(sizeof(GanvPortControl)); port->impl->control = control; guint control_col = highlight_color(GANV_NODE(port)->impl->fill_color, 0x40); control->value = 0.0f; control->min = 0.0f; control->max = 0.0f; control->is_toggle = FALSE; control->is_integer = FALSE; control->label = NULL; control->rect = GANV_BOX( ganv_item_new(GANV_ITEM(port), ganv_box_get_type(), "x1", 0.0, "y1", 0.0, "x2", 0.0, "y2", ganv_box_get_height(&port->box), "fill-color", control_col, "border-color", control_col, "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) { GanvPortImpl* impl = port->impl; if (!impl->control) { return; } if (!str || str[0] == '\0') { if (impl->control->label) { gtk_object_destroy(GTK_OBJECT(impl->control->label)); impl->control->label = NULL; } } else if (impl->control->label) { ganv_item_set(GANV_ITEM(impl->control->label), "text", str, NULL); } else { impl->control->label = GANV_TEXT(ganv_item_new(GANV_ITEM(port), ganv_text_get_type(), "text", str, "color", 0xFFFFFFFF, "managed", TRUE, NULL)); ganv_port_resize(GANV_NODE(port)); } } static void ganv_port_update_control_slider(GanvPort* port, float value, gboolean force) { GanvPortImpl* 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 w = (value - impl->control->min) / (impl->control->max - impl->control->min) * ganv_box_get_width(&port->box); 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 - 1.0)); 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) { GanvPortImpl* impl = port->impl; if (impl->control && impl->control->is_toggle) { ganv_port_set_value_label( port, (const char*)((value == 0.0f) ? check_off : check_on)); } 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 = 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; 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->control && port->impl->control->label && (GANV_ITEM(port->impl->control->label)->object.flags & GANV_ITEM_VISIBLE)) { double label_w; g_object_get(port->impl->control->label, "width", &label_w, NULL); w += (PORT_LABEL_HPAD * 4.0); } 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; }