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 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
22 #define OPTIONAL_CAIRO_IMAGE_SURFACE
25 /** @file canvas/canvas.cc
26 * @brief Implementation of the main canvas classes.
31 #include <gtkmm/adjustment.h>
32 #include <gtkmm/label.h>
33 #include <gtkmm/window.h>
35 #include "gtkmm2ext/persistent_tooltip.h"
37 #include "pbd/compose.h"
38 #include "pbd/stacktrace.h"
40 #include "canvas/canvas.h"
41 #include "canvas/colors.h"
42 #include "canvas/debug.h"
43 #include "canvas/line.h"
44 #include "canvas/scroll_group.h"
45 #include "canvas/utils.h"
48 using namespace ArdourCanvas;
50 uint32_t Canvas::tooltip_timeout_msecs = 750;
52 /** Construct a new Canvas */
55 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
61 Canvas::scroll_to (Coord x, Coord y)
63 /* We do things this way because we do not want to recurse through
64 the canvas for every scroll. In the presence of large MIDI
65 tracks this means traversing item lists that include
66 thousands of items (notes).
68 This design limits us to moving only those items (groups, typically)
69 that should move in certain ways as we scroll. In other terms, it
70 becomes O(1) rather than O(N).
73 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
74 (*i)->scroll_to (Duple (x, y));
77 pick_current_item (0); // no current mouse position
81 Canvas::add_scroller (ScrollGroup& i)
83 scrollers.push_back (&i);
89 pick_current_item (0); // no current mouse position
92 /** Render an area of the canvas.
93 * @param area Area in window coordinates.
94 * @param context Cairo context to render to.
97 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
100 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
101 cerr << this << " RENDER: " << area << endl;
102 //cerr << "CANVAS @ " << this << endl;
104 //cerr << "-------------------------\n";
110 Rect root_bbox = _root.bounding_box();
112 /* the root has no bounding box, so there's nothing to render */
116 Rect draw = root_bbox.intersection (area);
119 /* there's a common area between the root and the requested
123 _root.render (draw, context);
125 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
126 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
127 // This transparently colors the rect being rendered, after it has been drawn.
128 double r = (random() % 65536) /65536.0;
129 double g = (random() % 65536) /65536.0;
130 double b = (random() % 65536) /65536.0;
131 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
132 context->set_source_rgba (r, g, b, 0.25);
141 operator<< (ostream& o, Canvas& c)
148 Canvas::indent() const
152 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
160 Canvas::render_indent() const
164 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
172 Canvas::dump (ostream& o) const
178 /** Called when an item has been shown or hidden.
179 * @param item Item that has been shown or hidden.
182 Canvas::item_shown_or_hidden (Item* item)
184 Rect bbox = item->bounding_box ();
186 if (item->item_to_window (bbox).intersection (visible_area ())) {
187 queue_draw_item_area (item, bbox);
192 /** Called when an item has a change to its visual properties
193 * that do NOT affect its bounding box.
194 * @param item Item that has been modified.
197 Canvas::item_visual_property_changed (Item* item)
199 Rect bbox = item->bounding_box ();
201 if (item->item_to_window (bbox.intersection (visible_area ()))) {
202 queue_draw_item_area (item, bbox);
207 /** Called when an item has changed, but not moved.
208 * @param item Item that has changed.
209 * @param pre_change_bounding_box The bounding box of item before the change,
210 * in the item's coordinates.
213 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
215 Rect window_bbox = visible_area ();
217 if (pre_change_bounding_box) {
218 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
219 /* request a redraw of the item's old bounding box */
220 queue_draw_item_area (item, pre_change_bounding_box);
224 Rect post_change_bounding_box = item->bounding_box ();
226 if (post_change_bounding_box) {
227 if (item->item_to_window (post_change_bounding_box).intersection (window_bbox)) {
228 /* request a redraw of the item's new bounding box */
229 queue_draw_item_area (item, post_change_bounding_box);
235 Canvas::window_to_canvas (Duple const & d) const
237 ScrollGroup* best_group = 0;
240 /* if the coordinates are negative, clamp to zero and find the item
241 * that covers that "edge" position.
246 if (in_window.x < 0) {
249 if (in_window.y < 0) {
253 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
255 if ((*s)->covers_window (in_window)) {
258 /* XXX January 22nd 2015: leaving this in place for now
259 * but I think it fixes a bug that really should be
260 * fixed in a different way (and will be) by my next
261 * commit. But it may still be relevant.
264 /* If scroll groups overlap, choose the one with the highest sensitivity,
265 that is, choose an HV scroll group over an H or V
268 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
270 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
271 /* Can't do any better than this. */
279 return d.translate (best_group->scroll_offset());
286 Canvas::canvas_to_window (Duple const & d, bool rounded) const
288 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
289 * as children of the root group, so we just scan its first level
290 * children and see what we can find.
293 std::list<Item*> const& root_children (_root.items());
297 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
298 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
304 wd = d.translate (-sg->scroll_offset());
309 /* Note that this intentionally almost always returns integer coordinates */
319 /** Called when an item has moved.
320 * @param item Item that has moved.
321 * @param pre_change_parent_bounding_box The bounding box of the item before
322 * the move, in its parent's coordinates.
325 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
327 if (pre_change_parent_bounding_box) {
328 /* request a redraw of where the item used to be. The box has
329 * to be in parent coordinate space since the bounding box of
330 * an item does not change when moved. If we use
331 * item->item_to_canvas() on the old bounding box, we will be
333 * using the item's new position, and so will compute the wrong
334 * invalidation area. If we use the parent (which has not
335 * moved, then this will work.
337 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
340 Rect post_change_bounding_box = item->bounding_box ();
341 if (post_change_bounding_box) {
342 /* request a redraw of where the item now is */
343 queue_draw_item_area (item, post_change_bounding_box);
347 /** Request a redraw of a particular area in an item's coordinates.
349 * @param area Area to redraw in the item's coordinates.
352 Canvas::queue_draw_item_area (Item* item, Rect area)
354 request_redraw (item->item_to_window (area));
358 Canvas::set_tooltip_timeout (uint32_t msecs)
360 tooltip_timeout_msecs = msecs;
364 Canvas::set_background_color (Color c)
368 Rect r = _root.bounding_box();
371 request_redraw (_root.item_to_window (r));
376 GtkCanvas::re_enter ()
378 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
380 pick_current_item (0);
383 /** Construct a GtkCanvas */
384 GtkCanvas::GtkCanvas ()
386 , _new_current_item (0)
389 , _single_exposure (1)
390 , current_tooltip_item (0)
394 /* these are the events we want to know about */
395 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
396 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
397 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
401 GtkCanvas::pick_current_item (int state)
406 /* this version of ::pick_current_item() is called after an item is
407 * added or removed, so we have no coordinates to work from as is the
408 * case with a motion event. Find out where the mouse is and use that.
411 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
413 if (pointer_window != get_window()) {
417 pick_current_item (Duple (x, y), state);
420 /** Given @param point (a position in window coordinates)
421 * and mouse state @param state, check to see if _current_item
422 * (which will be used to deliver events) should change.
425 GtkCanvas::pick_current_item (Duple const & point, int state)
427 /* we do not enter/leave items during a drag/grab */
433 /* find the items at the given window position */
435 vector<Item const *> items;
436 _root.add_items_at_point (point, items);
438 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
441 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
442 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
444 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
446 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
452 /* put all items at point that are event-sensitive and visible and NOT
453 groups into within_items. Note that items is sorted from bottom to
454 top, but we're going to reverse that for within_items so that its
455 first item is the upper-most item that can be chosen as _current_item.
458 vector<Item const *>::const_iterator i;
459 list<Item const *> within_items;
461 for (i = items.begin(); i != items.end(); ++i) {
463 Item const * possible_item = *i;
465 /* We ignore invisible items, containers and items that ignore events */
467 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
470 within_items.push_front (possible_item);
473 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
475 if (within_items.empty()) {
477 /* no items at point, just send leave event below */
478 _new_current_item = 0;
482 if (within_items.front() == _current_item) {
483 /* uppermost item at point is already _current_item */
484 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
488 _new_current_item = const_cast<Item*> (within_items.front());
491 if (_new_current_item != _current_item) {
492 deliver_enter_leave (point, state);
496 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
498 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
503 /** Deliver a series of enter & leave events based on the pointer position being at window
504 * coordinate @param point, and pointer @param state (modifier keys, etc)
507 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
509 /* setup enter & leave event structures */
511 Glib::RefPtr<Gdk::Window> win = get_window();
517 GdkEventCrossing enter_event;
518 enter_event.type = GDK_ENTER_NOTIFY;
519 enter_event.window = win->gobj();
520 enter_event.send_event = 0;
521 enter_event.subwindow = 0;
522 enter_event.mode = GDK_CROSSING_NORMAL;
523 enter_event.focus = FALSE;
524 enter_event.state = state;
526 /* Events delivered to canvas items are expected to be in canvas
527 * coordinates but @param point is in window coordinates.
530 Duple c = window_to_canvas (point);
534 GdkEventCrossing leave_event = enter_event;
535 leave_event.type = GDK_LEAVE_NOTIFY;
538 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
539 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
540 vector<Item*> items_to_leave_virtual;
541 vector<Item*> items_to_enter_virtual;
543 if (_new_current_item == 0) {
545 leave_detail = GDK_NOTIFY_UNKNOWN;
549 /* no current item, so also send virtual leave events to the
550 * entire heirarchy for the current item
553 for (i = _current_item->parent(); i ; i = i->parent()) {
554 items_to_leave_virtual.push_back (i);
558 } else if (_current_item == 0) {
560 enter_detail = GDK_NOTIFY_UNKNOWN;
562 /* no current item, so also send virtual enter events to the
563 * entire heirarchy for the new item
566 for (i = _new_current_item->parent(); i ; i = i->parent()) {
567 items_to_enter_virtual.push_back (i);
570 } else if (_current_item->is_descendant_of (*_new_current_item)) {
572 /* move from descendant to ancestor (X: "_current_item is an
573 * inferior ("child") of _new_current_item")
575 * Deliver "virtual" leave notifications to all items in the
576 * heirarchy between current and new_current.
579 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
580 items_to_leave_virtual.push_back (i);
583 enter_detail = GDK_NOTIFY_INFERIOR;
584 leave_detail = GDK_NOTIFY_ANCESTOR;
586 } else if (_new_current_item->is_descendant_of (*_current_item)) {
587 /* move from ancestor to descendant (X: "_new_current_item is
588 * an inferior ("child") of _current_item")
590 * Deliver "virtual" enter notifications to all items in the
591 * heirarchy between current and new_current.
594 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
595 items_to_enter_virtual.push_back (i);
598 enter_detail = GDK_NOTIFY_ANCESTOR;
599 leave_detail = GDK_NOTIFY_INFERIOR;
603 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
605 /* deliver virtual leave events to everything between _current
606 * and common_ancestor.
609 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
610 items_to_leave_virtual.push_back (i);
613 /* deliver virtual enter events to everything between
614 * _new_current and common_ancestor.
617 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
618 items_to_enter_virtual.push_back (i);
621 enter_detail = GDK_NOTIFY_NONLINEAR;
622 leave_detail = GDK_NOTIFY_NONLINEAR;
626 if (_current_item && !_current_item->ignore_events ()) {
627 leave_event.detail = leave_detail;
628 _current_item->Event ((GdkEvent*)&leave_event);
629 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
632 leave_event.detail = GDK_NOTIFY_VIRTUAL;
633 enter_event.detail = GDK_NOTIFY_VIRTUAL;
635 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
636 if (!(*it)->ignore_events()) {
637 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
638 (*it)->Event ((GdkEvent*)&leave_event);
642 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
643 if (!(*it)->ignore_events()) {
644 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
645 (*it)->Event ((GdkEvent*)&enter_event);
646 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
650 if (_new_current_item && !_new_current_item->ignore_events()) {
651 enter_event.detail = enter_detail;
652 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
653 start_tooltip_timeout (_new_current_item);
654 _new_current_item->Event ((GdkEvent*)&enter_event);
657 _current_item = _new_current_item;
661 /** Deliver an event to the appropriate item; either the grabbed item, or
662 * one of the items underneath the event.
663 * @param point Position that the event has occurred at, in canvas coordinates.
664 * @param event The event.
667 GtkCanvas::deliver_event (GdkEvent* event)
669 /* Point in in canvas coordinate space */
671 const Item* event_item;
674 /* we have a grabbed item, so everything gets sent there */
675 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
676 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
677 event_item = _grabbed_item;
679 event_item = _current_item;
686 /* run through the items from child to parent, until one claims the event */
688 Item* item = const_cast<Item*> (event_item);
692 Item* parent = item->parent ();
694 if (!item->ignore_events () &&
695 item->Event (event)) {
696 /* this item has just handled the event */
698 PBD::DEBUG::CanvasEvents,
699 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
705 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)));
707 if ((item = parent) == 0) {
716 /** Called when an item is being destroyed.
717 * @param item Item being destroyed.
718 * @param bounding_box Last known bounding box of the item.
721 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
724 queue_draw_item_area (item, bounding_box);
727 if (_new_current_item == item) {
728 _new_current_item = 0;
731 if (_grabbed_item == item) {
735 if (_focused_item == item) {
739 if (current_tooltip_item) {
740 current_tooltip_item = 0;
741 stop_tooltip_timeout ();
744 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
746 scrollers.remove (sg);
749 if (_current_item == item) {
750 /* no need to send a leave event to this item, since it is going away
753 pick_current_item (0); // no mouse state
759 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
761 EventBox::on_size_allocate (a);
762 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
763 if (getenv("ARDOUR_IMAGE_SURFACE")) {
765 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
766 /* allocate an image surface as large as the canvas itself */
768 canvas_image.clear ();
769 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
771 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
776 /** Handler for GDK expose events.
778 * @return true if the event was handled.
781 GtkCanvas::on_expose_event (GdkEventExpose* ev)
787 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
788 Cairo::RefPtr<Cairo::Context> draw_context;
789 Cairo::RefPtr<Cairo::Context> window_context;
790 if (getenv("ARDOUR_IMAGE_SURFACE")) {
792 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
794 draw_context = Cairo::Context::create (canvas_image);
795 window_context = get_window()->create_cairo_context ();
797 draw_context = get_window()->create_cairo_context ();
799 #elif defined USE_CAIRO_IMAGE_SURFACE
801 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
803 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
804 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
806 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
809 /* draw background color */
811 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
812 draw_context->clip_preserve ();
813 set_source_rgba (draw_context, _bg_color);
814 draw_context->fill ();
817 if ( _single_exposure ) {
819 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
825 gdk_region_get_rectangles (ev->region, &rects, &nrects);
826 for (gint n = 0; n < nrects; ++n) {
827 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
828 render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
833 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
834 if (getenv("ARDOUR_IMAGE_SURFACE")) {
836 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
837 /* now blit our private surface back to the GDK one */
839 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
840 window_context->clip ();
841 window_context->set_source (canvas_image, 0, 0);
842 window_context->set_operator (Cairo::OPERATOR_SOURCE);
843 window_context->paint ();
845 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
852 /** Handler for GDK scroll events.
854 * @return true if the event was handled.
857 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
859 /* translate event coordinates from window to canvas */
861 GdkEvent copy = *((GdkEvent*)ev);
862 Duple winpos = Duple (ev->x, ev->y);
863 Duple where = window_to_canvas (winpos);
865 pick_current_item (winpos, ev->state);
867 copy.button.x = where.x;
868 copy.button.y = where.y;
870 /* Coordinates in the event will be canvas coordinates, correctly adjusted
871 for scroll if this GtkCanvas is in a GtkCanvasViewport.
874 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
875 return deliver_event (reinterpret_cast<GdkEvent*>(©));
878 /** Handler for GDK key press events.
880 * @return true if the event was handled.
883 GtkCanvas::on_key_press_event (GdkEventKey* ev)
885 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
886 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
889 /** Handler for GDK key release events.
891 * @return true if the event was handled.
894 GtkCanvas::on_key_release_event (GdkEventKey* ev)
896 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
897 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
900 /** Handler for GDK button press events.
902 * @return true if the event was handled.
905 GtkCanvas::on_button_press_event (GdkEventButton* ev)
907 /* translate event coordinates from window to canvas */
909 GdkEvent copy = *((GdkEvent*)ev);
910 Duple winpos = Duple (ev->x, ev->y);
911 Duple where = window_to_canvas (winpos);
913 pick_current_item (winpos, ev->state);
915 copy.button.x = where.x;
916 copy.button.y = where.y;
918 /* Coordinates in the event will be canvas coordinates, correctly adjusted
919 for scroll if this GtkCanvas is in a GtkCanvasViewport.
922 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
923 return deliver_event (reinterpret_cast<GdkEvent*>(©));
926 /** Handler for GDK button release events.
928 * @return true if the event was handled.
931 GtkCanvas::on_button_release_event (GdkEventButton* ev)
933 /* translate event coordinates from window to canvas */
935 GdkEvent copy = *((GdkEvent*)ev);
936 Duple winpos = Duple (ev->x, ev->y);
937 Duple where = window_to_canvas (winpos);
939 pick_current_item (winpos, ev->state);
941 copy.button.x = where.x;
942 copy.button.y = where.y;
944 /* Coordinates in the event will be canvas coordinates, correctly adjusted
945 for scroll if this GtkCanvas is in a GtkCanvasViewport.
948 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
949 return deliver_event (reinterpret_cast<GdkEvent*>(©));
953 GtkCanvas::get_mouse_position (Duple& winpos) const
957 Gdk::ModifierType mask;
958 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
961 std::cerr << " no self window\n";
962 winpos = Duple (0, 0);
966 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
974 /** Handler for GDK motion events.
976 * @return true if the event was handled.
979 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
983 /* translate event coordinates from window to canvas */
985 GdkEvent copy = *((GdkEvent*)ev);
986 Duple point (ev->x, ev->y);
987 Duple where = window_to_canvas (point);
989 copy.motion.x = where.x;
990 copy.motion.y = where.y;
992 /* Coordinates in "copy" will be canvas coordinates,
995 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));
997 MouseMotion (point); /* EMIT SIGNAL */
999 pick_current_item (point, ev->state);
1001 /* Now deliver the motion event. It may seem a little inefficient
1002 to recompute the items under the event, but the enter notify/leave
1003 events may have deleted canvas items so it is important to
1004 recompute the list in deliver_event.
1007 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1011 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1013 pick_current_item (Duple (ev->x, ev->y), ev->state);
1018 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1020 switch (ev->detail) {
1021 case GDK_NOTIFY_ANCESTOR:
1022 case GDK_NOTIFY_UNKNOWN:
1023 case GDK_NOTIFY_VIRTUAL:
1024 case GDK_NOTIFY_NONLINEAR:
1025 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1026 /* leaving window, cancel any tooltips */
1027 stop_tooltip_timeout ();
1031 /* we don't care about any other kind
1032 of leave event (notably GDK_NOTIFY_INFERIOR)
1036 _new_current_item = 0;
1037 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1041 /** Called to request a redraw of our canvas.
1042 * @param area Area to redraw, in window coordinates.
1045 GtkCanvas::request_redraw (Rect const & request)
1053 Coord const w = width ();
1054 Coord const h = height ();
1056 /* clamp area requested to actual visible window */
1058 real_area.x0 = max (0.0, min (w, request.x0));
1059 real_area.x1 = max (0.0, min (w, request.x1));
1060 real_area.y0 = max (0.0, min (h, request.y0));
1061 real_area.y1 = max (0.0, min (h, request.y1));
1063 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1066 /** Called to request that we try to get a particular size for ourselves.
1067 * @param size Size to request, in pixels.
1070 GtkCanvas::request_size (Duple size)
1074 if (req.x > INT_MAX) {
1078 if (req.y > INT_MAX) {
1082 set_size_request (req.x, req.y);
1085 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1086 * This is typically used for dragging items around, so that they are grabbed during
1088 * @param item Item to grab.
1091 GtkCanvas::grab (Item* item)
1093 /* XXX: should this be doing gdk_pointer_grab? */
1094 _grabbed_item = item;
1098 /** `Ungrab' any item that was previously grabbed */
1100 GtkCanvas::ungrab ()
1102 /* XXX: should this be doing gdk_pointer_ungrab? */
1106 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1108 * @param item Item to grab.
1111 GtkCanvas::focus (Item* item)
1113 _focused_item = item;
1117 GtkCanvas::unfocus (Item* item)
1119 if (item == _focused_item) {
1124 /** @return The visible area of the canvas, in window coordinates */
1126 GtkCanvas::visible_area () const
1128 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1132 GtkCanvas::width() const
1134 return get_allocation().get_width();
1138 GtkCanvas::height() const
1140 return get_allocation().get_height();
1144 GtkCanvas::start_tooltip_timeout (Item* item)
1146 stop_tooltip_timeout ();
1148 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1149 current_tooltip_item = item;
1151 /* wait for the first idle that happens after this is
1152 called. this means that we've stopped processing events, which
1153 in turn implies that the user has stopped doing stuff for a
1157 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1162 GtkCanvas::really_start_tooltip_timeout ()
1164 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1165 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1168 if (current_tooltip_item) {
1169 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1172 return false; /* this is called from an idle callback, don't call it again */
1176 GtkCanvas::stop_tooltip_timeout ()
1178 current_tooltip_item = 0;
1179 tooltip_timeout_connection.disconnect ();
1183 GtkCanvas::show_tooltip ()
1185 Rect tooltip_item_bbox;
1187 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1191 if (!tooltip_window) {
1192 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1193 tooltip_label = manage (new Gtk::Label);
1194 tooltip_label->show ();
1195 tooltip_window->add (*tooltip_label);
1196 tooltip_window->set_border_width (1);
1197 tooltip_window->set_name ("tooltip");
1200 tooltip_label->set_text (current_tooltip_item->tooltip());
1202 /* figure out where to position the tooltip */
1204 Gtk::Widget* toplevel = get_toplevel();
1206 int pointer_x, pointer_y;
1207 Gdk::ModifierType mask;
1209 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1211 Duple tooltip_window_origin (pointer_x, pointer_y);
1213 /* convert to root window coordinates */
1216 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1218 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1220 /* we don't want the pointer to be inside the window when it is
1221 * displayed, because then we generate a leave/enter event pair when
1222 * the window is displayed then hidden - the enter event will
1223 * trigger a new tooltip timeout.
1225 * So move the window right of the pointer position by just a enough
1226 * to get it away from the pointer.
1229 tooltip_window_origin.x += 30;
1230 tooltip_window_origin.y += 45;
1232 /* move the tooltip window into position */
1234 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1238 tooltip_window->present ();
1240 /* called from a timeout handler, don't call it again */
1246 GtkCanvas::hide_tooltip ()
1248 /* hide it if its there */
1250 if (tooltip_window) {
1251 tooltip_window->hide ();
1253 // Delete the tooltip window so it'll get re-created
1254 // (i.e. properly re-sized) on the next usage.
1255 delete tooltip_window;
1256 tooltip_window = NULL;
1260 Glib::RefPtr<Pango::Context>
1261 GtkCanvas::get_pango_context ()
1263 return Glib::wrap (gdk_pango_context_get());
1266 /** Create a GtkCanvaSViewport.
1267 * @param hadj Adjustment to use for horizontal scrolling.
1268 * @param vadj Adjustment to use for vertica scrolling.
1270 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1271 : Alignment (0, 0, 1.0, 1.0)
1272 , hadjustment (hadj)
1273 , vadjustment (vadj)
1277 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1278 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1282 GtkCanvasViewport::scrolled ()
1284 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1288 /** Handler for when GTK asks us what minimum size we want.
1289 * @param req Requsition to fill in.
1292 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1294 /* force the canvas to size itself */
1295 // _canvas.root()->bounding_box();