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))
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
107 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
108 cerr << this << " RENDER: " << area << endl;
109 //cerr << "CANVAS @ " << this << endl;
111 //cerr << "-------------------------\n";
117 Rect root_bbox = _root.bounding_box();
119 /* the root has no bounding box, so there's nothing to render */
123 Rect draw = root_bbox.intersection (area);
126 /* there's a common area between the root and the requested
130 _root.render (draw, context);
132 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
133 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
134 // This transparently colors the rect being rendered, after it has been drawn.
135 double r = (random() % 65536) /65536.0;
136 double g = (random() % 65536) /65536.0;
137 double b = (random() % 65536) /65536.0;
138 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
139 context->set_source_rgba (r, g, b, 0.25);
148 Canvas::prepare_for_render (Rect const & area) const
150 Rect root_bbox = _root.bounding_box();
152 /* the root has no bounding box, so there's nothing to render */
156 Rect draw = root_bbox.intersection (area);
159 _root.prepare_for_render (draw);
164 operator<< (ostream& o, Canvas& c)
171 Canvas::indent() const
175 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
183 Canvas::render_indent() const
187 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
195 Canvas::dump (ostream& o) const
201 /** Called when an item has been shown or hidden.
202 * @param item Item that has been shown or hidden.
205 Canvas::item_shown_or_hidden (Item* item)
207 Rect bbox = item->bounding_box ();
209 if (item->item_to_window (bbox).intersection (visible_area ())) {
210 queue_draw_item_area (item, bbox);
215 /** Called when an item has a change to its visual properties
216 * that do NOT affect its bounding box.
217 * @param item Item that has been modified.
220 Canvas::item_visual_property_changed (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 changed, but not moved.
231 * @param item Item that has changed.
232 * @param pre_change_bounding_box The bounding box of item before the change,
233 * in the item's coordinates.
236 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
238 Rect window_bbox = visible_area ();
240 if (pre_change_bounding_box) {
241 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
242 /* request a redraw of the item's old bounding box */
243 queue_draw_item_area (item, pre_change_bounding_box);
247 Rect post_change_bounding_box = item->bounding_box ();
249 if (post_change_bounding_box) {
250 Rect const window_intersection =
251 item->item_to_window (post_change_bounding_box).intersection (window_bbox);
253 if (window_intersection) {
254 /* request a redraw of the item's new bounding box */
255 queue_draw_item_area (item, post_change_bounding_box);
257 // Allow item to do any work necessary to prepare for being rendered.
258 item->prepare_for_render (window_intersection);
260 // No intersection with visible window area
266 Canvas::window_to_canvas (Duple const & d) const
268 ScrollGroup* best_group = 0;
271 /* if the coordinates are negative, clamp to zero and find the item
272 * that covers that "edge" position.
277 if (in_window.x < 0) {
280 if (in_window.y < 0) {
284 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
286 if ((*s)->covers_window (in_window)) {
289 /* XXX January 22nd 2015: leaving this in place for now
290 * but I think it fixes a bug that really should be
291 * fixed in a different way (and will be) by my next
292 * commit. But it may still be relevant.
295 /* If scroll groups overlap, choose the one with the highest sensitivity,
296 that is, choose an HV scroll group over an H or V
299 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
301 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
302 /* Can't do any better than this. */
310 return d.translate (best_group->scroll_offset());
317 Canvas::canvas_to_window (Duple const & d, bool rounded) const
319 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
320 * as children of the root group, so we just scan its first level
321 * children and see what we can find.
324 std::list<Item*> const& root_children (_root.items());
328 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
329 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
335 wd = d.translate (-sg->scroll_offset());
340 /* Note that this intentionally almost always returns integer coordinates */
350 /** Called when an item has moved.
351 * @param item Item that has moved.
352 * @param pre_change_parent_bounding_box The bounding box of the item before
353 * the move, in its parent's coordinates.
356 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
358 if (pre_change_parent_bounding_box) {
359 /* request a redraw of where the item used to be. The box has
360 * to be in parent coordinate space since the bounding box of
361 * an item does not change when moved. If we use
362 * item->item_to_canvas() on the old bounding box, we will be
364 * using the item's new position, and so will compute the wrong
365 * invalidation area. If we use the parent (which has not
366 * moved, then this will work.
368 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
371 Rect post_change_bounding_box = item->bounding_box ();
372 if (post_change_bounding_box) {
373 /* request a redraw of where the item now is */
374 queue_draw_item_area (item, post_change_bounding_box);
378 /** Request a redraw of a particular area in an item's coordinates.
380 * @param area Area to redraw in the item's coordinates.
383 Canvas::queue_draw_item_area (Item* item, Rect area)
385 request_redraw (item->item_to_window (area));
389 Canvas::set_tooltip_timeout (uint32_t msecs)
391 tooltip_timeout_msecs = msecs;
395 Canvas::set_background_color (Color c)
399 Rect r = _root.bounding_box();
402 request_redraw (_root.item_to_window (r));
407 GtkCanvas::re_enter ()
409 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
411 pick_current_item (0);
414 /** Construct a GtkCanvas */
415 GtkCanvas::GtkCanvas ()
417 , _new_current_item (0)
420 , _single_exposure (1)
421 , current_tooltip_item (0)
426 /* these are the events we want to know about */
427 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
428 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
429 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
433 GtkCanvas::use_nsglview ()
436 assert (!is_realized());
437 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
438 _nsglview = Gtkmm2ext::nsglview_create (this);
443 GtkCanvas::pick_current_item (int state)
448 /* this version of ::pick_current_item() is called after an item is
449 * added or removed, so we have no coordinates to work from as is the
450 * case with a motion event. Find out where the mouse is and use that.
453 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
455 if (pointer_window != get_window()) {
459 pick_current_item (Duple (x, y), state);
462 /** Given @param point (a position in window coordinates)
463 * and mouse state @param state, check to see if _current_item
464 * (which will be used to deliver events) should change.
467 GtkCanvas::pick_current_item (Duple const & point, int state)
469 /* we do not enter/leave items during a drag/grab */
475 /* find the items at the given window position */
477 vector<Item const *> items;
478 _root.add_items_at_point (point, items);
480 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
483 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
484 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
486 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
488 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
494 /* put all items at point that are event-sensitive and visible and NOT
495 groups into within_items. Note that items is sorted from bottom to
496 top, but we're going to reverse that for within_items so that its
497 first item is the upper-most item that can be chosen as _current_item.
500 vector<Item const *>::const_iterator i;
501 list<Item const *> within_items;
503 for (i = items.begin(); i != items.end(); ++i) {
505 Item const * possible_item = *i;
507 /* We ignore invisible items, containers and items that ignore events */
509 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
512 within_items.push_front (possible_item);
515 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
517 if (within_items.empty()) {
519 /* no items at point, just send leave event below */
520 _new_current_item = 0;
524 if (within_items.front() == _current_item) {
525 /* uppermost item at point is already _current_item */
526 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
530 _new_current_item = const_cast<Item*> (within_items.front());
533 if (_new_current_item != _current_item) {
534 deliver_enter_leave (point, state);
538 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
540 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
545 /** Deliver a series of enter & leave events based on the pointer position being at window
546 * coordinate @param point, and pointer @param state (modifier keys, etc)
549 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
551 /* setup enter & leave event structures */
553 Glib::RefPtr<Gdk::Window> win = get_window();
559 GdkEventCrossing enter_event;
560 enter_event.type = GDK_ENTER_NOTIFY;
561 enter_event.window = win->gobj();
562 enter_event.send_event = 0;
563 enter_event.subwindow = 0;
564 enter_event.mode = GDK_CROSSING_NORMAL;
565 enter_event.focus = FALSE;
566 enter_event.state = state;
568 /* Events delivered to canvas items are expected to be in canvas
569 * coordinates but @param point is in window coordinates.
572 Duple c = window_to_canvas (point);
576 GdkEventCrossing leave_event = enter_event;
577 leave_event.type = GDK_LEAVE_NOTIFY;
580 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
581 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
582 vector<Item*> items_to_leave_virtual;
583 vector<Item*> items_to_enter_virtual;
585 if (_new_current_item == 0) {
587 leave_detail = GDK_NOTIFY_UNKNOWN;
591 /* no current item, so also send virtual leave events to the
592 * entire heirarchy for the current item
595 for (i = _current_item->parent(); i ; i = i->parent()) {
596 items_to_leave_virtual.push_back (i);
600 } else if (_current_item == 0) {
602 enter_detail = GDK_NOTIFY_UNKNOWN;
604 /* no current item, so also send virtual enter events to the
605 * entire heirarchy for the new item
608 for (i = _new_current_item->parent(); i ; i = i->parent()) {
609 items_to_enter_virtual.push_back (i);
612 } else if (_current_item->is_descendant_of (*_new_current_item)) {
614 /* move from descendant to ancestor (X: "_current_item is an
615 * inferior ("child") of _new_current_item")
617 * Deliver "virtual" leave notifications to all items in the
618 * heirarchy between current and new_current.
621 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
622 items_to_leave_virtual.push_back (i);
625 enter_detail = GDK_NOTIFY_INFERIOR;
626 leave_detail = GDK_NOTIFY_ANCESTOR;
628 } else if (_new_current_item->is_descendant_of (*_current_item)) {
629 /* move from ancestor to descendant (X: "_new_current_item is
630 * an inferior ("child") of _current_item")
632 * Deliver "virtual" enter notifications to all items in the
633 * heirarchy between current and new_current.
636 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
637 items_to_enter_virtual.push_back (i);
640 enter_detail = GDK_NOTIFY_ANCESTOR;
641 leave_detail = GDK_NOTIFY_INFERIOR;
645 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
647 /* deliver virtual leave events to everything between _current
648 * and common_ancestor.
651 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
652 items_to_leave_virtual.push_back (i);
655 /* deliver virtual enter events to everything between
656 * _new_current and common_ancestor.
659 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
660 items_to_enter_virtual.push_back (i);
663 enter_detail = GDK_NOTIFY_NONLINEAR;
664 leave_detail = GDK_NOTIFY_NONLINEAR;
668 if (_current_item && !_current_item->ignore_events ()) {
669 leave_event.detail = leave_detail;
670 _current_item->Event ((GdkEvent*)&leave_event);
671 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
674 leave_event.detail = GDK_NOTIFY_VIRTUAL;
675 enter_event.detail = GDK_NOTIFY_VIRTUAL;
677 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
678 if (!(*it)->ignore_events()) {
679 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
680 (*it)->Event ((GdkEvent*)&leave_event);
684 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
685 if (!(*it)->ignore_events()) {
686 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
687 (*it)->Event ((GdkEvent*)&enter_event);
688 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
692 if (_new_current_item && !_new_current_item->ignore_events()) {
693 enter_event.detail = enter_detail;
694 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
695 start_tooltip_timeout (_new_current_item);
696 _new_current_item->Event ((GdkEvent*)&enter_event);
699 _current_item = _new_current_item;
703 /** Deliver an event to the appropriate item; either the grabbed item, or
704 * one of the items underneath the event.
705 * @param point Position that the event has occurred at, in canvas coordinates.
706 * @param event The event.
709 GtkCanvas::deliver_event (GdkEvent* event)
711 /* Point in in canvas coordinate space */
713 const Item* event_item;
716 /* we have a grabbed item, so everything gets sent there */
717 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
718 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
719 event_item = _grabbed_item;
721 event_item = _current_item;
728 /* run through the items from child to parent, until one claims the event */
730 Item* item = const_cast<Item*> (event_item);
734 Item* parent = item->parent ();
736 if (!item->ignore_events () &&
737 item->Event (event)) {
738 /* this item has just handled the event */
740 PBD::DEBUG::CanvasEvents,
741 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
747 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)));
749 if ((item = parent) == 0) {
759 GtkCanvas::item_shown_or_hidden (Item* item)
761 if (item == current_tooltip_item) {
762 stop_tooltip_timeout ();
764 Canvas::item_shown_or_hidden (item);
767 /** Called when an item is being destroyed.
768 * @param item Item being destroyed.
769 * @param bounding_box Last known bounding box of the item.
772 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
775 queue_draw_item_area (item, bounding_box);
778 if (_new_current_item == item) {
779 _new_current_item = 0;
782 if (_grabbed_item == item) {
786 if (_focused_item == item) {
790 if (current_tooltip_item) {
791 current_tooltip_item = 0;
792 stop_tooltip_timeout ();
795 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
797 scrollers.remove (sg);
800 if (_current_item == item) {
801 /* no need to send a leave event to this item, since it is going away
804 pick_current_item (0); // no mouse state
810 GtkCanvas::on_realize ()
812 Gtk::EventBox::on_realize();
815 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
821 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
823 EventBox::on_size_allocate (a);
824 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
825 if (getenv("ARDOUR_IMAGE_SURFACE")) {
827 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
828 /* allocate an image surface as large as the canvas itself */
830 canvas_image.clear ();
831 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
833 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
840 gtk_widget_translate_coordinates(
842 GTK_WIDGET(get_toplevel()->gobj()),
844 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
850 /** Handler for GDK expose events.
852 * @return true if the event was handled.
855 GtkCanvas::on_expose_event (GdkEventExpose* ev)
862 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
867 #ifdef CANVAS_PROFILE
868 const int64_t start = g_get_monotonic_time ();
871 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
872 Cairo::RefPtr<Cairo::Context> draw_context;
873 Cairo::RefPtr<Cairo::Context> window_context;
874 if (getenv("ARDOUR_IMAGE_SURFACE")) {
876 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
878 draw_context = Cairo::Context::create (canvas_image);
879 window_context = get_window()->create_cairo_context ();
881 draw_context = get_window()->create_cairo_context ();
883 #elif defined USE_CAIRO_IMAGE_SURFACE
885 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
887 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
888 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
890 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
893 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
894 draw_context->clip();
897 /* group calls cairo_quartz_surface_create() which
898 * effectively uses a CGBitmapContext + image-surface
900 * This avoids expensive argb32_image_mark_image() during drawing.
901 * Although the final paint() operation still takes the slow path
902 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
904 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
905 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
907 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
909 draw_context->push_group ();
912 /* draw background color */
913 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
914 set_source_rgba (draw_context, _bg_color);
915 draw_context->fill ();
918 if ( _single_exposure ) {
920 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
926 gdk_region_get_rectangles (ev->region, &rects, &nrects);
927 for (gint n = 0; n < nrects; ++n) {
928 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
929 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
935 draw_context->pop_group_to_source ();
936 draw_context->paint ();
939 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
940 if (getenv("ARDOUR_IMAGE_SURFACE")) {
942 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
943 /* now blit our private surface back to the GDK one */
945 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
946 window_context->clip ();
947 window_context->set_source (canvas_image, 0, 0);
948 window_context->set_operator (Cairo::OPERATOR_SOURCE);
949 window_context->paint ();
951 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
955 #ifdef CANVAS_PROFILE
956 const int64_t end = g_get_monotonic_time ();
957 const int64_t elapsed = end - start;
958 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
965 GtkCanvas::prepare_for_render () const
967 Rect window_bbox = visible_area ();
968 Canvas::prepare_for_render (window_bbox);
971 /** Handler for GDK scroll events.
973 * @return true if the event was handled.
976 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
978 /* translate event coordinates from window to canvas */
980 GdkEvent copy = *((GdkEvent*)ev);
981 Duple winpos = Duple (ev->x, ev->y);
982 Duple where = window_to_canvas (winpos);
984 pick_current_item (winpos, ev->state);
986 copy.button.x = where.x;
987 copy.button.y = where.y;
989 /* Coordinates in the event will be canvas coordinates, correctly adjusted
990 for scroll if this GtkCanvas is in a GtkCanvasViewport.
993 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
994 return deliver_event (reinterpret_cast<GdkEvent*>(©));
997 /** Handler for GDK key press events.
999 * @return true if the event was handled.
1002 GtkCanvas::on_key_press_event (GdkEventKey* ev)
1004 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
1005 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1008 /** Handler for GDK key release events.
1010 * @return true if the event was handled.
1013 GtkCanvas::on_key_release_event (GdkEventKey* ev)
1015 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
1016 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
1019 /** Handler for GDK button press events.
1021 * @return true if the event was handled.
1024 GtkCanvas::on_button_press_event (GdkEventButton* ev)
1026 /* translate event coordinates from window to canvas */
1028 GdkEvent copy = *((GdkEvent*)ev);
1029 Duple winpos = Duple (ev->x, ev->y);
1030 Duple where = window_to_canvas (winpos);
1032 pick_current_item (winpos, ev->state);
1034 copy.button.x = where.x;
1035 copy.button.y = where.y;
1037 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1038 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1041 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1042 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1045 /** Handler for GDK button release events.
1047 * @return true if the event was handled.
1050 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1052 /* translate event coordinates from window to canvas */
1054 GdkEvent copy = *((GdkEvent*)ev);
1055 Duple winpos = Duple (ev->x, ev->y);
1056 Duple where = window_to_canvas (winpos);
1058 pick_current_item (winpos, ev->state);
1060 copy.button.x = where.x;
1061 copy.button.y = where.y;
1063 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1064 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1067 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1068 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1072 GtkCanvas::get_mouse_position (Duple& winpos) const
1076 Gdk::ModifierType mask;
1077 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1080 std::cerr << " no self window\n";
1081 winpos = Duple (0, 0);
1085 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1093 /** Handler for GDK motion events.
1095 * @return true if the event was handled.
1098 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1102 /* translate event coordinates from window to canvas */
1104 GdkEvent copy = *((GdkEvent*)ev);
1105 Duple point (ev->x, ev->y);
1106 Duple where = window_to_canvas (point);
1108 copy.motion.x = where.x;
1109 copy.motion.y = where.y;
1111 /* Coordinates in "copy" will be canvas coordinates,
1114 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));
1116 MouseMotion (point); /* EMIT SIGNAL */
1118 pick_current_item (point, ev->state);
1120 /* Now deliver the motion event. It may seem a little inefficient
1121 to recompute the items under the event, but the enter notify/leave
1122 events may have deleted canvas items so it is important to
1123 recompute the list in deliver_event.
1126 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1130 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1132 pick_current_item (Duple (ev->x, ev->y), ev->state);
1137 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1139 switch (ev->detail) {
1140 case GDK_NOTIFY_ANCESTOR:
1141 case GDK_NOTIFY_UNKNOWN:
1142 case GDK_NOTIFY_VIRTUAL:
1143 case GDK_NOTIFY_NONLINEAR:
1144 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1145 /* leaving window, cancel any tooltips */
1146 stop_tooltip_timeout ();
1150 /* we don't care about any other kind
1151 of leave event (notably GDK_NOTIFY_INFERIOR)
1155 _new_current_item = 0;
1156 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1161 GtkCanvas::on_map ()
1163 Gtk::EventBox::on_map();
1166 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1167 Gtk::Allocation a = get_allocation();
1169 gtk_widget_translate_coordinates(
1171 GTK_WIDGET(get_toplevel()->gobj()),
1173 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1179 GtkCanvas::on_unmap ()
1181 stop_tooltip_timeout ();
1182 Gtk::EventBox::on_unmap();
1185 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1190 /** Called to request a redraw of our canvas.
1191 * @param area Area to redraw, in window coordinates.
1194 GtkCanvas::request_redraw (Rect const & request)
1202 Coord const w = width ();
1203 Coord const h = height ();
1205 /* clamp area requested to actual visible window */
1207 real_area.x0 = max (0.0, min (w, request.x0));
1208 real_area.x1 = max (0.0, min (w, request.x1));
1209 real_area.y0 = max (0.0, min (h, request.y0));
1210 real_area.y1 = max (0.0, min (h, request.y1));
1212 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1215 /** Called to request that we try to get a particular size for ourselves.
1216 * @param size Size to request, in pixels.
1219 GtkCanvas::request_size (Duple size)
1223 if (req.x > INT_MAX) {
1227 if (req.y > INT_MAX) {
1231 set_size_request (req.x, req.y);
1234 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1235 * This is typically used for dragging items around, so that they are grabbed during
1237 * @param item Item to grab.
1240 GtkCanvas::grab (Item* item)
1242 /* XXX: should this be doing gdk_pointer_grab? */
1243 _grabbed_item = item;
1247 /** `Ungrab' any item that was previously grabbed */
1249 GtkCanvas::ungrab ()
1251 /* XXX: should this be doing gdk_pointer_ungrab? */
1255 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1257 * @param item Item to grab.
1260 GtkCanvas::focus (Item* item)
1262 _focused_item = item;
1266 GtkCanvas::unfocus (Item* item)
1268 if (item == _focused_item) {
1273 /** @return The visible area of the canvas, in window coordinates */
1275 GtkCanvas::visible_area () const
1277 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1281 GtkCanvas::width() const
1283 return get_allocation().get_width();
1287 GtkCanvas::height() const
1289 return get_allocation().get_height();
1293 GtkCanvas::start_tooltip_timeout (Item* item)
1295 stop_tooltip_timeout ();
1297 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1298 current_tooltip_item = item;
1300 /* wait for the first idle that happens after this is
1301 called. this means that we've stopped processing events, which
1302 in turn implies that the user has stopped doing stuff for a
1306 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1311 GtkCanvas::really_start_tooltip_timeout ()
1313 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1314 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1317 if (current_tooltip_item) {
1318 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1321 return false; /* this is called from an idle callback, don't call it again */
1325 GtkCanvas::stop_tooltip_timeout ()
1327 current_tooltip_item = 0;
1328 tooltip_timeout_connection.disconnect ();
1332 GtkCanvas::show_tooltip ()
1334 Rect tooltip_item_bbox;
1336 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1340 if (!tooltip_window) {
1341 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1342 tooltip_label = manage (new Gtk::Label);
1343 tooltip_label->show ();
1344 tooltip_window->add (*tooltip_label);
1345 tooltip_window->set_border_width (1);
1346 tooltip_window->set_name ("tooltip");
1349 tooltip_label->set_text (current_tooltip_item->tooltip());
1351 /* figure out where to position the tooltip */
1353 Gtk::Widget* toplevel = get_toplevel();
1355 int pointer_x, pointer_y;
1356 Gdk::ModifierType mask;
1358 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1360 Duple tooltip_window_origin (pointer_x, pointer_y);
1362 /* convert to root window coordinates */
1365 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1367 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1369 /* we don't want the pointer to be inside the window when it is
1370 * displayed, because then we generate a leave/enter event pair when
1371 * the window is displayed then hidden - the enter event will
1372 * trigger a new tooltip timeout.
1374 * So move the window right of the pointer position by just a enough
1375 * to get it away from the pointer.
1378 tooltip_window_origin.x += 30;
1379 tooltip_window_origin.y += 45;
1381 /* move the tooltip window into position */
1383 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1387 tooltip_window->present ();
1389 /* called from a timeout handler, don't call it again */
1395 GtkCanvas::hide_tooltip ()
1397 /* hide it if its there */
1399 if (tooltip_window) {
1400 tooltip_window->hide ();
1402 // Delete the tooltip window so it'll get re-created
1403 // (i.e. properly re-sized) on the next usage.
1404 delete tooltip_window;
1405 tooltip_window = NULL;
1409 Glib::RefPtr<Pango::Context>
1410 GtkCanvas::get_pango_context ()
1412 return Glib::wrap (gdk_pango_context_get());
1415 /** Create a GtkCanvaSViewport.
1416 * @param hadj Adjustment to use for horizontal scrolling.
1417 * @param vadj Adjustment to use for vertica scrolling.
1419 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1420 : Alignment (0, 0, 1.0, 1.0)
1421 , hadjustment (hadj)
1422 , vadjustment (vadj)
1426 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1427 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1431 GtkCanvasViewport::scrolled ()
1433 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1437 /** Handler for when GTK asks us what minimum size we want.
1438 * @param req Requsition to fill in.
1441 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1443 /* force the canvas to size itself */
1444 // _canvas.root()->bounding_box();