fix issue with initialization of a BBT_Time variable.
[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                 return true;
877         }
878 #endif
879
880 #ifdef CANVAS_PROFILE
881         const int64_t start = g_get_monotonic_time ();
882 #endif
883
884 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
885         Cairo::RefPtr<Cairo::Context> draw_context;
886         Cairo::RefPtr<Cairo::Context> window_context;
887         if (getenv("ARDOUR_IMAGE_SURFACE")) {
888                 if (!canvas_image) {
889                         canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
890                 }
891                 draw_context = Cairo::Context::create (canvas_image);
892                 window_context = get_window()->create_cairo_context ();
893         } else {
894                 draw_context = get_window()->create_cairo_context ();
895         }
896 #elif defined USE_CAIRO_IMAGE_SURFACE
897         if (!canvas_image) {
898                 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
899         }
900         Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
901         Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
902 #else
903         Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
904 #endif
905
906         draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
907         draw_context->clip();
908
909 #ifdef __APPLE__
910         /* group calls cairo_quartz_surface_create() which
911          * effectively uses a CGBitmapContext + image-surface
912          *
913          * This avoids expensive argb32_image_mark_image() during drawing.
914          * Although the final paint() operation still takes the slow path
915          * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
916          *
917          * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
918          * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
919          *
920          * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
921          */
922         draw_context->push_group ();
923 #endif
924
925         /* draw background color */
926         draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
927         Gtkmm2ext::set_source_rgba (draw_context, _bg_color);
928         draw_context->fill ();
929
930         /* render canvas */
931         if ( _single_exposure ) {
932
933                 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
934
935         } else {
936                 GdkRectangle* rects;
937                 gint nrects;
938
939                 gdk_region_get_rectangles (ev->region, &rects, &nrects);
940                 for (gint n = 0; n < nrects; ++n) {
941                         draw_context->set_identity_matrix();  //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
942                         Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
943                 }
944                 g_free (rects);
945         }
946
947 #ifdef __APPLE__
948         draw_context->pop_group_to_source ();
949         draw_context->paint ();
950 #endif
951
952 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
953         if (getenv("ARDOUR_IMAGE_SURFACE")) {
954 #endif
955 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
956                 /* now blit our private surface back to the GDK one */
957
958                 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
959                 window_context->clip ();
960                 window_context->set_source (canvas_image, 0, 0);
961                 window_context->set_operator (Cairo::OPERATOR_SOURCE);
962                 window_context->paint ();
963 #endif
964 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
965         }
966 #endif
967
968 #ifdef CANVAS_PROFILE
969         const int64_t end = g_get_monotonic_time ();
970         const int64_t elapsed = end - start;
971         printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
972 #endif
973
974         return true;
975 }
976
977 void
978 GtkCanvas::prepare_for_render () const
979 {
980         Rect window_bbox = visible_area ();
981         Canvas::prepare_for_render (window_bbox);
982 }
983
984 /** Handler for GDK scroll events.
985  *  @param ev Event.
986  *  @return true if the event was handled.
987  */
988 bool
989 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
990 {
991         /* translate event coordinates from window to canvas */
992
993         GdkEvent copy = *((GdkEvent*)ev);
994         Duple winpos = Duple (ev->x, ev->y);
995         Duple where = window_to_canvas (winpos);
996
997         pick_current_item (winpos, ev->state);
998
999         copy.button.x = where.x;
1000         copy.button.y = where.y;
1001
1002         /* Coordinates in the event will be canvas coordinates, correctly adjusted
1003            for scroll if this GtkCanvas is in a GtkCanvasViewport.
1004         */
1005
1006         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
1007         return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1008 }
1009
1010 /** Handler for GDK key press events.
1011  *  @param ev Event.
1012  *  @return true if the event was handled.
1013  */
1014 bool
1015 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1016 {
1017         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1018         return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1019 }
1020
1021 /** Handler for GDK key release events.
1022  *  @param ev Event.
1023  *  @return true if the event was handled.
1024  */
1025 bool
1026 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1027 {
1028         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1029         return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1030 }
1031
1032 /** Handler for GDK button press events.
1033  *  @param ev Event.
1034  *  @return true if the event was handled.
1035  */
1036 bool
1037 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1038 {
1039         /* translate event coordinates from window to canvas */
1040
1041         GdkEvent copy = *((GdkEvent*)ev);
1042         Duple winpos = Duple (ev->x, ev->y);
1043         Duple where = window_to_canvas (winpos);
1044
1045         pick_current_item (winpos, ev->state);
1046
1047         copy.button.x = where.x;
1048         copy.button.y = where.y;
1049
1050         /* Coordinates in the event will be canvas coordinates, correctly adjusted
1051            for scroll if this GtkCanvas is in a GtkCanvasViewport.
1052         */
1053
1054         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1055         return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1056 }
1057
1058 /** Handler for GDK button release events.
1059  *  @param ev Event.
1060  *  @return true if the event was handled.
1061  */
1062 bool
1063 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1064 {
1065         /* translate event coordinates from window to canvas */
1066
1067         GdkEvent copy = *((GdkEvent*)ev);
1068         Duple winpos = Duple (ev->x, ev->y);
1069         Duple where = window_to_canvas (winpos);
1070
1071         pick_current_item (winpos, ev->state);
1072
1073         copy.button.x = where.x;
1074         copy.button.y = where.y;
1075
1076         /* Coordinates in the event will be canvas coordinates, correctly adjusted
1077            for scroll if this GtkCanvas is in a GtkCanvasViewport.
1078         */
1079
1080         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1081         return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
1082 }
1083
1084 bool
1085 GtkCanvas::get_mouse_position (Duple& winpos) const
1086 {
1087         int x;
1088         int y;
1089         Gdk::ModifierType mask;
1090         Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1091
1092         if (!self) {
1093                 std::cerr << " no self window\n";
1094                 winpos = Duple (0, 0);
1095                 return false;
1096         }
1097
1098         Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1099
1100         winpos.x = x;
1101         winpos.y = y;
1102
1103         return true;
1104 }
1105
1106 /** Handler for GDK motion events.
1107  *  @param ev Event.
1108  *  @return true if the event was handled.
1109  */
1110 bool
1111 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1112 {
1113         hide_tooltip ();
1114
1115         /* translate event coordinates from window to canvas */
1116
1117         GdkEvent copy = *((GdkEvent*)ev);
1118         Duple point (ev->x, ev->y);
1119         Duple where = window_to_canvas (point);
1120
1121         copy.motion.x = where.x;
1122         copy.motion.y = where.y;
1123
1124         /* Coordinates in "copy" will be canvas coordinates,
1125         */
1126
1127         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));
1128
1129         MouseMotion (point); /* EMIT SIGNAL */
1130
1131         pick_current_item (point, ev->state);
1132
1133         /* Now deliver the motion event.  It may seem a little inefficient
1134            to recompute the items under the event, but the enter notify/leave
1135            events may have deleted canvas items so it is important to
1136            recompute the list in deliver_event.
1137         */
1138
1139         return deliver_event (reinterpret_cast<GdkEvent*> (&copy));
1140 }
1141
1142 bool
1143 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1144 {
1145         pick_current_item (Duple (ev->x, ev->y), ev->state);
1146         return true;
1147 }
1148
1149 bool
1150 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1151 {
1152         switch (ev->detail) {
1153         case GDK_NOTIFY_ANCESTOR:
1154         case GDK_NOTIFY_UNKNOWN:
1155         case GDK_NOTIFY_VIRTUAL:
1156         case GDK_NOTIFY_NONLINEAR:
1157         case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1158                 /* leaving window, cancel any tooltips */
1159                 stop_tooltip_timeout ();
1160                 hide_tooltip ();
1161                 break;
1162         default:
1163                 /* we don't care about any other kind
1164                    of leave event (notably GDK_NOTIFY_INFERIOR)
1165                 */
1166                 break;
1167         }
1168         _new_current_item = 0;
1169         deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1170         return true;
1171 }
1172
1173 void
1174 GtkCanvas::on_map ()
1175 {
1176         Gtk::EventBox::on_map();
1177 #ifdef __APPLE__
1178         if (_nsglview) {
1179                 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1180                 Gtk::Allocation a = get_allocation();
1181                 gint xx, yy;
1182                 gtk_widget_translate_coordinates(
1183                                 GTK_WIDGET(gobj()),
1184                                 GTK_WIDGET(get_toplevel()->gobj()),
1185                                 0, 0, &xx, &yy);
1186                 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1187         }
1188 #endif
1189 }
1190
1191 void
1192 GtkCanvas::on_unmap ()
1193 {
1194         stop_tooltip_timeout ();
1195         Gtk::EventBox::on_unmap();
1196 #ifdef __APPLE__
1197         if (_nsglview) {
1198                 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1199         }
1200 #endif
1201 }
1202
1203 void
1204 GtkCanvas::queue_draw()
1205 {
1206 #ifdef __APPLE__
1207         if (_nsglview) {
1208                 Gtkmm2ext::nsglview_queue_draw (_nsglview, 0, 0, get_width (), get_height ());
1209                 return;
1210         }
1211 #endif
1212         Gtk::Widget::queue_draw ();
1213 }
1214
1215 void
1216 GtkCanvas::queue_draw_area (int x, int y, int width, int height)
1217 {
1218 #ifdef __APPLE__
1219         if (_nsglview) {
1220                 Gtkmm2ext::nsglview_queue_draw (_nsglview, x, y, width, height);
1221                 return;
1222         }
1223 #endif
1224         Gtk::Widget::queue_draw_area (x, y, width, height);
1225 }
1226
1227 /** Called to request a redraw of our canvas.
1228  *  @param area Area to redraw, in window coordinates.
1229  */
1230 void
1231 GtkCanvas::request_redraw (Rect const & request)
1232 {
1233         if (_in_dtor) {
1234                 return;
1235         }
1236
1237         /* clamp area requested to actual visible window */
1238
1239         Rect real_area = request.intersection (visible_area());
1240
1241         if (real_area) {
1242                 if (real_area.width () && real_area.height ()) {
1243                         // Item intersects with visible canvas area
1244                         queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1245                 }
1246
1247         } else {
1248                 // Item does not intersect with visible canvas area
1249         }
1250 }
1251
1252 /** Called to request that we try to get a particular size for ourselves.
1253  *  @param size Size to request, in pixels.
1254  */
1255 void
1256 GtkCanvas::request_size (Duple size)
1257 {
1258         Duple req = size;
1259
1260         if (req.x > INT_MAX) {
1261                 req.x = INT_MAX;
1262         }
1263
1264         if (req.y > INT_MAX) {
1265                 req.y = INT_MAX;
1266         }
1267
1268         set_size_request (req.x, req.y);
1269 }
1270
1271 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1272  *  This is typically used for dragging items around, so that they are grabbed during
1273  *  the drag.
1274  *  @param item Item to grab.
1275  */
1276 void
1277 GtkCanvas::grab (Item* item)
1278 {
1279         /* XXX: should this be doing gdk_pointer_grab? */
1280         _grabbed_item = item;
1281 }
1282
1283
1284 /** `Ungrab' any item that was previously grabbed */
1285 void
1286 GtkCanvas::ungrab ()
1287 {
1288         /* XXX: should this be doing gdk_pointer_ungrab? */
1289         _grabbed_item = 0;
1290 }
1291
1292 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1293  *  moves elsewhere.
1294  *  @param item Item to grab.
1295  */
1296 void
1297 GtkCanvas::focus (Item* item)
1298 {
1299         _focused_item = item;
1300 }
1301
1302 void
1303 GtkCanvas::unfocus (Item* item)
1304 {
1305         if (item == _focused_item) {
1306                 _focused_item = 0;
1307         }
1308 }
1309
1310 /** @return The visible area of the canvas, in window coordinates */
1311 ArdourCanvas::Rect
1312 GtkCanvas::visible_area () const
1313 {
1314         return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1315 }
1316
1317 Coord
1318 GtkCanvas::width() const
1319 {
1320         return get_allocation().get_width();
1321 }
1322
1323 Coord
1324 GtkCanvas::height() const
1325 {
1326         return get_allocation().get_height();
1327 }
1328
1329 void
1330 GtkCanvas::start_tooltip_timeout (Item* item)
1331 {
1332         stop_tooltip_timeout ();
1333
1334         if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1335                 current_tooltip_item = item;
1336
1337                 /* wait for the first idle that happens after this is
1338                    called. this means that we've stopped processing events, which
1339                    in turn implies that the user has stopped doing stuff for a
1340                    little while.
1341                 */
1342
1343                 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1344         }
1345 }
1346
1347 bool
1348 GtkCanvas::really_start_tooltip_timeout ()
1349 {
1350         /* an idle has occurred since we entered a tooltip-bearing widget. Now
1351          * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1352          */
1353
1354         if (current_tooltip_item) {
1355                 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1356         }
1357
1358         return false; /* this is called from an idle callback, don't call it again */
1359 }
1360
1361 void
1362 GtkCanvas::stop_tooltip_timeout ()
1363 {
1364         current_tooltip_item = 0;
1365         tooltip_timeout_connection.disconnect ();
1366 }
1367
1368 bool
1369 GtkCanvas::show_tooltip ()
1370 {
1371         Rect tooltip_item_bbox;
1372
1373         if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1374                 return false;
1375         }
1376
1377         if (!tooltip_window) {
1378                 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1379                 tooltip_label = manage (new Gtk::Label);
1380                 tooltip_label->show ();
1381                 tooltip_window->add (*tooltip_label);
1382                 tooltip_window->set_border_width (1);
1383                 tooltip_window->set_name ("tooltip");
1384         }
1385
1386         tooltip_label->set_text (current_tooltip_item->tooltip());
1387
1388         /* figure out where to position the tooltip */
1389
1390         Gtk::Widget* toplevel = get_toplevel();
1391         assert (toplevel);
1392         int pointer_x, pointer_y;
1393         Gdk::ModifierType mask;
1394
1395         (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1396
1397         Duple tooltip_window_origin (pointer_x, pointer_y);
1398
1399         /* convert to root window coordinates */
1400
1401         int win_x, win_y;
1402         dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1403
1404         tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1405
1406         /* we don't want the pointer to be inside the window when it is
1407          * displayed, because then we generate a leave/enter event pair when
1408          * the window is displayed then hidden - the enter event will
1409          * trigger a new tooltip timeout.
1410          *
1411          * So move the window right of the pointer position by just a enough
1412          * to get it away from the pointer.
1413          */
1414
1415         tooltip_window_origin.x += 30;
1416         tooltip_window_origin.y += 45;
1417
1418         /* move the tooltip window into position */
1419
1420         tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1421
1422         /* ready to show */
1423
1424         tooltip_window->present ();
1425
1426         /* called from a timeout handler, don't call it again */
1427
1428         return false;
1429 }
1430
1431 void
1432 GtkCanvas::hide_tooltip ()
1433 {
1434         /* hide it if its there */
1435
1436         if (tooltip_window) {
1437                 tooltip_window->hide ();
1438
1439                 // Delete the tooltip window so it'll get re-created
1440                 // (i.e. properly re-sized) on the next usage.
1441                 delete tooltip_window;
1442                 tooltip_window = NULL;
1443         }
1444 }
1445
1446 Glib::RefPtr<Pango::Context>
1447 GtkCanvas::get_pango_context ()
1448 {
1449         return Glib::wrap (gdk_pango_context_get());
1450 }
1451
1452 /** Create a GtkCanvaSViewport.
1453  *  @param hadj Adjustment to use for horizontal scrolling.
1454  *  @param vadj Adjustment to use for vertica scrolling.
1455  */
1456 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1457         : Alignment (0, 0, 1.0, 1.0)
1458         , hadjustment (hadj)
1459         , vadjustment (vadj)
1460 {
1461         add (_canvas);
1462
1463         hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1464         vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1465 }
1466
1467 void
1468 GtkCanvasViewport::scrolled ()
1469 {
1470         _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1471         queue_draw ();
1472 }
1473
1474 /** Handler for when GTK asks us what minimum size we want.
1475  *  @param req Requsition to fill in.
1476  */
1477 void
1478 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1479 {
1480         /* force the canvas to size itself */
1481         // _canvas.root()->bounding_box();
1482
1483         req->width = 16;
1484         req->height = 16;
1485 }
1486