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 "boilerplate.h"
17 #include "color.h"
18 #include "ganv-private.h"
19 #include "gettext.h"
20
21 #include "ganv/canvas.h"
22 #include "ganv/edge.h"
23 #include "ganv/item.h"
24 #include "ganv/node.h"
25 #include "ganv/types.h"
26
27 #include <cairo.h>
28 #include <glib-object.h>
29 #include <glib.h>
30 #include <gtk/gtk.h>
31 #include <stdarg.h>
32
33 #include <math.h>
34 #include <string.h>
35
36 #define ARROW_DEPTH 32
37 #define ARROW_BREADTH 32
38
39 // Uncomment to see control point path as straight lines
40 //#define GANV_DEBUG_CURVES 1
41
42 // Uncomment along with GANV_DEBUG_CURVES to see bounding box (buggy)
43 //#define GANV_DEBUG_BOUNDS 1
44
45 enum {
46 PROP_0,
47 PROP_TAIL,
48 PROP_HEAD,
49 PROP_WIDTH,
50 PROP_HANDLE_RADIUS,
51 PROP_DASH_LENGTH,
52 PROP_DASH_OFFSET,
53 PROP_COLOR,
54 PROP_CONSTRAINING,
55 PROP_CURVED,
56 PROP_ARROWHEAD,
57 PROP_SELECTED,
58 PROP_HIGHLIGHTED,
59 PROP_GHOST
60 };
61
62 G_DEFINE_TYPE_WITH_CODE(GanvEdge, ganv_edge, GANV_TYPE_ITEM,
63 G_ADD_PRIVATE(GanvEdge))
64
65 static GanvItemClass* parent_class;
66
67 static void
ganv_edge_init(GanvEdge * edge)68 ganv_edge_init(GanvEdge* edge)
69 {
70 GanvEdgePrivate* impl =
71 (GanvEdgePrivate*)ganv_edge_get_instance_private(edge);
72
73 edge->impl = impl;
74
75 impl->tail = NULL;
76 impl->head = NULL;
77
78 memset(&impl->coords, '\0', sizeof(GanvEdgeCoords));
79 impl->coords.width = 2.0;
80 impl->coords.handle_radius = 4.0;
81 impl->coords.constraining = TRUE;
82 impl->coords.curved = FALSE;
83 impl->coords.arrowhead = FALSE;
84
85 impl->old_coords = impl->coords;
86 impl->dash_length = 0.0;
87 impl->dash_offset = 0.0;
88 impl->color = 0;
89 }
90
91 static void
ganv_edge_destroy(GtkObject * object)92 ganv_edge_destroy(GtkObject* object)
93 {
94 g_return_if_fail(object != NULL);
95 g_return_if_fail(GANV_IS_EDGE(object));
96
97 GanvEdge* edge = GANV_EDGE(object);
98 GanvCanvas* canvas = GANV_CANVAS(edge->item.impl->canvas);
99 if (canvas && !edge->impl->ghost) {
100 edge->item.impl->canvas = NULL;
101 }
102 edge->item.impl->parent = NULL;
103
104 if (GTK_OBJECT_CLASS(parent_class)->destroy) {
105 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
106 }
107 }
108
109 static void
ganv_edge_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)110 ganv_edge_set_property(GObject* object,
111 guint prop_id,
112 const GValue* value,
113 GParamSpec* pspec)
114 {
115 g_return_if_fail(object != NULL);
116 g_return_if_fail(GANV_IS_EDGE(object));
117
118 GanvItem* item = GANV_ITEM(object);
119 GanvEdge* edge = GANV_EDGE(object);
120 GanvEdgePrivate* impl = edge->impl;
121 GanvEdgeCoords* coords = &impl->coords;
122
123 switch (prop_id) {
124 SET_CASE(WIDTH, double, coords->width)
125 SET_CASE(HANDLE_RADIUS, double, coords->handle_radius)
126 SET_CASE(DASH_LENGTH, double, impl->dash_length)
127 SET_CASE(DASH_OFFSET, double, impl->dash_offset)
128 SET_CASE(COLOR, uint, impl->color)
129 SET_CASE(CONSTRAINING, boolean, impl->coords.constraining)
130 SET_CASE(CURVED, boolean, impl->coords.curved)
131 SET_CASE(ARROWHEAD, boolean, impl->coords.arrowhead)
132 SET_CASE(SELECTED, boolean, impl->selected)
133 SET_CASE(HIGHLIGHTED, boolean, impl->highlighted)
134 SET_CASE(GHOST, boolean, impl->ghost)
135 case PROP_TAIL: {
136 const gobject tmp = g_value_get_object(value);
137 if (impl->tail != tmp) {
138 impl->tail = GANV_NODE(tmp);
139 ganv_item_request_update(item);
140 }
141 break;
142 }
143 case PROP_HEAD: {
144 const gobject tmp = g_value_get_object(value);
145 if (impl->head != tmp) {
146 impl->head = GANV_NODE(tmp);
147 ganv_item_request_update(item);
148 }
149 break;
150 }
151 default:
152 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
153 break;
154 }
155 }
156
157 static void
ganv_edge_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)158 ganv_edge_get_property(GObject* object,
159 guint prop_id,
160 GValue* value,
161 GParamSpec* pspec)
162 {
163 g_return_if_fail(object != NULL);
164 g_return_if_fail(GANV_IS_EDGE(object));
165
166 GanvEdge* edge = GANV_EDGE(object);
167 GanvEdgePrivate* impl = edge->impl;
168
169 switch (prop_id) {
170 GET_CASE(TAIL, object, impl->tail)
171 GET_CASE(HEAD, object, impl->head)
172 GET_CASE(WIDTH, double, impl->coords.width)
173 SET_CASE(HANDLE_RADIUS, double, impl->coords.handle_radius)
174 GET_CASE(DASH_LENGTH, double, impl->dash_length)
175 GET_CASE(DASH_OFFSET, double, impl->dash_offset)
176 GET_CASE(COLOR, uint, impl->color)
177 GET_CASE(CONSTRAINING, boolean, impl->coords.constraining)
178 GET_CASE(CURVED, boolean, impl->coords.curved)
179 GET_CASE(ARROWHEAD, boolean, impl->coords.arrowhead)
180 GET_CASE(SELECTED, boolean, impl->selected)
181 GET_CASE(HIGHLIGHTED, boolean, impl->highlighted)
182 SET_CASE(GHOST, boolean, impl->ghost)
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 break;
186 }
187 }
188
189 void
ganv_edge_request_redraw(GanvItem * item,const GanvEdgeCoords * coords)190 ganv_edge_request_redraw(GanvItem* item,
191 const GanvEdgeCoords* coords)
192 {
193 GanvCanvas* canvas = item->impl->canvas;
194 const double w = coords->width;
195 if (coords->curved) {
196 const double src_x = coords->x1;
197 const double src_y = coords->y1;
198 const double dst_x = coords->x2;
199 const double dst_y = coords->y2;
200 const double join_x = (src_x + dst_x) / 2.0;
201 const double join_y = (src_y + dst_y) / 2.0;
202 const double src_x1 = coords->cx1;
203 const double src_y1 = coords->cy1;
204 const double dst_x1 = coords->cx2;
205 const double dst_y1 = coords->cy2;
206
207 const double r1x1 = MIN(MIN(src_x, join_x), src_x1);
208 const double r1y1 = MIN(MIN(src_y, join_y), src_y1);
209 const double r1x2 = MAX(MAX(src_x, join_x), src_x1);
210 const double r1y2 = MAX(MAX(src_y, join_y), src_y1);
211 ganv_canvas_request_redraw_w(canvas,
212 r1x1 - w, r1y1 - w,
213 r1x2 + w, r1y2 + w);
214
215 const double r2x1 = MIN(MIN(dst_x, join_x), dst_x1);
216 const double r2y1 = MIN(MIN(dst_y, join_y), dst_y1);
217 const double r2x2 = MAX(MAX(dst_x, join_x), dst_x1);
218 const double r2y2 = MAX(MAX(dst_y, join_y), dst_y1);
219 ganv_canvas_request_redraw_w(canvas,
220 r2x1 - w, r2y1 - w,
221 r2x2 + w, r2y2 + w);
222
223 } else {
224 const double x1 = MIN(coords->x1, coords->x2);
225 const double y1 = MIN(coords->y1, coords->y2);
226 const double x2 = MAX(coords->x1, coords->x2);
227 const double y2 = MAX(coords->y1, coords->y2);
228
229 ganv_canvas_request_redraw_w(canvas,
230 x1 - w, y1 - w,
231 x2 + w, y2 + w);
232 }
233
234 if (coords->handle_radius > 0.0) {
235 ganv_canvas_request_redraw_w(
236 canvas,
237 coords->handle_x - coords->handle_radius - w,
238 coords->handle_y - coords->handle_radius - w,
239 coords->handle_x + coords->handle_radius + w,
240 coords->handle_y + coords->handle_radius + w);
241 }
242
243 if (coords->arrowhead) {
244 ganv_canvas_request_redraw_w(
245 canvas,
246 coords->x2 - ARROW_DEPTH,
247 coords->y2 - ARROW_BREADTH,
248 coords->x2 + ARROW_DEPTH,
249 coords->y2 + ARROW_BREADTH);
250 }
251 }
252
253 static void
ganv_edge_bounds(GanvItem * item,double * x1,double * y1,double * x2,double * y2)254 ganv_edge_bounds(GanvItem* item,
255 double* x1, double* y1,
256 double* x2, double* y2)
257 {
258 GanvEdge* edge = GANV_EDGE(item);
259 GanvEdgePrivate* impl = edge->impl;
260 GanvEdgeCoords* coords = &impl->coords;
261 const double w = coords->width;
262
263 if (coords->curved) {
264 *x1 = MIN(coords->x1, MIN(coords->cx1, MIN(coords->x2, coords->cx2))) - w;
265 *y1 = MIN(coords->y1, MIN(coords->cy1, MIN(coords->y2, coords->cy2))) - w;
266 *x2 = MAX(coords->x1, MAX(coords->cx1, MAX(coords->x2, coords->cx2))) + w;
267 *y2 = MAX(coords->y1, MAX(coords->cy1, MAX(coords->y2, coords->cy2))) + w;
268 } else {
269 *x1 = MIN(impl->coords.x1, impl->coords.x2) - w;
270 *y1 = MIN(impl->coords.y1, impl->coords.y2) - w;
271 *x2 = MAX(impl->coords.x1, impl->coords.x2) + w;
272 *y2 = MAX(impl->coords.y1, impl->coords.y2) + w;
273 }
274 }
275
276 void
ganv_edge_get_coords(const GanvEdge * edge,GanvEdgeCoords * coords)277 ganv_edge_get_coords(const GanvEdge* edge, GanvEdgeCoords* coords)
278 {
279 GanvEdgePrivate* impl = edge->impl;
280
281 GANV_NODE_GET_CLASS(impl->tail)->tail_vector(
282 impl->tail, impl->head,
283 &coords->x1, &coords->y1, &coords->cx1, &coords->cy1);
284 GANV_NODE_GET_CLASS(impl->head)->head_vector(
285 impl->head, impl->tail,
286 &coords->x2, &coords->y2, &coords->cx2, &coords->cy2);
287
288 const double dx = coords->x2 - coords->x1;
289 const double dy = coords->y2 - coords->y1;
290
291 coords->handle_x = coords->x1 + (dx / 2.0);
292 coords->handle_y = coords->y1 + (dy / 2.0);
293
294 const double abs_dx = fabs(dx);
295 const double abs_dy = fabs(dy);
296
297 coords->cx1 = coords->x1 + (coords->cx1 * (abs_dx / 4.0));
298 coords->cy1 = coords->y1 + (coords->cy1 * (abs_dy / 4.0));
299 coords->cx2 = coords->x2 + (coords->cx2 * (abs_dx / 4.0));
300 coords->cy2 = coords->y2 + (coords->cy2 * (abs_dy / 4.0));
301 }
302
303 static void
ganv_edge_update(GanvItem * item,int flags)304 ganv_edge_update(GanvItem* item, int flags)
305 {
306 GanvEdge* edge = GANV_EDGE(item);
307 GanvEdgePrivate* impl = edge->impl;
308
309 // Request redraw of old location
310 ganv_edge_request_redraw(item, &impl->old_coords);
311
312 // Calculate new coordinates from tail and head
313 ganv_edge_get_coords(edge, &impl->coords);
314
315 // Update old coordinates
316 impl->old_coords = impl->coords;
317
318 // Get bounding box
319 double x1 = 0.0;
320 double x2 = 0.0;
321 double y1 = 0.0;
322 double y2 = 0.0;
323 ganv_edge_bounds(item, &x1, &y1, &x2, &y2);
324
325 // Ensure bounding box has non-zero area
326 if (x1 == x2) {
327 x2 += 1.0;
328 }
329 if (y1 == y2) {
330 y2 += 1.0;
331 }
332
333 // Update world-relative bounding box
334 item->impl->x1 = x1;
335 item->impl->y1 = y1;
336 item->impl->x2 = x2;
337 item->impl->y2 = y2;
338 ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
339
340 // Request redraw of new location
341 ganv_edge_request_redraw(item, &impl->coords);
342
343 parent_class->update(item, flags);
344 }
345
346 static void
ganv_edge_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)347 ganv_edge_draw(GanvItem* item,
348 cairo_t* cr, double cx, double cy, double cw, double ch)
349 {
350 (void)cx;
351 (void)cy;
352 (void)cw;
353 (void)ch;
354
355 GanvEdge* edge = GANV_EDGE(item);
356 GanvEdgePrivate* impl = edge->impl;
357
358 double src_x = impl->coords.x1;
359 double src_y = impl->coords.y1;
360 double dst_x = impl->coords.x2;
361 double dst_y = impl->coords.y2;
362 double dx = src_x - dst_x;
363 double dy = src_y - dst_y;
364
365 double r = 0.0;
366 double g = 0.0;
367 double b = 0.0;
368 double a = 0.0;
369 if (impl->highlighted) {
370 color_to_rgba(highlight_color(impl->color, 0x40), &r, &g, &b, &a);
371 } else {
372 color_to_rgba(impl->color, &r, &g, &b, &a);
373 }
374 cairo_set_source_rgba(cr, r, g, b, a);
375
376 cairo_set_line_width(cr, impl->coords.width);
377 cairo_move_to(cr, src_x, src_y);
378
379 const double dash_length = (impl->selected ? 4.0 : impl->dash_length);
380 if (dash_length > 0.0) {
381 double dashed[2] = { dash_length, dash_length };
382 cairo_set_dash(cr, dashed, 2, impl->dash_offset);
383 } else {
384 cairo_set_dash(cr, &dash_length, 0, 0);
385 }
386
387 const double join_x = (src_x + dst_x) / 2.0;
388 const double join_y = (src_y + dst_y) / 2.0;
389
390 if (impl->coords.curved) {
391 // Curved line as 2 paths which join at the middle point
392
393 // Path 1 (src_x, src_y) -> (join_x, join_y)
394 // Control point 1
395 const double src_x1 = impl->coords.cx1;
396 const double src_y1 = impl->coords.cy1;
397 // Control point 2
398 const double src_x2 = (join_x + src_x1) / 2.0;
399 const double src_y2 = (join_y + src_y1) / 2.0;
400
401 // Path 2, (join_x, join_y) -> (dst_x, dst_y)
402 // Control point 1
403 const double dst_x1 = impl->coords.cx2;
404 const double dst_y1 = impl->coords.cy2;
405 // Control point 2
406 const double dst_x2 = (join_x + dst_x1) / 2.0;
407 const double dst_y2 = (join_y + dst_y1) / 2.0;
408
409 cairo_move_to(cr, src_x, src_y);
410 cairo_curve_to(cr, src_x1, src_y1, src_x2, src_y2, join_x, join_y);
411 cairo_curve_to(cr, dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y);
412
413 #ifdef GANV_DEBUG_CURVES
414 cairo_stroke(cr);
415 cairo_save(cr);
416 cairo_set_source_rgba(cr, 1.0, 0, 0, 0.5);
417
418 cairo_move_to(cr, src_x, src_y);
419 cairo_line_to(cr, src_x1, src_y1);
420 cairo_stroke(cr);
421
422 cairo_move_to(cr, join_x, join_y);
423 cairo_line_to(cr, src_x2, src_y2);
424 cairo_stroke(cr);
425
426 cairo_move_to(cr, join_x, join_y);
427 cairo_line_to(cr, dst_x2, dst_y2);
428 cairo_stroke(cr);
429
430 cairo_move_to(cr, dst_x, dst_y);
431 cairo_line_to(cr, dst_x1, dst_y1);
432 cairo_stroke(cr);
433
434 #ifdef GANV_DEBUG_BOUNDS
435 double bounds_x1, bounds_y1, bounds_x2, bounds_y2;
436 ganv_edge_bounds(item, &bounds_x1, &bounds_y1, &bounds_x2, &bounds_y2);
437 cairo_rectangle(cr,
438 bounds_x1, bounds_y1,
439 bounds_x2 - bounds_x1, bounds_y2 - bounds_y1);
440 #endif
441
442 cairo_restore(cr);
443 #endif
444
445 cairo_stroke(cr);
446 if (impl->coords.arrowhead) {
447 cairo_move_to(cr, dst_x - 12, dst_y - 4);
448 cairo_line_to(cr, dst_x, dst_y);
449 cairo_line_to(cr, dst_x - 12, dst_y + 4);
450 cairo_close_path(cr);
451 cairo_stroke_preserve(cr);
452 cairo_fill(cr);
453 }
454
455 } else {
456 // Straight line from (x1, y1) to (x2, y2)
457 cairo_move_to(cr, src_x, src_y);
458 cairo_line_to(cr, dst_x, dst_y);
459 cairo_stroke(cr);
460
461 if (impl->coords.arrowhead) {
462 const double ah = sqrt(dx * dx + dy * dy);
463 const double adx = dx / ah * 8.0;
464 const double ady = dy / ah * 8.0;
465
466 cairo_move_to(cr,
467 dst_x + adx - ady/1.5,
468 dst_y + ady + adx/1.5);
469 cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
470 cairo_line_to(cr, dst_x, dst_y);
471 cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
472 cairo_line_to(cr,
473 dst_x + adx + ady/1.5,
474 dst_y + ady - adx/1.5);
475 cairo_close_path(cr);
476 cairo_stroke_preserve(cr);
477 cairo_fill(cr);
478 }
479 }
480
481 if (!ganv_canvas_exporting(item->impl->canvas) &&
482 impl->coords.handle_radius > 0.0) {
483 cairo_move_to(cr, join_x, join_y);
484 cairo_arc(cr, join_x, join_y, impl->coords.handle_radius, 0, 2 * G_PI);
485 cairo_fill(cr);
486 }
487 }
488
489 static double
ganv_edge_point(GanvItem * item,double x,double y,GanvItem ** actual_item)490 ganv_edge_point(GanvItem* item, double x, double y, GanvItem** actual_item)
491 {
492 const GanvEdge* edge = GANV_EDGE(item);
493 const GanvEdgeCoords* coords = &edge->impl->coords;
494
495 const double dx = fabs(x - coords->handle_x);
496 const double dy = fabs(y - coords->handle_y);
497 const double d = sqrt((dx * dx) + (dy * dy));
498
499 *actual_item = item;
500
501 if (d <= coords->handle_radius) {
502 // Point is inside the handle
503 return 0.0;
504 } else {
505 // Distance from the edge of the handle
506 return d - (coords->handle_radius + coords->width);
507 }
508 }
509
510 gboolean
ganv_edge_is_within(const GanvEdge * edge,double x1,double y1,double x2,double y2)511 ganv_edge_is_within(const GanvEdge* edge,
512 double x1,
513 double y1,
514 double x2,
515 double y2)
516 {
517 const double handle_x = edge->impl->coords.handle_x;
518 const double handle_y = edge->impl->coords.handle_y;
519
520 return handle_x >= x1
521 && handle_x <= x2
522 && handle_y >= y1
523 && handle_y <= y2;
524 }
525
526 static void
ganv_edge_class_init(GanvEdgeClass * klass)527 ganv_edge_class_init(GanvEdgeClass* klass)
528 {
529 GObjectClass* gobject_class = (GObjectClass*)klass;
530 GtkObjectClass* object_class = (GtkObjectClass*)klass;
531 GanvItemClass* item_class = (GanvItemClass*)klass;
532
533 parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass));
534
535 gobject_class->set_property = ganv_edge_set_property;
536 gobject_class->get_property = ganv_edge_get_property;
537
538 g_object_class_install_property(
539 gobject_class, PROP_TAIL, g_param_spec_object(
540 "tail",
541 _("Tail"),
542 _("Node this edge starts from."),
543 GANV_TYPE_NODE,
544 G_PARAM_READWRITE));
545
546 g_object_class_install_property(
547 gobject_class, PROP_HEAD, g_param_spec_object(
548 "head",
549 _("Head"),
550 _("Node this edge ends at."),
551 GANV_TYPE_NODE,
552 G_PARAM_READWRITE));
553
554 g_object_class_install_property(
555 gobject_class, PROP_WIDTH, g_param_spec_double(
556 "width",
557 _("Line width"),
558 _("Width of edge line."),
559 0.0, G_MAXDOUBLE,
560 2.0,
561 G_PARAM_READWRITE));
562
563 g_object_class_install_property(
564 gobject_class, PROP_HANDLE_RADIUS, g_param_spec_double(
565 "handle-radius",
566 _("Gandle radius"),
567 _("Radius of handle in canvas units."),
568 0.0, G_MAXDOUBLE,
569 4.0,
570 G_PARAM_READWRITE));
571
572 g_object_class_install_property(
573 gobject_class, PROP_DASH_LENGTH, g_param_spec_double(
574 "dash-length",
575 _("Line dash length"),
576 _("Length of line dashes, or zero for no dashing."),
577 0.0, G_MAXDOUBLE,
578 0.0,
579 G_PARAM_READWRITE));
580
581 g_object_class_install_property(
582 gobject_class, PROP_DASH_OFFSET, g_param_spec_double(
583 "dash-offset",
584 _("Line dash offset"),
585 _("Start offset for line dashes, used for selected animation."),
586 0.0, G_MAXDOUBLE,
587 0.0,
588 G_PARAM_READWRITE));
589
590 g_object_class_install_property(
591 gobject_class, PROP_COLOR, g_param_spec_uint(
592 "color",
593 _("Color"),
594 _("Line color as an RGBA integer."),
595 0, G_MAXUINT,
596 0,
597 G_PARAM_READWRITE));
598
599 g_object_class_install_property(
600 gobject_class, PROP_CONSTRAINING, g_param_spec_boolean(
601 "constraining",
602 _("Constraining"),
603 _("Whether edge should constrain the layout."),
604 1,
605 G_PARAM_READWRITE));
606
607 g_object_class_install_property(
608 gobject_class, PROP_CURVED, g_param_spec_boolean(
609 "curved",
610 _("Curved"),
611 _("Whether line should be curved rather than straight."),
612 0,
613 G_PARAM_READWRITE));
614
615 g_object_class_install_property(
616 gobject_class, PROP_ARROWHEAD, g_param_spec_boolean(
617 "arrowhead",
618 _("Arrowhead"),
619 _("Whether to show an arrowhead at the head of this edge."),
620 0,
621 G_PARAM_READWRITE));
622
623 g_object_class_install_property(
624 gobject_class, PROP_SELECTED, g_param_spec_boolean(
625 "selected",
626 _("Selected"),
627 _("Whether this edge is selected."),
628 0,
629 G_PARAM_READWRITE));
630
631 g_object_class_install_property(
632 gobject_class, PROP_HIGHLIGHTED, g_param_spec_boolean(
633 "highlighted",
634 _("Highlighted"),
635 _("Whether to highlight the edge."),
636 0,
637 G_PARAM_READWRITE));
638
639 g_object_class_install_property(
640 gobject_class, PROP_GHOST, g_param_spec_boolean(
641 "ghost",
642 _("Ghost"),
643 _("Whether this edge is a `ghost', which is an edge that is not "
644 "added to the canvas data structures. Ghost edges are used for "
645 "temporary edges that are not considered `real', e.g. the edge "
646 "made while dragging to make a connection."),
647 0,
648 G_PARAM_READWRITE));
649
650 object_class->destroy = ganv_edge_destroy;
651
652 item_class->update = ganv_edge_update;
653 item_class->bounds = ganv_edge_bounds;
654 item_class->point = ganv_edge_point;
655 item_class->draw = ganv_edge_draw;
656 }
657
658 GanvEdge*
ganv_edge_new(GanvCanvas * canvas,GanvNode * tail,GanvNode * head,const char * first_prop_name,...)659 ganv_edge_new(GanvCanvas* canvas,
660 GanvNode* tail,
661 GanvNode* head,
662 const char* first_prop_name, ...)
663 {
664 GanvEdge* edge = GANV_EDGE(
665 g_object_new(ganv_edge_get_type(), NULL));
666
667 va_list args;
668 va_start(args, first_prop_name);
669 ganv_item_construct(&edge->item,
670 GANV_ITEM(ganv_canvas_root(canvas)),
671 first_prop_name, args);
672 va_end(args);
673
674 edge->impl->tail = tail;
675 edge->impl->head = head;
676
677 if (!edge->impl->color) {
678 const guint tail_color = GANV_NODE(tail)->impl->fill_color;
679 g_object_set(G_OBJECT(edge),
680 "color", EDGE_COLOR(tail_color),
681 NULL);
682 }
683
684 if (!edge->impl->ghost) {
685 ganv_canvas_add_edge(canvas, edge);
686 }
687 return edge;
688 }
689
690 void
ganv_edge_update_location(GanvEdge * edge)691 ganv_edge_update_location(GanvEdge* edge)
692 {
693 ganv_item_request_update(GANV_ITEM(edge));
694 }
695
696 gboolean
ganv_edge_get_constraining(const GanvEdge * edge)697 ganv_edge_get_constraining(const GanvEdge* edge)
698 {
699 return edge->impl->coords.constraining;
700 }
701
702 void
ganv_edge_set_constraining(GanvEdge * edge,gboolean constraining)703 ganv_edge_set_constraining(GanvEdge* edge, gboolean constraining)
704 {
705 edge->impl->coords.constraining = constraining;
706 ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords);
707 }
708
709 gboolean
ganv_edge_get_curved(const GanvEdge * edge)710 ganv_edge_get_curved(const GanvEdge* edge)
711 {
712 return edge->impl->coords.curved;
713 }
714
715 void
ganv_edge_set_curved(GanvEdge * edge,gboolean curved)716 ganv_edge_set_curved(GanvEdge* edge, gboolean curved)
717 {
718 edge->impl->coords.curved = curved;
719 ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords);
720 }
721
722 void
ganv_edge_set_selected(GanvEdge * edge,gboolean selected)723 ganv_edge_set_selected(GanvEdge* edge, gboolean selected)
724 {
725 GanvCanvas* canvas = GANV_CANVAS(edge->item.impl->canvas);
726 if (selected) {
727 ganv_canvas_select_edge(canvas, edge);
728 } else {
729 ganv_canvas_unselect_edge(canvas, edge);
730 }
731 }
732
733 void
ganv_edge_select(GanvEdge * edge)734 ganv_edge_select(GanvEdge* edge)
735 {
736 ganv_edge_set_selected(edge, TRUE);
737 }
738
739 void
ganv_edge_unselect(GanvEdge * edge)740 ganv_edge_unselect(GanvEdge* edge)
741 {
742 ganv_edge_set_selected(edge, FALSE);
743 }
744
745 void
ganv_edge_highlight(GanvEdge * edge)746 ganv_edge_highlight(GanvEdge* edge)
747 {
748 ganv_edge_set_highlighted(edge, TRUE);
749 }
750
751 void
ganv_edge_unhighlight(GanvEdge * edge)752 ganv_edge_unhighlight(GanvEdge* edge)
753 {
754 ganv_edge_set_highlighted(edge, FALSE);
755 }
756
757 void
ganv_edge_set_highlighted(GanvEdge * edge,gboolean highlighted)758 ganv_edge_set_highlighted(GanvEdge* edge, gboolean highlighted)
759 {
760 edge->impl->highlighted = highlighted;
761 ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords);
762 }
763
764 void
ganv_edge_tick(GanvEdge * edge,double seconds)765 ganv_edge_tick(GanvEdge* edge, double seconds)
766 {
767 ganv_item_set(GANV_ITEM(edge),
768 "dash-offset", seconds * 8.0,
769 NULL);
770 }
771
772 void
ganv_edge_disconnect(GanvEdge * edge)773 ganv_edge_disconnect(GanvEdge* edge)
774 {
775 if (!edge->impl->ghost) {
776 ganv_canvas_disconnect_edge(
777 GANV_CANVAS(edge->item.impl->canvas),
778 edge);
779 }
780 }
781
782 void
ganv_edge_remove(GanvEdge * edge)783 ganv_edge_remove(GanvEdge* edge)
784 {
785 if (!edge->impl->ghost) {
786 ganv_canvas_remove_edge(
787 GANV_CANVAS(edge->item.impl->canvas),
788 edge);
789 }
790 }
791
792 GanvNode*
ganv_edge_get_tail(const GanvEdge * edge)793 ganv_edge_get_tail(const GanvEdge* edge)
794 {
795 return edge->impl->tail;
796 }
797
798 GanvNode*
ganv_edge_get_head(const GanvEdge * edge)799 ganv_edge_get_head(const GanvEdge* edge)
800 {
801 return edge->impl->head;
802 }
803