2 Copyright (C) 2011 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
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.
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.
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.
21 /** @file canvas/canvas.cc
22 * @brief Implementation of the main canvas classes.
27 #include <gtkmm/adjustment.h>
28 #include <gtkmm/label.h>
30 #include "pbd/compose.h"
31 #include "pbd/stacktrace.h"
33 #include "canvas/canvas.h"
34 #include "canvas/debug.h"
35 #include "canvas/line.h"
36 #include "canvas/scroll_group.h"
39 using namespace ArdourCanvas;
41 /** Construct a new Canvas */
49 Canvas::scroll_to (Coord x, Coord y)
51 /* We do things this way because we do not want to recurse through
52 the canvas for every scroll. In the presence of large MIDI
53 tracks this means traversing item lists that include
54 thousands of items (notes).
56 This design limits us to moving only those items (groups, typically)
57 that should move in certain ways as we scroll. In other terms, it
58 becomes O(1) rather than O(N).
61 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
62 (*i)->scroll_to (Duple (x, y));
65 pick_current_item (0); // no current mouse position
69 Canvas::add_scroller (ScrollGroup& i)
71 scrollers.push_back (&i);
77 pick_current_item (0); // no current mouse position
80 /** Render an area of the canvas.
81 * @param area Area in window coordinates.
82 * @param context Cairo context to render to.
85 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
88 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
89 cerr << this << " RENDER: " << area << endl;
90 //cerr << "CANVAS @ " << this << endl;
92 //cerr << "-------------------------\n";
98 boost::optional<Rect> root_bbox = _root.bounding_box();
100 /* the root has no bounding box, so there's nothing to render */
104 boost::optional<Rect> draw = root_bbox->intersection (area);
107 /* there's a common area between the root and the requested
111 _root.render (*draw, context);
114 // This transparently colors the rect being rendered, after it has been drawn.
115 double r = (random() % 65536) /65536.0;
116 double g = (random() % 65536) /65536.0;
117 double b = (random() % 65536) /65536.0;
118 context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
119 context->set_source_rgba (r, g, b, 0.25);
128 operator<< (ostream& o, Canvas& c)
135 Canvas::indent() const
139 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
147 Canvas::render_indent() const
151 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
159 Canvas::dump (ostream& o) const
165 /** Called when an item has been shown or hidden.
166 * @param item Item that has been shown or hidden.
169 Canvas::item_shown_or_hidden (Item* item)
171 boost::optional<Rect> bbox = item->bounding_box ();
173 if (item->item_to_window (*bbox).intersection (visible_area ())) {
174 queue_draw_item_area (item, bbox.get ());
179 /** Called when an item has a change to its visual properties
180 * that do NOT affect its bounding box.
181 * @param item Item that has been modified.
184 Canvas::item_visual_property_changed (Item* item)
186 boost::optional<Rect> bbox = item->bounding_box ();
188 if (item->item_to_window (*bbox).intersection (visible_area ())) {
189 queue_draw_item_area (item, bbox.get ());
194 /** Called when an item has changed, but not moved.
195 * @param item Item that has changed.
196 * @param pre_change_bounding_box The bounding box of item before the change,
197 * in the item's coordinates.
200 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
203 Rect window_bbox = visible_area ();
205 if (pre_change_bounding_box) {
207 if (item->item_to_window (*pre_change_bounding_box).intersection (window_bbox)) {
208 /* request a redraw of the item's old bounding box */
209 queue_draw_item_area (item, pre_change_bounding_box.get ());
213 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
214 if (post_change_bounding_box) {
216 if (item->item_to_window (*post_change_bounding_box).intersection (window_bbox)) {
217 /* request a redraw of the item's new bounding box */
218 queue_draw_item_area (item, post_change_bounding_box.get ());
224 Canvas::window_to_canvas (Duple const & d) const
226 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
227 * as children of the root group, so we just scan its first level
228 * children and see what we can find.
231 std::list<Item*> const& root_children (_root.items());
234 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
235 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (d)) {
241 return d.translate (sg->scroll_offset());
248 Canvas::canvas_to_window (Duple const & d, bool rounded) const
250 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
251 * as children of the root group, so we just scan its first level
252 * children and see what we can find.
255 std::list<Item*> const& root_children (_root.items());
259 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
260 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
267 wd = d.translate (-sg->scroll_offset());
272 /* Note that this intentionally almost always returns integer coordinates */
282 /** Called when an item has moved.
283 * @param item Item that has moved.
284 * @param pre_change_parent_bounding_box The bounding box of the item before
285 * the move, in its parent's coordinates.
288 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
290 if (pre_change_parent_bounding_box) {
291 /* request a redraw of where the item used to be. The box has
292 * to be in parent coordinate space since the bounding box of
293 * an item does not change when moved. If we use
294 * item->item_to_canvas() on the old bounding box, we will be
296 * using the item's new position, and so will compute the wrong
297 * invalidation area. If we use the parent (which has not
298 * moved, then this will work.
300 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
303 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
304 if (post_change_bounding_box) {
305 /* request a redraw of where the item now is */
306 queue_draw_item_area (item, post_change_bounding_box.get ());
310 /** Request a redraw of a particular area in an item's coordinates.
312 * @param area Area to redraw in the item's coordinates.
315 Canvas::queue_draw_item_area (Item* item, Rect area)
317 request_redraw (item->item_to_window (area));
320 /** Construct a GtkCanvas */
321 GtkCanvas::GtkCanvas ()
323 , _new_current_item (0)
327 /* these are the events we want to know about */
328 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
329 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
333 GtkCanvas::pick_current_item (int state)
338 /* this version of ::pick_current_item() is called after an item is
339 * added or removed, so we have no coordinates to work from as is the
340 * case with a motion event. Find out where the mouse is and use that.
343 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
345 if (pointer_window != get_window()) {
349 pick_current_item (Duple (x, y), state);
352 /** Given @param point (a position in window coordinates)
353 * and mouse state @param state, check to see if _current_item
354 * (which will be used to deliver events) should change.
357 GtkCanvas::pick_current_item (Duple const & point, int state)
359 /* we do not enter/leave items during a drag/grab */
365 /* find the items at the given window position */
367 vector<Item const *> items;
368 _root.add_items_at_point (point, items);
370 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
373 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
374 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
376 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << std::endl;
378 std::cerr << "\tItem " << (*it)->whatami() << std::endl;
384 /* put all items at point that are event-sensitive and visible and NOT
385 groups into within_items. Note that items is sorted from bottom to
386 top, but we're going to reverse that for within_items so that its
387 first item is the upper-most item that can be chosen as _current_item.
390 vector<Item const *>::const_iterator i;
391 list<Item const *> within_items;
393 for (i = items.begin(); i != items.end(); ++i) {
395 Item const * possible_item = *i;
397 /* We ignore invisible items, containers and items that ignore events */
399 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
402 within_items.push_front (possible_item);
405 if (within_items.empty()) {
407 /* no items at point, just send leave event below */
408 _new_current_item = 0;
412 if (within_items.front() == _current_item) {
413 /* uppermost item at point is already _current_item */
417 _new_current_item = const_cast<Item*> (within_items.front());
420 if (_new_current_item != _current_item) {
421 deliver_enter_leave (point, state);
425 /** Deliver a series of enter & leave events based on the pointer position being at window
426 * coordinate @param point, and pointer @param state (modifier keys, etc)
429 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
431 /* setup enter & leave event structures */
433 GdkEventCrossing enter_event;
434 enter_event.type = GDK_ENTER_NOTIFY;
435 enter_event.window = get_window()->gobj();
436 enter_event.send_event = 0;
437 enter_event.subwindow = 0;
438 enter_event.mode = GDK_CROSSING_NORMAL;
439 enter_event.focus = FALSE;
440 enter_event.state = state;
442 /* Events delivered to canvas items are expected to be in canvas
443 * coordinates but @param point is in window coordinates.
446 Duple c = window_to_canvas (point);
450 GdkEventCrossing leave_event = enter_event;
451 leave_event.type = GDK_LEAVE_NOTIFY;
454 GdkNotifyType enter_detail;
455 GdkNotifyType leave_detail;
456 vector<Item*> items_to_leave_virtual;
457 vector<Item*> items_to_enter_virtual;
459 if (_new_current_item == 0) {
461 leave_detail = GDK_NOTIFY_UNKNOWN;
465 /* no current item, so also send virtual leave events to the
466 * entire heirarchy for the current item
469 for (i = _current_item->parent(); i ; i = i->parent()) {
470 items_to_leave_virtual.push_back (i);
474 } else if (_current_item == 0) {
476 enter_detail = GDK_NOTIFY_UNKNOWN;
478 /* no current item, so also send virtual enter events to the
479 * entire heirarchy for the new item
482 for (i = _new_current_item->parent(); i ; i = i->parent()) {
483 items_to_enter_virtual.push_back (i);
486 } else if (_current_item->is_descendant_of (*_new_current_item)) {
488 /* move from descendant to ancestor (X: "_current_item is an
489 * inferior ("child") of _new_current_item")
491 * Deliver "virtual" leave notifications to all items in the
492 * heirarchy between current and new_current.
495 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
496 items_to_leave_virtual.push_back (i);
499 enter_detail = GDK_NOTIFY_INFERIOR;
500 leave_detail = GDK_NOTIFY_ANCESTOR;
502 } else if (_new_current_item->is_descendant_of (*_current_item)) {
503 /* move from ancestor to descendant (X: "_new_current_item is
504 * an inferior ("child") of _current_item")
506 * Deliver "virtual" enter notifications to all items in the
507 * heirarchy between current and new_current.
510 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
511 items_to_enter_virtual.push_back (i);
514 enter_detail = GDK_NOTIFY_ANCESTOR;
515 leave_detail = GDK_NOTIFY_INFERIOR;
519 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
521 /* deliver virtual leave events to everything between _current
522 * and common_ancestor.
525 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
526 items_to_leave_virtual.push_back (i);
529 /* deliver virtual enter events to everything between
530 * _new_current and common_ancestor.
533 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
534 items_to_enter_virtual.push_back (i);
537 enter_detail = GDK_NOTIFY_NONLINEAR;
538 leave_detail = GDK_NOTIFY_NONLINEAR;
542 if (_current_item && !_current_item->ignore_events ()) {
543 leave_event.detail = leave_detail;
544 _current_item->Event ((GdkEvent*)&leave_event);
545 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
548 leave_event.detail = GDK_NOTIFY_VIRTUAL;
549 enter_event.detail = GDK_NOTIFY_VIRTUAL;
551 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
552 if (!(*it)->ignore_events()) {
553 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
554 (*it)->Event ((GdkEvent*)&leave_event);
558 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
559 if (!(*it)->ignore_events()) {
560 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
561 (*it)->Event ((GdkEvent*)&enter_event);
562 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
566 if (_new_current_item && !_new_current_item->ignore_events()) {
567 enter_event.detail = enter_detail;
568 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
569 _new_current_item->Event ((GdkEvent*)&enter_event);
572 _current_item = _new_current_item;
576 /** Deliver an event to the appropriate item; either the grabbed item, or
577 * one of the items underneath the event.
578 * @param point Position that the event has occurred at, in canvas coordinates.
579 * @param event The event.
582 GtkCanvas::deliver_event (GdkEvent* event)
584 /* Point in in canvas coordinate space */
586 const Item* event_item;
589 /* we have a grabbed item, so everything gets sent there */
590 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
591 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
592 event_item = _grabbed_item;
594 event_item = _current_item;
601 /* run through the items from child to parent, until one claims the event */
603 Item* item = const_cast<Item*> (event_item);
607 Item* parent = item->parent ();
609 if (!item->ignore_events () &&
610 item->Event (event)) {
611 /* this item has just handled the event */
613 PBD::DEBUG::CanvasEvents,
614 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
620 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)));
622 if ((item = parent) == 0) {
631 /** Called when an item is being destroyed.
632 * @param item Item being destroyed.
633 * @param bounding_box Last known bounding box of the item.
636 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
639 queue_draw_item_area (item, bounding_box.get ());
642 if (_new_current_item == item) {
643 _new_current_item = 0;
646 if (_grabbed_item == item) {
650 if (_focused_item == item) {
654 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
656 scrollers.remove (sg);
659 if (_current_item == item) {
660 /* no need to send a leave event to this item, since it is going away
663 pick_current_item (0); // no mouse state
668 /** Handler for GDK expose events.
670 * @return true if the event was handled.
673 GtkCanvas::on_expose_event (GdkEventExpose* ev)
675 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
676 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
680 /** @return Our Cairo context, or 0 if we don't have one */
681 Cairo::RefPtr<Cairo::Context>
682 GtkCanvas::context ()
684 Glib::RefPtr<Gdk::Window> w = get_window ();
686 return Cairo::RefPtr<Cairo::Context> ();
689 return w->create_cairo_context ();
692 /** Handler for GDK scroll events.
694 * @return true if the event was handled.
697 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
699 /* translate event coordinates from window to canvas */
701 GdkEvent copy = *((GdkEvent*)ev);
702 Duple winpos = Duple (ev->x, ev->y);
703 Duple where = window_to_canvas (winpos);
705 pick_current_item (winpos, ev->state);
707 copy.button.x = where.x;
708 copy.button.y = where.y;
710 /* Coordinates in the event will be canvas coordinates, correctly adjusted
711 for scroll if this GtkCanvas is in a GtkCanvasViewport.
714 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
715 return deliver_event (reinterpret_cast<GdkEvent*>(©));
718 /** Handler for GDK button press events.
720 * @return true if the event was handled.
723 GtkCanvas::on_button_press_event (GdkEventButton* ev)
725 /* translate event coordinates from window to canvas */
727 GdkEvent copy = *((GdkEvent*)ev);
728 Duple winpos = Duple (ev->x, ev->y);
729 Duple where = window_to_canvas (winpos);
731 pick_current_item (winpos, ev->state);
733 copy.button.x = where.x;
734 copy.button.y = where.y;
736 /* Coordinates in the event will be canvas coordinates, correctly adjusted
737 for scroll if this GtkCanvas is in a GtkCanvasViewport.
740 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
741 return deliver_event (reinterpret_cast<GdkEvent*>(©));
744 /** Handler for GDK button release events.
746 * @return true if the event was handled.
749 GtkCanvas::on_button_release_event (GdkEventButton* ev)
751 /* translate event coordinates from window to canvas */
753 GdkEvent copy = *((GdkEvent*)ev);
754 Duple winpos = Duple (ev->x, ev->y);
755 Duple where = window_to_canvas (winpos);
757 pick_current_item (winpos, ev->state);
759 copy.button.x = where.x;
760 copy.button.y = where.y;
762 /* Coordinates in the event will be canvas coordinates, correctly adjusted
763 for scroll if this GtkCanvas is in a GtkCanvasViewport.
766 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
767 return deliver_event (reinterpret_cast<GdkEvent*>(©));
771 GtkCanvas::get_mouse_position (Duple& winpos) const
775 Gdk::ModifierType mask;
776 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
779 std::cerr << " no self window\n";
780 winpos = Duple (0, 0);
784 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
792 /** Handler for GDK motion events.
794 * @return true if the event was handled.
797 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
799 /* translate event coordinates from window to canvas */
801 GdkEvent copy = *((GdkEvent*)ev);
802 Duple point (ev->x, ev->y);
803 Duple where = window_to_canvas (point);
805 copy.motion.x = where.x;
806 copy.motion.y = where.y;
808 /* Coordinates in "copy" will be canvas coordinates,
811 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));
813 MouseMotion (point); /* EMIT SIGNAL */
815 pick_current_item (point, ev->state);
817 /* Now deliver the motion event. It may seem a little inefficient
818 to recompute the items under the event, but the enter notify/leave
819 events may have deleted canvas items so it is important to
820 recompute the list in deliver_event.
823 return deliver_event (reinterpret_cast<GdkEvent*> (©));
827 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
829 pick_current_item (Duple (ev->x, ev->y), ev->state);
834 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
836 _new_current_item = 0;
837 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
841 /** Called to request a redraw of our canvas.
842 * @param area Area to redraw, in window coordinates.
845 GtkCanvas::request_redraw (Rect const & request)
849 Coord const w = width ();
850 Coord const h = height ();
852 /* clamp area requested to actual visible window */
854 real_area.x0 = max (0.0, min (w, request.x0));
855 real_area.x1 = max (0.0, min (w, request.x1));
856 real_area.y0 = max (0.0, min (h, request.y0));
857 real_area.y1 = max (0.0, min (h, request.y1));
859 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
862 /** Called to request that we try to get a particular size for ourselves.
863 * @param size Size to request, in pixels.
866 GtkCanvas::request_size (Duple size)
870 if (req.x > INT_MAX) {
874 if (req.y > INT_MAX) {
878 set_size_request (req.x, req.y);
881 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
882 * This is typically used for dragging items around, so that they are grabbed during
884 * @param item Item to grab.
887 GtkCanvas::grab (Item* item)
889 /* XXX: should this be doing gdk_pointer_grab? */
890 _grabbed_item = item;
894 /** `Ungrab' any item that was previously grabbed */
898 /* XXX: should this be doing gdk_pointer_ungrab? */
902 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
904 * @param item Item to grab.
907 GtkCanvas::focus (Item* item)
909 _focused_item = item;
913 GtkCanvas::unfocus (Item* item)
915 if (item == _focused_item) {
920 /** @return The visible area of the canvas, in window coordinates */
922 GtkCanvas::visible_area () const
924 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
928 GtkCanvas::width() const
930 return get_allocation().get_width();
934 GtkCanvas::height() const
936 return get_allocation().get_height();
939 /** Create a GtkCanvaSViewport.
940 * @param hadj Adjustment to use for horizontal scrolling.
941 * @param vadj Adjustment to use for vertica scrolling.
943 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
944 : Alignment (0, 0, 1.0, 1.0)
950 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
951 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
955 GtkCanvasViewport::scrolled ()
957 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
961 /** Handler for when GTK asks us what minimum size we want.
962 * @param req Requsition to fill in.
965 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
967 /* force the canvas to size itself */
968 // _canvas.root()->bounding_box();