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 /** @file canvas/canvas.cc
22 * @brief Implementation of the main canvas classes.
27 #include <gtkmm/adjustment.h>
28 #include <gtkmm/label.h>
30 #include "pbd/compose.h"
31 #include "pbd/stacktrace.h"
33 #include "canvas/canvas.h"
34 #include "canvas/debug.h"
35 #include "canvas/line.h"
36 #include "canvas/scroll_group.h"
39 using namespace ArdourCanvas;
41 /** Construct a new Canvas */
49 Canvas::scroll_to (Coord x, Coord y)
51 _scroll_offset = Duple (x, y);
53 /* We do things this way because we do not want to recurse through
54 the canvas for every scroll. In the presence of large MIDI
55 tracks this means traversing item lists that include
56 thousands of items (notes).
58 This design limits us to moving only those items (groups, typically)
59 that should move in certain ways as we scroll. In other terms, it
60 becomes O(1) rather than O(N).
63 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
64 (*i)->scroll_to (_scroll_offset);
67 pick_current_item (0); // no current mouse position
71 Canvas::add_scroller (ScrollGroup& i)
73 scrollers.push_back (&i);
79 pick_current_item (0); // no current mouse position
82 /** Render an area of the canvas.
83 * @param area Area in window coordinates.
84 * @param context Cairo context to render to.
87 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
90 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
91 cerr << this << " RENDER: " << area << endl;
92 //cerr << "CANVAS @ " << this << endl;
94 //cerr << "-------------------------\n";
100 boost::optional<Rect> root_bbox = _root.bounding_box();
102 /* the root has no bounding box, so there's nothing to render */
106 boost::optional<Rect> draw = root_bbox->intersection (area);
109 /* there's a common area between the root and the requested
113 _root.render (*draw, context);
115 // This outlines the rect being rendered, after it has been drawn.
116 // context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
117 // context->set_source_rgba (1.0, 0, 0, 1.0);
118 // context->stroke ();
125 operator<< (ostream& o, Canvas& c)
132 Canvas::indent() const
136 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
144 Canvas::render_indent() const
148 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
156 Canvas::dump (ostream& o) const
162 /** Called when an item has been shown or hidden.
163 * @param item Item that has been shown or hidden.
166 Canvas::item_shown_or_hidden (Item* item)
168 boost::optional<Rect> bbox = item->bounding_box ();
170 queue_draw_item_area (item, bbox.get ());
174 /** Called when an item has a change to its visual properties
175 * that do NOT affect its bounding box.
176 * @param item Item that has been modified.
179 Canvas::item_visual_property_changed (Item* item)
181 boost::optional<Rect> bbox = item->bounding_box ();
183 queue_draw_item_area (item, bbox.get ());
187 /** Called when an item has changed, but not moved.
188 * @param item Item that has changed.
189 * @param pre_change_bounding_box The bounding box of item before the change,
190 * in the item's coordinates.
193 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
195 if (pre_change_bounding_box) {
196 /* request a redraw of the item's old bounding box */
197 queue_draw_item_area (item, pre_change_bounding_box.get ());
200 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
201 if (post_change_bounding_box) {
202 /* request a redraw of the item's new bounding box */
203 queue_draw_item_area (item, post_change_bounding_box.get ());
208 Canvas::window_to_canvas (Duple const & d) const
210 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
211 * as children of the root group, so we just scan its first level
212 * children and see what we can find.
215 std::list<Item*> const& root_children (_root.items());
218 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
219 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (d)) {
225 return d.translate (sg->scroll_offset());
228 /* fallback to global canvas offset ... it would be nice to remove this */
230 return d.translate (_scroll_offset);
234 Canvas::canvas_to_window (Duple const & d, bool rounded) const
236 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
237 * as children of the root group, so we just scan its first level
238 * children and see what we can find.
241 std::list<Item*> const& root_children (_root.items());
245 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
246 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
253 wd = d.translate (-sg->scroll_offset());
255 wd = d.translate (-_scroll_offset);
258 /* Note that this intentionally almost always returns integer coordinates */
268 /** Called when an item has moved.
269 * @param item Item that has moved.
270 * @param pre_change_parent_bounding_box The bounding box of the item before
271 * the move, in its parent's coordinates.
274 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
276 if (pre_change_parent_bounding_box) {
277 /* request a redraw of where the item used to be. The box has
278 * to be in parent coordinate space since the bounding box of
279 * an item does not change when moved. If we use
280 * item->item_to_canvas() on the old bounding box, we will be
282 * using the item's new position, and so will compute the wrong
283 * invalidation area. If we use the parent (which has not
284 * moved, then this will work.
286 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
289 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
290 if (post_change_bounding_box) {
291 /* request a redraw of where the item now is */
292 queue_draw_item_area (item, post_change_bounding_box.get ());
296 /** Request a redraw of a particular area in an item's coordinates.
298 * @param area Area to redraw in the item's coordinates.
301 Canvas::queue_draw_item_area (Item* item, Rect area)
303 request_redraw (item->item_to_window (area));
306 /** Construct a GtkCanvas */
307 GtkCanvas::GtkCanvas ()
309 , _new_current_item (0)
313 /* these are the events we want to know about */
314 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
315 Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
319 GtkCanvas::pick_current_item (int state)
324 /* this version of ::pick_current_item() is called after an item is
325 * added or removed, so we have no coordinates to work from as is the
326 * case with a motion event. Find out where the mouse is and use that.
329 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
331 if (pointer_window != get_window()) {
335 pick_current_item (Duple (x, y), state);
338 /** Given @param point (a position in window coordinates)
339 * and mouse state @param state, check to see if _current_item
340 * (which will be used to deliver events) should change.
343 GtkCanvas::pick_current_item (Duple const & point, int state)
345 /* we do not enter/leave items during a drag/grab */
351 /* find the items at the given window position */
353 vector<Item const *> items;
354 _root.add_items_at_point (point, items);
356 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
359 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
360 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
362 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << std::endl;
364 std::cerr << "\tItem " << (*it)->whatami() << std::endl;
370 /* put all items at point that are event-sensitive and visible and NOT
371 groups into within_items. Note that items is sorted from bottom to
372 top, but we're going to reverse that for within_items so that its
373 first item is the upper-most item that can be chosen as _current_item.
376 vector<Item const *>::const_iterator i;
377 list<Item const *> within_items;
379 for (i = items.begin(); i != items.end(); ++i) {
381 Item const * new_item = *i;
383 /* We ignore invisible items, groups and items that ignore events */
385 if (!new_item->visible() || new_item->ignore_events() || dynamic_cast<Group const *>(new_item) != 0) {
389 within_items.push_front (new_item);
392 if (within_items.empty()) {
394 /* no items at point, just send leave event below */
395 _new_current_item = 0;
399 if (within_items.front() == _current_item) {
400 /* uppermost item at point is already _current_item */
404 _new_current_item = const_cast<Item*> (within_items.front());
407 if (_new_current_item != _current_item) {
408 deliver_enter_leave (point, state);
412 /** Deliver a series of enter & leave events based on the pointer position being at window
413 * coordinate @param point, and pointer @param state (modifier keys, etc)
416 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
418 /* setup enter & leave event structures */
420 GdkEventCrossing enter_event;
421 enter_event.type = GDK_ENTER_NOTIFY;
422 enter_event.window = get_window()->gobj();
423 enter_event.send_event = 0;
424 enter_event.subwindow = 0;
425 enter_event.mode = GDK_CROSSING_NORMAL;
426 enter_event.focus = FALSE;
427 enter_event.state = state;
429 /* Events delivered to canvas items are expected to be in canvas
430 * coordinates but @param point is in window coordinates.
433 Duple c = window_to_canvas (point);
437 GdkEventCrossing leave_event = enter_event;
438 leave_event.type = GDK_LEAVE_NOTIFY;
441 GdkNotifyType enter_detail;
442 GdkNotifyType leave_detail;
443 vector<Item*> items_to_leave_virtual;
444 vector<Item*> items_to_enter_virtual;
446 if (_new_current_item == 0) {
448 leave_detail = GDK_NOTIFY_UNKNOWN;
452 /* no current item, so also send virtual leave events to the
453 * entire heirarchy for the current item
456 for (i = _current_item->parent(); i ; i = i->parent()) {
457 items_to_leave_virtual.push_back (i);
461 } else if (_current_item == 0) {
463 enter_detail = GDK_NOTIFY_UNKNOWN;
465 /* no current item, so also send virtual enter events to the
466 * entire heirarchy for the new item
469 for (i = _new_current_item->parent(); i ; i = i->parent()) {
470 items_to_enter_virtual.push_back (i);
473 } else if (_current_item->is_descendant_of (*_new_current_item)) {
475 /* move from descendant to ancestor (X: "_current_item is an
476 * inferior ("child") of _new_current_item")
478 * Deliver "virtual" leave notifications to all items in the
479 * heirarchy between current and new_current.
482 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
483 items_to_leave_virtual.push_back (i);
486 enter_detail = GDK_NOTIFY_INFERIOR;
487 leave_detail = GDK_NOTIFY_ANCESTOR;
490 } else if (_new_current_item->is_descendant_of (*_current_item)) {
491 /* move from ancestor to descendant (X: "_new_current_item is
492 * an inferior ("child") of _current_item")
494 * Deliver "virtual" enter notifications to all items in the
495 * heirarchy between current and new_current.
498 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
499 items_to_enter_virtual.push_back (i);
502 enter_detail = GDK_NOTIFY_ANCESTOR;
503 leave_detail = GDK_NOTIFY_INFERIOR;
507 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
509 /* deliver virtual leave events to everything between _current
510 * and common_ancestor.
513 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
514 items_to_leave_virtual.push_back (i);
517 /* deliver virtual enter events to everything between
518 * _new_current and common_ancestor.
521 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
522 items_to_enter_virtual.push_back (i);
525 enter_detail = GDK_NOTIFY_NONLINEAR;
526 leave_detail = GDK_NOTIFY_NONLINEAR;
530 if (_current_item && !_current_item->ignore_events ()) {
531 leave_event.detail = leave_detail;
532 _current_item->Event ((GdkEvent*)&leave_event);
533 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
536 leave_event.detail = GDK_NOTIFY_VIRTUAL;
537 enter_event.detail = GDK_NOTIFY_VIRTUAL;
539 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
540 if (!(*it)->ignore_events()) {
541 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
542 (*it)->Event ((GdkEvent*)&leave_event);
546 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
547 if (!(*it)->ignore_events()) {
548 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
549 (*it)->Event ((GdkEvent*)&enter_event);
550 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
554 if (_new_current_item && !_new_current_item->ignore_events()) {
555 enter_event.detail = enter_detail;
556 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
557 _new_current_item->Event ((GdkEvent*)&enter_event);
560 _current_item = _new_current_item;
564 /** Deliver an event to the appropriate item; either the grabbed item, or
565 * one of the items underneath the event.
566 * @param point Position that the event has occurred at, in canvas coordinates.
567 * @param event The event.
570 GtkCanvas::deliver_event (GdkEvent* event)
572 /* Point in in canvas coordinate space */
574 const Item* event_item;
577 /* we have a grabbed item, so everything gets sent there */
578 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
579 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
580 event_item = _grabbed_item;
582 event_item = _current_item;
589 /* run through the items from child to parent, until one claims the event */
591 Item* item = const_cast<Item*> (event_item);
595 Item* parent = item->parent ();
597 if (!item->ignore_events () &&
598 item->Event (event)) {
599 /* this item has just handled the event */
601 PBD::DEBUG::CanvasEvents,
602 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
608 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)));
610 if ((item = parent) == 0) {
619 /** Called when an item is being destroyed.
620 * @param item Item being destroyed.
621 * @param bounding_box Last known bounding box of the item.
624 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
627 queue_draw_item_area (item, bounding_box.get ());
630 if (_new_current_item == item) {
631 _new_current_item = 0;
634 if (_grabbed_item == item) {
638 if (_focused_item == item) {
642 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
644 scrollers.remove (sg);
647 if (_current_item == item) {
648 /* no need to send a leave event to this item, since it is going away
651 pick_current_item (0); // no mouse state
656 /** Handler for GDK expose events.
658 * @return true if the event was handled.
661 GtkCanvas::on_expose_event (GdkEventExpose* ev)
663 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
664 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
668 /** @return Our Cairo context, or 0 if we don't have one */
669 Cairo::RefPtr<Cairo::Context>
670 GtkCanvas::context ()
672 Glib::RefPtr<Gdk::Window> w = get_window ();
674 return Cairo::RefPtr<Cairo::Context> ();
677 return w->create_cairo_context ();
680 /** Handler for GDK button press events.
682 * @return true if the event was handled.
685 GtkCanvas::on_button_press_event (GdkEventButton* ev)
687 /* translate event coordinates from window to canvas */
689 GdkEvent copy = *((GdkEvent*)ev);
690 Duple winpos = Duple (ev->x, ev->y);
691 Duple where = window_to_canvas (winpos);
693 pick_current_item (winpos, ev->state);
695 copy.button.x = where.x;
696 copy.button.y = where.y;
698 /* Coordinates in the event will be canvas coordinates, correctly adjusted
699 for scroll if this GtkCanvas is in a GtkCanvasViewport.
702 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
703 return deliver_event (reinterpret_cast<GdkEvent*>(©));
706 /** Handler for GDK button release events.
708 * @return true if the event was handled.
711 GtkCanvas::on_button_release_event (GdkEventButton* ev)
713 /* translate event coordinates from window to canvas */
715 GdkEvent copy = *((GdkEvent*)ev);
716 Duple winpos = Duple (ev->x, ev->y);
717 Duple where = window_to_canvas (winpos);
719 pick_current_item (winpos, ev->state);
721 copy.button.x = where.x;
722 copy.button.y = where.y;
724 /* Coordinates in the event will be canvas coordinates, correctly adjusted
725 for scroll if this GtkCanvas is in a GtkCanvasViewport.
728 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
729 return deliver_event (reinterpret_cast<GdkEvent*>(©));
732 /** Handler for GDK motion events.
734 * @return true if the event was handled.
737 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
739 /* translate event coordinates from window to canvas */
741 GdkEvent copy = *((GdkEvent*)ev);
742 Duple point (ev->x, ev->y);
743 Duple where = window_to_canvas (point);
745 copy.motion.x = where.x;
746 copy.motion.y = where.y;
748 /* Coordinates in "copy" will be canvas coordinates,
751 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));
753 pick_current_item (point, ev->state);
755 /* Now deliver the motion event. It may seem a little inefficient
756 to recompute the items under the event, but the enter notify/leave
757 events may have deleted canvas items so it is important to
758 recompute the list in deliver_event.
761 return deliver_event (reinterpret_cast<GdkEvent*> (©));
765 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
767 pick_current_item (Duple (ev->x, ev->y), ev->state);
772 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
774 _new_current_item = 0;
775 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
779 /** Called to request a redraw of our canvas.
780 * @param area Area to redraw, in window coordinates.
783 GtkCanvas::request_redraw (Rect const & request)
787 Coord const w = width ();
788 Coord const h = height ();
790 /* clamp area requested to actual visible window */
792 real_area.x0 = max (0.0, min (w, request.x0));
793 real_area.x1 = max (0.0, min (w, request.x1));
794 real_area.y0 = max (0.0, min (h, request.y0));
795 real_area.y1 = max (0.0, min (h, request.y1));
797 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
800 /** Called to request that we try to get a particular size for ourselves.
801 * @param size Size to request, in pixels.
804 GtkCanvas::request_size (Duple size)
808 if (req.x > INT_MAX) {
812 if (req.y > INT_MAX) {
816 set_size_request (req.x, req.y);
819 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
820 * This is typically used for dragging items around, so that they are grabbed during
822 * @param item Item to grab.
825 GtkCanvas::grab (Item* item)
827 /* XXX: should this be doing gdk_pointer_grab? */
828 _grabbed_item = item;
832 /** `Ungrab' any item that was previously grabbed */
836 /* XXX: should this be doing gdk_pointer_ungrab? */
840 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
842 * @param item Item to grab.
845 GtkCanvas::focus (Item* item)
847 _focused_item = item;
851 GtkCanvas::unfocus (Item* item)
853 if (item == _focused_item) {
858 /** @return The visible area of the canvas, in canvas coordinates */
860 GtkCanvas::visible_area () const
862 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
866 GtkCanvas::width() const
868 return get_allocation().get_width();
872 GtkCanvas::height() const
874 return get_allocation().get_height();
877 /** Create a GtkCanvaSViewport.
878 * @param hadj Adjustment to use for horizontal scrolling.
879 * @param vadj Adjustment to use for vertica scrolling.
881 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
882 : Alignment (0, 0, 1.0, 1.0)
888 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
889 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
893 GtkCanvasViewport::scrolled ()
895 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
899 /** Handler for when GTK asks us what minimum size we want.
900 * @param req Requsition to fill in.
903 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
905 /* force the canvas to size itself */
906 // _canvas.root()->bounding_box();