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