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