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 /* Parts based on GnomeCanvas, by Federico Mena <federico@nuclecu.unam.mx>
17 * and Raph Levien <raph@gimp.org>
18 * Copyright 1997-2000 Free Software Foundation
19 */
20
21 #define _POSIX_C_SOURCE 200809L // strdup
22 #define _XOPEN_SOURCE 600 // isascii on BSD
23
24 #include "color.h"
25 #include "ganv-marshal.h"
26 #include "ganv-private.h"
27 #include "gettext.h"
28
29 #include "ganv/Canvas.hpp"
30 #include "ganv/Edge.hpp"
31 #include "ganv/Item.hpp"
32 #include "ganv/Node.hpp"
33 #include "ganv/box.h"
34 #include "ganv/canvas.h"
35 #include "ganv/circle.h"
36 #include "ganv/edge.h"
37 #include "ganv/group.h"
38 #include "ganv/item.h"
39 #include "ganv/module.h"
40 #include "ganv/node.h"
41 #include "ganv/port.h"
42 #include "ganv/types.h"
43 #include "ganv_config.h"
44
45 #include <cairo-pdf.h>
46 #include <cairo-ps.h>
47 #include <cairo-svg.h>
48 #include <cairo.h>
49 #include <gdk/gdk.h>
50 #include <gdk/gdkkeysyms-compat.h>
51 #include <gdkmm/gc.h>
52 #include <gdkmm/screen.h>
53 #include <gdkmm/window.h>
54 #include <glib-object.h>
55 #include <glib.h>
56 #include <glibmm/object.h>
57 #include <gobject/gclosure.h>
58 #include <gtk/gtk.h>
59 #include <gtkmm/layout.h>
60 #include <gtkmm/object.h>
61 #include <gtkmm/style.h>
62 #include <gtkmm/widget.h>
63 #include <pango/pango-font.h>
64 #include <pango/pango-types.h>
65 #include <pangomm/fontdescription.h>
66 #include <sigc++/signal.h>
67
68 #include <algorithm>
69 #include <cassert>
70 #include <cfloat>
71 #include <clocale>
72 #include <cmath>
73 #include <cstdint>
74 #include <cstdio>
75 #include <cstdlib>
76 #include <cstring>
77 #include <iostream>
78 #include <map>
79 #include <set>
80 #include <sstream>
81 #include <string>
82 #include <utility>
83 #include <vector>
84
85 #ifdef HAVE_AGRAPH
86 // Deal with graphviz API amateur hour...
87 # define _DLL_BLD 0
88 # define _dll_import 0
89 # define _BLD_cdt 0
90 # define _PACKAGE_ast 0
91 # include <arith.h>
92 # include <gvc.h>
93 # include <gvcext.h>
94 # include <types.h>
95 #endif
96 #ifdef GANV_FDGL
97 # include "./fdgl.hpp"
98 #endif
99
100 #define CANVAS_IDLE_PRIORITY (GDK_PRIORITY_REDRAW - 5)
101
102 static const double GANV_CANVAS_PAD = 8.0;
103
104 struct GanvCanvasImpl;
105
106 struct IRect {
107 int x;
108 int y;
109 int width;
110 int height;
111 };
112
113 extern "C" {
114 static void add_idle(GanvCanvas* canvas);
115 static void ganv_canvas_destroy(GtkObject* object);
116 static void ganv_canvas_map(GtkWidget* widget);
117 static void ganv_canvas_unmap(GtkWidget* widget);
118 static void ganv_canvas_realize(GtkWidget* widget);
119 static void ganv_canvas_unrealize(GtkWidget* widget);
120 static void ganv_canvas_size_allocate(GtkWidget* widget,
121 GtkAllocation* allocation);
122 static gint ganv_canvas_button(GtkWidget* widget,
123 GdkEventButton* event);
124 static gint ganv_canvas_motion(GtkWidget* widget,
125 GdkEventMotion* event);
126 static gint ganv_canvas_expose(GtkWidget* widget,
127 GdkEventExpose* event);
128 static gboolean ganv_canvas_key(GtkWidget* widget,
129 GdkEventKey* event);
130 static gboolean ganv_canvas_scroll(GtkWidget* widget,
131 GdkEventScroll* event);
132 static gint ganv_canvas_crossing(GtkWidget* widget,
133 GdkEventCrossing* event);
134 static gint ganv_canvas_focus_in(GtkWidget* widget,
135 GdkEventFocus* event);
136 static gint ganv_canvas_focus_out(GtkWidget* widget,
137 GdkEventFocus* event);
138
139 static GtkLayoutClass* canvas_parent_class;
140 }
141
142 static guint signal_connect;
143 static guint signal_disconnect;
144
145 static GEnumValue dir_values[3];
146
147 using Items = std::set<GanvNode*>;
148
149 #define FOREACH_ITEM(items, i) \
150 for (Items::const_iterator i = items.begin(); i != items.end(); ++i)
151
152 #define FOREACH_ITEM_MUT(items, i) \
153 for (Items::iterator i = items.begin(); i != items.end(); ++i)
154
155 #define FOREACH_EDGE(edges, i) \
156 for (GanvCanvasImpl::Edges::const_iterator i = edges.begin(); \
157 i != edges.end(); \
158 ++i)
159
160 #define FOREACH_EDGE_MUT(edges, i) \
161 for (GanvCanvasImpl::Edges::iterator i = edges.begin(); \
162 i != edges.end(); \
163 ++i)
164
165 #define FOREACH_SELECTED_EDGE(edges, i) \
166 for (GanvCanvasImpl::SelectedEdges::const_iterator i = edges.begin(); \
167 i != edges.end(); \
168 ++i)
169
170 #define FOREACH_SELECTED_PORT(p) \
171 for (SelectedPorts::iterator p = _selected_ports.begin(); \
172 p != _selected_ports.end(); ++p)
173
174 #ifdef HAVE_AGRAPH
175 class GVNodes : public std::map<GanvNode*, Agnode_t*> {
176 public:
GVNodes()177 GVNodes() : gvc(0), G(0) {}
178
cleanup()179 void cleanup() {
180 gvFreeLayout(gvc, G);
181 agclose (G);
182 gvc = 0;
183 G = 0;
184 }
185
186 GVC_t* gvc;
187 Agraph_t* G;
188 };
189 #endif
190
191 static const uint32_t SELECT_RECT_FILL_COLOUR = 0x2E444577;
192 static const uint32_t SELECT_RECT_BORDER_COLOUR = 0x2E4445FF;
193
194 /* Order edges by (tail, head) */
195 struct TailHeadOrder {
operator ()TailHeadOrder196 inline bool operator()(const GanvEdge* a, const GanvEdge* b) const {
197 return ((a->impl->tail < b->impl->tail)
198 || (a->impl->tail == b->impl->tail
199 && a->impl->head < b->impl->head));
200 }
201 };
202
203 /* Order edges by (head, tail) */
204 struct HeadTailOrder {
operator ()HeadTailOrder205 inline bool operator()(const GanvEdge* a, const GanvEdge* b) const {
206 return ((a->impl->head < b->impl->head)
207 || (a->impl->head == b->impl->head
208 && a->impl->tail < b->impl->tail));
209 }
210 };
211
212 /* Callback used when the root item of a canvas is destroyed. The user should
213 * never ever do this, so we panic if this happens.
214 */
215 static void
panic_root_destroyed(GtkObject * object,gpointer data)216 panic_root_destroyed(GtkObject* object, gpointer data)
217 {
218 g_error("Eeeek, root item %p of canvas %p was destroyed!", (void*)object, data);
219 }
220
221 struct GanvCanvasImpl {
GanvCanvasImplGanvCanvasImpl222 explicit GanvCanvasImpl(GanvCanvas* canvas)
223 : _gcanvas(canvas)
224 , _wrapper(NULL)
225 , _connect_port(NULL)
226 , _last_selected_port(NULL)
227 , _drag_edge(NULL)
228 , _drag_node(NULL)
229 , _select_rect(NULL)
230 , _select_start_x(0.0)
231 , _select_start_y(0.0)
232 , _drag_state(NOT_DRAGGING)
233 {
234 this->root = GANV_ITEM(g_object_new(ganv_group_get_type(), NULL));
235 this->root->impl->canvas = canvas;
236 g_object_ref_sink(this->root);
237
238 this->direction = GANV_DIRECTION_RIGHT;
239 this->width = 0;
240 this->height = 0;
241
242 this->redraw_region = NULL;
243 this->current_item = NULL;
244 this->new_current_item = NULL;
245 this->grabbed_item = NULL;
246 this->focused_item = NULL;
247 this->pixmap_gc = NULL;
248
249 this->pick_event.type = GDK_LEAVE_NOTIFY;
250 this->pick_event.crossing.x = 0;
251 this->pick_event.crossing.y = 0;
252
253 this->scroll_x1 = 0.0;
254 this->scroll_y1 = 0.0;
255 this->scroll_x2 = canvas->layout.width;
256 this->scroll_y2 = canvas->layout.height;
257
258 this->pixels_per_unit = 1.0;
259 this->font_size = ganv_canvas_get_default_font_size(canvas);
260
261 this->idle_id = 0;
262 this->root_destroy_id = g_signal_connect(
263 this->root, "destroy", G_CALLBACK(panic_root_destroyed), canvas);
264
265 this->redraw_x1 = 0;
266 this->redraw_y1 = 0;
267 this->redraw_x2 = 0;
268 this->redraw_y2 = 0;
269
270 this->draw_xofs = 0;
271 this->draw_yofs = 0;
272 this->zoom_xofs = 0;
273 this->zoom_yofs = 0;
274
275 this->state = 0;
276 this->grabbed_event_mask = 0;
277
278 this->center_scroll_region = FALSE;
279 this->need_update = FALSE;
280 this->need_redraw = FALSE;
281 this->need_repick = TRUE;
282 this->left_grabbed_item = FALSE;
283 this->in_repick = FALSE;
284 this->locked = FALSE;
285 this->exporting = FALSE;
286
287 #ifdef GANV_FDGL
288 this->layout_idle_id = 0;
289 this->layout_energy = 0.4;
290 this->sprung_layout = FALSE;
291 #endif
292
293 _animate_idle_id = 0;
294
295 _port_order.port_cmp = NULL;
296 _port_order.data = NULL;
297
298 gtk_layout_set_hadjustment(GTK_LAYOUT(canvas), NULL);
299 gtk_layout_set_vadjustment(GTK_LAYOUT(canvas), NULL);
300
301 _move_cursor = gdk_cursor_new(GDK_FLEUR);
302 }
303
304 GanvCanvasImpl(const GanvCanvasImpl&) = delete;
305 GanvCanvasImpl& operator=(const GanvCanvasImpl&) = delete;
306
307 GanvCanvasImpl(GanvCanvasImpl&&) = delete;
308 GanvCanvasImpl& operator=(GanvCanvasImpl&&) = delete;
309
~GanvCanvasImplGanvCanvasImpl310 ~GanvCanvasImpl()
311 {
312 if (_animate_idle_id) {
313 g_source_remove(_animate_idle_id);
314 _animate_idle_id = 0;
315 }
316
317 while (g_idle_remove_by_data(this)) {}
318 ganv_canvas_clear(_gcanvas);
319 gdk_cursor_unref(_move_cursor);
320 }
321
322 static gboolean on_animate_timeout(gpointer impl);
323
324 #ifdef GANV_FDGL
on_layout_timeoutGanvCanvasImpl325 static gboolean on_layout_timeout(gpointer impl) {
326 return ((GanvCanvasImpl*)impl)->layout_iteration();
327 }
328
on_layout_doneGanvCanvasImpl329 static void on_layout_done(gpointer impl) {
330 ((GanvCanvasImpl*)impl)->layout_idle_id = 0;
331 }
332
333 gboolean layout_iteration();
334 gboolean layout_calculate(double dur, bool update);
335 #endif
336
337 void unselect_ports();
338
339 #ifdef HAVE_AGRAPH
340 GVNodes layout_dot(const std::string& filename);
341 #endif
342
343 using Edges = std::set<GanvEdge*, TailHeadOrder>;
344 using DstEdges = std::set<GanvEdge*, HeadTailOrder>;
345 using SelectedEdges = std::set<GanvEdge*>;
346 using SelectedPorts = std::set<GanvPort*>;
347
348 Edges::const_iterator first_edge_from(const GanvNode* src);
349 DstEdges::const_iterator first_edge_to(const GanvNode* dst);
350
351 void select_port(GanvPort* p, bool unique=false);
352 void select_port_toggle(GanvPort* p, int mod_state);
353 void unselect_port(GanvPort* p);
354 void selection_joined_with(GanvPort* port);
355 void join_selection();
356
357 GanvNode* get_node_at(double x, double y);
358
359 bool on_event(GdkEvent* event);
360
361 bool scroll_drag_handler(GdkEvent* event);
362 bool select_drag_handler(GdkEvent* event);
363 bool connect_drag_handler(GdkEvent* event);
364 void end_connect_drag();
365
366 /*
367 Event handler for ports.
368
369 This must be implemented as a Canvas method since port event handling
370 depends on shared data (for selection and connecting). This function
371 should only be used by Port implementations.
372 */
373 bool port_event(GdkEvent* event, GanvPort* port);
374
375 void ports_joined(GanvPort* port1, GanvPort* port2);
376 void port_clicked(GdkEvent* event, GanvPort* port);
377 void highlight_port(GanvPort* port, bool highlight);
378
379 void move_contents_to_internal(double x, double y, double min_x, double min_y);
380
381 GanvCanvas* _gcanvas;
382 Ganv::Canvas* _wrapper;
383
384 Items _items; ///< Items on this canvas
385 Edges _edges; ///< Edges ordered (src, dst)
386 DstEdges _dst_edges; ///< Edges ordered (dst, src)
387 Items _selected_items; ///< Currently selected items
388 SelectedEdges _selected_edges; ///< Currently selected edges
389
390 SelectedPorts _selected_ports; ///< Selected ports (hilited red)
391 GanvPort* _connect_port; ///< Port for which a edge is being made
392 GanvPort* _last_selected_port;
393 GanvEdge* _drag_edge;
394 GanvNode* _drag_node;
395
396 GanvBox* _select_rect; ///< Rectangle for drag selection
397 double _select_start_x; ///< Selection drag start x coordinate
398 double _select_start_y; ///< Selection drag start y coordinate
399
400 enum DragState { NOT_DRAGGING, EDGE, SCROLL, SELECT };
401 DragState _drag_state;
402
403 GdkCursor* _move_cursor;
404 guint _animate_idle_id;
405
406 PortOrderCtx _port_order;
407
408 /* Root canvas item */
409 GanvItem* root;
410
411 /* Flow direction */
412 GanvDirection direction;
413
414 /* Canvas width */
415 double width;
416
417 /* Canvas height */
418 double height;
419
420 /* Region that needs redrawing (list of rectangles) */
421 GSList* redraw_region;
422
423 /* The item containing the mouse pointer, or NULL if none */
424 GanvItem* current_item;
425
426 /* Item that is about to become current (used to track deletions and such) */
427 GanvItem* new_current_item;
428
429 /* Item that holds a pointer grab, or NULL if none */
430 GanvItem* grabbed_item;
431
432 /* If non-NULL, the currently focused item */
433 GanvItem* focused_item;
434
435 /* GC for temporary draw pixmap */
436 GdkGC* pixmap_gc;
437
438 /* Event on which selection of current item is based */
439 GdkEvent pick_event;
440
441 /* Scrolling region */
442 double scroll_x1;
443 double scroll_y1;
444 double scroll_x2;
445 double scroll_y2;
446
447 /* Scaling factor to be used for display */
448 double pixels_per_unit;
449
450 /* Font size in points */
451 double font_size;
452
453 /* Idle handler ID */
454 guint idle_id;
455
456 /* Signal handler ID for destruction of the root item */
457 guint root_destroy_id;
458
459 /* Area that is being redrawn. Contains (x1, y1) but not (x2, y2).
460 * Specified in canvas pixel coordinates.
461 */
462 int redraw_x1;
463 int redraw_y1;
464 int redraw_x2;
465 int redraw_y2;
466
467 /* Offsets of the temprary drawing pixmap */
468 int draw_xofs;
469 int draw_yofs;
470
471 /* Internal pixel offsets when zoomed out */
472 int zoom_xofs;
473 int zoom_yofs;
474
475 /* Last known modifier state, for deferred repick when a button is down */
476 int state;
477
478 /* Event mask specified when grabbing an item */
479 guint grabbed_event_mask;
480
481 /* Whether the canvas should center the scroll region in the middle of
482 * the window if the scroll region is smaller than the window.
483 */
484 gboolean center_scroll_region;
485
486 /* Whether items need update at next idle loop iteration */
487 gboolean need_update;
488
489 /* Whether the canvas needs redrawing at the next idle loop iteration */
490 gboolean need_redraw;
491
492 /* Whether current item will be repicked at next idle loop iteration */
493 gboolean need_repick;
494
495 /* For use by internal pick_current_item() function */
496 gboolean left_grabbed_item;
497
498 /* For use by internal pick_current_item() function */
499 gboolean in_repick;
500
501 /* Disable changes to canvas */
502 gboolean locked;
503
504 /* True if the current draw is an export */
505 gboolean exporting;
506
507 #ifdef GANV_FDGL
508 guint layout_idle_id;
509 gdouble layout_energy;
510 gboolean sprung_layout;
511 #endif
512 };
513
514 struct GanvEdgeKey {
515 GanvItem item;
516 GanvEdgePrivate* impl;
517 GanvEdgePrivate impl_data;
518 };
519
520 static void
make_edge_search_key(GanvEdgeKey * key,const GanvNode * tail,const GanvNode * head)521 make_edge_search_key(GanvEdgeKey* key,
522 const GanvNode* tail,
523 const GanvNode* head)
524 {
525 memset(key, '\0', sizeof(GanvEdgeKey));
526 key->impl = &key->impl_data;
527 key->impl->tail = const_cast<GanvNode*>(tail);
528 key->impl->head = const_cast<GanvNode*>(head);
529 }
530
531 GanvCanvasImpl::Edges::const_iterator
first_edge_from(const GanvNode * tail)532 GanvCanvasImpl::first_edge_from(const GanvNode* tail)
533 {
534 GanvEdgeKey key;
535 make_edge_search_key(&key, tail, NULL);
536 return _edges.lower_bound((GanvEdge*)&key);
537 }
538
539 GanvCanvasImpl::DstEdges::const_iterator
first_edge_to(const GanvNode * head)540 GanvCanvasImpl::first_edge_to(const GanvNode* head)
541 {
542 GanvEdgeKey key;
543 make_edge_search_key(&key, NULL, head);
544 return _dst_edges.lower_bound((GanvEdge*)&key);
545 }
546
547 static void
select_if_tail_is_selected(GanvEdge * edge,void *)548 select_if_tail_is_selected(GanvEdge* edge, void*)
549 {
550 GanvNode* tail = edge->impl->tail;
551 gboolean selected = FALSE;
552 g_object_get(tail, "selected", &selected, NULL);
553 if (!selected && GANV_IS_PORT(tail)) {
554 g_object_get(ganv_port_get_module(GANV_PORT(tail)),
555 "selected", &selected, NULL);
556 }
557
558 if (selected) {
559 ganv_edge_select(edge);
560 }
561 }
562
563 static void
select_if_head_is_selected(GanvEdge * edge,void *)564 select_if_head_is_selected(GanvEdge* edge, void*)
565 {
566 GanvNode* head = edge->impl->head;
567 gboolean selected = FALSE;
568 g_object_get(head, "selected", &selected, NULL);
569 if (!selected && GANV_IS_PORT(head)) {
570 g_object_get(ganv_port_get_module(GANV_PORT(head)),
571 "selected", &selected, NULL);
572 }
573
574 if (selected) {
575 ganv_edge_set_selected(edge, TRUE);
576 }
577 }
578
579 static void
select_edges(GanvPort * port,void * data)580 select_edges(GanvPort* port, void* data)
581 {
582 GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
583 if (port->impl->is_input) {
584 ganv_canvas_for_each_edge_to(impl->_gcanvas,
585 GANV_NODE(port),
586 select_if_tail_is_selected,
587 NULL);
588 } else {
589 ganv_canvas_for_each_edge_from(impl->_gcanvas,
590 GANV_NODE(port),
591 select_if_head_is_selected,
592 NULL);
593 }
594 }
595
596 #ifdef HAVE_AGRAPH
597 static void
gv_set(void * subject,const char * key,double value)598 gv_set(void* subject, const char* key, double value)
599 {
600 std::ostringstream ss;
601 ss << value;
602 agsafeset(subject, (char*)key, (char*)ss.str().c_str(), (char*)"");
603 }
604
605 GVNodes
layout_dot(const std::string & filename)606 GanvCanvasImpl::layout_dot(const std::string& filename)
607 {
608 GVNodes nodes;
609
610 const double dpi = gdk_screen_get_resolution(gdk_screen_get_default());
611
612 GVC_t* gvc = gvContext();
613
614 Agraph_t* G = agopen((char*)"g", Agdirected, NULL);
615
616 agsafeset(G, (char*)"splines", (char*)"false", (char*)"");
617 agsafeset(G, (char*)"compound", (char*)"true", (char*)"");
618 agsafeset(G, (char*)"remincross", (char*)"true", (char*)"");
619 agsafeset(G, (char*)"overlap", (char*)"scale", (char*)"");
620 agsafeset(G, (char*)"nodesep", (char*)"0.05", (char*)"");
621 gv_set(G, "fontsize", ganv_canvas_get_font_size(_gcanvas));
622 gv_set(G, "dpi", dpi);
623
624 nodes.gvc = gvc;
625 nodes.G = G;
626
627 const bool flow_right = _gcanvas->impl->direction;
628 if (flow_right) {
629 agattr(G, AGRAPH, (char*)"rankdir", (char*)"LR");
630 } else {
631 agattr(G, AGRAPH, (char*)"rankdir", (char*)"TD");
632 }
633
634 unsigned id = 0;
635 std::ostringstream ss;
636 FOREACH_ITEM(_items, i) {
637 ss.str("");
638 ss << "n" << id++;
639 const std::string node_id = ss.str();
640
641 Agnode_t* node = agnode(G, strdup(node_id.c_str()), true);
642 nodes.insert(std::make_pair(*i, node));
643
644 if (GANV_IS_MODULE(*i)) {
645 GanvModule* const m = GANV_MODULE(*i);
646
647 agsafeset(node, (char*)"shape", (char*)"plaintext", (char*)"");
648 gv_set(node, "width", ganv_box_get_width(GANV_BOX(*i)) / dpi);
649 gv_set(node, "height", ganv_box_get_height(GANV_BOX(*i)) / dpi);
650
651 std::string inputs; // Down flow
652 std::string outputs; // Down flow
653 std::string ports; // Right flow
654 unsigned n_inputs = 0;
655 unsigned n_outputs = 0;
656 for (size_t i = 0; i < ganv_module_num_ports(m); ++i) {
657 GanvPort* port = ganv_module_get_port(m, i);
658 ss.str("");
659 ss << port;
660
661 if (port->impl->is_input) {
662 ++n_inputs;
663 } else {
664 ++n_outputs;
665 }
666
667 std::string cell = std::string("<TD PORT=\"") + ss.str() + "\"";
668
669 cell += " FIXEDSIZE=\"TRUE\"";
670 ss.str("");
671 ss << ganv_box_get_width(GANV_BOX(port));// / dpp * 1.3333333;
672 cell += " WIDTH=\"" + ss.str() + "\"";
673
674 ss.str("");
675 ss << ganv_box_get_height(GANV_BOX(port));// / dpp * 1.333333;
676 cell += " HEIGHT=\"" + ss.str() + "\"";
677
678 cell += ">";
679 const char* label = ganv_node_get_label(GANV_NODE(port));
680 if (label && flow_right) {
681 cell += label;
682 }
683 cell += "</TD>";
684
685 if (flow_right) {
686 ports += "<TR>" + cell + "</TR>";
687 } else if (port->impl->is_input) {
688 inputs += cell;
689 } else {
690 outputs += cell;
691 }
692
693 nodes.insert(std::make_pair(GANV_NODE(port), node));
694 }
695
696 const unsigned n_cols = std::max(n_inputs, n_outputs);
697
698 std::string html = "<TABLE CELLPADDING=\"0\" CELLSPACING=\"0\">";
699
700 // Input row (down flow only)
701 if (!inputs.empty()) {
702 for (unsigned i = n_inputs; i < n_cols + 1; ++i) {
703 inputs += "<TD BORDER=\"0\"></TD>";
704 }
705 html += std::string("<TR>") + inputs + "</TR>";
706 }
707
708 // Label row
709 std::ostringstream colspan;
710 colspan << (flow_right ? 1 : (n_cols + 1));
711 html += std::string("<TR><TD BORDER=\"0\" CELLPADDING=\"2\" COLSPAN=\"")
712 + colspan.str()
713 + "\">";
714 const char* label = ganv_node_get_label(GANV_NODE(m));
715 if (label) {
716 html += label;
717 }
718 html += "</TD></TR>";
719
720 // Ports rows (right flow only)
721 if (!ports.empty()) {
722 html += ports;
723 }
724
725 // Output row (down flow only)
726 if (!outputs.empty()) {
727 for (unsigned i = n_outputs; i < n_cols + 1; ++i) {
728 outputs += "<TD BORDER=\"0\"></TD>";
729 }
730 html += std::string("<TR>") + outputs + "</TR>";
731 }
732 html += "</TABLE>";
733
734 char* html_label_str = agstrdup_html(G, (char*)html.c_str());
735
736 agsafeset(node, (char*)"label", (char*)html_label_str, (char*)"");
737 } else if (GANV_IS_CIRCLE(*i)) {
738 agsafeset(node, (char*)"shape", (char*)"circle", (char*)"");
739 agsafeset(node, (char*)"fixedsize", (char*)"true", (char*)"");
740 agsafeset(node, (char*)"margin", (char*)"0.0,0.0", (char*)"");
741
742 const double radius = ganv_circle_get_radius(GANV_CIRCLE(*i));
743 const double penwidth = ganv_node_get_border_width(GANV_NODE(*i));
744 const double span = (radius + penwidth) * 2.3 / dpi;
745 gv_set(node, (char*)"width", span);
746 gv_set(node, (char*)"height", span);
747 gv_set(node, (char*)"penwidth", penwidth);
748
749 if (ganv_node_get_dash_length(GANV_NODE(*i)) > 0.0) {
750 agsafeset(node, (char*)"style", (char*)"dashed", (char*)"");
751 }
752
753 const char* label = ganv_node_get_label(GANV_NODE(*i));
754 if (label) {
755 agsafeset(node, (char*)"label", (char*)label, (char*)"");
756 } else {
757 agsafeset(node, (char*)"label", (char*)"", (char*)"");
758 }
759 } else {
760 std::cerr << "Unable to arrange item of unknown type" << std::endl;
761 }
762 }
763
764 FOREACH_EDGE(_edges, i) {
765 const GanvEdge* const edge = *i;
766 GVNodes::iterator tail_i = nodes.find(edge->impl->tail);
767 GVNodes::iterator head_i = nodes.find(edge->impl->head);
768
769 if (tail_i != nodes.end() && head_i != nodes.end()) {
770 Agedge_t* e = agedge(G, tail_i->second, head_i->second, NULL, true);
771 if (GANV_IS_PORT(edge->impl->tail)) {
772 ss.str((char*)"");
773 ss << edge->impl->tail << (flow_right ? ":e" : ":s");
774 agsafeset(e, (char*)"tailport", (char*)ss.str().c_str(), (char*)"");
775 }
776 if (GANV_IS_PORT(edge->impl->head)) {
777 ss.str((char*)"");
778 ss << edge->impl->head << (flow_right ? ":w" : ":n");
779 agsafeset(e, (char*)"headport", (char*)ss.str().c_str(), (char*)"");
780 }
781 if (!ganv_edge_get_constraining(edge)) {
782 agsafeset(e, (char*)"constraint", (char*)"false", (char*)"");
783 }
784 } else {
785 std::cerr << "Unable to find graphviz node" << std::endl;
786 }
787 }
788
789 // Add edges between partners to have them lined up as if connected
790 for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) {
791 GanvNode* partner = ganv_node_get_partner(i->first);
792 if (partner) {
793 GVNodes::iterator p = nodes.find(partner);
794 if (p != nodes.end()) {
795 Agedge_t* e = agedge(G, i->second, p->second, NULL, true);
796 agsafeset(e, (char*)"style", (char*)"invis", (char*)"");
797 }
798 }
799 }
800
801 gvLayout(gvc, G, (char*)"dot");
802 FILE* tmp = fopen("/dev/null", "w");
803 gvRender(gvc, G, (char*)"dot", tmp);
804 fclose(tmp);
805
806 if (filename != "") {
807 FILE* fd = fopen(filename.c_str(), "w");
808 gvRender(gvc, G, (char*)"dot", fd);
809 fclose(fd);
810 }
811
812 return nodes;
813 }
814 #endif
815
816 inline uint64_t
get_monotonic_time()817 get_monotonic_time()
818 {
819 #if GLIB_CHECK_VERSION(2, 28, 0)
820 return g_get_monotonic_time();
821 #else
822 GTimeVal time;
823 g_get_current_time(&time);
824 return time.tv_sec + time.tv_usec;
825 #endif
826 }
827
828 #ifdef GANV_FDGL
829
830 inline Region
get_region(GanvNode * node)831 get_region(GanvNode* node)
832 {
833 GanvItem* item = &node->item;
834
835 double x1 = 0.0;
836 double y1 = 0.0;
837 double x2 = 0.0;
838 double y2 = 0.0;
839 ganv_item_get_bounds(item, &x1, &y1, &x2, &y2);
840
841 Region reg;
842 ganv_item_get_bounds(item, ®.pos.x, ®.pos.y, ®.area.x, ®.area.y);
843 reg.area.x = x2 - x1;
844 reg.area.y = y2 - y1;
845 reg.pos.x = item->impl->x + (reg.area.x / 2.0);
846 reg.pos.y = item->impl->y + (reg.area.y / 2.0);
847
848 // No need for i2w here since we only care about top-level items
849 return reg;
850 }
851
852 inline void
apply_force(GanvNode * a,GanvNode * b,const Vector & f)853 apply_force(GanvNode* a, GanvNode* b, const Vector& f)
854 {
855 a->impl->force = vec_add(a->impl->force, f);
856 b->impl->force = vec_sub(b->impl->force, f);
857 }
858
859 gboolean
layout_iteration()860 GanvCanvasImpl::layout_iteration()
861 {
862 if (_drag_state == EDGE) {
863 return FALSE; // Canvas is locked, halt layout process
864 } else if (!sprung_layout) {
865 return FALSE; // We shouldn't be running at all
866 }
867
868 static const double T_PER_US = .0001; // Sym time per real microsecond
869
870 static uint64_t prev = 0; // Previous iteration time
871
872 const uint64_t now = get_monotonic_time();
873 const double time_to_run = std::min((now - prev) * T_PER_US, 10.0);
874
875 prev = now;
876
877 const double QUANTUM = 0.05;
878 double sym_time = 0.0;
879 while (sym_time + QUANTUM < time_to_run) {
880 if (!layout_calculate(QUANTUM, FALSE)) {
881 break;
882 }
883 sym_time += QUANTUM;
884 }
885
886 return layout_calculate(QUANTUM, TRUE);
887 }
888
889 gboolean
layout_calculate(double dur,bool update)890 GanvCanvasImpl::layout_calculate(double dur, bool update)
891 {
892 // A light directional force to push sources to the top left
893 static const double DIR_MAGNITUDE = -1000.0;
894 Vector dir = { 0.0, 0.0 };
895 switch (_gcanvas->impl->direction) {
896 case GANV_DIRECTION_RIGHT: dir.x = DIR_MAGNITUDE; break;
897 case GANV_DIRECTION_DOWN: dir.y = DIR_MAGNITUDE; break;
898 }
899
900 // Calculate attractive spring forces for edges
901 FOREACH_EDGE(_edges, i) {
902 const GanvEdge* const edge = *i;
903 if (!ganv_edge_get_constraining(edge)) {
904 continue;
905 }
906
907 GanvNode* tail = ganv_edge_get_tail(edge);
908 GanvNode* head = ganv_edge_get_head(edge);
909 if (GANV_IS_PORT(tail)) {
910 tail = GANV_NODE(ganv_port_get_module(GANV_PORT(tail)));
911 }
912 if (GANV_IS_PORT(head)) {
913 head = GANV_NODE(ganv_port_get_module(GANV_PORT(head)));
914 }
915 if (tail == head) {
916 continue;
917 }
918
919 head->impl->connected = tail->impl->connected = TRUE;
920
921 GanvEdgeCoords coords;
922 ganv_edge_get_coords(edge, &coords);
923
924 const Vector tpos = { coords.x1, coords.y1 };
925 const Vector hpos = { coords.x2, coords.y2 };
926 apply_force(tail, head, edge_force(dir, hpos, tpos));
927 }
928
929 // Calculate repelling forces between nodes
930 FOREACH_ITEM(_items, i) {
931 if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) {
932 continue;
933 }
934
935 GanvNode* const node = *i;
936 GanvNode* partner = ganv_node_get_partner(node);
937 if (!partner && !node->impl->connected) {
938 continue;
939 }
940
941 const Region reg = get_region(node);
942 if (partner) {
943 // Add fake long spring to partner to line up as if connected
944 const Region preg = get_region(partner);
945 apply_force(node, partner, edge_force(dir, preg.pos, reg.pos));
946 }
947
948 /* Add tide force which pulls all objects as if the layout is happening
949 on a flowing river surface. This prevents disconnected components
950 from being ejected, since at some point the tide force will be
951 greater than distant repelling charges. */
952 const Vector mouth = { -100000.0, -100000.0 };
953 node->impl->force = vec_add(
954 node->impl->force,
955 tide_force(mouth, reg.pos, 4000000000000.0));
956
957 // Add slight noise to force to limit oscillation
958 const Vector noise = { rand() / (float)RAND_MAX * 128.0,
959 rand() / (float)RAND_MAX * 128.0 };
960 node->impl->force = vec_add(noise, node->impl->force);
961
962 FOREACH_ITEM(_items, j) {
963 if (i == j || (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i))) {
964 continue;
965 }
966 apply_force(node, *j, repel_force(reg, get_region(*j)));
967 }
968 }
969
970 // Update positions based on calculated forces
971 size_t n_moved = 0;
972 FOREACH_ITEM(_items, i) {
973 if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) {
974 continue;
975 }
976
977 GanvNode* const node = *i;
978
979 if (node->impl->grabbed ||
980 (!node->impl->connected && !ganv_node_get_partner(node))) {
981 node->impl->vel.x = 0.0;
982 node->impl->vel.y = 0.0;
983 } else {
984 node->impl->vel = vec_add(node->impl->vel,
985 vec_mult(node->impl->force, dur));
986 node->impl->vel = vec_mult(node->impl->vel, layout_energy);
987
988 static const double MAX_VEL = 1000.0;
989 static const double MIN_COORD = 4.0;
990
991 // Clamp velocity
992 const double vel_mag = vec_mag(node->impl->vel);
993 if (vel_mag > MAX_VEL) {
994 node->impl->vel = vec_mult(
995 vec_mult(node->impl->vel, 1.0 / vel_mag),
996 MAX_VEL);
997 }
998
999 // Update position
1000 GanvItem* item = &node->item;
1001 const double x0 = item->impl->x;
1002 const double y0 = item->impl->y;
1003 const Vector dpos = vec_mult(node->impl->vel, dur);
1004
1005 item->impl->x = std::max(MIN_COORD, item->impl->x + dpos.x);
1006 item->impl->y = std::max(MIN_COORD, item->impl->y + dpos.y);
1007
1008 if (update) {
1009 ganv_item_request_update(item);
1010 item->impl->canvas->impl->need_repick = TRUE;
1011 }
1012
1013 if (lrint(x0) != lrint(item->impl->x) || lrint(y0) != lrint(item->impl->y)) {
1014 ++n_moved;
1015 }
1016 }
1017
1018 // Reset forces for next time
1019 node->impl->force.x = 0.0;
1020 node->impl->force.y = 0.0;
1021 node->impl->connected = FALSE;
1022 }
1023
1024 if (update) {
1025 // Now update edge positions to reflect new node positions
1026 FOREACH_EDGE(_edges, i) {
1027 GanvEdge* const edge = *i;
1028 ganv_edge_update_location(edge);
1029 }
1030 }
1031
1032 layout_energy *= 0.999;
1033 return n_moved > 0;
1034 }
1035
1036 #endif // GANV_FDGL
1037
1038 void
select_port(GanvPort * p,bool unique)1039 GanvCanvasImpl::select_port(GanvPort* p, bool unique)
1040 {
1041 if (unique) {
1042 unselect_ports();
1043 }
1044 g_object_set(G_OBJECT(p), "selected", TRUE, NULL);
1045 _selected_ports.insert(p);
1046 _last_selected_port = p;
1047 }
1048
1049 void
select_port_toggle(GanvPort * port,int mod_state)1050 GanvCanvasImpl::select_port_toggle(GanvPort* port, int mod_state)
1051 {
1052 gboolean selected = FALSE;
1053 g_object_get(G_OBJECT(port), "selected", &selected, NULL);
1054 if ((mod_state & GDK_CONTROL_MASK)) {
1055 if (selected)
1056 unselect_port(port);
1057 else
1058 select_port(port);
1059 } else if ((mod_state & GDK_SHIFT_MASK)) {
1060 GanvModule* const m = ganv_port_get_module(port);
1061 if (_last_selected_port && m
1062 && ganv_port_get_module(_last_selected_port) == m) {
1063 // Pivot around _last_selected_port in a single pass over module ports each click
1064 GanvPort* old_last_selected = _last_selected_port;
1065 GanvPort* first = NULL;
1066 bool done = false;
1067 for (size_t i = 0; i < ganv_module_num_ports(m); ++i) {
1068 GanvPort* const p = ganv_module_get_port(m, i);
1069 if (!first && !done && (p == _last_selected_port || p == port)) {
1070 first = p;
1071 }
1072
1073 if (first && !done && p->impl->is_input == first->impl->is_input) {
1074 select_port(p, false);
1075 } else {
1076 unselect_port(p);
1077 }
1078
1079 if (p != first && (p == old_last_selected || p == port)) {
1080 done = true;
1081 }
1082 }
1083 _last_selected_port = old_last_selected;
1084 } else {
1085 if (selected) {
1086 unselect_port(port);
1087 } else {
1088 select_port(port);
1089 }
1090 }
1091 } else {
1092 if (selected) {
1093 unselect_ports();
1094 } else {
1095 select_port(port, true);
1096 }
1097 }
1098 }
1099
1100 void
unselect_port(GanvPort * p)1101 GanvCanvasImpl::unselect_port(GanvPort* p)
1102 {
1103 _selected_ports.erase(p);
1104 g_object_set(G_OBJECT(p), "selected", FALSE, NULL);
1105 if (_last_selected_port == p) {
1106 _last_selected_port = NULL;
1107 }
1108 }
1109
1110 void
selection_joined_with(GanvPort * port)1111 GanvCanvasImpl::selection_joined_with(GanvPort* port)
1112 {
1113 FOREACH_SELECTED_PORT(i)
1114 ports_joined(*i, port);
1115 }
1116
1117 void
join_selection()1118 GanvCanvasImpl::join_selection()
1119 {
1120 std::vector<GanvPort*> inputs;
1121 std::vector<GanvPort*> outputs;
1122 FOREACH_SELECTED_PORT(i) {
1123 if ((*i)->impl->is_input) {
1124 inputs.push_back(*i);
1125 } else {
1126 outputs.push_back(*i);
1127 }
1128 }
1129
1130 if (inputs.size() == 1) { // 1 -> n
1131 for (size_t i = 0; i < outputs.size(); ++i)
1132 ports_joined(inputs[0], outputs[i]);
1133 } else if (outputs.size() == 1) { // n -> 1
1134 for (size_t i = 0; i < inputs.size(); ++i)
1135 ports_joined(inputs[i], outputs[0]);
1136 } else { // n -> m
1137 size_t num_to_connect = std::min(inputs.size(), outputs.size());
1138 for (size_t i = 0; i < num_to_connect; ++i) {
1139 ports_joined(inputs[i], outputs[i]);
1140 }
1141 }
1142 }
1143
1144 GanvNode*
get_node_at(double x,double y)1145 GanvCanvasImpl::get_node_at(double x, double y)
1146 {
1147 GanvItem* item = ganv_canvas_get_item_at(GANV_CANVAS(_gcanvas), x, y);
1148 while (item) {
1149 if (GANV_IS_NODE(item)) {
1150 return GANV_NODE(item);
1151 } else {
1152 item = item->impl->parent;
1153 }
1154 }
1155
1156 return NULL;
1157 }
1158
1159 bool
on_event(GdkEvent * event)1160 GanvCanvasImpl::on_event(GdkEvent* event)
1161 {
1162 static const int scroll_increment = 10;
1163
1164 int scroll_x = 0;
1165 int scroll_y = 0;
1166
1167 bool handled = false;
1168 switch (event->type) {
1169 case GDK_KEY_PRESS:
1170 handled = true;
1171 ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &scroll_x, &scroll_y);
1172 switch (event->key.keyval) {
1173 case GDK_Up:
1174 scroll_y -= scroll_increment;
1175 break;
1176 case GDK_Down:
1177 scroll_y += scroll_increment;
1178 break;
1179 case GDK_Left:
1180 scroll_x -= scroll_increment;
1181 break;
1182 case GDK_Right:
1183 scroll_x += scroll_increment;
1184 break;
1185 case GDK_Return:
1186 if (_selected_ports.size() > 1) {
1187 join_selection();
1188 ganv_canvas_clear_selection(_gcanvas);
1189 }
1190 break;
1191 default:
1192 handled = false;
1193 }
1194 if (handled) {
1195 ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas), scroll_x, scroll_y);
1196 return true;
1197 }
1198 break;
1199
1200 case GDK_SCROLL:
1201 if ((event->scroll.state & GDK_CONTROL_MASK)) {
1202 const double zoom = ganv_canvas_get_zoom(_gcanvas);
1203 if (event->scroll.direction == GDK_SCROLL_UP) {
1204 ganv_canvas_set_zoom(_gcanvas, zoom * 1.25);
1205 return true;
1206 } else if (event->scroll.direction == GDK_SCROLL_DOWN) {
1207 ganv_canvas_set_zoom(_gcanvas, zoom * 0.75);
1208 return true;
1209 }
1210 }
1211 break;
1212
1213 default:
1214 break;
1215 }
1216
1217 return scroll_drag_handler(event)
1218 || select_drag_handler(event)
1219 || connect_drag_handler(event);
1220 }
1221
1222 bool
scroll_drag_handler(GdkEvent * event)1223 GanvCanvasImpl::scroll_drag_handler(GdkEvent* event)
1224 {
1225 bool handled = true;
1226
1227 static int original_scroll_x = 0;
1228 static int original_scroll_y = 0;
1229 static double origin_x = 0;
1230 static double origin_y = 0;
1231 static double scroll_offset_x = 0;
1232 static double scroll_offset_y = 0;
1233 static double last_x = 0;
1234 static double last_y = 0;
1235
1236 GanvItem* root = ganv_canvas_root(_gcanvas);
1237
1238 if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
1239 ganv_canvas_grab_item(
1240 root,
1241 GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
1242 NULL, event->button.time);
1243 ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &original_scroll_x, &original_scroll_y);
1244 scroll_offset_x = 0;
1245 scroll_offset_y = 0;
1246 origin_x = event->button.x_root;
1247 origin_y = event->button.y_root;
1248 //cerr << "Origin: (" << origin_x << "," << origin_y << ")\n";
1249 last_x = origin_x;
1250 last_y = origin_y;
1251 _drag_state = SCROLL;
1252
1253 } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SCROLL) {
1254 const double x = event->motion.x_root;
1255 const double y = event->motion.y_root;
1256 const double x_offset = last_x - x;
1257 const double y_offset = last_y - y;
1258
1259 //cerr << "Coord: (" << x << "," << y << ")\n";
1260 //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n";
1261
1262 scroll_offset_x += x_offset;
1263 scroll_offset_y += y_offset;
1264 ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas),
1265 lrint(original_scroll_x + scroll_offset_x),
1266 lrint(original_scroll_y + scroll_offset_y));
1267 last_x = x;
1268 last_y = y;
1269 } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SCROLL) {
1270 ganv_canvas_ungrab_item(root, event->button.time);
1271 _drag_state = NOT_DRAGGING;
1272 } else {
1273 handled = false;
1274 }
1275
1276 return handled;
1277 }
1278
1279 static void
get_motion_coords(GdkEventMotion * motion,double * x,double * y)1280 get_motion_coords(GdkEventMotion* motion, double* x, double* y)
1281 {
1282 if (motion->is_hint) {
1283 gint px = 0;
1284 gint py = 0;
1285 GdkModifierType state = (GdkModifierType)0;
1286 gdk_window_get_pointer(motion->window, &px, &py, &state);
1287 *x = px;
1288 *y = py;
1289 } else {
1290 *x = motion->x;
1291 *y = motion->y;
1292 }
1293 }
1294
1295 bool
select_drag_handler(GdkEvent * event)1296 GanvCanvasImpl::select_drag_handler(GdkEvent* event)
1297 {
1298 GanvItem* root = ganv_canvas_root(_gcanvas);
1299 if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) {
1300 assert(_select_rect == NULL);
1301 _drag_state = SELECT;
1302 if ( !(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) )
1303 ganv_canvas_clear_selection(_gcanvas);
1304 _select_rect = GANV_BOX(
1305 ganv_item_new(
1306 root,
1307 ganv_box_get_type(),
1308 "x1", event->button.x,
1309 "y1", event->button.y,
1310 "x2", event->button.x,
1311 "y2", event->button.y,
1312 "fill-color", SELECT_RECT_FILL_COLOUR,
1313 "border-color", SELECT_RECT_BORDER_COLOUR,
1314 NULL));
1315 _select_start_x = event->button.x;
1316 _select_start_y = event->button.y;
1317 ganv_canvas_grab_item(
1318 root, GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
1319 NULL, event->button.time);
1320 return true;
1321 } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SELECT) {
1322 assert(_select_rect);
1323 double x = 0.0;
1324 double y = 0.0;
1325 get_motion_coords(&event->motion, &x, &y);
1326 _select_rect->impl->coords.x1 = MIN(_select_start_x, x);
1327 _select_rect->impl->coords.y1 = MIN(_select_start_y, y);
1328 _select_rect->impl->coords.x2 = MAX(_select_start_x, x);
1329 _select_rect->impl->coords.y2 = MAX(_select_start_y, y);
1330 ganv_item_request_update(&_select_rect->node.item);
1331 return true;
1332 } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SELECT) {
1333 // Normalize select rect
1334 ganv_box_normalize(_select_rect);
1335
1336 // Select all modules within rect
1337 FOREACH_ITEM(_items, i) {
1338 GanvNode* node = *i;
1339 if ((void*)node != (void*)_select_rect &&
1340 ganv_node_is_within(
1341 node,
1342 ganv_box_get_x1(_select_rect),
1343 ganv_box_get_y1(_select_rect),
1344 ganv_box_get_x2(_select_rect),
1345 ganv_box_get_y2(_select_rect))) {
1346 gboolean selected = FALSE;
1347 g_object_get(G_OBJECT(node), "selected", &selected, NULL);
1348 if (selected) {
1349 ganv_canvas_unselect_node(_gcanvas, node);
1350 } else {
1351 ganv_canvas_select_node(_gcanvas, node);
1352 }
1353 }
1354 }
1355
1356 // Select all edges with handles within rect
1357 FOREACH_EDGE(_edges, i) {
1358 if (ganv_edge_is_within(
1359 (*i),
1360 ganv_box_get_x1(_select_rect),
1361 ganv_box_get_y1(_select_rect),
1362 ganv_box_get_x2(_select_rect),
1363 ganv_box_get_y2(_select_rect))) {
1364 ganv_canvas_select_edge(_gcanvas, *i);
1365 }
1366 }
1367
1368 ganv_canvas_ungrab_item(root, event->button.time);
1369
1370 gtk_object_destroy(GTK_OBJECT(_select_rect));
1371 _select_rect = NULL;
1372 _drag_state = NOT_DRAGGING;
1373 return true;
1374 }
1375 return false;
1376 }
1377
1378 bool
connect_drag_handler(GdkEvent * event)1379 GanvCanvasImpl::connect_drag_handler(GdkEvent* event)
1380 {
1381 static bool snapped = false;
1382
1383 if (_drag_state != EDGE) {
1384 return false;
1385 }
1386
1387 if (event->type == GDK_MOTION_NOTIFY) {
1388 double x = 0.0;
1389 double y = 0.0;
1390 get_motion_coords(&event->motion, &x, &y);
1391
1392 if (!_drag_edge) {
1393 // Create drag edge
1394 assert(!_drag_node);
1395 assert(_connect_port);
1396
1397 _drag_node = GANV_NODE(
1398 ganv_item_new(
1399 GANV_ITEM(ganv_canvas_root(GANV_CANVAS(_gcanvas))),
1400 ganv_node_get_type(),
1401 "x", x,
1402 "y", y,
1403 NULL));
1404
1405 _drag_edge = ganv_edge_new(
1406 _gcanvas,
1407 GANV_NODE(_connect_port),
1408 _drag_node,
1409 "color", GANV_NODE(_connect_port)->impl->fill_color,
1410 "curved", TRUE,
1411 "ghost", TRUE,
1412 NULL);
1413 }
1414
1415 GanvNode* joinee = get_node_at(x, y);
1416 if (joinee && ganv_node_can_head(joinee) && joinee != _drag_node) {
1417 // Snap to item
1418 snapped = true;
1419 ganv_item_set(&_drag_edge->item, "head", joinee, NULL);
1420 } else if (snapped) {
1421 // Unsnap from item
1422 snapped = false;
1423 ganv_item_set(&_drag_edge->item, "head", _drag_node, NULL);
1424 }
1425
1426 // Update drag edge for pointer position
1427 ganv_node_move_to(_drag_node, x, y);
1428 ganv_item_request_update(GANV_ITEM(_drag_node));
1429 ganv_item_request_update(GANV_ITEM(_drag_edge));
1430
1431 return true;
1432
1433 } else if (event->type == GDK_BUTTON_RELEASE) {
1434 ganv_canvas_ungrab_item(root, event->button.time);
1435
1436 double x = event->button.x;
1437 double y = event->button.y;
1438
1439 GanvNode* joinee = get_node_at(x, y);
1440
1441 if (GANV_IS_PORT(joinee)) {
1442 if (joinee == GANV_NODE(_connect_port)) {
1443 // Drag ended on the same port it started on, port clicked
1444 if (_selected_ports.empty()) {
1445 // No selected ports, port clicked
1446 select_port(_connect_port);
1447 } else {
1448 // Connect to selected ports
1449 selection_joined_with(_connect_port);
1450 _connect_port = NULL;
1451 }
1452 } else { // drag ended on different port
1453 ports_joined(_connect_port, GANV_PORT(joinee));
1454 _connect_port = NULL;
1455 }
1456 }
1457
1458 end_connect_drag();
1459 return true;
1460 }
1461
1462 return false;
1463 }
1464
1465 void
end_connect_drag()1466 GanvCanvasImpl::end_connect_drag()
1467 {
1468 if (_connect_port) {
1469 highlight_port(_connect_port, false);
1470 }
1471 gtk_object_destroy(GTK_OBJECT(_drag_edge));
1472 gtk_object_destroy(GTK_OBJECT(_drag_node));
1473 _drag_state = NOT_DRAGGING;
1474 _connect_port = NULL;
1475 _drag_edge = NULL;
1476 _drag_node = NULL;
1477 }
1478
1479 bool
port_event(GdkEvent * event,GanvPort * port)1480 GanvCanvasImpl::port_event(GdkEvent* event, GanvPort* port)
1481 {
1482 static bool port_pressed = true;
1483 static bool port_dragging = false;
1484 static bool control_dragging = false;
1485 static double control_start_x = 0;
1486 static double control_start_y = 0;
1487 static float control_start_value = 0;
1488
1489 gboolean selected = FALSE;
1490
1491 switch (event->type) {
1492 case GDK_BUTTON_PRESS:
1493 if (event->button.button == 1) {
1494 GanvModule* const module = ganv_port_get_module(port);
1495 double port_x = event->button.x;
1496 double port_y = event->button.y;
1497 ganv_item_w2i(GANV_ITEM(port), &port_x, &port_y);
1498
1499 if (_selected_ports.empty() && module && port->impl->control &&
1500 (port->impl->is_input ||
1501 (port->impl->is_controllable &&
1502 port_x < ganv_box_get_width(GANV_BOX(port)) / 2.0))) {
1503 if (port->impl->control->is_toggle) {
1504 if (port->impl->control->value >= 0.5) {
1505 ganv_port_set_control_value_internal(port, 0.0);
1506 } else {
1507 ganv_port_set_control_value_internal(port, 1.0);
1508 }
1509 } else {
1510 control_dragging = port_pressed = true;
1511 control_start_x = event->button.x_root;
1512 control_start_y = event->button.y_root;
1513 control_start_value = ganv_port_get_control_value(port);
1514 ganv_canvas_grab_item(
1515 GANV_ITEM(port),
1516 GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
1517 NULL, event->button.time);
1518 GANV_NODE(port)->impl->grabbed = TRUE;
1519
1520 }
1521 } else if (!port->impl->is_input) {
1522 port_dragging = port_pressed = true;
1523 ganv_canvas_grab_item(
1524 GANV_ITEM(port),
1525 GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK|
1526 GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK,
1527 NULL, event->button.time);
1528 } else {
1529 port_pressed = true;
1530 ganv_canvas_grab_item(GANV_ITEM(port),
1531 GDK_BUTTON_RELEASE_MASK,
1532 NULL, event->button.time);
1533 }
1534 return true;
1535 }
1536 break;
1537
1538 case GDK_MOTION_NOTIFY:
1539 if (control_dragging) {
1540 const double mouse_x = event->button.x_root;
1541 const double mouse_y = event->button.y_root;
1542 GdkScreen* screen = gdk_screen_get_default();
1543 const int screen_width = gdk_screen_get_width(screen);
1544 const int screen_height = gdk_screen_get_height(screen);
1545 const double drag_dx = mouse_x - control_start_x;
1546 const double drag_dy = mouse_y - control_start_y;
1547 const double ythresh = 0.2; // Minimum y fraction for fine
1548
1549 const double range_x = ((drag_dx > 0)
1550 ? (screen_width - control_start_x)
1551 : control_start_x) - GANV_CANVAS_PAD;
1552
1553 const double range_y = ((drag_dy > 0)
1554 ? (screen_height - control_start_y)
1555 : control_start_y);
1556
1557 const double dx = drag_dx / range_x;
1558 const double dy = fabs(drag_dy / range_y);
1559
1560 const double value_range = (drag_dx > 0)
1561 ? port->impl->control->max - control_start_value
1562 : control_start_value - port->impl->control->min;
1563
1564 const double sens = (dy < ythresh)
1565 ? 1.0
1566 : 1.0 - fabs(drag_dy / (range_y + ythresh));
1567
1568 const double dvalue = (dx * value_range) * sens;
1569 double value = control_start_value + dvalue;
1570 if (value < port->impl->control->min) {
1571 value = port->impl->control->min;
1572 } else if (value > port->impl->control->max) {
1573 value = port->impl->control->max;
1574 }
1575 ganv_port_set_control_value_internal(port, value);
1576 return true;
1577 } else if (port_dragging) {
1578 return true;
1579 }
1580 break;
1581
1582 case GDK_BUTTON_RELEASE:
1583 if (port_pressed) {
1584 ganv_canvas_ungrab_item(GANV_ITEM(port), event->button.time);
1585 }
1586
1587 if (port_dragging) {
1588 if (_connect_port) { // dragging
1589 ports_joined(port, _connect_port);
1590 } else {
1591 port_clicked(event, port);
1592 }
1593 port_dragging = false;
1594 } else if (control_dragging) {
1595 control_dragging = false;
1596 GANV_NODE(port)->impl->grabbed = FALSE;
1597 if (event->button.x_root == control_start_x &&
1598 event->button.y_root == control_start_y) {
1599 select_port_toggle(port, event->button.state);
1600 }
1601 } else {
1602 port_clicked(event, port);
1603 }
1604 return true;
1605
1606 case GDK_ENTER_NOTIFY:
1607 g_object_get(G_OBJECT(port), "selected", &selected, NULL);
1608 if (!control_dragging && !selected) {
1609 highlight_port(port, true);
1610 return true;
1611 }
1612 break;
1613
1614 case GDK_LEAVE_NOTIFY:
1615 if (port_dragging) {
1616 _drag_state = GanvCanvasImpl::EDGE;
1617 _connect_port = port;
1618 port_dragging = false;
1619 ganv_canvas_ungrab_item(GANV_ITEM(port), event->crossing.time);
1620 ganv_canvas_grab_item(
1621 root,
1622 GDK_BUTTON_PRESS_MASK|GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
1623 NULL, event->crossing.time);
1624 return true;
1625 } else if (!control_dragging) {
1626 highlight_port(port, false);
1627 return true;
1628 }
1629 break;
1630
1631 default:
1632 break;
1633 }
1634
1635 return false;
1636 }
1637
1638 /* Called when two ports are 'joined' (connected or disconnected) */
1639 void
ports_joined(GanvPort * port1,GanvPort * port2)1640 GanvCanvasImpl::ports_joined(GanvPort* port1, GanvPort* port2)
1641 {
1642 if (port1 == port2 || !port1 || !port2 || !port1->impl || !port2->impl) {
1643 return;
1644 }
1645
1646 highlight_port(port1, false);
1647 highlight_port(port2, false);
1648
1649 GanvNode* src_node = NULL;
1650 GanvNode* dst_node = NULL;
1651
1652 if (port2->impl->is_input && !port1->impl->is_input) {
1653 src_node = GANV_NODE(port1);
1654 dst_node = GANV_NODE(port2);
1655 } else if (!port2->impl->is_input && port1->impl->is_input) {
1656 src_node = GANV_NODE(port2);
1657 dst_node = GANV_NODE(port1);
1658 } else {
1659 return;
1660 }
1661
1662 if (!ganv_canvas_get_edge(_gcanvas, src_node, dst_node)) {
1663 g_signal_emit(_gcanvas, signal_connect, 0,
1664 src_node, dst_node, NULL);
1665 } else {
1666 g_signal_emit(_gcanvas, signal_disconnect, 0,
1667 src_node, dst_node, NULL);
1668 }
1669 }
1670
1671 void
port_clicked(GdkEvent * event,GanvPort * port)1672 GanvCanvasImpl::port_clicked(GdkEvent* event, GanvPort* port)
1673 {
1674 const bool modded = event->button.state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK);
1675 if (!modded && _last_selected_port &&
1676 _last_selected_port->impl->is_input != port->impl->is_input) {
1677 selection_joined_with(port);
1678 } else {
1679 select_port_toggle(port, event->button.state);
1680 }
1681 }
1682
1683 void
highlight_port(GanvPort * port,bool highlight)1684 GanvCanvasImpl::highlight_port(GanvPort* port, bool highlight)
1685 {
1686 g_object_set(G_OBJECT(port), "highlighted", highlight, NULL);
1687 ganv_canvas_for_each_edge_on(_gcanvas,
1688 GANV_NODE(port),
1689 (highlight
1690 ? (GanvEdgeFunc)ganv_edge_highlight
1691 : (GanvEdgeFunc)ganv_edge_unhighlight),
1692 NULL);
1693 }
1694
1695 /* Update animated "rubber band" selection effect. */
1696 gboolean
on_animate_timeout(gpointer data)1697 GanvCanvasImpl::on_animate_timeout(gpointer data)
1698 {
1699 GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
1700 if (!impl->pixmap_gc) {
1701 return FALSE; // Unrealized
1702 }
1703
1704 const double seconds = get_monotonic_time() / 1000000.0;
1705
1706 FOREACH_ITEM(impl->_selected_items, s) {
1707 ganv_node_tick(*s, seconds);
1708 }
1709
1710 for (SelectedPorts::iterator p = impl->_selected_ports.begin();
1711 p != impl->_selected_ports.end();
1712 ++p) {
1713 ganv_node_tick(GANV_NODE(*p), seconds);
1714 }
1715
1716 FOREACH_EDGE(impl->_selected_edges, c) {
1717 ganv_edge_tick(*c, seconds);
1718 }
1719
1720 return TRUE;
1721 }
1722
1723 void
move_contents_to_internal(double x,double y,double min_x,double min_y)1724 GanvCanvasImpl::move_contents_to_internal(double x, double y, double min_x, double min_y)
1725 {
1726 FOREACH_ITEM(_items, i) {
1727 ganv_node_move(*i,
1728 x - min_x,
1729 y - min_y);
1730 }
1731 }
1732
1733 void
unselect_ports()1734 GanvCanvasImpl::unselect_ports()
1735 {
1736 for (GanvCanvasImpl::SelectedPorts::iterator i = _selected_ports.begin();
1737 i != _selected_ports.end(); ++i)
1738 g_object_set(G_OBJECT(*i), "selected", FALSE, NULL);
1739
1740 _selected_ports.clear();
1741 _last_selected_port = NULL;
1742 }
1743
1744 namespace Ganv {
1745
1746 static gboolean
on_event_after(GanvItem *,GdkEvent * ev,void * canvas)1747 on_event_after(GanvItem*, GdkEvent* ev, void* canvas)
1748 {
1749 return ((Canvas*)canvas)->signal_event.emit(ev);
1750 }
1751
1752 static void
on_connect(GanvCanvas *,GanvNode * tail,GanvNode * head,void * data)1753 on_connect(GanvCanvas*, GanvNode* tail, GanvNode* head, void* data)
1754 {
1755 Canvas* canvasmm = (Canvas*)data;
1756 canvasmm->signal_connect.emit(Glib::wrap(tail), Glib::wrap(head));
1757 }
1758
1759 static void
on_disconnect(GanvCanvas *,GanvNode * tail,GanvNode * head,void * data)1760 on_disconnect(GanvCanvas*, GanvNode* tail, GanvNode* head, void* data)
1761 {
1762 Canvas* canvasmm = (Canvas*)data;
1763 canvasmm->signal_disconnect.emit(Glib::wrap(tail), Glib::wrap(head));
1764 }
1765
Canvas(double width,double height)1766 Canvas::Canvas(double width, double height)
1767 : _gobj(GANV_CANVAS(ganv_canvas_new(width, height)))
1768 {
1769 ganv_canvas_set_wrapper(_gobj, this);
1770
1771 g_signal_connect_after(ganv_canvas_root(_gobj), "event",
1772 G_CALLBACK(on_event_after), this);
1773 g_signal_connect(gobj(), "connect",
1774 G_CALLBACK(on_connect), this);
1775 g_signal_connect(gobj(), "disconnect",
1776 G_CALLBACK(on_disconnect), this);
1777 }
1778
~Canvas()1779 Canvas::~Canvas()
1780 {
1781 delete _gobj->impl;
1782 }
1783
1784 void
remove_edge_between(Node * item1,Node * item2)1785 Canvas::remove_edge_between(Node* item1, Node* item2)
1786 {
1787 GanvEdge* edge = ganv_canvas_get_edge(_gobj, item1->gobj(), item2->gobj());
1788 if (edge) {
1789 ganv_canvas_remove_edge(_gobj, edge);
1790 }
1791 }
1792
1793 void
remove_edge(Edge * edge)1794 Canvas::remove_edge(Edge* edge)
1795 {
1796 ganv_canvas_remove_edge(_gobj, edge->gobj());
1797 }
1798
1799 Item*
get_item_at(double x,double y) const1800 Canvas::get_item_at(double x, double y) const
1801 {
1802 GanvItem* item = ganv_canvas_get_item_at(_gobj, x, y);
1803 if (item) {
1804 return Glib::wrap(item);
1805 }
1806 return NULL;
1807 }
1808
1809 Edge*
get_edge(Node * tail,Node * head) const1810 Canvas::get_edge(Node* tail, Node* head) const
1811 {
1812 GanvEdge* e = ganv_canvas_get_edge(_gobj, tail->gobj(), head->gobj());
1813 if (e) {
1814 return Glib::wrap(e);
1815 }
1816 return NULL;
1817 }
1818
1819 } // namespace Ganv
1820
1821 extern "C" {
1822
1823 #include "boilerplate.h"
1824
1825 G_DEFINE_TYPE_WITH_CODE(GanvCanvas, ganv_canvas, GTK_TYPE_LAYOUT,
1826 G_ADD_PRIVATE(GanvCanvas))
1827
1828 enum {
1829 PROP_0,
1830 PROP_WIDTH,
1831 PROP_HEIGHT,
1832 PROP_DIRECTION,
1833 PROP_FONT_SIZE,
1834 PROP_LOCKED,
1835 PROP_FOCUSED_ITEM
1836 };
1837
1838 static gboolean
on_canvas_event(GanvItem *,GdkEvent * ev,void * impl)1839 on_canvas_event(GanvItem*, GdkEvent* ev, void* impl)
1840 {
1841 return ((GanvCanvasImpl*)impl)->on_event(ev);
1842 }
1843
1844 static void
ganv_canvas_init(GanvCanvas * canvas)1845 ganv_canvas_init(GanvCanvas* canvas)
1846 {
1847 GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS);
1848
1849 canvas->impl = new GanvCanvasImpl(canvas);
1850
1851 g_signal_connect(G_OBJECT(ganv_canvas_root(canvas)),
1852 "event", G_CALLBACK(on_canvas_event), canvas->impl);
1853 }
1854
1855 static void
ganv_canvas_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1856 ganv_canvas_set_property(GObject* object,
1857 guint prop_id,
1858 const GValue* value,
1859 GParamSpec* pspec)
1860 {
1861 g_return_if_fail(object != NULL);
1862 g_return_if_fail(GANV_IS_CANVAS(object));
1863
1864 GanvCanvas* canvas = GANV_CANVAS(object);
1865
1866 switch (prop_id) {
1867 case PROP_WIDTH:
1868 ganv_canvas_resize(canvas, g_value_get_double(value), canvas->impl->height);
1869 break;
1870 case PROP_HEIGHT:
1871 ganv_canvas_resize(canvas, canvas->impl->width, g_value_get_double(value));
1872 break;
1873 case PROP_DIRECTION:
1874 ganv_canvas_set_direction(canvas, (GanvDirection)g_value_get_enum(value));
1875 break;
1876 case PROP_FONT_SIZE:
1877 ganv_canvas_set_font_size(canvas, g_value_get_double(value));
1878 break;
1879 case PROP_LOCKED:
1880 canvas->impl->locked = g_value_get_boolean(value);
1881 break;
1882 case PROP_FOCUSED_ITEM:
1883 canvas->impl->focused_item = GANV_ITEM(g_value_get_object(value));
1884 break;
1885 default:
1886 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1887 break;
1888 }
1889 }
1890
1891 static void
ganv_canvas_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1892 ganv_canvas_get_property(GObject* object,
1893 guint prop_id,
1894 GValue* value,
1895 GParamSpec* pspec)
1896 {
1897 g_return_if_fail(object != NULL);
1898 g_return_if_fail(GANV_IS_CANVAS(object));
1899
1900 GanvCanvas* canvas = GANV_CANVAS(object);
1901
1902 switch (prop_id) {
1903 GET_CASE(WIDTH, double, canvas->impl->width)
1904 GET_CASE(HEIGHT, double, canvas->impl->height)
1905 GET_CASE(LOCKED, boolean, canvas->impl->locked)
1906 case PROP_FOCUSED_ITEM:
1907 g_value_set_object(value, GANV_CANVAS(object)->impl->focused_item);
1908 break;
1909 default:
1910 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1911 break;
1912 }
1913 }
1914
1915 static void
ganv_canvas_class_init(GanvCanvasClass * klass)1916 ganv_canvas_class_init(GanvCanvasClass* klass)
1917 {
1918 GObjectClass* gobject_class = (GObjectClass*)klass;
1919 GtkObjectClass* object_class = (GtkObjectClass*)klass;
1920 GtkWidgetClass* widget_class = (GtkWidgetClass*)klass;
1921
1922 canvas_parent_class = GTK_LAYOUT_CLASS(g_type_class_peek_parent(klass));
1923
1924 gobject_class->set_property = ganv_canvas_set_property;
1925 gobject_class->get_property = ganv_canvas_get_property;
1926
1927 object_class->destroy = ganv_canvas_destroy;
1928
1929 widget_class->map = ganv_canvas_map;
1930 widget_class->unmap = ganv_canvas_unmap;
1931 widget_class->realize = ganv_canvas_realize;
1932 widget_class->unrealize = ganv_canvas_unrealize;
1933 widget_class->size_allocate = ganv_canvas_size_allocate;
1934 widget_class->button_press_event = ganv_canvas_button;
1935 widget_class->button_release_event = ganv_canvas_button;
1936 widget_class->motion_notify_event = ganv_canvas_motion;
1937 widget_class->expose_event = ganv_canvas_expose;
1938 widget_class->key_press_event = ganv_canvas_key;
1939 widget_class->key_release_event = ganv_canvas_key;
1940 widget_class->enter_notify_event = ganv_canvas_crossing;
1941 widget_class->leave_notify_event = ganv_canvas_crossing;
1942 widget_class->focus_in_event = ganv_canvas_focus_in;
1943 widget_class->focus_out_event = ganv_canvas_focus_out;
1944 widget_class->scroll_event = ganv_canvas_scroll;
1945
1946 g_object_class_install_property(
1947 gobject_class, PROP_FOCUSED_ITEM, g_param_spec_object(
1948 "focused-item",
1949 _("Focused item"),
1950 _("The item that currently has keyboard focus."),
1951 GANV_TYPE_ITEM,
1952 (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE)));
1953
1954 g_object_class_install_property(
1955 gobject_class, PROP_WIDTH, g_param_spec_double(
1956 "width",
1957 _("Width"),
1958 _("The width of the canvas."),
1959 0.0, G_MAXDOUBLE,
1960 800.0,
1961 (GParamFlags)G_PARAM_READWRITE));
1962
1963 g_object_class_install_property(
1964 gobject_class, PROP_HEIGHT, g_param_spec_double(
1965 "height",
1966 _("Height"),
1967 _("The height of the canvas"),
1968 0.0, G_MAXDOUBLE,
1969 600.0,
1970 (GParamFlags)G_PARAM_READWRITE));
1971
1972 GEnumValue down_dir = { GANV_DIRECTION_DOWN, "down", "down" };
1973 GEnumValue right_dir = { GANV_DIRECTION_RIGHT, "right", "right" };
1974 GEnumValue null_dir = { 0, 0, 0 };
1975 dir_values[0] = down_dir;
1976 dir_values[1] = right_dir;
1977 dir_values[2] = null_dir;
1978 GType dir_type = g_enum_register_static("GanvDirection",
1979 dir_values);
1980
1981 g_object_class_install_property(
1982 gobject_class, PROP_DIRECTION, g_param_spec_enum(
1983 "direction",
1984 _("Direction"),
1985 _("The direction of the signal flow on the canvas."),
1986 dir_type,
1987 GANV_DIRECTION_RIGHT,
1988 (GParamFlags)G_PARAM_READWRITE));
1989
1990 g_object_class_install_property(
1991 gobject_class, PROP_FONT_SIZE, g_param_spec_double(
1992 "font-size",
1993 _("Font size"),
1994 _("The default font size for the canvas"),
1995 0.0, G_MAXDOUBLE,
1996 12.0,
1997 (GParamFlags)G_PARAM_READWRITE));
1998
1999 g_object_class_install_property(
2000 gobject_class, PROP_LOCKED, g_param_spec_boolean(
2001 "locked",
2002 _("Locked"),
2003 _("If true, nodes on the canvas can not be moved by the user."),
2004 FALSE,
2005 (GParamFlags)G_PARAM_READWRITE));
2006
2007 signal_connect = g_signal_new("connect",
2008 ganv_canvas_get_type(),
2009 G_SIGNAL_RUN_FIRST,
2010 0, NULL, NULL,
2011 ganv_marshal_VOID__OBJECT_OBJECT,
2012 G_TYPE_NONE,
2013 2,
2014 ganv_node_get_type(),
2015 ganv_node_get_type(),
2016 0);
2017
2018 signal_disconnect = g_signal_new("disconnect",
2019 ganv_canvas_get_type(),
2020 G_SIGNAL_RUN_FIRST,
2021 0, NULL, NULL,
2022 ganv_marshal_VOID__OBJECT_OBJECT,
2023 G_TYPE_NONE,
2024 2,
2025 ganv_node_get_type(),
2026 ganv_node_get_type(),
2027 0);
2028 }
2029
2030 void
ganv_canvas_get_size(GanvCanvas * canvas,double * width,double * height)2031 ganv_canvas_get_size(GanvCanvas* canvas, double* width, double* height)
2032 {
2033 *width = canvas->impl->width;
2034 *height = canvas->impl->height;
2035 }
2036
2037 void
ganv_canvas_resize(GanvCanvas * canvas,double width,double height)2038 ganv_canvas_resize(GanvCanvas* canvas, double width, double height)
2039 {
2040 if (width != canvas->impl->width || height != canvas->impl->height) {
2041 canvas->impl->width = width;
2042 canvas->impl->height = height;
2043 ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height);
2044 }
2045 }
2046
2047 void
ganv_canvas_contents_changed(GanvCanvas * canvas)2048 ganv_canvas_contents_changed(GanvCanvas* canvas)
2049 {
2050 #ifdef GANV_FDGL
2051 if (!canvas->impl->layout_idle_id && canvas->impl->sprung_layout) {
2052 canvas->impl->layout_energy = 0.4;
2053 canvas->impl->layout_idle_id = g_timeout_add_full(
2054 G_PRIORITY_DEFAULT_IDLE,
2055 33,
2056 GanvCanvasImpl::on_layout_timeout,
2057 canvas->impl,
2058 GanvCanvasImpl::on_layout_done);
2059 }
2060 #endif
2061 }
2062
2063 double
ganv_canvas_get_default_font_size(const GanvCanvas * canvas)2064 ganv_canvas_get_default_font_size(const GanvCanvas* canvas)
2065 {
2066 GtkStyle* style = gtk_rc_get_style(GTK_WIDGET(canvas));
2067 const PangoFontDescription* font = style->font_desc;
2068 return pango_font_description_get_size(font) / (double)PANGO_SCALE;
2069 }
2070
2071 double
ganv_canvas_get_font_size(const GanvCanvas * canvas)2072 ganv_canvas_get_font_size(const GanvCanvas* canvas)
2073 {
2074 return canvas->impl->font_size;
2075 }
2076
2077 void
ganv_canvas_set_zoom(GanvCanvas * canvas,double zoom)2078 ganv_canvas_set_zoom(GanvCanvas* canvas, double zoom)
2079 {
2080 g_return_if_fail(GANV_IS_CANVAS(canvas));
2081
2082 zoom = std::max(zoom, 0.01);
2083 if (zoom == canvas->impl->pixels_per_unit) {
2084 return;
2085 }
2086
2087 const int anchor_x = (canvas->impl->center_scroll_region)
2088 ? GTK_WIDGET(canvas)->allocation.width / 2
2089 : 0;
2090 const int anchor_y = (canvas->impl->center_scroll_region)
2091 ? GTK_WIDGET(canvas)->allocation.height / 2
2092 : 0;
2093
2094 /* Find the coordinates of the anchor point in units. */
2095 const double ax = (canvas->layout.hadjustment)
2096 ? ((canvas->layout.hadjustment->value + anchor_x)
2097 / canvas->impl->pixels_per_unit
2098 + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs)
2099 : ((0.0 + anchor_x) / canvas->impl->pixels_per_unit
2100 + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs);
2101 const double ay = (canvas->layout.hadjustment)
2102 ? ((canvas->layout.vadjustment->value + anchor_y)
2103 / canvas->impl->pixels_per_unit
2104 + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs)
2105 : ((0.0 + anchor_y) / canvas->impl->pixels_per_unit
2106 + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs);
2107
2108 /* Now calculate the new offset of the upper left corner. */
2109 const int x1 = ((ax - canvas->impl->scroll_x1) * zoom) - anchor_x;
2110 const int y1 = ((ay - canvas->impl->scroll_y1) * zoom) - anchor_y;
2111
2112 canvas->impl->pixels_per_unit = zoom;
2113 ganv_canvas_scroll_to(canvas, x1, y1);
2114
2115 ganv_canvas_request_update(canvas);
2116 gtk_widget_queue_draw(GTK_WIDGET(canvas));
2117
2118 canvas->impl->need_repick = TRUE;
2119 }
2120
2121 void
ganv_canvas_set_font_size(GanvCanvas * canvas,double points)2122 ganv_canvas_set_font_size(GanvCanvas* canvas, double points)
2123 {
2124 points = std::max(points, 1.0);
2125 if (points != canvas->impl->font_size) {
2126 canvas->impl->font_size = points;
2127 FOREACH_ITEM(canvas->impl->_items, i) {
2128 ganv_node_redraw_text(*i);
2129 }
2130 }
2131 }
2132
2133 void
ganv_canvas_zoom_full(GanvCanvas * canvas)2134 ganv_canvas_zoom_full(GanvCanvas* canvas)
2135 {
2136 if (canvas->impl->_items.empty())
2137 return;
2138
2139 int win_width = 0;
2140 int win_height = 0;
2141 GdkWindow* win = gtk_widget_get_window(
2142 GTK_WIDGET(canvas->impl->_gcanvas));
2143 gdk_window_get_size(win, &win_width, &win_height);
2144
2145 // Box containing all canvas items
2146 double left = DBL_MAX;
2147 double right = DBL_MIN;
2148 double top = DBL_MIN;
2149 double bottom = DBL_MAX;
2150
2151 FOREACH_ITEM(canvas->impl->_items, i) {
2152 GanvItem* const item = GANV_ITEM(*i);
2153 const double x = item->impl->x;
2154 const double y = item->impl->y;
2155 if (GANV_IS_CIRCLE(*i)) {
2156 const double r = GANV_CIRCLE(*i)->impl->coords.radius;
2157 left = MIN(left, x - r);
2158 right = MAX(right, x + r);
2159 bottom = MIN(bottom, y - r);
2160 top = MAX(top, y + r);
2161 } else {
2162 left = MIN(left, x);
2163 right = MAX(right, x + ganv_box_get_width(GANV_BOX(*i)));
2164 bottom = MIN(bottom, y);
2165 top = MAX(top, y + ganv_box_get_height(GANV_BOX(*i)));
2166 }
2167 }
2168
2169 static const double pad = GANV_CANVAS_PAD;
2170
2171 const double new_zoom = std::min(
2172 ((double)win_width / (double)(right - left + pad*2.0)),
2173 ((double)win_height / (double)(top - bottom + pad*2.0)));
2174
2175 ganv_canvas_set_zoom(canvas, new_zoom);
2176
2177 int scroll_x = 0;
2178 int scroll_y = 0;
2179 ganv_canvas_w2c(canvas->impl->_gcanvas,
2180 lrintf(left - pad), lrintf(bottom - pad),
2181 &scroll_x, &scroll_y);
2182
2183 ganv_canvas_scroll_to(canvas->impl->_gcanvas,
2184 scroll_x, scroll_y);
2185 }
2186
2187 static void
set_node_direction(GanvNode * node,void * data)2188 set_node_direction(GanvNode* node, void* data)
2189 {
2190 if (GANV_IS_MODULE(node)) {
2191 ganv_module_set_direction(GANV_MODULE(node), *(GanvDirection*)data);
2192 }
2193 }
2194
2195 GanvDirection
ganv_canvas_get_direction(GanvCanvas * canvas)2196 ganv_canvas_get_direction(GanvCanvas* canvas)
2197 {
2198 return canvas->impl->direction;
2199 }
2200
2201 void
ganv_canvas_set_direction(GanvCanvas * canvas,GanvDirection dir)2202 ganv_canvas_set_direction(GanvCanvas* canvas, GanvDirection dir)
2203 {
2204 if (canvas->impl->direction != dir) {
2205 canvas->impl->direction = dir;
2206 ganv_canvas_for_each_node(canvas, set_node_direction, &dir);
2207 ganv_canvas_contents_changed(canvas);
2208 }
2209 }
2210
2211 void
ganv_canvas_clear_selection(GanvCanvas * canvas)2212 ganv_canvas_clear_selection(GanvCanvas* canvas)
2213 {
2214 canvas->impl->unselect_ports();
2215
2216 Items items(canvas->impl->_selected_items);
2217 canvas->impl->_selected_items.clear();
2218 FOREACH_ITEM(items, i) {
2219 ganv_item_set(GANV_ITEM(*i), "selected", FALSE, NULL);
2220 }
2221
2222 GanvCanvasImpl::SelectedEdges edges(canvas->impl->_selected_edges);
2223 canvas->impl->_selected_edges.clear();
2224 FOREACH_SELECTED_EDGE(edges, c) {
2225 ganv_item_set(GANV_ITEM(*c), "selected", FALSE, NULL);
2226 }
2227 }
2228
2229 void
ganv_canvas_move_selected_items(GanvCanvas * canvas,double dx,double dy)2230 ganv_canvas_move_selected_items(GanvCanvas* canvas,
2231 double dx,
2232 double dy)
2233 {
2234 FOREACH_ITEM(canvas->impl->_selected_items, i) {
2235 if ((*i)->item.impl->parent == canvas->impl->root) {
2236 ganv_node_move(*i, dx, dy);
2237 }
2238 }
2239 }
2240
2241 void
ganv_canvas_selection_move_finished(GanvCanvas * canvas)2242 ganv_canvas_selection_move_finished(GanvCanvas* canvas)
2243 {
2244 FOREACH_ITEM(canvas->impl->_selected_items, i) {
2245 const double x = GANV_ITEM(*i)->impl->x;
2246 const double y = GANV_ITEM(*i)->impl->y;
2247 g_signal_emit(*i, signal_moved, 0, x, y, NULL);
2248 }
2249 }
2250
2251 static void
select_if_ends_are_selected(GanvEdge * edge,void *)2252 select_if_ends_are_selected(GanvEdge* edge, void*)
2253 {
2254 if (ganv_node_is_selected(ganv_edge_get_tail(edge)) &&
2255 ganv_node_is_selected(ganv_edge_get_head(edge))) {
2256 ganv_edge_set_selected(edge, TRUE);
2257 }
2258 }
2259
2260 static void
unselect_edges(GanvPort * port,void * data)2261 unselect_edges(GanvPort* port, void* data)
2262 {
2263 GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
2264 if (port->impl->is_input) {
2265 ganv_canvas_for_each_edge_to(impl->_gcanvas,
2266 GANV_NODE(port),
2267 (GanvEdgeFunc)ganv_edge_unselect,
2268 NULL);
2269 } else {
2270 ganv_canvas_for_each_edge_from(impl->_gcanvas,
2271 GANV_NODE(port),
2272 (GanvEdgeFunc)ganv_edge_unselect,
2273 NULL);
2274 }
2275 }
2276
2277 void
ganv_canvas_select_node(GanvCanvas * canvas,GanvNode * node)2278 ganv_canvas_select_node(GanvCanvas* canvas,
2279 GanvNode* node)
2280 {
2281 canvas->impl->_selected_items.insert(node);
2282
2283 // Select any connections to or from this node
2284 if (GANV_IS_MODULE(node)) {
2285 ganv_module_for_each_port(GANV_MODULE(node), select_edges, canvas->impl);
2286 } else {
2287 ganv_canvas_for_each_edge_on(
2288 canvas, node, select_if_ends_are_selected, canvas->impl);
2289 }
2290
2291 g_object_set(node, "selected", TRUE, NULL);
2292 }
2293
2294 void
ganv_canvas_unselect_node(GanvCanvas * canvas,GanvNode * node)2295 ganv_canvas_unselect_node(GanvCanvas* canvas,
2296 GanvNode* node)
2297 {
2298 // Unselect any connections to or from canvas->impl node
2299 if (GANV_IS_MODULE(node)) {
2300 ganv_module_for_each_port(GANV_MODULE(node), unselect_edges, canvas->impl);
2301 } else {
2302 ganv_canvas_for_each_edge_on(
2303 canvas, node, (GanvEdgeFunc)ganv_edge_unselect, NULL);
2304 }
2305
2306 // Unselect item
2307 canvas->impl->_selected_items.erase(node);
2308 g_object_set(node, "selected", FALSE, NULL);
2309 }
2310
2311 void
ganv_canvas_add_node(GanvCanvas * canvas,GanvNode * node)2312 ganv_canvas_add_node(GanvCanvas* canvas,
2313 GanvNode* node)
2314 {
2315 GanvItem* item = GANV_ITEM(node);
2316 if (item->impl->parent == ganv_canvas_root(canvas)) {
2317 canvas->impl->_items.insert(node);
2318 }
2319 }
2320
2321 void
ganv_canvas_remove_node(GanvCanvas * canvas,GanvNode * node)2322 ganv_canvas_remove_node(GanvCanvas* canvas,
2323 GanvNode* node)
2324 {
2325 if (node == (GanvNode*)canvas->impl->_connect_port) {
2326 if (canvas->impl->_drag_state == GanvCanvasImpl::EDGE) {
2327 ganv_canvas_ungrab_item(ganv_canvas_root(canvas), 0);
2328 canvas->impl->end_connect_drag();
2329 }
2330 canvas->impl->_connect_port = NULL;
2331 }
2332
2333 // Remove from selection
2334 canvas->impl->_selected_items.erase(node);
2335
2336 // Remove children ports from selection if item is a module
2337 if (GANV_IS_MODULE(node)) {
2338 GanvModule* const module = GANV_MODULE(node);
2339 for (unsigned i = 0; i < ganv_module_num_ports(module); ++i) {
2340 canvas->impl->unselect_port(ganv_module_get_port(module, i));
2341 }
2342 }
2343
2344 // Remove from items
2345 canvas->impl->_items.erase(node);
2346 }
2347
2348 GanvEdge*
ganv_canvas_get_edge(GanvCanvas * canvas,GanvNode * tail,GanvNode * head)2349 ganv_canvas_get_edge(GanvCanvas* canvas,
2350 GanvNode* tail,
2351 GanvNode* head)
2352 {
2353 GanvEdgeKey key;
2354 make_edge_search_key(&key, tail, head);
2355 GanvCanvasImpl::Edges::const_iterator i = canvas->impl->_edges.find((GanvEdge*)&key);
2356 return (i != canvas->impl->_edges.end()) ? *i : NULL;
2357 }
2358
2359 void
ganv_canvas_remove_edge_between(GanvCanvas * canvas,GanvNode * tail,GanvNode * head)2360 ganv_canvas_remove_edge_between(GanvCanvas* canvas,
2361 GanvNode* tail,
2362 GanvNode* head)
2363 {
2364 ganv_canvas_remove_edge(canvas, ganv_canvas_get_edge(canvas, tail, head));
2365 }
2366
2367 void
ganv_canvas_disconnect_edge(GanvCanvas * canvas,GanvEdge * edge)2368 ganv_canvas_disconnect_edge(GanvCanvas* canvas,
2369 GanvEdge* edge)
2370 {
2371 g_signal_emit(canvas, signal_disconnect, 0,
2372 edge->impl->tail, edge->impl->head, NULL);
2373 }
2374
2375 void
ganv_canvas_add_edge(GanvCanvas * canvas,GanvEdge * edge)2376 ganv_canvas_add_edge(GanvCanvas* canvas,
2377 GanvEdge* edge)
2378 {
2379 canvas->impl->_edges.insert(edge);
2380 canvas->impl->_dst_edges.insert(edge);
2381 ganv_canvas_contents_changed(canvas);
2382 }
2383
2384 void
ganv_canvas_remove_edge(GanvCanvas * canvas,GanvEdge * edge)2385 ganv_canvas_remove_edge(GanvCanvas* canvas,
2386 GanvEdge* edge)
2387 {
2388 if (edge) {
2389 canvas->impl->_selected_edges.erase(edge);
2390 canvas->impl->_edges.erase(edge);
2391 canvas->impl->_dst_edges.erase(edge);
2392 ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords);
2393 gtk_object_destroy(GTK_OBJECT(edge));
2394 ganv_canvas_contents_changed(canvas);
2395 }
2396 }
2397
2398 void
ganv_canvas_select_edge(GanvCanvas * canvas,GanvEdge * edge)2399 ganv_canvas_select_edge(GanvCanvas* canvas,
2400 GanvEdge* edge)
2401 {
2402 ganv_item_set(GANV_ITEM(edge), "selected", TRUE, NULL);
2403 canvas->impl->_selected_edges.insert(edge);
2404 }
2405
2406 void
ganv_canvas_unselect_edge(GanvCanvas * canvas,GanvEdge * edge)2407 ganv_canvas_unselect_edge(GanvCanvas* canvas,
2408 GanvEdge* edge)
2409 {
2410 ganv_item_set(GANV_ITEM(edge), "selected", FALSE, NULL);
2411 canvas->impl->_selected_edges.erase(edge);
2412 }
2413
2414 void
ganv_canvas_for_each_node(GanvCanvas * canvas,GanvNodeFunc f,void * data)2415 ganv_canvas_for_each_node(GanvCanvas* canvas,
2416 GanvNodeFunc f,
2417 void* data)
2418 {
2419 FOREACH_ITEM(canvas->impl->_items, i) {
2420 f(*i, data);
2421 }
2422 }
2423
2424 void
ganv_canvas_for_each_selected_node(GanvCanvas * canvas,GanvNodeFunc f,void * data)2425 ganv_canvas_for_each_selected_node(GanvCanvas* canvas,
2426 GanvNodeFunc f,
2427 void* data)
2428 {
2429 FOREACH_ITEM(canvas->impl->_selected_items, i) {
2430 f(*i, data);
2431 }
2432 }
2433
2434 gboolean
ganv_canvas_empty(const GanvCanvas * canvas)2435 ganv_canvas_empty(const GanvCanvas* canvas)
2436 {
2437 return canvas->impl->_items.empty();
2438 }
2439
2440 void
ganv_canvas_for_each_edge(GanvCanvas * canvas,GanvEdgeFunc f,void * data)2441 ganv_canvas_for_each_edge(GanvCanvas* canvas,
2442 GanvEdgeFunc f,
2443 void* data)
2444 {
2445 GanvCanvasImpl* impl = canvas->impl;
2446 for (GanvCanvasImpl::Edges::const_iterator i = impl->_edges.begin();
2447 i != impl->_edges.end();) {
2448 GanvCanvasImpl::Edges::const_iterator next = i;
2449 ++next;
2450 f((*i), data);
2451 i = next;
2452 }
2453 }
2454
2455 void
ganv_canvas_for_each_edge_from(GanvCanvas * canvas,const GanvNode * tail,GanvEdgeFunc f,void * data)2456 ganv_canvas_for_each_edge_from(GanvCanvas* canvas,
2457 const GanvNode* tail,
2458 GanvEdgeFunc f,
2459 void* data)
2460 {
2461 GanvCanvasImpl* impl = canvas->impl;
2462 for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_from(tail);
2463 i != impl->_edges.end() && (*i)->impl->tail == tail;) {
2464 GanvCanvasImpl::Edges::const_iterator next = i;
2465 ++next;
2466 f((*i), data);
2467 i = next;
2468 }
2469 }
2470
2471 void
ganv_canvas_for_each_edge_to(GanvCanvas * canvas,const GanvNode * head,GanvEdgeFunc f,void * data)2472 ganv_canvas_for_each_edge_to(GanvCanvas* canvas,
2473 const GanvNode* head,
2474 GanvEdgeFunc f,
2475 void* data)
2476 {
2477 GanvCanvasImpl* impl = canvas->impl;
2478 for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_to(head);
2479 i != impl->_dst_edges.end() && (*i)->impl->head == head;) {
2480 GanvCanvasImpl::Edges::const_iterator next = i;
2481 ++next;
2482 f((*i), data);
2483 i = next;
2484 }
2485 }
2486
2487 void
ganv_canvas_for_each_edge_on(GanvCanvas * canvas,const GanvNode * node,GanvEdgeFunc f,void * data)2488 ganv_canvas_for_each_edge_on(GanvCanvas* canvas,
2489 const GanvNode* node,
2490 GanvEdgeFunc f,
2491 void* data)
2492 {
2493 ganv_canvas_for_each_edge_from(canvas, node, f, data);
2494 ganv_canvas_for_each_edge_to(canvas, node, f, data);
2495 }
2496
2497 void
ganv_canvas_for_each_selected_edge(GanvCanvas * canvas,GanvEdgeFunc f,void * data)2498 ganv_canvas_for_each_selected_edge(GanvCanvas* canvas,
2499 GanvEdgeFunc f,
2500 void* data)
2501 {
2502 FOREACH_EDGE(canvas->impl->_selected_edges, i) {
2503 f((*i), data);
2504 }
2505 }
2506
2507 GdkCursor*
ganv_canvas_get_move_cursor(const GanvCanvas * canvas)2508 ganv_canvas_get_move_cursor(const GanvCanvas* canvas)
2509 {
2510 return canvas->impl->_move_cursor;
2511 }
2512
2513 gboolean
ganv_canvas_port_event(GanvCanvas * canvas,GanvPort * port,GdkEvent * event)2514 ganv_canvas_port_event(GanvCanvas* canvas,
2515 GanvPort* port,
2516 GdkEvent* event)
2517 {
2518 return canvas->impl->port_event(event, port);
2519 }
2520
2521 void
ganv_canvas_clear(GanvCanvas * canvas)2522 ganv_canvas_clear(GanvCanvas* canvas)
2523 {
2524 canvas->impl->_selected_items.clear();
2525 canvas->impl->_selected_edges.clear();
2526
2527 Items items = canvas->impl->_items; // copy
2528 FOREACH_ITEM(items, i) {
2529 gtk_object_destroy(GTK_OBJECT(*i));
2530 }
2531 canvas->impl->_items.clear();
2532
2533 GanvCanvasImpl::Edges edges = canvas->impl->_edges; // copy
2534 FOREACH_EDGE(edges, i) {
2535 gtk_object_destroy(GTK_OBJECT(*i));
2536 }
2537 canvas->impl->_edges.clear();
2538 canvas->impl->_dst_edges.clear();
2539
2540 canvas->impl->_selected_ports.clear();
2541 canvas->impl->_connect_port = NULL;
2542 }
2543
2544 void
ganv_canvas_select_all(GanvCanvas * canvas)2545 ganv_canvas_select_all(GanvCanvas* canvas)
2546 {
2547 ganv_canvas_clear_selection(canvas);
2548 FOREACH_ITEM(canvas->impl->_items, i) {
2549 ganv_canvas_select_node(canvas, *i);
2550 }
2551 }
2552
2553 double
ganv_canvas_get_zoom(const GanvCanvas * canvas)2554 ganv_canvas_get_zoom(const GanvCanvas* canvas)
2555 {
2556 return canvas->impl->pixels_per_unit;
2557 }
2558
2559 void
ganv_canvas_move_contents_to(GanvCanvas * canvas,double x,double y)2560 ganv_canvas_move_contents_to(GanvCanvas* canvas, double x, double y)
2561 {
2562 double min_x = HUGE_VAL;
2563 double min_y = HUGE_VAL;
2564 FOREACH_ITEM(canvas->impl->_items, i) {
2565 const double x = GANV_ITEM(*i)->impl->x;
2566 const double y = GANV_ITEM(*i)->impl->y;
2567 min_x = std::min(min_x, x);
2568 min_y = std::min(min_y, y);
2569 }
2570 canvas->impl->move_contents_to_internal(x, y, min_x, min_y);
2571 }
2572
2573 void
ganv_canvas_arrange(GanvCanvas * canvas)2574 ganv_canvas_arrange(GanvCanvas* canvas)
2575 {
2576 #ifdef HAVE_AGRAPH
2577 GVNodes nodes = canvas->impl->layout_dot((char*)"");
2578
2579 double least_x = HUGE_VAL;
2580 double least_y = HUGE_VAL;
2581 double most_x = 0.0;
2582 double most_y = 0.0;
2583
2584 // Set numeric locale to POSIX for reading graphviz output with strtod
2585 char* locale = strdup(setlocale(LC_NUMERIC, NULL));
2586 setlocale(LC_NUMERIC, "POSIX");
2587
2588 const double dpi = gdk_screen_get_resolution(gdk_screen_get_default());
2589 const double dpp = dpi / 72.0;
2590
2591 // Arrange to graphviz coordinates
2592 for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) {
2593 if (GANV_ITEM(i->first)->impl->parent != GANV_ITEM(ganv_canvas_root(canvas))) {
2594 continue;
2595 }
2596 const std::string pos = agget(i->second, (char*)"pos");
2597 const std::string x_str = pos.substr(0, pos.find(","));
2598 const std::string y_str = pos.substr(pos.find(",") + 1);
2599 const double cx = lrint(strtod(x_str.c_str(), NULL) * dpp);
2600 const double cy = lrint(strtod(y_str.c_str(), NULL) * dpp);
2601
2602 double w = 0.0;
2603 double h = 0.0;
2604 if (GANV_IS_BOX(i->first)) {
2605 w = ganv_box_get_width(GANV_BOX(i->first));
2606 h = ganv_box_get_height(GANV_BOX(i->first));
2607 } else {
2608 w = h = ganv_circle_get_radius(GANV_CIRCLE(i->first)) * 2.3;
2609 }
2610
2611 /* Dot node positions are supposedly node centers, but things only
2612 match up if x is interpreted as center and y as top...
2613 */
2614 double x = cx - (w / 2.0);
2615 double y = -cy - (h / 2.0);
2616
2617 ganv_node_move_to(i->first, x, y);
2618
2619 if (GANV_IS_CIRCLE(i->first)) {
2620 // Offset least x and y to avoid cutting off circles at origin
2621 const double r = ganv_circle_get_radius(GANV_CIRCLE(i->first));
2622 x -= r;
2623 y -= r;
2624 }
2625
2626 least_x = std::min(least_x, x);
2627 least_y = std::min(least_y, y);
2628 most_x = std::max(most_x, x + w);
2629 most_y = std::max(most_y, y + h);
2630 }
2631
2632 // Reset numeric locale to original value
2633 setlocale(LC_NUMERIC, locale);
2634 free(locale);
2635
2636 const double graph_width = most_x - least_x;
2637 const double graph_height = most_y - least_y;
2638
2639 //cerr << "CWH: " << _width << ", " << _height << endl;
2640 //cerr << "GWH: " << graph_width << ", " << graph_height << endl;
2641
2642 double old_width = 0.0;
2643 double old_height = 0.0;
2644 g_object_get(G_OBJECT(canvas),
2645 "width", &old_width,
2646 "height", &old_height,
2647 NULL);
2648
2649 const double new_width = std::max(graph_width + 10.0, old_width);
2650 const double new_height = std::max(graph_height + 10.0, old_height);
2651 if (new_width != old_width || new_height != old_height) {
2652 ganv_canvas_resize(canvas, new_width, new_height);
2653 }
2654 nodes.cleanup();
2655
2656 static const double border_width = GANV_CANVAS_PAD;
2657 canvas->impl->move_contents_to_internal(border_width, border_width, least_x, least_y);
2658 ganv_canvas_scroll_to(canvas->impl->_gcanvas, 0, 0);
2659
2660 FOREACH_ITEM(canvas->impl->_items, i) {
2661 const double x = GANV_ITEM(*i)->impl->x;
2662 const double y = GANV_ITEM(*i)->impl->y;
2663 g_signal_emit(*i, signal_moved, 0, x, y, NULL);
2664 }
2665 #endif
2666 }
2667
2668 int
ganv_canvas_export_image(GanvCanvas * canvas,const char * filename,gboolean draw_background)2669 ganv_canvas_export_image(GanvCanvas* canvas,
2670 const char* filename,
2671 gboolean draw_background)
2672 {
2673 const char* ext = strrchr(filename, '.');
2674 if (!ext) {
2675 return 1;
2676 } else if (!strcmp(ext, ".dot")) {
2677 ganv_canvas_export_dot(canvas, filename);
2678 return 0;
2679 }
2680
2681 cairo_surface_t* rec_surface = cairo_recording_surface_create(
2682 CAIRO_CONTENT_COLOR_ALPHA, NULL);
2683
2684 // Draw to recording surface
2685 cairo_t* cr = cairo_create(rec_surface);
2686 canvas->impl->exporting = TRUE;
2687 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)(
2688 canvas->impl->root, cr,
2689 0, 0, canvas->impl->width, canvas->impl->height);
2690 canvas->impl->exporting = FALSE;
2691 cairo_destroy(cr);
2692
2693 // Get draw extent
2694 double x = 0.0;
2695 double y = 0.0;
2696 double w = 0.0;
2697 double h = 0.0;
2698 cairo_recording_surface_ink_extents(rec_surface, &x, &y, &w, &h);
2699
2700 // Create image surface with the appropriate size
2701 const double pad = GANV_CANVAS_PAD;
2702 const double img_w = w + pad * 2;
2703 const double img_h = h + pad * 2;
2704 cairo_surface_t* img = NULL;
2705 if (!strcmp(ext, ".svg")) {
2706 img = cairo_svg_surface_create(filename, img_w, img_h);
2707 } else if (!strcmp(ext, ".pdf")) {
2708 img = cairo_pdf_surface_create(filename, img_w, img_h);
2709 } else if (!strcmp(ext, ".ps")) {
2710 img = cairo_ps_surface_create(filename, img_w, img_h);
2711 } else {
2712 cairo_surface_destroy(rec_surface);
2713 return 1;
2714 }
2715
2716 // Draw recording to image surface
2717 cr = cairo_create(img);
2718 if (draw_background) {
2719 double r = 0.0;
2720 double g = 0.0;
2721 double b = 0.0;
2722 double a = 0.0;
2723 color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a);
2724 cairo_set_source_rgba(cr, r, g, b, a);
2725 cairo_rectangle(cr, 0, 0, w + 2 * pad, h + 2 * pad);
2726 cairo_fill(cr);
2727 }
2728 cairo_set_source_surface(cr, rec_surface, -x + pad, -y + pad);
2729 cairo_paint(cr);
2730 cairo_destroy(cr);
2731 cairo_surface_destroy(rec_surface);
2732 cairo_surface_destroy(img);
2733 return 0;
2734 }
2735
2736 void
ganv_canvas_export_dot(GanvCanvas * canvas,const char * filename)2737 ganv_canvas_export_dot(GanvCanvas* canvas, const char* filename)
2738 {
2739 #ifdef HAVE_AGRAPH
2740 GVNodes nodes = canvas->impl->layout_dot(filename);
2741 nodes.cleanup();
2742 #endif
2743 }
2744
2745 gboolean
ganv_canvas_supports_sprung_layout(const GanvCanvas *)2746 ganv_canvas_supports_sprung_layout(const GanvCanvas*)
2747 {
2748 #ifdef GANV_FDGL
2749 return TRUE;
2750 #else
2751 return FALSE;
2752 #endif
2753 }
2754
2755 gboolean
ganv_canvas_set_sprung_layout(GanvCanvas * canvas,gboolean sprung_layout)2756 ganv_canvas_set_sprung_layout(GanvCanvas* canvas, gboolean sprung_layout)
2757 {
2758 #ifndef GANV_FDGL
2759 return FALSE;
2760 #else
2761 canvas->impl->sprung_layout = sprung_layout;
2762 ganv_canvas_contents_changed(canvas);
2763 return TRUE;
2764 #endif
2765 }
2766
2767 gboolean
ganv_canvas_get_locked(const GanvCanvas * canvas)2768 ganv_canvas_get_locked(const GanvCanvas* canvas)
2769 {
2770 return canvas->impl->locked;
2771 }
2772
2773 /* Convenience function to remove the idle handler of a canvas */
2774 static void
remove_idle(GanvCanvas * canvas)2775 remove_idle(GanvCanvas* canvas)
2776 {
2777 if (canvas->impl->idle_id == 0) {
2778 return;
2779 }
2780
2781 g_source_remove(canvas->impl->idle_id);
2782 canvas->impl->idle_id = 0;
2783 }
2784
2785 /* Removes the transient state of the canvas (idle handler, grabs). */
2786 static void
shutdown_transients(GanvCanvas * canvas)2787 shutdown_transients(GanvCanvas* canvas)
2788 {
2789 /* We turn off the need_redraw flag, since if the canvas is mapped again
2790 * it will request a redraw anyways. We do not turn off the need_update
2791 * flag, though, because updates are not queued when the canvas remaps
2792 * itself.
2793 */
2794 if (canvas->impl->need_redraw) {
2795 canvas->impl->need_redraw = FALSE;
2796 g_slist_foreach(canvas->impl->redraw_region, (GFunc)g_free, NULL);
2797 g_slist_free(canvas->impl->redraw_region);
2798 canvas->impl->redraw_region = NULL;
2799 canvas->impl->redraw_x1 = 0;
2800 canvas->impl->redraw_y1 = 0;
2801 canvas->impl->redraw_x2 = 0;
2802 canvas->impl->redraw_y2 = 0;
2803 }
2804
2805 if (canvas->impl->grabbed_item) {
2806 canvas->impl->grabbed_item = NULL;
2807 gdk_pointer_ungrab(GDK_CURRENT_TIME);
2808 }
2809
2810 remove_idle(canvas);
2811 }
2812
2813 /* Destroy handler for GanvCanvas */
2814 static void
ganv_canvas_destroy(GtkObject * object)2815 ganv_canvas_destroy(GtkObject* object)
2816 {
2817 g_return_if_fail(GANV_IS_CANVAS(object));
2818
2819 /* remember, destroy can be run multiple times! */
2820
2821 GanvCanvas* canvas = GANV_CANVAS(object);
2822
2823 if (canvas->impl->root_destroy_id) {
2824 g_signal_handler_disconnect(canvas->impl->root, canvas->impl->root_destroy_id);
2825 canvas->impl->root_destroy_id = 0;
2826 }
2827 if (canvas->impl->root) {
2828 gtk_object_destroy(GTK_OBJECT(canvas->impl->root));
2829 g_object_unref(G_OBJECT(canvas->impl->root));
2830 canvas->impl->root = NULL;
2831 }
2832
2833 shutdown_transients(canvas);
2834
2835 if (GTK_OBJECT_CLASS(canvas_parent_class)->destroy) {
2836 (*GTK_OBJECT_CLASS(canvas_parent_class)->destroy)(object);
2837 }
2838 }
2839
2840 GanvCanvas*
ganv_canvas_new(double width,double height)2841 ganv_canvas_new(double width, double height)
2842 {
2843 GanvCanvas* canvas = GANV_CANVAS(
2844 g_object_new(ganv_canvas_get_type(),
2845 "width", width,
2846 "height", height,
2847 NULL));
2848
2849 ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height);
2850
2851 return canvas;
2852 }
2853
2854 void
ganv_canvas_set_wrapper(GanvCanvas * canvas,void * wrapper)2855 ganv_canvas_set_wrapper(GanvCanvas* canvas, void* wrapper)
2856 {
2857 canvas->impl->_wrapper = (Ganv::Canvas*)wrapper;
2858 }
2859
2860 void*
ganv_canvas_get_wrapper(GanvCanvas * canvas)2861 ganv_canvas_get_wrapper(GanvCanvas* canvas)
2862 {
2863 return canvas->impl->_wrapper;
2864 }
2865
2866 /* Map handler for the canvas */
2867 static void
ganv_canvas_map(GtkWidget * widget)2868 ganv_canvas_map(GtkWidget* widget)
2869 {
2870 g_return_if_fail(GANV_IS_CANVAS(widget));
2871
2872 /* Normal widget mapping stuff */
2873
2874 if (GTK_WIDGET_CLASS(canvas_parent_class)->map) {
2875 (*GTK_WIDGET_CLASS(canvas_parent_class)->map)(widget);
2876 }
2877
2878 GanvCanvas* canvas = GANV_CANVAS(widget);
2879
2880 if (canvas->impl->need_update) {
2881 add_idle(canvas);
2882 }
2883
2884 /* Map items */
2885
2886 if (GANV_ITEM_GET_CLASS(canvas->impl->root)->map) {
2887 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->map)(canvas->impl->root);
2888 }
2889 }
2890
2891 /* Unmap handler for the canvas */
2892 static void
ganv_canvas_unmap(GtkWidget * widget)2893 ganv_canvas_unmap(GtkWidget* widget)
2894 {
2895 g_return_if_fail(GANV_IS_CANVAS(widget));
2896
2897 GanvCanvas* canvas = GANV_CANVAS(widget);
2898
2899 shutdown_transients(canvas);
2900
2901 /* Unmap items */
2902
2903 if (GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap) {
2904 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap)(canvas->impl->root);
2905 }
2906
2907 /* Normal widget unmapping stuff */
2908
2909 if (GTK_WIDGET_CLASS(canvas_parent_class)->unmap) {
2910 (*GTK_WIDGET_CLASS(canvas_parent_class)->unmap)(widget);
2911 }
2912 }
2913
2914 /* Realize handler for the canvas */
2915 static void
ganv_canvas_realize(GtkWidget * widget)2916 ganv_canvas_realize(GtkWidget* widget)
2917 {
2918 g_return_if_fail(GANV_IS_CANVAS(widget));
2919
2920 /* Normal widget realization stuff */
2921
2922 if (GTK_WIDGET_CLASS(canvas_parent_class)->realize) {
2923 (*GTK_WIDGET_CLASS(canvas_parent_class)->realize)(widget);
2924 }
2925
2926 GanvCanvas* canvas = GANV_CANVAS(widget);
2927
2928 gdk_window_set_events(
2929 canvas->layout.bin_window,
2930 (GdkEventMask)(gdk_window_get_events(canvas->layout.bin_window)
2931 | GDK_EXPOSURE_MASK
2932 | GDK_BUTTON_PRESS_MASK
2933 | GDK_BUTTON_RELEASE_MASK
2934 | GDK_POINTER_MOTION_MASK
2935 | GDK_KEY_PRESS_MASK
2936 | GDK_KEY_RELEASE_MASK
2937 | GDK_ENTER_NOTIFY_MASK
2938 | GDK_LEAVE_NOTIFY_MASK
2939 | GDK_FOCUS_CHANGE_MASK));
2940
2941 /* Create our own temporary pixmap gc and realize all the items */
2942
2943 canvas->impl->pixmap_gc = gdk_gc_new(canvas->layout.bin_window);
2944
2945 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->realize)(canvas->impl->root);
2946
2947 canvas->impl->_animate_idle_id = g_timeout_add(
2948 120, GanvCanvasImpl::on_animate_timeout, canvas->impl);
2949 }
2950
2951 /* Unrealize handler for the canvas */
2952 static void
ganv_canvas_unrealize(GtkWidget * widget)2953 ganv_canvas_unrealize(GtkWidget* widget)
2954 {
2955 g_return_if_fail(GANV_IS_CANVAS(widget));
2956
2957 GanvCanvas* canvas = GANV_CANVAS(widget);
2958
2959 if (canvas->impl->_animate_idle_id) {
2960 g_source_remove(canvas->impl->_animate_idle_id);
2961 canvas->impl->_animate_idle_id = 0;
2962 }
2963 while (g_idle_remove_by_data(canvas->impl)) {}
2964
2965 shutdown_transients(canvas);
2966
2967 /* Unrealize items and parent widget */
2968
2969 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unrealize)(canvas->impl->root);
2970
2971 g_object_unref(canvas->impl->pixmap_gc);
2972 canvas->impl->pixmap_gc = NULL;
2973
2974 if (GTK_WIDGET_CLASS(canvas_parent_class)->unrealize) {
2975 (*GTK_WIDGET_CLASS(canvas_parent_class)->unrealize)(widget);
2976 }
2977 }
2978
2979 /* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to
2980 * keep as much as possible of the canvas scrolling region in view.
2981 */
2982 static void
scroll_to(GanvCanvas * canvas,int cx,int cy)2983 scroll_to(GanvCanvas* canvas, int cx, int cy)
2984 {
2985 int scroll_width = 0;
2986 int scroll_height = 0;
2987 int right_limit = 0;
2988 int bottom_limit = 0;
2989 int old_zoom_xofs = 0;
2990 int old_zoom_yofs = 0;
2991 int changed_x = 0;
2992 int changed_y = 0;
2993 int canvas_width = 0;
2994 int canvas_height = 0;
2995
2996 canvas_width = GTK_WIDGET(canvas)->allocation.width;
2997 canvas_height = GTK_WIDGET(canvas)->allocation.height;
2998
2999 scroll_width = floor((canvas->impl->scroll_x2 - canvas->impl->scroll_x1) * canvas->impl->pixels_per_unit
3000 + 0.5);
3001 scroll_height = floor((canvas->impl->scroll_y2 - canvas->impl->scroll_y1) * canvas->impl->pixels_per_unit
3002 + 0.5);
3003
3004 right_limit = scroll_width - canvas_width;
3005 bottom_limit = scroll_height - canvas_height;
3006
3007 old_zoom_xofs = canvas->impl->zoom_xofs;
3008 old_zoom_yofs = canvas->impl->zoom_yofs;
3009
3010 if (right_limit < 0) {
3011 cx = 0;
3012
3013 if (canvas->impl->center_scroll_region) {
3014 canvas->impl->zoom_xofs = (canvas_width - scroll_width) / 2;
3015 scroll_width = canvas_width;
3016 } else {
3017 canvas->impl->zoom_xofs = 0;
3018 }
3019 } else if (cx < 0) {
3020 cx = 0;
3021 canvas->impl->zoom_xofs = 0;
3022 } else if (cx > right_limit) {
3023 cx = right_limit;
3024 canvas->impl->zoom_xofs = 0;
3025 } else {
3026 canvas->impl->zoom_xofs = 0;
3027 }
3028
3029 if (bottom_limit < 0) {
3030 cy = 0;
3031
3032 if (canvas->impl->center_scroll_region) {
3033 canvas->impl->zoom_yofs = (canvas_height - scroll_height) / 2;
3034 scroll_height = canvas_height;
3035 } else {
3036 canvas->impl->zoom_yofs = 0;
3037 }
3038 } else if (cy < 0) {
3039 cy = 0;
3040 canvas->impl->zoom_yofs = 0;
3041 } else if (cy > bottom_limit) {
3042 cy = bottom_limit;
3043 canvas->impl->zoom_yofs = 0;
3044 } else {
3045 canvas->impl->zoom_yofs = 0;
3046 }
3047
3048 if ((canvas->impl->zoom_xofs != old_zoom_xofs) || (canvas->impl->zoom_yofs != old_zoom_yofs)) {
3049 ganv_canvas_request_update(canvas);
3050 gtk_widget_queue_draw(GTK_WIDGET(canvas));
3051 }
3052
3053 if (canvas->layout.hadjustment && ( ((int)canvas->layout.hadjustment->value) != cx) ) {
3054 canvas->layout.hadjustment->value = cx;
3055 changed_x = TRUE;
3056 }
3057
3058 if (canvas->layout.vadjustment && ( ((int)canvas->layout.vadjustment->value) != cy) ) {
3059 canvas->layout.vadjustment->value = cy;
3060 changed_y = TRUE;
3061 }
3062
3063 if ((scroll_width != (int)canvas->layout.width)
3064 || (scroll_height != (int)canvas->layout.height)) {
3065 gtk_layout_set_size(GTK_LAYOUT(canvas), scroll_width, scroll_height);
3066 }
3067
3068 /* Signal GtkLayout that it should do a redraw. */
3069
3070 if (changed_x) {
3071 g_signal_emit_by_name(canvas->layout.hadjustment, "value_changed");
3072 }
3073
3074 if (changed_y) {
3075 g_signal_emit_by_name(canvas->layout.vadjustment, "value_changed");
3076 }
3077 }
3078
3079 /* Size allocation handler for the canvas */
3080 static void
ganv_canvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation)3081 ganv_canvas_size_allocate(GtkWidget* widget, GtkAllocation* allocation)
3082 {
3083 g_return_if_fail(GANV_IS_CANVAS(widget));
3084 g_return_if_fail(allocation != NULL);
3085
3086 if (GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate) {
3087 (*GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate)(widget, allocation);
3088 }
3089
3090 GanvCanvas* canvas = GANV_CANVAS(widget);
3091
3092 /* Recenter the view, if appropriate */
3093
3094 canvas->layout.hadjustment->page_size = allocation->width;
3095 canvas->layout.hadjustment->page_increment = allocation->width / 2;
3096
3097 canvas->layout.vadjustment->page_size = allocation->height;
3098 canvas->layout.vadjustment->page_increment = allocation->height / 2;
3099
3100 scroll_to(canvas,
3101 canvas->layout.hadjustment->value,
3102 canvas->layout.vadjustment->value);
3103
3104 g_signal_emit_by_name(canvas->layout.hadjustment, "changed");
3105 g_signal_emit_by_name(canvas->layout.vadjustment, "changed");
3106 }
3107
3108 /* Returns whether the item is an inferior of or is equal to the parent. */
3109 static gboolean
is_descendant(GanvItem * item,GanvItem * parent)3110 is_descendant(GanvItem* item, GanvItem* parent)
3111 {
3112 for (; item; item = item->impl->parent) {
3113 if (item == parent) {
3114 return TRUE;
3115 }
3116 }
3117
3118 return FALSE;
3119 }
3120
3121
3122 /* Emits an event for an item in the canvas, be it the current item, grabbed
3123 * item, or focused item, as appropriate.
3124 */
3125 int
ganv_canvas_emit_event(GanvCanvas * canvas,GdkEvent * event)3126 ganv_canvas_emit_event(GanvCanvas* canvas, GdkEvent* event)
3127 {
3128 GdkEvent* ev = NULL;
3129 gint finished = 0;
3130 GanvItem* item = NULL;
3131 GanvItem* parent = NULL;
3132 guint mask = 0;
3133
3134 /* Perform checks for grabbed items */
3135
3136 if (canvas->impl->grabbed_item
3137 && !is_descendant(canvas->impl->current_item, canvas->impl->grabbed_item)) {
3138 /* I think this warning is annoying and I don't know what it's for
3139 * so I'll disable it for now.
3140 */
3141 /* g_warning ("emit_event() returning FALSE!\n");*/
3142 return FALSE;
3143 }
3144
3145 if (canvas->impl->grabbed_item) {
3146 switch (event->type) {
3147 case GDK_ENTER_NOTIFY:
3148 mask = GDK_ENTER_NOTIFY_MASK;
3149 break;
3150
3151 case GDK_LEAVE_NOTIFY:
3152 mask = GDK_LEAVE_NOTIFY_MASK;
3153 break;
3154
3155 case GDK_MOTION_NOTIFY:
3156 mask = GDK_POINTER_MOTION_MASK;
3157 break;
3158
3159 case GDK_BUTTON_PRESS:
3160 case GDK_2BUTTON_PRESS:
3161 case GDK_3BUTTON_PRESS:
3162 mask = GDK_BUTTON_PRESS_MASK;
3163 break;
3164
3165 case GDK_BUTTON_RELEASE:
3166 mask = GDK_BUTTON_RELEASE_MASK;
3167 break;
3168
3169 case GDK_KEY_PRESS:
3170 mask = GDK_KEY_PRESS_MASK;
3171 break;
3172
3173 case GDK_KEY_RELEASE:
3174 mask = GDK_KEY_RELEASE_MASK;
3175 break;
3176
3177 case GDK_SCROLL:
3178 mask = GDK_SCROLL_MASK;
3179 break;
3180
3181 default:
3182 mask = 0;
3183 break;
3184 }
3185
3186 if (!(mask & canvas->impl->grabbed_event_mask)) {
3187 return FALSE;
3188 }
3189 }
3190
3191 /* Convert to world coordinates -- we have two cases because of diferent
3192 * offsets of the fields in the event structures.
3193 */
3194
3195 ev = gdk_event_copy(event);
3196
3197 switch (ev->type) {
3198 case GDK_ENTER_NOTIFY:
3199 case GDK_LEAVE_NOTIFY:
3200 ganv_canvas_window_to_world(canvas,
3201 ev->crossing.x, ev->crossing.y,
3202 &ev->crossing.x, &ev->crossing.y);
3203 break;
3204
3205 case GDK_MOTION_NOTIFY:
3206 case GDK_BUTTON_PRESS:
3207 case GDK_2BUTTON_PRESS:
3208 case GDK_3BUTTON_PRESS:
3209 case GDK_BUTTON_RELEASE:
3210 ganv_canvas_window_to_world(canvas,
3211 ev->motion.x, ev->motion.y,
3212 &ev->motion.x, &ev->motion.y);
3213 break;
3214
3215 default:
3216 break;
3217 }
3218
3219 /* Choose where we send the event */
3220
3221 item = canvas->impl->current_item;
3222
3223 if (canvas->impl->focused_item
3224 && ((event->type == GDK_KEY_PRESS)
3225 || (event->type == GDK_KEY_RELEASE)
3226 || (event->type == GDK_FOCUS_CHANGE))) {
3227 item = canvas->impl->focused_item;
3228 }
3229
3230 /* The event is propagated up the hierarchy (for if someone connected to
3231 * a group instead of a leaf event), and emission is stopped if a
3232 * handler returns TRUE, just like for GtkWidget events.
3233 */
3234
3235 finished = FALSE;
3236
3237 while (item && !finished) {
3238 g_object_ref(G_OBJECT(item));
3239
3240 ganv_item_emit_event(item, ev, &finished);
3241
3242 parent = item->impl->parent;
3243 g_object_unref(G_OBJECT(item));
3244
3245 item = parent;
3246 }
3247
3248 gdk_event_free(ev);
3249
3250 return finished;
3251 }
3252
3253 void
ganv_canvas_set_need_repick(GanvCanvas * canvas)3254 ganv_canvas_set_need_repick(GanvCanvas* canvas)
3255 {
3256 canvas->impl->need_repick = TRUE;
3257 }
3258
3259 void
ganv_canvas_forget_item(GanvCanvas * canvas,GanvItem * item)3260 ganv_canvas_forget_item(GanvCanvas* canvas, GanvItem* item)
3261 {
3262 if (canvas->impl && item == canvas->impl->current_item) {
3263 canvas->impl->current_item = NULL;
3264 canvas->impl->need_repick = TRUE;
3265 }
3266
3267 if (canvas->impl && item == canvas->impl->new_current_item) {
3268 canvas->impl->new_current_item = NULL;
3269 canvas->impl->need_repick = TRUE;
3270 }
3271
3272 if (canvas->impl && item == canvas->impl->grabbed_item) {
3273 canvas->impl->grabbed_item = NULL;
3274 gdk_pointer_ungrab(GDK_CURRENT_TIME);
3275 }
3276
3277 if (canvas->impl && item == canvas->impl->focused_item) {
3278 canvas->impl->focused_item = NULL;
3279 }
3280 }
3281
3282 void
ganv_canvas_grab_focus(GanvCanvas * canvas,GanvItem * item)3283 ganv_canvas_grab_focus(GanvCanvas* canvas, GanvItem* item)
3284 {
3285 g_return_if_fail(GANV_IS_ITEM(item));
3286 g_return_if_fail(GTK_WIDGET_CAN_FOCUS(GTK_WIDGET(canvas)));
3287
3288 GanvItem* focused_item = canvas->impl->focused_item;
3289 GdkEvent ev;
3290
3291 if (focused_item) {
3292 ev.focus_change.type = GDK_FOCUS_CHANGE;
3293 ev.focus_change.window = canvas->layout.bin_window;
3294 ev.focus_change.send_event = FALSE;
3295 ev.focus_change.in = FALSE;
3296
3297 ganv_canvas_emit_event(canvas, &ev);
3298 }
3299
3300 canvas->impl->focused_item = item;
3301 gtk_widget_grab_focus(GTK_WIDGET(canvas));
3302
3303 if (focused_item) {
3304 ev.focus_change.type = GDK_FOCUS_CHANGE;
3305 ev.focus_change.window = canvas->layout.bin_window;
3306 ev.focus_change.send_event = FALSE;
3307 ev.focus_change.in = TRUE;
3308
3309 ganv_canvas_emit_event(canvas, &ev);
3310 }
3311 }
3312
3313 /**
3314 * ganv_canvas_grab_item:
3315 * @item: A canvas item.
3316 * @event_mask: Mask of events that will be sent to this item.
3317 * @cursor: If non-NULL, the cursor that will be used while the grab is active.
3318 * @etime: The timestamp required for grabbing the mouse, or GDK_CURRENT_TIME.
3319 *
3320 * Specifies that all events that match the specified event mask should be sent
3321 * to the specified item, and also grabs the mouse by calling
3322 * gdk_pointer_grab(). The event mask is also used when grabbing the pointer.
3323 * If @cursor is not NULL, then that cursor is used while the grab is active.
3324 * The @etime parameter is the timestamp required for grabbing the mouse.
3325 *
3326 * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If
3327 * the specified item was hidden by calling ganv_item_hide(), then it
3328 * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling
3329 * gdk_pointer_grab().
3330 **/
3331 int
ganv_canvas_grab_item(GanvItem * item,guint event_mask,GdkCursor * cursor,guint32 etime)3332 ganv_canvas_grab_item(GanvItem* item, guint event_mask, GdkCursor* cursor, guint32 etime)
3333 {
3334 g_return_val_if_fail(GANV_IS_ITEM(item), GDK_GRAB_NOT_VIEWABLE);
3335 g_return_val_if_fail(GTK_WIDGET_MAPPED(item->impl->canvas), GDK_GRAB_NOT_VIEWABLE);
3336
3337 if (item->impl->canvas->impl->grabbed_item) {
3338 return GDK_GRAB_ALREADY_GRABBED;
3339 }
3340
3341 if (!(item->object.flags & GANV_ITEM_VISIBLE)) {
3342 return GDK_GRAB_NOT_VIEWABLE;
3343 }
3344
3345 int retval = gdk_pointer_grab(item->impl->canvas->layout.bin_window,
3346 FALSE,
3347 (GdkEventMask)event_mask,
3348 NULL,
3349 cursor,
3350 etime);
3351
3352 if (retval != GDK_GRAB_SUCCESS) {
3353 return retval;
3354 }
3355
3356 item->impl->canvas->impl->grabbed_item = item;
3357 item->impl->canvas->impl->grabbed_event_mask = event_mask;
3358 item->impl->canvas->impl->current_item = item; /* So that events go to the grabbed item */
3359
3360 return retval;
3361 }
3362
3363 /**
3364 * ganv_canvas_ungrab_item:
3365 * @item: A canvas item that holds a grab.
3366 * @etime: The timestamp for ungrabbing the mouse.
3367 *
3368 * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
3369 * mouse.
3370 **/
3371 void
ganv_canvas_ungrab_item(GanvItem * item,guint32 etime)3372 ganv_canvas_ungrab_item(GanvItem* item, guint32 etime)
3373 {
3374 g_return_if_fail(GANV_IS_ITEM(item));
3375
3376 if (item->impl->canvas->impl->grabbed_item != item) {
3377 return;
3378 }
3379
3380 item->impl->canvas->impl->grabbed_item = NULL;
3381
3382 gdk_pointer_ungrab(etime);
3383 }
3384
3385 void
ganv_canvas_get_zoom_offsets(GanvCanvas * canvas,int * x,int * y)3386 ganv_canvas_get_zoom_offsets(GanvCanvas* canvas, int* x, int* y)
3387 {
3388 *x = canvas->impl->zoom_xofs;
3389 *y = canvas->impl->zoom_yofs;
3390 }
3391
3392 /* Re-picks the current item in the canvas, based on the event's coordinates.
3393 * Also emits enter/leave events for items as appropriate.
3394 */
3395 static int
pick_current_item(GanvCanvas * canvas,GdkEvent * event)3396 pick_current_item(GanvCanvas* canvas, GdkEvent* event)
3397 {
3398 int retval = FALSE;
3399
3400 /* If a button is down, we'll perform enter and leave events on the
3401 * current item, but not enter on any other item. This is more or less
3402 * like X pointer grabbing for canvas items.
3403 */
3404 int button_down = canvas->impl->state & (GDK_BUTTON1_MASK
3405 | GDK_BUTTON2_MASK
3406 | GDK_BUTTON3_MASK
3407 | GDK_BUTTON4_MASK
3408 | GDK_BUTTON5_MASK);
3409 if (!button_down) {
3410 canvas->impl->left_grabbed_item = FALSE;
3411 }
3412
3413 /* Save the event in the canvas. This is used to synthesize enter and
3414 * leave events in case the current item changes. It is also used to
3415 * re-pick the current item if the current one gets deleted. Also,
3416 * synthesize an enter event.
3417 */
3418 if (event != &canvas->impl->pick_event) {
3419 if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) {
3420 /* these fields have the same offsets in both types of events */
3421
3422 canvas->impl->pick_event.crossing.type = GDK_ENTER_NOTIFY;
3423 canvas->impl->pick_event.crossing.window = event->motion.window;
3424 canvas->impl->pick_event.crossing.send_event = event->motion.send_event;
3425 canvas->impl->pick_event.crossing.subwindow = NULL;
3426 canvas->impl->pick_event.crossing.x = event->motion.x;
3427 canvas->impl->pick_event.crossing.y = event->motion.y;
3428 canvas->impl->pick_event.crossing.mode = GDK_CROSSING_NORMAL;
3429 canvas->impl->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR;
3430 canvas->impl->pick_event.crossing.focus = FALSE;
3431 canvas->impl->pick_event.crossing.state = event->motion.state;
3432
3433 /* these fields don't have the same offsets in both types of events */
3434
3435 if (event->type == GDK_MOTION_NOTIFY) {
3436 canvas->impl->pick_event.crossing.x_root = event->motion.x_root;
3437 canvas->impl->pick_event.crossing.y_root = event->motion.y_root;
3438 } else {
3439 canvas->impl->pick_event.crossing.x_root = event->button.x_root;
3440 canvas->impl->pick_event.crossing.y_root = event->button.y_root;
3441 }
3442 } else {
3443 canvas->impl->pick_event = *event;
3444 }
3445 }
3446
3447 /* Don't do anything else if this is a recursive call */
3448
3449 if (canvas->impl->in_repick) {
3450 return retval;
3451 }
3452
3453 /* LeaveNotify means that there is no current item, so we don't look for one */
3454
3455 if (canvas->impl->pick_event.type != GDK_LEAVE_NOTIFY) {
3456 /* these fields don't have the same offsets in both types of events */
3457
3458 double x = 0.0;
3459 double y = 0.0;
3460 if (canvas->impl->pick_event.type == GDK_ENTER_NOTIFY) {
3461 x = canvas->impl->pick_event.crossing.x - canvas->impl->zoom_xofs;
3462 y = canvas->impl->pick_event.crossing.y - canvas->impl->zoom_yofs;
3463 } else {
3464 x = canvas->impl->pick_event.motion.x - canvas->impl->zoom_xofs;
3465 y = canvas->impl->pick_event.motion.y - canvas->impl->zoom_yofs;
3466 }
3467
3468 /* world coords */
3469
3470 x = canvas->impl->scroll_x1 + x / canvas->impl->pixels_per_unit;
3471 y = canvas->impl->scroll_y1 + y / canvas->impl->pixels_per_unit;
3472
3473 /* find the closest item */
3474
3475 if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) {
3476 GANV_ITEM_GET_CLASS(canvas->impl->root)->point(
3477 canvas->impl->root,
3478 x - canvas->impl->root->impl->x, y - canvas->impl->root->impl->y,
3479 &canvas->impl->new_current_item);
3480 } else {
3481 canvas->impl->new_current_item = NULL;
3482 }
3483 } else {
3484 canvas->impl->new_current_item = NULL;
3485 }
3486
3487 if ((canvas->impl->new_current_item == canvas->impl->current_item) && !canvas->impl->left_grabbed_item) {
3488 return retval; /* current item did not change */
3489
3490 }
3491 /* Synthesize events for old and new current items */
3492
3493 if ((canvas->impl->new_current_item != canvas->impl->current_item)
3494 && (canvas->impl->current_item != NULL)
3495 && !canvas->impl->left_grabbed_item) {
3496 GdkEvent new_event;
3497
3498 new_event = canvas->impl->pick_event;
3499 new_event.type = GDK_LEAVE_NOTIFY;
3500
3501 new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
3502 new_event.crossing.subwindow = NULL;
3503 canvas->impl->in_repick = TRUE;
3504 retval = ganv_canvas_emit_event(canvas, &new_event);
3505 canvas->impl->in_repick = FALSE;
3506 }
3507
3508 /* new_current_item may have been set to NULL during the call to ganv_canvas_emit_event() above */
3509
3510 if ((canvas->impl->new_current_item != canvas->impl->current_item) && button_down) {
3511 canvas->impl->left_grabbed_item = TRUE;
3512 return retval;
3513 }
3514
3515 /* Handle the rest of cases */
3516
3517 canvas->impl->left_grabbed_item = FALSE;
3518 canvas->impl->current_item = canvas->impl->new_current_item;
3519
3520 if (canvas->impl->current_item != NULL) {
3521 GdkEvent new_event;
3522
3523 new_event = canvas->impl->pick_event;
3524 new_event.type = GDK_ENTER_NOTIFY;
3525 new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
3526 new_event.crossing.subwindow = NULL;
3527 retval = ganv_canvas_emit_event(canvas, &new_event);
3528 }
3529
3530 return retval;
3531 }
3532
3533 /* Button event handler for the canvas */
3534 static gint
ganv_canvas_button(GtkWidget * widget,GdkEventButton * event)3535 ganv_canvas_button(GtkWidget* widget, GdkEventButton* event)
3536 {
3537 int mask = 0;
3538 int retval = FALSE;
3539
3540 g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
3541 g_return_val_if_fail(event != NULL, FALSE);
3542
3543 GanvCanvas* canvas = GANV_CANVAS(widget);
3544
3545 /*
3546 * dispatch normally regardless of the event's window if an item has
3547 * has a pointer grab in effect
3548 */
3549 if (!canvas->impl->grabbed_item && ( event->window != canvas->layout.bin_window) ) {
3550 return retval;
3551 }
3552
3553 switch (event->button) {
3554 case 1:
3555 mask = GDK_BUTTON1_MASK;
3556 break;
3557 case 2:
3558 mask = GDK_BUTTON2_MASK;
3559 break;
3560 case 3:
3561 mask = GDK_BUTTON3_MASK;
3562 break;
3563 case 4:
3564 mask = GDK_BUTTON4_MASK;
3565 break;
3566 case 5:
3567 mask = GDK_BUTTON5_MASK;
3568 break;
3569 default:
3570 mask = 0;
3571 }
3572
3573 switch (event->type) {
3574 case GDK_BUTTON_PRESS:
3575 case GDK_2BUTTON_PRESS:
3576 case GDK_3BUTTON_PRESS:
3577 /* Pick the current item as if the button were not pressed, and
3578 * then process the event.
3579 */
3580 canvas->impl->state = event->state;
3581 pick_current_item(canvas, (GdkEvent*)event);
3582 canvas->impl->state ^= mask;
3583 retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3584 break;
3585
3586 case GDK_BUTTON_RELEASE:
3587 /* Process the event as if the button were pressed, then repick
3588 * after the button has been released
3589 */
3590 canvas->impl->state = event->state;
3591 retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3592 event->state ^= mask;
3593 canvas->impl->state = event->state;
3594 pick_current_item(canvas, (GdkEvent*)event);
3595 event->state ^= mask;
3596 break;
3597
3598 default:
3599 g_assert_not_reached();
3600 }
3601
3602 return retval;
3603 }
3604
3605 /* Motion event handler for the canvas */
3606 static gint
ganv_canvas_motion(GtkWidget * widget,GdkEventMotion * event)3607 ganv_canvas_motion(GtkWidget* widget, GdkEventMotion* event)
3608 {
3609 g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
3610 g_return_val_if_fail(event != NULL, FALSE);
3611
3612 GanvCanvas* canvas = GANV_CANVAS(widget);
3613
3614 if (event->window != canvas->layout.bin_window) {
3615 return FALSE;
3616 }
3617
3618 canvas->impl->state = event->state;
3619 pick_current_item(canvas, (GdkEvent*)event);
3620 return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3621 }
3622
3623 static gboolean
ganv_canvas_scroll(GtkWidget * widget,GdkEventScroll * event)3624 ganv_canvas_scroll(GtkWidget* widget, GdkEventScroll* event)
3625 {
3626 g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
3627 g_return_val_if_fail(event != NULL, FALSE);
3628
3629 GanvCanvas* canvas = GANV_CANVAS(widget);
3630
3631 if (event->window != canvas->layout.bin_window) {
3632 return FALSE;
3633 }
3634
3635 canvas->impl->state = event->state;
3636 pick_current_item(canvas, (GdkEvent*)event);
3637 return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3638 }
3639
3640 /* Key event handler for the canvas */
3641 static gboolean
ganv_canvas_key(GtkWidget * widget,GdkEventKey * event)3642 ganv_canvas_key(GtkWidget* widget, GdkEventKey* event)
3643 {
3644 g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
3645 g_return_val_if_fail(event != NULL, FALSE);
3646
3647 GanvCanvas* canvas = GANV_CANVAS(widget);
3648
3649 if (!ganv_canvas_emit_event(canvas, (GdkEvent*)event)) {
3650 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(canvas_parent_class);
3651
3652 if (event->type == GDK_KEY_PRESS) {
3653 if (widget_class->key_press_event) {
3654 return (*widget_class->key_press_event)(widget, event);
3655 }
3656 } else if (event->type == GDK_KEY_RELEASE) {
3657 if (widget_class->key_release_event) {
3658 return (*widget_class->key_release_event)(widget, event);
3659 }
3660 } else {
3661 g_assert_not_reached();
3662 }
3663
3664 return FALSE;
3665 } else {
3666 return TRUE;
3667 }
3668 }
3669
3670 /* Crossing event handler for the canvas */
3671 static gint
ganv_canvas_crossing(GtkWidget * widget,GdkEventCrossing * event)3672 ganv_canvas_crossing(GtkWidget* widget, GdkEventCrossing* event)
3673 {
3674 g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
3675 g_return_val_if_fail(event != NULL, FALSE);
3676
3677 GanvCanvas* canvas = GANV_CANVAS(widget);
3678
3679 if (event->window != canvas->layout.bin_window) {
3680 return FALSE;
3681 }
3682
3683 canvas->impl->state = event->state;
3684 return pick_current_item(canvas, (GdkEvent*)event);
3685 }
3686
3687 /* Focus in handler for the canvas */
3688 static gint
ganv_canvas_focus_in(GtkWidget * widget,GdkEventFocus * event)3689 ganv_canvas_focus_in(GtkWidget* widget, GdkEventFocus* event)
3690 {
3691 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
3692
3693 GanvCanvas* canvas = GANV_CANVAS(widget);
3694
3695 if (canvas->impl->focused_item) {
3696 return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3697 } else {
3698 return FALSE;
3699 }
3700 }
3701
3702 /* Focus out handler for the canvas */
3703 static gint
ganv_canvas_focus_out(GtkWidget * widget,GdkEventFocus * event)3704 ganv_canvas_focus_out(GtkWidget* widget, GdkEventFocus* event)
3705 {
3706 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
3707
3708 GanvCanvas* canvas = GANV_CANVAS(widget);
3709
3710 if (canvas->impl->focused_item) {
3711 return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
3712 } else {
3713 return FALSE;
3714 }
3715 }
3716
3717 #define REDRAW_QUANTUM_SIZE 512
3718
3719 static void
ganv_canvas_paint_rect(GanvCanvas * canvas,gint x0,gint y0,gint x1,gint y1)3720 ganv_canvas_paint_rect(GanvCanvas* canvas, gint x0, gint y0, gint x1, gint y1)
3721 {
3722 g_return_if_fail(!canvas->impl->need_update);
3723
3724 gint draw_x1 =
3725 MAX(x0, canvas->layout.hadjustment->value - canvas->impl->zoom_xofs);
3726 gint draw_y1 =
3727 MAX(y0, canvas->layout.vadjustment->value - canvas->impl->zoom_yofs);
3728
3729 gint draw_x2 = MIN(draw_x1 + GTK_WIDGET(canvas)->allocation.width, x1);
3730 gint draw_y2 = MIN(draw_y1 + GTK_WIDGET(canvas)->allocation.height, y1);
3731
3732 gint draw_width = draw_x2 - draw_x1;
3733 gint draw_height = draw_y2 - draw_y1;
3734
3735 if ((draw_width < 1) || (draw_height < 1)) {
3736 return;
3737 }
3738
3739 canvas->impl->redraw_x1 = draw_x1;
3740 canvas->impl->redraw_y1 = draw_y1;
3741 canvas->impl->redraw_x2 = draw_x2;
3742 canvas->impl->redraw_y2 = draw_y2;
3743 canvas->impl->draw_xofs = draw_x1;
3744 canvas->impl->draw_yofs = draw_y1;
3745
3746 cairo_t* cr = gdk_cairo_create(canvas->layout.bin_window);
3747
3748 double win_x = 0.0;
3749 double win_y = 0.0;
3750 ganv_canvas_window_to_world(canvas, 0, 0, &win_x, &win_y);
3751 cairo_translate(cr, -win_x, -win_y);
3752 cairo_scale(cr, canvas->impl->pixels_per_unit, canvas->impl->pixels_per_unit);
3753
3754 if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) {
3755 double wx1 = 0.0;
3756 double wy1 = 0.0;
3757 double ww = 0.0;
3758 double wh = 0.0;
3759 ganv_canvas_c2w(canvas, draw_x1, draw_y1, &wx1, &wy1);
3760 ganv_canvas_c2w(canvas, draw_width, draw_height, &ww, &wh);
3761
3762 // Draw background
3763 double r = 0.0;
3764 double g = 0.0;
3765 double b = 0.0;
3766 double a = 0.0;
3767 color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a);
3768 cairo_set_source_rgba(cr, r, g, b, a);
3769 cairo_rectangle(cr, wx1, wy1, ww, wh);
3770 cairo_fill(cr);
3771
3772 // Draw root group
3773 (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)(
3774 canvas->impl->root, cr,
3775 wx1, wy1, ww, wh);
3776 }
3777
3778 cairo_destroy(cr);
3779 }
3780
3781 /* Expose handler for the canvas */
3782 static gint
ganv_canvas_expose(GtkWidget * widget,GdkEventExpose * event)3783 ganv_canvas_expose(GtkWidget* widget, GdkEventExpose* event)
3784 {
3785 GanvCanvas* canvas = GANV_CANVAS(widget);
3786 if (!GTK_WIDGET_DRAWABLE(widget) ||
3787 (event->window != canvas->layout.bin_window)) {
3788 return FALSE;
3789 }
3790
3791 /* Find a single bounding rectangle for all rectangles in the region.
3792 Since drawing the root group is O(n) and thus very expensive for large
3793 canvases, it's much faster to do a single paint than many, even though
3794 more area may be painted that way. With a better group implementation,
3795 it would likely be better to paint each changed rectangle separately. */
3796 GdkRectangle clip;
3797 gdk_region_get_clipbox(event->region, &clip);
3798
3799 const int x2 = clip.x + clip.width;
3800 const int y2 = clip.y + clip.height;
3801
3802 if (canvas->impl->need_update || canvas->impl->need_redraw) {
3803 /* Update or drawing is scheduled, so just mark exposed area as dirty */
3804 ganv_canvas_request_redraw_c(canvas, clip.x, clip.y, x2, y2);
3805 } else {
3806 /* No pending updates, draw exposed area immediately */
3807 ganv_canvas_paint_rect(canvas, clip.x, clip.y, x2, y2);
3808
3809 /* And call expose on parent container class */
3810 if (GTK_WIDGET_CLASS(canvas_parent_class)->expose_event) {
3811 (*GTK_WIDGET_CLASS(canvas_parent_class)->expose_event)(
3812 widget, event);
3813 }
3814 }
3815
3816 return FALSE;
3817 }
3818
3819 /* Repaints the areas in the canvas that need it */
3820 static void
paint(GanvCanvas * canvas)3821 paint(GanvCanvas* canvas)
3822 {
3823 for (GSList* l = canvas->impl->redraw_region; l; l = l->next) {
3824 IRect* rect = (IRect*)l->data;
3825
3826 const GdkRectangle gdkrect = {
3827 rect->x + canvas->impl->zoom_xofs,
3828 rect->y + canvas->impl->zoom_yofs,
3829 rect->width,
3830 rect->height
3831 };
3832
3833 gdk_window_invalidate_rect(canvas->layout.bin_window, &gdkrect, FALSE);
3834 g_free(rect);
3835 }
3836
3837 g_slist_free(canvas->impl->redraw_region);
3838 canvas->impl->redraw_region = NULL;
3839 canvas->impl->need_redraw = FALSE;
3840
3841 canvas->impl->redraw_x1 = 0;
3842 canvas->impl->redraw_y1 = 0;
3843 canvas->impl->redraw_x2 = 0;
3844 canvas->impl->redraw_y2 = 0;
3845 }
3846
3847 static void
do_update(GanvCanvas * canvas)3848 do_update(GanvCanvas* canvas)
3849 {
3850 /* Cause the update if necessary */
3851
3852 update_again:
3853 if (canvas->impl->need_update) {
3854 ganv_item_invoke_update(canvas->impl->root, 0);
3855
3856 canvas->impl->need_update = FALSE;
3857 }
3858
3859 /* Pick new current item */
3860
3861 while (canvas->impl->need_repick) {
3862 canvas->impl->need_repick = FALSE;
3863 pick_current_item(canvas, &canvas->impl->pick_event);
3864 }
3865
3866 /* it is possible that during picking we emitted an event in which
3867 the user then called some function which then requested update
3868 of something. Without this we'd be left in a state where
3869 need_update would have been left TRUE and the canvas would have
3870 been left unpainted. */
3871 if (canvas->impl->need_update) {
3872 goto update_again;
3873 }
3874
3875 /* Paint if able to */
3876
3877 if (GTK_WIDGET_DRAWABLE(canvas) && canvas->impl->need_redraw) {
3878 paint(canvas);
3879 }
3880 }
3881
3882 /* Idle handler for the canvas. It deals with pending updates and redraws. */
3883 static gboolean
idle_handler(gpointer data)3884 idle_handler(gpointer data)
3885 {
3886 GDK_THREADS_ENTER();
3887
3888 GanvCanvas* canvas = GANV_CANVAS(data);
3889
3890 do_update(canvas);
3891
3892 /* Reset idle id */
3893 canvas->impl->idle_id = 0;
3894
3895 GDK_THREADS_LEAVE();
3896
3897 return FALSE;
3898 }
3899
3900 /* Convenience function to add an idle handler to a canvas */
3901 static void
add_idle(GanvCanvas * canvas)3902 add_idle(GanvCanvas* canvas)
3903 {
3904 g_assert(canvas->impl->need_update || canvas->impl->need_redraw);
3905
3906 if (!canvas->impl->idle_id) {
3907 canvas->impl->idle_id = g_idle_add_full(CANVAS_IDLE_PRIORITY,
3908 idle_handler,
3909 canvas,
3910 NULL);
3911 }
3912
3913 /* canvas->idle_id = gtk_idle_add (idle_handler, canvas); */
3914 }
3915
3916 GanvItem*
ganv_canvas_root(GanvCanvas * canvas)3917 ganv_canvas_root(GanvCanvas* canvas)
3918 {
3919 g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL);
3920
3921 return canvas->impl->root;
3922 }
3923
3924 void
ganv_canvas_set_scroll_region(GanvCanvas * canvas,double x1,double y1,double x2,double y2)3925 ganv_canvas_set_scroll_region(GanvCanvas* canvas,
3926 double x1, double y1, double x2, double y2)
3927 {
3928 double wxofs = 0.0;
3929 double wyofs = 0.0;
3930 int xofs = 0;
3931 int yofs = 0;
3932
3933 g_return_if_fail(GANV_IS_CANVAS(canvas));
3934
3935 /*
3936 * Set the new scrolling region. If possible, do not move the visible contents of the
3937 * canvas.
3938 */
3939
3940 ganv_canvas_c2w(canvas,
3941 GTK_LAYOUT(canvas)->hadjustment->value + canvas->impl->zoom_xofs,
3942 GTK_LAYOUT(canvas)->vadjustment->value + canvas->impl->zoom_yofs,
3943 /*canvas->impl->zoom_xofs,
3944 canvas->impl->zoom_yofs,*/
3945 &wxofs, &wyofs);
3946
3947 canvas->impl->scroll_x1 = x1;
3948 canvas->impl->scroll_y1 = y1;
3949 canvas->impl->scroll_x2 = x2;
3950 canvas->impl->scroll_y2 = y2;
3951
3952 ganv_canvas_w2c(canvas, wxofs, wyofs, &xofs, &yofs);
3953
3954 scroll_to(canvas, xofs, yofs);
3955
3956 canvas->impl->need_repick = TRUE;
3957 #if 0
3958 /* todo: should be requesting update */
3959 (*GANV_ITEM_CLASS(canvas->impl->root->object.klass)->update)(
3960 canvas->impl->root, NULL, NULL, 0);
3961 #endif
3962 }
3963
3964 void
ganv_canvas_get_scroll_region(GanvCanvas * canvas,double * x1,double * y1,double * x2,double * y2)3965 ganv_canvas_get_scroll_region(GanvCanvas* canvas,
3966 double* x1, double* y1, double* x2, double* y2)
3967 {
3968 g_return_if_fail(GANV_IS_CANVAS(canvas));
3969
3970 if (x1) {
3971 *x1 = canvas->impl->scroll_x1;
3972 }
3973
3974 if (y1) {
3975 *y1 = canvas->impl->scroll_y1;
3976 }
3977
3978 if (x2) {
3979 *x2 = canvas->impl->scroll_x2;
3980 }
3981
3982 if (y2) {
3983 *y2 = canvas->impl->scroll_y2;
3984 }
3985 }
3986
3987 void
ganv_canvas_set_center_scroll_region(GanvCanvas * canvas,gboolean center_scroll_region)3988 ganv_canvas_set_center_scroll_region(GanvCanvas* canvas, gboolean center_scroll_region)
3989 {
3990 g_return_if_fail(GANV_IS_CANVAS(canvas));
3991
3992 canvas->impl->center_scroll_region = center_scroll_region != 0;
3993
3994 scroll_to(canvas,
3995 canvas->layout.hadjustment->value,
3996 canvas->layout.vadjustment->value);
3997 }
3998
3999 gboolean
ganv_canvas_get_center_scroll_region(const GanvCanvas * canvas)4000 ganv_canvas_get_center_scroll_region(const GanvCanvas* canvas)
4001 {
4002 g_return_val_if_fail(GANV_IS_CANVAS(canvas), FALSE);
4003
4004 return canvas->impl->center_scroll_region ? TRUE : FALSE;
4005 }
4006
4007 void
ganv_canvas_scroll_to(GanvCanvas * canvas,int cx,int cy)4008 ganv_canvas_scroll_to(GanvCanvas* canvas, int cx, int cy)
4009 {
4010 g_return_if_fail(GANV_IS_CANVAS(canvas));
4011
4012 scroll_to(canvas, cx, cy);
4013 }
4014
4015 void
ganv_canvas_get_scroll_offsets(const GanvCanvas * canvas,int * cx,int * cy)4016 ganv_canvas_get_scroll_offsets(const GanvCanvas* canvas, int* cx, int* cy)
4017 {
4018 g_return_if_fail(GANV_IS_CANVAS(canvas));
4019
4020 if (cx) {
4021 *cx = canvas->layout.hadjustment->value;
4022 }
4023
4024 if (cy) {
4025 *cy = canvas->layout.vadjustment->value;
4026 }
4027 }
4028
4029 GanvItem*
ganv_canvas_get_item_at(GanvCanvas * canvas,double x,double y)4030 ganv_canvas_get_item_at(GanvCanvas* canvas, double x, double y)
4031 {
4032 g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL);
4033
4034 GanvItem* item = NULL;
4035 double dist = GANV_ITEM_GET_CLASS(canvas->impl->root)->point(
4036 canvas->impl->root,
4037 x - canvas->impl->root->impl->x,
4038 y - canvas->impl->root->impl->y,
4039 &item);
4040 if ((int)(dist * canvas->impl->pixels_per_unit + 0.5) <= GANV_CLOSE_ENOUGH) {
4041 return item;
4042 } else {
4043 return NULL;
4044 }
4045 }
4046
4047 void
ganv_canvas_request_update(GanvCanvas * canvas)4048 ganv_canvas_request_update(GanvCanvas* canvas)
4049 {
4050 if (canvas->impl->need_update) {
4051 return;
4052 }
4053
4054 canvas->impl->need_update = TRUE;
4055 if (GTK_WIDGET_MAPPED((GtkWidget*)canvas)) {
4056 add_idle(canvas);
4057 }
4058 }
4059
4060 static inline gboolean
rect_overlaps(const IRect * a,const IRect * b)4061 rect_overlaps(const IRect* a, const IRect* b)
4062 {
4063 if ((a->x > b->x + b->width) ||
4064 (a->y > b->y + b->height) ||
4065 (a->x + a->width < b->x) ||
4066 (a->y + a->height < b->y)) {
4067 return FALSE;
4068 }
4069 return TRUE;
4070 }
4071
4072 static inline gboolean
rect_is_visible(GanvCanvas * canvas,const IRect * r)4073 rect_is_visible(GanvCanvas* canvas, const IRect* r)
4074 {
4075 const IRect rect = {
4076 (int)(canvas->layout.hadjustment->value - canvas->impl->zoom_xofs),
4077 (int)(canvas->layout.vadjustment->value - canvas->impl->zoom_yofs),
4078 GTK_WIDGET(canvas)->allocation.width,
4079 GTK_WIDGET(canvas)->allocation.height
4080 };
4081
4082 return rect_overlaps(&rect, r);
4083 }
4084
4085 void
ganv_canvas_request_redraw_c(GanvCanvas * canvas,int x1,int y1,int x2,int y2)4086 ganv_canvas_request_redraw_c(GanvCanvas* canvas,
4087 int x1, int y1, int x2, int y2)
4088 {
4089 g_return_if_fail(GANV_IS_CANVAS(canvas));
4090
4091 if (!GTK_WIDGET_DRAWABLE(canvas) || (x1 >= x2) || (y1 >= y2)) {
4092 return;
4093 }
4094
4095 const IRect rect = { x1, y1, x2 - x1, y2 - y1 };
4096
4097 if (!rect_is_visible(canvas, &rect)) {
4098 return;
4099 }
4100
4101 IRect* r = (IRect*)g_malloc(sizeof(IRect));
4102 *r = rect;
4103
4104 canvas->impl->redraw_region = g_slist_prepend(canvas->impl->redraw_region, r);
4105 canvas->impl->need_redraw = TRUE;
4106
4107 if (canvas->impl->idle_id == 0) {
4108 add_idle(canvas);
4109 }
4110 }
4111
4112 /* Request a redraw of the specified rectangle in world coordinates */
4113 void
ganv_canvas_request_redraw_w(GanvCanvas * canvas,double x1,double y1,double x2,double y2)4114 ganv_canvas_request_redraw_w(GanvCanvas* canvas,
4115 double x1, double y1, double x2, double y2)
4116 {
4117 int cx1 = 0;
4118 int cx2 = 0;
4119 int cy1 = 0;
4120 int cy2 = 0;
4121 ganv_canvas_w2c(canvas, x1, y1, &cx1, &cy1);
4122 ganv_canvas_w2c(canvas, x2, y2, &cx2, &cy2);
4123 ganv_canvas_request_redraw_c(canvas, cx1, cy1, cx2, cy2);
4124 }
4125
4126 void
ganv_canvas_w2c_affine(GanvCanvas * canvas,cairo_matrix_t * matrix)4127 ganv_canvas_w2c_affine(GanvCanvas* canvas, cairo_matrix_t* matrix)
4128 {
4129 g_return_if_fail(GANV_IS_CANVAS(canvas));
4130 g_return_if_fail(matrix != NULL);
4131
4132 cairo_matrix_init_translate(matrix,
4133 -canvas->impl->scroll_x1,
4134 -canvas->impl->scroll_y1);
4135
4136 cairo_matrix_scale(matrix,
4137 canvas->impl->pixels_per_unit,
4138 canvas->impl->pixels_per_unit);
4139 }
4140
4141 void
ganv_canvas_w2c(GanvCanvas * canvas,double wx,double wy,int * cx,int * cy)4142 ganv_canvas_w2c(GanvCanvas* canvas, double wx, double wy, int* cx, int* cy)
4143 {
4144 g_return_if_fail(GANV_IS_CANVAS(canvas));
4145
4146 cairo_matrix_t matrix;
4147 ganv_canvas_w2c_affine(canvas, &matrix);
4148
4149 cairo_matrix_transform_point(&matrix, &wx, &wy);
4150
4151 if (cx) {
4152 *cx = floor(wx + 0.5);
4153 }
4154 if (cy) {
4155 *cy = floor(wy + 0.5);
4156 }
4157 }
4158
4159 void
ganv_canvas_w2c_d(GanvCanvas * canvas,double wx,double wy,double * cx,double * cy)4160 ganv_canvas_w2c_d(GanvCanvas* canvas, double wx, double wy, double* cx, double* cy)
4161 {
4162 g_return_if_fail(GANV_IS_CANVAS(canvas));
4163
4164 cairo_matrix_t matrix;
4165 ganv_canvas_w2c_affine(canvas, &matrix);
4166
4167 cairo_matrix_transform_point(&matrix, &wx, &wy);
4168
4169 if (cx) {
4170 *cx = wx;
4171 }
4172 if (cy) {
4173 *cy = wy;
4174 }
4175 }
4176
4177 void
ganv_canvas_c2w(GanvCanvas * canvas,int cx,int cy,double * wx,double * wy)4178 ganv_canvas_c2w(GanvCanvas* canvas, int cx, int cy, double* wx, double* wy)
4179 {
4180 g_return_if_fail(GANV_IS_CANVAS(canvas));
4181
4182 cairo_matrix_t matrix;
4183 ganv_canvas_w2c_affine(canvas, &matrix);
4184 cairo_matrix_invert(&matrix);
4185
4186 double x = cx;
4187 double y = cy;
4188 cairo_matrix_transform_point(&matrix, &x, &y);
4189
4190 if (wx) {
4191 *wx = x;
4192 }
4193 if (wy) {
4194 *wy = y;
4195 }
4196 }
4197
4198 void
ganv_canvas_window_to_world(GanvCanvas * canvas,double winx,double winy,double * worldx,double * worldy)4199 ganv_canvas_window_to_world(GanvCanvas* canvas, double winx, double winy,
4200 double* worldx, double* worldy)
4201 {
4202 g_return_if_fail(GANV_IS_CANVAS(canvas));
4203
4204 if (worldx) {
4205 *worldx = canvas->impl->scroll_x1 + ((winx - canvas->impl->zoom_xofs)
4206 / canvas->impl->pixels_per_unit);
4207 }
4208
4209 if (worldy) {
4210 *worldy = canvas->impl->scroll_y1 + ((winy - canvas->impl->zoom_yofs)
4211 / canvas->impl->pixels_per_unit);
4212 }
4213 }
4214
4215 void
ganv_canvas_world_to_window(GanvCanvas * canvas,double worldx,double worldy,double * winx,double * winy)4216 ganv_canvas_world_to_window(GanvCanvas* canvas, double worldx, double worldy,
4217 double* winx, double* winy)
4218 {
4219 g_return_if_fail(GANV_IS_CANVAS(canvas));
4220
4221 if (winx) {
4222 *winx = (canvas->impl->pixels_per_unit) * (worldx - canvas->impl->scroll_x1) + canvas->impl->zoom_xofs;
4223 }
4224
4225 if (winy) {
4226 *winy = (canvas->impl->pixels_per_unit) * (worldy - canvas->impl->scroll_y1) + canvas->impl->zoom_yofs;
4227 }
4228 }
4229
4230 void
ganv_canvas_set_port_order(GanvCanvas * canvas,GanvPortOrderFunc port_cmp,void * data)4231 ganv_canvas_set_port_order(GanvCanvas* canvas,
4232 GanvPortOrderFunc port_cmp,
4233 void* data)
4234 {
4235 g_return_if_fail(GANV_IS_CANVAS(canvas));
4236
4237 canvas->impl->_port_order.port_cmp = port_cmp;
4238 canvas->impl->_port_order.data = data;
4239 }
4240
4241 PortOrderCtx
ganv_canvas_get_port_order(GanvCanvas * canvas)4242 ganv_canvas_get_port_order(GanvCanvas* canvas)
4243 {
4244 return canvas->impl->_port_order;
4245 }
4246
4247 gboolean
ganv_canvas_exporting(GanvCanvas * canvas)4248 ganv_canvas_exporting(GanvCanvas* canvas)
4249 {
4250 return canvas->impl->exporting;
4251 }
4252
4253 } // extern "C"
4254