1 /* This file is part of Ganv.
2 * Copyright 2007-2016 David Robillard <http://drobilla.net>
3 *
4 * Ganv is free software: you can redistribute it and/or modify it under the
5 * terms of the GNU General Public License as published by the Free Software
6 * Foundation, either version 3 of the License, or any later version.
7 *
8 * Ganv is distributed in the hope that it will be useful, but WITHOUT ANY
9 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
11 *
12 * You should have received a copy of the GNU General Public License along
13 * with Ganv. If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include "ganv-private.h"
17
18 #include "ganv/box.h"
19 #include "ganv/canvas.h"
20 #include "ganv/item.h"
21 #include "ganv/module.h"
22 #include "ganv/node.h"
23 #include "ganv/port.h"
24 #include "ganv/text.h"
25 #include "ganv/types.h"
26 #include "ganv/widget.h"
27
28 #include <cairo.h>
29 #include <glib-object.h>
30 #include <glib.h>
31 #include <gobject/gclosure.h>
32 #include <gtk/gtk.h>
33
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #define FOREACH_PORT(ports, i) \
40 for (GanvPort** i = (GanvPort**)ports->pdata; \
41 i != (GanvPort**)ports->pdata + ports->len; ++i)
42
43 #define FOREACH_PORT_CONST(ports, i) \
44 for (const GanvPort** i = (const GanvPort**)ports->pdata; \
45 i != (const GanvPort**)ports->pdata + ports->len; ++i)
46
47 static const double PAD = 2.0;
48 static const double EDGE_PAD = 5.0;
49 static const double MODULE_LABEL_PAD = 2.0;
50
51 G_DEFINE_TYPE_WITH_CODE(GanvModule, ganv_module, GANV_TYPE_BOX,
52 G_ADD_PRIVATE(GanvModule))
53
54 static GanvBoxClass* parent_class;
55
56 enum {
57 PROP_0
58 };
59
60 static void
ganv_module_init(GanvModule * module)61 ganv_module_init(GanvModule* module)
62 {
63 GanvModulePrivate* impl =
64 (GanvModulePrivate*)ganv_module_get_instance_private(module);
65
66 module->impl = impl;
67
68 GANV_NODE(module)->impl->can_head = FALSE;
69 GANV_NODE(module)->impl->can_tail = FALSE;
70
71 impl->ports = g_ptr_array_new();
72 impl->embed_item = NULL;
73 impl->embed_width = 0;
74 impl->embed_height = 0;
75 impl->widest_input = 0.0;
76 impl->widest_output = 0.0;
77 impl->must_reorder = FALSE;
78 }
79
80 static void
ganv_module_destroy(GtkObject * object)81 ganv_module_destroy(GtkObject* object)
82 {
83 g_return_if_fail(object != NULL);
84 g_return_if_fail(GANV_IS_MODULE(object));
85
86 GanvModule* module = GANV_MODULE(object);
87 GanvModulePrivate* impl = module->impl;
88
89 if (impl->ports) {
90 FOREACH_PORT(impl->ports, p) {
91 g_object_unref(GTK_OBJECT(*p));
92 }
93 g_ptr_array_free(impl->ports, TRUE);
94 impl->ports = NULL;
95 }
96
97 if (impl->embed_item) {
98 g_object_unref(GTK_OBJECT(impl->embed_item));
99 impl->embed_item = NULL;
100 }
101
102 if (GTK_OBJECT_CLASS(parent_class)->destroy) {
103 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
104 }
105 }
106
107 static void
ganv_module_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)108 ganv_module_set_property(GObject* object,
109 guint prop_id,
110 const GValue* value,
111 GParamSpec* pspec)
112 {
113 (void)value;
114
115 g_return_if_fail(object != NULL);
116 g_return_if_fail(GANV_IS_MODULE(object));
117
118 switch (prop_id) {
119 default:
120 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
121 break;
122 }
123 }
124
125 static void
ganv_module_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)126 ganv_module_get_property(GObject* object,
127 guint prop_id,
128 GValue* value,
129 GParamSpec* pspec)
130 {
131 (void)value;
132
133 g_return_if_fail(object != NULL);
134 g_return_if_fail(GANV_IS_MODULE(object));
135
136 switch (prop_id) {
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
139 break;
140 }
141 }
142
143 typedef struct {
144 double embed_x;
145 double width;
146 double input_width;
147 double output_width;
148 gboolean horiz;
149 gboolean embed_between;
150 } Metrics;
151
152 static void
title_size(GanvModule * module,double * w,double * h)153 title_size(GanvModule* module, double* w, double* h)
154 {
155 if (module->box.node.impl->label) {
156 g_object_get(G_OBJECT(module->box.node.impl->label),
157 "width", w,
158 "height", h,
159 NULL);
160 } else {
161 *w = *h = 0.0;
162 }
163 }
164
165 static void
measure(GanvModule * module,Metrics * m)166 measure(GanvModule* module, Metrics* m)
167 {
168 memset(m, '\0', sizeof(Metrics));
169
170 double title_w = 0.0;
171 double title_h = 0.0;
172 title_size(module, &title_w, &title_h);
173
174 GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module));
175 GanvText* canvas_title = GANV_NODE(module)->impl->label;
176 GanvModulePrivate* impl = module->impl;
177
178 if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) {
179 double contents_width = 0.0;
180 if (canvas_title) {
181 contents_width += title_w + (2.0 * PAD);
182 }
183
184 m->embed_x = 0;
185 m->input_width = ganv_module_get_empty_port_breadth(module);
186 m->output_width = ganv_module_get_empty_port_breadth(module);
187
188 // TODO: cache this or merge with resize_right
189 unsigned n_inputs = 0;
190 unsigned n_outputs = 0;
191 FOREACH_PORT(impl->ports, pi) {
192 if ((*pi)->impl->is_input) {
193 ++n_inputs;
194 } else {
195 ++n_outputs;
196 }
197 }
198
199 const unsigned hor_ports = MAX(1, MAX(n_inputs, n_outputs));
200 const double ports_width = (2 * EDGE_PAD) +
201 ((m->input_width) * hor_ports) +
202 ((PAD + 1.0) * (hor_ports - 1));
203
204 m->width = MAX(contents_width, ports_width);
205 m->width = MAX(m->width, impl->embed_width);
206
207 if (impl->embed_item) {
208 m->width = MAX(impl->embed_width + 2.0 * PAD, m->width);
209 m->embed_x = PAD;
210 }
211 return;
212 }
213
214 // The amount of space between a port edge and the module edge (on the
215 // side that the port isn't right on the edge).
216 const double hor_pad = (canvas_title ? 10.0 : 20.0);
217
218 m->width = (canvas_title) ? title_w + 10.0 : 1.0;
219
220 // Title is wide or there is an embedded widget,
221 // put inputs and outputs beside each other
222 m->horiz = (impl->embed_item ||
223 (impl->widest_input + impl->widest_output + 10.0
224 < MAX(m->width, impl->embed_width)));
225
226 // Fit ports to module (or vice-versa)
227 m->input_width = impl->widest_input;
228 m->output_width = impl->widest_output;
229 double expand_w = (m->horiz ? (m->width / 2.0) : m->width) - hor_pad;
230 if (!impl->embed_item) {
231 m->input_width = MAX(impl->widest_input, expand_w);
232 m->output_width = MAX(impl->widest_output, expand_w);
233 }
234
235 const double widest = MAX(m->input_width, m->output_width);
236
237 if (impl->embed_item) {
238 double above_w = MAX(m->width, widest + hor_pad);
239 double between_w = MAX(m->width,
240 (m->input_width
241 + m->output_width
242 + impl->embed_width));
243
244 above_w = MAX(above_w, impl->embed_width);
245
246 // Decide where to place embedded widget if necessary)
247 if (impl->embed_width < impl->embed_height * 2.0) {
248 m->embed_between = TRUE;
249 m->width = between_w;
250 m->embed_x = m->input_width;
251 } else {
252 m->width = above_w;
253 m->embed_x = 2.0;
254 }
255 }
256
257 if (!canvas_title && (impl->widest_input == 0.0
258 || impl->widest_output == 0.0)) {
259 m->width += 10.0;
260 }
261
262 m->width += 4.0;
263 m->width = MAX(m->width, widest + hor_pad);
264 }
265
266 static void
place_title(GanvModule * module,GanvDirection dir)267 place_title(GanvModule* module, GanvDirection dir)
268 {
269 GanvBox* box = GANV_BOX(module);
270 GanvText* canvas_title = GANV_NODE(module)->impl->label;
271
272 double title_w = 0.0;
273 double title_h = 0.0;
274 title_size(module, &title_w, &title_h);
275
276 if (!canvas_title) {
277 return;
278 }
279
280 GanvItem* t = GANV_ITEM(canvas_title);
281 if (dir == GANV_DIRECTION_RIGHT) {
282 t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0;
283 t->impl->y = 1.0;
284 } else {
285 t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0;
286 t->impl->y = ganv_module_get_empty_port_depth(module) + 1.0;
287 }
288 }
289
290 static void
resize_right(GanvModule * module)291 resize_right(GanvModule* module)
292 {
293 GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module));
294 GanvModulePrivate* impl = module->impl;
295
296 Metrics m;
297 measure(module, &m);
298
299 double title_w = 0.0;
300 double title_h = 0.0;
301 title_size(module, &title_w, &title_h);
302
303 // Basic height contains title
304 double header_height = title_h ? (3.0 + title_h) : EDGE_PAD;
305
306 if (impl->embed_item) {
307 ganv_item_set(impl->embed_item,
308 "x", (double)m.embed_x,
309 "y", header_height,
310 NULL);
311 }
312
313 // Actually set width and height
314 ganv_box_set_width(GANV_BOX(module), m.width);
315
316 // Offset ports below embedded widget
317 if (!m.embed_between) {
318 header_height += impl->embed_height;
319 }
320
321 // Move ports to appropriate locations
322 double in_y = header_height;
323 double out_y = header_height;
324 FOREACH_PORT(impl->ports, pi) {
325 GanvPort* const p = (*pi);
326 GanvBox* const pbox = GANV_BOX(p);
327 GanvNode* const pnode = GANV_NODE(p);
328 const double h = ganv_box_get_height(pbox);
329
330 // Offset to shift ports to make borders line up
331 const double border_off = (GANV_NODE(module)->impl->border_width -
332 pnode->impl->border_width) / 2.0;
333
334 if (p->impl->is_input) {
335 ganv_node_move_to(pnode, -border_off, in_y + 1.0);
336 ganv_box_set_width(pbox, m.input_width);
337 in_y += h + pnode->impl->border_width + 1.0;
338
339 ganv_canvas_for_each_edge_to(
340 canvas, pnode,
341 (GanvEdgeFunc)ganv_edge_update_location, NULL);
342 } else {
343 ganv_node_move_to(pnode, m.width - m.output_width + border_off, out_y + 1.0);
344 ganv_box_set_width(pbox, m.output_width);
345 out_y += h + pnode->impl->border_width + 1.0;
346
347 ganv_canvas_for_each_edge_from(
348 canvas, pnode,
349 (GanvEdgeFunc)ganv_edge_update_location, NULL);
350 }
351
352 if (!m.horiz) {
353 in_y = MAX(in_y, out_y);
354 out_y = MAX(in_y, out_y);
355 }
356 }
357
358 double height = MAX(in_y, out_y) + EDGE_PAD;
359 if (impl->embed_item && m.embed_between)
360 height = MAX(height, impl->embed_height + header_height + 2.0);
361
362 ganv_box_set_height(GANV_BOX(module), height);
363
364 place_title(module, GANV_DIRECTION_RIGHT);
365 }
366
367 static void
resize_down(GanvModule * module)368 resize_down(GanvModule* module)
369 {
370 GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module));
371 GanvModulePrivate* impl = module->impl;
372
373 Metrics m;
374 measure(module, &m);
375
376 double title_w = 0.0;
377 double title_h = 0.0;
378 title_size(module, &title_w, &title_h);
379
380 const double port_depth = ganv_module_get_empty_port_depth(module);
381 const double port_breadth = ganv_module_get_empty_port_breadth(module);
382
383 if (impl->embed_item) {
384 ganv_item_set(impl->embed_item,
385 "x", (double)m.embed_x,
386 "y", port_depth + title_h,
387 NULL);
388 }
389
390 const double height = PAD + title_h
391 + impl->embed_height + (port_depth * 2.0);
392
393 // Move ports to appropriate locations
394 guint in_count = 0;
395 guint out_count = 0;
396 double in_x = 0.0;
397 double out_x = 0.0;
398 FOREACH_PORT(impl->ports, pi) {
399 GanvPort* const p = (*pi);
400 GanvBox* const pbox = GANV_BOX(p);
401 GanvNode* const pnode = GANV_NODE(p);
402 ganv_box_set_width(pbox, port_breadth);
403 ganv_box_set_height(pbox, port_depth);
404
405 // Offset to shift ports to make borders line up
406 const double border_off = (GANV_NODE(module)->impl->border_width -
407 pnode->impl->border_width) / 2.0;
408
409 if (p->impl->is_input) {
410 in_x = EDGE_PAD + (in_count++ * (port_breadth + PAD + 1.0));
411 ganv_node_move_to(pnode, in_x, -border_off);
412 ganv_canvas_for_each_edge_to(
413 canvas, pnode,
414 (GanvEdgeFunc)ganv_edge_update_location, NULL);
415 } else {
416 out_x = EDGE_PAD + (out_count++ * (port_breadth + PAD + 1.0));
417 ganv_node_move_to(pnode, out_x, height - port_depth + border_off);
418 ganv_canvas_for_each_edge_from(
419 canvas, pnode,
420 (GanvEdgeFunc)ganv_edge_update_location, NULL);
421 }
422 }
423
424 ganv_box_set_height(GANV_BOX(module), height);
425 ganv_box_set_width(GANV_BOX(module), m.width);
426 place_title(module, GANV_DIRECTION_DOWN);
427 }
428
429 static void
measure_ports(GanvModule * module)430 measure_ports(GanvModule* module)
431 {
432 GanvModulePrivate* impl = module->impl;
433
434 impl->widest_input = 0.0;
435 impl->widest_output = 0.0;
436 FOREACH_PORT_CONST(impl->ports, pi) {
437 const GanvPort* const p = (*pi);
438 const double w = ganv_port_get_natural_width(p);
439 if (p->impl->is_input) {
440 if (w > impl->widest_input) {
441 impl->widest_input = w;
442 }
443 } else {
444 if (w > impl->widest_output) {
445 impl->widest_output = w;
446 }
447 }
448 }
449 }
450
451 static void
ganv_module_resize(GanvNode * self)452 ganv_module_resize(GanvNode* self)
453 {
454 GanvModule* module = GANV_MODULE(self);
455 GanvNode* node = GANV_NODE(self);
456 GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module));
457
458 double label_w = 0.0;
459 double label_h = 0.0;
460 if (node->impl->label) {
461 g_object_get(node->impl->label,
462 "width", &label_w,
463 "height", &label_h,
464 NULL);
465 }
466
467 measure_ports(module);
468
469 ganv_box_set_width(GANV_BOX(module), label_w + (MODULE_LABEL_PAD * 2.0));
470 ganv_box_set_height(GANV_BOX(module), label_h);
471
472 switch (ganv_canvas_get_direction(canvas)) {
473 case GANV_DIRECTION_RIGHT:
474 resize_right(module);
475 break;
476 case GANV_DIRECTION_DOWN:
477 resize_down(module);
478 break;
479 }
480
481 if (GANV_NODE_CLASS(parent_class)->resize) {
482 GANV_NODE_CLASS(parent_class)->resize(self);
483 }
484 }
485
486 static void
ganv_module_redraw_text(GanvNode * self)487 ganv_module_redraw_text(GanvNode* self)
488 {
489 FOREACH_PORT(GANV_MODULE(self)->impl->ports, p) {
490 ganv_node_redraw_text(GANV_NODE(*p));
491 }
492
493 if (parent_class->parent_class.redraw_text) {
494 parent_class->parent_class.redraw_text(self);
495 }
496 }
497
498 static void
ganv_module_add_port(GanvModule * module,GanvPort * port)499 ganv_module_add_port(GanvModule* module,
500 GanvPort* port)
501 {
502 GanvModulePrivate* impl = module->impl;
503
504 // Update widest input/output measurements if necessary
505 const double width = ganv_port_get_natural_width(port);
506 if (port->impl->is_input && width > impl->widest_input) {
507 impl->widest_input = width;
508 } else if (!port->impl->is_input && width > impl->widest_output) {
509 impl->widest_output = width;
510 }
511
512 // Add to port array
513 g_ptr_array_add(impl->ports, port);
514
515 // Request update with resize and reorder
516 GANV_NODE(module)->impl->must_resize = TRUE;
517 impl->must_reorder = TRUE;
518 }
519
520 static void
ganv_module_remove_port(GanvModule * module,GanvPort * port)521 ganv_module_remove_port(GanvModule* module,
522 GanvPort* port)
523 {
524 gboolean removed = g_ptr_array_remove(module->impl->ports, port);
525 if (removed) {
526 const double width = ganv_box_get_width(GANV_BOX(port));
527 // Find new widest input or output, if necessary
528 if (port->impl->is_input && width >= module->impl->widest_input) {
529 module->impl->widest_input = 0;
530 FOREACH_PORT_CONST(module->impl->ports, i) {
531 const GanvPort* const p = (*i);
532 const double w = ganv_box_get_width(GANV_BOX(p));
533 if (p->impl->is_input && w >= module->impl->widest_input) {
534 module->impl->widest_input = w;
535 }
536 }
537 } else if (!port->impl->is_input && width >= module->impl->widest_output) {
538 module->impl->widest_output = 0;
539 FOREACH_PORT_CONST(module->impl->ports, i) {
540 const GanvPort* const p = (*i);
541 const double w = ganv_box_get_width(GANV_BOX(p));
542 if (!p->impl->is_input && w >= module->impl->widest_output) {
543 module->impl->widest_output = w;
544 }
545 }
546 }
547
548 GANV_NODE(module)->impl->must_resize = TRUE;
549 } else {
550 fprintf(stderr, "Failed to find port to remove\n");
551 }
552 }
553
554 static void
ganv_module_add(GanvItem * item,GanvItem * child)555 ganv_module_add(GanvItem* item, GanvItem* child)
556 {
557 if (GANV_IS_PORT(child)) {
558 ganv_module_add_port(GANV_MODULE(item), GANV_PORT(child));
559 }
560 ganv_item_request_update(item);
561 if (GANV_ITEM_CLASS(parent_class)->add) {
562 GANV_ITEM_CLASS(parent_class)->add(item, child);
563 }
564 }
565
566 static void
ganv_module_remove(GanvItem * item,GanvItem * child)567 ganv_module_remove(GanvItem* item, GanvItem* child)
568 {
569 if (GANV_IS_PORT(child)) {
570 ganv_module_remove_port(GANV_MODULE(item), GANV_PORT(child));
571 }
572 ganv_item_request_update(item);
573 if (GANV_ITEM_CLASS(parent_class)->remove) {
574 GANV_ITEM_CLASS(parent_class)->remove(item, child);
575 }
576 }
577
578 static int
ptr_sort(const GanvPort ** a,const GanvPort ** b,const PortOrderCtx * ctx)579 ptr_sort(const GanvPort** a, const GanvPort** b, const PortOrderCtx* ctx)
580 {
581 return ctx->port_cmp(*a, *b, ctx->data);
582 }
583
584 static void
ganv_module_update(GanvItem * item,int flags)585 ganv_module_update(GanvItem* item, int flags)
586 {
587 GanvModule* module = GANV_MODULE(item);
588 GanvCanvas* canvas = ganv_item_get_canvas(item);
589
590 if (module->impl->must_reorder) {
591 // Sort ports array
592 PortOrderCtx ctx = ganv_canvas_get_port_order(canvas);
593 if (ctx.port_cmp) {
594 g_ptr_array_sort_with_data(module->impl->ports,
595 (GCompareDataFunc)ptr_sort,
596
597 &ctx);
598 }
599 module->impl->must_reorder = FALSE;
600 }
601
602 if (module->impl->embed_item) {
603 // Kick the embedded item to update position if we have moved
604 ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0);
605 }
606
607 FOREACH_PORT(module->impl->ports, p) {
608 ganv_item_invoke_update(GANV_ITEM(*p), flags);
609 }
610
611 if (module->impl->embed_item) {
612 ganv_item_invoke_update(GANV_ITEM(module->impl->embed_item), flags);
613 }
614
615 GANV_ITEM_CLASS(parent_class)->update(item, flags);
616 }
617
618 static void
ganv_module_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)619 ganv_module_draw(GanvItem* item,
620 cairo_t* cr, double cx, double cy, double cw, double ch)
621 {
622 GanvNode* node = GANV_NODE(item);
623 GanvModule* module = GANV_MODULE(item);
624
625 // Draw box
626 if (GANV_ITEM_CLASS(parent_class)->draw) {
627 (*GANV_ITEM_CLASS(parent_class)->draw)(item, cr, cx, cy, cw, ch);
628 }
629
630 // Draw label
631 if (node->impl->label) {
632 GanvItem* label_item = GANV_ITEM(node->impl->label);
633 GANV_ITEM_GET_CLASS(label_item)->draw(label_item, cr, cx, cy, cw, ch);
634 }
635
636 // Draw ports
637 FOREACH_PORT(module->impl->ports, p) {
638 GANV_ITEM_GET_CLASS(GANV_ITEM(*p))->draw(
639 GANV_ITEM(*p), cr, cx, cy, cw, ch);
640 }
641
642 // Draw embed item
643 if (module->impl->embed_item) {
644 GANV_ITEM_GET_CLASS(module->impl->embed_item)->draw(
645 module->impl->embed_item, cr, cx, cy, cw, ch);
646 }
647 }
648
649 static void
ganv_module_move_to(GanvNode * node,double x,double y)650 ganv_module_move_to(GanvNode* node,
651 double x,
652 double y)
653 {
654 GanvModule* module = GANV_MODULE(node);
655 GANV_NODE_CLASS(parent_class)->move_to(node, x, y);
656 FOREACH_PORT(module->impl->ports, p) {
657 ganv_node_move(GANV_NODE(*p), 0.0, 0.0);
658 }
659 if (module->impl->embed_item) {
660 ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0);
661 }
662 }
663
664 static void
ganv_module_move(GanvNode * node,double dx,double dy)665 ganv_module_move(GanvNode* node,
666 double dx,
667 double dy)
668 {
669 GanvModule* module = GANV_MODULE(node);
670 GANV_NODE_CLASS(parent_class)->move(node, dx, dy);
671 FOREACH_PORT(module->impl->ports, p) {
672 ganv_node_move(GANV_NODE(*p), 0.0, 0.0);
673 }
674 if (module->impl->embed_item) {
675 ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0);
676 }
677 }
678
679 static double
ganv_module_point(GanvItem * item,double x,double y,GanvItem ** actual_item)680 ganv_module_point(GanvItem* item, double x, double y, GanvItem** actual_item)
681 {
682 GanvModule* module = GANV_MODULE(item);
683
684 double d = GANV_ITEM_CLASS(parent_class)->point(item, x, y, actual_item);
685
686 if (!*actual_item) {
687 // Point is not inside module at all, no point in checking children
688 return d;
689 }
690
691 FOREACH_PORT(module->impl->ports, p) {
692 GanvItem* const port = GANV_ITEM(*p);
693
694 *actual_item = NULL;
695 d = GANV_ITEM_GET_CLASS(port)->point(
696 port, x - port->impl->x, y - port->impl->y, actual_item);
697
698 if (*actual_item) {
699 // Point is inside a port
700 return d;
701 }
702 }
703
704 // Point is inside module, but not a child port
705 *actual_item = item;
706 return 0.0;
707 }
708
709 static void
ganv_module_class_init(GanvModuleClass * klass)710 ganv_module_class_init(GanvModuleClass* klass)
711 {
712 GObjectClass* gobject_class = (GObjectClass*)klass;
713 GtkObjectClass* object_class = (GtkObjectClass*)klass;
714 GanvItemClass* item_class = (GanvItemClass*)klass;
715 GanvNodeClass* node_class = (GanvNodeClass*)klass;
716
717 parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass));
718
719 gobject_class->set_property = ganv_module_set_property;
720 gobject_class->get_property = ganv_module_get_property;
721
722 object_class->destroy = ganv_module_destroy;
723
724 item_class->add = ganv_module_add;
725 item_class->remove = ganv_module_remove;
726 item_class->update = ganv_module_update;
727 item_class->draw = ganv_module_draw;
728 item_class->point = ganv_module_point;
729
730 node_class->move = ganv_module_move;
731 node_class->move_to = ganv_module_move_to;
732 node_class->resize = ganv_module_resize;
733 node_class->redraw_text = ganv_module_redraw_text;
734 }
735
736 GanvModule*
ganv_module_new(GanvCanvas * canvas,const char * first_property_name,...)737 ganv_module_new(GanvCanvas* canvas,
738 const char* first_property_name, ...)
739 {
740 GanvModule* module = GANV_MODULE(
741 g_object_new(ganv_module_get_type(), "canvas", canvas, NULL));
742
743 va_list args;
744 va_start(args, first_property_name);
745 g_object_set_valist(G_OBJECT(module), first_property_name, args);
746 va_end(args);
747
748 return module;
749 }
750
751 guint
ganv_module_num_ports(const GanvModule * module)752 ganv_module_num_ports(const GanvModule* module)
753 {
754 return module->impl->ports ? module->impl->ports->len : 0;
755 }
756
757 GanvPort*
ganv_module_get_port(GanvModule * module,guint index)758 ganv_module_get_port(GanvModule* module,
759 guint index)
760 {
761 return (GanvPort*)g_ptr_array_index(module->impl->ports, index);
762 }
763
764 double
ganv_module_get_empty_port_breadth(const GanvModule * module)765 ganv_module_get_empty_port_breadth(const GanvModule* module)
766 {
767 return ganv_module_get_empty_port_depth(module) * 2.0;
768 }
769
770 double
ganv_module_get_empty_port_depth(const GanvModule * module)771 ganv_module_get_empty_port_depth(const GanvModule* module)
772 {
773 GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module));
774
775 return ganv_canvas_get_font_size(canvas) * 1.1;
776 }
777
778 static void
on_embed_size_request(GtkWidget * widget,GtkRequisition * r,void * user_data)779 on_embed_size_request(GtkWidget* widget,
780 GtkRequisition* r,
781 void* user_data)
782 {
783 GanvModule* module = GANV_MODULE(user_data);
784 GanvModulePrivate* impl = module->impl;
785 if (impl->embed_width == r->width && impl->embed_height == r->height) {
786 return;
787 }
788
789 impl->embed_width = r->width;
790 impl->embed_height = r->height;
791 GANV_NODE(module)->impl->must_resize = TRUE;
792
793 GtkAllocation allocation;
794 allocation.width = r->width;
795 allocation.height = r->width;
796
797 gtk_widget_size_allocate(widget, &allocation);
798 ganv_item_set(impl->embed_item,
799 "width", (double)r->width,
800 "height", (double)r->height,
801 NULL);
802 }
803
804 void
ganv_module_embed(GanvModule * module,GtkWidget * widget)805 ganv_module_embed(GanvModule* module,
806 GtkWidget* widget)
807 {
808 GanvModulePrivate* impl = module->impl;
809 if (!widget && !impl->embed_item) {
810 return;
811 }
812
813 if (impl->embed_item) {
814 // Free existing embedded widget
815 gtk_object_destroy(GTK_OBJECT(impl->embed_item));
816 impl->embed_item = NULL;
817 }
818
819 if (!widget) {
820 // Removing an existing embedded widget
821 impl->embed_width = 0;
822 impl->embed_height = 0;
823 GANV_NODE(module)->impl->must_resize = TRUE;
824 ganv_item_request_update(GANV_ITEM(module));
825 return;
826 }
827
828 double title_w = 0.0;
829 double title_h = 0.0;
830 title_size(module, &title_w, &title_h);
831
832 impl->embed_item = ganv_item_new(
833 GANV_ITEM(module),
834 ganv_widget_get_type(),
835 "x", 2.0,
836 "y", 4.0 + title_h,
837 "widget", widget,
838 NULL);
839
840 GtkRequisition r;
841 gtk_widget_show_all(widget);
842 gtk_widget_size_request(widget, &r);
843 on_embed_size_request(widget, &r, module);
844 ganv_item_show(impl->embed_item);
845
846 g_signal_connect(widget, "size-request",
847 G_CALLBACK(on_embed_size_request), module);
848
849 GANV_NODE(module)->impl->must_resize = TRUE;
850 ganv_item_request_update(GANV_ITEM(module));
851 }
852
853 void
ganv_module_set_direction(GanvModule * module,GanvDirection direction)854 ganv_module_set_direction(GanvModule* module,
855 GanvDirection direction)
856 {
857 FOREACH_PORT(module->impl->ports, p) {
858 ganv_port_set_direction(*p, direction);
859 }
860 GANV_NODE(module)->impl->must_resize = TRUE;
861 ganv_item_request_update(GANV_ITEM(module));
862 }
863
864 void
ganv_module_for_each_port(GanvModule * module,GanvPortFunc f,void * data)865 ganv_module_for_each_port(GanvModule* module,
866 GanvPortFunc f,
867 void* data)
868 {
869 GanvModulePrivate* impl = module->impl;
870 const int len = impl->ports->len;
871 GanvPort** copy = (GanvPort**)malloc(sizeof(GanvPort*) * len);
872 memcpy(copy, impl->ports->pdata, sizeof(GanvPort*) * len);
873
874 for (int i = 0; i < len; ++i) {
875 f(copy[i], data);
876 }
877
878 free(copy);
879 }
880