1 /*
2  * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2014-2015 Ben Loftis <ben@harrisonconsoles.com>
5  * Copyright (C) 2014-2015 David Robillard <d@drobilla.net>
6  * Copyright (C) 2014-2017 Robin Gareus <robin@gareus.org>
7  * Copyright (C) 2015-2017 Tim Mayberry <mojofunk@gmail.com>
8  * Copyright (C) 2015 John Emmas <john@creativepost.co.uk>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 /** @file  canvas/canvas.cc
26  *  @brief Implementation of the main canvas classes.
27  */
28 
29 #include <list>
30 #include <cassert>
31 #include <gtkmm/adjustment.h>
32 #include <gtkmm/label.h>
33 #include <gtkmm/window.h>
34 
35 #include "gtkmm2ext/persistent_tooltip.h"
36 
37 #include "pbd/compose.h"
38 
39 #include "canvas/canvas.h"
40 #include "gtkmm2ext/colors.h"
41 #include "canvas/debug.h"
42 #include "canvas/line.h"
43 #include "canvas/scroll_group.h"
44 
45 #ifdef __APPLE__
46 #include <gdk/gdk.h>
47 #include "gtkmm2ext/nsglview.h"
48 #endif
49 
50 using namespace std;
51 using namespace ArdourCanvas;
52 
53 uint32_t Canvas::tooltip_timeout_msecs = 750;
54 
55 /** Construct a new Canvas */
Canvas()56 Canvas::Canvas ()
57 	: _root (this)
58 	, _bg_color (Gtkmm2ext::rgba_to_color (0, 1.0, 0.0, 1.0))
59 	, _last_render_start_timestamp(0)
60 	, _use_intermediate_surface (false)
61 {
62 #ifdef __APPLE__
63 	_use_intermediate_surface = true;
64 #else
65 	_use_intermediate_surface = NULL != g_getenv("ARDOUR_INTERMEDIATE_SURFACE");
66 #endif
67 	set_epoch ();
68 }
69 
70 void
use_intermediate_surface(bool yn)71 Canvas::use_intermediate_surface (bool yn)
72 {
73 	if (_use_intermediate_surface == yn) {
74 		return;
75 	}
76 	_use_intermediate_surface = yn;
77 }
78 
79 void
scroll_to(Coord x,Coord y)80 Canvas::scroll_to (Coord x, Coord y)
81 {
82 	/* We do things this way because we do not want to recurse through
83 	   the canvas for every scroll. In the presence of large MIDI
84 	   tracks this means traversing item lists that include
85 	   thousands of items (notes).
86 
87 	   This design limits us to moving only those items (groups, typically)
88 	   that should move in certain ways as we scroll. In other terms, it
89 	   becomes O(1) rather than O(N).
90 	*/
91 
92 	for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
93 		(*i)->scroll_to (Duple (x, y));
94 	}
95 
96 	pick_current_item (0); // no current mouse position
97 }
98 
99 void
add_scroller(ScrollGroup & i)100 Canvas::add_scroller (ScrollGroup& i)
101 {
102 	scrollers.push_back (&i);
103 }
104 
105 void
zoomed()106 Canvas::zoomed ()
107 {
108 	pick_current_item (0); // no current mouse position
109 }
110 
111 /** Render an area of the canvas.
112  *  @param area Area in window coordinates.
113  *  @param context Cairo context to render to.
114  */
115 void
render(Rect const & area,Cairo::RefPtr<Cairo::Context> const & context) const116 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
117 {
118 	PreRender (); // emit signal
119 
120 	_last_render_start_timestamp = g_get_monotonic_time();
121 
122 #ifdef CANVAS_DEBUG
123 	if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
124 		cerr << this << " RENDER: " << area << endl;
125 		//cerr << "CANVAS @ " << this << endl;
126 		//dump (cerr);
127 		//cerr << "-------------------------\n";
128 	}
129 #endif
130 
131 	render_count = 0;
132 
133 	Rect root_bbox = _root.bounding_box();
134 	if (!root_bbox) {
135 		/* the root has no bounding box, so there's nothing to render */
136 		return;
137 	}
138 
139 	Rect draw = root_bbox.intersection (area);
140 	if (draw) {
141 
142 		/* there's a common area between the root and the requested
143 		   area, so render it.
144 		*/
145 
146 		_root.render (draw, context);
147 
148 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
149 		if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
150 			// This transparently colors the rect being rendered, after it has been drawn.
151 			double r = (random() % 65536) /65536.0;
152 			double g = (random() % 65536) /65536.0;
153 			double b = (random() % 65536) /65536.0;
154 			context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
155 			context->set_source_rgba (r, g, b, 0.25);
156 			context->fill ();
157 		}
158 #endif
159 	}
160 
161 }
162 
163 void
prepare_for_render(Rect const & area) const164 Canvas::prepare_for_render (Rect const & area) const
165 {
166 	Rect root_bbox = _root.bounding_box();
167 	if (!root_bbox) {
168 		/* the root has no bounding box, so there's nothing to render */
169 		return;
170 	}
171 
172 	Rect draw = root_bbox.intersection (area);
173 
174 	if (draw) {
175 		_root.prepare_for_render (draw);
176 	}
177 }
178 
179 gint64
get_microseconds_since_render_start() const180 Canvas::get_microseconds_since_render_start () const
181 {
182 	gint64 timestamp = g_get_monotonic_time();
183 
184 	if (_last_render_start_timestamp == 0 || timestamp <= _last_render_start_timestamp) {
185 		return 0;
186 	}
187 
188 	return timestamp - _last_render_start_timestamp;
189 }
190 
191 ostream&
operator <<(ostream & o,Canvas & c)192 operator<< (ostream& o, Canvas& c)
193 {
194 	c.dump (o);
195 	return o;
196 }
197 
198 std::string
indent() const199 Canvas::indent() const
200 {
201 	string s;
202 
203 	for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
204 		s += '\t';
205 	}
206 
207 	return s;
208 }
209 
210 std::string
render_indent() const211 Canvas::render_indent() const
212 {
213 	string s;
214 
215 	for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
216 		s += ' ';
217 	}
218 
219 	return s;
220 }
221 
222 void
dump(ostream & o) const223 Canvas::dump (ostream& o) const
224 {
225 	dump_depth = 0;
226 	_root.dump (o);
227 }
228 
229 /** Called when an item has been shown or hidden.
230  *  @param item Item that has been shown or hidden.
231  */
232 void
item_shown_or_hidden(Item * item)233 Canvas::item_shown_or_hidden (Item* item)
234 {
235 	Rect bbox = item->bounding_box ();
236 	if (bbox) {
237 		if (item->item_to_window (bbox).intersection (visible_area ())) {
238 			queue_draw_item_area (item, bbox);
239 		}
240 	}
241 }
242 
243 /** Called when an item has a change to its visual properties
244  *  that do NOT affect its bounding box.
245  *  @param item Item that has been modified.
246  */
247 void
item_visual_property_changed(Item * item)248 Canvas::item_visual_property_changed (Item* item)
249 {
250 	Rect bbox = item->bounding_box ();
251 	if (bbox) {
252 		if (item->item_to_window (bbox).intersection (visible_area ())) {
253 			queue_draw_item_area (item, bbox);
254 		}
255 	}
256 }
257 
258 /** Called when an item has changed, but not moved.
259  *  @param item Item that has changed.
260  *  @param pre_change_bounding_box The bounding box of item before the change,
261  *  in the item's coordinates.
262  */
263 void
item_changed(Item * item,Rect pre_change_bounding_box)264 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
265 {
266 	Rect window_bbox = visible_area ();
267 
268 	if (pre_change_bounding_box) {
269 		if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
270 			/* request a redraw of the item's old bounding box */
271 			queue_draw_item_area (item, pre_change_bounding_box);
272 		}
273 	}
274 
275 	Rect post_change_bounding_box = item->bounding_box ();
276 
277 	if (post_change_bounding_box) {
278 		Rect const window_intersection =
279 		    item->item_to_window (post_change_bounding_box).intersection (window_bbox);
280 
281 		if (window_intersection) {
282 			/* request a redraw of the item's new bounding box */
283 			queue_draw_item_area (item, post_change_bounding_box);
284 
285 			// Allow item to do any work necessary to prepare for being rendered.
286 			item->prepare_for_render (window_intersection);
287 		} else {
288 			// No intersection with visible window area
289 		}
290 	}
291 }
292 
293 Duple
window_to_canvas(Duple const & d) const294 Canvas::window_to_canvas (Duple const & d) const
295 {
296 	ScrollGroup* best_group = 0;
297 	ScrollGroup* sg = 0;
298 
299 	/* if the coordinates are negative, clamp to zero and find the item
300 	 * that covers that "edge" position.
301 	 */
302 
303 	Duple in_window (d);
304 
305 	if (in_window.x < 0) {
306 		in_window.x = 0;
307 	}
308 	if (in_window.y < 0) {
309 		in_window.y = 0;
310 	}
311 
312 	for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
313 
314 		if ((*s)->covers_window (in_window)) {
315 			sg = *s;
316 
317 			/* XXX January 22nd 2015: leaving this in place for now
318 			 * but I think it fixes a bug that really should be
319 			 * fixed in a different way (and will be) by my next
320 			 * commit. But it may still be relevant.
321 			 */
322 
323 			/* If scroll groups overlap, choose the one with the highest sensitivity,
324 			   that is, choose an HV scroll group over an H or V
325 			   only group.
326 			*/
327 			if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
328 				best_group = sg;
329 				if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
330 					/* Can't do any better than this. */
331 					break;
332 				}
333 			}
334 		}
335 	}
336 
337 	if (best_group) {
338 		return d.translate (best_group->scroll_offset());
339 	}
340 
341 	return d;
342 }
343 
344 Duple
canvas_to_window(Duple const & d,bool rounded) const345 Canvas::canvas_to_window (Duple const & d, bool rounded) const
346 {
347 	/* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
348 	 * as children of the root group, so we just scan its first level
349 	 * children and see what we can find.
350 	 */
351 
352 	std::list<Item*> const& root_children (_root.items());
353 	ScrollGroup* sg = 0;
354 	Duple wd;
355 
356 	for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
357 		if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
358 			break;
359 		}
360 	}
361 
362 	if (sg) {
363 		wd = d.translate (-sg->scroll_offset());
364 	} else {
365 		wd = d;
366 	}
367 
368 	/* Note that this intentionally almost always returns integer coordinates */
369 
370 	if (rounded) {
371 		wd.x = round (wd.x);
372 		wd.y = round (wd.y);
373 	}
374 
375 	return wd;
376 }
377 
378 /** Called when an item has moved.
379  *  @param item Item that has moved.
380  *  @param pre_change_parent_bounding_box The bounding box of the item before
381  *  the move, in its parent's coordinates.
382  */
383 void
item_moved(Item * item,Rect pre_change_parent_bounding_box)384 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
385 {
386 	if (pre_change_parent_bounding_box) {
387 		/* request a redraw of where the item used to be. The box has
388 		 * to be in parent coordinate space since the bounding box of
389 		 * an item does not change when moved. If we use
390 		 * item->item_to_canvas() on the old bounding box, we will be
391 
392 		 * using the item's new position, and so will compute the wrong
393 		 * invalidation area. If we use the parent (which has not
394 		 * moved, then this will work.
395 		 */
396 		queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
397 	}
398 
399 	Rect post_change_bounding_box = item->bounding_box ();
400 	if (post_change_bounding_box) {
401 		/* request a redraw of where the item now is */
402 		queue_draw_item_area (item, post_change_bounding_box);
403 	}
404 }
405 
406 /** Request a redraw of a particular area in an item's coordinates.
407  *  @param item Item.
408  *  @param area Area to redraw in the item's coordinates.
409  */
410 void
queue_draw_item_area(Item * item,Rect area)411 Canvas::queue_draw_item_area (Item* item, Rect area)
412 {
413 	request_redraw (item->item_to_window (area));
414 }
415 
416 void
set_tooltip_timeout(uint32_t msecs)417 Canvas::set_tooltip_timeout (uint32_t msecs)
418 {
419 	tooltip_timeout_msecs = msecs;
420 }
421 
422 void
set_background_color(Gtkmm2ext::Color c)423 Canvas::set_background_color (Gtkmm2ext::Color c)
424 {
425 	_bg_color = c;
426 
427 	Rect r = _root.bounding_box();
428 
429 	if (r) {
430 		request_redraw (_root.item_to_window (r));
431 	}
432 }
433 
434 void
re_enter()435 GtkCanvas::re_enter ()
436 {
437 	DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
438 	_current_item = 0;
439 	pick_current_item (0);
440 }
441 
442 /** Construct a GtkCanvas */
GtkCanvas()443 GtkCanvas::GtkCanvas ()
444 	: _current_item (0)
445 	, _new_current_item (0)
446 	, _grabbed_item (0)
447 	, _focused_item (0)
448 	, _single_exposure (true)
449 	, _use_image_surface (false)
450 	, current_tooltip_item (0)
451 	, tooltip_window (0)
452 	, _in_dtor (false)
453 	, _nsglview (0)
454 {
455 #ifdef USE_CAIRO_IMAGE_SURFACE /* usually Windows builds */
456 	_use_image_surface = true;
457 #else
458 	_use_image_surface = NULL != g_getenv("ARDOUR_IMAGE_SURFACE");
459 #endif
460 
461 	/* these are the events we want to know about */
462 	add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
463 		    Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
464 		    Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
465 }
466 
467 void
use_nsglview()468 GtkCanvas::use_nsglview ()
469 {
470 	assert (!_nsglview);
471 	assert (!is_realized());
472 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
473 	_nsglview = Gtkmm2ext::nsglview_create (this);
474 #endif
475 }
476 
477 void
pick_current_item(int state)478 GtkCanvas::pick_current_item (int state)
479 {
480 	int x;
481 	int y;
482 
483 	/* this version of ::pick_current_item() is called after an item is
484 	 * added or removed, so we have no coordinates to work from as is the
485 	 * case with a motion event. Find out where the mouse is and use that.
486 	 */
487 
488 	Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
489 
490 	if (pointer_window != get_window()) {
491 		return;
492 	}
493 
494 	pick_current_item (Duple (x, y), state);
495 }
496 
497 /** Given @param point (a position in window coordinates)
498  *  and mouse state @param state, check to see if _current_item
499  *  (which will be used to deliver events) should change.
500  */
501 void
pick_current_item(Duple const & point,int state)502 GtkCanvas::pick_current_item (Duple const & point, int state)
503 {
504 	/* we do not enter/leave items during a drag/grab */
505 
506 	if (_grabbed_item) {
507 		return;
508 	}
509 
510 	/* find the items at the given window position */
511 
512 	vector<Item const *> items;
513 	_root.add_items_at_point (point, items);
514 
515 	DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
516 
517 #ifndef NDEBUG
518 	if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
519 		for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
520 #ifdef CANVAS_DEBUG
521 			std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
522 #else
523 			std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
524 #endif
525 		}
526 	}
527 #endif
528 
529 	/* put all items at point that are event-sensitive and visible and NOT
530 	   groups into within_items. Note that items is sorted from bottom to
531 	   top, but we're going to reverse that for within_items so that its
532 	   first item is the upper-most item that can be chosen as _current_item.
533 	*/
534 
535 	vector<Item const *>::const_iterator i;
536 	list<Item const *> within_items;
537 
538 	for (i = items.begin(); i != items.end(); ++i) {
539 
540 		Item const * possible_item = *i;
541 
542 		/* We ignore invisible items, containers and items that ignore events */
543 
544 		if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
545 			continue;
546 		}
547 		within_items.push_front (possible_item);
548 	}
549 
550 	DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have  %1 items\n", within_items.size()));
551 
552 	if (within_items.empty()) {
553 
554 		/* no items at point, just send leave event below */
555 		_new_current_item = 0;
556 
557 	} else {
558 
559 		if (within_items.front() == _current_item) {
560 			/* uppermost item at point is already _current_item */
561 			DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
562 			return;
563 		}
564 
565 		_new_current_item = const_cast<Item*> (within_items.front());
566 	}
567 
568 	if (_new_current_item != _current_item) {
569 		deliver_enter_leave (point, state);
570 	}
571 
572 	if (_current_item) {
573 		DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
574 	} else {
575 		DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
576 	}
577 
578 }
579 
580 /** Deliver a series of enter & leave events based on the pointer position being at window
581  * coordinate @param point, and pointer @param state (modifier keys, etc)
582  */
583 void
deliver_enter_leave(Duple const & point,int state)584 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
585 {
586 	/* setup enter & leave event structures */
587 
588 	Glib::RefPtr<Gdk::Window> win = get_window();
589 
590 	if (!win) {
591 		return;
592 	}
593 
594 	GdkEventCrossing enter_event;
595 	enter_event.type = GDK_ENTER_NOTIFY;
596 	enter_event.window = win->gobj();
597 	enter_event.send_event = 0;
598 	enter_event.subwindow = 0;
599 	enter_event.mode = GDK_CROSSING_NORMAL;
600 	enter_event.focus = FALSE;
601 	enter_event.state = state;
602 
603 	/* Events delivered to canvas items are expected to be in canvas
604 	 * coordinates but @param point is in window coordinates.
605 	 */
606 
607 	Duple c = window_to_canvas (point);
608 	enter_event.x = c.x;
609 	enter_event.y = c.y;
610 
611 	GdkEventCrossing leave_event = enter_event;
612 	leave_event.type = GDK_LEAVE_NOTIFY;
613 
614 	Item* i;
615 	GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
616 	GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
617 	vector<Item*> items_to_leave_virtual;
618 	vector<Item*> items_to_enter_virtual;
619 
620 	if (_new_current_item == 0) {
621 
622 		leave_detail = GDK_NOTIFY_UNKNOWN;
623 
624 		if (_current_item) {
625 
626 			/* no current item, so also send virtual leave events to the
627 			 * entire heirarchy for the current item
628 			 */
629 
630 			for (i = _current_item->parent(); i ; i = i->parent()) {
631 				items_to_leave_virtual.push_back (i);
632 			}
633 		}
634 
635 	} else if (_current_item == 0) {
636 
637 		enter_detail = GDK_NOTIFY_UNKNOWN;
638 
639 		/* no current item, so also send virtual enter events to the
640 		 * entire heirarchy for the new item
641 		 */
642 
643 		for (i = _new_current_item->parent(); i ; i = i->parent()) {
644 			items_to_enter_virtual.push_back (i);
645 		}
646 
647 	} else if (_current_item->is_descendant_of (*_new_current_item)) {
648 
649 		/* move from descendant to ancestor (X: "_current_item is an
650 		 * inferior ("child") of _new_current_item")
651 		 *
652 		 * Deliver "virtual" leave notifications to all items in the
653 		 * heirarchy between current and new_current.
654 		 */
655 
656 		for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
657 			items_to_leave_virtual.push_back (i);
658 		}
659 
660 		enter_detail = GDK_NOTIFY_INFERIOR;
661 		leave_detail = GDK_NOTIFY_ANCESTOR;
662 
663 	} else if (_new_current_item->is_descendant_of (*_current_item)) {
664 		/* move from ancestor to descendant (X: "_new_current_item is
665 		 * an inferior ("child") of _current_item")
666 		 *
667 		 * Deliver "virtual" enter notifications to all items in the
668 		 * heirarchy between current and new_current.
669 		 */
670 
671 		for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
672 			items_to_enter_virtual.push_back (i);
673 		}
674 
675 		enter_detail = GDK_NOTIFY_ANCESTOR;
676 		leave_detail = GDK_NOTIFY_INFERIOR;
677 
678 	} else {
679 
680 		Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
681 
682 		/* deliver virtual leave events to everything between _current
683 		 * and common_ancestor.
684 		 */
685 
686 		for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
687 			items_to_leave_virtual.push_back (i);
688 		}
689 
690 		/* deliver virtual enter events to everything between
691 		 * _new_current and common_ancestor.
692 		 */
693 
694 		for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
695 			items_to_enter_virtual.push_back (i);
696 		}
697 
698 		enter_detail = GDK_NOTIFY_NONLINEAR;
699 		leave_detail = GDK_NOTIFY_NONLINEAR;
700 	}
701 
702 
703 	if (_current_item && !_current_item->ignore_events ()) {
704 		leave_event.detail = leave_detail;
705 		_current_item->Event ((GdkEvent*)&leave_event);
706 		DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
707 	}
708 
709 	leave_event.detail = GDK_NOTIFY_VIRTUAL;
710 	enter_event.detail = GDK_NOTIFY_VIRTUAL;
711 
712 	for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
713 		if (!(*it)->ignore_events()) {
714 			DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
715 			(*it)->Event ((GdkEvent*)&leave_event);
716 		}
717 	}
718 
719 	for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
720 		if (!(*it)->ignore_events()) {
721 			DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
722 			(*it)->Event ((GdkEvent*)&enter_event);
723 			// std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
724 		}
725 	}
726 
727 	if (_new_current_item && !_new_current_item->ignore_events()) {
728 		enter_event.detail = enter_detail;
729 		DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
730 		start_tooltip_timeout (_new_current_item);
731 		_new_current_item->Event ((GdkEvent*)&enter_event);
732 	}
733 
734 	_current_item = _new_current_item;
735 }
736 
737 
738 /** Deliver an event to the appropriate item; either the grabbed item, or
739  *  one of the items underneath the event.
740  *  @param point Position that the event has occurred at, in canvas coordinates.
741  *  @param event The event.
742  */
743 bool
deliver_event(GdkEvent * event)744 GtkCanvas::deliver_event (GdkEvent* event)
745 {
746 	/* Point in in canvas coordinate space */
747 
748 	const Item* event_item;
749 
750 	if (_grabbed_item) {
751 		/* we have a grabbed item, so everything gets sent there */
752 		DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
753 								       _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
754 		event_item = _grabbed_item;
755 	} else {
756 		event_item = _current_item;
757 	}
758 
759 	if (!event_item) {
760 		return false;
761 	}
762 
763 	/* run through the items from child to parent, until one claims the event */
764 
765 	Item* item = const_cast<Item*> (event_item);
766 
767 	while (item) {
768 
769 		Item* parent = item->parent ();
770 
771 		if (!item->ignore_events () &&
772 		    item->Event (event)) {
773 			/* this item has just handled the event */
774 			DEBUG_TRACE (
775 				PBD::DEBUG::CanvasEvents,
776 				string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
777 				);
778 
779 			return true;
780 		}
781 
782 		DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event %3 left unhandled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name, event_type_string (event->type)));
783 
784 		if ((item = parent) == 0) {
785 			break;
786 		}
787 
788 	}
789 
790 	return false;
791 }
792 
793 void
item_shown_or_hidden(Item * item)794 GtkCanvas::item_shown_or_hidden (Item* item)
795 {
796 	if (item == current_tooltip_item) {
797 		stop_tooltip_timeout ();
798 	}
799 	Canvas::item_shown_or_hidden (item);
800 }
801 
802 /** Called when an item is being destroyed.
803  *  @param item Item being destroyed.
804  *  @param bounding_box Last known bounding box of the item.
805  */
806 void
item_going_away(Item * item,Rect bounding_box)807 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
808 {
809 	if (bounding_box) {
810 		queue_draw_item_area (item, bounding_box);
811 	}
812 
813 	if (_new_current_item == item) {
814 		_new_current_item = 0;
815 	}
816 
817 	if (_grabbed_item == item) {
818 		_grabbed_item = 0;
819 	}
820 
821 	if (_focused_item == item) {
822 		_focused_item = 0;
823 	}
824 
825 	if (current_tooltip_item) {
826 		current_tooltip_item = 0;
827 		stop_tooltip_timeout ();
828 	}
829 
830 	ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
831 	if (sg) {
832 		scrollers.remove (sg);
833 	}
834 
835 	if (_current_item == item) {
836 		/* no need to send a leave event to this item, since it is going away
837 		 */
838 		_current_item = 0;
839 		pick_current_item (0); // no mouse state
840 	}
841 
842 }
843 
844 void
on_realize()845 GtkCanvas::on_realize ()
846 {
847 	Gtk::EventBox::on_realize();
848 #ifdef __APPLE__
849 	if (_nsglview) {
850 		Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
851 	}
852 #endif
853 }
854 
855 void
on_size_allocate(Gtk::Allocation & a)856 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
857 {
858 	EventBox::on_size_allocate (a);
859 
860 	if (_use_image_surface) {
861 		_canvas_image.clear ();
862 		_canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
863 	}
864 
865 #ifdef __APPLE__
866 	if (_nsglview) {
867 		gint xx, yy;
868 		gtk_widget_translate_coordinates(
869 				GTK_WIDGET(gobj()),
870 				GTK_WIDGET(get_toplevel()->gobj()),
871 				0, 0, &xx, &yy);
872 		Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
873 	}
874 #endif
875 
876 }
877 
878 /** Handler for GDK expose events.
879  *  @param ev Event.
880  *  @return true if the event was handled.
881  */
882 bool
on_expose_event(GdkEventExpose * ev)883 GtkCanvas::on_expose_event (GdkEventExpose* ev)
884 {
885 	if (_in_dtor) {
886 		return true;
887 	}
888 #ifdef __APPLE__
889 	if (_nsglview) {
890 		return true;
891 	}
892 #endif
893 
894 #ifdef CANVAS_PROFILE
895 	const int64_t start = g_get_monotonic_time ();
896 #endif
897 
898 	Cairo::RefPtr<Cairo::Context> draw_context;
899 	if (_use_image_surface) {
900 		if (!_canvas_image) {
901 			_canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
902 		}
903 		draw_context = Cairo::Context::create (_canvas_image);
904 	} else {
905 		draw_context = get_window()->create_cairo_context ();
906 	}
907 
908 	draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
909 	draw_context->clip();
910 
911 	/* (this comment applies to macOS, but is other platforms
912 	 * also benefit from using CPU-rendering on a image-surface
913 	 * with a final bitblt).
914 	 *
915 	 * group calls cairo_quartz_surface_create() which
916 	 * effectively uses a CGBitmapContext + image-surface
917 	 *
918 	 * This avoids expensive argb32_image_mark_image() during drawing.
919 	 * Although the final paint() operation still takes the slow path
920 	 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
921 	 *
922 	 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
923 	 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
924 	 *
925 	 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
926 	 */
927 	if (_use_intermediate_surface && !_use_image_surface) {
928 		draw_context->push_group ();
929 	}
930 
931 	/* draw background color */
932 	draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
933 	Gtkmm2ext::set_source_rgba (draw_context, _bg_color);
934 	draw_context->fill ();
935 
936 	/* render canvas */
937 	if (_single_exposure) {
938 
939 		Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
940 
941 	} else {
942 		GdkRectangle* rects;
943 		gint nrects;
944 
945 		gdk_region_get_rectangles (ev->region, &rects, &nrects);
946 		for (gint n = 0; n < nrects; ++n) {
947 			draw_context->set_identity_matrix();  //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
948 			Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
949 		}
950 		g_free (rects);
951 	}
952 
953 	if (_use_image_surface) {
954 		_canvas_image->flush ();
955 		Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
956 		window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
957 		window_context->clip ();
958 		window_context->set_source (_canvas_image, 0, 0);
959 		window_context->set_operator (Cairo::OPERATOR_SOURCE);
960 		window_context->paint ();
961 	} else if (_use_intermediate_surface) {
962 		draw_context->pop_group_to_source ();
963 		draw_context->paint ();
964 	}
965 
966 
967 #ifdef CANVAS_PROFILE
968 	const int64_t end = g_get_monotonic_time ();
969 	const int64_t elapsed = end - start;
970 	printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
971 #endif
972 
973 	return true;
974 }
975 
976 void
prepare_for_render() const977 GtkCanvas::prepare_for_render () const
978 {
979 	Rect window_bbox = visible_area ();
980 	Canvas::prepare_for_render (window_bbox);
981 }
982 
983 /** Handler for GDK scroll events.
984  *  @param ev Event.
985  *  @return true if the event was handled.
986  */
987 bool
on_scroll_event(GdkEventScroll * ev)988 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
989 {
990 	/* translate event coordinates from window to canvas */
991 
992 	GdkEvent copy = *((GdkEvent*)ev);
993 	Duple winpos = Duple (ev->x, ev->y);
994 	Duple where = window_to_canvas (winpos);
995 
996 	pick_current_item (winpos, ev->state);
997 
998 	copy.button.x = where.x;
999 	copy.button.y = where.y;
1000 
1001 	/* Coordinates in the event will be canvas coordinates, correctly adjusted
1002 	   for scroll if this GtkCanvas is in a GtkCanvasViewport.
1003 	*/
1004 
1005 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
1006 	return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1007 }
1008 
1009 /** Handler for GDK key press events.
1010  *  @param ev Event.
1011  *  @return true if the event was handled.
1012  */
1013 bool
on_key_press_event(GdkEventKey * ev)1014 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1015 {
1016 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1017 	return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1018 }
1019 
1020 /** Handler for GDK key release events.
1021  *  @param ev Event.
1022  *  @return true if the event was handled.
1023  */
1024 bool
on_key_release_event(GdkEventKey * ev)1025 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1026 {
1027 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1028 	return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1029 }
1030 
1031 /** Handler for GDK button press events.
1032  *  @param ev Event.
1033  *  @return true if the event was handled.
1034  */
1035 bool
on_button_press_event(GdkEventButton * ev)1036 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1037 {
1038 	/* translate event coordinates from window to canvas */
1039 
1040 	GdkEvent copy = *((GdkEvent*)ev);
1041 	Duple winpos = Duple (ev->x, ev->y);
1042 	Duple where = window_to_canvas (winpos);
1043 
1044 	pick_current_item (winpos, ev->state);
1045 
1046 	copy.button.x = where.x;
1047 	copy.button.y = where.y;
1048 
1049 	/* Coordinates in the event will be canvas coordinates, correctly adjusted
1050 	   for scroll if this GtkCanvas is in a GtkCanvasViewport.
1051 	*/
1052 
1053 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1054 	return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1055 }
1056 
1057 /** Handler for GDK button release events.
1058  *  @param ev Event.
1059  *  @return true if the event was handled.
1060  */
1061 bool
on_button_release_event(GdkEventButton * ev)1062 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1063 {
1064 	/* translate event coordinates from window to canvas */
1065 
1066 	GdkEvent copy = *((GdkEvent*)ev);
1067 	Duple winpos = Duple (ev->x, ev->y);
1068 	Duple where = window_to_canvas (winpos);
1069 
1070 	pick_current_item (winpos, ev->state);
1071 
1072 	copy.button.x = where.x;
1073 	copy.button.y = where.y;
1074 
1075 	/* Coordinates in the event will be canvas coordinates, correctly adjusted
1076 	   for scroll if this GtkCanvas is in a GtkCanvasViewport.
1077 	*/
1078 
1079 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1080 	return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1081 }
1082 
1083 bool
get_mouse_position(Duple & winpos) const1084 GtkCanvas::get_mouse_position (Duple& winpos) const
1085 {
1086 	int x;
1087 	int y;
1088 	Gdk::ModifierType mask;
1089 	Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1090 
1091 	if (!self) {
1092 		std::cerr << " no self window\n";
1093 		winpos = Duple (0, 0);
1094 		return false;
1095 	}
1096 
1097 	Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1098 
1099 	winpos.x = x;
1100 	winpos.y = y;
1101 
1102 	return true;
1103 }
1104 
1105 /** Handler for GDK motion events.
1106  *  @param ev Event.
1107  *  @return true if the event was handled.
1108  */
1109 bool
on_motion_notify_event(GdkEventMotion * ev)1110 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1111 {
1112 	hide_tooltip ();
1113 
1114 	/* translate event coordinates from window to canvas */
1115 
1116 	GdkEvent copy = *((GdkEvent*)ev);
1117 	Duple point (ev->x, ev->y);
1118 	Duple where = window_to_canvas (point);
1119 
1120 	copy.motion.x = where.x;
1121 	copy.motion.y = where.y;
1122 
1123 	/* Coordinates in "copy" will be canvas coordinates,
1124 	*/
1125 
1126 	DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas motion @ %1, %2 canvas @ %3, %4\n", ev->x, ev->y, copy.motion.x, copy.motion.y));
1127 
1128 	MouseMotion (point); /* EMIT SIGNAL */
1129 
1130 	pick_current_item (point, ev->state);
1131 
1132 	/* Now deliver the motion event.  It may seem a little inefficient
1133 	   to recompute the items under the event, but the enter notify/leave
1134 	   events may have deleted canvas items so it is important to
1135 	   recompute the list in deliver_event.
1136 	*/
1137 
1138 	return deliver_event (reinterpret_cast<GdkEvent*> (&copy));
1139 }
1140 
1141 bool
on_enter_notify_event(GdkEventCrossing * ev)1142 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1143 {
1144 	pick_current_item (Duple (ev->x, ev->y), ev->state);
1145 	return true;
1146 }
1147 
1148 bool
on_leave_notify_event(GdkEventCrossing * ev)1149 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1150 {
1151 	switch (ev->detail) {
1152 	case GDK_NOTIFY_ANCESTOR:
1153 	case GDK_NOTIFY_UNKNOWN:
1154 	case GDK_NOTIFY_VIRTUAL:
1155 	case GDK_NOTIFY_NONLINEAR:
1156 	case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1157 		/* leaving window, cancel any tooltips */
1158 		stop_tooltip_timeout ();
1159 		hide_tooltip ();
1160 		break;
1161 	default:
1162 		/* we don't care about any other kind
1163 		   of leave event (notably GDK_NOTIFY_INFERIOR)
1164 		*/
1165 		break;
1166 	}
1167 	_new_current_item = 0;
1168 	deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1169 	return true;
1170 }
1171 
1172 void
on_map()1173 GtkCanvas::on_map ()
1174 {
1175 	Gtk::EventBox::on_map();
1176 #ifdef __APPLE__
1177 	if (_nsglview) {
1178 		Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1179 		Gtk::Allocation a = get_allocation();
1180 		gint xx, yy;
1181 		gtk_widget_translate_coordinates(
1182 				GTK_WIDGET(gobj()),
1183 				GTK_WIDGET(get_toplevel()->gobj()),
1184 				0, 0, &xx, &yy);
1185 		Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1186 	}
1187 #endif
1188 }
1189 
1190 void
on_unmap()1191 GtkCanvas::on_unmap ()
1192 {
1193 	stop_tooltip_timeout ();
1194 	Gtk::EventBox::on_unmap();
1195 #ifdef __APPLE__
1196 	if (_nsglview) {
1197 		Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1198 	}
1199 #endif
1200 }
1201 
1202 void
queue_draw()1203 GtkCanvas::queue_draw()
1204 {
1205 #ifdef __APPLE__
1206 	if (_nsglview) {
1207 		Gtkmm2ext::nsglview_queue_draw (_nsglview, 0, 0, get_width (), get_height ());
1208 		return;
1209 	}
1210 #endif
1211 	Gtk::Widget::queue_draw ();
1212 }
1213 
1214 void
queue_draw_area(int x,int y,int width,int height)1215 GtkCanvas::queue_draw_area (int x, int y, int width, int height)
1216 {
1217 #ifdef __APPLE__
1218 	if (_nsglview) {
1219 		Gtkmm2ext::nsglview_queue_draw (_nsglview, x, y, width, height);
1220 		return;
1221 	}
1222 #endif
1223 	Gtk::Widget::queue_draw_area (x, y, width, height);
1224 }
1225 
1226 /** Called to request a redraw of our canvas.
1227  *  @param area Area to redraw, in window coordinates.
1228  */
1229 void
request_redraw(Rect const & request)1230 GtkCanvas::request_redraw (Rect const & request)
1231 {
1232 	if (_in_dtor) {
1233 		return;
1234 	}
1235 
1236 	/* clamp area requested to actual visible window */
1237 
1238 	Rect real_area = request.intersection (visible_area());
1239 
1240 	if (real_area) {
1241 		if (real_area.width () && real_area.height ()) {
1242 			// Item intersects with visible canvas area
1243 			queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1244 		}
1245 
1246 	} else {
1247 		// Item does not intersect with visible canvas area
1248 	}
1249 }
1250 
1251 /** Called to request that we try to get a particular size for ourselves.
1252  *  @param size Size to request, in pixels.
1253  */
1254 void
request_size(Duple size)1255 GtkCanvas::request_size (Duple size)
1256 {
1257 	Duple req = size;
1258 
1259 	if (req.x > INT_MAX) {
1260 		req.x = INT_MAX;
1261 	}
1262 
1263 	if (req.y > INT_MAX) {
1264 		req.y = INT_MAX;
1265 	}
1266 
1267 	set_size_request (req.x, req.y);
1268 }
1269 
1270 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1271  *  This is typically used for dragging items around, so that they are grabbed during
1272  *  the drag.
1273  *  @param item Item to grab.
1274  */
1275 void
grab(Item * item)1276 GtkCanvas::grab (Item* item)
1277 {
1278 	/* XXX: should this be doing gdk_pointer_grab? */
1279 	_grabbed_item = item;
1280 }
1281 
1282 
1283 /** `Ungrab' any item that was previously grabbed */
1284 void
ungrab()1285 GtkCanvas::ungrab ()
1286 {
1287 	/* XXX: should this be doing gdk_pointer_ungrab? */
1288 	_grabbed_item = 0;
1289 }
1290 
1291 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1292  *  moves elsewhere.
1293  *  @param item Item to grab.
1294  */
1295 void
focus(Item * item)1296 GtkCanvas::focus (Item* item)
1297 {
1298 	_focused_item = item;
1299 }
1300 
1301 void
unfocus(Item * item)1302 GtkCanvas::unfocus (Item* item)
1303 {
1304 	if (item == _focused_item) {
1305 		_focused_item = 0;
1306 	}
1307 }
1308 
1309 /** @return The visible area of the canvas, in window coordinates */
1310 ArdourCanvas::Rect
visible_area() const1311 GtkCanvas::visible_area () const
1312 {
1313 	return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1314 }
1315 
1316 Coord
width() const1317 GtkCanvas::width() const
1318 {
1319 	return get_allocation().get_width();
1320 }
1321 
1322 Coord
height() const1323 GtkCanvas::height() const
1324 {
1325 	return get_allocation().get_height();
1326 }
1327 
1328 void
start_tooltip_timeout(Item * item)1329 GtkCanvas::start_tooltip_timeout (Item* item)
1330 {
1331 	stop_tooltip_timeout ();
1332 
1333 	if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1334 		current_tooltip_item = item;
1335 
1336 		/* wait for the first idle that happens after this is
1337 		   called. this means that we've stopped processing events, which
1338 		   in turn implies that the user has stopped doing stuff for a
1339 		   little while.
1340 		*/
1341 
1342 		Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1343 	}
1344 }
1345 
1346 bool
really_start_tooltip_timeout()1347 GtkCanvas::really_start_tooltip_timeout ()
1348 {
1349 	/* an idle has occurred since we entered a tooltip-bearing widget. Now
1350 	 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1351 	 */
1352 
1353 	if (current_tooltip_item) {
1354 		tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1355 	}
1356 
1357 	return false; /* this is called from an idle callback, don't call it again */
1358 }
1359 
1360 void
stop_tooltip_timeout()1361 GtkCanvas::stop_tooltip_timeout ()
1362 {
1363 	current_tooltip_item = 0;
1364 	tooltip_timeout_connection.disconnect ();
1365 }
1366 
1367 bool
show_tooltip()1368 GtkCanvas::show_tooltip ()
1369 {
1370 	Rect tooltip_item_bbox;
1371 
1372 	if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1373 		return false;
1374 	}
1375 
1376 	if (!tooltip_window) {
1377 		tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1378 		tooltip_label = manage (new Gtk::Label);
1379 		tooltip_label->show ();
1380 		tooltip_window->add (*tooltip_label);
1381 		tooltip_window->set_border_width (1);
1382 		tooltip_window->set_name ("tooltip");
1383 	}
1384 
1385 	tooltip_label->set_text (current_tooltip_item->tooltip());
1386 
1387 	/* figure out where to position the tooltip */
1388 
1389 	Gtk::Widget* toplevel = get_toplevel();
1390 	assert (toplevel);
1391 	int pointer_x, pointer_y;
1392 	Gdk::ModifierType mask;
1393 
1394 	(void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1395 
1396 	Duple tooltip_window_origin (pointer_x, pointer_y);
1397 
1398 	/* convert to root window coordinates */
1399 
1400 	int win_x, win_y;
1401 	dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1402 
1403 	tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1404 
1405 	/* we don't want the pointer to be inside the window when it is
1406 	 * displayed, because then we generate a leave/enter event pair when
1407 	 * the window is displayed then hidden - the enter event will
1408 	 * trigger a new tooltip timeout.
1409 	 *
1410 	 * So move the window right of the pointer position by just a enough
1411 	 * to get it away from the pointer.
1412 	 */
1413 
1414 	tooltip_window_origin.x += 30;
1415 	tooltip_window_origin.y += 45;
1416 
1417 	/* move the tooltip window into position */
1418 
1419 	tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1420 
1421 	/* ready to show */
1422 
1423 	tooltip_window->present ();
1424 
1425 	/* called from a timeout handler, don't call it again */
1426 
1427 	return false;
1428 }
1429 
1430 void
hide_tooltip()1431 GtkCanvas::hide_tooltip ()
1432 {
1433 	/* hide it if its there */
1434 
1435 	if (tooltip_window) {
1436 		tooltip_window->hide ();
1437 
1438 		// Delete the tooltip window so it'll get re-created
1439 		// (i.e. properly re-sized) on the next usage.
1440 		delete tooltip_window;
1441 		tooltip_window = NULL;
1442 	}
1443 }
1444 
1445 Glib::RefPtr<Pango::Context>
get_pango_context()1446 GtkCanvas::get_pango_context ()
1447 {
1448 	return Glib::wrap (gdk_pango_context_get());
1449 }
1450 
1451 /** Create a GtkCanvaSViewport.
1452  *  @param hadj Adjustment to use for horizontal scrolling.
1453  *  @param vadj Adjustment to use for vertica scrolling.
1454  */
GtkCanvasViewport(Gtk::Adjustment & hadj,Gtk::Adjustment & vadj)1455 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1456 	: Alignment (0, 0, 1.0, 1.0)
1457 	, hadjustment (hadj)
1458 	, vadjustment (vadj)
1459 {
1460 	add (_canvas);
1461 
1462 	hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1463 	vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1464 }
1465 
1466 void
scrolled()1467 GtkCanvasViewport::scrolled ()
1468 {
1469 	_canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1470 	queue_draw ();
1471 }
1472 
1473 /** Handler for when GTK asks us what minimum size we want.
1474  *  @param req Requsition to fill in.
1475  */
1476 void
on_size_request(Gtk::Requisition * req)1477 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1478 {
1479 	/* force the canvas to size itself */
1480 	// _canvas.root()->bounding_box();
1481 
1482 	req->width = 16;
1483 	req->height = 16;
1484 }
1485