summaryrefslogtreecommitdiffstats
path: root/src/module.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-12-06 21:01:38 +0000
committerDavid Robillard <d@drobilla.net>2011-12-06 21:01:38 +0000
commit0731f12beaa0cfc0de56dc05ca3814143fd394a5 (patch)
treed8a98ee48badba378172d3a1c46fba2f2e266d37 /src/module.c
downloadganv-0731f12beaa0cfc0de56dc05ca3814143fd394a5.tar.gz
ganv-0731f12beaa0cfc0de56dc05ca3814143fd394a5.tar.bz2
ganv-0731f12beaa0cfc0de56dc05ca3814143fd394a5.zip
FlowCanvas's successor is hereby dubbed Ganv.
git-svn-id: http://svn.drobilla.net/lad/trunk/ganv@3820 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/module.c')
-rw-r--r--src/module.c792
1 files changed, 792 insertions, 0 deletions
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 0000000..66eb486
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,792 @@
+/* This file is part of Ganv.
+ * Copyright 2007-2011 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 (at your option)
+ * 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 <string.h>
+
+#include "ganv/canvas.h"
+#include "ganv/module.h"
+#include "ganv/port.h"
+
+#include "./color.h"
+#include "./boilerplate.h"
+#include "./gettext.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 MODULE_LABEL_PAD = 2.0;
+static const double MODULE_ICON_SIZE = 16;
+
+G_DEFINE_TYPE(GanvModule, ganv_module, GANV_TYPE_BOX)
+
+static GanvBoxClass* parent_class;
+
+enum {
+ PROP_0,
+ PROP_SHOW_PORT_LABELS
+};
+
+static void
+ganv_module_init(GanvModule* module)
+{
+ GANV_NODE(module)->can_head = FALSE;
+ GANV_NODE(module)->can_tail = FALSE;
+
+ module->ports = g_ptr_array_new();
+ module->icon_box = NULL;
+ module->embed_item = NULL;
+ module->embed_width = 0;
+ module->embed_height = 0;
+ module->widest_input = 0.0;
+ module->widest_output = 0.0;
+ module->show_port_labels = FALSE;
+ module->must_resize = FALSE;
+ module->port_size_changed = FALSE;
+}
+
+static void
+ganv_module_destroy(GtkObject* object)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_MODULE(object));
+
+ g_ptr_array_free(GANV_MODULE(object)->ports, TRUE);
+
+ GanvModule* module = GANV_MODULE(object);
+ FOREACH_PORT(module->ports, p) {
+ gtk_object_destroy(GTK_OBJECT(*p));
+ }
+
+ 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));
+
+ GanvModule* module = GANV_MODULE(object);
+
+ switch (prop_id) {
+ case PROP_SHOW_PORT_LABELS: {
+ const gboolean tmp = g_value_get_boolean(value);
+ if (module->show_port_labels != tmp) {
+ module->show_port_labels = tmp;
+ module->port_size_changed = TRUE;
+ module->must_resize = TRUE;
+ /* FIXME
+ FOREACH_PORT_CONST(gobj()->ports, p) {
+ (*p)->show_label(b);
+ }*/
+ gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(object));
+ }
+ break;
+ }
+ 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));
+
+ GanvModule* module = GANV_MODULE(object);
+
+ switch (prop_id) {
+ GET_CASE(SHOW_PORT_LABELS, boolean, module->show_port_labels);
+ 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.label) {
+ g_object_get(G_OBJECT(module->box.node.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_CANVAS(GNOME_CANVAS_ITEM(module)->canvas);
+ GanvText* canvas_title = module->box.node.label;
+
+ GanvDirection direction = canvas->direction;
+
+ if (direction == GANV_VERTICAL) {
+ static const double PAD = 2.0;
+
+ double contents_width = PAD;
+ if (canvas_title) {
+ contents_width += title_w;
+ }
+ if (module->icon_box) {
+ contents_width += MODULE_ICON_SIZE + 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);
+
+ const double ports_width = PAD + ((m->input_width + PAD) * module->ports->len);
+
+ m->width = MAX(contents_width, ports_width);
+ m->width = MAX(m->width, module->embed_width);
+ 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;
+
+ if (module->icon_box)
+ m->width += MODULE_ICON_SIZE + 2;
+
+ // Title is wide, put inputs and outputs beside each other
+ m->horiz = (module->widest_input + module->widest_output + 10.0
+ < MAX(m->width, module->embed_width));
+
+ // Fit ports to module (or vice-versa)
+ m->input_width = module->widest_input;
+ m->output_width = module->widest_output;
+ double expand_w = (m->horiz ? (m->width / 2.0) : m->width) - hor_pad;
+ if (module->show_port_labels && !module->embed_item) {
+ m->input_width = MAX(module->widest_input, expand_w);
+ m->output_width = MAX(module->widest_output, expand_w);
+ }
+
+ const double widest = MAX(m->input_width, m->output_width);
+
+ if (module->embed_item) {
+ double above_w = MAX(m->width, widest + hor_pad);
+ double between_w = MAX(m->width,
+ (m->input_width
+ + m->output_width
+ + module->embed_width));
+
+ above_w = MAX(above_w, module->embed_width);
+
+ // Decide where to place embedded widget if necessary)
+ if (module->embed_width < module->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 && (module->widest_input == 0.0
+ || module->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);
+
+ double title_w, title_h;
+ title_size(module, &title_w, &title_h);
+
+ GanvText* canvas_title = module->box.node.label;
+
+ if (!canvas_title) {
+ return;
+ } else if (dir == GANV_HORIZONTAL) {
+ if (module->icon_box) {
+ gnome_canvas_item_set(GNOME_CANVAS_ITEM(canvas_title),
+ "x", MODULE_ICON_SIZE + 1.0,
+ NULL);
+ } else {
+ gnome_canvas_item_set(GNOME_CANVAS_ITEM(canvas_title),
+ "x", ((ganv_box_get_width(box) / 2.0)
+ - (title_w / 2.0)),
+ NULL);
+ }
+ } else {
+ gnome_canvas_item_set(GNOME_CANVAS_ITEM(canvas_title),
+ "x", ((ganv_box_get_width(box) / 2.0)
+ - (title_w / 2.0)),
+ "y", ganv_module_get_empty_port_depth(module) + 2.0,
+ NULL);
+ }
+}
+
+static void
+resize_horiz(GanvModule* module)
+{
+ GanvCanvas* canvas = GANV_CANVAS(GNOME_CANVAS_ITEM(module)->canvas);
+
+ Metrics m;
+ measure(module, &m);
+
+ double title_w, title_h;
+ title_size(module, &title_w, &title_h);
+
+ // Basic height contains title, icon
+ double header_height = 2.0 + title_h;
+
+ double height = header_height;
+
+ if (module->embed_item) {
+ gnome_canvas_item_set(module->embed_item,
+ "x", (double)m.embed_x,
+ 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 += module->embed_height;
+ }
+
+ // Move ports to appropriate locations
+ int i = 0;
+ gboolean last_was_input = FALSE;
+ double y = 0.0;
+ double h = 0.0;
+ FOREACH_PORT(module->ports, pi) {
+ GanvPort* const p = (*pi);
+ GanvBox* const pbox = GANV_BOX(p);
+ GanvNode* const pnode = GANV_NODE(p);
+ h = ganv_box_get_height(pbox);
+
+ if (p->is_input) {
+ y = header_height + (i * (h + 1.0));
+ ++i;
+ ganv_box_set_width(pbox, m.input_width);
+ ganv_node_move_to(pnode, 0.0, y);
+ last_was_input = TRUE;
+
+ ganv_canvas_for_each_edge_to(
+ canvas, pnode, ganv_edge_update_location);
+ } else {
+ if (!m.horiz || !last_was_input) {
+ y = header_height + (i * (h + 1.0));
+ ++i;
+ }
+ ganv_box_set_width(pbox, m.output_width);
+ ganv_node_move_to(pnode,
+ m.width - ganv_box_get_width(pbox),
+ y);
+ last_was_input = FALSE;
+
+ ganv_canvas_for_each_edge_from(
+ canvas, pnode, ganv_edge_update_location);
+ }
+ }
+
+ if (module->ports->len == 0) {
+ h += header_height;
+ }
+
+ height = y + h + 4.0;
+ if (module->embed_item && m.embed_between)
+ height = MAX(height, module->embed_height + header_height + 2.0);
+
+ ganv_box_set_height(&module->box, height);
+
+ place_title(module, GANV_HORIZONTAL);
+}
+
+static void
+resize_vert(GanvModule* module)
+{
+ GanvCanvas* canvas = GANV_CANVAS(GNOME_CANVAS_ITEM(module)->canvas);
+
+ Metrics m;
+ measure(module, &m);
+
+ double title_w, title_h;
+ title_size(module, &title_w, &title_h);
+
+ static const double PAD = 2.0;
+
+ const double port_depth = ganv_module_get_empty_port_depth(module);
+ const double port_breadth = ganv_module_get_empty_port_breadth(module);
+
+ if (module->embed_item) {
+ gnome_canvas_item_set(module->embed_item,
+ "x", (double)m.embed_x,
+ "y", port_depth + title_h,
+ NULL);
+ }
+
+ const double height = PAD + title_h
+ + module->embed_height + (port_depth * 2.0);
+
+ // Move ports to appropriate locations
+ int i = 0;
+ gboolean last_was_input = FALSE;
+ double x = 0.0;
+ FOREACH_PORT(module->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);
+ if (p->is_input) {
+ x = PAD + (i * (port_breadth + PAD));
+ ++i;
+ ganv_node_move_to(pnode, x, 0);
+ last_was_input = TRUE;
+
+ ganv_canvas_for_each_edge_to(canvas, pnode,
+ ganv_edge_update_location);
+ } else {
+ if (!last_was_input) {
+ x = PAD + (i * (port_breadth + PAD));
+ ++i;
+ }
+ ganv_node_move_to(pnode,
+ x,
+ height - ganv_box_get_height(pbox));
+ last_was_input = FALSE;
+
+ ganv_canvas_for_each_edge_from(canvas, pnode,
+ ganv_edge_update_location);
+ }
+ }
+
+ ganv_box_set_width(GANV_BOX(module), m.width);
+ ganv_box_set_height(GANV_BOX(module), height);
+
+ place_title(module, GANV_VERTICAL);
+}
+
+static void
+measure_ports(GanvModule* module)
+{
+ module->widest_input = 0.0;
+ module->widest_output = 0.0;
+ FOREACH_PORT_CONST(module->ports, pi) {
+ const GanvPort* const p = (*pi);
+ const double w = ganv_port_get_natural_width(p);
+ if (p->is_input) {
+ if (w > module->widest_input) {
+ module->widest_input = w;
+ }
+ } else {
+ if (w > module->widest_output) {
+ module->widest_output = w;
+ }
+ }
+ }
+}
+
+static void
+layout(GanvNode* self)
+{
+ GanvModule* module = GANV_MODULE(self);
+ GanvNode* node = GANV_NODE(self);
+ GanvCanvas* canvas = GANV_CANVAS(GNOME_CANVAS_ITEM(module)->canvas);
+
+ double label_w = 0.0;
+ double label_h = 0.0;
+ if (node->label) {
+ g_object_get(node->label,
+ "width", &label_w,
+ "height", &label_h,
+ NULL);
+ }
+
+ ganv_box_set_width(&module->box, label_w + (MODULE_LABEL_PAD * 2.0));
+ ganv_box_set_height(&module->box, label_h);
+
+ if (module->port_size_changed) {
+ measure_ports(module);
+ module->port_size_changed = FALSE;
+ }
+
+ switch (canvas->direction) {
+ case GANV_HORIZONTAL:
+ resize_horiz(module);
+ break;
+ case GANV_VERTICAL:
+ resize_vert(module);
+ break;
+ }
+
+ module->must_resize = FALSE;
+}
+
+static void
+ganv_module_resize(GanvNode* self)
+{
+ GanvModule* module = GANV_MODULE(self);
+
+ if (module->must_resize) {
+ layout(self);
+ }
+
+ if (parent_class->parent_class.resize) {
+ parent_class->parent_class.resize(self);
+ }
+}
+
+static void
+ganv_module_update(GnomeCanvasItem* item,
+ double* affine,
+ ArtSVP* clip_path,
+ int flags)
+{
+ GanvNode* node = GANV_NODE(item);
+ GanvModule* module = GANV_MODULE(item);
+ if (module->must_resize) {
+ layout(node);
+ }
+
+ GnomeCanvasItemClass* item_class = GNOME_CANVAS_ITEM_CLASS(parent_class);
+ item_class->update(item, affine, clip_path, flags);
+}
+
+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->ports, p) {
+ ganv_node_move(GANV_NODE(*p), 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->ports, p) {
+ ganv_node_move(GANV_NODE(*p), 0.0, 0.0);
+ }
+}
+
+static void
+ganv_module_class_init(GanvModuleClass* class)
+{
+ GObjectClass* gobject_class = (GObjectClass*)class;
+ GtkObjectClass* object_class = (GtkObjectClass*)class;
+ GnomeCanvasItemClass* item_class = (GnomeCanvasItemClass*)class;
+ GanvNodeClass* node_class = (GanvNodeClass*)class;
+
+ parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(class));
+
+ gobject_class->set_property = ganv_module_set_property;
+ gobject_class->get_property = ganv_module_get_property;
+
+ object_class->destroy = ganv_module_destroy;
+
+ item_class->update = ganv_module_update;
+
+ node_class->move = ganv_module_move;
+ node_class->move_to = ganv_module_move_to;
+ node_class->resize = ganv_module_resize;
+}
+
+void
+ganv_module_add_port(GanvModule* module,
+ GanvPort* port)
+{
+ const double width = ganv_port_get_natural_width(port);
+ if (port->is_input && width > module->widest_input) {
+ module->widest_input = width;
+ module->must_resize = TRUE;
+ } else if (!port->is_input && width > module->widest_output) {
+ module->widest_output = width;
+ module->must_resize = TRUE;
+ }
+
+#if 0
+
+ double port_x, port_y;
+
+ // Place vertically
+ if (canvas()->direction() == Canvas::HORIZONTAL) {
+ if (gobj()->ports->len != 0) {
+ const Port* const last_port = *back();
+ port_y = last_port->get_y() + last_port->get_height() + 1;
+ } else {
+ port_y = 2.0 + title_height();
+ }
+ } else {
+ if (p->is_input()) {
+ port_y = 0.0;
+ } else {
+ port_y = get_height() - get_empty_port_depth();
+ }
+ }
+
+ // Place horizontally
+ Metrics m;
+ calculate_metrics(m);
+
+ set_width(m.width);
+ if (p->is_input()) {
+ p->set_width(m.input_width);
+ port_x = 0;
+ } else {
+ p->set_width(m.output_width);
+ port_x = m.width - p->get_width();
+ }
+
+ p->move_to(port_x, port_y);
+
+ g_ptr_array_add(gobj()->ports, p->gobj());
+ if (canvas()->direction() == Canvas::HORIZONTAL) {
+ set_height(p->get_y() + p->get_height() + 1);
+ }
+
+ place_title();
+#endif
+ module->must_resize = TRUE;
+
+ g_ptr_array_add(module->ports, port);
+ //if (canvas()->direction() == Canvas::HORIZONTAL) {
+ // set_height(p->get_y() + p->get_height() + 1);
+ //}
+
+ GanvCanvas* canvas = GANV_CANVAS(
+ GNOME_CANVAS_ITEM(module)->canvas);
+
+ place_title(module, canvas->direction);
+
+}
+
+void
+ganv_module_remove_port(GanvModule* module,
+ GanvPort* port)
+{
+#if 0
+ gboolean removed = g_ptr_array_remove(gobj()->ports, port->gobj());
+ if (removed) {
+ // Find new widest input or output, if necessary
+ if (port->is_input() && port->get_width() >= _widest_input) {
+ _widest_input = 0;
+ FOREACH_PORT_CONST(gobj()->ports, i) {
+ const Port* const p = (*i);
+ if (p->is_input() && p->get_width() >= _widest_input) {
+ _widest_input = p->get_width();
+ }
+ }
+ } else if (port->is_output() && port->get_width() >= _widest_output) {
+ _widest_output = 0;
+ FOREACH_PORT_CONST(gobj()->ports, i) {
+ const Port* const p = (*i);
+ if (p->is_output() && p->get_width() >= _widest_output) {
+ _widest_output = p->get_width();
+ }
+ }
+ }
+
+ _must_resize = true;
+ gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(_gobj));
+ } else {
+ std::cerr << "Failed to find port to remove" << std::endl;
+ }
+#endif
+}
+
+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_CANVAS(
+ GNOME_CANVAS_ITEM(module)->canvas);
+
+ return ganv_canvas_get_font_size(canvas);
+}
+
+void
+ganv_module_set_icon(GanvModule* module,
+ GdkPixbuf* icon)
+{
+ if (module->icon_box) {
+ gtk_object_destroy(GTK_OBJECT(module->icon_box));
+ module->icon_box = NULL;
+ }
+
+ if (icon) {
+ module->icon_box = gnome_canvas_item_new(
+ GNOME_CANVAS_GROUP(module),
+ gnome_canvas_pixbuf_get_type(),
+ "x", 8.0,
+ "y", 10.0,
+ "pixbuf", icon,
+ NULL);
+
+ const double icon_w = gdk_pixbuf_get_width(icon);
+ const double icon_h = gdk_pixbuf_get_height(icon);
+ const double scale = MODULE_ICON_SIZE / ((icon_w > icon_h)
+ ? icon_w : icon_h);
+ const double scale_trans[6] = {
+ scale, 0.0, 0.0,
+ scale, 0.0, 0.0
+ };
+
+ gnome_canvas_item_affine_relative(module->icon_box, scale_trans);
+ gnome_canvas_item_raise_to_top(module->icon_box);
+ gnome_canvas_item_show(module->icon_box);
+ }
+ module->must_resize = TRUE;
+}
+
+static void
+on_embed_size_request(GtkWidget* widget,
+ GtkRequisition* r,
+ void* user_data)
+{
+ GanvModule* module = GANV_MODULE(user_data);
+ if (module->embed_width == r->width && module->embed_height == r->height) {
+ return;
+ }
+
+ module->embed_width = r->width;
+ module->embed_height = r->height;
+
+ module->must_resize = TRUE;
+
+ GtkAllocation allocation;
+ allocation.width = r->width;
+ allocation.height = r->width;
+
+ gtk_widget_size_allocate(widget, &allocation);
+ gnome_canvas_item_set(module->embed_item,
+ "width", (double)r->width,
+ "height", (double)r->height,
+ NULL);
+}
+
+void
+ganv_module_embed(GanvModule* module,
+ GtkWidget* widget)
+{
+ if (module->embed_item) {
+ gtk_object_destroy(GTK_OBJECT(module->embed_item));
+ module->embed_item = NULL;
+ }
+
+ if (!widget) {
+ module->embed_width = 0;
+ module->embed_height = 0;
+ module->must_resize = TRUE;
+ return;
+ }
+
+ double title_w, title_h;
+ title_size(module, &title_w, &title_h);
+
+ const double y = 4.0 + title_h;
+ module->embed_item = gnome_canvas_item_new(
+ GNOME_CANVAS_GROUP(module),
+ gnome_canvas_widget_get_type(),
+ "x", 2.0,
+ "y", y,
+ "widget", widget,
+ NULL);
+
+ gtk_widget_show_all(widget);
+
+ GtkRequisition r;
+ gtk_widget_size_request(widget, &r);
+ on_embed_size_request(widget, &r, module);
+
+ gnome_canvas_item_show(module->embed_item);
+ gnome_canvas_item_raise_to_top(module->embed_item);
+
+ g_signal_connect(widget, "size-request",
+ G_CALLBACK(on_embed_size_request), module);
+
+ layout(GANV_NODE(module));
+ gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(module));
+}
+
+void
+ganv_module_for_each_port(GanvModule* module,
+ GanvPortFunction f,
+ void* data)
+{
+ const int len = module->ports->len;
+ GanvPort** copy = (GanvPort**)malloc(sizeof(GanvPort*) * len);
+ memcpy(copy, module->ports->pdata, sizeof(GanvPort*) * len);
+
+ for (int i = 0; i < len; ++i) {
+ f(copy[i], data);
+ }
+
+ free(copy);
+}
+