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);
407 GtkCanvas::use_nsglview ()
410 assert (!is_realized());
411 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
412 _nsglview = Gtkmm2ext::nsglview_create (this);
417 GtkCanvas::pick_current_item (int state)
422 /* this version of ::pick_current_item() is called after an item is
423 * added or removed, so we have no coordinates to work from as is the
424 * case with a motion event. Find out where the mouse is and use that.
427 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
429 if (pointer_window != get_window()) {
433 pick_current_item (Duple (x, y), state);
436 /** Given @param point (a position in window coordinates)
437 * and mouse state @param state, check to see if _current_item
438 * (which will be used to deliver events) should change.
441 GtkCanvas::pick_current_item (Duple const & point, int state)
443 /* we do not enter/leave items during a drag/grab */
449 /* find the items at the given window position */
451 vector<Item const *> items;
452 _root.add_items_at_point (point, items);
454 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
457 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
458 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
460 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
462 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
468 /* put all items at point that are event-sensitive and visible and NOT
469 groups into within_items. Note that items is sorted from bottom to
470 top, but we're going to reverse that for within_items so that its
471 first item is the upper-most item that can be chosen as _current_item.
474 vector<Item const *>::const_iterator i;
475 list<Item const *> within_items;
477 for (i = items.begin(); i != items.end(); ++i) {
479 Item const * possible_item = *i;
481 /* We ignore invisible items, containers and items that ignore events */
483 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
486 within_items.push_front (possible_item);
489 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
491 if (within_items.empty()) {
493 /* no items at point, just send leave event below */
494 _new_current_item = 0;
498 if (within_items.front() == _current_item) {
499 /* uppermost item at point is already _current_item */
500 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
504 _new_current_item = const_cast<Item*> (within_items.front());
507 if (_new_current_item != _current_item) {
508 deliver_enter_leave (point, state);
512 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
514 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
519 /** Deliver a series of enter & leave events based on the pointer position being at window
520 * coordinate @param point, and pointer @param state (modifier keys, etc)
523 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
525 /* setup enter & leave event structures */
527 Glib::RefPtr<Gdk::Window> win = get_window();
533 GdkEventCrossing enter_event;
534 enter_event.type = GDK_ENTER_NOTIFY;
535 enter_event.window = win->gobj();
536 enter_event.send_event = 0;
537 enter_event.subwindow = 0;
538 enter_event.mode = GDK_CROSSING_NORMAL;
539 enter_event.focus = FALSE;
540 enter_event.state = state;
542 /* Events delivered to canvas items are expected to be in canvas
543 * coordinates but @param point is in window coordinates.
546 Duple c = window_to_canvas (point);
550 GdkEventCrossing leave_event = enter_event;
551 leave_event.type = GDK_LEAVE_NOTIFY;
554 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
555 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
556 vector<Item*> items_to_leave_virtual;
557 vector<Item*> items_to_enter_virtual;
559 if (_new_current_item == 0) {
561 leave_detail = GDK_NOTIFY_UNKNOWN;
565 /* no current item, so also send virtual leave events to the
566 * entire heirarchy for the current item
569 for (i = _current_item->parent(); i ; i = i->parent()) {
570 items_to_leave_virtual.push_back (i);
574 } else if (_current_item == 0) {
576 enter_detail = GDK_NOTIFY_UNKNOWN;
578 /* no current item, so also send virtual enter events to the
579 * entire heirarchy for the new item
582 for (i = _new_current_item->parent(); i ; i = i->parent()) {
583 items_to_enter_virtual.push_back (i);
586 } else if (_current_item->is_descendant_of (*_new_current_item)) {
588 /* move from descendant to ancestor (X: "_current_item is an
589 * inferior ("child") of _new_current_item")
591 * Deliver "virtual" leave notifications to all items in the
592 * heirarchy between current and new_current.
595 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
596 items_to_leave_virtual.push_back (i);
599 enter_detail = GDK_NOTIFY_INFERIOR;
600 leave_detail = GDK_NOTIFY_ANCESTOR;
602 } else if (_new_current_item->is_descendant_of (*_current_item)) {
603 /* move from ancestor to descendant (X: "_new_current_item is
604 * an inferior ("child") of _current_item")
606 * Deliver "virtual" enter notifications to all items in the
607 * heirarchy between current and new_current.
610 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
611 items_to_enter_virtual.push_back (i);
614 enter_detail = GDK_NOTIFY_ANCESTOR;
615 leave_detail = GDK_NOTIFY_INFERIOR;
619 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
621 /* deliver virtual leave events to everything between _current
622 * and common_ancestor.
625 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
626 items_to_leave_virtual.push_back (i);
629 /* deliver virtual enter events to everything between
630 * _new_current and common_ancestor.
633 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
634 items_to_enter_virtual.push_back (i);
637 enter_detail = GDK_NOTIFY_NONLINEAR;
638 leave_detail = GDK_NOTIFY_NONLINEAR;
642 if (_current_item && !_current_item->ignore_events ()) {
643 leave_event.detail = leave_detail;
644 _current_item->Event ((GdkEvent*)&leave_event);
645 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
648 leave_event.detail = GDK_NOTIFY_VIRTUAL;
649 enter_event.detail = GDK_NOTIFY_VIRTUAL;
651 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
652 if (!(*it)->ignore_events()) {
653 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
654 (*it)->Event ((GdkEvent*)&leave_event);
658 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
659 if (!(*it)->ignore_events()) {
660 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
661 (*it)->Event ((GdkEvent*)&enter_event);
662 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
666 if (_new_current_item && !_new_current_item->ignore_events()) {
667 enter_event.detail = enter_detail;
668 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
669 start_tooltip_timeout (_new_current_item);
670 _new_current_item->Event ((GdkEvent*)&enter_event);
673 _current_item = _new_current_item;
677 /** Deliver an event to the appropriate item; either the grabbed item, or
678 * one of the items underneath the event.
679 * @param point Position that the event has occurred at, in canvas coordinates.
680 * @param event The event.
683 GtkCanvas::deliver_event (GdkEvent* event)
685 /* Point in in canvas coordinate space */
687 const Item* event_item;
690 /* we have a grabbed item, so everything gets sent there */
691 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
692 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
693 event_item = _grabbed_item;
695 event_item = _current_item;
702 /* run through the items from child to parent, until one claims the event */
704 Item* item = const_cast<Item*> (event_item);
708 Item* parent = item->parent ();
710 if (!item->ignore_events () &&
711 item->Event (event)) {
712 /* this item has just handled the event */
714 PBD::DEBUG::CanvasEvents,
715 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
721 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)));
723 if ((item = parent) == 0) {
733 GtkCanvas::item_shown_or_hidden (Item* item)
735 if (item == current_tooltip_item) {
736 stop_tooltip_timeout ();
738 Canvas::item_shown_or_hidden (item);
741 /** Called when an item is being destroyed.
742 * @param item Item being destroyed.
743 * @param bounding_box Last known bounding box of the item.
746 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
749 queue_draw_item_area (item, bounding_box);
752 if (_new_current_item == item) {
753 _new_current_item = 0;
756 if (_grabbed_item == item) {
760 if (_focused_item == item) {
764 if (current_tooltip_item) {
765 current_tooltip_item = 0;
766 stop_tooltip_timeout ();
769 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
771 scrollers.remove (sg);
774 if (_current_item == item) {
775 /* no need to send a leave event to this item, since it is going away
778 pick_current_item (0); // no mouse state
784 GtkCanvas::on_realize ()
786 Gtk::EventBox::on_realize();
789 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
795 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
797 EventBox::on_size_allocate (a);
798 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
799 if (getenv("ARDOUR_IMAGE_SURFACE")) {
801 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
802 /* allocate an image surface as large as the canvas itself */
804 canvas_image.clear ();
805 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
807 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
814 gtk_widget_translate_coordinates(
816 GTK_WIDGET(get_toplevel()->gobj()),
818 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
824 /** Handler for GDK expose events.
826 * @return true if the event was handled.
829 GtkCanvas::on_expose_event (GdkEventExpose* ev)
836 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
841 #ifdef CANVAS_PROFILE
842 const int64_t start = g_get_monotonic_time ();
845 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
846 Cairo::RefPtr<Cairo::Context> draw_context;
847 Cairo::RefPtr<Cairo::Context> window_context;
848 if (getenv("ARDOUR_IMAGE_SURFACE")) {
850 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
852 draw_context = Cairo::Context::create (canvas_image);
853 window_context = get_window()->create_cairo_context ();
855 draw_context = get_window()->create_cairo_context ();
857 #elif defined USE_CAIRO_IMAGE_SURFACE
859 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
861 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
862 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
864 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
867 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
868 draw_context->clip();
871 /* group calls cairo_quartz_surface_create() which
872 * effectively uses a CGBitmapContext + image-surface
874 * This avoids expensive argb32_image_mark_image() during drawing.
875 * Although the final paint() operation still takes the slow path
876 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
878 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
879 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
881 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
883 draw_context->push_group ();
886 /* draw background color */
887 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
888 set_source_rgba (draw_context, _bg_color);
889 draw_context->fill ();
892 if ( _single_exposure ) {
894 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
900 gdk_region_get_rectangles (ev->region, &rects, &nrects);
901 for (gint n = 0; n < nrects; ++n) {
902 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
903 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
909 draw_context->pop_group_to_source ();
910 draw_context->paint ();
913 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
914 if (getenv("ARDOUR_IMAGE_SURFACE")) {
916 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
917 /* now blit our private surface back to the GDK one */
919 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
920 window_context->clip ();
921 window_context->set_source (canvas_image, 0, 0);
922 window_context->set_operator (Cairo::OPERATOR_SOURCE);
923 window_context->paint ();
925 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
929 #ifdef CANVAS_PROFILE
930 const int64_t end = g_get_monotonic_time ();
931 const int64_t elapsed = end - start;
932 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
938 /** Handler for GDK scroll events.
940 * @return true if the event was handled.
943 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
945 /* translate event coordinates from window to canvas */
947 GdkEvent copy = *((GdkEvent*)ev);
948 Duple winpos = Duple (ev->x, ev->y);
949 Duple where = window_to_canvas (winpos);
951 pick_current_item (winpos, ev->state);
953 copy.button.x = where.x;
954 copy.button.y = where.y;
956 /* Coordinates in the event will be canvas coordinates, correctly adjusted
957 for scroll if this GtkCanvas is in a GtkCanvasViewport.
960 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
961 return deliver_event (reinterpret_cast<GdkEvent*>(©));
964 /** Handler for GDK key press events.
966 * @return true if the event was handled.
969 GtkCanvas::on_key_press_event (GdkEventKey* ev)
971 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
972 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
975 /** Handler for GDK key release events.
977 * @return true if the event was handled.
980 GtkCanvas::on_key_release_event (GdkEventKey* ev)
982 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
983 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
986 /** Handler for GDK button press events.
988 * @return true if the event was handled.
991 GtkCanvas::on_button_press_event (GdkEventButton* ev)
993 /* translate event coordinates from window to canvas */
995 GdkEvent copy = *((GdkEvent*)ev);
996 Duple winpos = Duple (ev->x, ev->y);
997 Duple where = window_to_canvas (winpos);
999 pick_current_item (winpos, ev->state);
1001 copy.button.x = where.x;
1002 copy.button.y = where.y;
1004 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1005 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1008 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1009 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1012 /** Handler for GDK button release events.
1014 * @return true if the event was handled.
1017 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1019 /* translate event coordinates from window to canvas */
1021 GdkEvent copy = *((GdkEvent*)ev);
1022 Duple winpos = Duple (ev->x, ev->y);
1023 Duple where = window_to_canvas (winpos);
1025 pick_current_item (winpos, ev->state);
1027 copy.button.x = where.x;
1028 copy.button.y = where.y;
1030 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1031 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1034 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1035 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1039 GtkCanvas::get_mouse_position (Duple& winpos) const
1043 Gdk::ModifierType mask;
1044 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1047 std::cerr << " no self window\n";
1048 winpos = Duple (0, 0);
1052 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1060 /** Handler for GDK motion events.
1062 * @return true if the event was handled.
1065 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1069 /* translate event coordinates from window to canvas */
1071 GdkEvent copy = *((GdkEvent*)ev);
1072 Duple point (ev->x, ev->y);
1073 Duple where = window_to_canvas (point);
1075 copy.motion.x = where.x;
1076 copy.motion.y = where.y;
1078 /* Coordinates in "copy" will be canvas coordinates,
1081 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));
1083 MouseMotion (point); /* EMIT SIGNAL */
1085 pick_current_item (point, ev->state);
1087 /* Now deliver the motion event. It may seem a little inefficient
1088 to recompute the items under the event, but the enter notify/leave
1089 events may have deleted canvas items so it is important to
1090 recompute the list in deliver_event.
1093 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1097 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1099 pick_current_item (Duple (ev->x, ev->y), ev->state);
1104 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1106 switch (ev->detail) {
1107 case GDK_NOTIFY_ANCESTOR:
1108 case GDK_NOTIFY_UNKNOWN:
1109 case GDK_NOTIFY_VIRTUAL:
1110 case GDK_NOTIFY_NONLINEAR:
1111 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1112 /* leaving window, cancel any tooltips */
1113 stop_tooltip_timeout ();
1117 /* we don't care about any other kind
1118 of leave event (notably GDK_NOTIFY_INFERIOR)
1122 _new_current_item = 0;
1123 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1128 GtkCanvas::on_map ()
1130 Gtk::EventBox::on_map();
1133 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1134 Gtk::Allocation a = get_allocation();
1136 gtk_widget_translate_coordinates(
1138 GTK_WIDGET(get_toplevel()->gobj()),
1140 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1146 GtkCanvas::on_unmap ()
1148 stop_tooltip_timeout ();
1149 Gtk::EventBox::on_unmap();
1152 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1157 /** Called to request a redraw of our canvas.
1158 * @param area Area to redraw, in window coordinates.
1161 GtkCanvas::request_redraw (Rect const & request)
1169 Coord const w = width ();
1170 Coord const h = height ();
1172 /* clamp area requested to actual visible window */
1174 real_area.x0 = max (0.0, min (w, request.x0));
1175 real_area.x1 = max (0.0, min (w, request.x1));
1176 real_area.y0 = max (0.0, min (h, request.y0));
1177 real_area.y1 = max (0.0, min (h, request.y1));
1179 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1182 /** Called to request that we try to get a particular size for ourselves.
1183 * @param size Size to request, in pixels.
1186 GtkCanvas::request_size (Duple size)
1190 if (req.x > INT_MAX) {
1194 if (req.y > INT_MAX) {
1198 set_size_request (req.x, req.y);
1201 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1202 * This is typically used for dragging items around, so that they are grabbed during
1204 * @param item Item to grab.
1207 GtkCanvas::grab (Item* item)
1209 /* XXX: should this be doing gdk_pointer_grab? */
1210 _grabbed_item = item;
1214 /** `Ungrab' any item that was previously grabbed */
1216 GtkCanvas::ungrab ()
1218 /* XXX: should this be doing gdk_pointer_ungrab? */
1222 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1224 * @param item Item to grab.
1227 GtkCanvas::focus (Item* item)
1229 _focused_item = item;
1233 GtkCanvas::unfocus (Item* item)
1235 if (item == _focused_item) {
1240 /** @return The visible area of the canvas, in window coordinates */
1242 GtkCanvas::visible_area () const
1244 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1248 GtkCanvas::width() const
1250 return get_allocation().get_width();
1254 GtkCanvas::height() const
1256 return get_allocation().get_height();
1260 GtkCanvas::start_tooltip_timeout (Item* item)
1262 stop_tooltip_timeout ();
1264 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1265 current_tooltip_item = item;
1267 /* wait for the first idle that happens after this is
1268 called. this means that we've stopped processing events, which
1269 in turn implies that the user has stopped doing stuff for a
1273 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1278 GtkCanvas::really_start_tooltip_timeout ()
1280 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1281 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1284 if (current_tooltip_item) {
1285 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1288 return false; /* this is called from an idle callback, don't call it again */
1292 GtkCanvas::stop_tooltip_timeout ()
1294 current_tooltip_item = 0;
1295 tooltip_timeout_connection.disconnect ();
1299 GtkCanvas::show_tooltip ()
1301 Rect tooltip_item_bbox;
1303 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1307 if (!tooltip_window) {
1308 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1309 tooltip_label = manage (new Gtk::Label);
1310 tooltip_label->show ();
1311 tooltip_window->add (*tooltip_label);
1312 tooltip_window->set_border_width (1);
1313 tooltip_window->set_name ("tooltip");
1316 tooltip_label->set_text (current_tooltip_item->tooltip());
1318 /* figure out where to position the tooltip */
1320 Gtk::Widget* toplevel = get_toplevel();
1322 int pointer_x, pointer_y;
1323 Gdk::ModifierType mask;
1325 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1327 Duple tooltip_window_origin (pointer_x, pointer_y);
1329 /* convert to root window coordinates */
1332 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1334 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1336 /* we don't want the pointer to be inside the window when it is
1337 * displayed, because then we generate a leave/enter event pair when
1338 * the window is displayed then hidden - the enter event will
1339 * trigger a new tooltip timeout.
1341 * So move the window right of the pointer position by just a enough
1342 * to get it away from the pointer.
1345 tooltip_window_origin.x += 30;
1346 tooltip_window_origin.y += 45;
1348 /* move the tooltip window into position */
1350 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1354 tooltip_window->present ();
1356 /* called from a timeout handler, don't call it again */
1362 GtkCanvas::hide_tooltip ()
1364 /* hide it if its there */
1366 if (tooltip_window) {
1367 tooltip_window->hide ();
1369 // Delete the tooltip window so it'll get re-created
1370 // (i.e. properly re-sized) on the next usage.
1371 delete tooltip_window;
1372 tooltip_window = NULL;
1376 Glib::RefPtr<Pango::Context>
1377 GtkCanvas::get_pango_context ()
1379 return Glib::wrap (gdk_pango_context_get());
1382 /** Create a GtkCanvaSViewport.
1383 * @param hadj Adjustment to use for horizontal scrolling.
1384 * @param vadj Adjustment to use for vertica scrolling.
1386 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1387 : Alignment (0, 0, 1.0, 1.0)
1388 , hadjustment (hadj)
1389 , vadjustment (vadj)
1393 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1394 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1398 GtkCanvasViewport::scrolled ()
1400 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1404 /** Handler for when GTK asks us what minimum size we want.
1405 * @param req Requsition to fill in.
1408 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1410 /* force the canvas to size itself */
1411 // _canvas.root()->bounding_box();