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>
29 #include <gtkmm/window.h>
31 #include "pbd/compose.h"
32 #include "pbd/stacktrace.h"
34 #include "canvas/canvas.h"
35 #include "canvas/debug.h"
36 #include "canvas/line.h"
37 #include "canvas/scroll_group.h"
40 using namespace ArdourCanvas;
42 uint32_t Canvas::tooltip_timeout_msecs = 750;
44 /** Construct a new Canvas */
52 Canvas::scroll_to (Coord x, Coord y)
54 /* We do things this way because we do not want to recurse through
55 the canvas for every scroll. In the presence of large MIDI
56 tracks this means traversing item lists that include
57 thousands of items (notes).
59 This design limits us to moving only those items (groups, typically)
60 that should move in certain ways as we scroll. In other terms, it
61 becomes O(1) rather than O(N).
64 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
65 (*i)->scroll_to (Duple (x, y));
68 pick_current_item (0); // no current mouse position
72 Canvas::add_scroller (ScrollGroup& i)
74 scrollers.push_back (&i);
80 pick_current_item (0); // no current mouse position
83 /** Render an area of the canvas.
84 * @param area Area in window coordinates.
85 * @param context Cairo context to render to.
88 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
91 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
92 cerr << this << " RENDER: " << area << endl;
93 //cerr << "CANVAS @ " << this << endl;
95 //cerr << "-------------------------\n";
101 boost::optional<Rect> root_bbox = _root.bounding_box();
103 /* the root has no bounding box, so there's nothing to render */
107 boost::optional<Rect> draw = root_bbox->intersection (area);
110 /* there's a common area between the root and the requested
114 _root.render (*draw, context);
117 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
118 // This transparently colors the rect being rendered, after it has been drawn.
119 double r = (random() % 65536) /65536.0;
120 double g = (random() % 65536) /65536.0;
121 double b = (random() % 65536) /65536.0;
122 context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
123 context->set_source_rgba (r, g, b, 0.25);
132 operator<< (ostream& o, Canvas& c)
139 Canvas::indent() const
143 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
151 Canvas::render_indent() const
155 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
163 Canvas::dump (ostream& o) const
169 /** Called when an item has been shown or hidden.
170 * @param item Item that has been shown or hidden.
173 Canvas::item_shown_or_hidden (Item* item)
175 boost::optional<Rect> bbox = item->bounding_box ();
177 if (item->item_to_window (*bbox).intersection (visible_area ())) {
178 queue_draw_item_area (item, bbox.get ());
183 /** Called when an item has a change to its visual properties
184 * that do NOT affect its bounding box.
185 * @param item Item that has been modified.
188 Canvas::item_visual_property_changed (Item* item)
190 boost::optional<Rect> bbox = item->bounding_box ();
192 if (item->item_to_window (*bbox).intersection (visible_area ())) {
193 queue_draw_item_area (item, bbox.get ());
198 /** Called when an item has changed, but not moved.
199 * @param item Item that has changed.
200 * @param pre_change_bounding_box The bounding box of item before the change,
201 * in the item's coordinates.
204 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
207 Rect window_bbox = visible_area ();
209 if (pre_change_bounding_box) {
211 if (item->item_to_window (*pre_change_bounding_box).intersection (window_bbox)) {
212 /* request a redraw of the item's old bounding box */
213 queue_draw_item_area (item, pre_change_bounding_box.get ());
217 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
218 if (post_change_bounding_box) {
220 if (item->item_to_window (*post_change_bounding_box).intersection (window_bbox)) {
221 /* request a redraw of the item's new bounding box */
222 queue_draw_item_area (item, post_change_bounding_box.get ());
228 Canvas::window_to_canvas (Duple const & d) const
230 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
231 * as children of the root group, so we just scan its first level
232 * children and see what we can find.
235 std::list<Item*> const& root_children (_root.items());
238 /* if the coordinates are negative, clamp to zero and find the item
239 * that covers that "edge" position.
244 if (in_window.x < 0) {
247 if (in_window.y < 0) {
251 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
252 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (in_window)) {
258 return d.translate (sg->scroll_offset());
265 Canvas::canvas_to_window (Duple const & d, bool rounded) const
267 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
268 * as children of the root group, so we just scan its first level
269 * children and see what we can find.
272 std::list<Item*> const& root_children (_root.items());
276 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
277 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
284 wd = d.translate (-sg->scroll_offset());
289 /* Note that this intentionally almost always returns integer coordinates */
299 /** Called when an item has moved.
300 * @param item Item that has moved.
301 * @param pre_change_parent_bounding_box The bounding box of the item before
302 * the move, in its parent's coordinates.
305 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
307 if (pre_change_parent_bounding_box) {
308 /* request a redraw of where the item used to be. The box has
309 * to be in parent coordinate space since the bounding box of
310 * an item does not change when moved. If we use
311 * item->item_to_canvas() on the old bounding box, we will be
313 * using the item's new position, and so will compute the wrong
314 * invalidation area. If we use the parent (which has not
315 * moved, then this will work.
317 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
320 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
321 if (post_change_bounding_box) {
322 /* request a redraw of where the item now is */
323 queue_draw_item_area (item, post_change_bounding_box.get ());
327 /** Request a redraw of a particular area in an item's coordinates.
329 * @param area Area to redraw in the item's coordinates.
332 Canvas::queue_draw_item_area (Item* item, Rect area)
334 request_redraw (item->item_to_window (area));
338 Canvas::set_tooltip_timeout (uint32_t msecs)
340 tooltip_timeout_msecs = msecs;
344 GtkCanvas::re_enter ()
346 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
348 pick_current_item (0);
351 /** Construct a GtkCanvas */
352 GtkCanvas::GtkCanvas ()
354 , _new_current_item (0)
357 , current_tooltip_item (0)
360 /* these are the events we want to know about */
361 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
362 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
366 GtkCanvas::pick_current_item (int state)
371 /* this version of ::pick_current_item() is called after an item is
372 * added or removed, so we have no coordinates to work from as is the
373 * case with a motion event. Find out where the mouse is and use that.
376 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
378 if (pointer_window != get_window()) {
382 pick_current_item (Duple (x, y), state);
385 /** Given @param point (a position in window coordinates)
386 * and mouse state @param state, check to see if _current_item
387 * (which will be used to deliver events) should change.
390 GtkCanvas::pick_current_item (Duple const & point, int state)
392 /* we do not enter/leave items during a drag/grab */
398 /* find the items at the given window position */
400 vector<Item const *> items;
401 _root.add_items_at_point (point, items);
403 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
406 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
407 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
409 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
411 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
417 /* put all items at point that are event-sensitive and visible and NOT
418 groups into within_items. Note that items is sorted from bottom to
419 top, but we're going to reverse that for within_items so that its
420 first item is the upper-most item that can be chosen as _current_item.
423 vector<Item const *>::const_iterator i;
424 list<Item const *> within_items;
426 for (i = items.begin(); i != items.end(); ++i) {
428 Item const * possible_item = *i;
430 /* We ignore invisible items, containers and items that ignore events */
432 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
435 within_items.push_front (possible_item);
438 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
440 if (within_items.empty()) {
442 /* no items at point, just send leave event below */
443 _new_current_item = 0;
447 if (within_items.front() == _current_item) {
448 /* uppermost item at point is already _current_item */
449 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
453 _new_current_item = const_cast<Item*> (within_items.front());
456 if (_new_current_item != _current_item) {
457 deliver_enter_leave (point, state);
461 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
463 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
468 /** Deliver a series of enter & leave events based on the pointer position being at window
469 * coordinate @param point, and pointer @param state (modifier keys, etc)
472 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
474 /* setup enter & leave event structures */
476 Glib::RefPtr<Gdk::Window> win = get_window();
482 GdkEventCrossing enter_event;
483 enter_event.type = GDK_ENTER_NOTIFY;
484 enter_event.window = win->gobj();
485 enter_event.send_event = 0;
486 enter_event.subwindow = 0;
487 enter_event.mode = GDK_CROSSING_NORMAL;
488 enter_event.focus = FALSE;
489 enter_event.state = state;
491 /* Events delivered to canvas items are expected to be in canvas
492 * coordinates but @param point is in window coordinates.
495 Duple c = window_to_canvas (point);
499 GdkEventCrossing leave_event = enter_event;
500 leave_event.type = GDK_LEAVE_NOTIFY;
503 GdkNotifyType enter_detail;
504 GdkNotifyType leave_detail;
505 vector<Item*> items_to_leave_virtual;
506 vector<Item*> items_to_enter_virtual;
508 if (_new_current_item == 0) {
510 leave_detail = GDK_NOTIFY_UNKNOWN;
514 /* no current item, so also send virtual leave events to the
515 * entire heirarchy for the current item
518 for (i = _current_item->parent(); i ; i = i->parent()) {
519 items_to_leave_virtual.push_back (i);
523 } else if (_current_item == 0) {
525 enter_detail = GDK_NOTIFY_UNKNOWN;
527 /* no current item, so also send virtual enter events to the
528 * entire heirarchy for the new item
531 for (i = _new_current_item->parent(); i ; i = i->parent()) {
532 items_to_enter_virtual.push_back (i);
535 } else if (_current_item->is_descendant_of (*_new_current_item)) {
537 /* move from descendant to ancestor (X: "_current_item is an
538 * inferior ("child") of _new_current_item")
540 * Deliver "virtual" leave notifications to all items in the
541 * heirarchy between current and new_current.
544 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
545 items_to_leave_virtual.push_back (i);
548 enter_detail = GDK_NOTIFY_INFERIOR;
549 leave_detail = GDK_NOTIFY_ANCESTOR;
551 } else if (_new_current_item->is_descendant_of (*_current_item)) {
552 /* move from ancestor to descendant (X: "_new_current_item is
553 * an inferior ("child") of _current_item")
555 * Deliver "virtual" enter notifications to all items in the
556 * heirarchy between current and new_current.
559 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
560 items_to_enter_virtual.push_back (i);
563 enter_detail = GDK_NOTIFY_ANCESTOR;
564 leave_detail = GDK_NOTIFY_INFERIOR;
568 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
570 /* deliver virtual leave events to everything between _current
571 * and common_ancestor.
574 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
575 items_to_leave_virtual.push_back (i);
578 /* deliver virtual enter events to everything between
579 * _new_current and common_ancestor.
582 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
583 items_to_enter_virtual.push_back (i);
586 enter_detail = GDK_NOTIFY_NONLINEAR;
587 leave_detail = GDK_NOTIFY_NONLINEAR;
591 if (_current_item && !_current_item->ignore_events ()) {
592 leave_event.detail = leave_detail;
593 _current_item->Event ((GdkEvent*)&leave_event);
594 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
597 leave_event.detail = GDK_NOTIFY_VIRTUAL;
598 enter_event.detail = GDK_NOTIFY_VIRTUAL;
600 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
601 if (!(*it)->ignore_events()) {
602 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
603 (*it)->Event ((GdkEvent*)&leave_event);
607 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
608 if (!(*it)->ignore_events()) {
609 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
610 (*it)->Event ((GdkEvent*)&enter_event);
611 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
615 if (_new_current_item && !_new_current_item->ignore_events()) {
616 enter_event.detail = enter_detail;
617 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
618 start_tooltip_timeout (_new_current_item);
619 _new_current_item->Event ((GdkEvent*)&enter_event);
622 _current_item = _new_current_item;
626 /** Deliver an event to the appropriate item; either the grabbed item, or
627 * one of the items underneath the event.
628 * @param point Position that the event has occurred at, in canvas coordinates.
629 * @param event The event.
632 GtkCanvas::deliver_event (GdkEvent* event)
634 /* Point in in canvas coordinate space */
636 const Item* event_item;
639 /* we have a grabbed item, so everything gets sent there */
640 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
641 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
642 event_item = _grabbed_item;
644 event_item = _current_item;
651 /* run through the items from child to parent, until one claims the event */
653 Item* item = const_cast<Item*> (event_item);
657 Item* parent = item->parent ();
659 if (!item->ignore_events () &&
660 item->Event (event)) {
661 /* this item has just handled the event */
663 PBD::DEBUG::CanvasEvents,
664 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
670 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)));
672 if ((item = parent) == 0) {
681 /** Called when an item is being destroyed.
682 * @param item Item being destroyed.
683 * @param bounding_box Last known bounding box of the item.
686 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
689 queue_draw_item_area (item, bounding_box.get ());
692 if (_new_current_item == item) {
693 _new_current_item = 0;
696 if (_grabbed_item == item) {
700 if (_focused_item == item) {
704 if (current_tooltip_item) {
705 current_tooltip_item = 0;
706 stop_tooltip_timeout ();
709 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
711 scrollers.remove (sg);
714 if (_current_item == item) {
715 /* no need to send a leave event to this item, since it is going away
718 pick_current_item (0); // no mouse state
723 /** Handler for GDK expose events.
725 * @return true if the event was handled.
728 GtkCanvas::on_expose_event (GdkEventExpose* ev)
730 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
731 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
735 /** @return Our Cairo context, or 0 if we don't have one */
736 Cairo::RefPtr<Cairo::Context>
737 GtkCanvas::context ()
739 Glib::RefPtr<Gdk::Window> w = get_window ();
741 return Cairo::RefPtr<Cairo::Context> ();
744 return w->create_cairo_context ();
747 /** Handler for GDK scroll events.
749 * @return true if the event was handled.
752 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
754 /* translate event coordinates from window to canvas */
756 GdkEvent copy = *((GdkEvent*)ev);
757 Duple winpos = Duple (ev->x, ev->y);
758 Duple where = window_to_canvas (winpos);
760 pick_current_item (winpos, ev->state);
762 copy.button.x = where.x;
763 copy.button.y = where.y;
765 /* Coordinates in the event will be canvas coordinates, correctly adjusted
766 for scroll if this GtkCanvas is in a GtkCanvasViewport.
769 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
770 return deliver_event (reinterpret_cast<GdkEvent*>(©));
773 /** Handler for GDK button press events.
775 * @return true if the event was handled.
778 GtkCanvas::on_button_press_event (GdkEventButton* ev)
780 /* translate event coordinates from window to canvas */
782 GdkEvent copy = *((GdkEvent*)ev);
783 Duple winpos = Duple (ev->x, ev->y);
784 Duple where = window_to_canvas (winpos);
786 pick_current_item (winpos, ev->state);
788 copy.button.x = where.x;
789 copy.button.y = where.y;
791 /* Coordinates in the event will be canvas coordinates, correctly adjusted
792 for scroll if this GtkCanvas is in a GtkCanvasViewport.
795 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
796 return deliver_event (reinterpret_cast<GdkEvent*>(©));
799 /** Handler for GDK button release events.
801 * @return true if the event was handled.
804 GtkCanvas::on_button_release_event (GdkEventButton* ev)
806 /* translate event coordinates from window to canvas */
808 GdkEvent copy = *((GdkEvent*)ev);
809 Duple winpos = Duple (ev->x, ev->y);
810 Duple where = window_to_canvas (winpos);
812 pick_current_item (winpos, ev->state);
814 copy.button.x = where.x;
815 copy.button.y = where.y;
817 /* Coordinates in the event will be canvas coordinates, correctly adjusted
818 for scroll if this GtkCanvas is in a GtkCanvasViewport.
821 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
822 return deliver_event (reinterpret_cast<GdkEvent*>(©));
826 GtkCanvas::get_mouse_position (Duple& winpos) const
830 Gdk::ModifierType mask;
831 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
834 std::cerr << " no self window\n";
835 winpos = Duple (0, 0);
839 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
847 /** Handler for GDK motion events.
849 * @return true if the event was handled.
852 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
856 /* translate event coordinates from window to canvas */
858 GdkEvent copy = *((GdkEvent*)ev);
859 Duple point (ev->x, ev->y);
860 Duple where = window_to_canvas (point);
862 copy.motion.x = where.x;
863 copy.motion.y = where.y;
865 /* Coordinates in "copy" will be canvas coordinates,
868 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));
870 MouseMotion (point); /* EMIT SIGNAL */
872 pick_current_item (point, ev->state);
874 /* Now deliver the motion event. It may seem a little inefficient
875 to recompute the items under the event, but the enter notify/leave
876 events may have deleted canvas items so it is important to
877 recompute the list in deliver_event.
880 return deliver_event (reinterpret_cast<GdkEvent*> (©));
884 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
886 pick_current_item (Duple (ev->x, ev->y), ev->state);
891 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
893 _new_current_item = 0;
894 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
898 /** Called to request a redraw of our canvas.
899 * @param area Area to redraw, in window coordinates.
902 GtkCanvas::request_redraw (Rect const & request)
906 Coord const w = width ();
907 Coord const h = height ();
909 /* clamp area requested to actual visible window */
911 real_area.x0 = max (0.0, min (w, request.x0));
912 real_area.x1 = max (0.0, min (w, request.x1));
913 real_area.y0 = max (0.0, min (h, request.y0));
914 real_area.y1 = max (0.0, min (h, request.y1));
916 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
919 /** Called to request that we try to get a particular size for ourselves.
920 * @param size Size to request, in pixels.
923 GtkCanvas::request_size (Duple size)
927 if (req.x > INT_MAX) {
931 if (req.y > INT_MAX) {
935 set_size_request (req.x, req.y);
938 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
939 * This is typically used for dragging items around, so that they are grabbed during
941 * @param item Item to grab.
944 GtkCanvas::grab (Item* item)
946 /* XXX: should this be doing gdk_pointer_grab? */
947 _grabbed_item = item;
951 /** `Ungrab' any item that was previously grabbed */
955 /* XXX: should this be doing gdk_pointer_ungrab? */
959 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
961 * @param item Item to grab.
964 GtkCanvas::focus (Item* item)
966 _focused_item = item;
970 GtkCanvas::unfocus (Item* item)
972 if (item == _focused_item) {
977 /** @return The visible area of the canvas, in window coordinates */
979 GtkCanvas::visible_area () const
981 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
985 GtkCanvas::width() const
987 return get_allocation().get_width();
991 GtkCanvas::height() const
993 return get_allocation().get_height();
997 GtkCanvas::start_tooltip_timeout (Item* item)
999 stop_tooltip_timeout ();
1002 current_tooltip_item = item;
1004 /* wait for the first idle that happens after this is
1005 called. this means that we've stopped processing events, which
1006 in turn implies that the user has stopped doing stuff for a
1010 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1015 GtkCanvas::really_start_tooltip_timeout ()
1017 /* an idle has occured since we entered a tooltip-bearing widget. Now
1018 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1021 if (current_tooltip_item) {
1022 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1025 return false; /* this is called from an idle callback, don't call it again */
1029 GtkCanvas::stop_tooltip_timeout ()
1031 current_tooltip_item = 0;
1032 tooltip_timeout_connection.disconnect ();
1036 GtkCanvas::show_tooltip ()
1038 Rect tooltip_item_bbox;
1040 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1044 if (!tooltip_window) {
1045 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1046 tooltip_label = manage (new Gtk::Label);
1047 tooltip_label->show ();
1048 tooltip_window->add (*tooltip_label);
1049 tooltip_window->set_border_width (6);
1050 tooltip_window->set_name ("tooltip");
1053 tooltip_label->set_text (current_tooltip_item->tooltip());
1055 /* figure out where to position the tooltip */
1057 Gtk::Widget* toplevel = get_toplevel();
1059 int pointer_x, pointer_y;
1060 Gdk::ModifierType mask;
1062 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1064 Duple tooltip_window_origin (pointer_x, pointer_y);
1066 /* convert to root window coordinates */
1069 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1071 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1073 /* we don't want the pointer to be inside the window when it is
1074 * displayed, because then we generate a leave/enter event pair when
1075 * the window is displayed then hidden - the enter event will
1076 * trigger a new tooltip timeout.
1078 * So move the window right of the pointer position by just a enough
1079 * to get it away from the pointer.
1082 tooltip_window_origin.x += 20;
1084 /* move the tooltip window into position */
1086 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1090 tooltip_window->present ();
1092 /* called from a timeout handler, don't call it again */
1098 GtkCanvas::hide_tooltip ()
1100 /* hide it if its there */
1102 if (tooltip_window) {
1103 tooltip_window->hide ();
1107 /** Create a GtkCanvaSViewport.
1108 * @param hadj Adjustment to use for horizontal scrolling.
1109 * @param vadj Adjustment to use for vertica scrolling.
1111 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1112 : Alignment (0, 0, 1.0, 1.0)
1113 , hadjustment (hadj)
1114 , vadjustment (vadj)
1118 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1119 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1123 GtkCanvasViewport::scrolled ()
1125 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1129 /** Handler for when GTK asks us what minimum size we want.
1130 * @param req Requsition to fill in.
1133 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1135 /* force the canvas to size itself */
1136 // _canvas.root()->bounding_box();