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 "pbd/compose.h"
36 #include "pbd/stacktrace.h"
38 #include "canvas/canvas.h"
39 #include "canvas/colors.h"
40 #include "canvas/debug.h"
41 #include "canvas/line.h"
42 #include "canvas/scroll_group.h"
43 #include "canvas/utils.h"
46 using namespace ArdourCanvas;
48 uint32_t Canvas::tooltip_timeout_msecs = 750;
50 /** Construct a new Canvas */
53 , _bg_color (rgba_to_color (0, 1.0, 0.0, 1.0))
59 Canvas::scroll_to (Coord x, Coord y)
61 /* We do things this way because we do not want to recurse through
62 the canvas for every scroll. In the presence of large MIDI
63 tracks this means traversing item lists that include
64 thousands of items (notes).
66 This design limits us to moving only those items (groups, typically)
67 that should move in certain ways as we scroll. In other terms, it
68 becomes O(1) rather than O(N).
71 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
72 (*i)->scroll_to (Duple (x, y));
75 pick_current_item (0); // no current mouse position
79 Canvas::add_scroller (ScrollGroup& i)
81 scrollers.push_back (&i);
87 pick_current_item (0); // no current mouse position
90 /** Render an area of the canvas.
91 * @param area Area in window coordinates.
92 * @param context Cairo context to render to.
95 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
98 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
99 cerr << this << " RENDER: " << area << endl;
100 //cerr << "CANVAS @ " << this << endl;
102 //cerr << "-------------------------\n";
108 boost::optional<Rect> root_bbox = _root.bounding_box();
110 /* the root has no bounding box, so there's nothing to render */
114 boost::optional<Rect> draw = root_bbox->intersection (area);
117 /* there's a common area between the root and the requested
121 _root.render (*draw, context);
123 #if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
124 if (getenv ("CANVAS_HARLEQUIN_DEBUGGING")) {
125 // This transparently colors the rect being rendered, after it has been drawn.
126 double r = (random() % 65536) /65536.0;
127 double g = (random() % 65536) /65536.0;
128 double b = (random() % 65536) /65536.0;
129 context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
130 context->set_source_rgba (r, g, b, 0.25);
139 operator<< (ostream& o, Canvas& c)
146 Canvas::indent() const
150 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
158 Canvas::render_indent() const
162 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
170 Canvas::dump (ostream& o) const
176 /** Called when an item has been shown or hidden.
177 * @param item Item that has been shown or hidden.
180 Canvas::item_shown_or_hidden (Item* item)
182 boost::optional<Rect> bbox = item->bounding_box ();
184 if (item->item_to_window (*bbox).intersection (visible_area ())) {
185 queue_draw_item_area (item, bbox.get ());
190 /** Called when an item has a change to its visual properties
191 * that do NOT affect its bounding box.
192 * @param item Item that has been modified.
195 Canvas::item_visual_property_changed (Item* item)
197 boost::optional<Rect> bbox = item->bounding_box ();
199 if (item->item_to_window (*bbox).intersection (visible_area ())) {
200 queue_draw_item_area (item, bbox.get ());
205 /** Called when an item has changed, but not moved.
206 * @param item Item that has changed.
207 * @param pre_change_bounding_box The bounding box of item before the change,
208 * in the item's coordinates.
211 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
214 Rect window_bbox = visible_area ();
216 if (pre_change_bounding_box) {
218 if (item->item_to_window (*pre_change_bounding_box).intersection (window_bbox)) {
219 /* request a redraw of the item's old bounding box */
220 queue_draw_item_area (item, pre_change_bounding_box.get ());
224 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
225 if (post_change_bounding_box) {
227 if (item->item_to_window (*post_change_bounding_box).intersection (window_bbox)) {
228 /* request a redraw of the item's new bounding box */
229 queue_draw_item_area (item, post_change_bounding_box.get ());
235 Canvas::window_to_canvas (Duple const & d) const
237 ScrollGroup* best_group = 0;
240 /* if the coordinates are negative, clamp to zero and find the item
241 * that covers that "edge" position.
246 if (in_window.x < 0) {
249 if (in_window.y < 0) {
253 for (list<ScrollGroup*>::const_iterator s = scrollers.begin(); s != scrollers.end(); ++s) {
255 if ((*s)->covers_window (in_window)) {
258 /* XXX January 22nd 2015: leaving this in place for now
259 * but I think it fixes a bug that really should be
260 * fixed in a different way (and will be) by my next
261 * commit. But it may still be relevant.
264 /* If scroll groups overlap, choose the one with the highest sensitivity,
265 that is, choose an HV scroll group over an H or V
268 if (!best_group || sg->sensitivity() > best_group->sensitivity()) {
270 if (sg->sensitivity() == (ScrollGroup::ScrollsVertically | ScrollGroup::ScrollsHorizontally)) {
271 /* Can't do any better than this. */
279 return d.translate (best_group->scroll_offset());
286 Canvas::canvas_to_window (Duple const & d, bool rounded) const
288 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
289 * as children of the root group, so we just scan its first level
290 * children and see what we can find.
293 std::list<Item*> const& root_children (_root.items());
297 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
298 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
304 wd = d.translate (-sg->scroll_offset());
309 /* Note that this intentionally almost always returns integer coordinates */
319 /** Called when an item has moved.
320 * @param item Item that has moved.
321 * @param pre_change_parent_bounding_box The bounding box of the item before
322 * the move, in its parent's coordinates.
325 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
327 if (pre_change_parent_bounding_box) {
328 /* request a redraw of where the item used to be. The box has
329 * to be in parent coordinate space since the bounding box of
330 * an item does not change when moved. If we use
331 * item->item_to_canvas() on the old bounding box, we will be
333 * using the item's new position, and so will compute the wrong
334 * invalidation area. If we use the parent (which has not
335 * moved, then this will work.
337 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
340 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
341 if (post_change_bounding_box) {
342 /* request a redraw of where the item now is */
343 queue_draw_item_area (item, post_change_bounding_box.get ());
347 /** Request a redraw of a particular area in an item's coordinates.
349 * @param area Area to redraw in the item's coordinates.
352 Canvas::queue_draw_item_area (Item* item, Rect area)
354 request_redraw (item->item_to_window (area));
358 Canvas::set_tooltip_timeout (uint32_t msecs)
360 tooltip_timeout_msecs = msecs;
364 Canvas::set_background_color (Color c)
368 boost::optional<Rect> r = _root.bounding_box();
371 request_redraw (_root.item_to_window (r.get()));
376 GtkCanvas::re_enter ()
378 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "re-enter canvas by request\n");
380 pick_current_item (0);
383 /** Construct a GtkCanvas */
384 GtkCanvas::GtkCanvas ()
386 , _new_current_item (0)
389 , _single_exposure (1)
390 , current_tooltip_item (0)
393 /* these are the events we want to know about */
394 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
395 Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
396 Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
400 GtkCanvas::pick_current_item (int state)
405 /* this version of ::pick_current_item() is called after an item is
406 * added or removed, so we have no coordinates to work from as is the
407 * case with a motion event. Find out where the mouse is and use that.
410 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
412 if (pointer_window != get_window()) {
416 pick_current_item (Duple (x, y), state);
419 /** Given @param point (a position in window coordinates)
420 * and mouse state @param state, check to see if _current_item
421 * (which will be used to deliver events) should change.
424 GtkCanvas::pick_current_item (Duple const & point, int state)
426 /* we do not enter/leave items during a drag/grab */
432 /* find the items at the given window position */
434 vector<Item const *> items;
435 _root.add_items_at_point (point, items);
437 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
440 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
441 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
443 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
445 std::cerr << "\tItem " << (*it)->whatami() << '/' << " ignore events ? " << (*it)->ignore_events() << " vis ? " << (*it)->visible() << std::endl;
451 /* put all items at point that are event-sensitive and visible and NOT
452 groups into within_items. Note that items is sorted from bottom to
453 top, but we're going to reverse that for within_items so that its
454 first item is the upper-most item that can be chosen as _current_item.
457 vector<Item const *>::const_iterator i;
458 list<Item const *> within_items;
460 for (i = items.begin(); i != items.end(); ++i) {
462 Item const * possible_item = *i;
464 /* We ignore invisible items, containers and items that ignore events */
466 if (!possible_item->visible() || possible_item->ignore_events() || dynamic_cast<ArdourCanvas::Container const *>(possible_item) != 0) {
469 within_items.push_front (possible_item);
472 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size()));
474 if (within_items.empty()) {
476 /* no items at point, just send leave event below */
477 _new_current_item = 0;
481 if (within_items.front() == _current_item) {
482 /* uppermost item at point is already _current_item */
483 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
487 _new_current_item = const_cast<Item*> (within_items.front());
490 if (_new_current_item != _current_item) {
491 deliver_enter_leave (point, state);
495 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
497 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
502 /** Deliver a series of enter & leave events based on the pointer position being at window
503 * coordinate @param point, and pointer @param state (modifier keys, etc)
506 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
508 /* setup enter & leave event structures */
510 Glib::RefPtr<Gdk::Window> win = get_window();
516 GdkEventCrossing enter_event;
517 enter_event.type = GDK_ENTER_NOTIFY;
518 enter_event.window = win->gobj();
519 enter_event.send_event = 0;
520 enter_event.subwindow = 0;
521 enter_event.mode = GDK_CROSSING_NORMAL;
522 enter_event.focus = FALSE;
523 enter_event.state = state;
525 /* Events delivered to canvas items are expected to be in canvas
526 * coordinates but @param point is in window coordinates.
529 Duple c = window_to_canvas (point);
533 GdkEventCrossing leave_event = enter_event;
534 leave_event.type = GDK_LEAVE_NOTIFY;
537 GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
538 GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
539 vector<Item*> items_to_leave_virtual;
540 vector<Item*> items_to_enter_virtual;
542 if (_new_current_item == 0) {
544 leave_detail = GDK_NOTIFY_UNKNOWN;
548 /* no current item, so also send virtual leave events to the
549 * entire heirarchy for the current item
552 for (i = _current_item->parent(); i ; i = i->parent()) {
553 items_to_leave_virtual.push_back (i);
557 } else if (_current_item == 0) {
559 enter_detail = GDK_NOTIFY_UNKNOWN;
561 /* no current item, so also send virtual enter events to the
562 * entire heirarchy for the new item
565 for (i = _new_current_item->parent(); i ; i = i->parent()) {
566 items_to_enter_virtual.push_back (i);
569 } else if (_current_item->is_descendant_of (*_new_current_item)) {
571 /* move from descendant to ancestor (X: "_current_item is an
572 * inferior ("child") of _new_current_item")
574 * Deliver "virtual" leave notifications to all items in the
575 * heirarchy between current and new_current.
578 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
579 items_to_leave_virtual.push_back (i);
582 enter_detail = GDK_NOTIFY_INFERIOR;
583 leave_detail = GDK_NOTIFY_ANCESTOR;
585 } else if (_new_current_item->is_descendant_of (*_current_item)) {
586 /* move from ancestor to descendant (X: "_new_current_item is
587 * an inferior ("child") of _current_item")
589 * Deliver "virtual" enter notifications to all items in the
590 * heirarchy between current and new_current.
593 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
594 items_to_enter_virtual.push_back (i);
597 enter_detail = GDK_NOTIFY_ANCESTOR;
598 leave_detail = GDK_NOTIFY_INFERIOR;
602 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
604 /* deliver virtual leave events to everything between _current
605 * and common_ancestor.
608 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
609 items_to_leave_virtual.push_back (i);
612 /* deliver virtual enter events to everything between
613 * _new_current and common_ancestor.
616 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
617 items_to_enter_virtual.push_back (i);
620 enter_detail = GDK_NOTIFY_NONLINEAR;
621 leave_detail = GDK_NOTIFY_NONLINEAR;
625 if (_current_item && !_current_item->ignore_events ()) {
626 leave_event.detail = leave_detail;
627 _current_item->Event ((GdkEvent*)&leave_event);
628 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
631 leave_event.detail = GDK_NOTIFY_VIRTUAL;
632 enter_event.detail = GDK_NOTIFY_VIRTUAL;
634 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
635 if (!(*it)->ignore_events()) {
636 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
637 (*it)->Event ((GdkEvent*)&leave_event);
641 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
642 if (!(*it)->ignore_events()) {
643 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
644 (*it)->Event ((GdkEvent*)&enter_event);
645 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
649 if (_new_current_item && !_new_current_item->ignore_events()) {
650 enter_event.detail = enter_detail;
651 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
652 start_tooltip_timeout (_new_current_item);
653 _new_current_item->Event ((GdkEvent*)&enter_event);
656 _current_item = _new_current_item;
660 /** Deliver an event to the appropriate item; either the grabbed item, or
661 * one of the items underneath the event.
662 * @param point Position that the event has occurred at, in canvas coordinates.
663 * @param event The event.
666 GtkCanvas::deliver_event (GdkEvent* event)
668 /* Point in in canvas coordinate space */
670 const Item* event_item;
673 /* we have a grabbed item, so everything gets sent there */
674 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
675 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
676 event_item = _grabbed_item;
678 event_item = _current_item;
685 /* run through the items from child to parent, until one claims the event */
687 Item* item = const_cast<Item*> (event_item);
691 Item* parent = item->parent ();
693 if (!item->ignore_events () &&
694 item->Event (event)) {
695 /* this item has just handled the event */
697 PBD::DEBUG::CanvasEvents,
698 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
704 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)));
706 if ((item = parent) == 0) {
715 /** Called when an item is being destroyed.
716 * @param item Item being destroyed.
717 * @param bounding_box Last known bounding box of the item.
720 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
723 queue_draw_item_area (item, bounding_box.get ());
726 if (_new_current_item == item) {
727 _new_current_item = 0;
730 if (_grabbed_item == item) {
734 if (_focused_item == item) {
738 if (current_tooltip_item) {
739 current_tooltip_item = 0;
740 stop_tooltip_timeout ();
743 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
745 scrollers.remove (sg);
748 if (_current_item == item) {
749 /* no need to send a leave event to this item, since it is going away
752 pick_current_item (0); // no mouse state
758 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
760 EventBox::on_size_allocate (a);
761 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
762 if (getenv("ARDOUR_IMAGE_SURFACE")) {
764 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
765 /* allocate an image surface as large as the canvas itself */
767 canvas_image.clear ();
768 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width(), a.get_height());
770 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
775 /** Handler for GDK expose events.
777 * @return true if the event was handled.
780 GtkCanvas::on_expose_event (GdkEventExpose* ev)
782 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
783 Cairo::RefPtr<Cairo::Context> draw_context;
784 Cairo::RefPtr<Cairo::Context> window_context;
785 if (getenv("ARDOUR_IMAGE_SURFACE")) {
787 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
789 draw_context = Cairo::Context::create (canvas_image);
790 window_context = get_window()->create_cairo_context ();
792 draw_context = get_window()->create_cairo_context ();
794 #elif defined USE_CAIRO_IMAGE_SURFACE
796 canvas_image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, get_width(), get_height());
798 Cairo::RefPtr<Cairo::Context> draw_context = Cairo::Context::create (canvas_image);
799 Cairo::RefPtr<Cairo::Context> window_context = get_window()->create_cairo_context ();
801 Cairo::RefPtr<Cairo::Context> draw_context = get_window()->create_cairo_context ();
804 /* draw background color */
806 draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
807 draw_context->clip_preserve ();
808 set_source_rgba (draw_context, _bg_color);
809 draw_context->fill ();
812 if ( _single_exposure ) {
814 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), draw_context);
820 gdk_region_get_rectangles (ev->region, &rects, &nrects);
821 for (gint n = 0; n < nrects; ++n) {
822 draw_context->set_identity_matrix(); //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
823 render (Rect (rects[n].x, rects[n].y, rects[n].x + rects[n].width, rects[n].y + rects[n].height), draw_context);
828 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
829 if (getenv("ARDOUR_IMAGE_SURFACE")) {
831 #if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
832 /* now blit our private surface back to the GDK one */
834 window_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
835 window_context->clip ();
836 window_context->set_source (canvas_image, 0, 0);
837 window_context->set_operator (Cairo::OPERATOR_SOURCE);
838 window_context->paint ();
840 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
847 /** Handler for GDK scroll events.
849 * @return true if the event was handled.
852 GtkCanvas::on_scroll_event (GdkEventScroll* ev)
854 /* translate event coordinates from window to canvas */
856 GdkEvent copy = *((GdkEvent*)ev);
857 Duple winpos = Duple (ev->x, ev->y);
858 Duple where = window_to_canvas (winpos);
860 pick_current_item (winpos, ev->state);
862 copy.button.x = where.x;
863 copy.button.y = where.y;
865 /* Coordinates in the event will be canvas coordinates, correctly adjusted
866 for scroll if this GtkCanvas is in a GtkCanvasViewport.
869 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas scroll @ %1, %2 => %3\n", ev->x, ev->y, where));
870 return deliver_event (reinterpret_cast<GdkEvent*>(©));
873 /** Handler for GDK key press events.
875 * @return true if the event was handled.
878 GtkCanvas::on_key_press_event (GdkEventKey* ev)
880 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key press\n");
881 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
884 /** Handler for GDK key release events.
886 * @return true if the event was handled.
889 GtkCanvas::on_key_release_event (GdkEventKey* ev)
891 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, "canvas key release\n");
892 return deliver_event (reinterpret_cast<GdkEvent*>(ev));
895 /** Handler for GDK button press events.
897 * @return true if the event was handled.
900 GtkCanvas::on_button_press_event (GdkEventButton* ev)
902 /* translate event coordinates from window to canvas */
904 GdkEvent copy = *((GdkEvent*)ev);
905 Duple winpos = Duple (ev->x, ev->y);
906 Duple where = window_to_canvas (winpos);
908 pick_current_item (winpos, ev->state);
910 copy.button.x = where.x;
911 copy.button.y = where.y;
913 /* Coordinates in the event will be canvas coordinates, correctly adjusted
914 for scroll if this GtkCanvas is in a GtkCanvasViewport.
917 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
918 return deliver_event (reinterpret_cast<GdkEvent*>(©));
921 /** Handler for GDK button release events.
923 * @return true if the event was handled.
926 GtkCanvas::on_button_release_event (GdkEventButton* ev)
928 /* translate event coordinates from window to canvas */
930 GdkEvent copy = *((GdkEvent*)ev);
931 Duple winpos = Duple (ev->x, ev->y);
932 Duple where = window_to_canvas (winpos);
934 pick_current_item (winpos, ev->state);
936 copy.button.x = where.x;
937 copy.button.y = where.y;
939 /* Coordinates in the event will be canvas coordinates, correctly adjusted
940 for scroll if this GtkCanvas is in a GtkCanvasViewport.
943 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
944 return deliver_event (reinterpret_cast<GdkEvent*>(©));
948 GtkCanvas::get_mouse_position (Duple& winpos) const
952 Gdk::ModifierType mask;
953 Glib::RefPtr<Gdk::Window> self = Glib::RefPtr<Gdk::Window>::cast_const (get_window ());
956 std::cerr << " no self window\n";
957 winpos = Duple (0, 0);
961 Glib::RefPtr<Gdk::Window> win = self->get_pointer (x, y, mask);
969 /** Handler for GDK motion events.
971 * @return true if the event was handled.
974 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
978 /* translate event coordinates from window to canvas */
980 GdkEvent copy = *((GdkEvent*)ev);
981 Duple point (ev->x, ev->y);
982 Duple where = window_to_canvas (point);
984 copy.motion.x = where.x;
985 copy.motion.y = where.y;
987 /* Coordinates in "copy" will be canvas coordinates,
990 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));
992 MouseMotion (point); /* EMIT SIGNAL */
994 pick_current_item (point, ev->state);
996 /* Now deliver the motion event. It may seem a little inefficient
997 to recompute the items under the event, but the enter notify/leave
998 events may have deleted canvas items so it is important to
999 recompute the list in deliver_event.
1002 return deliver_event (reinterpret_cast<GdkEvent*> (©));
1006 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
1008 pick_current_item (Duple (ev->x, ev->y), ev->state);
1013 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
1015 switch (ev->detail) {
1016 case GDK_NOTIFY_ANCESTOR:
1017 case GDK_NOTIFY_UNKNOWN:
1018 case GDK_NOTIFY_VIRTUAL:
1019 case GDK_NOTIFY_NONLINEAR:
1020 case GDK_NOTIFY_NONLINEAR_VIRTUAL:
1021 /* leaving window, cancel any tooltips */
1022 stop_tooltip_timeout ();
1026 /* we don't care about any other kind
1027 of leave event (notably GDK_NOTIFY_INFERIOR)
1031 _new_current_item = 0;
1032 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
1036 /** Called to request a redraw of our canvas.
1037 * @param area Area to redraw, in window coordinates.
1040 GtkCanvas::request_redraw (Rect const & request)
1044 Coord const w = width ();
1045 Coord const h = height ();
1047 /* clamp area requested to actual visible window */
1049 real_area.x0 = max (0.0, min (w, request.x0));
1050 real_area.x1 = max (0.0, min (w, request.x1));
1051 real_area.y0 = max (0.0, min (h, request.y0));
1052 real_area.y1 = max (0.0, min (h, request.y1));
1054 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
1057 /** Called to request that we try to get a particular size for ourselves.
1058 * @param size Size to request, in pixels.
1061 GtkCanvas::request_size (Duple size)
1065 if (req.x > INT_MAX) {
1069 if (req.y > INT_MAX) {
1073 set_size_request (req.x, req.y);
1076 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
1077 * This is typically used for dragging items around, so that they are grabbed during
1079 * @param item Item to grab.
1082 GtkCanvas::grab (Item* item)
1084 /* XXX: should this be doing gdk_pointer_grab? */
1085 _grabbed_item = item;
1089 /** `Ungrab' any item that was previously grabbed */
1091 GtkCanvas::ungrab ()
1093 /* XXX: should this be doing gdk_pointer_ungrab? */
1097 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
1099 * @param item Item to grab.
1102 GtkCanvas::focus (Item* item)
1104 _focused_item = item;
1108 GtkCanvas::unfocus (Item* item)
1110 if (item == _focused_item) {
1115 /** @return The visible area of the canvas, in window coordinates */
1117 GtkCanvas::visible_area () const
1119 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
1123 GtkCanvas::width() const
1125 return get_allocation().get_width();
1129 GtkCanvas::height() const
1131 return get_allocation().get_height();
1135 GtkCanvas::start_tooltip_timeout (Item* item)
1137 stop_tooltip_timeout ();
1140 current_tooltip_item = item;
1142 /* wait for the first idle that happens after this is
1143 called. this means that we've stopped processing events, which
1144 in turn implies that the user has stopped doing stuff for a
1148 Glib::signal_idle().connect (sigc::mem_fun (*this, &GtkCanvas::really_start_tooltip_timeout));
1153 GtkCanvas::really_start_tooltip_timeout ()
1155 /* an idle has occured since we entered a tooltip-bearing widget. Now
1156 * wait 1 second and if the timeout isn't cancelled, show the tooltip.
1159 if (current_tooltip_item) {
1160 tooltip_timeout_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &GtkCanvas::show_tooltip), tooltip_timeout_msecs);
1163 return false; /* this is called from an idle callback, don't call it again */
1167 GtkCanvas::stop_tooltip_timeout ()
1169 current_tooltip_item = 0;
1170 tooltip_timeout_connection.disconnect ();
1174 GtkCanvas::show_tooltip ()
1176 Rect tooltip_item_bbox;
1178 if (!current_tooltip_item || current_tooltip_item->tooltip().empty() || !current_tooltip_item->bounding_box()) {
1182 if (!tooltip_window) {
1183 tooltip_window = new Gtk::Window (Gtk::WINDOW_POPUP);
1184 tooltip_label = manage (new Gtk::Label);
1185 tooltip_label->show ();
1186 tooltip_window->add (*tooltip_label);
1187 tooltip_window->set_border_width (1);
1188 tooltip_window->set_name ("tooltip");
1191 tooltip_label->set_text (current_tooltip_item->tooltip());
1193 /* figure out where to position the tooltip */
1195 Gtk::Widget* toplevel = get_toplevel();
1197 int pointer_x, pointer_y;
1198 Gdk::ModifierType mask;
1200 (void) toplevel->get_window()->get_pointer (pointer_x, pointer_y, mask);
1202 Duple tooltip_window_origin (pointer_x, pointer_y);
1204 /* convert to root window coordinates */
1207 dynamic_cast<Gtk::Window*>(toplevel)->get_position (win_x, win_y);
1209 tooltip_window_origin = tooltip_window_origin.translate (Duple (win_x, win_y));
1211 /* we don't want the pointer to be inside the window when it is
1212 * displayed, because then we generate a leave/enter event pair when
1213 * the window is displayed then hidden - the enter event will
1214 * trigger a new tooltip timeout.
1216 * So move the window right of the pointer position by just a enough
1217 * to get it away from the pointer.
1220 tooltip_window_origin.x += 30;
1221 tooltip_window_origin.y += 45;
1223 /* move the tooltip window into position */
1225 tooltip_window->move (tooltip_window_origin.x, tooltip_window_origin.y);
1229 tooltip_window->present ();
1231 /* called from a timeout handler, don't call it again */
1237 GtkCanvas::hide_tooltip ()
1239 /* hide it if its there */
1241 if (tooltip_window) {
1242 tooltip_window->hide ();
1244 // Delete the tooltip window so it'll get re-created
1245 // (i.e. properly re-sized) on the next usage.
1246 delete tooltip_window;
1247 tooltip_window = NULL;
1251 /** Create a GtkCanvaSViewport.
1252 * @param hadj Adjustment to use for horizontal scrolling.
1253 * @param vadj Adjustment to use for vertica scrolling.
1255 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
1256 : Alignment (0, 0, 1.0, 1.0)
1257 , hadjustment (hadj)
1258 , vadjustment (vadj)
1262 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1263 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
1267 GtkCanvasViewport::scrolled ()
1269 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
1273 /** Handler for when GTK asks us what minimum size we want.
1274 * @param req Requsition to fill in.
1277 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
1279 /* force the canvas to size itself */
1280 // _canvas.root()->bounding_box();