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*>(©));
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*>(©));
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*>(©));
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*> (©));
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