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 ScrollGroup* best_group = 0;
236 /* if the coordinates are negative, clamp to zero and find the item
237 * that covers that "edge" position.
242 if (in_window.x < 0) {
245 if (in_window.y < 0) {
249 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
251 if ((*s)->covers_window (in_window)) {
254 /* XXX January 22nd 2015: leaving this in place for now
255 * but I think it fixes a bug that really should be
256 * fixed in a different way (and will be) by my next
257 * commit. But it may still be relevant.
260 /* If scroll groups overlap, choose the one with the highest sensitivity,
261 that is, choose an HV scroll group over an H or V
264 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
266 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
267 /* Can't do any better than this. */
275 return d.translate (best_group->scroll_offset());
282 Canvas::canvas_to_window (Duple const & d, bool rounded) const
284 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
285 * as children of the root group, so we just scan its first level
286 * children and see what we can find.
289 std::list<Item*> const& root_children (_root.items());
293 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
294 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
300 wd = d.translate (-sg->scroll_offset());
305 /* Note that this intentionally almost always returns integer coordinates */
315 /** Called when an item has moved.
316 * @param item Item that has moved.
317 * @param pre_change_parent_bounding_box The bounding box of the item before
318 * the move, in its parent's coordinates.
321 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
323 if (pre_change_parent_bounding_box) {
324 /* request a redraw of where the item used to be. The box has
325 * to be in parent coordinate space since the bounding box of
326 * an item does not change when moved. If we use
327 * item->item_to_canvas() on the old bounding box, we will be
329 * using the item's new position, and so will compute the wrong
330 * invalidation area. If we use the parent (which has not
331 * moved, then this will work.
333 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
336 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
337 if (post_change_bounding_box) {
338 /* request a redraw of where the item now is */
339 queue_draw_item_area (item, post_change_bounding_box.get ());
343 /** Request a redraw of a particular area in an item's coordinates.
345 * @param area Area to redraw in the item's coordinates.
348 Canvas::queue_draw_item_area (Item* item, Rect area)
350 request_redraw (item->item_to_window (area));
354 Canvas::set_tooltip_timeout (uint32_t msecs)
356 tooltip_timeout_msecs = msecs;
360 Canvas::set_background_color (Color c)
364 boost::optional<Rect> r = _root.bounding_box();
367 request_redraw (_root.item_to_window (r.get()));
372 GtkCanvas::re_enter ()
374 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
376 pick_current_item (0);
379 /** Construct a GtkCanvas */
380 GtkCanvas::GtkCanvas ()
382 , _new_current_item (0)
385 , _single_exposure (1)
386 , current_tooltip_item (0)
389 /* these are the events we want to know about */
390 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
391 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
392 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
396 GtkCanvas::pick_current_item (int state)
401 /* this version of ::pick_current_item() is called after an item is
402 * added or removed, so we have no coordinates to work from as is the
403 * case with a motion event. Find out where the mouse is and use that.
406 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
408 if (pointer_window != get_window()) {
412 pick_current_item (Duple (x, y), state);
415 /** Given @param point (a position in window coordinates)
416 * and mouse state @param state, check to see if _current_item
417 * (which will be used to deliver events) should change.
420 GtkCanvas::pick_current_item (Duple const & point, int state)
422 /* we do not enter/leave items during a drag/grab */
428 /* find the items at the given window position */
430 vector<Item const *> items;
431 _root.add_items_at_point (point, items);
433 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
436 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
437 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
439 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
441 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
447 /* put all items at point that are event-sensitive and visible and NOT
448 groups into within_items. Note that items is sorted from bottom to
449 top, but we're going to reverse that for within_items so that its
450 first item is the upper-most item that can be chosen as _current_item.
453 vector<Item const *>::const_iterator i;
454 list<Item const *> within_items;
456 for (i = items.begin(); i != items.end(); ++i) {
458 Item const * possible_item = *i;
460 /* We ignore invisible items, containers and items that ignore events */
462 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
465 within_items.push_front (possible_item);
468 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
470 if (within_items.empty()) {
472 /* no items at point, just send leave event below */
473 _new_current_item = 0;
477 if (within_items.front() == _current_item) {
478 /* uppermost item at point is already _current_item */
479 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
483 _new_current_item = const_cast<Item*> (within_items.front());
486 if (_new_current_item != _current_item) {
487 deliver_enter_leave (point, state);
491 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
493 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
498 /** Deliver a series of enter & leave events based on the pointer position being at window
499 * coordinate @param point, and pointer @param state (modifier keys, etc)
502 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
504 /* setup enter & leave event structures */
506 Glib::RefPtr<Gdk::Window> win = get_window();
512 GdkEventCrossing enter_event;
513 enter_event.type = GDK_ENTER_NOTIFY;
514 enter_event.window = win->gobj();
515 enter_event.send_event = 0;
516 enter_event.subwindow = 0;
517 enter_event.mode = GDK_CROSSING_NORMAL;
518 enter_event.focus = FALSE;
519 enter_event.state = state;
521 /* Events delivered to canvas items are expected to be in canvas
522 * coordinates but @param point is in window coordinates.
525 Duple c = window_to_canvas (point);
529 GdkEventCrossing leave_event = enter_event;
530 leave_event.type = GDK_LEAVE_NOTIFY;
533 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
534 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
535 vector<Item*> items_to_leave_virtual;
536 vector<Item*> items_to_enter_virtual;
538 if (_new_current_item == 0) {
540 leave_detail = GDK_NOTIFY_UNKNOWN;
544 /* no current item, so also send virtual leave events to the
545 * entire heirarchy for the current item
548 for (i = _current_item->parent(); i ; i = i->parent()) {
549 items_to_leave_virtual.push_back (i);
553 } else if (_current_item == 0) {
555 enter_detail = GDK_NOTIFY_UNKNOWN;
557 /* no current item, so also send virtual enter events to the
558 * entire heirarchy for the new item
561 for (i = _new_current_item->parent(); i ; i = i->parent()) {
562 items_to_enter_virtual.push_back (i);
565 } else if (_current_item->is_descendant_of (*_new_current_item)) {
567 /* move from descendant to ancestor (X: "_current_item is an
568 * inferior ("child") of _new_current_item")
570 * Deliver "virtual" leave notifications to all items in the
571 * heirarchy between current and new_current.
574 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
575 items_to_leave_virtual.push_back (i);
578 enter_detail = GDK_NOTIFY_INFERIOR;
579 leave_detail = GDK_NOTIFY_ANCESTOR;
581 } else if (_new_current_item->is_descendant_of (*_current_item)) {
582 /* move from ancestor to descendant (X: "_new_current_item is
583 * an inferior ("child") of _current_item")
585 * Deliver "virtual" enter notifications to all items in the
586 * heirarchy between current and new_current.
589 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
590 items_to_enter_virtual.push_back (i);
593 enter_detail = GDK_NOTIFY_ANCESTOR;
594 leave_detail = GDK_NOTIFY_INFERIOR;
598 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
600 /* deliver virtual leave events to everything between _current
601 * and common_ancestor.
604 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
605 items_to_leave_virtual.push_back (i);
608 /* deliver virtual enter events to everything between
609 * _new_current and common_ancestor.
612 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
613 items_to_enter_virtual.push_back (i);
616 enter_detail = GDK_NOTIFY_NONLINEAR;
617 leave_detail = GDK_NOTIFY_NONLINEAR;
621 if (_current_item && !_current_item->ignore_events ()) {
622 leave_event.detail = leave_detail;
623 _current_item->Event ((GdkEvent*)&leave_event);
624 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
627 leave_event.detail = GDK_NOTIFY_VIRTUAL;
628 enter_event.detail = GDK_NOTIFY_VIRTUAL;
630 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
631 if (!(*it)->ignore_events()) {
632 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
633 (*it)->Event ((GdkEvent*)&leave_event);
637 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
638 if (!(*it)->ignore_events()) {
639 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
640 (*it)->Event ((GdkEvent*)&enter_event);
641 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
645 if (_new_current_item && !_new_current_item->ignore_events()) {
646 enter_event.detail = enter_detail;
647 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
648 start_tooltip_timeout (_new_current_item);
649 _new_current_item->Event ((GdkEvent*)&enter_event);
652 _current_item = _new_current_item;
656 /** Deliver an event to the appropriate item; either the grabbed item, or
657 * one of the items underneath the event.
658 * @param point Position that the event has occurred at, in canvas coordinates.
659 * @param event The event.
662 GtkCanvas::deliver_event (GdkEvent* event)
664 /* Point in in canvas coordinate space */
666 const Item* event_item;
669 /* we have a grabbed item, so everything gets sent there */
670 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
671 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
672 event_item = _grabbed_item;
674 event_item = _current_item;
681 /* run through the items from child to parent, until one claims the event */
683 Item* item = const_cast<Item*> (event_item);
687 Item* parent = item->parent ();
689 if (!item->ignore_events () &&
690 item->Event (event)) {
691 /* this item has just handled the event */
693 PBD::DEBUG::CanvasEvents,
694 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
700 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)));
702 if ((item = parent) == 0) {
711 /** Called when an item is being destroyed.
712 * @param item Item being destroyed.
713 * @param bounding_box Last known bounding box of the item.
716 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
719 queue_draw_item_area (item, bounding_box.get ());
722 if (_new_current_item == item) {
723 _new_current_item = 0;
726 if (_grabbed_item == item) {
730 if (_focused_item == item) {
734 if (current_tooltip_item) {
735 current_tooltip_item = 0;
736 stop_tooltip_timeout ();
739 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
741 scrollers.remove (sg);
744 if (_current_item == item) {
745 /* no need to send a leave event to this item, since it is going away
748 pick_current_item (0); // no mouse state
754 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
756 EventBox::on_size_allocate (a);
757 #ifdef USE_CAIRO_IMAGE_SURFACE
758 /* allocate an image surface as large as the canvas itself */
760 canvas_image.clear ();
761 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
765 /** Handler for GDK expose events.
767 * @return true if the event was handled.
770 GtkCanvas::on_expose_event (GdkEventExpose* ev)
772 #ifdef USE_CAIRO_IMAGE_SURFACE
774 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
776 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
777 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
779 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
782 /* draw background color */
784 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
785 draw_context->clip_preserve ();
786 set_source_rgba (draw_context, _bg_color);
787 draw_context->fill ();
790 if ( _single_exposure ) {
792 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
798 gdk_region_get_rectangles (ev->region, &rects, &nrects);
799 for (gint n = 0; n < nrects; ++n) {
800 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
801 render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
806 #ifdef USE_CAIRO_IMAGE_SURFACE
807 /* now blit our private surface back to the GDK one */
809 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
810 window_context->clip ();
811 window_context->set_source (canvas_image, 0, 0);
812 window_context->set_operator (Cairo::OPERATOR_SOURCE);
813 window_context->paint ();
819 /** Handler for GDK scroll events.
821 * @return true if the event was handled.
824 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
826 /* translate event coordinates from window to canvas */
828 GdkEvent copy = *((GdkEvent*)ev);
829 Duple winpos = Duple (ev->x, ev->y);
830 Duple where = window_to_canvas (winpos);
832 pick_current_item (winpos, ev->state);
834 copy.button.x = where.x;
835 copy.button.y = where.y;
837 /* Coordinates in the event will be canvas coordinates, correctly adjusted
838 for scroll if this GtkCanvas is in a GtkCanvasViewport.
841 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
842 return deliver_event (reinterpret_cast<GdkEvent*>(©));
845 /** Handler for GDK key press events.
847 * @return true if the event was handled.
850 GtkCanvas::on_key_press_event (GdkEventKey* ev)
852 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
853 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
856 /** Handler for GDK key release events.
858 * @return true if the event was handled.
861 GtkCanvas::on_key_release_event (GdkEventKey* ev)
863 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
864 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
867 /** Handler for GDK button press events.
869 * @return true if the event was handled.
872 GtkCanvas::on_button_press_event (GdkEventButton* ev)
874 /* translate event coordinates from window to canvas */
876 GdkEvent copy = *((GdkEvent*)ev);
877 Duple winpos = Duple (ev->x, ev->y);
878 Duple where = window_to_canvas (winpos);
880 pick_current_item (winpos, ev->state);
882 copy.button.x = where.x;
883 copy.button.y = where.y;
885 /* Coordinates in the event will be canvas coordinates, correctly adjusted
886 for scroll if this GtkCanvas is in a GtkCanvasViewport.
889 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
890 return deliver_event (reinterpret_cast<GdkEvent*>(©));
893 /** Handler for GDK button release events.
895 * @return true if the event was handled.
898 GtkCanvas::on_button_release_event (GdkEventButton* ev)
900 /* translate event coordinates from window to canvas */
902 GdkEvent copy = *((GdkEvent*)ev);
903 Duple winpos = Duple (ev->x, ev->y);
904 Duple where = window_to_canvas (winpos);
906 pick_current_item (winpos, ev->state);
908 copy.button.x = where.x;
909 copy.button.y = where.y;
911 /* Coordinates in the event will be canvas coordinates, correctly adjusted
912 for scroll if this GtkCanvas is in a GtkCanvasViewport.
915 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
916 return deliver_event (reinterpret_cast<GdkEvent*>(©));
920 GtkCanvas::get_mouse_position (Duple& winpos) const
924 Gdk::ModifierType mask;
925 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
928 std::cerr << " no self window\n";
929 winpos = Duple (0, 0);
933 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
941 /** Handler for GDK motion events.
943 * @return true if the event was handled.
946 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
950 /* translate event coordinates from window to canvas */
952 GdkEvent copy = *((GdkEvent*)ev);
953 Duple point (ev->x, ev->y);
954 Duple where = window_to_canvas (point);
956 copy.motion.x = where.x;
957 copy.motion.y = where.y;
959 /* Coordinates in "copy" will be canvas coordinates,
962 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));
964 MouseMotion (point); /* EMIT SIGNAL */
966 pick_current_item (point, ev->state);
968 /* Now deliver the motion event. It may seem a little inefficient
969 to recompute the items under the event, but the enter notify/leave
970 events may have deleted canvas items so it is important to
971 recompute the list in deliver_event.
974 return deliver_event (reinterpret_cast<GdkEvent*> (©));
978 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
980 pick_current_item (Duple (ev->x, ev->y), ev->state);
985 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
987 switch (ev->detail) {
988 case GDK_NOTIFY_ANCESTOR:
989 case GDK_NOTIFY_UNKNOWN:
990 case GDK_NOTIFY_VIRTUAL:
991 case GDK_NOTIFY_NONLINEAR:
992 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
993 /* leaving window, cancel any tooltips */
994 stop_tooltip_timeout ();
998 /* we don't care about any other kind
999 of leave event (notably GDK_NOTIFY_INFERIOR)
1003 _new_current_item = 0;
1004 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1008 /** Called to request a redraw of our canvas.
1009 * @param area Area to redraw, in window coordinates.
1012 GtkCanvas::request_redraw (Rect const & request)
1016 Coord const w = width ();
1017 Coord const h = height ();
1019 /* clamp area requested to actual visible window */
1021 real_area.x0 = max (0.0, min (w, request.x0));
1022 real_area.x1 = max (0.0, min (w, request.x1));
1023 real_area.y0 = max (0.0, min (h, request.y0));
1024 real_area.y1 = max (0.0, min (h, request.y1));
1026 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1029 /** Called to request that we try to get a particular size for ourselves.
1030 * @param size Size to request, in pixels.
1033 GtkCanvas::request_size (Duple size)
1037 if (req.x > INT_MAX) {
1041 if (req.y > INT_MAX) {
1045 set_size_request (req.x, req.y);
1048 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1049 * This is typically used for dragging items around, so that they are grabbed during
1051 * @param item Item to grab.
1054 GtkCanvas::grab (Item* item)
1056 /* XXX: should this be doing gdk_pointer_grab? */
1057 _grabbed_item = item;
1061 /** `Ungrab' any item that was previously grabbed */
1063 GtkCanvas::ungrab ()
1065 /* XXX: should this be doing gdk_pointer_ungrab? */
1069 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1071 * @param item Item to grab.
1074 GtkCanvas::focus (Item* item)
1076 _focused_item = item;
1080 GtkCanvas::unfocus (Item* item)
1082 if (item == _focused_item) {
1087 /** @return The visible area of the canvas, in window coordinates */
1089 GtkCanvas::visible_area () const
1091 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1095 GtkCanvas::width() const
1097 return get_allocation().get_width();
1101 GtkCanvas::height() const
1103 return get_allocation().get_height();
1107 GtkCanvas::start_tooltip_timeout (Item* item)
1109 stop_tooltip_timeout ();
1112 current_tooltip_item = item;
1114 /* wait for the first idle that happens after this is
1115 called. this means that we've stopped processing events, which
1116 in turn implies that the user has stopped doing stuff for a
1120 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1125 GtkCanvas::really_start_tooltip_timeout ()
1127 /* an idle has occured since we entered a tooltip-bearing widget. Now
1128 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1131 if (current_tooltip_item) {
1132 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1135 return false; /* this is called from an idle callback, don't call it again */
1139 GtkCanvas::stop_tooltip_timeout ()
1141 current_tooltip_item = 0;
1142 tooltip_timeout_connection.disconnect ();
1146 GtkCanvas::show_tooltip ()
1148 Rect tooltip_item_bbox;
1150 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1154 if (!tooltip_window) {
1155 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1156 tooltip_label = manage (new Gtk::Label);
1157 tooltip_label->show ();
1158 tooltip_window->add (*tooltip_label);
1159 tooltip_window->set_border_width (6);
1160 tooltip_window->set_name ("tooltip");
1163 tooltip_label->set_text (current_tooltip_item->tooltip());
1165 /* figure out where to position the tooltip */
1167 Gtk::Widget* toplevel = get_toplevel();
1169 int pointer_x, pointer_y;
1170 Gdk::ModifierType mask;
1172 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1174 Duple tooltip_window_origin (pointer_x, pointer_y);
1176 /* convert to root window coordinates */
1179 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1181 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1183 /* we don't want the pointer to be inside the window when it is
1184 * displayed, because then we generate a leave/enter event pair when
1185 * the window is displayed then hidden - the enter event will
1186 * trigger a new tooltip timeout.
1188 * So move the window right of the pointer position by just a enough
1189 * to get it away from the pointer.
1192 tooltip_window_origin.x += 20;
1194 /* move the tooltip window into position */
1196 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1200 tooltip_window->present ();
1202 /* called from a timeout handler, don't call it again */
1208 GtkCanvas::hide_tooltip ()
1210 /* hide it if its there */
1212 if (tooltip_window) {
1213 tooltip_window->hide ();
1215 // Delete the tooltip window so it'll get re-created
1216 // (i.e. properly re-sized) on the next usage.
1217 delete tooltip_window;
1218 tooltip_window = NULL;
1222 /** Create a GtkCanvaSViewport.
1223 * @param hadj Adjustment to use for horizontal scrolling.
1224 * @param vadj Adjustment to use for vertica scrolling.
1226 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1227 : Alignment (0, 0, 1.0, 1.0)
1228 , hadjustment (hadj)
1229 , vadjustment (vadj)
1233 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1234 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1238 GtkCanvasViewport::scrolled ()
1240 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1244 /** Handler for when GTK asks us what minimum size we want.
1245 * @param req Requsition to fill in.
1248 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1250 /* force the canvas to size itself */
1251 // _canvas.root()->bounding_box();