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
105 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
106 cerr << this << " RENDER: " << area << endl;
107 //cerr << "CANVAS @ " << this << endl;
109 //cerr << "-------------------------\n";
115 Rect root_bbox = _root.bounding_box();
117 /* the root has no bounding box, so there's nothing to render */
121 Rect draw = root_bbox.intersection (area);
124 /* there's a common area between the root and the requested
128 _root.render (draw, context);
130 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
131 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
132 // This transparently colors the rect being rendered, after it has been drawn.
133 double r = (random() % 65536) /65536.0;
134 double g = (random() % 65536) /65536.0;
135 double b = (random() % 65536) /65536.0;
136 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
137 context->set_source_rgba (r, g, b, 0.25);
146 operator<< (ostream& o, Canvas& c)
153 Canvas::indent() const
157 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
165 Canvas::render_indent() const
169 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
177 Canvas::dump (ostream& o) const
183 /** Called when an item has been shown or hidden.
184 * @param item Item that has been shown or hidden.
187 Canvas::item_shown_or_hidden (Item* item)
189 Rect bbox = item->bounding_box ();
191 if (item->item_to_window (bbox).intersection (visible_area ())) {
192 queue_draw_item_area (item, bbox);
197 /** Called when an item has a change to its visual properties
198 * that do NOT affect its bounding box.
199 * @param item Item that has been modified.
202 Canvas::item_visual_property_changed (Item* item)
204 Rect bbox = item->bounding_box ();
206 if (item->item_to_window (bbox).intersection (visible_area ())) {
207 queue_draw_item_area (item, bbox);
212 /** Called when an item has changed, but not moved.
213 * @param item Item that has changed.
214 * @param pre_change_bounding_box The bounding box of item before the change,
215 * in the item's coordinates.
218 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
220 Rect window_bbox = visible_area ();
222 if (pre_change_bounding_box) {
223 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
224 /* request a redraw of the item's old bounding box */
225 queue_draw_item_area (item, pre_change_bounding_box);
229 Rect post_change_bounding_box = item->bounding_box ();
231 if (post_change_bounding_box) {
232 if (item->item_to_window (post_change_bounding_box).intersection (window_bbox)) {
233 /* request a redraw of the item's new bounding box */
234 queue_draw_item_area (item, post_change_bounding_box);
240 Canvas::window_to_canvas (Duple const & d) const
242 ScrollGroup* best_group = 0;
245 /* if the coordinates are negative, clamp to zero and find the item
246 * that covers that "edge" position.
251 if (in_window.x < 0) {
254 if (in_window.y < 0) {
258 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
260 if ((*s)->covers_window (in_window)) {
263 /* XXX January 22nd 2015: leaving this in place for now
264 * but I think it fixes a bug that really should be
265 * fixed in a different way (and will be) by my next
266 * commit. But it may still be relevant.
269 /* If scroll groups overlap, choose the one with the highest sensitivity,
270 that is, choose an HV scroll group over an H or V
273 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
275 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
276 /* Can't do any better than this. */
284 return d.translate (best_group->scroll_offset());
291 Canvas::canvas_to_window (Duple const & d, bool rounded) const
293 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
294 * as children of the root group, so we just scan its first level
295 * children and see what we can find.
298 std::list<Item*> const& root_children (_root.items());
302 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
303 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
309 wd = d.translate (-sg->scroll_offset());
314 /* Note that this intentionally almost always returns integer coordinates */
324 /** Called when an item has moved.
325 * @param item Item that has moved.
326 * @param pre_change_parent_bounding_box The bounding box of the item before
327 * the move, in its parent's coordinates.
330 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
332 if (pre_change_parent_bounding_box) {
333 /* request a redraw of where the item used to be. The box has
334 * to be in parent coordinate space since the bounding box of
335 * an item does not change when moved. If we use
336 * item->item_to_canvas() on the old bounding box, we will be
338 * using the item's new position, and so will compute the wrong
339 * invalidation area. If we use the parent (which has not
340 * moved, then this will work.
342 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
345 Rect post_change_bounding_box = item->bounding_box ();
346 if (post_change_bounding_box) {
347 /* request a redraw of where the item now is */
348 queue_draw_item_area (item, post_change_bounding_box);
352 /** Request a redraw of a particular area in an item's coordinates.
354 * @param area Area to redraw in the item's coordinates.
357 Canvas::queue_draw_item_area (Item* item, Rect area)
359 request_redraw (item->item_to_window (area));
363 Canvas::set_tooltip_timeout (uint32_t msecs)
365 tooltip_timeout_msecs = msecs;
369 Canvas::set_background_color (Color c)
373 Rect r = _root.bounding_box();
376 request_redraw (_root.item_to_window (r));
381 GtkCanvas::re_enter ()
383 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
385 pick_current_item (0);
388 /** Construct a GtkCanvas */
389 GtkCanvas::GtkCanvas ()
391 , _new_current_item (0)
394 , _single_exposure (1)
395 , current_tooltip_item (0)
400 /* these are the events we want to know about */
401 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
402 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
403 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
405 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
406 # ifndef __ppc__ // would need to flip RGBA <> RGBA
407 _nsglview = Gtkmm2ext::nsglview_create (this);
413 GtkCanvas::pick_current_item (int state)
418 /* this version of ::pick_current_item() is called after an item is
419 * added or removed, so we have no coordinates to work from as is the
420 * case with a motion event. Find out where the mouse is and use that.
423 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
425 if (pointer_window != get_window()) {
429 pick_current_item (Duple (x, y), state);
432 /** Given @param point (a position in window coordinates)
433 * and mouse state @param state, check to see if _current_item
434 * (which will be used to deliver events) should change.
437 GtkCanvas::pick_current_item (Duple const & point, int state)
439 /* we do not enter/leave items during a drag/grab */
445 /* find the items at the given window position */
447 vector<Item const *> items;
448 _root.add_items_at_point (point, items);
450 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
453 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
454 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
456 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
458 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
464 /* put all items at point that are event-sensitive and visible and NOT
465 groups into within_items. Note that items is sorted from bottom to
466 top, but we're going to reverse that for within_items so that its
467 first item is the upper-most item that can be chosen as _current_item.
470 vector<Item const *>::const_iterator i;
471 list<Item const *> within_items;
473 for (i = items.begin(); i != items.end(); ++i) {
475 Item const * possible_item = *i;
477 /* We ignore invisible items, containers and items that ignore events */
479 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
482 within_items.push_front (possible_item);
485 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
487 if (within_items.empty()) {
489 /* no items at point, just send leave event below */
490 _new_current_item = 0;
494 if (within_items.front() == _current_item) {
495 /* uppermost item at point is already _current_item */
496 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
500 _new_current_item = const_cast<Item*> (within_items.front());
503 if (_new_current_item != _current_item) {
504 deliver_enter_leave (point, state);
508 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
510 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
515 /** Deliver a series of enter & leave events based on the pointer position being at window
516 * coordinate @param point, and pointer @param state (modifier keys, etc)
519 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
521 /* setup enter & leave event structures */
523 Glib::RefPtr<Gdk::Window> win = get_window();
529 GdkEventCrossing enter_event;
530 enter_event.type = GDK_ENTER_NOTIFY;
531 enter_event.window = win->gobj();
532 enter_event.send_event = 0;
533 enter_event.subwindow = 0;
534 enter_event.mode = GDK_CROSSING_NORMAL;
535 enter_event.focus = FALSE;
536 enter_event.state = state;
538 /* Events delivered to canvas items are expected to be in canvas
539 * coordinates but @param point is in window coordinates.
542 Duple c = window_to_canvas (point);
546 GdkEventCrossing leave_event = enter_event;
547 leave_event.type = GDK_LEAVE_NOTIFY;
550 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
551 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
552 vector<Item*> items_to_leave_virtual;
553 vector<Item*> items_to_enter_virtual;
555 if (_new_current_item == 0) {
557 leave_detail = GDK_NOTIFY_UNKNOWN;
561 /* no current item, so also send virtual leave events to the
562 * entire heirarchy for the current item
565 for (i = _current_item->parent(); i ; i = i->parent()) {
566 items_to_leave_virtual.push_back (i);
570 } else if (_current_item == 0) {
572 enter_detail = GDK_NOTIFY_UNKNOWN;
574 /* no current item, so also send virtual enter events to the
575 * entire heirarchy for the new item
578 for (i = _new_current_item->parent(); i ; i = i->parent()) {
579 items_to_enter_virtual.push_back (i);
582 } else if (_current_item->is_descendant_of (*_new_current_item)) {
584 /* move from descendant to ancestor (X: "_current_item is an
585 * inferior ("child") of _new_current_item")
587 * Deliver "virtual" leave notifications to all items in the
588 * heirarchy between current and new_current.
591 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
592 items_to_leave_virtual.push_back (i);
595 enter_detail = GDK_NOTIFY_INFERIOR;
596 leave_detail = GDK_NOTIFY_ANCESTOR;
598 } else if (_new_current_item->is_descendant_of (*_current_item)) {
599 /* move from ancestor to descendant (X: "_new_current_item is
600 * an inferior ("child") of _current_item")
602 * Deliver "virtual" enter notifications to all items in the
603 * heirarchy between current and new_current.
606 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
607 items_to_enter_virtual.push_back (i);
610 enter_detail = GDK_NOTIFY_ANCESTOR;
611 leave_detail = GDK_NOTIFY_INFERIOR;
615 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
617 /* deliver virtual leave events to everything between _current
618 * and common_ancestor.
621 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
622 items_to_leave_virtual.push_back (i);
625 /* deliver virtual enter events to everything between
626 * _new_current and common_ancestor.
629 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
630 items_to_enter_virtual.push_back (i);
633 enter_detail = GDK_NOTIFY_NONLINEAR;
634 leave_detail = GDK_NOTIFY_NONLINEAR;
638 if (_current_item && !_current_item->ignore_events ()) {
639 leave_event.detail = leave_detail;
640 _current_item->Event ((GdkEvent*)&leave_event);
641 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
644 leave_event.detail = GDK_NOTIFY_VIRTUAL;
645 enter_event.detail = GDK_NOTIFY_VIRTUAL;
647 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
648 if (!(*it)->ignore_events()) {
649 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
650 (*it)->Event ((GdkEvent*)&leave_event);
654 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
655 if (!(*it)->ignore_events()) {
656 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
657 (*it)->Event ((GdkEvent*)&enter_event);
658 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
662 if (_new_current_item && !_new_current_item->ignore_events()) {
663 enter_event.detail = enter_detail;
664 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
665 start_tooltip_timeout (_new_current_item);
666 _new_current_item->Event ((GdkEvent*)&enter_event);
669 _current_item = _new_current_item;
673 /** Deliver an event to the appropriate item; either the grabbed item, or
674 * one of the items underneath the event.
675 * @param point Position that the event has occurred at, in canvas coordinates.
676 * @param event The event.
679 GtkCanvas::deliver_event (GdkEvent* event)
681 /* Point in in canvas coordinate space */
683 const Item* event_item;
686 /* we have a grabbed item, so everything gets sent there */
687 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
688 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
689 event_item = _grabbed_item;
691 event_item = _current_item;
698 /* run through the items from child to parent, until one claims the event */
700 Item* item = const_cast<Item*> (event_item);
704 Item* parent = item->parent ();
706 if (!item->ignore_events () &&
707 item->Event (event)) {
708 /* this item has just handled the event */
710 PBD::DEBUG::CanvasEvents,
711 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
717 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)));
719 if ((item = parent) == 0) {
728 /** Called when an item is being destroyed.
729 * @param item Item being destroyed.
730 * @param bounding_box Last known bounding box of the item.
733 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
736 queue_draw_item_area (item, bounding_box);
739 if (_new_current_item == item) {
740 _new_current_item = 0;
743 if (_grabbed_item == item) {
747 if (_focused_item == item) {
751 if (current_tooltip_item) {
752 current_tooltip_item = 0;
753 stop_tooltip_timeout ();
756 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
758 scrollers.remove (sg);
761 if (_current_item == item) {
762 /* no need to send a leave event to this item, since it is going away
765 pick_current_item (0); // no mouse state
771 GtkCanvas::on_realize ()
773 Gtk::EventBox::on_realize();
776 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
782 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
784 EventBox::on_size_allocate (a);
785 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
786 if (getenv("ARDOUR_IMAGE_SURFACE")) {
788 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
789 /* allocate an image surface as large as the canvas itself */
791 canvas_image.clear ();
792 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
794 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
801 gtk_widget_translate_coordinates(
803 GTK_WIDGET(get_toplevel()->gobj()),
805 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
811 /** Handler for GDK expose events.
813 * @return true if the event was handled.
816 GtkCanvas::on_expose_event (GdkEventExpose* ev)
823 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
828 #ifdef CANVAS_PROFILE
829 const int64_t start = g_get_monotonic_time ();
832 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
833 Cairo::RefPtr<Cairo::Context> draw_context;
834 Cairo::RefPtr<Cairo::Context> window_context;
835 if (getenv("ARDOUR_IMAGE_SURFACE")) {
837 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
839 draw_context = Cairo::Context::create (canvas_image);
840 window_context = get_window()->create_cairo_context ();
842 draw_context = get_window()->create_cairo_context ();
844 #elif defined USE_CAIRO_IMAGE_SURFACE
846 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
848 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
849 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
851 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
854 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
855 draw_context->clip();
858 /* group calls cairo_quartz_surface_create() which
859 * effectively uses a CGBitmapContext + image-surface
861 * This avoids expensive argb32_image_mark_image() during drawing.
862 * Although the final paint() operation still takes the slow path
863 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
865 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
866 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
868 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
870 draw_context->push_group ();
873 /* draw background color */
874 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
875 set_source_rgba (draw_context, _bg_color);
876 draw_context->fill ();
879 if ( _single_exposure ) {
881 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
887 gdk_region_get_rectangles (ev->region, &rects, &nrects);
888 for (gint n = 0; n < nrects; ++n) {
889 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
890 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
896 draw_context->pop_group_to_source ();
897 draw_context->paint ();
900 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
901 if (getenv("ARDOUR_IMAGE_SURFACE")) {
903 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
904 /* now blit our private surface back to the GDK one */
906 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
907 window_context->clip ();
908 window_context->set_source (canvas_image, 0, 0);
909 window_context->set_operator (Cairo::OPERATOR_SOURCE);
910 window_context->paint ();
912 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
916 #ifdef CANVAS_PROFILE
917 const int64_t end = g_get_monotonic_time ();
918 const int64_t elapsed = end - start;
919 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
925 /** Handler for GDK scroll events.
927 * @return true if the event was handled.
930 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
932 /* translate event coordinates from window to canvas */
934 GdkEvent copy = *((GdkEvent*)ev);
935 Duple winpos = Duple (ev->x, ev->y);
936 Duple where = window_to_canvas (winpos);
938 pick_current_item (winpos, ev->state);
940 copy.button.x = where.x;
941 copy.button.y = where.y;
943 /* Coordinates in the event will be canvas coordinates, correctly adjusted
944 for scroll if this GtkCanvas is in a GtkCanvasViewport.
947 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
948 return deliver_event (reinterpret_cast<GdkEvent*>(©));
951 /** Handler for GDK key press events.
953 * @return true if the event was handled.
956 GtkCanvas::on_key_press_event (GdkEventKey* ev)
958 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
959 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
962 /** Handler for GDK key release events.
964 * @return true if the event was handled.
967 GtkCanvas::on_key_release_event (GdkEventKey* ev)
969 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
970 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
973 /** Handler for GDK button press events.
975 * @return true if the event was handled.
978 GtkCanvas::on_button_press_event (GdkEventButton* ev)
980 /* translate event coordinates from window to canvas */
982 GdkEvent copy = *((GdkEvent*)ev);
983 Duple winpos = Duple (ev->x, ev->y);
984 Duple where = window_to_canvas (winpos);
986 pick_current_item (winpos, ev->state);
988 copy.button.x = where.x;
989 copy.button.y = where.y;
991 /* Coordinates in the event will be canvas coordinates, correctly adjusted
992 for scroll if this GtkCanvas is in a GtkCanvasViewport.
995 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
996 return deliver_event (reinterpret_cast<GdkEvent*>(©));
999 /** Handler for GDK button release events.
1001 * @return true if the event was handled.
1004 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1006 /* translate event coordinates from window to canvas */
1008 GdkEvent copy = *((GdkEvent*)ev);
1009 Duple winpos = Duple (ev->x, ev->y);
1010 Duple where = window_to_canvas (winpos);
1012 pick_current_item (winpos, ev->state);
1014 copy.button.x = where.x;
1015 copy.button.y = where.y;
1017 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1018 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1021 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1022 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1026 GtkCanvas::get_mouse_position (Duple& winpos) const
1030 Gdk::ModifierType mask;
1031 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1034 std::cerr << " no self window\n";
1035 winpos = Duple (0, 0);
1039 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1047 /** Handler for GDK motion events.
1049 * @return true if the event was handled.
1052 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1056 /* translate event coordinates from window to canvas */
1058 GdkEvent copy = *((GdkEvent*)ev);
1059 Duple point (ev->x, ev->y);
1060 Duple where = window_to_canvas (point);
1062 copy.motion.x = where.x;
1063 copy.motion.y = where.y;
1065 /* Coordinates in "copy" will be canvas coordinates,
1068 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));
1070 MouseMotion (point); /* EMIT SIGNAL */
1072 pick_current_item (point, ev->state);
1074 /* Now deliver the motion event. It may seem a little inefficient
1075 to recompute the items under the event, but the enter notify/leave
1076 events may have deleted canvas items so it is important to
1077 recompute the list in deliver_event.
1080 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1084 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1086 pick_current_item (Duple (ev->x, ev->y), ev->state);
1091 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1093 switch (ev->detail) {
1094 case GDK_NOTIFY_ANCESTOR:
1095 case GDK_NOTIFY_UNKNOWN:
1096 case GDK_NOTIFY_VIRTUAL:
1097 case GDK_NOTIFY_NONLINEAR:
1098 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1099 /* leaving window, cancel any tooltips */
1100 stop_tooltip_timeout ();
1104 /* we don't care about any other kind
1105 of leave event (notably GDK_NOTIFY_INFERIOR)
1109 _new_current_item = 0;
1110 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1114 /** Called to request a redraw of our canvas.
1115 * @param area Area to redraw, in window coordinates.
1118 GtkCanvas::request_redraw (Rect const & request)
1126 Coord const w = width ();
1127 Coord const h = height ();
1129 /* clamp area requested to actual visible window */
1131 real_area.x0 = max (0.0, min (w, request.x0));
1132 real_area.x1 = max (0.0, min (w, request.x1));
1133 real_area.y0 = max (0.0, min (h, request.y0));
1134 real_area.y1 = max (0.0, min (h, request.y1));
1136 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1139 /** Called to request that we try to get a particular size for ourselves.
1140 * @param size Size to request, in pixels.
1143 GtkCanvas::request_size (Duple size)
1147 if (req.x > INT_MAX) {
1151 if (req.y > INT_MAX) {
1155 set_size_request (req.x, req.y);
1158 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1159 * This is typically used for dragging items around, so that they are grabbed during
1161 * @param item Item to grab.
1164 GtkCanvas::grab (Item* item)
1166 /* XXX: should this be doing gdk_pointer_grab? */
1167 _grabbed_item = item;
1171 /** `Ungrab' any item that was previously grabbed */
1173 GtkCanvas::ungrab ()
1175 /* XXX: should this be doing gdk_pointer_ungrab? */
1179 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1181 * @param item Item to grab.
1184 GtkCanvas::focus (Item* item)
1186 _focused_item = item;
1190 GtkCanvas::unfocus (Item* item)
1192 if (item == _focused_item) {
1197 /** @return The visible area of the canvas, in window coordinates */
1199 GtkCanvas::visible_area () const
1201 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1205 GtkCanvas::width() const
1207 return get_allocation().get_width();
1211 GtkCanvas::height() const
1213 return get_allocation().get_height();
1217 GtkCanvas::start_tooltip_timeout (Item* item)
1219 stop_tooltip_timeout ();
1221 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1222 current_tooltip_item = item;
1224 /* wait for the first idle that happens after this is
1225 called. this means that we've stopped processing events, which
1226 in turn implies that the user has stopped doing stuff for a
1230 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1235 GtkCanvas::really_start_tooltip_timeout ()
1237 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1238 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1241 if (current_tooltip_item) {
1242 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1245 return false; /* this is called from an idle callback, don't call it again */
1249 GtkCanvas::stop_tooltip_timeout ()
1251 current_tooltip_item = 0;
1252 tooltip_timeout_connection.disconnect ();
1256 GtkCanvas::show_tooltip ()
1258 Rect tooltip_item_bbox;
1260 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1264 if (!tooltip_window) {
1265 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1266 tooltip_label = manage (new Gtk::Label);
1267 tooltip_label->show ();
1268 tooltip_window->add (*tooltip_label);
1269 tooltip_window->set_border_width (1);
1270 tooltip_window->set_name ("tooltip");
1273 tooltip_label->set_text (current_tooltip_item->tooltip());
1275 /* figure out where to position the tooltip */
1277 Gtk::Widget* toplevel = get_toplevel();
1279 int pointer_x, pointer_y;
1280 Gdk::ModifierType mask;
1282 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1284 Duple tooltip_window_origin (pointer_x, pointer_y);
1286 /* convert to root window coordinates */
1289 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1291 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1293 /* we don't want the pointer to be inside the window when it is
1294 * displayed, because then we generate a leave/enter event pair when
1295 * the window is displayed then hidden - the enter event will
1296 * trigger a new tooltip timeout.
1298 * So move the window right of the pointer position by just a enough
1299 * to get it away from the pointer.
1302 tooltip_window_origin.x += 30;
1303 tooltip_window_origin.y += 45;
1305 /* move the tooltip window into position */
1307 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1311 tooltip_window->present ();
1313 /* called from a timeout handler, don't call it again */
1319 GtkCanvas::hide_tooltip ()
1321 /* hide it if its there */
1323 if (tooltip_window) {
1324 tooltip_window->hide ();
1326 // Delete the tooltip window so it'll get re-created
1327 // (i.e. properly re-sized) on the next usage.
1328 delete tooltip_window;
1329 tooltip_window = NULL;
1333 Glib::RefPtr<Pango::Context>
1334 GtkCanvas::get_pango_context ()
1336 return Glib::wrap (gdk_pango_context_get());
1339 /** Create a GtkCanvaSViewport.
1340 * @param hadj Adjustment to use for horizontal scrolling.
1341 * @param vadj Adjustment to use for vertica scrolling.
1343 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1344 : Alignment (0, 0, 1.0, 1.0)
1345 , hadjustment (hadj)
1346 , vadjustment (vadj)
1350 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1351 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1355 GtkCanvasViewport::scrolled ()
1357 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1361 /** Handler for when GTK asks us what minimum size we want.
1362 * @param req Requsition to fill in.
1365 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1367 /* force the canvas to size itself */
1368 // _canvas.root()->bounding_box();