diff options
Diffstat (limited to 'src/module.c')
-rw-r--r-- | src/module.c | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/src/module.c b/src/module.c new file mode 100644 index 0000000..bad930b --- /dev/null +++ b/src/module.c @@ -0,0 +1,859 @@ +/* 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 <stdlib.h> +#include <string.h> + +#include "ganv/canvas.h" +#include "ganv/module.h" +#include "ganv/port.h" +#include "ganv/widget.h" + +#include "./color.h" +#include "./boilerplate.h" +#include "./gettext.h" +#include "./ganv-private.h" + +#define FOREACH_PORT(ports, i) \ + for (GanvPort** i = (GanvPort**)ports->pdata; \ + i != (GanvPort**)ports->pdata + ports->len; ++i) + +#define FOREACH_PORT_CONST(ports, i) \ + for (const GanvPort** i = (const GanvPort**)ports->pdata; \ + i != (const GanvPort**)ports->pdata + ports->len; ++i) + +static const double PAD = 2.0; +static const double EDGE_PAD = 5.0; +static const double MODULE_LABEL_PAD = 2.0; + +G_DEFINE_TYPE_WITH_CODE(GanvModule, ganv_module, GANV_TYPE_BOX, + G_ADD_PRIVATE(GanvModule)) + +static GanvBoxClass* parent_class; + +enum { + PROP_0 +}; + +static void +ganv_module_init(GanvModule* module) +{ + GanvModulePrivate* impl = ganv_module_get_instance_private(module); + + module->impl = impl; + + GANV_NODE(module)->impl->can_head = FALSE; + GANV_NODE(module)->impl->can_tail = FALSE; + + impl->ports = g_ptr_array_new(); + impl->embed_item = NULL; + impl->embed_width = 0; + impl->embed_height = 0; + impl->widest_input = 0.0; + impl->widest_output = 0.0; + impl->must_reorder = FALSE; +} + +static void +ganv_module_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + GanvModule* module = GANV_MODULE(object); + GanvModulePrivate* impl = module->impl; + + if (impl->ports) { + FOREACH_PORT(impl->ports, p) { + g_object_unref(GTK_OBJECT(*p)); + } + g_ptr_array_free(impl->ports, TRUE); + impl->ports = NULL; + } + + if (impl->embed_item) { + g_object_unref(GTK_OBJECT(impl->embed_item)); + impl->embed_item = NULL; + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_module_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_module_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +typedef struct { + double embed_x; + double width; + double input_width; + double output_width; + gboolean horiz; + gboolean embed_between; +} Metrics; + +static void +title_size(GanvModule* module, double* w, double* h) +{ + if (module->box.node.impl->label) { + g_object_get(G_OBJECT(module->box.node.impl->label), + "width", w, + "height", h, + NULL); + } else { + *w = *h = 0.0; + } +} + +static void +measure(GanvModule* module, Metrics* m) +{ + memset(m, '\0', sizeof(Metrics)); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvText* canvas_title = GANV_NODE(module)->impl->label; + GanvModulePrivate* impl = module->impl; + + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) { + double contents_width = 0.0; + if (canvas_title) { + contents_width += title_w + (2.0 * PAD); + } + + m->embed_x = 0; + m->input_width = ganv_module_get_empty_port_breadth(module); + m->output_width = ganv_module_get_empty_port_breadth(module); + + // TODO: cache this or merge with resize_right + unsigned n_inputs = 0; + unsigned n_outputs = 0; + FOREACH_PORT(impl->ports, pi) { + if ((*pi)->impl->is_input) { + ++n_inputs; + } else { + ++n_outputs; + } + } + + const unsigned hor_ports = MAX(1, MAX(n_inputs, n_outputs)); + const double ports_width = (2 * EDGE_PAD) + + ((m->input_width) * hor_ports) + + ((PAD + 1.0) * (hor_ports - 1)); + + m->width = MAX(contents_width, ports_width); + m->width = MAX(m->width, impl->embed_width); + + if (impl->embed_item) { + m->width = MAX(impl->embed_width + 2.0 * PAD, m->width); + m->embed_x = PAD; + } + return; + } + + // The amount of space between a port edge and the module edge (on the + // side that the port isn't right on the edge). + const double hor_pad = (canvas_title ? 10.0 : 20.0); + + m->width = (canvas_title) ? title_w + 10.0 : 1.0; + + // Title is wide or there is an embedded widget, + // put inputs and outputs beside each other + m->horiz = (impl->embed_item || + (impl->widest_input + impl->widest_output + 10.0 + < MAX(m->width, impl->embed_width))); + + // Fit ports to module (or vice-versa) + m->input_width = impl->widest_input; + m->output_width = impl->widest_output; + double expand_w = (m->horiz ? (m->width / 2.0) : m->width) - hor_pad; + if (!impl->embed_item) { + m->input_width = MAX(impl->widest_input, expand_w); + m->output_width = MAX(impl->widest_output, expand_w); + } + + const double widest = MAX(m->input_width, m->output_width); + + if (impl->embed_item) { + double above_w = MAX(m->width, widest + hor_pad); + double between_w = MAX(m->width, + (m->input_width + + m->output_width + + impl->embed_width)); + + above_w = MAX(above_w, impl->embed_width); + + // Decide where to place embedded widget if necessary) + if (impl->embed_width < impl->embed_height * 2.0) { + m->embed_between = TRUE; + m->width = between_w; + m->embed_x = m->input_width; + } else { + m->width = above_w; + m->embed_x = 2.0; + } + } + + if (!canvas_title && (impl->widest_input == 0.0 + || impl->widest_output == 0.0)) { + m->width += 10.0; + } + + m->width += 4.0; + m->width = MAX(m->width, widest + hor_pad); +} + +static void +place_title(GanvModule* module, GanvDirection dir) +{ + GanvBox* box = GANV_BOX(module); + GanvText* canvas_title = GANV_NODE(module)->impl->label; + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + if (!canvas_title) { + return; + } + + GanvItem* t = GANV_ITEM(canvas_title); + if (dir == GANV_DIRECTION_RIGHT) { + t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0; + t->impl->y = 1.0; + } else { + t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0; + t->impl->y = ganv_module_get_empty_port_depth(module) + 1.0; + } +} + +static void +resize_right(GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvModulePrivate* impl = module->impl; + + Metrics m; + measure(module, &m); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + // Basic height contains title + double header_height = title_h ? (3.0 + title_h) : EDGE_PAD; + + if (impl->embed_item) { + ganv_item_set(impl->embed_item, + "x", (double)m.embed_x, + "y", header_height, + NULL); + } + + // Actually set width and height + ganv_box_set_width(GANV_BOX(module), m.width); + + // Offset ports below embedded widget + if (!m.embed_between) { + header_height += impl->embed_height; + } + + // Move ports to appropriate locations + double in_y = header_height; + double out_y = header_height; + FOREACH_PORT(impl->ports, pi) { + GanvPort* const p = (*pi); + GanvBox* const pbox = GANV_BOX(p); + GanvNode* const pnode = GANV_NODE(p); + const double h = ganv_box_get_height(pbox); + + // Offset to shift ports to make borders line up + const double border_off = (GANV_NODE(module)->impl->border_width - + pnode->impl->border_width) / 2.0; + + if (p->impl->is_input) { + ganv_node_move_to(pnode, -border_off, in_y + 1.0); + ganv_box_set_width(pbox, m.input_width); + in_y += h + pnode->impl->border_width + 1.0; + + ganv_canvas_for_each_edge_to( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } else { + ganv_node_move_to(pnode, m.width - m.output_width + border_off, out_y + 1.0); + ganv_box_set_width(pbox, m.output_width); + out_y += h + pnode->impl->border_width + 1.0; + + ganv_canvas_for_each_edge_from( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } + + if (!m.horiz) { + in_y = MAX(in_y, out_y); + out_y = MAX(in_y, out_y); + } + } + + double height = MAX(in_y, out_y) + EDGE_PAD; + if (impl->embed_item && m.embed_between) + height = MAX(height, impl->embed_height + header_height + 2.0); + + ganv_box_set_height(GANV_BOX(module), height); + + place_title(module, GANV_DIRECTION_RIGHT); +} + +static void +resize_down(GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvModulePrivate* impl = module->impl; + + Metrics m; + measure(module, &m); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + const double port_depth = ganv_module_get_empty_port_depth(module); + const double port_breadth = ganv_module_get_empty_port_breadth(module); + + if (impl->embed_item) { + ganv_item_set(impl->embed_item, + "x", (double)m.embed_x, + "y", port_depth + title_h, + NULL); + } + + const double height = PAD + title_h + + impl->embed_height + (port_depth * 2.0); + + // Move ports to appropriate locations + guint in_count = 0; + guint out_count = 0; + double in_x = 0.0; + double out_x = 0.0; + FOREACH_PORT(impl->ports, pi) { + GanvPort* const p = (*pi); + GanvBox* const pbox = GANV_BOX(p); + GanvNode* const pnode = GANV_NODE(p); + ganv_box_set_width(pbox, port_breadth); + ganv_box_set_height(pbox, port_depth); + + // Offset to shift ports to make borders line up + const double border_off = (GANV_NODE(module)->impl->border_width - + pnode->impl->border_width) / 2.0; + + if (p->impl->is_input) { + in_x = EDGE_PAD + (in_count++ * (port_breadth + PAD + 1.0)); + ganv_node_move_to(pnode, in_x, -border_off); + ganv_canvas_for_each_edge_to( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } else { + out_x = EDGE_PAD + (out_count++ * (port_breadth + PAD + 1.0)); + ganv_node_move_to(pnode, out_x, height - port_depth + border_off); + ganv_canvas_for_each_edge_from( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } + } + + ganv_box_set_height(GANV_BOX(module), height); + ganv_box_set_width(GANV_BOX(module), m.width); + place_title(module, GANV_DIRECTION_DOWN); +} + +static void +measure_ports(GanvModule* module) +{ + GanvModulePrivate* impl = module->impl; + + impl->widest_input = 0.0; + impl->widest_output = 0.0; + FOREACH_PORT_CONST(impl->ports, pi) { + const GanvPort* const p = (*pi); + const double w = ganv_port_get_natural_width(p); + if (p->impl->is_input) { + if (w > impl->widest_input) { + impl->widest_input = w; + } + } else { + if (w > impl->widest_output) { + impl->widest_output = w; + } + } + } +} + +static void +ganv_module_resize(GanvNode* self) +{ + GanvModule* module = GANV_MODULE(self); + GanvNode* node = GANV_NODE(self); + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + + double label_w = 0.0; + double label_h = 0.0; + if (node->impl->label) { + g_object_get(node->impl->label, + "width", &label_w, + "height", &label_h, + NULL); + } + + measure_ports(module); + + ganv_box_set_width(GANV_BOX(module), label_w + (MODULE_LABEL_PAD * 2.0)); + ganv_box_set_height(GANV_BOX(module), label_h); + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + resize_right(module); + break; + case GANV_DIRECTION_DOWN: + resize_down(module); + break; + } + + if (GANV_NODE_CLASS(parent_class)->resize) { + GANV_NODE_CLASS(parent_class)->resize(self); + } +} + +static void +ganv_module_redraw_text(GanvNode* self) +{ + FOREACH_PORT(GANV_MODULE(self)->impl->ports, p) { + ganv_node_redraw_text(GANV_NODE(*p)); + } + + if (parent_class->parent_class.redraw_text) { + parent_class->parent_class.redraw_text(self); + } +} + +static void +ganv_module_add_port(GanvModule* module, + GanvPort* port) +{ + GanvModulePrivate* impl = module->impl; + + // Update widest input/output measurements if necessary + const double width = ganv_port_get_natural_width(port); + if (port->impl->is_input && width > impl->widest_input) { + impl->widest_input = width; + } else if (!port->impl->is_input && width > impl->widest_output) { + impl->widest_output = width; + } + + // Add to port array + g_ptr_array_add(impl->ports, port); + + // Request update with resize and reorder + GANV_NODE(module)->impl->must_resize = TRUE; + impl->must_reorder = TRUE; +} + +static void +ganv_module_remove_port(GanvModule* module, + GanvPort* port) +{ + gboolean removed = g_ptr_array_remove(module->impl->ports, port); + if (removed) { + const double width = ganv_box_get_width(GANV_BOX(port)); + // Find new widest input or output, if necessary + if (port->impl->is_input && width >= module->impl->widest_input) { + module->impl->widest_input = 0; + FOREACH_PORT_CONST(module->impl->ports, i) { + const GanvPort* const p = (*i); + const double w = ganv_box_get_width(GANV_BOX(p)); + if (p->impl->is_input && w >= module->impl->widest_input) { + module->impl->widest_input = w; + } + } + } else if (!port->impl->is_input && width >= module->impl->widest_output) { + module->impl->widest_output = 0; + FOREACH_PORT_CONST(module->impl->ports, i) { + const GanvPort* const p = (*i); + const double w = ganv_box_get_width(GANV_BOX(p)); + if (!p->impl->is_input && w >= module->impl->widest_output) { + module->impl->widest_output = w; + } + } + } + + GANV_NODE(module)->impl->must_resize = TRUE; + } else { + fprintf(stderr, "Failed to find port to remove\n"); + } +} + +static void +ganv_module_add(GanvItem* item, GanvItem* child) +{ + if (GANV_IS_PORT(child)) { + ganv_module_add_port(GANV_MODULE(item), GANV_PORT(child)); + } + ganv_item_request_update(item); + if (GANV_ITEM_CLASS(parent_class)->add) { + GANV_ITEM_CLASS(parent_class)->add(item, child); + } +} + +static void +ganv_module_remove(GanvItem* item, GanvItem* child) +{ + if (GANV_IS_PORT(child)) { + ganv_module_remove_port(GANV_MODULE(item), GANV_PORT(child)); + } + ganv_item_request_update(item); + if (GANV_ITEM_CLASS(parent_class)->remove) { + GANV_ITEM_CLASS(parent_class)->remove(item, child); + } +} + +static int +ptr_sort(const GanvPort** a, const GanvPort** b, const PortOrderCtx* ctx) +{ + return ctx->port_cmp(*a, *b, ctx->data); +} + +static void +ganv_module_update(GanvItem* item, int flags) +{ + GanvModule* module = GANV_MODULE(item); + GanvCanvas* canvas = ganv_item_get_canvas(item); + + if (module->impl->must_reorder) { + // Sort ports array + PortOrderCtx ctx = ganv_canvas_get_port_order(canvas); + if (ctx.port_cmp) { + g_ptr_array_sort_with_data(module->impl->ports, + (GCompareDataFunc)ptr_sort, + + &ctx); + } + module->impl->must_reorder = FALSE; + } + + if (module->impl->embed_item) { + // Kick the embedded item to update position if we have moved + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } + + FOREACH_PORT(module->impl->ports, p) { + ganv_item_invoke_update(GANV_ITEM(*p), flags); + } + + if (module->impl->embed_item) { + ganv_item_invoke_update(GANV_ITEM(module->impl->embed_item), flags); + } + + GANV_ITEM_CLASS(parent_class)->update(item, flags); +} + +static void +ganv_module_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvNode* node = GANV_NODE(item); + GanvModule* module = GANV_MODULE(item); + + // Draw box + if (GANV_ITEM_CLASS(parent_class)->draw) { + (*GANV_ITEM_CLASS(parent_class)->draw)(item, cr, cx, cy, cw, ch); + } + + // Draw label + if (node->impl->label) { + GanvItem* label_item = GANV_ITEM(node->impl->label); + GANV_ITEM_GET_CLASS(label_item)->draw(label_item, cr, cx, cy, cw, ch); + } + + // Draw ports + FOREACH_PORT(module->impl->ports, p) { + GANV_ITEM_GET_CLASS(GANV_ITEM(*p))->draw( + GANV_ITEM(*p), cr, cx, cy, cw, ch); + } + + // Draw embed item + if (module->impl->embed_item) { + GANV_ITEM_GET_CLASS(module->impl->embed_item)->draw( + module->impl->embed_item, cr, cx, cy, cw, ch); + } +} + +static void +ganv_module_move_to(GanvNode* node, + double x, + double y) +{ + GanvModule* module = GANV_MODULE(node); + GANV_NODE_CLASS(parent_class)->move_to(node, x, y); + FOREACH_PORT(module->impl->ports, p) { + ganv_node_move(GANV_NODE(*p), 0.0, 0.0); + } + if (module->impl->embed_item) { + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } +} + +static void +ganv_module_move(GanvNode* node, + double dx, + double dy) +{ + GanvModule* module = GANV_MODULE(node); + GANV_NODE_CLASS(parent_class)->move(node, dx, dy); + FOREACH_PORT(module->impl->ports, p) { + ganv_node_move(GANV_NODE(*p), 0.0, 0.0); + } + if (module->impl->embed_item) { + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } +} + +static double +ganv_module_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + GanvModule* module = GANV_MODULE(item); + + double d = GANV_ITEM_CLASS(parent_class)->point(item, x, y, actual_item); + + if (!*actual_item) { + // Point is not inside module at all, no point in checking children + return d; + } + + FOREACH_PORT(module->impl->ports, p) { + GanvItem* const port = GANV_ITEM(*p); + + *actual_item = NULL; + d = GANV_ITEM_GET_CLASS(port)->point( + port, x - port->impl->x, y - port->impl->y, actual_item); + + if (*actual_item) { + // Point is inside a port + return d; + } + } + + // Point is inside module, but not a child port + *actual_item = item; + return 0.0; +} + +static void +ganv_module_class_init(GanvModuleClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + GanvNodeClass* node_class = (GanvNodeClass*)klass; + + parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass)); + + gobject_class->set_property = ganv_module_set_property; + gobject_class->get_property = ganv_module_get_property; + + object_class->destroy = ganv_module_destroy; + + item_class->add = ganv_module_add; + item_class->remove = ganv_module_remove; + item_class->update = ganv_module_update; + item_class->draw = ganv_module_draw; + item_class->point = ganv_module_point; + + node_class->move = ganv_module_move; + node_class->move_to = ganv_module_move_to; + node_class->resize = ganv_module_resize; + node_class->redraw_text = ganv_module_redraw_text; +} + +GanvModule* +ganv_module_new(GanvCanvas* canvas, + const char* first_property_name, ...) +{ + GanvModule* module = GANV_MODULE( + g_object_new(ganv_module_get_type(), "canvas", canvas, NULL)); + + va_list args; + va_start(args, first_property_name); + g_object_set_valist(G_OBJECT(module), first_property_name, args); + va_end(args); + + return module; +} + +guint +ganv_module_num_ports(const GanvModule* module) +{ + return module->impl->ports ? module->impl->ports->len : 0; +} + +GanvPort* +ganv_module_get_port(GanvModule* module, + guint index) +{ + return (GanvPort*)g_ptr_array_index(module->impl->ports, index); +} + +double +ganv_module_get_empty_port_breadth(const GanvModule* module) +{ + return ganv_module_get_empty_port_depth(module) * 2.0; +} + +double +ganv_module_get_empty_port_depth(const GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + + return ganv_canvas_get_font_size(canvas) * 1.1; +} + +static void +on_embed_size_request(GtkWidget* widget, + GtkRequisition* r, + void* user_data) +{ + GanvModule* module = GANV_MODULE(user_data); + GanvModulePrivate* impl = module->impl; + if (impl->embed_width == r->width && impl->embed_height == r->height) { + return; + } + + impl->embed_width = r->width; + impl->embed_height = r->height; + GANV_NODE(module)->impl->must_resize = TRUE; + + GtkAllocation allocation; + allocation.width = r->width; + allocation.height = r->width; + + gtk_widget_size_allocate(widget, &allocation); + ganv_item_set(impl->embed_item, + "width", (double)r->width, + "height", (double)r->height, + NULL); +} + +void +ganv_module_embed(GanvModule* module, + GtkWidget* widget) +{ + GanvModulePrivate* impl = module->impl; + if (!widget && !impl->embed_item) { + return; + } + + if (impl->embed_item) { + // Free existing embedded widget + gtk_object_destroy(GTK_OBJECT(impl->embed_item)); + impl->embed_item = NULL; + } + + if (!widget) { + // Removing an existing embedded widget + impl->embed_width = 0; + impl->embed_height = 0; + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); + return; + } + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + impl->embed_item = ganv_item_new( + GANV_ITEM(module), + ganv_widget_get_type(), + "x", 2.0, + "y", 4.0 + title_h, + "widget", widget, + NULL); + + GtkRequisition r; + gtk_widget_show_all(widget); + gtk_widget_size_request(widget, &r); + on_embed_size_request(widget, &r, module); + ganv_item_show(impl->embed_item); + + g_signal_connect(widget, "size-request", + G_CALLBACK(on_embed_size_request), module); + + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); +} + +void +ganv_module_set_direction(GanvModule* module, + GanvDirection direction) +{ + FOREACH_PORT(module->impl->ports, p) { + ganv_port_set_direction(*p, direction); + } + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); +} + +void +ganv_module_for_each_port(GanvModule* module, + GanvPortFunc f, + void* data) +{ + GanvModulePrivate* impl = module->impl; + const int len = impl->ports->len; + GanvPort** copy = (GanvPort**)malloc(sizeof(GanvPort*) * len); + memcpy(copy, impl->ports->pdata, sizeof(GanvPort*) * len); + + for (int i = 0; i < len; ++i) { + f(copy[i], data); + } + + free(copy); +} |