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"
49 #include "gtkmm2ext/nsglview.h"
53 using namespace ArdourCanvas;
55 uint32_t Canvas::tooltip_timeout_msecs = 750;
57 /** Construct a new Canvas */
60 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
61 , _last_render_start_timestamp(0)
67 Canvas::scroll_to (Coord x, Coord y)
69 /* We do things this way because we do not want to recurse through
70 the canvas for every scroll. In the presence of large MIDI
71 tracks this means traversing item lists that include
72 thousands of items (notes).
74 This design limits us to moving only those items (groups, typically)
75 that should move in certain ways as we scroll. In other terms, it
76 becomes O(1) rather than O(N).
79 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
80 (*i)->scroll_to (Duple (x, y));
83 pick_current_item (0); // no current mouse position
87 Canvas::add_scroller (ScrollGroup& i)
89 scrollers.push_back (&i);
95 pick_current_item (0); // no current mouse position
98 /** Render an area of the canvas.
99 * @param area Area in window coordinates.
100 * @param context Cairo context to render to.
103 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
105 PreRender (); // emit signal
107 _last_render_start_timestamp = g_get_monotonic_time();
110 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
111 cerr << this << " RENDER: " << area << endl;
112 //cerr << "CANVAS @ " << this << endl;
114 //cerr << "-------------------------\n";
120 Rect root_bbox = _root.bounding_box();
122 /* the root has no bounding box, so there's nothing to render */
126 Rect draw = root_bbox.intersection (area);
129 /* there's a common area between the root and the requested
133 _root.render (draw, context);
135 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
136 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
137 // This transparently colors the rect being rendered, after it has been drawn.
138 double r = (random() % 65536) /65536.0;
139 double g = (random() % 65536) /65536.0;
140 double b = (random() % 65536) /65536.0;
141 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
142 context->set_source_rgba (r, g, b, 0.25);
151 Canvas::prepare_for_render (Rect const & area) const
153 Rect root_bbox = _root.bounding_box();
155 /* the root has no bounding box, so there's nothing to render */
159 Rect draw = root_bbox.intersection (area);
162 _root.prepare_for_render (draw);
167 operator<< (ostream& o, Canvas& c)
174 Canvas::indent() const
178 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
186 Canvas::render_indent() const
190 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
198 Canvas::dump (ostream& o) const
204 /** Called when an item has been shown or hidden.
205 * @param item Item that has been shown or hidden.
208 Canvas::item_shown_or_hidden (Item* item)
210 Rect bbox = item->bounding_box ();
212 if (item->item_to_window (bbox).intersection (visible_area ())) {
213 queue_draw_item_area (item, bbox);
218 /** Called when an item has a change to its visual properties
219 * that do NOT affect its bounding box.
220 * @param item Item that has been modified.
223 Canvas::item_visual_property_changed (Item* item)
225 Rect bbox = item->bounding_box ();
227 if (item->item_to_window (bbox).intersection (visible_area ())) {
228 queue_draw_item_area (item, bbox);
233 /** Called when an item has changed, but not moved.
234 * @param item Item that has changed.
235 * @param pre_change_bounding_box The bounding box of item before the change,
236 * in the item's coordinates.
239 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
241 Rect window_bbox = visible_area ();
243 if (pre_change_bounding_box) {
244 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
245 /* request a redraw of the item's old bounding box */
246 queue_draw_item_area (item, pre_change_bounding_box);
250 Rect post_change_bounding_box = item->bounding_box ();
252 if (post_change_bounding_box) {
253 Rect const window_intersection =
254 item->item_to_window (post_change_bounding_box).intersection (window_bbox);
256 if (window_intersection) {
257 /* request a redraw of the item's new bounding box */
258 queue_draw_item_area (item, post_change_bounding_box);
260 // Allow item to do any work necessary to prepare for being rendered.
261 item->prepare_for_render (window_intersection);
263 // No intersection with visible window area
269 Canvas::window_to_canvas (Duple const & d) const
271 ScrollGroup* best_group = 0;
274 /* if the coordinates are negative, clamp to zero and find the item
275 * that covers that "edge" position.
280 if (in_window.x < 0) {
283 if (in_window.y < 0) {
287 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
289 if ((*s)->covers_window (in_window)) {
292 /* XXX January 22nd 2015: leaving this in place for now
293 * but I think it fixes a bug that really should be
294 * fixed in a different way (and will be) by my next
295 * commit. But it may still be relevant.
298 /* If scroll groups overlap, choose the one with the highest sensitivity,
299 that is, choose an HV scroll group over an H or V
302 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
304 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
305 /* Can't do any better than this. */
313 return d.translate (best_group->scroll_offset());
320 Canvas::canvas_to_window (Duple const & d, bool rounded) const
322 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
323 * as children of the root group, so we just scan its first level
324 * children and see what we can find.
327 std::list<Item*> const& root_children (_root.items());
331 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
332 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
338 wd = d.translate (-sg->scroll_offset());
343 /* Note that this intentionally almost always returns integer coordinates */
353 /** Called when an item has moved.
354 * @param item Item that has moved.
355 * @param pre_change_parent_bounding_box The bounding box of the item before
356 * the move, in its parent's coordinates.
359 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
361 if (pre_change_parent_bounding_box) {
362 /* request a redraw of where the item used to be. The box has
363 * to be in parent coordinate space since the bounding box of
364 * an item does not change when moved. If we use
365 * item->item_to_canvas() on the old bounding box, we will be
367 * using the item's new position, and so will compute the wrong
368 * invalidation area. If we use the parent (which has not
369 * moved, then this will work.
371 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
374 Rect post_change_bounding_box = item->bounding_box ();
375 if (post_change_bounding_box) {
376 /* request a redraw of where the item now is */
377 queue_draw_item_area (item, post_change_bounding_box);
381 /** Request a redraw of a particular area in an item's coordinates.
383 * @param area Area to redraw in the item's coordinates.
386 Canvas::queue_draw_item_area (Item* item, Rect area)
388 request_redraw (item->item_to_window (area));
392 Canvas::set_tooltip_timeout (uint32_t msecs)
394 tooltip_timeout_msecs = msecs;
398 Canvas::set_background_color (Color c)
402 Rect r = _root.bounding_box();
405 request_redraw (_root.item_to_window (r));
410 GtkCanvas::re_enter ()
412 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
414 pick_current_item (0);
417 /** Construct a GtkCanvas */
418 GtkCanvas::GtkCanvas ()
420 , _new_current_item (0)
423 , _single_exposure (1)
424 , current_tooltip_item (0)
429 /* these are the events we want to know about */
430 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
431 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
432 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
436 GtkCanvas::use_nsglview ()
439 assert (!is_realized());
440 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
441 _nsglview = Gtkmm2ext::nsglview_create (this);
446 GtkCanvas::pick_current_item (int state)
451 /* this version of ::pick_current_item() is called after an item is
452 * added or removed, so we have no coordinates to work from as is the
453 * case with a motion event. Find out where the mouse is and use that.
456 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
458 if (pointer_window != get_window()) {
462 pick_current_item (Duple (x, y), state);
465 /** Given @param point (a position in window coordinates)
466 * and mouse state @param state, check to see if _current_item
467 * (which will be used to deliver events) should change.
470 GtkCanvas::pick_current_item (Duple const & point, int state)
472 /* we do not enter/leave items during a drag/grab */
478 /* find the items at the given window position */
480 vector<Item const *> items;
481 _root.add_items_at_point (point, items);
483 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
486 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
487 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
489 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
491 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
497 /* put all items at point that are event-sensitive and visible and NOT
498 groups into within_items. Note that items is sorted from bottom to
499 top, but we're going to reverse that for within_items so that its
500 first item is the upper-most item that can be chosen as _current_item.
503 vector<Item const *>::const_iterator i;
504 list<Item const *> within_items;
506 for (i = items.begin(); i != items.end(); ++i) {
508 Item const * possible_item = *i;
510 /* We ignore invisible items, containers and items that ignore events */
512 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
515 within_items.push_front (possible_item);
518 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
520 if (within_items.empty()) {
522 /* no items at point, just send leave event below */
523 _new_current_item = 0;
527 if (within_items.front() == _current_item) {
528 /* uppermost item at point is already _current_item */
529 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
533 _new_current_item = const_cast<Item*> (within_items.front());
536 if (_new_current_item != _current_item) {
537 deliver_enter_leave (point, state);
541 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
543 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
548 /** Deliver a series of enter & leave events based on the pointer position being at window
549 * coordinate @param point, and pointer @param state (modifier keys, etc)
552 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
554 /* setup enter & leave event structures */
556 Glib::RefPtr<Gdk::Window> win = get_window();
562 GdkEventCrossing enter_event;
563 enter_event.type = GDK_ENTER_NOTIFY;
564 enter_event.window = win->gobj();
565 enter_event.send_event = 0;
566 enter_event.subwindow = 0;
567 enter_event.mode = GDK_CROSSING_NORMAL;
568 enter_event.focus = FALSE;
569 enter_event.state = state;
571 /* Events delivered to canvas items are expected to be in canvas
572 * coordinates but @param point is in window coordinates.
575 Duple c = window_to_canvas (point);
579 GdkEventCrossing leave_event = enter_event;
580 leave_event.type = GDK_LEAVE_NOTIFY;
583 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
584 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
585 vector<Item*> items_to_leave_virtual;
586 vector<Item*> items_to_enter_virtual;
588 if (_new_current_item == 0) {
590 leave_detail = GDK_NOTIFY_UNKNOWN;
594 /* no current item, so also send virtual leave events to the
595 * entire heirarchy for the current item
598 for (i = _current_item->parent(); i ; i = i->parent()) {
599 items_to_leave_virtual.push_back (i);
603 } else if (_current_item == 0) {
605 enter_detail = GDK_NOTIFY_UNKNOWN;
607 /* no current item, so also send virtual enter events to the
608 * entire heirarchy for the new item
611 for (i = _new_current_item->parent(); i ; i = i->parent()) {
612 items_to_enter_virtual.push_back (i);
615 } else if (_current_item->is_descendant_of (*_new_current_item)) {
617 /* move from descendant to ancestor (X: "_current_item is an
618 * inferior ("child") of _new_current_item")
620 * Deliver "virtual" leave notifications to all items in the
621 * heirarchy between current and new_current.
624 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
625 items_to_leave_virtual.push_back (i);
628 enter_detail = GDK_NOTIFY_INFERIOR;
629 leave_detail = GDK_NOTIFY_ANCESTOR;
631 } else if (_new_current_item->is_descendant_of (*_current_item)) {
632 /* move from ancestor to descendant (X: "_new_current_item is
633 * an inferior ("child") of _current_item")
635 * Deliver "virtual" enter notifications to all items in the
636 * heirarchy between current and new_current.
639 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
640 items_to_enter_virtual.push_back (i);
643 enter_detail = GDK_NOTIFY_ANCESTOR;
644 leave_detail = GDK_NOTIFY_INFERIOR;
648 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
650 /* deliver virtual leave events to everything between _current
651 * and common_ancestor.
654 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
655 items_to_leave_virtual.push_back (i);
658 /* deliver virtual enter events to everything between
659 * _new_current and common_ancestor.
662 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
663 items_to_enter_virtual.push_back (i);
666 enter_detail = GDK_NOTIFY_NONLINEAR;
667 leave_detail = GDK_NOTIFY_NONLINEAR;
671 if (_current_item && !_current_item->ignore_events ()) {
672 leave_event.detail = leave_detail;
673 _current_item->Event ((GdkEvent*)&leave_event);
674 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
677 leave_event.detail = GDK_NOTIFY_VIRTUAL;
678 enter_event.detail = GDK_NOTIFY_VIRTUAL;
680 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
681 if (!(*it)->ignore_events()) {
682 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
683 (*it)->Event ((GdkEvent*)&leave_event);
687 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
688 if (!(*it)->ignore_events()) {
689 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
690 (*it)->Event ((GdkEvent*)&enter_event);
691 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
695 if (_new_current_item && !_new_current_item->ignore_events()) {
696 enter_event.detail = enter_detail;
697 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
698 start_tooltip_timeout (_new_current_item);
699 _new_current_item->Event ((GdkEvent*)&enter_event);
702 _current_item = _new_current_item;
706 /** Deliver an event to the appropriate item; either the grabbed item, or
707 * one of the items underneath the event.
708 * @param point Position that the event has occurred at, in canvas coordinates.
709 * @param event The event.
712 GtkCanvas::deliver_event (GdkEvent* event)
714 /* Point in in canvas coordinate space */
716 const Item* event_item;
719 /* we have a grabbed item, so everything gets sent there */
720 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
721 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
722 event_item = _grabbed_item;
724 event_item = _current_item;
731 /* run through the items from child to parent, until one claims the event */
733 Item* item = const_cast<Item*> (event_item);
737 Item* parent = item->parent ();
739 if (!item->ignore_events () &&
740 item->Event (event)) {
741 /* this item has just handled the event */
743 PBD::DEBUG::CanvasEvents,
744 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
750 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)));
752 if ((item = parent) == 0) {
762 GtkCanvas::item_shown_or_hidden (Item* item)
764 if (item == current_tooltip_item) {
765 stop_tooltip_timeout ();
767 Canvas::item_shown_or_hidden (item);
770 /** Called when an item is being destroyed.
771 * @param item Item being destroyed.
772 * @param bounding_box Last known bounding box of the item.
775 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
778 queue_draw_item_area (item, bounding_box);
781 if (_new_current_item == item) {
782 _new_current_item = 0;
785 if (_grabbed_item == item) {
789 if (_focused_item == item) {
793 if (current_tooltip_item) {
794 current_tooltip_item = 0;
795 stop_tooltip_timeout ();
798 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
800 scrollers.remove (sg);
803 if (_current_item == item) {
804 /* no need to send a leave event to this item, since it is going away
807 pick_current_item (0); // no mouse state
813 GtkCanvas::on_realize ()
815 Gtk::EventBox::on_realize();
818 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
824 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
826 EventBox::on_size_allocate (a);
827 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
828 if (getenv("ARDOUR_IMAGE_SURFACE")) {
830 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
831 /* allocate an image surface as large as the canvas itself */
833 canvas_image.clear ();
834 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
836 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
843 gtk_widget_translate_coordinates(
845 GTK_WIDGET(get_toplevel()->gobj()),
847 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
853 /** Handler for GDK expose events.
855 * @return true if the event was handled.
858 GtkCanvas::on_expose_event (GdkEventExpose* ev)
865 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
870 #ifdef CANVAS_PROFILE
871 const int64_t start = g_get_monotonic_time ();
874 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
875 Cairo::RefPtr<Cairo::Context> draw_context;
876 Cairo::RefPtr<Cairo::Context> window_context;
877 if (getenv("ARDOUR_IMAGE_SURFACE")) {
879 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
881 draw_context = Cairo::Context::create (canvas_image);
882 window_context = get_window()->create_cairo_context ();
884 draw_context = get_window()->create_cairo_context ();
886 #elif defined USE_CAIRO_IMAGE_SURFACE
888 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
890 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
891 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
893 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
896 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
897 draw_context->clip();
900 /* group calls cairo_quartz_surface_create() which
901 * effectively uses a CGBitmapContext + image-surface
903 * This avoids expensive argb32_image_mark_image() during drawing.
904 * Although the final paint() operation still takes the slow path
905 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
907 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
908 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
910 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
912 draw_context->push_group ();
915 /* draw background color */
916 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
917 set_source_rgba (draw_context, _bg_color);
918 draw_context->fill ();
921 if ( _single_exposure ) {
923 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
929 gdk_region_get_rectangles (ev->region, &rects, &nrects);
930 for (gint n = 0; n < nrects; ++n) {
931 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
932 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
938 draw_context->pop_group_to_source ();
939 draw_context->paint ();
942 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
943 if (getenv("ARDOUR_IMAGE_SURFACE")) {
945 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
946 /* now blit our private surface back to the GDK one */
948 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
949 window_context->clip ();
950 window_context->set_source (canvas_image, 0, 0);
951 window_context->set_operator (Cairo::OPERATOR_SOURCE);
952 window_context->paint ();
954 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
958 #ifdef CANVAS_PROFILE
959 const int64_t end = g_get_monotonic_time ();
960 const int64_t elapsed = end - start;
961 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
968 GtkCanvas::prepare_for_render () const
970 Rect window_bbox = visible_area ();
971 Canvas::prepare_for_render (window_bbox);
974 /** Handler for GDK scroll events.
976 * @return true if the event was handled.
979 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
981 /* translate event coordinates from window to canvas */
983 GdkEvent copy = *((GdkEvent*)ev);
984 Duple winpos = Duple (ev->x, ev->y);
985 Duple where = window_to_canvas (winpos);
987 pick_current_item (winpos, ev->state);
989 copy.button.x = where.x;
990 copy.button.y = where.y;
992 /* Coordinates in the event will be canvas coordinates, correctly adjusted
993 for scroll if this GtkCanvas is in a GtkCanvasViewport.
996 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
997 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1000 /** Handler for GDK key press events.
1002 * @return true if the event was handled.
1005 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1007 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1008 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1011 /** Handler for GDK key release events.
1013 * @return true if the event was handled.
1016 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1018 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1019 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1022 /** Handler for GDK button press events.
1024 * @return true if the event was handled.
1027 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1029 /* translate event coordinates from window to canvas */
1031 GdkEvent copy = *((GdkEvent*)ev);
1032 Duple winpos = Duple (ev->x, ev->y);
1033 Duple where = window_to_canvas (winpos);
1035 pick_current_item (winpos, ev->state);
1037 copy.button.x = where.x;
1038 copy.button.y = where.y;
1040 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1041 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1044 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1045 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1048 /** Handler for GDK button release events.
1050 * @return true if the event was handled.
1053 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1055 /* translate event coordinates from window to canvas */
1057 GdkEvent copy = *((GdkEvent*)ev);
1058 Duple winpos = Duple (ev->x, ev->y);
1059 Duple where = window_to_canvas (winpos);
1061 pick_current_item (winpos, ev->state);
1063 copy.button.x = where.x;
1064 copy.button.y = where.y;
1066 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1067 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1070 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1071 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1075 GtkCanvas::get_mouse_position (Duple& winpos) const
1079 Gdk::ModifierType mask;
1080 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1083 std::cerr << " no self window\n";
1084 winpos = Duple (0, 0);
1088 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1096 /** Handler for GDK motion events.
1098 * @return true if the event was handled.
1101 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1105 /* translate event coordinates from window to canvas */
1107 GdkEvent copy = *((GdkEvent*)ev);
1108 Duple point (ev->x, ev->y);
1109 Duple where = window_to_canvas (point);
1111 copy.motion.x = where.x;
1112 copy.motion.y = where.y;
1114 /* Coordinates in "copy" will be canvas coordinates,
1117 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));
1119 MouseMotion (point); /* EMIT SIGNAL */
1121 pick_current_item (point, ev->state);
1123 /* Now deliver the motion event. It may seem a little inefficient
1124 to recompute the items under the event, but the enter notify/leave
1125 events may have deleted canvas items so it is important to
1126 recompute the list in deliver_event.
1129 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1133 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1135 pick_current_item (Duple (ev->x, ev->y), ev->state);
1140 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1142 switch (ev->detail) {
1143 case GDK_NOTIFY_ANCESTOR:
1144 case GDK_NOTIFY_UNKNOWN:
1145 case GDK_NOTIFY_VIRTUAL:
1146 case GDK_NOTIFY_NONLINEAR:
1147 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1148 /* leaving window, cancel any tooltips */
1149 stop_tooltip_timeout ();
1153 /* we don't care about any other kind
1154 of leave event (notably GDK_NOTIFY_INFERIOR)
1158 _new_current_item = 0;
1159 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1164 GtkCanvas::on_map ()
1166 Gtk::EventBox::on_map();
1169 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1170 Gtk::Allocation a = get_allocation();
1172 gtk_widget_translate_coordinates(
1174 GTK_WIDGET(get_toplevel()->gobj()),
1176 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1182 GtkCanvas::on_unmap ()
1184 stop_tooltip_timeout ();
1185 Gtk::EventBox::on_unmap();
1188 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1193 /** Called to request a redraw of our canvas.
1194 * @param area Area to redraw, in window coordinates.
1197 GtkCanvas::request_redraw (Rect const & request)
1203 /* clamp area requested to actual visible window */
1205 Rect real_area = request.intersection (visible_area());
1208 if (real_area.width () && real_area.height ()) {
1209 // Item intersects with visible canvas area
1210 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1214 // Item does not intersect with visible canvas area
1218 /** Called to request that we try to get a particular size for ourselves.
1219 * @param size Size to request, in pixels.
1222 GtkCanvas::request_size (Duple size)
1226 if (req.x > INT_MAX) {
1230 if (req.y > INT_MAX) {
1234 set_size_request (req.x, req.y);
1237 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1238 * This is typically used for dragging items around, so that they are grabbed during
1240 * @param item Item to grab.
1243 GtkCanvas::grab (Item* item)
1245 /* XXX: should this be doing gdk_pointer_grab? */
1246 _grabbed_item = item;
1250 /** `Ungrab' any item that was previously grabbed */
1252 GtkCanvas::ungrab ()
1254 /* XXX: should this be doing gdk_pointer_ungrab? */
1258 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1260 * @param item Item to grab.
1263 GtkCanvas::focus (Item* item)
1265 _focused_item = item;
1269 GtkCanvas::unfocus (Item* item)
1271 if (item == _focused_item) {
1276 /** @return The visible area of the canvas, in window coordinates */
1278 GtkCanvas::visible_area () const
1280 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1284 GtkCanvas::width() const
1286 return get_allocation().get_width();
1290 GtkCanvas::height() const
1292 return get_allocation().get_height();
1296 GtkCanvas::start_tooltip_timeout (Item* item)
1298 stop_tooltip_timeout ();
1300 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1301 current_tooltip_item = item;
1303 /* wait for the first idle that happens after this is
1304 called. this means that we've stopped processing events, which
1305 in turn implies that the user has stopped doing stuff for a
1309 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1314 GtkCanvas::really_start_tooltip_timeout ()
1316 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1317 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1320 if (current_tooltip_item) {
1321 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1324 return false; /* this is called from an idle callback, don't call it again */
1328 GtkCanvas::stop_tooltip_timeout ()
1330 current_tooltip_item = 0;
1331 tooltip_timeout_connection.disconnect ();
1335 GtkCanvas::show_tooltip ()
1337 Rect tooltip_item_bbox;
1339 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1343 if (!tooltip_window) {
1344 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1345 tooltip_label = manage (new Gtk::Label);
1346 tooltip_label->show ();
1347 tooltip_window->add (*tooltip_label);
1348 tooltip_window->set_border_width (1);
1349 tooltip_window->set_name ("tooltip");
1352 tooltip_label->set_text (current_tooltip_item->tooltip());
1354 /* figure out where to position the tooltip */
1356 Gtk::Widget* toplevel = get_toplevel();
1358 int pointer_x, pointer_y;
1359 Gdk::ModifierType mask;
1361 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1363 Duple tooltip_window_origin (pointer_x, pointer_y);
1365 /* convert to root window coordinates */
1368 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1370 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1372 /* we don't want the pointer to be inside the window when it is
1373 * displayed, because then we generate a leave/enter event pair when
1374 * the window is displayed then hidden - the enter event will
1375 * trigger a new tooltip timeout.
1377 * So move the window right of the pointer position by just a enough
1378 * to get it away from the pointer.
1381 tooltip_window_origin.x += 30;
1382 tooltip_window_origin.y += 45;
1384 /* move the tooltip window into position */
1386 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1390 tooltip_window->present ();
1392 /* called from a timeout handler, don't call it again */
1398 GtkCanvas::hide_tooltip ()
1400 /* hide it if its there */
1402 if (tooltip_window) {
1403 tooltip_window->hide ();
1405 // Delete the tooltip window so it'll get re-created
1406 // (i.e. properly re-sized) on the next usage.
1407 delete tooltip_window;
1408 tooltip_window = NULL;
1412 Glib::RefPtr<Pango::Context>
1413 GtkCanvas::get_pango_context ()
1415 return Glib::wrap (gdk_pango_context_get());
1418 /** Create a GtkCanvaSViewport.
1419 * @param hadj Adjustment to use for horizontal scrolling.
1420 * @param vadj Adjustment to use for vertica scrolling.
1422 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1423 : Alignment (0, 0, 1.0, 1.0)
1424 , hadjustment (hadj)
1425 , vadjustment (vadj)
1429 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1430 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1434 GtkCanvasViewport::scrolled ()
1436 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1440 /** Handler for when GTK asks us what minimum size we want.
1441 * @param req Requsition to fill in.
1444 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1446 /* force the canvas to size itself */
1447 // _canvas.root()->bounding_box();