/* This file is part of Ganv. * Copyright 2007-2016 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 "ganv-private.h" #include "ganv/box.h" #include "ganv/canvas.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 "ganv/widget.h" #include #include #include #include #include #include #include #include #include #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 = (GanvModulePrivate*)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) { (void)value; 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) { (void)value; 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 = 0.0; double title_h = 0.0; 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 = 0.0; double title_h = 0.0; 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 = 0.0; double title_h = 0.0; 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 = 0.0; double title_h = 0.0; 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 = 0.0; double title_h = 0.0; 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); }