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