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 "gtkmm2ext/colors.h"
42 #include "canvas/debug.h"
43 #include "canvas/line.h"
44 #include "canvas/scroll_group.h"
48 #include "gtkmm2ext/nsglview.h"
52 using namespace ArdourCanvas;
54 uint32_t Canvas::tooltip_timeout_msecs = 750;
56 /** Construct a new Canvas */
59 , _bg_color (Gtkmm2ext::rgba_to_color (0, 1.0, 0.0, 1.0))
60 , _last_render_start_timestamp(0)
66 Canvas::scroll_to (Coord x, Coord y)
68 /* We do things this way because we do not want to recurse through
69 the canvas for every scroll. In the presence of large MIDI
70 tracks this means traversing item lists that include
71 thousands of items (notes).
73 This design limits us to moving only those items (groups, typically)
74 that should move in certain ways as we scroll. In other terms, it
75 becomes O(1) rather than O(N).
78 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
79 (*i)->scroll_to (Duple (x, y));
82 pick_current_item (0); // no current mouse position
86 Canvas::add_scroller (ScrollGroup& i)
88 scrollers.push_back (&i);
94 pick_current_item (0); // no current mouse position
97 /** Render an area of the canvas.
98 * @param area Area in window coordinates.
99 * @param context Cairo context to render to.
102 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
104 PreRender (); // emit signal
106 _last_render_start_timestamp = g_get_monotonic_time();
109 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
110 cerr << this << " RENDER: " << area << endl;
111 //cerr << "CANVAS @ " << this << endl;
113 //cerr << "-------------------------\n";
119 Rect root_bbox = _root.bounding_box();
121 /* the root has no bounding box, so there's nothing to render */
125 Rect draw = root_bbox.intersection (area);
128 /* there's a common area between the root and the requested
132 _root.render (draw, context);
134 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
135 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
136 // This transparently colors the rect being rendered, after it has been drawn.
137 double r = (random() % 65536) /65536.0;
138 double g = (random() % 65536) /65536.0;
139 double b = (random() % 65536) /65536.0;
140 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
141 context->set_source_rgba (r, g, b, 0.25);
150 Canvas::prepare_for_render (Rect const & area) const
152 Rect root_bbox = _root.bounding_box();
154 /* the root has no bounding box, so there's nothing to render */
158 Rect draw = root_bbox.intersection (area);
161 _root.prepare_for_render (draw);
166 Canvas::get_microseconds_since_render_start () const
168 gint64 timestamp = g_get_monotonic_time();
170 if (_last_render_start_timestamp == 0 || timestamp <= _last_render_start_timestamp) {
174 return timestamp - _last_render_start_timestamp;
178 operator<< (ostream& o, Canvas& c)
185 Canvas::indent() const
189 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
197 Canvas::render_indent() const
201 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
209 Canvas::dump (ostream& o) const
215 /** Called when an item has been shown or hidden.
216 * @param item Item that has been shown or hidden.
219 Canvas::item_shown_or_hidden (Item* item)
221 Rect bbox = item->bounding_box ();
223 if (item->item_to_window (bbox).intersection (visible_area ())) {
224 queue_draw_item_area (item, bbox);
229 /** Called when an item has a change to its visual properties
230 * that do NOT affect its bounding box.
231 * @param item Item that has been modified.
234 Canvas::item_visual_property_changed (Item* item)
236 Rect bbox = item->bounding_box ();
238 if (item->item_to_window (bbox).intersection (visible_area ())) {
239 queue_draw_item_area (item, bbox);
244 /** Called when an item has changed, but not moved.
245 * @param item Item that has changed.
246 * @param pre_change_bounding_box The bounding box of item before the change,
247 * in the item's coordinates.
250 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
252 Rect window_bbox = visible_area ();
254 if (pre_change_bounding_box) {
255 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
256 /* request a redraw of the item's old bounding box */
257 queue_draw_item_area (item, pre_change_bounding_box);
261 Rect post_change_bounding_box = item->bounding_box ();
263 if (post_change_bounding_box) {
264 Rect const window_intersection =
265 item->item_to_window (post_change_bounding_box).intersection (window_bbox);
267 if (window_intersection) {
268 /* request a redraw of the item's new bounding box */
269 queue_draw_item_area (item, post_change_bounding_box);
271 // Allow item to do any work necessary to prepare for being rendered.
272 item->prepare_for_render (window_intersection);
274 // No intersection with visible window area
280 Canvas::window_to_canvas (Duple const & d) const
282 ScrollGroup* best_group = 0;
285 /* if the coordinates are negative, clamp to zero and find the item
286 * that covers that "edge" position.
291 if (in_window.x < 0) {
294 if (in_window.y < 0) {
298 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
300 if ((*s)->covers_window (in_window)) {
303 /* XXX January 22nd 2015: leaving this in place for now
304 * but I think it fixes a bug that really should be
305 * fixed in a different way (and will be) by my next
306 * commit. But it may still be relevant.
309 /* If scroll groups overlap, choose the one with the highest sensitivity,
310 that is, choose an HV scroll group over an H or V
313 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
315 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
316 /* Can't do any better than this. */
324 return d.translate (best_group->scroll_offset());
331 Canvas::canvas_to_window (Duple const & d, bool rounded) const
333 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
334 * as children of the root group, so we just scan its first level
335 * children and see what we can find.
338 std::list<Item*> const& root_children (_root.items());
342 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
343 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
349 wd = d.translate (-sg->scroll_offset());
354 /* Note that this intentionally almost always returns integer coordinates */
364 /** Called when an item has moved.
365 * @param item Item that has moved.
366 * @param pre_change_parent_bounding_box The bounding box of the item before
367 * the move, in its parent's coordinates.
370 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
372 if (pre_change_parent_bounding_box) {
373 /* request a redraw of where the item used to be. The box has
374 * to be in parent coordinate space since the bounding box of
375 * an item does not change when moved. If we use
376 * item->item_to_canvas() on the old bounding box, we will be
378 * using the item's new position, and so will compute the wrong
379 * invalidation area. If we use the parent (which has not
380 * moved, then this will work.
382 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
385 Rect post_change_bounding_box = item->bounding_box ();
386 if (post_change_bounding_box) {
387 /* request a redraw of where the item now is */
388 queue_draw_item_area (item, post_change_bounding_box);
392 /** Request a redraw of a particular area in an item's coordinates.
394 * @param area Area to redraw in the item's coordinates.
397 Canvas::queue_draw_item_area (Item* item, Rect area)
399 request_redraw (item->item_to_window (area));
403 Canvas::set_tooltip_timeout (uint32_t msecs)
405 tooltip_timeout_msecs = msecs;
409 Canvas::set_background_color (Gtkmm2ext::Color c)
413 Rect r = _root.bounding_box();
416 request_redraw (_root.item_to_window (r));
421 GtkCanvas::re_enter ()
423 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
425 pick_current_item (0);
428 /** Construct a GtkCanvas */
429 GtkCanvas::GtkCanvas ()
431 , _new_current_item (0)
434 , _single_exposure (1)
435 , current_tooltip_item (0)
440 /* these are the events we want to know about */
441 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
442 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
443 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
447 GtkCanvas::use_nsglview ()
450 assert (!is_realized());
451 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
452 _nsglview = Gtkmm2ext::nsglview_create (this);
457 GtkCanvas::pick_current_item (int state)
462 /* this version of ::pick_current_item() is called after an item is
463 * added or removed, so we have no coordinates to work from as is the
464 * case with a motion event. Find out where the mouse is and use that.
467 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
469 if (pointer_window != get_window()) {
473 pick_current_item (Duple (x, y), state);
476 /** Given @param point (a position in window coordinates)
477 * and mouse state @param state, check to see if _current_item
478 * (which will be used to deliver events) should change.
481 GtkCanvas::pick_current_item (Duple const & point, int state)
483 /* we do not enter/leave items during a drag/grab */
489 /* find the items at the given window position */
491 vector<Item const *> items;
492 _root.add_items_at_point (point, items);
494 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
497 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
498 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
500 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
502 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
508 /* put all items at point that are event-sensitive and visible and NOT
509 groups into within_items. Note that items is sorted from bottom to
510 top, but we're going to reverse that for within_items so that its
511 first item is the upper-most item that can be chosen as _current_item.
514 vector<Item const *>::const_iterator i;
515 list<Item const *> within_items;
517 for (i = items.begin(); i != items.end(); ++i) {
519 Item const * possible_item = *i;
521 /* We ignore invisible items, containers and items that ignore events */
523 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
526 within_items.push_front (possible_item);
529 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
531 if (within_items.empty()) {
533 /* no items at point, just send leave event below */
534 _new_current_item = 0;
538 if (within_items.front() == _current_item) {
539 /* uppermost item at point is already _current_item */
540 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
544 _new_current_item = const_cast<Item*> (within_items.front());
547 if (_new_current_item != _current_item) {
548 deliver_enter_leave (point, state);
552 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
554 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
559 /** Deliver a series of enter & leave events based on the pointer position being at window
560 * coordinate @param point, and pointer @param state (modifier keys, etc)
563 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
565 /* setup enter & leave event structures */
567 Glib::RefPtr<Gdk::Window> win = get_window();
573 GdkEventCrossing enter_event;
574 enter_event.type = GDK_ENTER_NOTIFY;
575 enter_event.window = win->gobj();
576 enter_event.send_event = 0;
577 enter_event.subwindow = 0;
578 enter_event.mode = GDK_CROSSING_NORMAL;
579 enter_event.focus = FALSE;
580 enter_event.state = state;
582 /* Events delivered to canvas items are expected to be in canvas
583 * coordinates but @param point is in window coordinates.
586 Duple c = window_to_canvas (point);
590 GdkEventCrossing leave_event = enter_event;
591 leave_event.type = GDK_LEAVE_NOTIFY;
594 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
595 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
596 vector<Item*> items_to_leave_virtual;
597 vector<Item*> items_to_enter_virtual;
599 if (_new_current_item == 0) {
601 leave_detail = GDK_NOTIFY_UNKNOWN;
605 /* no current item, so also send virtual leave events to the
606 * entire heirarchy for the current item
609 for (i = _current_item->parent(); i ; i = i->parent()) {
610 items_to_leave_virtual.push_back (i);
614 } else if (_current_item == 0) {
616 enter_detail = GDK_NOTIFY_UNKNOWN;
618 /* no current item, so also send virtual enter events to the
619 * entire heirarchy for the new item
622 for (i = _new_current_item->parent(); i ; i = i->parent()) {
623 items_to_enter_virtual.push_back (i);
626 } else if (_current_item->is_descendant_of (*_new_current_item)) {
628 /* move from descendant to ancestor (X: "_current_item is an
629 * inferior ("child") of _new_current_item")
631 * Deliver "virtual" leave notifications to all items in the
632 * heirarchy between current and new_current.
635 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
636 items_to_leave_virtual.push_back (i);
639 enter_detail = GDK_NOTIFY_INFERIOR;
640 leave_detail = GDK_NOTIFY_ANCESTOR;
642 } else if (_new_current_item->is_descendant_of (*_current_item)) {
643 /* move from ancestor to descendant (X: "_new_current_item is
644 * an inferior ("child") of _current_item")
646 * Deliver "virtual" enter notifications to all items in the
647 * heirarchy between current and new_current.
650 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
651 items_to_enter_virtual.push_back (i);
654 enter_detail = GDK_NOTIFY_ANCESTOR;
655 leave_detail = GDK_NOTIFY_INFERIOR;
659 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
661 /* deliver virtual leave events to everything between _current
662 * and common_ancestor.
665 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
666 items_to_leave_virtual.push_back (i);
669 /* deliver virtual enter events to everything between
670 * _new_current and common_ancestor.
673 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
674 items_to_enter_virtual.push_back (i);
677 enter_detail = GDK_NOTIFY_NONLINEAR;
678 leave_detail = GDK_NOTIFY_NONLINEAR;
682 if (_current_item && !_current_item->ignore_events ()) {
683 leave_event.detail = leave_detail;
684 _current_item->Event ((GdkEvent*)&leave_event);
685 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
688 leave_event.detail = GDK_NOTIFY_VIRTUAL;
689 enter_event.detail = GDK_NOTIFY_VIRTUAL;
691 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
692 if (!(*it)->ignore_events()) {
693 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
694 (*it)->Event ((GdkEvent*)&leave_event);
698 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
699 if (!(*it)->ignore_events()) {
700 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
701 (*it)->Event ((GdkEvent*)&enter_event);
702 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
706 if (_new_current_item && !_new_current_item->ignore_events()) {
707 enter_event.detail = enter_detail;
708 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
709 start_tooltip_timeout (_new_current_item);
710 _new_current_item->Event ((GdkEvent*)&enter_event);
713 _current_item = _new_current_item;
717 /** Deliver an event to the appropriate item; either the grabbed item, or
718 * one of the items underneath the event.
719 * @param point Position that the event has occurred at, in canvas coordinates.
720 * @param event The event.
723 GtkCanvas::deliver_event (GdkEvent* event)
725 /* Point in in canvas coordinate space */
727 const Item* event_item;
730 /* we have a grabbed item, so everything gets sent there */
731 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
732 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
733 event_item = _grabbed_item;
735 event_item = _current_item;
742 /* run through the items from child to parent, until one claims the event */
744 Item* item = const_cast<Item*> (event_item);
748 Item* parent = item->parent ();
750 if (!item->ignore_events () &&
751 item->Event (event)) {
752 /* this item has just handled the event */
754 PBD::DEBUG::CanvasEvents,
755 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
761 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)));
763 if ((item = parent) == 0) {
773 GtkCanvas::item_shown_or_hidden (Item* item)
775 if (item == current_tooltip_item) {
776 stop_tooltip_timeout ();
778 Canvas::item_shown_or_hidden (item);
781 /** Called when an item is being destroyed.
782 * @param item Item being destroyed.
783 * @param bounding_box Last known bounding box of the item.
786 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
789 queue_draw_item_area (item, bounding_box);
792 if (_new_current_item == item) {
793 _new_current_item = 0;
796 if (_grabbed_item == item) {
800 if (_focused_item == item) {
804 if (current_tooltip_item) {
805 current_tooltip_item = 0;
806 stop_tooltip_timeout ();
809 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
811 scrollers.remove (sg);
814 if (_current_item == item) {
815 /* no need to send a leave event to this item, since it is going away
818 pick_current_item (0); // no mouse state
824 GtkCanvas::on_realize ()
826 Gtk::EventBox::on_realize();
829 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
835 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
837 EventBox::on_size_allocate (a);
838 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
839 if (getenv("ARDOUR_IMAGE_SURFACE")) {
841 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
842 /* allocate an image surface as large as the canvas itself */
844 canvas_image.clear ();
845 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
847 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
854 gtk_widget_translate_coordinates(
856 GTK_WIDGET(get_toplevel()->gobj()),
858 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
864 /** Handler for GDK expose events.
866 * @return true if the event was handled.
869 GtkCanvas::on_expose_event (GdkEventExpose* ev)
880 #ifdef CANVAS_PROFILE
881 const int64_t start = g_get_monotonic_time ();
884 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
885 Cairo::RefPtr<Cairo::Context> draw_context;
886 Cairo::RefPtr<Cairo::Context> window_context;
887 if (getenv("ARDOUR_IMAGE_SURFACE")) {
889 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
891 draw_context = Cairo::Context::create (canvas_image);
892 window_context = get_window()->create_cairo_context ();
894 draw_context = get_window()->create_cairo_context ();
896 #elif defined USE_CAIRO_IMAGE_SURFACE
898 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
900 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
901 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
903 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
906 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
907 draw_context->clip();
910 /* group calls cairo_quartz_surface_create() which
911 * effectively uses a CGBitmapContext + image-surface
913 * This avoids expensive argb32_image_mark_image() during drawing.
914 * Although the final paint() operation still takes the slow path
915 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
917 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
918 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
920 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
922 draw_context->push_group ();
925 /* draw background color */
926 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
927 Gtkmm2ext::set_source_rgba (draw_context, _bg_color);
928 draw_context->fill ();
931 if ( _single_exposure ) {
933 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
939 gdk_region_get_rectangles (ev->region, &rects, &nrects);
940 for (gint n = 0; n < nrects; ++n) {
941 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
942 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
948 draw_context->pop_group_to_source ();
949 draw_context->paint ();
952 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
953 if (getenv("ARDOUR_IMAGE_SURFACE")) {
955 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
956 /* now blit our private surface back to the GDK one */
958 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
959 window_context->clip ();
960 window_context->set_source (canvas_image, 0, 0);
961 window_context->set_operator (Cairo::OPERATOR_SOURCE);
962 window_context->paint ();
964 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
968 #ifdef CANVAS_PROFILE
969 const int64_t end = g_get_monotonic_time ();
970 const int64_t elapsed = end - start;
971 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
978 GtkCanvas::prepare_for_render () const
980 Rect window_bbox = visible_area ();
981 Canvas::prepare_for_render (window_bbox);
984 /** Handler for GDK scroll events.
986 * @return true if the event was handled.
989 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
991 /* translate event coordinates from window to canvas */
993 GdkEvent copy = *((GdkEvent*)ev);
994 Duple winpos = Duple (ev->x, ev->y);
995 Duple where = window_to_canvas (winpos);
997 pick_current_item (winpos, ev->state);
999 copy.button.x = where.x;
1000 copy.button.y = where.y;
1002 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1003 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1006 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
1007 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1010 /** Handler for GDK key press events.
1012 * @return true if the event was handled.
1015 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1017 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1018 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1021 /** Handler for GDK key release events.
1023 * @return true if the event was handled.
1026 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1028 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1029 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1032 /** Handler for GDK button press events.
1034 * @return true if the event was handled.
1037 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1039 /* translate event coordinates from window to canvas */
1041 GdkEvent copy = *((GdkEvent*)ev);
1042 Duple winpos = Duple (ev->x, ev->y);
1043 Duple where = window_to_canvas (winpos);
1045 pick_current_item (winpos, ev->state);
1047 copy.button.x = where.x;
1048 copy.button.y = where.y;
1050 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1051 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1054 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1055 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1058 /** Handler for GDK button release events.
1060 * @return true if the event was handled.
1063 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1065 /* translate event coordinates from window to canvas */
1067 GdkEvent copy = *((GdkEvent*)ev);
1068 Duple winpos = Duple (ev->x, ev->y);
1069 Duple where = window_to_canvas (winpos);
1071 pick_current_item (winpos, ev->state);
1073 copy.button.x = where.x;
1074 copy.button.y = where.y;
1076 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1077 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1080 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1081 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1085 GtkCanvas::get_mouse_position (Duple& winpos) const
1089 Gdk::ModifierType mask;
1090 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1093 std::cerr << " no self window\n";
1094 winpos = Duple (0, 0);
1098 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1106 /** Handler for GDK motion events.
1108 * @return true if the event was handled.
1111 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1115 /* translate event coordinates from window to canvas */
1117 GdkEvent copy = *((GdkEvent*)ev);
1118 Duple point (ev->x, ev->y);
1119 Duple where = window_to_canvas (point);
1121 copy.motion.x = where.x;
1122 copy.motion.y = where.y;
1124 /* Coordinates in "copy" will be canvas coordinates,
1127 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));
1129 MouseMotion (point); /* EMIT SIGNAL */
1131 pick_current_item (point, ev->state);
1133 /* Now deliver the motion event. It may seem a little inefficient
1134 to recompute the items under the event, but the enter notify/leave
1135 events may have deleted canvas items so it is important to
1136 recompute the list in deliver_event.
1139 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1143 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1145 pick_current_item (Duple (ev->x, ev->y), ev->state);
1150 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1152 switch (ev->detail) {
1153 case GDK_NOTIFY_ANCESTOR:
1154 case GDK_NOTIFY_UNKNOWN:
1155 case GDK_NOTIFY_VIRTUAL:
1156 case GDK_NOTIFY_NONLINEAR:
1157 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1158 /* leaving window, cancel any tooltips */
1159 stop_tooltip_timeout ();
1163 /* we don't care about any other kind
1164 of leave event (notably GDK_NOTIFY_INFERIOR)
1168 _new_current_item = 0;
1169 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1174 GtkCanvas::on_map ()
1176 Gtk::EventBox::on_map();
1179 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1180 Gtk::Allocation a = get_allocation();
1182 gtk_widget_translate_coordinates(
1184 GTK_WIDGET(get_toplevel()->gobj()),
1186 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1192 GtkCanvas::on_unmap ()
1194 stop_tooltip_timeout ();
1195 Gtk::EventBox::on_unmap();
1198 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1204 GtkCanvas::queue_draw()
1208 Gtkmm2ext::nsglview_queue_draw (_nsglview, 0, 0, get_width (), get_height ());
1212 Gtk::Widget::queue_draw ();
1216 GtkCanvas::queue_draw_area (int x, int y, int width, int height)
1220 Gtkmm2ext::nsglview_queue_draw (_nsglview, x, y, width, height);
1224 Gtk::Widget::queue_draw_area (x, y, width, height);
1227 /** Called to request a redraw of our canvas.
1228 * @param area Area to redraw, in window coordinates.
1231 GtkCanvas::request_redraw (Rect const & request)
1237 /* clamp area requested to actual visible window */
1239 Rect real_area = request.intersection (visible_area());
1242 if (real_area.width () && real_area.height ()) {
1243 // Item intersects with visible canvas area
1244 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1248 // Item does not intersect with visible canvas area
1252 /** Called to request that we try to get a particular size for ourselves.
1253 * @param size Size to request, in pixels.
1256 GtkCanvas::request_size (Duple size)
1260 if (req.x > INT_MAX) {
1264 if (req.y > INT_MAX) {
1268 set_size_request (req.x, req.y);
1271 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1272 * This is typically used for dragging items around, so that they are grabbed during
1274 * @param item Item to grab.
1277 GtkCanvas::grab (Item* item)
1279 /* XXX: should this be doing gdk_pointer_grab? */
1280 _grabbed_item = item;
1284 /** `Ungrab' any item that was previously grabbed */
1286 GtkCanvas::ungrab ()
1288 /* XXX: should this be doing gdk_pointer_ungrab? */
1292 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1294 * @param item Item to grab.
1297 GtkCanvas::focus (Item* item)
1299 _focused_item = item;
1303 GtkCanvas::unfocus (Item* item)
1305 if (item == _focused_item) {
1310 /** @return The visible area of the canvas, in window coordinates */
1312 GtkCanvas::visible_area () const
1314 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1318 GtkCanvas::width() const
1320 return get_allocation().get_width();
1324 GtkCanvas::height() const
1326 return get_allocation().get_height();
1330 GtkCanvas::start_tooltip_timeout (Item* item)
1332 stop_tooltip_timeout ();
1334 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1335 current_tooltip_item = item;
1337 /* wait for the first idle that happens after this is
1338 called. this means that we've stopped processing events, which
1339 in turn implies that the user has stopped doing stuff for a
1343 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1348 GtkCanvas::really_start_tooltip_timeout ()
1350 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1351 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1354 if (current_tooltip_item) {
1355 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1358 return false; /* this is called from an idle callback, don't call it again */
1362 GtkCanvas::stop_tooltip_timeout ()
1364 current_tooltip_item = 0;
1365 tooltip_timeout_connection.disconnect ();
1369 GtkCanvas::show_tooltip ()
1371 Rect tooltip_item_bbox;
1373 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1377 if (!tooltip_window) {
1378 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1379 tooltip_label = manage (new Gtk::Label);
1380 tooltip_label->show ();
1381 tooltip_window->add (*tooltip_label);
1382 tooltip_window->set_border_width (1);
1383 tooltip_window->set_name ("tooltip");
1386 tooltip_label->set_text (current_tooltip_item->tooltip());
1388 /* figure out where to position the tooltip */
1390 Gtk::Widget* toplevel = get_toplevel();
1392 int pointer_x, pointer_y;
1393 Gdk::ModifierType mask;
1395 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1397 Duple tooltip_window_origin (pointer_x, pointer_y);
1399 /* convert to root window coordinates */
1402 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1404 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1406 /* we don't want the pointer to be inside the window when it is
1407 * displayed, because then we generate a leave/enter event pair when
1408 * the window is displayed then hidden - the enter event will
1409 * trigger a new tooltip timeout.
1411 * So move the window right of the pointer position by just a enough
1412 * to get it away from the pointer.
1415 tooltip_window_origin.x += 30;
1416 tooltip_window_origin.y += 45;
1418 /* move the tooltip window into position */
1420 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1424 tooltip_window->present ();
1426 /* called from a timeout handler, don't call it again */
1432 GtkCanvas::hide_tooltip ()
1434 /* hide it if its there */
1436 if (tooltip_window) {
1437 tooltip_window->hide ();
1439 // Delete the tooltip window so it'll get re-created
1440 // (i.e. properly re-sized) on the next usage.
1441 delete tooltip_window;
1442 tooltip_window = NULL;
1446 Glib::RefPtr<Pango::Context>
1447 GtkCanvas::get_pango_context ()
1449 return Glib::wrap (gdk_pango_context_get());
1452 /** Create a GtkCanvaSViewport.
1453 * @param hadj Adjustment to use for horizontal scrolling.
1454 * @param vadj Adjustment to use for vertica scrolling.
1456 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1457 : Alignment (0, 0, 1.0, 1.0)
1458 , hadjustment (hadj)
1459 , vadjustment (vadj)
1463 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1464 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1468 GtkCanvasViewport::scrolled ()
1470 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1474 /** Handler for when GTK asks us what minimum size we want.
1475 * @param req Requsition to fill in.
1478 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1480 /* force the canvas to size itself */
1481 // _canvas.root()->bounding_box();