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