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