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 boost::optional<Rect> root_bbox = _root.bounding_box();
112 /* the root has no bounding box, so there's nothing to render */
116 boost::optional<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 boost::optional<Rect> bbox = item->bounding_box ();
186 if (item->item_to_window (*bbox).intersection (visible_area ())) {
187 queue_draw_item_area (item, bbox.get ());
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 boost::optional<Rect> bbox = item->bounding_box ();
201 if (item->item_to_window (*bbox).intersection (visible_area ())) {
202 queue_draw_item_area (item, bbox.get ());
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, boost::optional<Rect> pre_change_bounding_box)
216 Rect window_bbox = visible_area ();
218 if (pre_change_bounding_box) {
220 if (item->item_to_window (*pre_change_bounding_box).intersection (window_bbox)) {
221 /* request a redraw of the item's old bounding box */
222 queue_draw_item_area (item, pre_change_bounding_box.get ());
226 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
227 if (post_change_bounding_box) {
229 if (item->item_to_window (*post_change_bounding_box).intersection (window_bbox)) {
230 /* request a redraw of the item's new bounding box */
231 queue_draw_item_area (item, post_change_bounding_box.get ());
237 Canvas::window_to_canvas (Duple const & d) const
239 ScrollGroup* best_group = 0;
242 /* if the coordinates are negative, clamp to zero and find the item
243 * that covers that "edge" position.
248 if (in_window.x < 0) {
251 if (in_window.y < 0) {
255 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
257 if ((*s)->covers_window (in_window)) {
260 /* XXX January 22nd 2015: leaving this in place for now
261 * but I think it fixes a bug that really should be
262 * fixed in a different way (and will be) by my next
263 * commit. But it may still be relevant.
266 /* If scroll groups overlap, choose the one with the highest sensitivity,
267 that is, choose an HV scroll group over an H or V
270 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
272 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
273 /* Can't do any better than this. */
281 return d.translate (best_group->scroll_offset());
288 Canvas::canvas_to_window (Duple const & d, bool rounded) const
290 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
291 * as children of the root group, so we just scan its first level
292 * children and see what we can find.
295 std::list<Item*> const& root_children (_root.items());
299 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
300 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
306 wd = d.translate (-sg->scroll_offset());
311 /* Note that this intentionally almost always returns integer coordinates */
321 /** Called when an item has moved.
322 * @param item Item that has moved.
323 * @param pre_change_parent_bounding_box The bounding box of the item before
324 * the move, in its parent's coordinates.
327 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
329 if (pre_change_parent_bounding_box) {
330 /* request a redraw of where the item used to be. The box has
331 * to be in parent coordinate space since the bounding box of
332 * an item does not change when moved. If we use
333 * item->item_to_canvas() on the old bounding box, we will be
335 * using the item's new position, and so will compute the wrong
336 * invalidation area. If we use the parent (which has not
337 * moved, then this will work.
339 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
342 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
343 if (post_change_bounding_box) {
344 /* request a redraw of where the item now is */
345 queue_draw_item_area (item, post_change_bounding_box.get ());
349 /** Request a redraw of a particular area in an item's coordinates.
351 * @param area Area to redraw in the item's coordinates.
354 Canvas::queue_draw_item_area (Item* item, Rect area)
356 request_redraw (item->item_to_window (area));
360 Canvas::set_tooltip_timeout (uint32_t msecs)
362 tooltip_timeout_msecs = msecs;
366 Canvas::set_background_color (Color c)
370 boost::optional<Rect> r = _root.bounding_box();
373 request_redraw (_root.item_to_window (r.get()));
378 GtkCanvas::re_enter ()
380 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
382 pick_current_item (0);
385 /** Construct a GtkCanvas */
386 GtkCanvas::GtkCanvas ()
388 , _new_current_item (0)
391 , _single_exposure (1)
392 , current_tooltip_item (0)
396 /* these are the events we want to know about */
397 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
398 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
399 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
403 GtkCanvas::pick_current_item (int state)
408 /* this version of ::pick_current_item() is called after an item is
409 * added or removed, so we have no coordinates to work from as is the
410 * case with a motion event. Find out where the mouse is and use that.
413 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
415 if (pointer_window != get_window()) {
419 pick_current_item (Duple (x, y), state);
422 /** Given @param point (a position in window coordinates)
423 * and mouse state @param state, check to see if _current_item
424 * (which will be used to deliver events) should change.
427 GtkCanvas::pick_current_item (Duple const & point, int state)
429 /* we do not enter/leave items during a drag/grab */
435 /* find the items at the given window position */
437 vector<Item const *> items;
438 _root.add_items_at_point (point, items);
440 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
443 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
444 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
446 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
448 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
454 /* put all items at point that are event-sensitive and visible and NOT
455 groups into within_items. Note that items is sorted from bottom to
456 top, but we're going to reverse that for within_items so that its
457 first item is the upper-most item that can be chosen as _current_item.
460 vector<Item const *>::const_iterator i;
461 list<Item const *> within_items;
463 for (i = items.begin(); i != items.end(); ++i) {
465 Item const * possible_item = *i;
467 /* We ignore invisible items, containers and items that ignore events */
469 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
472 within_items.push_front (possible_item);
475 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
477 if (within_items.empty()) {
479 /* no items at point, just send leave event below */
480 _new_current_item = 0;
484 if (within_items.front() == _current_item) {
485 /* uppermost item at point is already _current_item */
486 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
490 _new_current_item = const_cast<Item*> (within_items.front());
493 if (_new_current_item != _current_item) {
494 deliver_enter_leave (point, state);
498 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
500 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
505 /** Deliver a series of enter & leave events based on the pointer position being at window
506 * coordinate @param point, and pointer @param state (modifier keys, etc)
509 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
511 /* setup enter & leave event structures */
513 Glib::RefPtr<Gdk::Window> win = get_window();
519 GdkEventCrossing enter_event;
520 enter_event.type = GDK_ENTER_NOTIFY;
521 enter_event.window = win->gobj();
522 enter_event.send_event = 0;
523 enter_event.subwindow = 0;
524 enter_event.mode = GDK_CROSSING_NORMAL;
525 enter_event.focus = FALSE;
526 enter_event.state = state;
528 /* Events delivered to canvas items are expected to be in canvas
529 * coordinates but @param point is in window coordinates.
532 Duple c = window_to_canvas (point);
536 GdkEventCrossing leave_event = enter_event;
537 leave_event.type = GDK_LEAVE_NOTIFY;
540 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
541 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
542 vector<Item*> items_to_leave_virtual;
543 vector<Item*> items_to_enter_virtual;
545 if (_new_current_item == 0) {
547 leave_detail = GDK_NOTIFY_UNKNOWN;
551 /* no current item, so also send virtual leave events to the
552 * entire heirarchy for the current item
555 for (i = _current_item->parent(); i ; i = i->parent()) {
556 items_to_leave_virtual.push_back (i);
560 } else if (_current_item == 0) {
562 enter_detail = GDK_NOTIFY_UNKNOWN;
564 /* no current item, so also send virtual enter events to the
565 * entire heirarchy for the new item
568 for (i = _new_current_item->parent(); i ; i = i->parent()) {
569 items_to_enter_virtual.push_back (i);
572 } else if (_current_item->is_descendant_of (*_new_current_item)) {
574 /* move from descendant to ancestor (X: "_current_item is an
575 * inferior ("child") of _new_current_item")
577 * Deliver "virtual" leave notifications to all items in the
578 * heirarchy between current and new_current.
581 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
582 items_to_leave_virtual.push_back (i);
585 enter_detail = GDK_NOTIFY_INFERIOR;
586 leave_detail = GDK_NOTIFY_ANCESTOR;
588 } else if (_new_current_item->is_descendant_of (*_current_item)) {
589 /* move from ancestor to descendant (X: "_new_current_item is
590 * an inferior ("child") of _current_item")
592 * Deliver "virtual" enter notifications to all items in the
593 * heirarchy between current and new_current.
596 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
597 items_to_enter_virtual.push_back (i);
600 enter_detail = GDK_NOTIFY_ANCESTOR;
601 leave_detail = GDK_NOTIFY_INFERIOR;
605 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
607 /* deliver virtual leave events to everything between _current
608 * and common_ancestor.
611 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
612 items_to_leave_virtual.push_back (i);
615 /* deliver virtual enter events to everything between
616 * _new_current and common_ancestor.
619 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
620 items_to_enter_virtual.push_back (i);
623 enter_detail = GDK_NOTIFY_NONLINEAR;
624 leave_detail = GDK_NOTIFY_NONLINEAR;
628 if (_current_item && !_current_item->ignore_events ()) {
629 leave_event.detail = leave_detail;
630 _current_item->Event ((GdkEvent*)&leave_event);
631 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
634 leave_event.detail = GDK_NOTIFY_VIRTUAL;
635 enter_event.detail = GDK_NOTIFY_VIRTUAL;
637 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
638 if (!(*it)->ignore_events()) {
639 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
640 (*it)->Event ((GdkEvent*)&leave_event);
644 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
645 if (!(*it)->ignore_events()) {
646 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
647 (*it)->Event ((GdkEvent*)&enter_event);
648 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
652 if (_new_current_item && !_new_current_item->ignore_events()) {
653 enter_event.detail = enter_detail;
654 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
655 start_tooltip_timeout (_new_current_item);
656 _new_current_item->Event ((GdkEvent*)&enter_event);
659 _current_item = _new_current_item;
663 /** Deliver an event to the appropriate item; either the grabbed item, or
664 * one of the items underneath the event.
665 * @param point Position that the event has occurred at, in canvas coordinates.
666 * @param event The event.
669 GtkCanvas::deliver_event (GdkEvent* event)
671 /* Point in in canvas coordinate space */
673 const Item* event_item;
676 /* we have a grabbed item, so everything gets sent there */
677 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
678 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
679 event_item = _grabbed_item;
681 event_item = _current_item;
688 /* run through the items from child to parent, until one claims the event */
690 Item* item = const_cast<Item*> (event_item);
694 Item* parent = item->parent ();
696 if (!item->ignore_events () &&
697 item->Event (event)) {
698 /* this item has just handled the event */
700 PBD::DEBUG::CanvasEvents,
701 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
707 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)));
709 if ((item = parent) == 0) {
718 /** Called when an item is being destroyed.
719 * @param item Item being destroyed.
720 * @param bounding_box Last known bounding box of the item.
723 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
726 queue_draw_item_area (item, bounding_box.get ());
729 if (_new_current_item == item) {
730 _new_current_item = 0;
733 if (_grabbed_item == item) {
737 if (_focused_item == item) {
741 if (current_tooltip_item) {
742 current_tooltip_item = 0;
743 stop_tooltip_timeout ();
746 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
748 scrollers.remove (sg);
751 if (_current_item == item) {
752 /* no need to send a leave event to this item, since it is going away
755 pick_current_item (0); // no mouse state
761 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
763 EventBox::on_size_allocate (a);
764 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
765 if (getenv("ARDOUR_IMAGE_SURFACE")) {
767 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
768 /* allocate an image surface as large as the canvas itself */
770 canvas_image.clear ();
771 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
773 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
778 /** Handler for GDK expose events.
780 * @return true if the event was handled.
783 GtkCanvas::on_expose_event (GdkEventExpose* ev)
789 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
790 Cairo::RefPtr<Cairo::Context> draw_context;
791 Cairo::RefPtr<Cairo::Context> window_context;
792 if (getenv("ARDOUR_IMAGE_SURFACE")) {
794 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
796 draw_context = Cairo::Context::create (canvas_image);
797 window_context = get_window()->create_cairo_context ();
799 draw_context = get_window()->create_cairo_context ();
801 #elif defined USE_CAIRO_IMAGE_SURFACE
803 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
805 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
806 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
808 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
811 /* draw background color */
813 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
814 draw_context->clip_preserve ();
815 set_source_rgba (draw_context, _bg_color);
816 draw_context->fill ();
819 if ( _single_exposure ) {
821 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
827 gdk_region_get_rectangles (ev->region, &rects, &nrects);
828 for (gint n = 0; n < nrects; ++n) {
829 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
830 render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
835 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
836 if (getenv("ARDOUR_IMAGE_SURFACE")) {
838 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
839 /* now blit our private surface back to the GDK one */
841 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
842 window_context->clip ();
843 window_context->set_source (canvas_image, 0, 0);
844 window_context->set_operator (Cairo::OPERATOR_SOURCE);
845 window_context->paint ();
847 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
854 /** Handler for GDK scroll events.
856 * @return true if the event was handled.
859 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
861 /* translate event coordinates from window to canvas */
863 GdkEvent copy = *((GdkEvent*)ev);
864 Duple winpos = Duple (ev->x, ev->y);
865 Duple where = window_to_canvas (winpos);
867 pick_current_item (winpos, ev->state);
869 copy.button.x = where.x;
870 copy.button.y = where.y;
872 /* Coordinates in the event will be canvas coordinates, correctly adjusted
873 for scroll if this GtkCanvas is in a GtkCanvasViewport.
876 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
877 return deliver_event (reinterpret_cast<GdkEvent*>(©));
880 /** Handler for GDK key press events.
882 * @return true if the event was handled.
885 GtkCanvas::on_key_press_event (GdkEventKey* ev)
887 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
888 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
891 /** Handler for GDK key release events.
893 * @return true if the event was handled.
896 GtkCanvas::on_key_release_event (GdkEventKey* ev)
898 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
899 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
902 /** Handler for GDK button press events.
904 * @return true if the event was handled.
907 GtkCanvas::on_button_press_event (GdkEventButton* ev)
909 /* translate event coordinates from window to canvas */
911 GdkEvent copy = *((GdkEvent*)ev);
912 Duple winpos = Duple (ev->x, ev->y);
913 Duple where = window_to_canvas (winpos);
915 pick_current_item (winpos, ev->state);
917 copy.button.x = where.x;
918 copy.button.y = where.y;
920 /* Coordinates in the event will be canvas coordinates, correctly adjusted
921 for scroll if this GtkCanvas is in a GtkCanvasViewport.
924 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
925 return deliver_event (reinterpret_cast<GdkEvent*>(©));
928 /** Handler for GDK button release events.
930 * @return true if the event was handled.
933 GtkCanvas::on_button_release_event (GdkEventButton* ev)
935 /* translate event coordinates from window to canvas */
937 GdkEvent copy = *((GdkEvent*)ev);
938 Duple winpos = Duple (ev->x, ev->y);
939 Duple where = window_to_canvas (winpos);
941 pick_current_item (winpos, ev->state);
943 copy.button.x = where.x;
944 copy.button.y = where.y;
946 /* Coordinates in the event will be canvas coordinates, correctly adjusted
947 for scroll if this GtkCanvas is in a GtkCanvasViewport.
950 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
951 return deliver_event (reinterpret_cast<GdkEvent*>(©));
955 GtkCanvas::get_mouse_position (Duple& winpos) const
959 Gdk::ModifierType mask;
960 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
963 std::cerr << " no self window\n";
964 winpos = Duple (0, 0);
968 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
976 /** Handler for GDK motion events.
978 * @return true if the event was handled.
981 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
985 /* translate event coordinates from window to canvas */
987 GdkEvent copy = *((GdkEvent*)ev);
988 Duple point (ev->x, ev->y);
989 Duple where = window_to_canvas (point);
991 copy.motion.x = where.x;
992 copy.motion.y = where.y;
994 /* Coordinates in "copy" will be canvas coordinates,
997 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));
999 MouseMotion (point); /* EMIT SIGNAL */
1001 pick_current_item (point, ev->state);
1003 /* Now deliver the motion event. It may seem a little inefficient
1004 to recompute the items under the event, but the enter notify/leave
1005 events may have deleted canvas items so it is important to
1006 recompute the list in deliver_event.
1009 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1013 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1015 pick_current_item (Duple (ev->x, ev->y), ev->state);
1020 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1022 switch (ev->detail) {
1023 case GDK_NOTIFY_ANCESTOR:
1024 case GDK_NOTIFY_UNKNOWN:
1025 case GDK_NOTIFY_VIRTUAL:
1026 case GDK_NOTIFY_NONLINEAR:
1027 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1028 /* leaving window, cancel any tooltips */
1029 stop_tooltip_timeout ();
1033 /* we don't care about any other kind
1034 of leave event (notably GDK_NOTIFY_INFERIOR)
1038 _new_current_item = 0;
1039 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1043 /** Called to request a redraw of our canvas.
1044 * @param area Area to redraw, in window coordinates.
1047 GtkCanvas::request_redraw (Rect const & request)
1055 Coord const w = width ();
1056 Coord const h = height ();
1058 /* clamp area requested to actual visible window */
1060 real_area.x0 = max (0.0, min (w, request.x0));
1061 real_area.x1 = max (0.0, min (w, request.x1));
1062 real_area.y0 = max (0.0, min (h, request.y0));
1063 real_area.y1 = max (0.0, min (h, request.y1));
1065 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1068 /** Called to request that we try to get a particular size for ourselves.
1069 * @param size Size to request, in pixels.
1072 GtkCanvas::request_size (Duple size)
1076 if (req.x > INT_MAX) {
1080 if (req.y > INT_MAX) {
1084 set_size_request (req.x, req.y);
1087 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1088 * This is typically used for dragging items around, so that they are grabbed during
1090 * @param item Item to grab.
1093 GtkCanvas::grab (Item* item)
1095 /* XXX: should this be doing gdk_pointer_grab? */
1096 _grabbed_item = item;
1100 /** `Ungrab' any item that was previously grabbed */
1102 GtkCanvas::ungrab ()
1104 /* XXX: should this be doing gdk_pointer_ungrab? */
1108 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1110 * @param item Item to grab.
1113 GtkCanvas::focus (Item* item)
1115 _focused_item = item;
1119 GtkCanvas::unfocus (Item* item)
1121 if (item == _focused_item) {
1126 /** @return The visible area of the canvas, in window coordinates */
1128 GtkCanvas::visible_area () const
1130 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1134 GtkCanvas::width() const
1136 return get_allocation().get_width();
1140 GtkCanvas::height() const
1142 return get_allocation().get_height();
1146 GtkCanvas::start_tooltip_timeout (Item* item)
1148 stop_tooltip_timeout ();
1150 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1151 current_tooltip_item = item;
1153 /* wait for the first idle that happens after this is
1154 called. this means that we've stopped processing events, which
1155 in turn implies that the user has stopped doing stuff for a
1159 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1164 GtkCanvas::really_start_tooltip_timeout ()
1166 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1167 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1170 if (current_tooltip_item) {
1171 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1174 return false; /* this is called from an idle callback, don't call it again */
1178 GtkCanvas::stop_tooltip_timeout ()
1180 current_tooltip_item = 0;
1181 tooltip_timeout_connection.disconnect ();
1185 GtkCanvas::show_tooltip ()
1187 Rect tooltip_item_bbox;
1189 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1193 if (!tooltip_window) {
1194 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1195 tooltip_label = manage (new Gtk::Label);
1196 tooltip_label->show ();
1197 tooltip_window->add (*tooltip_label);
1198 tooltip_window->set_border_width (1);
1199 tooltip_window->set_name ("tooltip");
1202 tooltip_label->set_text (current_tooltip_item->tooltip());
1204 /* figure out where to position the tooltip */
1206 Gtk::Widget* toplevel = get_toplevel();
1208 int pointer_x, pointer_y;
1209 Gdk::ModifierType mask;
1211 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1213 Duple tooltip_window_origin (pointer_x, pointer_y);
1215 /* convert to root window coordinates */
1218 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1220 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1222 /* we don't want the pointer to be inside the window when it is
1223 * displayed, because then we generate a leave/enter event pair when
1224 * the window is displayed then hidden - the enter event will
1225 * trigger a new tooltip timeout.
1227 * So move the window right of the pointer position by just a enough
1228 * to get it away from the pointer.
1231 tooltip_window_origin.x += 30;
1232 tooltip_window_origin.y += 45;
1234 /* move the tooltip window into position */
1236 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1240 tooltip_window->present ();
1242 /* called from a timeout handler, don't call it again */
1248 GtkCanvas::hide_tooltip ()
1250 /* hide it if its there */
1252 if (tooltip_window) {
1253 tooltip_window->hide ();
1255 // Delete the tooltip window so it'll get re-created
1256 // (i.e. properly re-sized) on the next usage.
1257 delete tooltip_window;
1258 tooltip_window = NULL;
1262 /** Create a GtkCanvaSViewport.
1263 * @param hadj Adjustment to use for horizontal scrolling.
1264 * @param vadj Adjustment to use for vertica scrolling.
1266 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1267 : Alignment (0, 0, 1.0, 1.0)
1268 , hadjustment (hadj)
1269 , vadjustment (vadj)
1273 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1274 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1278 GtkCanvasViewport::scrolled ()
1280 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1284 /** Handler for when GTK asks us what minimum size we want.
1285 * @param req Requsition to fill in.
1288 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1290 /* force the canvas to size itself */
1291 // _canvas.root()->bounding_box();