2 Copyright (C) 2011 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
22 #define OPTIONAL_CAIRO_IMAGE_SURFACE
25 /** @file canvas/canvas.cc
26 * @brief Implementation of the main canvas classes.
31 #include <gtkmm/adjustment.h>
32 #include <gtkmm/label.h>
33 #include <gtkmm/window.h>
35 #include "gtkmm2ext/persistent_tooltip.h"
37 #include "pbd/compose.h"
38 #include "pbd/stacktrace.h"
40 #include "canvas/canvas.h"
41 #include "canvas/colors.h"
42 #include "canvas/debug.h"
43 #include "canvas/line.h"
44 #include "canvas/scroll_group.h"
45 #include "canvas/utils.h"
49 #include "gtkmm2ext/nsglview.h"
53 using namespace ArdourCanvas;
55 uint32_t Canvas::tooltip_timeout_msecs = 750;
57 /** Construct a new Canvas */
60 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
66 Canvas::scroll_to (Coord x, Coord y)
68 /* We do things this way because we do not want to recurse through
69 the canvas for every scroll. In the presence of large MIDI
70 tracks this means traversing item lists that include
71 thousands of items (notes).
73 This design limits us to moving only those items (groups, typically)
74 that should move in certain ways as we scroll. In other terms, it
75 becomes O(1) rather than O(N).
78 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
79 (*i)->scroll_to (Duple (x, y));
82 pick_current_item (0); // no current mouse position
86 Canvas::add_scroller (ScrollGroup& i)
88 scrollers.push_back (&i);
94 pick_current_item (0); // no current mouse position
97 /** Render an area of the canvas.
98 * @param area Area in window coordinates.
99 * @param context Cairo context to render to.
102 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
104 PreRender (); // emit signal
107 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
108 cerr << this << " RENDER: " << area << endl;
109 //cerr << "CANVAS @ " << this << endl;
111 //cerr << "-------------------------\n";
117 Rect root_bbox = _root.bounding_box();
119 /* the root has no bounding box, so there's nothing to render */
123 Rect draw = root_bbox.intersection (area);
126 /* there's a common area between the root and the requested
130 _root.render (draw, context);
132 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
133 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
134 // This transparently colors the rect being rendered, after it has been drawn.
135 double r = (random() % 65536) /65536.0;
136 double g = (random() % 65536) /65536.0;
137 double b = (random() % 65536) /65536.0;
138 context->rectangle (draw.x0, draw.y0, draw.x1 - draw.x0, draw.y1 - draw.y0);
139 context->set_source_rgba (r, g, b, 0.25);
148 operator<< (ostream& o, Canvas& c)
155 Canvas::indent() const
159 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
167 Canvas::render_indent() const
171 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
179 Canvas::dump (ostream& o) const
185 /** Called when an item has been shown or hidden.
186 * @param item Item that has been shown or hidden.
189 Canvas::item_shown_or_hidden (Item* item)
191 Rect bbox = item->bounding_box ();
193 if (item->item_to_window (bbox).intersection (visible_area ())) {
194 queue_draw_item_area (item, bbox);
199 /** Called when an item has a change to its visual properties
200 * that do NOT affect its bounding box.
201 * @param item Item that has been modified.
204 Canvas::item_visual_property_changed (Item* item)
206 Rect bbox = item->bounding_box ();
208 if (item->item_to_window (bbox).intersection (visible_area ())) {
209 queue_draw_item_area (item, bbox);
214 /** Called when an item has changed, but not moved.
215 * @param item Item that has changed.
216 * @param pre_change_bounding_box The bounding box of item before the change,
217 * in the item's coordinates.
220 Canvas::item_changed (Item* item, Rect pre_change_bounding_box)
222 Rect window_bbox = visible_area ();
224 if (pre_change_bounding_box) {
225 if (item->item_to_window (pre_change_bounding_box).intersection (window_bbox)) {
226 /* request a redraw of the item's old bounding box */
227 queue_draw_item_area (item, pre_change_bounding_box);
231 Rect post_change_bounding_box = item->bounding_box ();
233 if (post_change_bounding_box) {
234 if (item->item_to_window (post_change_bounding_box).intersection (window_bbox)) {
235 /* request a redraw of the item's new bounding box */
236 queue_draw_item_area (item, post_change_bounding_box);
242 Canvas::window_to_canvas (Duple const & d) const
244 ScrollGroup* best_group = 0;
247 /* if the coordinates are negative, clamp to zero and find the item
248 * that covers that "edge" position.
253 if (in_window.x < 0) {
256 if (in_window.y < 0) {
260 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
262 if ((*s)->covers_window (in_window)) {
265 /* XXX January 22nd 2015: leaving this in place for now
266 * but I think it fixes a bug that really should be
267 * fixed in a different way (and will be) by my next
268 * commit. But it may still be relevant.
271 /* If scroll groups overlap, choose the one with the highest sensitivity,
272 that is, choose an HV scroll group over an H or V
275 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
277 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
278 /* Can't do any better than this. */
286 return d.translate (best_group->scroll_offset());
293 Canvas::canvas_to_window (Duple const & d, bool rounded) const
295 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
296 * as children of the root group, so we just scan its first level
297 * children and see what we can find.
300 std::list<Item*> const& root_children (_root.items());
304 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
305 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
311 wd = d.translate (-sg->scroll_offset());
316 /* Note that this intentionally almost always returns integer coordinates */
326 /** Called when an item has moved.
327 * @param item Item that has moved.
328 * @param pre_change_parent_bounding_box The bounding box of the item before
329 * the move, in its parent's coordinates.
332 Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
334 if (pre_change_parent_bounding_box) {
335 /* request a redraw of where the item used to be. The box has
336 * to be in parent coordinate space since the bounding box of
337 * an item does not change when moved. If we use
338 * item->item_to_canvas() on the old bounding box, we will be
340 * using the item's new position, and so will compute the wrong
341 * invalidation area. If we use the parent (which has not
342 * moved, then this will work.
344 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box);
347 Rect post_change_bounding_box = item->bounding_box ();
348 if (post_change_bounding_box) {
349 /* request a redraw of where the item now is */
350 queue_draw_item_area (item, post_change_bounding_box);
354 /** Request a redraw of a particular area in an item's coordinates.
356 * @param area Area to redraw in the item's coordinates.
359 Canvas::queue_draw_item_area (Item* item, Rect area)
361 request_redraw (item->item_to_window (area));
365 Canvas::set_tooltip_timeout (uint32_t msecs)
367 tooltip_timeout_msecs = msecs;
371 Canvas::set_background_color (Color c)
375 Rect r = _root.bounding_box();
378 request_redraw (_root.item_to_window (r));
383 GtkCanvas::re_enter ()
385 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
387 pick_current_item (0);
390 /** Construct a GtkCanvas */
391 GtkCanvas::GtkCanvas ()
393 , _new_current_item (0)
396 , _single_exposure (1)
397 , current_tooltip_item (0)
402 /* these are the events we want to know about */
403 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
404 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
405 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
409 GtkCanvas::use_nsglview ()
412 assert (!is_realized());
413 #ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
414 _nsglview = Gtkmm2ext::nsglview_create (this);
419 GtkCanvas::pick_current_item (int state)
424 /* this version of ::pick_current_item() is called after an item is
425 * added or removed, so we have no coordinates to work from as is the
426 * case with a motion event. Find out where the mouse is and use that.
429 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
431 if (pointer_window != get_window()) {
435 pick_current_item (Duple (x, y), state);
438 /** Given @param point (a position in window coordinates)
439 * and mouse state @param state, check to see if _current_item
440 * (which will be used to deliver events) should change.
443 GtkCanvas::pick_current_item (Duple const & point, int state)
445 /* we do not enter/leave items during a drag/grab */
451 /* find the items at the given window position */
453 vector<Item const *> items;
454 _root.add_items_at_point (point, items);
456 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
459 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
460 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
462 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
464 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
470 /* put all items at point that are event-sensitive and visible and NOT
471 groups into within_items. Note that items is sorted from bottom to
472 top, but we're going to reverse that for within_items so that its
473 first item is the upper-most item that can be chosen as _current_item.
476 vector<Item const *>::const_iterator i;
477 list<Item const *> within_items;
479 for (i = items.begin(); i != items.end(); ++i) {
481 Item const * possible_item = *i;
483 /* We ignore invisible items, containers and items that ignore events */
485 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
488 within_items.push_front (possible_item);
491 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
493 if (within_items.empty()) {
495 /* no items at point, just send leave event below */
496 _new_current_item = 0;
500 if (within_items.front() == _current_item) {
501 /* uppermost item at point is already _current_item */
502 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
506 _new_current_item = const_cast<Item*> (within_items.front());
509 if (_new_current_item != _current_item) {
510 deliver_enter_leave (point, state);
514 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
516 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
521 /** Deliver a series of enter & leave events based on the pointer position being at window
522 * coordinate @param point, and pointer @param state (modifier keys, etc)
525 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
527 /* setup enter & leave event structures */
529 Glib::RefPtr<Gdk::Window> win = get_window();
535 GdkEventCrossing enter_event;
536 enter_event.type = GDK_ENTER_NOTIFY;
537 enter_event.window = win->gobj();
538 enter_event.send_event = 0;
539 enter_event.subwindow = 0;
540 enter_event.mode = GDK_CROSSING_NORMAL;
541 enter_event.focus = FALSE;
542 enter_event.state = state;
544 /* Events delivered to canvas items are expected to be in canvas
545 * coordinates but @param point is in window coordinates.
548 Duple c = window_to_canvas (point);
552 GdkEventCrossing leave_event = enter_event;
553 leave_event.type = GDK_LEAVE_NOTIFY;
556 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
557 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
558 vector<Item*> items_to_leave_virtual;
559 vector<Item*> items_to_enter_virtual;
561 if (_new_current_item == 0) {
563 leave_detail = GDK_NOTIFY_UNKNOWN;
567 /* no current item, so also send virtual leave events to the
568 * entire heirarchy for the current item
571 for (i = _current_item->parent(); i ; i = i->parent()) {
572 items_to_leave_virtual.push_back (i);
576 } else if (_current_item == 0) {
578 enter_detail = GDK_NOTIFY_UNKNOWN;
580 /* no current item, so also send virtual enter events to the
581 * entire heirarchy for the new item
584 for (i = _new_current_item->parent(); i ; i = i->parent()) {
585 items_to_enter_virtual.push_back (i);
588 } else if (_current_item->is_descendant_of (*_new_current_item)) {
590 /* move from descendant to ancestor (X: "_current_item is an
591 * inferior ("child") of _new_current_item")
593 * Deliver "virtual" leave notifications to all items in the
594 * heirarchy between current and new_current.
597 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
598 items_to_leave_virtual.push_back (i);
601 enter_detail = GDK_NOTIFY_INFERIOR;
602 leave_detail = GDK_NOTIFY_ANCESTOR;
604 } else if (_new_current_item->is_descendant_of (*_current_item)) {
605 /* move from ancestor to descendant (X: "_new_current_item is
606 * an inferior ("child") of _current_item")
608 * Deliver "virtual" enter notifications to all items in the
609 * heirarchy between current and new_current.
612 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
613 items_to_enter_virtual.push_back (i);
616 enter_detail = GDK_NOTIFY_ANCESTOR;
617 leave_detail = GDK_NOTIFY_INFERIOR;
621 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
623 /* deliver virtual leave events to everything between _current
624 * and common_ancestor.
627 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
628 items_to_leave_virtual.push_back (i);
631 /* deliver virtual enter events to everything between
632 * _new_current and common_ancestor.
635 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
636 items_to_enter_virtual.push_back (i);
639 enter_detail = GDK_NOTIFY_NONLINEAR;
640 leave_detail = GDK_NOTIFY_NONLINEAR;
644 if (_current_item && !_current_item->ignore_events ()) {
645 leave_event.detail = leave_detail;
646 _current_item->Event ((GdkEvent*)&leave_event);
647 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
650 leave_event.detail = GDK_NOTIFY_VIRTUAL;
651 enter_event.detail = GDK_NOTIFY_VIRTUAL;
653 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
654 if (!(*it)->ignore_events()) {
655 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
656 (*it)->Event ((GdkEvent*)&leave_event);
660 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
661 if (!(*it)->ignore_events()) {
662 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
663 (*it)->Event ((GdkEvent*)&enter_event);
664 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
668 if (_new_current_item && !_new_current_item->ignore_events()) {
669 enter_event.detail = enter_detail;
670 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
671 start_tooltip_timeout (_new_current_item);
672 _new_current_item->Event ((GdkEvent*)&enter_event);
675 _current_item = _new_current_item;
679 /** Deliver an event to the appropriate item; either the grabbed item, or
680 * one of the items underneath the event.
681 * @param point Position that the event has occurred at, in canvas coordinates.
682 * @param event The event.
685 GtkCanvas::deliver_event (GdkEvent* event)
687 /* Point in in canvas coordinate space */
689 const Item* event_item;
692 /* we have a grabbed item, so everything gets sent there */
693 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
694 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
695 event_item = _grabbed_item;
697 event_item = _current_item;
704 /* run through the items from child to parent, until one claims the event */
706 Item* item = const_cast<Item*> (event_item);
710 Item* parent = item->parent ();
712 if (!item->ignore_events () &&
713 item->Event (event)) {
714 /* this item has just handled the event */
716 PBD::DEBUG::CanvasEvents,
717 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
723 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)));
725 if ((item = parent) == 0) {
735 GtkCanvas::item_shown_or_hidden (Item* item)
737 if (item == current_tooltip_item) {
738 stop_tooltip_timeout ();
740 Canvas::item_shown_or_hidden (item);
743 /** Called when an item is being destroyed.
744 * @param item Item being destroyed.
745 * @param bounding_box Last known bounding box of the item.
748 GtkCanvas::item_going_away (Item* item, Rect bounding_box)
751 queue_draw_item_area (item, bounding_box);
754 if (_new_current_item == item) {
755 _new_current_item = 0;
758 if (_grabbed_item == item) {
762 if (_focused_item == item) {
766 if (current_tooltip_item) {
767 current_tooltip_item = 0;
768 stop_tooltip_timeout ();
771 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
773 scrollers.remove (sg);
776 if (_current_item == item) {
777 /* no need to send a leave event to this item, since it is going away
780 pick_current_item (0); // no mouse state
786 GtkCanvas::on_realize ()
788 Gtk::EventBox::on_realize();
791 Gtkmm2ext::nsglview_overlay (_nsglview, get_window()->gobj());
797 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
799 EventBox::on_size_allocate (a);
800 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
801 if (getenv("ARDOUR_IMAGE_SURFACE")) {
803 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
804 /* allocate an image surface as large as the canvas itself */
806 canvas_image.clear ();
807 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
809 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
816 gtk_widget_translate_coordinates(
818 GTK_WIDGET(get_toplevel()->gobj()),
820 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
826 /** Handler for GDK expose events.
828 * @return true if the event was handled.
831 GtkCanvas::on_expose_event (GdkEventExpose* ev)
838 Gtkmm2ext::nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
843 #ifdef CANVAS_PROFILE
844 const int64_t start = g_get_monotonic_time ();
847 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
848 Cairo::RefPtr<Cairo::Context> draw_context;
849 Cairo::RefPtr<Cairo::Context> window_context;
850 if (getenv("ARDOUR_IMAGE_SURFACE")) {
852 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
854 draw_context = Cairo::Context::create (canvas_image);
855 window_context = get_window()->create_cairo_context ();
857 draw_context = get_window()->create_cairo_context ();
859 #elif defined USE_CAIRO_IMAGE_SURFACE
861 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
863 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
864 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
866 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
869 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
870 draw_context->clip();
873 /* group calls cairo_quartz_surface_create() which
874 * effectively uses a CGBitmapContext + image-surface
876 * This avoids expensive argb32_image_mark_image() during drawing.
877 * Although the final paint() operation still takes the slow path
878 * through image_mark_image instead of ColorMaskCopyARGB888_sse :(
880 * profiling indicates a speed up of factor 2. (~ 5-10ms render time,
881 * instead of 10-20ms, which is still slow compared to XCB and win32 surfaces (~0.2 ms)
883 * Fixing this for good likely involves changes to GdkQuartzWindow, GdkQuartzView
885 draw_context->push_group ();
888 /* draw background color */
889 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
890 set_source_rgba (draw_context, _bg_color);
891 draw_context->fill ();
894 if ( _single_exposure ) {
896 Canvas::render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
902 gdk_region_get_rectangles (ev->region, &rects, &nrects);
903 for (gint n = 0; n < nrects; ++n) {
904 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
905 Canvas::render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
911 draw_context->pop_group_to_source ();
912 draw_context->paint ();
915 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
916 if (getenv("ARDOUR_IMAGE_SURFACE")) {
918 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
919 /* now blit our private surface back to the GDK one */
921 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
922 window_context->clip ();
923 window_context->set_source (canvas_image, 0, 0);
924 window_context->set_operator (Cairo::OPERATOR_SOURCE);
925 window_context->paint ();
927 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
931 #ifdef CANVAS_PROFILE
932 const int64_t end = g_get_monotonic_time ();
933 const int64_t elapsed = end - start;
934 printf ("GtkCanvas::on_expose_event %f ms\n", elapsed / 1000.f);
940 /** Handler for GDK scroll events.
942 * @return true if the event was handled.
945 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
947 /* translate event coordinates from window to canvas */
949 GdkEvent copy = *((GdkEvent*)ev);
950 Duple winpos = Duple (ev->x, ev->y);
951 Duple where = window_to_canvas (winpos);
953 pick_current_item (winpos, ev->state);
955 copy.button.x = where.x;
956 copy.button.y = where.y;
958 /* Coordinates in the event will be canvas coordinates, correctly adjusted
959 for scroll if this GtkCanvas is in a GtkCanvasViewport.
962 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
963 return deliver_event (reinterpret_cast<GdkEvent*>(©));
966 /** Handler for GDK key press events.
968 * @return true if the event was handled.
971 GtkCanvas::on_key_press_event (GdkEventKey* ev)
973 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
974 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
977 /** Handler for GDK key release events.
979 * @return true if the event was handled.
982 GtkCanvas::on_key_release_event (GdkEventKey* ev)
984 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
985 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
988 /** Handler for GDK button press events.
990 * @return true if the event was handled.
993 GtkCanvas::on_button_press_event (GdkEventButton* ev)
995 /* translate event coordinates from window to canvas */
997 GdkEvent copy = *((GdkEvent*)ev);
998 Duple winpos = Duple (ev->x, ev->y);
999 Duple where = window_to_canvas (winpos);
1001 pick_current_item (winpos, ev->state);
1003 copy.button.x = where.x;
1004 copy.button.y = where.y;
1006 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1007 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1010 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1011 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1014 /** Handler for GDK button release events.
1016 * @return true if the event was handled.
1019 GtkCanvas::on_button_release_event (GdkEventButton* ev)
1021 /* translate event coordinates from window to canvas */
1023 GdkEvent copy = *((GdkEvent*)ev);
1024 Duple winpos = Duple (ev->x, ev->y);
1025 Duple where = window_to_canvas (winpos);
1027 pick_current_item (winpos, ev->state);
1029 copy.button.x = where.x;
1030 copy.button.y = where.y;
1032 /* Coordinates in the event will be canvas coordinates, correctly adjusted
1033 for scroll if this GtkCanvas is in a GtkCanvasViewport.
1036 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release %1 @ %2, %3 => %4\n", ev->button, ev->x, ev->y, where));
1037 return deliver_event (reinterpret_cast<GdkEvent*>(©));
1041 GtkCanvas::get_mouse_position (Duple& winpos) const
1045 Gdk::ModifierType mask;
1046 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
1049 std::cerr << " no self window\n";
1050 winpos = Duple (0, 0);
1054 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
1062 /** Handler for GDK motion events.
1064 * @return true if the event was handled.
1067 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
1071 /* translate event coordinates from window to canvas */
1073 GdkEvent copy = *((GdkEvent*)ev);
1074 Duple point (ev->x, ev->y);
1075 Duple where = window_to_canvas (point);
1077 copy.motion.x = where.x;
1078 copy.motion.y = where.y;
1080 /* Coordinates in "copy" will be canvas coordinates,
1083 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));
1085 MouseMotion (point); /* EMIT SIGNAL */
1087 pick_current_item (point, ev->state);
1089 /* Now deliver the motion event. It may seem a little inefficient
1090 to recompute the items under the event, but the enter notify/leave
1091 events may have deleted canvas items so it is important to
1092 recompute the list in deliver_event.
1095 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1099 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1101 pick_current_item (Duple (ev->x, ev->y), ev->state);
1106 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1108 switch (ev->detail) {
1109 case GDK_NOTIFY_ANCESTOR:
1110 case GDK_NOTIFY_UNKNOWN:
1111 case GDK_NOTIFY_VIRTUAL:
1112 case GDK_NOTIFY_NONLINEAR:
1113 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1114 /* leaving window, cancel any tooltips */
1115 stop_tooltip_timeout ();
1119 /* we don't care about any other kind
1120 of leave event (notably GDK_NOTIFY_INFERIOR)
1124 _new_current_item = 0;
1125 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1130 GtkCanvas::on_map ()
1132 Gtk::EventBox::on_map();
1135 Gtkmm2ext::nsglview_set_visible (_nsglview, true);
1136 Gtk::Allocation a = get_allocation();
1138 gtk_widget_translate_coordinates(
1140 GTK_WIDGET(get_toplevel()->gobj()),
1142 Gtkmm2ext::nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
1148 GtkCanvas::on_unmap ()
1150 stop_tooltip_timeout ();
1151 Gtk::EventBox::on_unmap();
1154 Gtkmm2ext::nsglview_set_visible (_nsglview, false);
1159 /** Called to request a redraw of our canvas.
1160 * @param area Area to redraw, in window coordinates.
1163 GtkCanvas::request_redraw (Rect const & request)
1171 Coord const w = width ();
1172 Coord const h = height ();
1174 /* clamp area requested to actual visible window */
1176 real_area.x0 = max (0.0, min (w, request.x0));
1177 real_area.x1 = max (0.0, min (w, request.x1));
1178 real_area.y0 = max (0.0, min (h, request.y0));
1179 real_area.y1 = max (0.0, min (h, request.y1));
1181 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1184 /** Called to request that we try to get a particular size for ourselves.
1185 * @param size Size to request, in pixels.
1188 GtkCanvas::request_size (Duple size)
1192 if (req.x > INT_MAX) {
1196 if (req.y > INT_MAX) {
1200 set_size_request (req.x, req.y);
1203 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1204 * This is typically used for dragging items around, so that they are grabbed during
1206 * @param item Item to grab.
1209 GtkCanvas::grab (Item* item)
1211 /* XXX: should this be doing gdk_pointer_grab? */
1212 _grabbed_item = item;
1216 /** `Ungrab' any item that was previously grabbed */
1218 GtkCanvas::ungrab ()
1220 /* XXX: should this be doing gdk_pointer_ungrab? */
1224 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1226 * @param item Item to grab.
1229 GtkCanvas::focus (Item* item)
1231 _focused_item = item;
1235 GtkCanvas::unfocus (Item* item)
1237 if (item == _focused_item) {
1242 /** @return The visible area of the canvas, in window coordinates */
1244 GtkCanvas::visible_area () const
1246 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1250 GtkCanvas::width() const
1252 return get_allocation().get_width();
1256 GtkCanvas::height() const
1258 return get_allocation().get_height();
1262 GtkCanvas::start_tooltip_timeout (Item* item)
1264 stop_tooltip_timeout ();
1266 if (item && Gtkmm2ext::PersistentTooltip::tooltips_enabled ()) {
1267 current_tooltip_item = item;
1269 /* wait for the first idle that happens after this is
1270 called. this means that we've stopped processing events, which
1271 in turn implies that the user has stopped doing stuff for a
1275 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1280 GtkCanvas::really_start_tooltip_timeout ()
1282 /* an idle has occurred since we entered a tooltip-bearing widget. Now
1283 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1286 if (current_tooltip_item) {
1287 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1290 return false; /* this is called from an idle callback, don't call it again */
1294 GtkCanvas::stop_tooltip_timeout ()
1296 current_tooltip_item = 0;
1297 tooltip_timeout_connection.disconnect ();
1301 GtkCanvas::show_tooltip ()
1303 Rect tooltip_item_bbox;
1305 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1309 if (!tooltip_window) {
1310 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1311 tooltip_label = manage (new Gtk::Label);
1312 tooltip_label->show ();
1313 tooltip_window->add (*tooltip_label);
1314 tooltip_window->set_border_width (1);
1315 tooltip_window->set_name ("tooltip");
1318 tooltip_label->set_text (current_tooltip_item->tooltip());
1320 /* figure out where to position the tooltip */
1322 Gtk::Widget* toplevel = get_toplevel();
1324 int pointer_x, pointer_y;
1325 Gdk::ModifierType mask;
1327 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1329 Duple tooltip_window_origin (pointer_x, pointer_y);
1331 /* convert to root window coordinates */
1334 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1336 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1338 /* we don't want the pointer to be inside the window when it is
1339 * displayed, because then we generate a leave/enter event pair when
1340 * the window is displayed then hidden - the enter event will
1341 * trigger a new tooltip timeout.
1343 * So move the window right of the pointer position by just a enough
1344 * to get it away from the pointer.
1347 tooltip_window_origin.x += 30;
1348 tooltip_window_origin.y += 45;
1350 /* move the tooltip window into position */
1352 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1356 tooltip_window->present ();
1358 /* called from a timeout handler, don't call it again */
1364 GtkCanvas::hide_tooltip ()
1366 /* hide it if its there */
1368 if (tooltip_window) {
1369 tooltip_window->hide ();
1371 // Delete the tooltip window so it'll get re-created
1372 // (i.e. properly re-sized) on the next usage.
1373 delete tooltip_window;
1374 tooltip_window = NULL;
1378 Glib::RefPtr<Pango::Context>
1379 GtkCanvas::get_pango_context ()
1381 return Glib::wrap (gdk_pango_context_get());
1384 /** Create a GtkCanvaSViewport.
1385 * @param hadj Adjustment to use for horizontal scrolling.
1386 * @param vadj Adjustment to use for vertica scrolling.
1388 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1389 : Alignment (0, 0, 1.0, 1.0)
1390 , hadjustment (hadj)
1391 , vadjustment (vadj)
1395 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1396 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1400 GtkCanvasViewport::scrolled ()
1402 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1406 /** Handler for when GTK asks us what minimum size we want.
1407 * @param req Requsition to fill in.
1410 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1412 /* force the canvas to size itself */
1413 // _canvas.root()->bounding_box();