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 Canvas::get_microseconds_since_render_start () const
169 gint64 timestamp = g_get_monotonic_time();
171 if (_last_render_start_timestamp == 0 || timestamp <= _last_render_start_timestamp) {
175 return timestamp - _last_render_start_timestamp;
179 operator<< (ostream& o, Canvas& c)
186 Canvas::indent() const
190 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
198 Canvas::render_indent() const
202 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
210 Canvas::dump (ostream& o) const
216 /** Called when an item has been shown or hidden.
217 * @param item Item that has been shown or hidden.
220 Canvas::item_shown_or_hidden (Item* item)
222 Rect bbox = item->bounding_box ();
224 if (item->item_to_window (bbox).intersection (visible_area ())) {
225 queue_draw_item_area (item, bbox);
230 /** Called when an item has a change to its visual properties
231 * that do NOT affect its bounding box.
232 * @param item Item that has been modified.
235 Canvas::item_visual_property_changed (Item* item)
237 Rect bbox = item->bounding_box ();
239 if (item->item_to_window (bbox).intersection (visible_area ())) {
240 queue_draw_item_area (item, bbox);
245 /** Called when an item has changed, but not moved.
246 * @param item Item that has changed.
247 * @param pre_change_bounding_box The bounding box of item before the change,
248 * in the item's coordinates.
251 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
253 Rect window_bbox = visible_area ();
255 if (pre_change_bounding_box) {
256 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
257 /* request a redraw of the item's old bounding box */
258 queue_draw_item_area (item, pre_change_bounding_box);
262 Rect post_change_bounding_box = item->bounding_box ();
264 if (post_change_bounding_box) {
265 Rect const window_intersection =
266 item->item_to_window (post_change_bounding_box).intersection (window_bbox);
268 if (window_intersection) {
269 /* request a redraw of the item's new bounding box */
270 queue_draw_item_area (item, post_change_bounding_box);
272 // Allow item to do any work necessary to prepare for being rendered.
273 item->prepare_for_render (window_intersection);
275 // No intersection with visible window area
281 Canvas::window_to_canvas (Duple const & d) const
283 ScrollGroup* best_group = 0;
286 /* if the coordinates are negative, clamp to zero and find the item
287 * that covers that "edge" position.
292 if (in_window.x < 0) {
295 if (in_window.y < 0) {
299 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
301 if ((*s)->covers_window (in_window)) {
304 /* XXX January 22nd 2015: leaving this in place for now
305 * but I think it fixes a bug that really should be
306 * fixed in a different way (and will be) by my next
307 * commit. But it may still be relevant.
310 /* If scroll groups overlap, choose the one with the highest sensitivity,
311 that is, choose an HV scroll group over an H or V
314 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
316 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
317 /* Can't do any better than this. */
325 return d.translate (best_group->scroll_offset());
332 Canvas::canvas_to_window (Duple const & d, bool rounded) const
334 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
335 * as children of the root group, so we just scan its first level
336 * children and see what we can find.
339 std::list<Item*> const& root_children (_root.items());
343 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
344 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
350 wd = d.translate (-sg->scroll_offset());
355 /* Note that this intentionally almost always returns integer coordinates */
365 /** Called when an item has moved.
366 * @param item Item that has moved.
367 * @param pre_change_parent_bounding_box The bounding box of the item before
368 * the move, in its parent's coordinates.
371 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
373 if (pre_change_parent_bounding_box) {
374 /* request a redraw of where the item used to be. The box has
375 * to be in parent coordinate space since the bounding box of
376 * an item does not change when moved. If we use
377 * item->item_to_canvas() on the old bounding box, we will be
379 * using the item's new position, and so will compute the wrong
380 * invalidation area. If we use the parent (which has not
381 * moved, then this will work.
383 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
386 Rect post_change_bounding_box = item->bounding_box ();
387 if (post_change_bounding_box) {
388 /* request a redraw of where the item now is */
389 queue_draw_item_area (item, post_change_bounding_box);
393 /** Request a redraw of a particular area in an item's coordinates.
395 * @param area Area to redraw in the item's coordinates.
398 Canvas::queue_draw_item_area (Item* item, Rect area)
400 request_redraw (item->item_to_window (area));
404 Canvas::set_tooltip_timeout (uint32_t msecs)
406 tooltip_timeout_msecs = msecs;
410 Canvas::set_background_color (Color c)
414 Rect r = _root.bounding_box();
417 request_redraw (_root.item_to_window (r));
422 GtkCanvas::re_enter ()
424 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
426 pick_current_item (0);
429 /** Construct a GtkCanvas */
430 GtkCanvas::GtkCanvas ()
432 , _new_current_item (0)
435 , _single_exposure (1)
436 , current_tooltip_item (0)
441 /* these are the events we want to know about */
442 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
443 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
444 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
448 GtkCanvas::use_nsglview ()
451 assert (!is_realized());
452 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
453 _nsglview = Gtkmm2ext::nsglview_create (this);
458 GtkCanvas::pick_current_item (int state)
463 /* this version of ::pick_current_item() is called after an item is
464 * added or removed, so we have no coordinates to work from as is the
465 * case with a motion event. Find out where the mouse is and use that.
468 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
470 if (pointer_window != get_window()) {
474 pick_current_item (Duple (x, y), state);
477 /** Given @param point (a position in window coordinates)
478 * and mouse state @param state, check to see if _current_item
479 * (which will be used to deliver events) should change.
482 GtkCanvas::pick_current_item (Duple const & point, int state)
484 /* we do not enter/leave items during a drag/grab */
490 /* find the items at the given window position */
492 vector<Item const *> items;
493 _root.add_items_at_point (point, items);
495 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
498 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
499 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
501 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
503 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
509 /* put all items at point that are event-sensitive and visible and NOT
510 groups into within_items. Note that items is sorted from bottom to
511 top, but we're going to reverse that for within_items so that its
512 first item is the upper-most item that can be chosen as _current_item.
515 vector<Item const *>::const_iterator i;
516 list<Item const *> within_items;
518 for (i = items.begin(); i != items.end(); ++i) {
520 Item const * possible_item = *i;
522 /* We ignore invisible items, containers and items that ignore events */
524 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
527 within_items.push_front (possible_item);
530 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
532 if (within_items.empty()) {
534 /* no items at point, just send leave event below */
535 _new_current_item = 0;
539 if (within_items.front() == _current_item) {
540 /* uppermost item at point is already _current_item */
541 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
545 _new_current_item = const_cast<Item*> (within_items.front());
548 if (_new_current_item != _current_item) {
549 deliver_enter_leave (point, state);
553 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
555 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
560 /** Deliver a series of enter & leave events based on the pointer position being at window
561 * coordinate @param point, and pointer @param state (modifier keys, etc)
564 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
566 /* setup enter & leave event structures */
568 Glib::RefPtr<Gdk::Window> win = get_window();
574 GdkEventCrossing enter_event;
575 enter_event.type = GDK_ENTER_NOTIFY;
576 enter_event.window = win->gobj();
577 enter_event.send_event = 0;
578 enter_event.subwindow = 0;
579 enter_event.mode = GDK_CROSSING_NORMAL;
580 enter_event.focus = FALSE;
581 enter_event.state = state;
583 /* Events delivered to canvas items are expected to be in canvas
584 * coordinates but @param point is in window coordinates.
587 Duple c = window_to_canvas (point);
591 GdkEventCrossing leave_event = enter_event;
592 leave_event.type = GDK_LEAVE_NOTIFY;
595 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
596 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
597 vector<Item*> items_to_leave_virtual;
598 vector<Item*> items_to_enter_virtual;
600 if (_new_current_item == 0) {
602 leave_detail = GDK_NOTIFY_UNKNOWN;
606 /* no current item, so also send virtual leave events to the
607 * entire heirarchy for the current item
610 for (i = _current_item->parent(); i ; i = i->parent()) {
611 items_to_leave_virtual.push_back (i);
615 } else if (_current_item == 0) {
617 enter_detail = GDK_NOTIFY_UNKNOWN;
619 /* no current item, so also send virtual enter events to the
620 * entire heirarchy for the new item
623 for (i = _new_current_item->parent(); i ; i = i->parent()) {
624 items_to_enter_virtual.push_back (i);
627 } else if (_current_item->is_descendant_of (*_new_current_item)) {
629 /* move from descendant to ancestor (X: "_current_item is an
630 * inferior ("child") of _new_current_item")
632 * Deliver "virtual" leave notifications to all items in the
633 * heirarchy between current and new_current.
636 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
637 items_to_leave_virtual.push_back (i);
640 enter_detail = GDK_NOTIFY_INFERIOR;
641 leave_detail = GDK_NOTIFY_ANCESTOR;
643 } else if (_new_current_item->is_descendant_of (*_current_item)) {
644 /* move from ancestor to descendant (X: "_new_current_item is
645 * an inferior ("child") of _current_item")
647 * Deliver "virtual" enter notifications to all items in the
648 * heirarchy between current and new_current.
651 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
652 items_to_enter_virtual.push_back (i);
655 enter_detail = GDK_NOTIFY_ANCESTOR;
656 leave_detail = GDK_NOTIFY_INFERIOR;
660 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
662 /* deliver virtual leave events to everything between _current
663 * and common_ancestor.
666 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
667 items_to_leave_virtual.push_back (i);
670 /* deliver virtual enter events to everything between
671 * _new_current and common_ancestor.
674 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
675 items_to_enter_virtual.push_back (i);
678 enter_detail = GDK_NOTIFY_NONLINEAR;
679 leave_detail = GDK_NOTIFY_NONLINEAR;
683 if (_current_item && !_current_item->ignore_events ()) {
684 leave_event.detail = leave_detail;
685 _current_item->Event ((GdkEvent*)&leave_event);
686 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
689 leave_event.detail = GDK_NOTIFY_VIRTUAL;
690 enter_event.detail = GDK_NOTIFY_VIRTUAL;
692 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
693 if (!(*it)->ignore_events()) {
694 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
695 (*it)->Event ((GdkEvent*)&leave_event);
699 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
700 if (!(*it)->ignore_events()) {
701 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
702 (*it)->Event ((GdkEvent*)&enter_event);
703 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
707 if (_new_current_item && !_new_current_item->ignore_events()) {
708 enter_event.detail = enter_detail;
709 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
710 start_tooltip_timeout (_new_current_item);
711 _new_current_item->Event ((GdkEvent*)&enter_event);
714 _current_item = _new_current_item;
718 /** Deliver an event to the appropriate item; either the grabbed item, or
719 * one of the items underneath the event.
720 * @param point Position that the event has occurred at, in canvas coordinates.
721 * @param event The event.
724 GtkCanvas::deliver_event (GdkEvent* event)
726 /* Point in in canvas coordinate space */
728 const Item* event_item;
731 /* we have a grabbed item, so everything gets sent there */
732 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
733 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
734 event_item = _grabbed_item;
736 event_item = _current_item;
743 /* run through the items from child to parent, until one claims the event */
745 Item* item = const_cast<Item*> (event_item);
749 Item* parent = item->parent ();
751 if (!item->ignore_events () &&
752 item->Event (event)) {
753 /* this item has just handled the event */
755 PBD::DEBUG::CanvasEvents,
756 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
762 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)));
764 if ((item = parent) == 0) {
774 GtkCanvas::item_shown_or_hidden (Item* item)
776 if (item == current_tooltip_item) {
777 stop_tooltip_timeout ();
779 Canvas::item_shown_or_hidden (item);
782 /** Called when an item is being destroyed.
783 * @param item Item being destroyed.
784 * @param bounding_box Last known bounding box of the item.
787 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
790 queue_draw_item_area (item, bounding_box);
793 if (_new_current_item == item) {
794 _new_current_item = 0;
797 if (_grabbed_item == item) {
801 if (_focused_item == item) {
805 if (current_tooltip_item) {
806 current_tooltip_item = 0;
807 stop_tooltip_timeout ();
810 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
812 scrollers.remove (sg);
815 if (_current_item == item) {
816 /* no need to send a leave event to this item, since it is going away
819 pick_current_item (0); // no mouse state
825 GtkCanvas::on_realize ()
827 Gtk::EventBox::on_realize();
830 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
836 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
838 EventBox::on_size_allocate (a);
839 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
840 if (getenv("ARDOUR_IMAGE_SURFACE")) {
842 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
843 /* allocate an image surface as large as the canvas itself */
845 canvas_image.clear ();
846 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
848 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
855 gtk_widget_translate_coordinates(
857 GTK_WIDGET(get_toplevel()->gobj()),
859 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
865 /** Handler for GDK expose events.
867 * @return true if the event was handled.
870 GtkCanvas::on_expose_event (GdkEventExpose* ev)
877 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
882 #ifdef CANVAS_PROFILE
883 const int64_t start = g_get_monotonic_time ();
886 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
887 Cairo::RefPtr<Cairo::Context> draw_context;
888 Cairo::RefPtr<Cairo::Context> window_context;
889 if (getenv("ARDOUR_IMAGE_SURFACE")) {
891 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
893 draw_context = Cairo::Context::create (canvas_image);
894 window_context = get_window()->create_cairo_context ();
896 draw_context = get_window()->create_cairo_context ();
898 #elif defined USE_CAIRO_IMAGE_SURFACE
900 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
902 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
903 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
905 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
908 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
909 draw_context->clip();
912 /* group calls cairo_quartz_surface_create() which
913 * effectively uses a CGBitmapContext + image-surface
915 * This avoids expensive argb32_image_mark_image() during drawing.
916 * Although the final paint() operation still takes the slow path
917 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
919 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
920 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
922 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
924 draw_context->push_group ();
927 /* draw background color */
928 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
929 set_source_rgba (draw_context, _bg_color);
930 draw_context->fill ();
933 if ( _single_exposure ) {
935 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
941 gdk_region_get_rectangles (ev->region, &rects, &nrects);
942 for (gint n = 0; n < nrects; ++n) {
943 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
944 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
950 draw_context->pop_group_to_source ();
951 draw_context->paint ();
954 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
955 if (getenv("ARDOUR_IMAGE_SURFACE")) {
957 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
958 /* now blit our private surface back to the GDK one */
960 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
961 window_context->clip ();
962 window_context->set_source (canvas_image, 0, 0);
963 window_context->set_operator (Cairo::OPERATOR_SOURCE);
964 window_context->paint ();
966 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
970 #ifdef CANVAS_PROFILE
971 const int64_t end = g_get_monotonic_time ();
972 const int64_t elapsed = end - start;
973 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
980 GtkCanvas::prepare_for_render () const
982 Rect window_bbox = visible_area ();
983 Canvas::prepare_for_render (window_bbox);
986 /** Handler for GDK scroll events.
988 * @return true if the event was handled.
991 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
993 /* translate event coordinates from window to canvas */
995 GdkEvent copy = *((GdkEvent*)ev);
996 Duple winpos = Duple (ev->x, ev->y);
997 Duple where = window_to_canvas (winpos);
999 pick_current_item (winpos, ev->state);
1001 copy.button.x = where.x;
1002 copy.button.y = where.y;
1004 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1005 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1008 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
1009 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1012 /** Handler for GDK key press events.
1014 * @return true if the event was handled.
1017 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1019 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1020 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1023 /** Handler for GDK key release events.
1025 * @return true if the event was handled.
1028 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1030 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1031 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1034 /** Handler for GDK button press events.
1036 * @return true if the event was handled.
1039 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1041 /* translate event coordinates from window to canvas */
1043 GdkEvent copy = *((GdkEvent*)ev);
1044 Duple winpos = Duple (ev->x, ev->y);
1045 Duple where = window_to_canvas (winpos);
1047 pick_current_item (winpos, ev->state);
1049 copy.button.x = where.x;
1050 copy.button.y = where.y;
1052 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1053 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1056 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1057 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1060 /** Handler for GDK button release events.
1062 * @return true if the event was handled.
1065 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1067 /* translate event coordinates from window to canvas */
1069 GdkEvent copy = *((GdkEvent*)ev);
1070 Duple winpos = Duple (ev->x, ev->y);
1071 Duple where = window_to_canvas (winpos);
1073 pick_current_item (winpos, ev->state);
1075 copy.button.x = where.x;
1076 copy.button.y = where.y;
1078 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1079 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1082 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1083 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1087 GtkCanvas::get_mouse_position (Duple& winpos) const
1091 Gdk::ModifierType mask;
1092 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1095 std::cerr << " no self window\n";
1096 winpos = Duple (0, 0);
1100 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1108 /** Handler for GDK motion events.
1110 * @return true if the event was handled.
1113 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1117 /* translate event coordinates from window to canvas */
1119 GdkEvent copy = *((GdkEvent*)ev);
1120 Duple point (ev->x, ev->y);
1121 Duple where = window_to_canvas (point);
1123 copy.motion.x = where.x;
1124 copy.motion.y = where.y;
1126 /* Coordinates in "copy" will be canvas coordinates,
1129 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));
1131 MouseMotion (point); /* EMIT SIGNAL */
1133 pick_current_item (point, ev->state);
1135 /* Now deliver the motion event. It may seem a little inefficient
1136 to recompute the items under the event, but the enter notify/leave
1137 events may have deleted canvas items so it is important to
1138 recompute the list in deliver_event.
1141 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1145 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1147 pick_current_item (Duple (ev->x, ev->y), ev->state);
1152 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1154 switch (ev->detail) {
1155 case GDK_NOTIFY_ANCESTOR:
1156 case GDK_NOTIFY_UNKNOWN:
1157 case GDK_NOTIFY_VIRTUAL:
1158 case GDK_NOTIFY_NONLINEAR:
1159 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1160 /* leaving window, cancel any tooltips */
1161 stop_tooltip_timeout ();
1165 /* we don't care about any other kind
1166 of leave event (notably GDK_NOTIFY_INFERIOR)
1170 _new_current_item = 0;
1171 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1176 GtkCanvas::on_map ()
1178 Gtk::EventBox::on_map();
1181 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1182 Gtk::Allocation a = get_allocation();
1184 gtk_widget_translate_coordinates(
1186 GTK_WIDGET(get_toplevel()->gobj()),
1188 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1194 GtkCanvas::on_unmap ()
1196 stop_tooltip_timeout ();
1197 Gtk::EventBox::on_unmap();
1200 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1205 /** Called to request a redraw of our canvas.
1206 * @param area Area to redraw, in window coordinates.
1209 GtkCanvas::request_redraw (Rect const & request)
1215 /* clamp area requested to actual visible window */
1217 Rect real_area = request.intersection (visible_area());
1220 if (real_area.width () && real_area.height ()) {
1221 // Item intersects with visible canvas area
1222 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1226 // Item does not intersect with visible canvas area
1230 /** Called to request that we try to get a particular size for ourselves.
1231 * @param size Size to request, in pixels.
1234 GtkCanvas::request_size (Duple size)
1238 if (req.x > INT_MAX) {
1242 if (req.y > INT_MAX) {
1246 set_size_request (req.x, req.y);
1249 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1250 * This is typically used for dragging items around, so that they are grabbed during
1252 * @param item Item to grab.
1255 GtkCanvas::grab (Item* item)
1257 /* XXX: should this be doing gdk_pointer_grab? */
1258 _grabbed_item = item;
1262 /** `Ungrab' any item that was previously grabbed */
1264 GtkCanvas::ungrab ()
1266 /* XXX: should this be doing gdk_pointer_ungrab? */
1270 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1272 * @param item Item to grab.
1275 GtkCanvas::focus (Item* item)
1277 _focused_item = item;
1281 GtkCanvas::unfocus (Item* item)
1283 if (item == _focused_item) {
1288 /** @return The visible area of the canvas, in window coordinates */
1290 GtkCanvas::visible_area () const
1292 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1296 GtkCanvas::width() const
1298 return get_allocation().get_width();
1302 GtkCanvas::height() const
1304 return get_allocation().get_height();
1308 GtkCanvas::start_tooltip_timeout (Item* item)
1310 stop_tooltip_timeout ();
1312 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1313 current_tooltip_item = item;
1315 /* wait for the first idle that happens after this is
1316 called. this means that we've stopped processing events, which
1317 in turn implies that the user has stopped doing stuff for a
1321 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1326 GtkCanvas::really_start_tooltip_timeout ()
1328 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1329 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1332 if (current_tooltip_item) {
1333 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1336 return false; /* this is called from an idle callback, don't call it again */
1340 GtkCanvas::stop_tooltip_timeout ()
1342 current_tooltip_item = 0;
1343 tooltip_timeout_connection.disconnect ();
1347 GtkCanvas::show_tooltip ()
1349 Rect tooltip_item_bbox;
1351 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1355 if (!tooltip_window) {
1356 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1357 tooltip_label = manage (new Gtk::Label);
1358 tooltip_label->show ();
1359 tooltip_window->add (*tooltip_label);
1360 tooltip_window->set_border_width (1);
1361 tooltip_window->set_name ("tooltip");
1364 tooltip_label->set_text (current_tooltip_item->tooltip());
1366 /* figure out where to position the tooltip */
1368 Gtk::Widget* toplevel = get_toplevel();
1370 int pointer_x, pointer_y;
1371 Gdk::ModifierType mask;
1373 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1375 Duple tooltip_window_origin (pointer_x, pointer_y);
1377 /* convert to root window coordinates */
1380 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1382 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1384 /* we don't want the pointer to be inside the window when it is
1385 * displayed, because then we generate a leave/enter event pair when
1386 * the window is displayed then hidden - the enter event will
1387 * trigger a new tooltip timeout.
1389 * So move the window right of the pointer position by just a enough
1390 * to get it away from the pointer.
1393 tooltip_window_origin.x += 30;
1394 tooltip_window_origin.y += 45;
1396 /* move the tooltip window into position */
1398 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1402 tooltip_window->present ();
1404 /* called from a timeout handler, don't call it again */
1410 GtkCanvas::hide_tooltip ()
1412 /* hide it if its there */
1414 if (tooltip_window) {
1415 tooltip_window->hide ();
1417 // Delete the tooltip window so it'll get re-created
1418 // (i.e. properly re-sized) on the next usage.
1419 delete tooltip_window;
1420 tooltip_window = NULL;
1424 Glib::RefPtr<Pango::Context>
1425 GtkCanvas::get_pango_context ()
1427 return Glib::wrap (gdk_pango_context_get());
1430 /** Create a GtkCanvaSViewport.
1431 * @param hadj Adjustment to use for horizontal scrolling.
1432 * @param vadj Adjustment to use for vertica scrolling.
1434 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1435 : Alignment (0, 0, 1.0, 1.0)
1436 , hadjustment (hadj)
1437 , vadjustment (vadj)
1441 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1442 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1446 GtkCanvasViewport::scrolled ()
1448 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1452 /** Handler for when GTK asks us what minimum size we want.
1453 * @param req Requsition to fill in.
1456 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1458 /* force the canvas to size itself */
1459 // _canvas.root()->bounding_box();