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)
876 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
881 #ifdef CANVAS_PROFILE
882 const int64_t start = g_get_monotonic_time ();
885 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
886 Cairo::RefPtr<Cairo::Context> draw_context;
887 Cairo::RefPtr<Cairo::Context> window_context;
888 if (getenv("ARDOUR_IMAGE_SURFACE")) {
890 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
892 draw_context = Cairo::Context::create (canvas_image);
893 window_context = get_window()->create_cairo_context ();
895 draw_context = get_window()->create_cairo_context ();
897 #elif defined USE_CAIRO_IMAGE_SURFACE
899 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
901 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
902 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
904 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
907 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
908 draw_context->clip();
911 /* group calls cairo_quartz_surface_create() which
912 * effectively uses a CGBitmapContext + image-surface
914 * This avoids expensive argb32_image_mark_image() during drawing.
915 * Although the final paint() operation still takes the slow path
916 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
918 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
919 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
921 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
923 draw_context->push_group ();
926 /* draw background color */
927 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
928 Gtkmm2ext::set_source_rgba (draw_context, _bg_color);
929 draw_context->fill ();
932 if ( _single_exposure ) {
934 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
940 gdk_region_get_rectangles (ev->region, &rects, &nrects);
941 for (gint n = 0; n < nrects; ++n) {
942 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
943 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
949 draw_context->pop_group_to_source ();
950 draw_context->paint ();
953 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
954 if (getenv("ARDOUR_IMAGE_SURFACE")) {
956 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
957 /* now blit our private surface back to the GDK one */
959 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
960 window_context->clip ();
961 window_context->set_source (canvas_image, 0, 0);
962 window_context->set_operator (Cairo::OPERATOR_SOURCE);
963 window_context->paint ();
965 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
969 #ifdef CANVAS_PROFILE
970 const int64_t end = g_get_monotonic_time ();
971 const int64_t elapsed = end - start;
972 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
979 GtkCanvas::prepare_for_render () const
981 Rect window_bbox = visible_area ();
982 Canvas::prepare_for_render (window_bbox);
985 /** Handler for GDK scroll events.
987 * @return true if the event was handled.
990 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
992 /* translate event coordinates from window to canvas */
994 GdkEvent copy = *((GdkEvent*)ev);
995 Duple winpos = Duple (ev->x, ev->y);
996 Duple where = window_to_canvas (winpos);
998 pick_current_item (winpos, ev->state);
1000 copy.button.x = where.x;
1001 copy.button.y = where.y;
1003 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1004 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1007 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
1008 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1011 /** Handler for GDK key press events.
1013 * @return true if the event was handled.
1016 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1018 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1019 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1022 /** Handler for GDK key release events.
1024 * @return true if the event was handled.
1027 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1029 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1030 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1033 /** Handler for GDK button press events.
1035 * @return true if the event was handled.
1038 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1040 /* translate event coordinates from window to canvas */
1042 GdkEvent copy = *((GdkEvent*)ev);
1043 Duple winpos = Duple (ev->x, ev->y);
1044 Duple where = window_to_canvas (winpos);
1046 pick_current_item (winpos, ev->state);
1048 copy.button.x = where.x;
1049 copy.button.y = where.y;
1051 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1052 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1055 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1056 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1059 /** Handler for GDK button release events.
1061 * @return true if the event was handled.
1064 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1066 /* translate event coordinates from window to canvas */
1068 GdkEvent copy = *((GdkEvent*)ev);
1069 Duple winpos = Duple (ev->x, ev->y);
1070 Duple where = window_to_canvas (winpos);
1072 pick_current_item (winpos, ev->state);
1074 copy.button.x = where.x;
1075 copy.button.y = where.y;
1077 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1078 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1081 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1082 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1086 GtkCanvas::get_mouse_position (Duple& winpos) const
1090 Gdk::ModifierType mask;
1091 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1094 std::cerr << " no self window\n";
1095 winpos = Duple (0, 0);
1099 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1107 /** Handler for GDK motion events.
1109 * @return true if the event was handled.
1112 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1116 /* translate event coordinates from window to canvas */
1118 GdkEvent copy = *((GdkEvent*)ev);
1119 Duple point (ev->x, ev->y);
1120 Duple where = window_to_canvas (point);
1122 copy.motion.x = where.x;
1123 copy.motion.y = where.y;
1125 /* Coordinates in "copy" will be canvas coordinates,
1128 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));
1130 MouseMotion (point); /* EMIT SIGNAL */
1132 pick_current_item (point, ev->state);
1134 /* Now deliver the motion event. It may seem a little inefficient
1135 to recompute the items under the event, but the enter notify/leave
1136 events may have deleted canvas items so it is important to
1137 recompute the list in deliver_event.
1140 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1144 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1146 pick_current_item (Duple (ev->x, ev->y), ev->state);
1151 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1153 switch (ev->detail) {
1154 case GDK_NOTIFY_ANCESTOR:
1155 case GDK_NOTIFY_UNKNOWN:
1156 case GDK_NOTIFY_VIRTUAL:
1157 case GDK_NOTIFY_NONLINEAR:
1158 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1159 /* leaving window, cancel any tooltips */
1160 stop_tooltip_timeout ();
1164 /* we don't care about any other kind
1165 of leave event (notably GDK_NOTIFY_INFERIOR)
1169 _new_current_item = 0;
1170 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1175 GtkCanvas::on_map ()
1177 Gtk::EventBox::on_map();
1180 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1181 Gtk::Allocation a = get_allocation();
1183 gtk_widget_translate_coordinates(
1185 GTK_WIDGET(get_toplevel()->gobj()),
1187 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1193 GtkCanvas::on_unmap ()
1195 stop_tooltip_timeout ();
1196 Gtk::EventBox::on_unmap();
1199 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1204 /** Called to request a redraw of our canvas.
1205 * @param area Area to redraw, in window coordinates.
1208 GtkCanvas::request_redraw (Rect const & request)
1214 /* clamp area requested to actual visible window */
1216 Rect real_area = request.intersection (visible_area());
1219 if (real_area.width () && real_area.height ()) {
1220 // Item intersects with visible canvas area
1221 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1225 // Item does not intersect with visible canvas area
1229 /** Called to request that we try to get a particular size for ourselves.
1230 * @param size Size to request, in pixels.
1233 GtkCanvas::request_size (Duple size)
1237 if (req.x > INT_MAX) {
1241 if (req.y > INT_MAX) {
1245 set_size_request (req.x, req.y);
1248 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1249 * This is typically used for dragging items around, so that they are grabbed during
1251 * @param item Item to grab.
1254 GtkCanvas::grab (Item* item)
1256 /* XXX: should this be doing gdk_pointer_grab? */
1257 _grabbed_item = item;
1261 /** `Ungrab' any item that was previously grabbed */
1263 GtkCanvas::ungrab ()
1265 /* XXX: should this be doing gdk_pointer_ungrab? */
1269 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1271 * @param item Item to grab.
1274 GtkCanvas::focus (Item* item)
1276 _focused_item = item;
1280 GtkCanvas::unfocus (Item* item)
1282 if (item == _focused_item) {
1287 /** @return The visible area of the canvas, in window coordinates */
1289 GtkCanvas::visible_area () const
1291 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1295 GtkCanvas::width() const
1297 return get_allocation().get_width();
1301 GtkCanvas::height() const
1303 return get_allocation().get_height();
1307 GtkCanvas::start_tooltip_timeout (Item* item)
1309 stop_tooltip_timeout ();
1311 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1312 current_tooltip_item = item;
1314 /* wait for the first idle that happens after this is
1315 called. this means that we've stopped processing events, which
1316 in turn implies that the user has stopped doing stuff for a
1320 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1325 GtkCanvas::really_start_tooltip_timeout ()
1327 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1328 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1331 if (current_tooltip_item) {
1332 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1335 return false; /* this is called from an idle callback, don't call it again */
1339 GtkCanvas::stop_tooltip_timeout ()
1341 current_tooltip_item = 0;
1342 tooltip_timeout_connection.disconnect ();
1346 GtkCanvas::show_tooltip ()
1348 Rect tooltip_item_bbox;
1350 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1354 if (!tooltip_window) {
1355 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1356 tooltip_label = manage (new Gtk::Label);
1357 tooltip_label->show ();
1358 tooltip_window->add (*tooltip_label);
1359 tooltip_window->set_border_width (1);
1360 tooltip_window->set_name ("tooltip");
1363 tooltip_label->set_text (current_tooltip_item->tooltip());
1365 /* figure out where to position the tooltip */
1367 Gtk::Widget* toplevel = get_toplevel();
1369 int pointer_x, pointer_y;
1370 Gdk::ModifierType mask;
1372 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1374 Duple tooltip_window_origin (pointer_x, pointer_y);
1376 /* convert to root window coordinates */
1379 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1381 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1383 /* we don't want the pointer to be inside the window when it is
1384 * displayed, because then we generate a leave/enter event pair when
1385 * the window is displayed then hidden - the enter event will
1386 * trigger a new tooltip timeout.
1388 * So move the window right of the pointer position by just a enough
1389 * to get it away from the pointer.
1392 tooltip_window_origin.x += 30;
1393 tooltip_window_origin.y += 45;
1395 /* move the tooltip window into position */
1397 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1401 tooltip_window->present ();
1403 /* called from a timeout handler, don't call it again */
1409 GtkCanvas::hide_tooltip ()
1411 /* hide it if its there */
1413 if (tooltip_window) {
1414 tooltip_window->hide ();
1416 // Delete the tooltip window so it'll get re-created
1417 // (i.e. properly re-sized) on the next usage.
1418 delete tooltip_window;
1419 tooltip_window = NULL;
1423 Glib::RefPtr<Pango::Context>
1424 GtkCanvas::get_pango_context ()
1426 return Glib::wrap (gdk_pango_context_get());
1429 /** Create a GtkCanvaSViewport.
1430 * @param hadj Adjustment to use for horizontal scrolling.
1431 * @param vadj Adjustment to use for vertica scrolling.
1433 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1434 : Alignment (0, 0, 1.0, 1.0)
1435 , hadjustment (hadj)
1436 , vadjustment (vadj)
1440 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1441 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1445 GtkCanvasViewport::scrolled ()
1447 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1451 /** Handler for when GTK asks us what minimum size we want.
1452 * @param req Requsition to fill in.
1455 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1457 /* force the canvas to size itself */
1458 // _canvas.root()->bounding_box();