1 /* This file is part of Ganv.
2  * Copyright 2007-2015 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 "boilerplate.h"
17 #include "color.h"
18 #include "ganv-private.h"
19 #include "gettext.h"
20 
21 #include "ganv/box.h"
22 #include "ganv/canvas.h"
23 #include "ganv/edge.h"
24 #include "ganv/item.h"
25 #include "ganv/module.h"
26 #include "ganv/node.h"
27 #include "ganv/port.h"
28 #include "ganv/text.h"
29 #include "ganv/types.h"
30 
31 #include <cairo.h>
32 #include <gdk/gdk.h>
33 #include <glib-object.h>
34 #include <glib.h>
35 #include <gtk/gtk.h>
36 
37 #include <math.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 
41 static const double PORT_LABEL_HPAD = 4.0;
42 static const double PORT_LABEL_VPAD = 1.0;
43 
44 static void
45 ganv_port_update_control_slider(GanvPort* port, float value, gboolean force);
46 
47 G_DEFINE_TYPE_WITH_CODE(GanvPort, ganv_port, GANV_TYPE_BOX,
48                         G_ADD_PRIVATE(GanvPort))
49 
50 static GanvBoxClass* parent_class;
51 
52 enum {
53 	PROP_0,
54 	PROP_IS_INPUT,
55 	PROP_IS_CONTROLLABLE
56 };
57 
58 enum {
59 	PORT_VALUE_CHANGED,
60 	PORT_LAST_SIGNAL
61 };
62 
63 static guint port_signals[PORT_LAST_SIGNAL];
64 
65 static void
ganv_port_init(GanvPort * port)66 ganv_port_init(GanvPort* port)
67 {
68 	port->impl = (GanvPortPrivate*)ganv_port_get_instance_private(port);
69 
70 	port->impl->control         = NULL;
71 	port->impl->value_label     = NULL;
72 	port->impl->is_input        = TRUE;
73 	port->impl->is_controllable = FALSE;
74 }
75 
76 static void
ganv_port_destroy(GtkObject * object)77 ganv_port_destroy(GtkObject* object)
78 {
79 	g_return_if_fail(object != NULL);
80 	g_return_if_fail(GANV_IS_PORT(object));
81 
82 	GanvItem*   item   = GANV_ITEM(object);
83 	GanvPort*   port   = GANV_PORT(object);
84 	GanvCanvas* canvas = ganv_item_get_canvas(item);
85 	if (canvas) {
86 		if (port->impl->is_input) {
87 			ganv_canvas_for_each_edge_to(
88 				canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL);
89 		} else {
90 			ganv_canvas_for_each_edge_from(
91 				canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL);
92 		}
93 	}
94 
95 	if (GTK_OBJECT_CLASS(parent_class)->destroy) {
96 		(*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
97 	}
98 }
99 
100 static void
ganv_port_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)101 ganv_port_set_property(GObject*      object,
102                        guint         prop_id,
103                        const GValue* value,
104                        GParamSpec*   pspec)
105 {
106 	g_return_if_fail(object != NULL);
107 	g_return_if_fail(GANV_IS_PORT(object));
108 
109 	GanvPort* port = GANV_PORT(object);
110 
111 	switch (prop_id) {
112 		SET_CASE(IS_INPUT, boolean, port->impl->is_input)
113 		SET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable)
114 	default:
115 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
116 		break;
117 	}
118 }
119 
120 static void
ganv_port_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)121 ganv_port_get_property(GObject*    object,
122                        guint       prop_id,
123                        GValue*     value,
124                        GParamSpec* pspec)
125 {
126 	g_return_if_fail(object != NULL);
127 	g_return_if_fail(GANV_IS_PORT(object));
128 
129 	GanvPort* port = GANV_PORT(object);
130 
131 	switch (prop_id) {
132 		GET_CASE(IS_INPUT, boolean, port->impl->is_input)
133 		GET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable)
134 	default:
135 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
136 		break;
137 	}
138 }
139 
140 static void
ganv_port_update(GanvItem * item,int flags)141 ganv_port_update(GanvItem* item, int flags)
142 {
143 	GanvPort*        port = GANV_PORT(item);
144 	GanvPortPrivate* impl = port->impl;
145 
146 	if (impl->control) {
147 		ganv_item_invoke_update(GANV_ITEM(impl->control->rect), flags);
148 	}
149 
150 	if (impl->value_label) {
151 		ganv_item_invoke_update(GANV_ITEM(port->impl->value_label), flags);
152 	}
153 
154 	GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class);
155 	item_class->update(item, flags);
156 }
157 
158 static void
ganv_port_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)159 ganv_port_draw(GanvItem* item,
160                cairo_t* cr, double cx, double cy, double cw, double ch)
161 {
162 	GanvPort*   port   = GANV_PORT(item);
163 	GanvCanvas* canvas = ganv_item_get_canvas(item);
164 
165 	// Draw Box
166 	GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class);
167 	item_class->draw(item, cr, cx, cy, cw, ch);
168 
169 	if (port->impl->control) {
170 		// Clip to port boundaries (to stay within radiused borders)
171 		cairo_save(cr);
172 		const double  pad    = GANV_NODE(port)->impl->border_width / 2.0;
173 		GanvBoxCoords coords = GANV_BOX(port)->impl->coords;
174 		ganv_item_i2w_pair(GANV_ITEM(port),
175 		                   &coords.x1, &coords.y1, &coords.x2, &coords.y2);
176 		ganv_box_path(GANV_BOX(port), cr,
177 		              coords.x1 + pad, coords.y1 + pad,
178 		              coords.x2 - pad, coords.y2 - pad,
179 		              -pad);
180 		cairo_clip(cr);
181 
182 		GanvItem* const rect = GANV_ITEM(port->impl->control->rect);
183 		GANV_ITEM_GET_CLASS(rect)->draw(rect, cr, cx, cy, cw, ch);
184 
185 		cairo_restore(cr);
186 	}
187 
188 	if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN ||
189 	    !GANV_NODE(port)->impl->show_label) {
190 		return;
191 	}
192 
193 	GanvItem* labels[2] = {
194 		GANV_ITEM(GANV_NODE(item)->impl->label),
195 		port->impl->value_label ? GANV_ITEM(port->impl->value_label) : NULL
196 	};
197 	for (int i = 0; i < 2; ++i) {
198 		if (labels[i] && (labels[i]->object.flags & GANV_ITEM_VISIBLE)) {
199 			GANV_ITEM_GET_CLASS(labels[i])->draw(
200 				labels[i], cr, cx, cy, cw, ch);
201 		}
202 	}
203 }
204 
205 static void
ganv_port_tail_vector(const GanvNode * self,const GanvNode * head,double * x,double * y,double * dx,double * dy)206 ganv_port_tail_vector(const GanvNode* self,
207                       const GanvNode* head,
208                       double*         x,
209                       double*         y,
210                       double*         dx,
211                       double*         dy)
212 {
213 	(void)head;
214 
215 	GanvPort*   port   = GANV_PORT(self);
216 	GanvItem*   item   = &port->box.node.item;
217 	GanvCanvas* canvas = ganv_item_get_canvas(item);
218 
219 	const double px           = item->impl->x;
220 	const double py           = item->impl->y;
221 	const double border_width = GANV_NODE(port)->impl->border_width;
222 
223 	switch (ganv_canvas_get_direction(canvas)) {
224 	case GANV_DIRECTION_RIGHT:
225 		*x  = px + ganv_box_get_width(&port->box) + (border_width / 2.0);
226 		*y  = py + ganv_box_get_height(&port->box) / 2.0;
227 		*dx = 1.0;
228 		*dy = 0.0;
229 		break;
230 	case GANV_DIRECTION_DOWN:
231 		*x  = px + ganv_box_get_width(&port->box) / 2.0;
232 		*y  = py + ganv_box_get_height(&port->box) + (border_width / 2.0);
233 		*dx = 0.0;
234 		*dy = 1.0;
235 		break;
236 	}
237 
238 	ganv_item_i2w(item->impl->parent, x, y);
239 }
240 
241 static void
ganv_port_head_vector(const GanvNode * self,const GanvNode * tail,double * x,double * y,double * dx,double * dy)242 ganv_port_head_vector(const GanvNode* self,
243                       const GanvNode* tail,
244                       double*         x,
245                       double*         y,
246                       double*         dx,
247                       double*         dy)
248 {
249 	(void)tail;
250 
251 	GanvPort*   port   = GANV_PORT(self);
252 	GanvItem*   item   = &port->box.node.item;
253 	GanvCanvas* canvas = ganv_item_get_canvas(item);
254 
255 	const double px           = item->impl->x;
256 	const double py           = item->impl->y;
257 	const double border_width = GANV_NODE(port)->impl->border_width;
258 
259 	switch (ganv_canvas_get_direction(canvas)) {
260 	case GANV_DIRECTION_RIGHT:
261 		*x  = px - (border_width / 2.0);
262 		*y  = py + ganv_box_get_height(&port->box) / 2.0;
263 		*dx = -1.0;
264 		*dy = 0.0;
265 		break;
266 	case GANV_DIRECTION_DOWN:
267 		*x  = px + ganv_box_get_width(&port->box) / 2.0;
268 		*y  = py - (border_width / 2.0);
269 		*dx = 0.0;
270 		*dy = -1.0;
271 		break;
272 	}
273 
274 	ganv_item_i2w(item->impl->parent, x, y);
275 }
276 
277 static void
ganv_port_place_labels(GanvPort * port)278 ganv_port_place_labels(GanvPort* port)
279 {
280 	GanvCanvas*      canvas   = ganv_item_get_canvas(GANV_ITEM(port));
281 	GanvPortPrivate* impl     = port->impl;
282 	GanvText*        label    = GANV_NODE(port)->impl->label;
283 	const double     port_w   = ganv_box_get_width(&port->box);
284 	const double     port_h   = ganv_box_get_height(&port->box);
285 	double           vlabel_w = 0.0;
286 	if (impl->value_label) {
287 		const double vlabel_h = impl->value_label->impl->coords.height;
288 		vlabel_w = impl->value_label->impl->coords.width;
289 		if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) {
290 			ganv_item_set(GANV_ITEM(impl->value_label),
291 			              "x", PORT_LABEL_HPAD,
292 			              "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD,
293 			              NULL);
294 		} else {
295 			ganv_item_set(GANV_ITEM(impl->value_label),
296 			              "x", (port_w - vlabel_w) / 2.0,
297 			              "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD,
298 			              NULL);
299 		}
300 		vlabel_w += PORT_LABEL_HPAD;
301 	}
302 	if (label) {
303 		const double label_h = label->impl->coords.height;
304 		if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) {
305 			ganv_item_set(GANV_ITEM(label),
306 			              "x", vlabel_w + PORT_LABEL_HPAD,
307 			              "y", (port_h - label_h) / 2.0 - PORT_LABEL_VPAD,
308 			              NULL);
309 		}
310 	}
311 }
312 
313 static void
ganv_port_resize(GanvNode * self)314 ganv_port_resize(GanvNode* self)
315 {
316 	GanvPort* port   = GANV_PORT(self);
317 	GanvNode* node   = GANV_NODE(self);
318 	GanvText* label  = node->impl->label;
319 	GanvText* vlabel = port->impl->value_label;
320 
321 	double label_w  = 0.0;
322 	double label_h  = 0.0;
323 	double vlabel_w = 0.0;
324 	double vlabel_h = 0.0;
325 	if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) {
326 		g_object_get(label, "width", &label_w, "height", &label_h, NULL);
327 	}
328 	if (vlabel && (GANV_ITEM(vlabel)->object.flags & GANV_ITEM_VISIBLE)) {
329 		g_object_get(vlabel, "width", &vlabel_w, "height", &vlabel_h, NULL);
330 	}
331 
332 	if (label || vlabel) {
333 		double labels_w = label_w + PORT_LABEL_HPAD * 2.0;
334 		if (vlabel_w != 0.0) {
335 			labels_w += vlabel_w + PORT_LABEL_HPAD;
336 		}
337 		ganv_box_set_width(&port->box, labels_w);
338 		ganv_box_set_height(&port->box,
339 		                    MAX(label_h, vlabel_h) + (PORT_LABEL_VPAD * 2.0));
340 
341 		ganv_port_place_labels(port);
342 	}
343 
344 	if (GANV_NODE_CLASS(parent_class)->resize) {
345 		GANV_NODE_CLASS(parent_class)->resize(self);
346 	}
347 }
348 
349 static void
ganv_port_redraw_text(GanvNode * node)350 ganv_port_redraw_text(GanvNode* node)
351 {
352 	GanvPort* port = GANV_PORT(node);
353 	if (port->impl->value_label) {
354 		ganv_text_layout(port->impl->value_label);
355 	}
356 	if (GANV_NODE_CLASS(parent_class)->redraw_text) {
357 		(*GANV_NODE_CLASS(parent_class)->redraw_text)(node);
358 	}
359 	ganv_port_place_labels(port);
360 }
361 
362 static void
ganv_port_set_width(GanvBox * box,double width)363 ganv_port_set_width(GanvBox* box,
364                     double   width)
365 {
366 	GanvPort* port = GANV_PORT(box);
367 	parent_class->set_width(box, width);
368 	if (port->impl->control) {
369 		ganv_port_update_control_slider(port, port->impl->control->value, TRUE);
370 	}
371 	ganv_port_place_labels(port);
372 }
373 
374 static void
ganv_port_set_height(GanvBox * box,double height)375 ganv_port_set_height(GanvBox* box,
376                      double   height)
377 {
378 	GanvPort* port = GANV_PORT(box);
379 	parent_class->set_height(box, height);
380 	if (port->impl->control) {
381 		ganv_item_set(GANV_ITEM(port->impl->control->rect),
382 		              "y1", box->impl->coords.border_width / 2.0,
383 		              "y2", height - box->impl->coords.border_width / 2.0,
384 		              NULL);
385 	}
386 	ganv_port_place_labels(port);
387 }
388 
389 static gboolean
ganv_port_event(GanvItem * item,GdkEvent * event)390 ganv_port_event(GanvItem* item, GdkEvent* event)
391 {
392 	GanvCanvas* canvas = ganv_item_get_canvas(item);
393 
394 	return ganv_canvas_port_event(canvas, GANV_PORT(item), event);
395 }
396 
397 static void
ganv_port_class_init(GanvPortClass * klass)398 ganv_port_class_init(GanvPortClass* klass)
399 {
400 	GObjectClass*   gobject_class = (GObjectClass*)klass;
401 	GtkObjectClass* object_class  = (GtkObjectClass*)klass;
402 	GanvItemClass*  item_class    = (GanvItemClass*)klass;
403 	GanvNodeClass*  node_class    = (GanvNodeClass*)klass;
404 	GanvBoxClass*   box_class     = (GanvBoxClass*)klass;
405 
406 	parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass));
407 
408 	gobject_class->set_property = ganv_port_set_property;
409 	gobject_class->get_property = ganv_port_get_property;
410 
411 	g_object_class_install_property(
412 		gobject_class, PROP_IS_INPUT, g_param_spec_boolean(
413 			"is-input",
414 			_("Is input"),
415 			_("Whether this port is an input, rather than an output."),
416 			0,
417 			G_PARAM_READWRITE));
418 
419 	g_object_class_install_property(
420 		gobject_class, PROP_IS_CONTROLLABLE, g_param_spec_boolean(
421 			"is-controllable",
422 			_("Is controllable"),
423 			_("Whether this port can be controlled by the user."),
424 			0,
425 			G_PARAM_READWRITE));
426 
427 	port_signals[PORT_VALUE_CHANGED]
428 	    = g_signal_new("value-changed",
429 	                   G_TYPE_FROM_CLASS(klass),
430 	                   G_SIGNAL_RUN_LAST,
431 	                   0,
432 	                   NULL, NULL,
433 	                   NULL,
434 	                   G_TYPE_NONE, 1,
435 	                   G_TYPE_DOUBLE);
436 
437 	object_class->destroy = ganv_port_destroy;
438 
439 	item_class->update = ganv_port_update;
440 	item_class->event  = ganv_port_event;
441 	item_class->draw   = ganv_port_draw;
442 
443 	node_class->tail_vector = ganv_port_tail_vector;
444 	node_class->head_vector = ganv_port_head_vector;
445 	node_class->resize      = ganv_port_resize;
446 	node_class->redraw_text = ganv_port_redraw_text;
447 
448 	box_class->set_width  = ganv_port_set_width;
449 	box_class->set_height = ganv_port_set_height;
450 }
451 
452 GanvPort*
ganv_port_new(GanvModule * module,gboolean is_input,const char * first_prop_name,...)453 ganv_port_new(GanvModule* module,
454               gboolean    is_input,
455               const char* first_prop_name, ...)
456 {
457 	GanvPort* port = GANV_PORT(g_object_new(ganv_port_get_type(), NULL));
458 
459 	port->impl->is_input = is_input;
460 
461 	GanvItem* item = GANV_ITEM(port);
462 	va_list args;
463 	va_start(args, first_prop_name);
464 	ganv_item_construct(item,
465 	                    GANV_ITEM(module),
466 	                    first_prop_name, args);
467 	va_end(args);
468 
469 	GanvBox* box = GANV_BOX(port);
470 	box->impl->coords.border_width = 1.0;
471 
472 	GanvNode* node = GANV_NODE(port);
473 	node->impl->can_tail     = !is_input;
474 	node->impl->can_head     = is_input;
475 	node->impl->draggable    = FALSE;
476 	node->impl->border_width = 2.0;
477 
478 	GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port));
479 	ganv_port_set_direction(port, ganv_canvas_get_direction(canvas));
480 
481 	return port;
482 }
483 
484 void
ganv_port_set_direction(GanvPort * port,GanvDirection direction)485 ganv_port_set_direction(GanvPort*     port,
486                         GanvDirection direction)
487 {
488 	GanvNode* node     = GANV_NODE(port);
489 	GanvBox*  box      = GANV_BOX(port);
490 	gboolean  is_input = port->impl->is_input;
491 	switch (direction) {
492 	case GANV_DIRECTION_RIGHT:
493 		box->impl->radius_tl = (is_input ? 0.0 : 5.0);
494 		box->impl->radius_tr = (is_input ? 5.0 : 0.0);
495 		box->impl->radius_br = (is_input ? 5.0 : 0.0);
496 		box->impl->radius_bl = (is_input ? 0.0 : 5.0);
497 		break;
498 	case GANV_DIRECTION_DOWN:
499 		box->impl->radius_tl = (is_input ? 0.0 : 5.0);
500 		box->impl->radius_tr = (is_input ? 0.0 : 5.0);
501 		box->impl->radius_br = (is_input ? 5.0 : 0.0);
502 		box->impl->radius_bl = (is_input ? 5.0 : 0.0);
503 		break;
504 	}
505 
506 	node->impl->must_resize = TRUE;
507 	ganv_item_request_update(GANV_ITEM(node));
508 }
509 
510 void
ganv_port_show_control(GanvPort * port)511 ganv_port_show_control(GanvPort* port)
512 {
513 	if (port->impl->control) {
514 		return;
515 	}
516 
517 	const guint  color        = 0xFFFFFF66;
518 	const double border_width = GANV_NODE(port)->impl->border_width;
519 
520 	GanvPortControl* control = (GanvPortControl*)malloc(sizeof(GanvPortControl));
521 	port->impl->control = control;
522 
523 	control->value      = 0.0f;
524 	control->min        = 0.0f;
525 	control->max        = 1.0f;
526 	control->is_toggle  = FALSE;
527 	control->is_integer = FALSE;
528 	control->rect       = GANV_BOX(
529 		ganv_item_new(GANV_ITEM(port),
530 		              ganv_box_get_type(),
531 		              "x1", border_width / 2.0,
532 		              "y1", border_width / 2.0,
533 		              "x2", 0.0,
534 		              "y2", ganv_box_get_height(&port->box) - border_width / 2.0,
535 		              "fill-color", color,
536 		              "border-color", color,
537 		              "border-width", 0.0,
538 		              "managed", TRUE,
539 		              NULL));
540 	ganv_item_show(GANV_ITEM(control->rect));
541 }
542 
543 void
ganv_port_hide_control(GanvPort * port)544 ganv_port_hide_control(GanvPort* port)
545 {
546 	gtk_object_destroy(GTK_OBJECT(port->impl->control->rect));
547 	free(port->impl->control);
548 	port->impl->control = NULL;
549 }
550 
551 void
ganv_port_set_value_label(GanvPort * port,const char * str)552 ganv_port_set_value_label(GanvPort*   port,
553                           const char* str)
554 {
555 	GanvPortPrivate* impl = port->impl;
556 
557 	if (!str || str[0] == '\0') {
558 		if (impl->value_label) {
559 			gtk_object_destroy(GTK_OBJECT(impl->value_label));
560 			impl->value_label = NULL;
561 		}
562 	} else if (impl->value_label) {
563 		ganv_item_set(GANV_ITEM(impl->value_label),
564 		              "text", str,
565 		              NULL);
566 	} else {
567 		impl->value_label = GANV_TEXT(ganv_item_new(GANV_ITEM(port),
568 		                                            ganv_text_get_type(),
569 		                                            "text", str,
570 		                                            "color", DIM_TEXT_COLOR,
571 		                                            "managed", TRUE,
572 		                                            NULL));
573 	}
574 }
575 
576 static void
ganv_port_update_control_slider(GanvPort * port,float value,gboolean force)577 ganv_port_update_control_slider(GanvPort* port, float value, gboolean force)
578 {
579 	GanvPortPrivate* impl = port->impl;
580 	if (!impl->control) {
581 		return;
582 	}
583 
584 	// Clamp to toggle or integer value if applicable
585 	if (impl->control->is_toggle) {
586 		if (value != 0.0f) {
587 			value = impl->control->max;
588 		} else {
589 			value = impl->control->min;
590 		}
591 	} else if (impl->control->is_integer) {
592 		value = lrintf(value);
593 	}
594 
595 	// Clamp to range
596 	if (value < impl->control->min) {
597 		value = impl->control->min;
598 	}
599 	if (value > impl->control->max) {
600 		value = impl->control->max;
601 	}
602 
603 	if (!force && value == impl->control->value) {
604 		return;  // No change, do nothing
605 	}
606 
607 	const double span = (ganv_box_get_width(&port->box) -
608 	                     GANV_NODE(port)->impl->border_width);
609 
610 	const double w = (value - impl->control->min)
611 		/ (impl->control->max - impl->control->min)
612 		* span;
613 
614 	if (isnan(w)) {
615 		return;  // Shouldn't happen, but ignore crazy values
616 	}
617 
618 	// Redraw port
619 	impl->control->value = value;
620 	ganv_box_set_width(impl->control->rect, MAX(0.0, w));
621 	ganv_box_request_redraw(
622 		GANV_ITEM(port), &GANV_BOX(port)->impl->coords, FALSE);
623 }
624 
625 void
ganv_port_set_control_is_toggle(GanvPort * port,gboolean is_toggle)626 ganv_port_set_control_is_toggle(GanvPort* port,
627                                 gboolean  is_toggle)
628 {
629 	if (port->impl->control) {
630 		port->impl->control->is_toggle = is_toggle;
631 		ganv_port_update_control_slider(port, port->impl->control->value, TRUE);
632 	}
633 }
634 
635 void
ganv_port_set_control_is_integer(GanvPort * port,gboolean is_integer)636 ganv_port_set_control_is_integer(GanvPort* port,
637                                  gboolean  is_integer)
638 {
639 	if (port->impl->control) {
640 		port->impl->control->is_integer = is_integer;
641 		const float rounded = rintf(port->impl->control->value);
642 		ganv_port_update_control_slider(port, rounded, TRUE);
643 	}
644 }
645 
646 void
ganv_port_set_control_value(GanvPort * port,float value)647 ganv_port_set_control_value(GanvPort* port,
648                             float     value)
649 {
650 	ganv_port_update_control_slider(port, value, FALSE);
651 }
652 
653 void
ganv_port_set_control_value_internal(GanvPort * port,float value)654 ganv_port_set_control_value_internal(GanvPort* port,
655                                      float     value)
656 {
657 	// Update slider
658 	ganv_port_set_control_value(port, value);
659 
660 	// Fire signal to notify user value has changed
661 	const double dvalue = port->impl->control->value;
662 	g_signal_emit(port, port_signals[PORT_VALUE_CHANGED], 0, dvalue, NULL);
663 }
664 
665 void
ganv_port_set_control_min(GanvPort * port,float min)666 ganv_port_set_control_min(GanvPort* port,
667                           float     min)
668 {
669 	if (port->impl->control) {
670 		const gboolean force = port->impl->control->min != min;
671 		port->impl->control->min = min;
672 		if (port->impl->control->max < min) {
673 			port->impl->control->max = min;
674 		}
675 		ganv_port_update_control_slider(port, port->impl->control->value, force);
676 	}
677 }
678 
679 void
ganv_port_set_control_max(GanvPort * port,float max)680 ganv_port_set_control_max(GanvPort* port,
681                           float     max)
682 {
683 	if (port->impl->control) {
684 		const gboolean force = port->impl->control->max != max;
685 		port->impl->control->max = max;
686 		if (port->impl->control->min > max) {
687 			port->impl->control->min = max;
688 		}
689 		ganv_port_update_control_slider(port, port->impl->control->value, force);
690 	}
691 }
692 
693 double
ganv_port_get_natural_width(const GanvPort * port)694 ganv_port_get_natural_width(const GanvPort* port)
695 {
696 	GanvCanvas* const canvas = ganv_item_get_canvas(GANV_ITEM(port));
697 	GanvText* const   label  = port->box.node.impl->label;
698 	double            w      = 0.0;
699 	if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) {
700 		w = ganv_module_get_empty_port_breadth(ganv_port_get_module(port));
701 	} else if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) {
702 		double label_w = 0.0;
703 		g_object_get(port->box.node.impl->label, "width", &label_w, NULL);
704 		w = label_w + (PORT_LABEL_HPAD * 2.0);
705 	} else {
706 		w = ganv_module_get_empty_port_depth(ganv_port_get_module(port));
707 	}
708 	if (port->impl->value_label &&
709 	    (GANV_ITEM(port->impl->value_label)->object.flags
710 	     & GANV_ITEM_VISIBLE)) {
711 		double label_w = 0.0;
712 		g_object_get(port->impl->value_label, "width", &label_w, NULL);
713 		w += label_w + PORT_LABEL_HPAD;
714 	}
715 	return w;
716 }
717 
718 GanvModule*
ganv_port_get_module(const GanvPort * port)719 ganv_port_get_module(const GanvPort* port)
720 {
721 	return GANV_MODULE(GANV_ITEM(port)->impl->parent);
722 }
723 
724 float
ganv_port_get_control_value(const GanvPort * port)725 ganv_port_get_control_value(const GanvPort* port)
726 {
727 	return port->impl->control ? port->impl->control->value : 0.0f;
728 }
729 
730 float
ganv_port_get_control_min(const GanvPort * port)731 ganv_port_get_control_min(const GanvPort* port)
732 {
733 	return port->impl->control ? port->impl->control->min : 0.0f;
734 }
735 
736 float
ganv_port_get_control_max(const GanvPort * port)737 ganv_port_get_control_max(const GanvPort* port)
738 {
739 	return port->impl->control ? port->impl->control->max : 0.0f;
740 }
741 
742 gboolean
ganv_port_is_input(const GanvPort * port)743 ganv_port_is_input(const GanvPort* port)
744 {
745 	return port->impl->is_input;
746 }
747 
748 gboolean
ganv_port_is_output(const GanvPort * port)749 ganv_port_is_output(const GanvPort* port)
750 {
751 	return !port->impl->is_input;
752 }
753