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