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 canvas 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 return d.translate (Duple (_scroll_offset.x, _scroll_offset.y));
214 Canvas::canvas_to_window (Duple const & d, bool rounded) const
216 Duple wd = d.translate (Duple (-_scroll_offset.x, -_scroll_offset.y));
218 /* Note that this intentionally almost always returns integer coordinates */
229 Canvas::window_to_canvas (Rect const & r) const
231 return r.translate (Duple (_scroll_offset.x, _scroll_offset.y));
235 Canvas::canvas_to_window (Rect const & r, bool rounded) const
237 Rect wr = r.translate (Duple (-_scroll_offset.x, -_scroll_offset.y));
239 /* Note that this intentionally almost always returns integer coordinates */
242 wr.x0 = round (wr.x0);
243 wr.x1 = round (wr.x1);
244 wr.y0 = round (wr.y0);
245 wr.y1 = round (wr.y1);
251 /** Called when an item has moved.
252 * @param item Item that has moved.
253 * @param pre_change_parent_bounding_box The bounding box of the item before
254 * the move, in its parent's coordinates.
257 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
259 if (pre_change_parent_bounding_box) {
260 /* request a redraw of where the item used to be. The box has
261 * to be in parent coordinate space since the bounding box of
262 * an item does not change when moved. If we use
263 * item->item_to_canvas() on the old bounding box, we will be
265 * using the item's new position, and so will compute the wrong
266 * invalidation area. If we use the parent (which has not
267 * moved, then this will work.
269 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
272 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
273 if (post_change_bounding_box) {
274 /* request a redraw of where the item now is */
275 queue_draw_item_area (item, post_change_bounding_box.get ());
279 /** Request a redraw of a particular area in an item's coordinates.
281 * @param area Area to redraw in the item's coordinates.
284 Canvas::queue_draw_item_area (Item* item, Rect area)
286 ArdourCanvas::Rect canvas_area = item->item_to_canvas (area);
287 // cerr << "CANVAS " << this << " for " << item << ' ' << item->whatami() << ' ' << item->name << " invalidate " << area << " TRANSLATE AS " << canvas_area << " window = " << canvas_to_window (canvas_area) << std::endl;
288 request_redraw (canvas_area);
291 /** Construct a GtkCanvas */
292 GtkCanvas::GtkCanvas ()
294 , _new_current_item (0)
298 /* these are the events we want to know about */
299 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
300 Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
304 GtkCanvas::pick_current_item (int state)
309 /* this version of ::pick_current_item() is called after an item is
310 * added or removed, so we have no coordinates to work from as is the
311 * case with a motion event. Find out where the mouse is and use that.
314 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
316 if (pointer_window != get_window()) {
320 pick_current_item (window_to_canvas (Duple (x, y)), state);
324 GtkCanvas::pick_current_item (Duple const & point, int state)
326 /* we do not enter/leave items during a drag/grab */
332 /* find the items at the given position */
334 vector<Item const *> items;
335 _root.add_items_at_point (point, items);
337 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
340 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
341 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
343 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << std::endl;
345 std::cerr << "\tItem " << (*it)->whatami() << std::endl;
351 /* put all items at point that are event-sensitive and visible and NOT
352 groups into within_items. Note that items is sorted from bottom to
353 top, but we're going to reverse that for within_items so that its
354 first item is the upper-most item that can be chosen as _current_item.
357 vector<Item const *>::const_iterator i;
358 list<Item const *> within_items;
360 for (i = items.begin(); i != items.end(); ++i) {
362 Item const * new_item = *i;
364 /* We ignore invisible items, groups and items that ignore events */
366 if (!new_item->visible() || new_item->ignore_events() || dynamic_cast<Group const *>(new_item) != 0) {
370 within_items.push_front (new_item);
373 if (within_items.empty()) {
375 /* no items at point, just send leave event below */
376 _new_current_item = 0;
380 if (within_items.front() == _current_item) {
381 /* uppermost item at point is already _current_item */
385 _new_current_item = const_cast<Item*> (within_items.front());
388 if (_new_current_item != _current_item) {
389 deliver_enter_leave (point, state);
394 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
396 /* setup enter & leave event structures */
398 GdkEventCrossing enter_event;
399 enter_event.type = GDK_ENTER_NOTIFY;
400 enter_event.window = get_window()->gobj();
401 enter_event.send_event = 0;
402 enter_event.subwindow = 0;
403 enter_event.mode = GDK_CROSSING_NORMAL;
404 enter_event.focus = FALSE;
405 enter_event.state = state;
406 enter_event.x = point.x;
407 enter_event.y = point.y;
409 GdkEventCrossing leave_event = enter_event;
410 leave_event.type = GDK_LEAVE_NOTIFY;
413 GdkNotifyType enter_detail;
414 GdkNotifyType leave_detail;
415 vector<Item*> items_to_leave_virtual;
416 vector<Item*> items_to_enter_virtual;
418 if (_new_current_item == 0) {
420 leave_detail = GDK_NOTIFY_UNKNOWN;
424 /* no current item, so also send virtual leave events to the
425 * entire heirarchy for the current item
428 for (i = _current_item->parent(); i ; i = i->parent()) {
429 items_to_leave_virtual.push_back (i);
433 } else if (_current_item == 0) {
435 enter_detail = GDK_NOTIFY_UNKNOWN;
437 /* no current item, so also send virtual enter events to the
438 * entire heirarchy for the new item
441 for (i = _new_current_item->parent(); i ; i = i->parent()) {
442 items_to_enter_virtual.push_back (i);
445 } else if (_current_item->is_descendant_of (*_new_current_item)) {
447 /* move from descendant to ancestor (X: "_current_item is an
448 * inferior ("child") of _new_current_item")
450 * Deliver "virtual" leave notifications to all items in the
451 * heirarchy between current and new_current.
455 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
456 items_to_leave_virtual.push_back (i);
459 enter_detail = GDK_NOTIFY_INFERIOR;
460 leave_detail = GDK_NOTIFY_ANCESTOR;
463 } else if (_new_current_item->is_descendant_of (*_current_item)) {
464 /* move from ancestor to descendant (X: "_new_current_item is
465 * an inferior ("child") of _current_item")
467 * Deliver "virtual" enter notifications to all items in the
468 * heirarchy between current and new_current.
471 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
472 items_to_enter_virtual.push_back (i);
475 enter_detail = GDK_NOTIFY_ANCESTOR;
476 leave_detail = GDK_NOTIFY_INFERIOR;
480 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
482 /* deliver virtual leave events to everything between _current
483 * and common_ancestor.
486 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
487 items_to_leave_virtual.push_back (i);
490 /* deliver virtual enter events to everything between
491 * _new_current and common_ancestor.
494 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
495 items_to_enter_virtual.push_back (i);
498 enter_detail = GDK_NOTIFY_NONLINEAR;
499 leave_detail = GDK_NOTIFY_NONLINEAR;
503 if (_current_item && !_current_item->ignore_events ()) {
504 leave_event.detail = leave_detail;
505 _current_item->Event ((GdkEvent*)&leave_event);
506 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
509 leave_event.detail = GDK_NOTIFY_VIRTUAL;
510 enter_event.detail = GDK_NOTIFY_VIRTUAL;
512 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
513 if (!(*it)->ignore_events()) {
514 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
515 (*it)->Event ((GdkEvent*)&leave_event);
519 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
520 if (!(*it)->ignore_events()) {
521 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
522 (*it)->Event ((GdkEvent*)&enter_event);
523 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
527 if (_new_current_item && !_new_current_item->ignore_events()) {
528 enter_event.detail = enter_detail;
529 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
530 _new_current_item->Event ((GdkEvent*)&enter_event);
533 _current_item = _new_current_item;
537 /** Deliver an event to the appropriate item; either the grabbed item, or
538 * one of the items underneath the event.
539 * @param point Position that the event has occurred at, in canvas coordinates.
540 * @param event The event.
543 GtkCanvas::deliver_event (GdkEvent* event)
545 /* Point in in canvas coordinate space */
547 const Item* event_item;
550 /* we have a grabbed item, so everything gets sent there */
551 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
552 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
553 event_item = _grabbed_item;
555 event_item = _current_item;
562 /* run through the items from child to parent, until one claims the event */
564 Item* item = const_cast<Item*> (event_item);
568 Item* parent = item->parent ();
570 if (!item->ignore_events () &&
571 item->Event (event)) {
572 /* this item has just handled the event */
574 PBD::DEBUG::CanvasEvents,
575 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
581 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)));
583 if ((item = parent) == 0) {
592 /** Called when an item is being destroyed.
593 * @param item Item being destroyed.
594 * @param bounding_box Last known bounding box of the item.
597 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
600 queue_draw_item_area (item, bounding_box.get ());
603 if (_new_current_item == item) {
604 _new_current_item = 0;
607 if (_grabbed_item == item) {
611 if (_focused_item == item) {
615 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
617 scrollers.remove (sg);
620 if (_current_item == item) {
621 /* no need to send a leave event to this item, since it is going away
624 pick_current_item (0); // no mouse state
629 /** Handler for GDK expose events.
631 * @return true if the event was handled.
634 GtkCanvas::on_expose_event (GdkEventExpose* ev)
636 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
637 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
641 /** @return Our Cairo context, or 0 if we don't have one */
642 Cairo::RefPtr<Cairo::Context>
643 GtkCanvas::context ()
645 Glib::RefPtr<Gdk::Window> w = get_window ();
647 return Cairo::RefPtr<Cairo::Context> ();
650 return w->create_cairo_context ();
653 /** Handler for GDK button press events.
655 * @return true if the event was handled.
658 GtkCanvas::on_button_press_event (GdkEventButton* ev)
660 /* translate event coordinates from window to canvas */
662 GdkEvent copy = *((GdkEvent*)ev);
663 Duple where = window_to_canvas (Duple (ev->x, ev->y));
665 copy.button.x = where.x;
666 copy.button.y = where.y;
668 /* Coordinates in the event will be canvas coordinates, correctly adjusted
669 for scroll if this GtkCanvas is in a GtkCanvasViewport.
672 pick_current_item (where, ev->state);
673 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
674 return deliver_event (reinterpret_cast<GdkEvent*>(©));
677 /** Handler for GDK button release events.
679 * @return true if the event was handled.
682 GtkCanvas::on_button_release_event (GdkEventButton* ev)
684 /* translate event coordinates from window to canvas */
686 GdkEvent copy = *((GdkEvent*)ev);
687 Duple where = window_to_canvas (Duple (ev->x, ev->y));
689 pick_current_item (where, ev->state);
691 copy.button.x = where.x;
692 copy.button.y = where.y;
694 /* Coordinates in the event will be canvas coordinates, correctly adjusted
695 for scroll if this GtkCanvas is in a GtkCanvasViewport.
698 pick_current_item (where, ev->state);
699 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
700 return deliver_event (reinterpret_cast<GdkEvent*>(©));
703 /** Handler for GDK motion events.
705 * @return true if the event was handled.
708 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
710 /* translate event coordinates from window to canvas */
712 GdkEvent copy = *((GdkEvent*)ev);
713 Duple point (ev->x, ev->y);
714 Duple where = window_to_canvas (point);
716 copy.motion.x = where.x;
717 copy.motion.y = where.y;
719 /* Coordinates in "copy" will be canvas coordinates,
722 // DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas motion @ %1, %2\n", ev->x, ev->y));
724 pick_current_item (where, ev->state);
726 /* Now deliver the motion event. It may seem a little inefficient
727 to recompute the items under the event, but the enter notify/leave
728 events may have deleted canvas items so it is important to
729 recompute the list in deliver_event.
732 return deliver_event (reinterpret_cast<GdkEvent*> (©));
736 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
738 Duple where = window_to_canvas (Duple (ev->x, ev->y));
739 pick_current_item (where, ev->state);
744 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
746 _new_current_item = 0;
747 Duple where = window_to_canvas (Duple (ev->x, ev->y));
748 deliver_enter_leave (where, ev->state);
752 /** Called to request a redraw of our canvas.
753 * @param area Area to redraw, in canvas coordinates.
756 GtkCanvas::request_redraw (Rect const & request)
758 boost::optional<Rect> req = request.intersection (visible_area());
762 Rect area = canvas_to_window (r);
763 queue_draw_area (area.x0, area.y0, area.width(), area.height());
767 /** Called to request that we try to get a particular size for ourselves.
768 * @param size Size to request, in pixels.
771 GtkCanvas::request_size (Duple size)
775 if (req.x > INT_MAX) {
779 if (req.y > INT_MAX) {
783 set_size_request (req.x, req.y);
786 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
787 * This is typically used for dragging items around, so that they are grabbed during
789 * @param item Item to grab.
792 GtkCanvas::grab (Item* item)
794 /* XXX: should this be doing gdk_pointer_grab? */
795 _grabbed_item = item;
799 /** `Ungrab' any item that was previously grabbed */
803 /* XXX: should this be doing gdk_pointer_ungrab? */
807 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
809 * @param item Item to grab.
812 GtkCanvas::focus (Item* item)
814 _focused_item = item;
818 GtkCanvas::unfocus (Item* item)
820 if (item == _focused_item) {
825 /** @return The visible area of the canvas, in canvas coordinates */
827 GtkCanvas::visible_area () const
829 Distance const xo = _scroll_offset.x;
830 Distance const yo = _scroll_offset.y;
831 return Rect (xo, yo, xo + get_allocation().get_width (), yo + get_allocation().get_height ());
834 /** Create a GtkCanvaSViewport.
835 * @param hadj Adjustment to use for horizontal scrolling.
836 * @param vadj Adjustment to use for vertica scrolling.
838 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
839 : Alignment (0, 0, 1.0, 1.0)
845 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
846 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
850 GtkCanvasViewport::scrolled ()
852 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
856 /** Handler for when GTK asks us what minimum size we want.
857 * @param req Requsition to fill in.
860 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
862 /* force the canvas to size itself */
863 // _canvas.root()->bounding_box();