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, &reg.pos.x, &reg.pos.y, &reg.area.x, &reg.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