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/colors.h"
36 #include "canvas/debug.h"
37 #include "canvas/line.h"
38 #include "canvas/scroll_group.h"
39 #include "canvas/utils.h"
42 using namespace ArdourCanvas;
44 uint32_t Canvas::tooltip_timeout_msecs = 750;
46 /** Construct a new Canvas */
49 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
55 Canvas::scroll_to (Coord x, Coord y)
57 /* We do things this way because we do not want to recurse through
58 the canvas for every scroll. In the presence of large MIDI
59 tracks this means traversing item lists that include
60 thousands of items (notes).
62 This design limits us to moving only those items (groups, typically)
63 that should move in certain ways as we scroll. In other terms, it
64 becomes O(1) rather than O(N).
67 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
68 (*i)->scroll_to (Duple (x, y));
71 pick_current_item (0); // no current mouse position
75 Canvas::add_scroller (ScrollGroup& i)
77 scrollers.push_back (&i);
83 pick_current_item (0); // no current mouse position
86 /** Render an area of the canvas.
87 * @param area Area in window coordinates.
88 * @param context Cairo context to render to.
91 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
94 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
95 cerr << this << " RENDER: " << area << endl;
96 //cerr << "CANVAS @ " << this << endl;
98 //cerr << "-------------------------\n";
104 boost::optional<Rect> root_bbox = _root.bounding_box();
106 /* the root has no bounding box, so there's nothing to render */
110 boost::optional<Rect> draw = root_bbox->intersection (area);
113 /* there's a common area between the root and the requested
117 _root.render (*draw, context);
119 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
120 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
121 // This transparently colors the rect being rendered, after it has been drawn.
122 double r = (random() % 65536) /65536.0;
123 double g = (random() % 65536) /65536.0;
124 double b = (random() % 65536) /65536.0;
125 context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
126 context->set_source_rgba (r, g, b, 0.25);
135 operator<< (ostream& o, Canvas& c)
142 Canvas::indent() const
146 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
154 Canvas::render_indent() const
158 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
166 Canvas::dump (ostream& o) const
172 /** Called when an item has been shown or hidden.
173 * @param item Item that has been shown or hidden.
176 Canvas::item_shown_or_hidden (Item* item)
178 boost::optional<Rect> bbox = item->bounding_box ();
180 if (item->item_to_window (*bbox).intersection (visible_area ())) {
181 queue_draw_item_area (item, bbox.get ());
186 /** Called when an item has a change to its visual properties
187 * that do NOT affect its bounding box.
188 * @param item Item that has been modified.
191 Canvas::item_visual_property_changed (Item* item)
193 boost::optional<Rect> bbox = item->bounding_box ();
195 if (item->item_to_window (*bbox).intersection (visible_area ())) {
196 queue_draw_item_area (item, bbox.get ());
201 /** Called when an item has changed, but not moved.
202 * @param item Item that has changed.
203 * @param pre_change_bounding_box The bounding box of item before the change,
204 * in the item's coordinates.
207 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
210 Rect window_bbox = visible_area ();
212 if (pre_change_bounding_box) {
214 if (item->item_to_window (*pre_change_bounding_box).intersection (window_bbox)) {
215 /* request a redraw of the item's old bounding box */
216 queue_draw_item_area (item, pre_change_bounding_box.get ());
220 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
221 if (post_change_bounding_box) {
223 if (item->item_to_window (*post_change_bounding_box).intersection (window_bbox)) {
224 /* request a redraw of the item's new bounding box */
225 queue_draw_item_area (item, post_change_bounding_box.get ());
231 Canvas::window_to_canvas (Duple const & d) const
233 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
234 * as children of the root group, so we just scan its first level
235 * children and see what we can find.
238 std::list<Item*> const& root_children (_root.items());
241 /* if the coordinates are negative, clamp to zero and find the item
242 * that covers that "edge" position.
247 if (in_window.x < 0) {
250 if (in_window.y < 0) {
254 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
255 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (in_window)) {
261 return d.translate (sg->scroll_offset());
268 Canvas::canvas_to_window (Duple const & d, bool rounded) const
270 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
271 * as children of the root group, so we just scan its first level
272 * children and see what we can find.
275 std::list<Item*> const& root_children (_root.items());
279 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
280 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
287 wd = d.translate (-sg->scroll_offset());
292 /* Note that this intentionally almost always returns integer coordinates */
302 /** Called when an item has moved.
303 * @param item Item that has moved.
304 * @param pre_change_parent_bounding_box The bounding box of the item before
305 * the move, in its parent's coordinates.
308 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
310 if (pre_change_parent_bounding_box) {
311 /* request a redraw of where the item used to be. The box has
312 * to be in parent coordinate space since the bounding box of
313 * an item does not change when moved. If we use
314 * item->item_to_canvas() on the old bounding box, we will be
316 * using the item's new position, and so will compute the wrong
317 * invalidation area. If we use the parent (which has not
318 * moved, then this will work.
320 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
323 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
324 if (post_change_bounding_box) {
325 /* request a redraw of where the item now is */
326 queue_draw_item_area (item, post_change_bounding_box.get ());
330 /** Request a redraw of a particular area in an item's coordinates.
332 * @param area Area to redraw in the item's coordinates.
335 Canvas::queue_draw_item_area (Item* item, Rect area)
337 request_redraw (item->item_to_window (area));
341 Canvas::set_tooltip_timeout (uint32_t msecs)
343 tooltip_timeout_msecs = msecs;
347 Canvas::set_background_color (Color c)
351 boost::optional<Rect> r = _root.bounding_box();
354 request_redraw (_root.item_to_window (r.get()));
359 GtkCanvas::re_enter ()
361 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
363 pick_current_item (0);
366 /** Construct a GtkCanvas */
367 GtkCanvas::GtkCanvas ()
369 , _new_current_item (0)
372 , current_tooltip_item (0)
375 /* these are the events we want to know about */
376 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
377 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
378 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
382 GtkCanvas::pick_current_item (int state)
387 /* this version of ::pick_current_item() is called after an item is
388 * added or removed, so we have no coordinates to work from as is the
389 * case with a motion event. Find out where the mouse is and use that.
392 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
394 if (pointer_window != get_window()) {
398 pick_current_item (Duple (x, y), state);
401 /** Given @param point (a position in window coordinates)
402 * and mouse state @param state, check to see if _current_item
403 * (which will be used to deliver events) should change.
406 GtkCanvas::pick_current_item (Duple const & point, int state)
408 /* we do not enter/leave items during a drag/grab */
414 /* find the items at the given window position */
416 vector<Item const *> items;
417 _root.add_items_at_point (point, items);
419 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
422 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
423 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
425 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
427 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
433 /* put all items at point that are event-sensitive and visible and NOT
434 groups into within_items. Note that items is sorted from bottom to
435 top, but we're going to reverse that for within_items so that its
436 first item is the upper-most item that can be chosen as _current_item.
439 vector<Item const *>::const_iterator i;
440 list<Item const *> within_items;
442 for (i = items.begin(); i != items.end(); ++i) {
444 Item const * possible_item = *i;
446 /* We ignore invisible items, containers and items that ignore events */
448 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
451 within_items.push_front (possible_item);
454 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
456 if (within_items.empty()) {
458 /* no items at point, just send leave event below */
459 _new_current_item = 0;
463 if (within_items.front() == _current_item) {
464 /* uppermost item at point is already _current_item */
465 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
469 _new_current_item = const_cast<Item*> (within_items.front());
472 if (_new_current_item != _current_item) {
473 deliver_enter_leave (point, state);
477 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
479 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
484 /** Deliver a series of enter & leave events based on the pointer position being at window
485 * coordinate @param point, and pointer @param state (modifier keys, etc)
488 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
490 /* setup enter & leave event structures */
492 Glib::RefPtr<Gdk::Window> win = get_window();
498 GdkEventCrossing enter_event;
499 enter_event.type = GDK_ENTER_NOTIFY;
500 enter_event.window = win->gobj();
501 enter_event.send_event = 0;
502 enter_event.subwindow = 0;
503 enter_event.mode = GDK_CROSSING_NORMAL;
504 enter_event.focus = FALSE;
505 enter_event.state = state;
507 /* Events delivered to canvas items are expected to be in canvas
508 * coordinates but @param point is in window coordinates.
511 Duple c = window_to_canvas (point);
515 GdkEventCrossing leave_event = enter_event;
516 leave_event.type = GDK_LEAVE_NOTIFY;
519 GdkNotifyType enter_detail;
520 GdkNotifyType leave_detail;
521 vector<Item*> items_to_leave_virtual;
522 vector<Item*> items_to_enter_virtual;
524 if (_new_current_item == 0) {
526 leave_detail = GDK_NOTIFY_UNKNOWN;
530 /* no current item, so also send virtual leave events to the
531 * entire heirarchy for the current item
534 for (i = _current_item->parent(); i ; i = i->parent()) {
535 items_to_leave_virtual.push_back (i);
539 } else if (_current_item == 0) {
541 enter_detail = GDK_NOTIFY_UNKNOWN;
543 /* no current item, so also send virtual enter events to the
544 * entire heirarchy for the new item
547 for (i = _new_current_item->parent(); i ; i = i->parent()) {
548 items_to_enter_virtual.push_back (i);
551 } else if (_current_item->is_descendant_of (*_new_current_item)) {
553 /* move from descendant to ancestor (X: "_current_item is an
554 * inferior ("child") of _new_current_item")
556 * Deliver "virtual" leave notifications to all items in the
557 * heirarchy between current and new_current.
560 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
561 items_to_leave_virtual.push_back (i);
564 enter_detail = GDK_NOTIFY_INFERIOR;
565 leave_detail = GDK_NOTIFY_ANCESTOR;
567 } else if (_new_current_item->is_descendant_of (*_current_item)) {
568 /* move from ancestor to descendant (X: "_new_current_item is
569 * an inferior ("child") of _current_item")
571 * Deliver "virtual" enter notifications to all items in the
572 * heirarchy between current and new_current.
575 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
576 items_to_enter_virtual.push_back (i);
579 enter_detail = GDK_NOTIFY_ANCESTOR;
580 leave_detail = GDK_NOTIFY_INFERIOR;
584 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
586 /* deliver virtual leave events to everything between _current
587 * and common_ancestor.
590 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
591 items_to_leave_virtual.push_back (i);
594 /* deliver virtual enter events to everything between
595 * _new_current and common_ancestor.
598 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
599 items_to_enter_virtual.push_back (i);
602 enter_detail = GDK_NOTIFY_NONLINEAR;
603 leave_detail = GDK_NOTIFY_NONLINEAR;
607 if (_current_item && !_current_item->ignore_events ()) {
608 leave_event.detail = leave_detail;
609 _current_item->Event ((GdkEvent*)&leave_event);
610 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
613 leave_event.detail = GDK_NOTIFY_VIRTUAL;
614 enter_event.detail = GDK_NOTIFY_VIRTUAL;
616 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
617 if (!(*it)->ignore_events()) {
618 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
619 (*it)->Event ((GdkEvent*)&leave_event);
623 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
624 if (!(*it)->ignore_events()) {
625 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
626 (*it)->Event ((GdkEvent*)&enter_event);
627 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
631 if (_new_current_item && !_new_current_item->ignore_events()) {
632 enter_event.detail = enter_detail;
633 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
634 start_tooltip_timeout (_new_current_item);
635 _new_current_item->Event ((GdkEvent*)&enter_event);
638 _current_item = _new_current_item;
642 /** Deliver an event to the appropriate item; either the grabbed item, or
643 * one of the items underneath the event.
644 * @param point Position that the event has occurred at, in canvas coordinates.
645 * @param event The event.
648 GtkCanvas::deliver_event (GdkEvent* event)
650 /* Point in in canvas coordinate space */
652 const Item* event_item;
655 /* we have a grabbed item, so everything gets sent there */
656 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
657 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
658 event_item = _grabbed_item;
660 event_item = _current_item;
667 /* run through the items from child to parent, until one claims the event */
669 Item* item = const_cast<Item*> (event_item);
673 Item* parent = item->parent ();
675 if (!item->ignore_events () &&
676 item->Event (event)) {
677 /* this item has just handled the event */
679 PBD::DEBUG::CanvasEvents,
680 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
686 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)));
688 if ((item = parent) == 0) {
697 /** Called when an item is being destroyed.
698 * @param item Item being destroyed.
699 * @param bounding_box Last known bounding box of the item.
702 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
705 queue_draw_item_area (item, bounding_box.get ());
708 if (_new_current_item == item) {
709 _new_current_item = 0;
712 if (_grabbed_item == item) {
716 if (_focused_item == item) {
720 if (current_tooltip_item) {
721 current_tooltip_item = 0;
722 stop_tooltip_timeout ();
725 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
727 scrollers.remove (sg);
730 if (_current_item == item) {
731 /* no need to send a leave event to this item, since it is going away
734 pick_current_item (0); // no mouse state
740 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
742 EventBox::on_size_allocate (a);
743 #ifdef USE_CAIRO_IMAGE_SURFACE
744 /* allocate an image surface as large as the canvas itself */
746 canvas_image.clear ();
747 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
751 /** Handler for GDK expose events.
753 * @return true if the event was handled.
756 GtkCanvas::on_expose_event (GdkEventExpose* ev)
758 #ifdef USE_CAIRO_IMAGE_SURFACE
760 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
762 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
763 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
765 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
768 /* draw background color */
770 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
771 draw_context->clip_preserve ();
772 set_source_rgba (draw_context, _bg_color);
773 draw_context->fill ();
777 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
779 #ifdef USE_CAIRO_IMAGE_SURFACE
780 /* now blit our private surface back to the GDK one */
782 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
783 window_context->clip ();
784 window_context->set_source (canvas_image, 0, 0);
785 window_context->set_operator (Cairo::OPERATOR_SOURCE);
786 window_context->paint ();
792 /** Handler for GDK scroll events.
794 * @return true if the event was handled.
797 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
799 /* translate event coordinates from window to canvas */
801 GdkEvent copy = *((GdkEvent*)ev);
802 Duple winpos = Duple (ev->x, ev->y);
803 Duple where = window_to_canvas (winpos);
805 pick_current_item (winpos, ev->state);
807 copy.button.x = where.x;
808 copy.button.y = where.y;
810 /* Coordinates in the event will be canvas coordinates, correctly adjusted
811 for scroll if this GtkCanvas is in a GtkCanvasViewport.
814 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
815 return deliver_event (reinterpret_cast<GdkEvent*>(©));
818 /** Handler for GDK key press events.
820 * @return true if the event was handled.
823 GtkCanvas::on_key_press_event (GdkEventKey* ev)
825 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
826 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
829 /** Handler for GDK key release events.
831 * @return true if the event was handled.
834 GtkCanvas::on_key_release_event (GdkEventKey* ev)
836 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
837 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
840 /** Handler for GDK button press events.
842 * @return true if the event was handled.
845 GtkCanvas::on_button_press_event (GdkEventButton* ev)
847 /* translate event coordinates from window to canvas */
849 GdkEvent copy = *((GdkEvent*)ev);
850 Duple winpos = Duple (ev->x, ev->y);
851 Duple where = window_to_canvas (winpos);
853 pick_current_item (winpos, ev->state);
855 copy.button.x = where.x;
856 copy.button.y = where.y;
858 /* Coordinates in the event will be canvas coordinates, correctly adjusted
859 for scroll if this GtkCanvas is in a GtkCanvasViewport.
862 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
863 return deliver_event (reinterpret_cast<GdkEvent*>(©));
866 /** Handler for GDK button release events.
868 * @return true if the event was handled.
871 GtkCanvas::on_button_release_event (GdkEventButton* ev)
873 /* translate event coordinates from window to canvas */
875 GdkEvent copy = *((GdkEvent*)ev);
876 Duple winpos = Duple (ev->x, ev->y);
877 Duple where = window_to_canvas (winpos);
879 pick_current_item (winpos, ev->state);
881 copy.button.x = where.x;
882 copy.button.y = where.y;
884 /* Coordinates in the event will be canvas coordinates, correctly adjusted
885 for scroll if this GtkCanvas is in a GtkCanvasViewport.
888 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
889 return deliver_event (reinterpret_cast<GdkEvent*>(©));
893 GtkCanvas::get_mouse_position (Duple& winpos) const
897 Gdk::ModifierType mask;
898 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
901 std::cerr << " no self window\n";
902 winpos = Duple (0, 0);
906 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
914 /** Handler for GDK motion events.
916 * @return true if the event was handled.
919 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
923 /* translate event coordinates from window to canvas */
925 GdkEvent copy = *((GdkEvent*)ev);
926 Duple point (ev->x, ev->y);
927 Duple where = window_to_canvas (point);
929 copy.motion.x = where.x;
930 copy.motion.y = where.y;
932 /* Coordinates in "copy" will be canvas coordinates,
935 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));
937 MouseMotion (point); /* EMIT SIGNAL */
939 pick_current_item (point, ev->state);
941 /* Now deliver the motion event. It may seem a little inefficient
942 to recompute the items under the event, but the enter notify/leave
943 events may have deleted canvas items so it is important to
944 recompute the list in deliver_event.
947 return deliver_event (reinterpret_cast<GdkEvent*> (©));
951 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
953 pick_current_item (Duple (ev->x, ev->y), ev->state);
958 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
960 switch (ev->detail) {
961 case GDK_NOTIFY_ANCESTOR:
962 case GDK_NOTIFY_UNKNOWN:
963 case GDK_NOTIFY_VIRTUAL:
964 case GDK_NOTIFY_NONLINEAR:
965 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
966 /* leaving window, cancel any tooltips */
967 stop_tooltip_timeout ();
971 /* we don't care about any other kind
972 of leave event (notably GDK_NOTIFY_INFERIOR)
976 _new_current_item = 0;
977 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
981 /** Called to request a redraw of our canvas.
982 * @param area Area to redraw, in window coordinates.
985 GtkCanvas::request_redraw (Rect const & request)
989 Coord const w = width ();
990 Coord const h = height ();
992 /* clamp area requested to actual visible window */
994 real_area.x0 = max (0.0, min (w, request.x0));
995 real_area.x1 = max (0.0, min (w, request.x1));
996 real_area.y0 = max (0.0, min (h, request.y0));
997 real_area.y1 = max (0.0, min (h, request.y1));
999 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1002 /** Called to request that we try to get a particular size for ourselves.
1003 * @param size Size to request, in pixels.
1006 GtkCanvas::request_size (Duple size)
1010 if (req.x > INT_MAX) {
1014 if (req.y > INT_MAX) {
1018 set_size_request (req.x, req.y);
1021 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1022 * This is typically used for dragging items around, so that they are grabbed during
1024 * @param item Item to grab.
1027 GtkCanvas::grab (Item* item)
1029 /* XXX: should this be doing gdk_pointer_grab? */
1030 _grabbed_item = item;
1034 /** `Ungrab' any item that was previously grabbed */
1036 GtkCanvas::ungrab ()
1038 /* XXX: should this be doing gdk_pointer_ungrab? */
1042 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1044 * @param item Item to grab.
1047 GtkCanvas::focus (Item* item)
1049 _focused_item = item;
1053 GtkCanvas::unfocus (Item* item)
1055 if (item == _focused_item) {
1060 /** @return The visible area of the canvas, in window coordinates */
1062 GtkCanvas::visible_area () const
1064 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1068 GtkCanvas::width() const
1070 return get_allocation().get_width();
1074 GtkCanvas::height() const
1076 return get_allocation().get_height();
1080 GtkCanvas::start_tooltip_timeout (Item* item)
1082 stop_tooltip_timeout ();
1085 current_tooltip_item = item;
1087 /* wait for the first idle that happens after this is
1088 called. this means that we've stopped processing events, which
1089 in turn implies that the user has stopped doing stuff for a
1093 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1098 GtkCanvas::really_start_tooltip_timeout ()
1100 /* an idle has occured since we entered a tooltip-bearing widget. Now
1101 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1104 if (current_tooltip_item) {
1105 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1108 return false; /* this is called from an idle callback, don't call it again */
1112 GtkCanvas::stop_tooltip_timeout ()
1114 current_tooltip_item = 0;
1115 tooltip_timeout_connection.disconnect ();
1119 GtkCanvas::show_tooltip ()
1121 Rect tooltip_item_bbox;
1123 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1127 if (!tooltip_window) {
1128 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1129 tooltip_label = manage (new Gtk::Label);
1130 tooltip_label->show ();
1131 tooltip_window->add (*tooltip_label);
1132 tooltip_window->set_border_width (6);
1133 tooltip_window->set_name ("tooltip");
1136 tooltip_label->set_text (current_tooltip_item->tooltip());
1138 /* figure out where to position the tooltip */
1140 Gtk::Widget* toplevel = get_toplevel();
1142 int pointer_x, pointer_y;
1143 Gdk::ModifierType mask;
1145 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1147 Duple tooltip_window_origin (pointer_x, pointer_y);
1149 /* convert to root window coordinates */
1152 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1154 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1156 /* we don't want the pointer to be inside the window when it is
1157 * displayed, because then we generate a leave/enter event pair when
1158 * the window is displayed then hidden - the enter event will
1159 * trigger a new tooltip timeout.
1161 * So move the window right of the pointer position by just a enough
1162 * to get it away from the pointer.
1165 tooltip_window_origin.x += 20;
1167 /* move the tooltip window into position */
1169 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1173 tooltip_window->present ();
1175 /* called from a timeout handler, don't call it again */
1181 GtkCanvas::hide_tooltip ()
1183 /* hide it if its there */
1185 if (tooltip_window) {
1186 tooltip_window->hide ();
1190 /** Create a GtkCanvaSViewport.
1191 * @param hadj Adjustment to use for horizontal scrolling.
1192 * @param vadj Adjustment to use for vertica scrolling.
1194 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1195 : Alignment (0, 0, 1.0, 1.0)
1196 , hadjustment (hadj)
1197 , vadjustment (vadj)
1201 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1202 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1206 GtkCanvasViewport::scrolled ()
1208 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1212 /** Handler for when GTK asks us what minimum size we want.
1213 * @param req Requsition to fill in.
1216 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1218 /* force the canvas to size itself */
1219 // _canvas.root()->bounding_box();