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"
48 #include "canvas/nsglview.h"
52 using namespace ArdourCanvas;
54 uint32_t Canvas::tooltip_timeout_msecs = 750;
56 /** Construct a new Canvas */
59 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
65 Canvas::scroll_to (Coord x, Coord y)
67 /* We do things this way because we do not want to recurse through
68 the canvas for every scroll. In the presence of large MIDI
69 tracks this means traversing item lists that include
70 thousands of items (notes).
72 This design limits us to moving only those items (groups, typically)
73 that should move in certain ways as we scroll. In other terms, it
74 becomes O(1) rather than O(N).
77 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
78 (*i)->scroll_to (Duple (x, y));
81 pick_current_item (0); // no current mouse position
85 Canvas::add_scroller (ScrollGroup& i)
87 scrollers.push_back (&i);
93 pick_current_item (0); // no current mouse position
96 /** Render an area of the canvas.
97 * @param area Area in window coordinates.
98 * @param context Cairo context to render to.
101 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
104 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
105 cerr << this << " RENDER: " << area << endl;
106 //cerr << "CANVAS @ " << this << endl;
108 //cerr << "-------------------------\n";
114 Rect root_bbox = _root.bounding_box();
116 /* the root has no bounding box, so there's nothing to render */
120 Rect draw = root_bbox.intersection (area);
123 /* there's a common area between the root and the requested
127 _root.render (draw, context);
129 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
130 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
131 // This transparently colors the rect being rendered, after it has been drawn.
132 double r = (random() % 65536) /65536.0;
133 double g = (random() % 65536) /65536.0;
134 double b = (random() % 65536) /65536.0;
135 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
136 context->set_source_rgba (r, g, b, 0.25);
145 operator<< (ostream& o, Canvas& c)
152 Canvas::indent() const
156 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
164 Canvas::render_indent() const
168 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
176 Canvas::dump (ostream& o) const
182 /** Called when an item has been shown or hidden.
183 * @param item Item that has been shown or hidden.
186 Canvas::item_shown_or_hidden (Item* item)
188 Rect bbox = item->bounding_box ();
190 if (item->item_to_window (bbox).intersection (visible_area ())) {
191 queue_draw_item_area (item, bbox);
196 /** Called when an item has a change to its visual properties
197 * that do NOT affect its bounding box.
198 * @param item Item that has been modified.
201 Canvas::item_visual_property_changed (Item* item)
203 Rect bbox = item->bounding_box ();
205 if (item->item_to_window (bbox).intersection (visible_area ())) {
206 queue_draw_item_area (item, bbox);
211 /** Called when an item has changed, but not moved.
212 * @param item Item that has changed.
213 * @param pre_change_bounding_box The bounding box of item before the change,
214 * in the item's coordinates.
217 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
219 Rect window_bbox = visible_area ();
221 if (pre_change_bounding_box) {
222 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
223 /* request a redraw of the item's old bounding box */
224 queue_draw_item_area (item, pre_change_bounding_box);
228 Rect post_change_bounding_box = item->bounding_box ();
230 if (post_change_bounding_box) {
231 if (item->item_to_window (post_change_bounding_box).intersection (window_bbox)) {
232 /* request a redraw of the item's new bounding box */
233 queue_draw_item_area (item, post_change_bounding_box);
239 Canvas::window_to_canvas (Duple const & d) const
241 ScrollGroup* best_group = 0;
244 /* if the coordinates are negative, clamp to zero and find the item
245 * that covers that "edge" position.
250 if (in_window.x < 0) {
253 if (in_window.y < 0) {
257 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
259 if ((*s)->covers_window (in_window)) {
262 /* XXX January 22nd 2015: leaving this in place for now
263 * but I think it fixes a bug that really should be
264 * fixed in a different way (and will be) by my next
265 * commit. But it may still be relevant.
268 /* If scroll groups overlap, choose the one with the highest sensitivity,
269 that is, choose an HV scroll group over an H or V
272 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
274 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
275 /* Can't do any better than this. */
283 return d.translate (best_group->scroll_offset());
290 Canvas::canvas_to_window (Duple const & d, bool rounded) const
292 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
293 * as children of the root group, so we just scan its first level
294 * children and see what we can find.
297 std::list<Item*> const& root_children (_root.items());
301 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
302 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
308 wd = d.translate (-sg->scroll_offset());
313 /* Note that this intentionally almost always returns integer coordinates */
323 /** Called when an item has moved.
324 * @param item Item that has moved.
325 * @param pre_change_parent_bounding_box The bounding box of the item before
326 * the move, in its parent's coordinates.
329 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
331 if (pre_change_parent_bounding_box) {
332 /* request a redraw of where the item used to be. The box has
333 * to be in parent coordinate space since the bounding box of
334 * an item does not change when moved. If we use
335 * item->item_to_canvas() on the old bounding box, we will be
337 * using the item's new position, and so will compute the wrong
338 * invalidation area. If we use the parent (which has not
339 * moved, then this will work.
341 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
344 Rect post_change_bounding_box = item->bounding_box ();
345 if (post_change_bounding_box) {
346 /* request a redraw of where the item now is */
347 queue_draw_item_area (item, post_change_bounding_box);
351 /** Request a redraw of a particular area in an item's coordinates.
353 * @param area Area to redraw in the item's coordinates.
356 Canvas::queue_draw_item_area (Item* item, Rect area)
358 request_redraw (item->item_to_window (area));
362 Canvas::set_tooltip_timeout (uint32_t msecs)
364 tooltip_timeout_msecs = msecs;
368 Canvas::set_background_color (Color c)
372 Rect r = _root.bounding_box();
375 request_redraw (_root.item_to_window (r));
380 GtkCanvas::re_enter ()
382 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
384 pick_current_item (0);
387 /** Construct a GtkCanvas */
388 GtkCanvas::GtkCanvas ()
390 , _new_current_item (0)
393 , _single_exposure (1)
394 , current_tooltip_item (0)
399 /* these are the events we want to know about */
400 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
401 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
402 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
404 #ifdef __APPLE__NotYetToDueGdkForeignViewMousePatch // XXX
405 # ifndef __ppc__ // would need to flip RGBA <> RGBA
406 _nsglview = nsglview_create (this);
412 GtkCanvas::pick_current_item (int state)
417 /* this version of ::pick_current_item() is called after an item is
418 * added or removed, so we have no coordinates to work from as is the
419 * case with a motion event. Find out where the mouse is and use that.
422 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
424 if (pointer_window != get_window()) {
428 pick_current_item (Duple (x, y), state);
431 /** Given @param point (a position in window coordinates)
432 * and mouse state @param state, check to see if _current_item
433 * (which will be used to deliver events) should change.
436 GtkCanvas::pick_current_item (Duple const & point, int state)
438 /* we do not enter/leave items during a drag/grab */
444 /* find the items at the given window position */
446 vector<Item const *> items;
447 _root.add_items_at_point (point, items);
449 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
452 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
453 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
455 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
457 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
463 /* put all items at point that are event-sensitive and visible and NOT
464 groups into within_items. Note that items is sorted from bottom to
465 top, but we're going to reverse that for within_items so that its
466 first item is the upper-most item that can be chosen as _current_item.
469 vector<Item const *>::const_iterator i;
470 list<Item const *> within_items;
472 for (i = items.begin(); i != items.end(); ++i) {
474 Item const * possible_item = *i;
476 /* We ignore invisible items, containers and items that ignore events */
478 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
481 within_items.push_front (possible_item);
484 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
486 if (within_items.empty()) {
488 /* no items at point, just send leave event below */
489 _new_current_item = 0;
493 if (within_items.front() == _current_item) {
494 /* uppermost item at point is already _current_item */
495 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
499 _new_current_item = const_cast<Item*> (within_items.front());
502 if (_new_current_item != _current_item) {
503 deliver_enter_leave (point, state);
507 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
509 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
514 /** Deliver a series of enter & leave events based on the pointer position being at window
515 * coordinate @param point, and pointer @param state (modifier keys, etc)
518 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
520 /* setup enter & leave event structures */
522 Glib::RefPtr<Gdk::Window> win = get_window();
528 GdkEventCrossing enter_event;
529 enter_event.type = GDK_ENTER_NOTIFY;
530 enter_event.window = win->gobj();
531 enter_event.send_event = 0;
532 enter_event.subwindow = 0;
533 enter_event.mode = GDK_CROSSING_NORMAL;
534 enter_event.focus = FALSE;
535 enter_event.state = state;
537 /* Events delivered to canvas items are expected to be in canvas
538 * coordinates but @param point is in window coordinates.
541 Duple c = window_to_canvas (point);
545 GdkEventCrossing leave_event = enter_event;
546 leave_event.type = GDK_LEAVE_NOTIFY;
549 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
550 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
551 vector<Item*> items_to_leave_virtual;
552 vector<Item*> items_to_enter_virtual;
554 if (_new_current_item == 0) {
556 leave_detail = GDK_NOTIFY_UNKNOWN;
560 /* no current item, so also send virtual leave events to the
561 * entire heirarchy for the current item
564 for (i = _current_item->parent(); i ; i = i->parent()) {
565 items_to_leave_virtual.push_back (i);
569 } else if (_current_item == 0) {
571 enter_detail = GDK_NOTIFY_UNKNOWN;
573 /* no current item, so also send virtual enter events to the
574 * entire heirarchy for the new item
577 for (i = _new_current_item->parent(); i ; i = i->parent()) {
578 items_to_enter_virtual.push_back (i);
581 } else if (_current_item->is_descendant_of (*_new_current_item)) {
583 /* move from descendant to ancestor (X: "_current_item is an
584 * inferior ("child") of _new_current_item")
586 * Deliver "virtual" leave notifications to all items in the
587 * heirarchy between current and new_current.
590 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
591 items_to_leave_virtual.push_back (i);
594 enter_detail = GDK_NOTIFY_INFERIOR;
595 leave_detail = GDK_NOTIFY_ANCESTOR;
597 } else if (_new_current_item->is_descendant_of (*_current_item)) {
598 /* move from ancestor to descendant (X: "_new_current_item is
599 * an inferior ("child") of _current_item")
601 * Deliver "virtual" enter notifications to all items in the
602 * heirarchy between current and new_current.
605 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
606 items_to_enter_virtual.push_back (i);
609 enter_detail = GDK_NOTIFY_ANCESTOR;
610 leave_detail = GDK_NOTIFY_INFERIOR;
614 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
616 /* deliver virtual leave events to everything between _current
617 * and common_ancestor.
620 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
621 items_to_leave_virtual.push_back (i);
624 /* deliver virtual enter events to everything between
625 * _new_current and common_ancestor.
628 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
629 items_to_enter_virtual.push_back (i);
632 enter_detail = GDK_NOTIFY_NONLINEAR;
633 leave_detail = GDK_NOTIFY_NONLINEAR;
637 if (_current_item && !_current_item->ignore_events ()) {
638 leave_event.detail = leave_detail;
639 _current_item->Event ((GdkEvent*)&leave_event);
640 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
643 leave_event.detail = GDK_NOTIFY_VIRTUAL;
644 enter_event.detail = GDK_NOTIFY_VIRTUAL;
646 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
647 if (!(*it)->ignore_events()) {
648 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
649 (*it)->Event ((GdkEvent*)&leave_event);
653 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
654 if (!(*it)->ignore_events()) {
655 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
656 (*it)->Event ((GdkEvent*)&enter_event);
657 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
661 if (_new_current_item && !_new_current_item->ignore_events()) {
662 enter_event.detail = enter_detail;
663 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
664 start_tooltip_timeout (_new_current_item);
665 _new_current_item->Event ((GdkEvent*)&enter_event);
668 _current_item = _new_current_item;
672 /** Deliver an event to the appropriate item; either the grabbed item, or
673 * one of the items underneath the event.
674 * @param point Position that the event has occurred at, in canvas coordinates.
675 * @param event The event.
678 GtkCanvas::deliver_event (GdkEvent* event)
680 /* Point in in canvas coordinate space */
682 const Item* event_item;
685 /* we have a grabbed item, so everything gets sent there */
686 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
687 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
688 event_item = _grabbed_item;
690 event_item = _current_item;
697 /* run through the items from child to parent, until one claims the event */
699 Item* item = const_cast<Item*> (event_item);
703 Item* parent = item->parent ();
705 if (!item->ignore_events () &&
706 item->Event (event)) {
707 /* this item has just handled the event */
709 PBD::DEBUG::CanvasEvents,
710 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
716 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)));
718 if ((item = parent) == 0) {
727 /** Called when an item is being destroyed.
728 * @param item Item being destroyed.
729 * @param bounding_box Last known bounding box of the item.
732 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
735 queue_draw_item_area (item, bounding_box);
738 if (_new_current_item == item) {
739 _new_current_item = 0;
742 if (_grabbed_item == item) {
746 if (_focused_item == item) {
750 if (current_tooltip_item) {
751 current_tooltip_item = 0;
752 stop_tooltip_timeout ();
755 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
757 scrollers.remove (sg);
760 if (_current_item == item) {
761 /* no need to send a leave event to this item, since it is going away
764 pick_current_item (0); // no mouse state
770 GtkCanvas::on_realize ()
772 Gtk::EventBox::on_realize();
774 nsglview_overlay (_nsglview, get_window()->gobj());
779 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
781 EventBox::on_size_allocate (a);
782 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
783 if (getenv("ARDOUR_IMAGE_SURFACE")) {
785 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
786 /* allocate an image surface as large as the canvas itself */
788 canvas_image.clear ();
789 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
791 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
798 gtk_widget_translate_coordinates(
800 GTK_WIDGET(get_toplevel()->gobj()),
802 nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
808 /** Handler for GDK expose events.
810 * @return true if the event was handled.
813 GtkCanvas::on_expose_event (GdkEventExpose* ev)
820 nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
825 #ifdef CANVAS_PROFILE
826 const int64_t start = g_get_monotonic_time ();
829 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
830 Cairo::RefPtr<Cairo::Context> draw_context;
831 Cairo::RefPtr<Cairo::Context> window_context;
832 if (getenv("ARDOUR_IMAGE_SURFACE")) {
834 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
836 draw_context = Cairo::Context::create (canvas_image);
837 window_context = get_window()->create_cairo_context ();
839 draw_context = get_window()->create_cairo_context ();
841 #elif defined USE_CAIRO_IMAGE_SURFACE
843 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
845 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
846 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
848 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
851 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
852 draw_context->clip();
855 /* group calls cairo_quartz_surface_create() which
856 * effectively uses a CGBitmapContext + image-surface
858 * This avoids expensive argb32_image_mark_image() during drawing.
859 * Although the final paint() operation still takes the slow path
860 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
862 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
863 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
865 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
867 draw_context->push_group ();
870 /* draw background color */
871 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
872 set_source_rgba (draw_context, _bg_color);
873 draw_context->fill ();
876 if ( _single_exposure ) {
878 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
884 gdk_region_get_rectangles (ev->region, &rects, &nrects);
885 for (gint n = 0; n < nrects; ++n) {
886 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
887 render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
893 draw_context->pop_group_to_source ();
894 draw_context->paint ();
897 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
898 if (getenv("ARDOUR_IMAGE_SURFACE")) {
900 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
901 /* now blit our private surface back to the GDK one */
903 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
904 window_context->clip ();
905 window_context->set_source (canvas_image, 0, 0);
906 window_context->set_operator (Cairo::OPERATOR_SOURCE);
907 window_context->paint ();
909 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
913 #ifdef CANVAS_PROFILE
914 const int64_t end = g_get_monotonic_time ();
915 const int64_t elapsed = end - start;
916 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
922 /** Handler for GDK scroll events.
924 * @return true if the event was handled.
927 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
929 /* translate event coordinates from window to canvas */
931 GdkEvent copy = *((GdkEvent*)ev);
932 Duple winpos = Duple (ev->x, ev->y);
933 Duple where = window_to_canvas (winpos);
935 pick_current_item (winpos, ev->state);
937 copy.button.x = where.x;
938 copy.button.y = where.y;
940 /* Coordinates in the event will be canvas coordinates, correctly adjusted
941 for scroll if this GtkCanvas is in a GtkCanvasViewport.
944 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
945 return deliver_event (reinterpret_cast<GdkEvent*>(©));
948 /** Handler for GDK key press events.
950 * @return true if the event was handled.
953 GtkCanvas::on_key_press_event (GdkEventKey* ev)
955 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
956 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
959 /** Handler for GDK key release events.
961 * @return true if the event was handled.
964 GtkCanvas::on_key_release_event (GdkEventKey* ev)
966 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
967 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
970 /** Handler for GDK button press events.
972 * @return true if the event was handled.
975 GtkCanvas::on_button_press_event (GdkEventButton* ev)
977 /* translate event coordinates from window to canvas */
979 GdkEvent copy = *((GdkEvent*)ev);
980 Duple winpos = Duple (ev->x, ev->y);
981 Duple where = window_to_canvas (winpos);
983 pick_current_item (winpos, ev->state);
985 copy.button.x = where.x;
986 copy.button.y = where.y;
988 /* Coordinates in the event will be canvas coordinates, correctly adjusted
989 for scroll if this GtkCanvas is in a GtkCanvasViewport.
992 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
993 return deliver_event (reinterpret_cast<GdkEvent*>(©));
996 /** Handler for GDK button release events.
998 * @return true if the event was handled.
1001 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1003 /* translate event coordinates from window to canvas */
1005 GdkEvent copy = *((GdkEvent*)ev);
1006 Duple winpos = Duple (ev->x, ev->y);
1007 Duple where = window_to_canvas (winpos);
1009 pick_current_item (winpos, ev->state);
1011 copy.button.x = where.x;
1012 copy.button.y = where.y;
1014 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1015 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1018 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1019 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1023 GtkCanvas::get_mouse_position (Duple& winpos) const
1027 Gdk::ModifierType mask;
1028 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1031 std::cerr << " no self window\n";
1032 winpos = Duple (0, 0);
1036 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1044 /** Handler for GDK motion events.
1046 * @return true if the event was handled.
1049 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1053 /* translate event coordinates from window to canvas */
1055 GdkEvent copy = *((GdkEvent*)ev);
1056 Duple point (ev->x, ev->y);
1057 Duple where = window_to_canvas (point);
1059 copy.motion.x = where.x;
1060 copy.motion.y = where.y;
1062 /* Coordinates in "copy" will be canvas coordinates,
1065 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));
1067 MouseMotion (point); /* EMIT SIGNAL */
1069 pick_current_item (point, ev->state);
1071 /* Now deliver the motion event. It may seem a little inefficient
1072 to recompute the items under the event, but the enter notify/leave
1073 events may have deleted canvas items so it is important to
1074 recompute the list in deliver_event.
1077 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1081 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1083 pick_current_item (Duple (ev->x, ev->y), ev->state);
1088 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1090 switch (ev->detail) {
1091 case GDK_NOTIFY_ANCESTOR:
1092 case GDK_NOTIFY_UNKNOWN:
1093 case GDK_NOTIFY_VIRTUAL:
1094 case GDK_NOTIFY_NONLINEAR:
1095 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1096 /* leaving window, cancel any tooltips */
1097 stop_tooltip_timeout ();
1101 /* we don't care about any other kind
1102 of leave event (notably GDK_NOTIFY_INFERIOR)
1106 _new_current_item = 0;
1107 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1111 /** Called to request a redraw of our canvas.
1112 * @param area Area to redraw, in window coordinates.
1115 GtkCanvas::request_redraw (Rect const & request)
1123 Coord const w = width ();
1124 Coord const h = height ();
1126 /* clamp area requested to actual visible window */
1128 real_area.x0 = max (0.0, min (w, request.x0));
1129 real_area.x1 = max (0.0, min (w, request.x1));
1130 real_area.y0 = max (0.0, min (h, request.y0));
1131 real_area.y1 = max (0.0, min (h, request.y1));
1133 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1136 /** Called to request that we try to get a particular size for ourselves.
1137 * @param size Size to request, in pixels.
1140 GtkCanvas::request_size (Duple size)
1144 if (req.x > INT_MAX) {
1148 if (req.y > INT_MAX) {
1152 set_size_request (req.x, req.y);
1155 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1156 * This is typically used for dragging items around, so that they are grabbed during
1158 * @param item Item to grab.
1161 GtkCanvas::grab (Item* item)
1163 /* XXX: should this be doing gdk_pointer_grab? */
1164 _grabbed_item = item;
1168 /** `Ungrab' any item that was previously grabbed */
1170 GtkCanvas::ungrab ()
1172 /* XXX: should this be doing gdk_pointer_ungrab? */
1176 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1178 * @param item Item to grab.
1181 GtkCanvas::focus (Item* item)
1183 _focused_item = item;
1187 GtkCanvas::unfocus (Item* item)
1189 if (item == _focused_item) {
1194 /** @return The visible area of the canvas, in window coordinates */
1196 GtkCanvas::visible_area () const
1198 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1202 GtkCanvas::width() const
1204 return get_allocation().get_width();
1208 GtkCanvas::height() const
1210 return get_allocation().get_height();
1214 GtkCanvas::start_tooltip_timeout (Item* item)
1216 stop_tooltip_timeout ();
1218 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1219 current_tooltip_item = item;
1221 /* wait for the first idle that happens after this is
1222 called. this means that we've stopped processing events, which
1223 in turn implies that the user has stopped doing stuff for a
1227 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1232 GtkCanvas::really_start_tooltip_timeout ()
1234 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1235 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1238 if (current_tooltip_item) {
1239 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1242 return false; /* this is called from an idle callback, don't call it again */
1246 GtkCanvas::stop_tooltip_timeout ()
1248 current_tooltip_item = 0;
1249 tooltip_timeout_connection.disconnect ();
1253 GtkCanvas::show_tooltip ()
1255 Rect tooltip_item_bbox;
1257 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1261 if (!tooltip_window) {
1262 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1263 tooltip_label = manage (new Gtk::Label);
1264 tooltip_label->show ();
1265 tooltip_window->add (*tooltip_label);
1266 tooltip_window->set_border_width (1);
1267 tooltip_window->set_name ("tooltip");
1270 tooltip_label->set_text (current_tooltip_item->tooltip());
1272 /* figure out where to position the tooltip */
1274 Gtk::Widget* toplevel = get_toplevel();
1276 int pointer_x, pointer_y;
1277 Gdk::ModifierType mask;
1279 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1281 Duple tooltip_window_origin (pointer_x, pointer_y);
1283 /* convert to root window coordinates */
1286 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1288 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1290 /* we don't want the pointer to be inside the window when it is
1291 * displayed, because then we generate a leave/enter event pair when
1292 * the window is displayed then hidden - the enter event will
1293 * trigger a new tooltip timeout.
1295 * So move the window right of the pointer position by just a enough
1296 * to get it away from the pointer.
1299 tooltip_window_origin.x += 30;
1300 tooltip_window_origin.y += 45;
1302 /* move the tooltip window into position */
1304 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1308 tooltip_window->present ();
1310 /* called from a timeout handler, don't call it again */
1316 GtkCanvas::hide_tooltip ()
1318 /* hide it if its there */
1320 if (tooltip_window) {
1321 tooltip_window->hide ();
1323 // Delete the tooltip window so it'll get re-created
1324 // (i.e. properly re-sized) on the next usage.
1325 delete tooltip_window;
1326 tooltip_window = NULL;
1330 Glib::RefPtr<Pango::Context>
1331 GtkCanvas::get_pango_context ()
1333 return Glib::wrap (gdk_pango_context_get());
1336 /** Create a GtkCanvaSViewport.
1337 * @param hadj Adjustment to use for horizontal scrolling.
1338 * @param vadj Adjustment to use for vertica scrolling.
1340 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1341 : Alignment (0, 0, 1.0, 1.0)
1342 , hadjustment (hadj)
1343 , vadjustment (vadj)
1347 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1348 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1352 GtkCanvasViewport::scrolled ()
1354 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1358 /** Handler for when GTK asks us what minimum size we want.
1359 * @param req Requsition to fill in.
1362 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1364 /* force the canvas to size itself */
1365 // _canvas.root()->bounding_box();