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 /* We do things this way because we do not want to recurse through
52 the canvas for every scroll. In the presence of large MIDI
53 tracks this means traversing item lists that include
54 thousands of items (notes).
56 This design limits us to moving only those items (groups, typically)
57 that should move in certain ways as we scroll. In other terms, it
58 becomes O(1) rather than O(N).
61 for (list<ScrollGroup*>::iterator i = scrollers.begin(); i != scrollers.end(); ++i) {
62 (*i)->scroll_to (Duple (x, y));
65 pick_current_item (0); // no current mouse position
69 Canvas::add_scroller (ScrollGroup& i)
71 scrollers.push_back (&i);
77 pick_current_item (0); // no current mouse position
80 /** Render an area of the canvas.
81 * @param area Area in window coordinates.
82 * @param context Cairo context to render to.
85 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
88 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
89 cerr << this << " RENDER: " << area << endl;
90 //cerr << "CANVAS @ " << this << endl;
92 //cerr << "-------------------------\n";
98 boost::optional<Rect> root_bbox = _root.bounding_box();
100 /* the root has no bounding box, so there's nothing to render */
104 boost::optional<Rect> draw = root_bbox->intersection (area);
107 /* there's a common area between the root and the requested
111 _root.render (*draw, context);
113 // This outlines the rect being rendered, after it has been drawn.
114 // context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
115 // context->set_source_rgba (1.0, 0, 0, 1.0);
116 // context->stroke ();
123 operator<< (ostream& o, Canvas& c)
130 Canvas::indent() const
134 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
142 Canvas::render_indent() const
146 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
154 Canvas::dump (ostream& o) const
160 /** Called when an item has been shown or hidden.
161 * @param item Item that has been shown or hidden.
164 Canvas::item_shown_or_hidden (Item* item)
166 boost::optional<Rect> bbox = item->bounding_box ();
168 queue_draw_item_area (item, bbox.get ());
172 /** Called when an item has a change to its visual properties
173 * that do NOT affect its bounding box.
174 * @param item Item that has been modified.
177 Canvas::item_visual_property_changed (Item* item)
179 boost::optional<Rect> bbox = item->bounding_box ();
181 queue_draw_item_area (item, bbox.get ());
185 /** Called when an item has changed, but not moved.
186 * @param item Item that has changed.
187 * @param pre_change_bounding_box The bounding box of item before the change,
188 * in the item's coordinates.
191 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
193 if (pre_change_bounding_box) {
194 /* request a redraw of the item's old bounding box */
195 queue_draw_item_area (item, pre_change_bounding_box.get ());
198 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
199 if (post_change_bounding_box) {
200 /* request a redraw of the item's new bounding box */
201 queue_draw_item_area (item, post_change_bounding_box.get ());
206 Canvas::window_to_canvas (Duple const & d) const
208 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
209 * as children of the root group, so we just scan its first level
210 * children and see what we can find.
213 std::list<Item*> const& root_children (_root.items());
216 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
217 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (d)) {
223 return d.translate (sg->scroll_offset());
230 Canvas::canvas_to_window (Duple const & d, bool rounded) const
232 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
233 * as children of the root group, so we just scan its first level
234 * children and see what we can find.
237 std::list<Item*> const& root_children (_root.items());
241 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
242 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
249 wd = d.translate (-sg->scroll_offset());
254 /* Note that this intentionally almost always returns integer coordinates */
264 /** Called when an item has moved.
265 * @param item Item that has moved.
266 * @param pre_change_parent_bounding_box The bounding box of the item before
267 * the move, in its parent's coordinates.
270 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
272 if (pre_change_parent_bounding_box) {
273 /* request a redraw of where the item used to be. The box has
274 * to be in parent coordinate space since the bounding box of
275 * an item does not change when moved. If we use
276 * item->item_to_canvas() on the old bounding box, we will be
278 * using the item's new position, and so will compute the wrong
279 * invalidation area. If we use the parent (which has not
280 * moved, then this will work.
282 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
285 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
286 if (post_change_bounding_box) {
287 /* request a redraw of where the item now is */
288 queue_draw_item_area (item, post_change_bounding_box.get ());
292 /** Request a redraw of a particular area in an item's coordinates.
294 * @param area Area to redraw in the item's coordinates.
297 Canvas::queue_draw_item_area (Item* item, Rect area)
299 request_redraw (item->item_to_window (area));
302 /** Construct a GtkCanvas */
303 GtkCanvas::GtkCanvas ()
305 , _new_current_item (0)
309 /* these are the events we want to know about */
310 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
311 Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
315 GtkCanvas::pick_current_item (int state)
320 /* this version of ::pick_current_item() is called after an item is
321 * added or removed, so we have no coordinates to work from as is the
322 * case with a motion event. Find out where the mouse is and use that.
325 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
327 if (pointer_window != get_window()) {
331 pick_current_item (Duple (x, y), state);
334 /** Given @param point (a position in window coordinates)
335 * and mouse state @param state, check to see if _current_item
336 * (which will be used to deliver events) should change.
339 GtkCanvas::pick_current_item (Duple const & point, int state)
341 /* we do not enter/leave items during a drag/grab */
347 /* find the items at the given window position */
349 vector<Item const *> items;
350 _root.add_items_at_point (point, items);
352 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
355 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
356 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
358 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << std::endl;
360 std::cerr << "\tItem " << (*it)->whatami() << std::endl;
366 /* put all items at point that are event-sensitive and visible and NOT
367 groups into within_items. Note that items is sorted from bottom to
368 top, but we're going to reverse that for within_items so that its
369 first item is the upper-most item that can be chosen as _current_item.
372 vector<Item const *>::const_iterator i;
373 list<Item const *> within_items;
375 for (i = items.begin(); i != items.end(); ++i) {
377 Item const * new_item = *i;
379 /* We ignore invisible items, groups and items that ignore events */
381 if (!new_item->visible() || new_item->ignore_events() || dynamic_cast<Group const *>(new_item) != 0) {
385 within_items.push_front (new_item);
388 if (within_items.empty()) {
390 /* no items at point, just send leave event below */
391 _new_current_item = 0;
395 if (within_items.front() == _current_item) {
396 /* uppermost item at point is already _current_item */
400 _new_current_item = const_cast<Item*> (within_items.front());
403 if (_new_current_item != _current_item) {
404 deliver_enter_leave (point, state);
408 /** Deliver a series of enter & leave events based on the pointer position being at window
409 * coordinate @param point, and pointer @param state (modifier keys, etc)
412 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
414 /* setup enter & leave event structures */
416 GdkEventCrossing enter_event;
417 enter_event.type = GDK_ENTER_NOTIFY;
418 enter_event.window = get_window()->gobj();
419 enter_event.send_event = 0;
420 enter_event.subwindow = 0;
421 enter_event.mode = GDK_CROSSING_NORMAL;
422 enter_event.focus = FALSE;
423 enter_event.state = state;
425 /* Events delivered to canvas items are expected to be in canvas
426 * coordinates but @param point is in window coordinates.
429 Duple c = window_to_canvas (point);
433 GdkEventCrossing leave_event = enter_event;
434 leave_event.type = GDK_LEAVE_NOTIFY;
437 GdkNotifyType enter_detail;
438 GdkNotifyType leave_detail;
439 vector<Item*> items_to_leave_virtual;
440 vector<Item*> items_to_enter_virtual;
442 if (_new_current_item == 0) {
444 leave_detail = GDK_NOTIFY_UNKNOWN;
448 /* no current item, so also send virtual leave events to the
449 * entire heirarchy for the current item
452 for (i = _current_item->parent(); i ; i = i->parent()) {
453 items_to_leave_virtual.push_back (i);
457 } else if (_current_item == 0) {
459 enter_detail = GDK_NOTIFY_UNKNOWN;
461 /* no current item, so also send virtual enter events to the
462 * entire heirarchy for the new item
465 for (i = _new_current_item->parent(); i ; i = i->parent()) {
466 items_to_enter_virtual.push_back (i);
469 } else if (_current_item->is_descendant_of (*_new_current_item)) {
471 /* move from descendant to ancestor (X: "_current_item is an
472 * inferior ("child") of _new_current_item")
474 * Deliver "virtual" leave notifications to all items in the
475 * heirarchy between current and new_current.
478 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
479 items_to_leave_virtual.push_back (i);
482 enter_detail = GDK_NOTIFY_INFERIOR;
483 leave_detail = GDK_NOTIFY_ANCESTOR;
486 } else if (_new_current_item->is_descendant_of (*_current_item)) {
487 /* move from ancestor to descendant (X: "_new_current_item is
488 * an inferior ("child") of _current_item")
490 * Deliver "virtual" enter notifications to all items in the
491 * heirarchy between current and new_current.
494 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
495 items_to_enter_virtual.push_back (i);
498 enter_detail = GDK_NOTIFY_ANCESTOR;
499 leave_detail = GDK_NOTIFY_INFERIOR;
503 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
505 /* deliver virtual leave events to everything between _current
506 * and common_ancestor.
509 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
510 items_to_leave_virtual.push_back (i);
513 /* deliver virtual enter events to everything between
514 * _new_current and common_ancestor.
517 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
518 items_to_enter_virtual.push_back (i);
521 enter_detail = GDK_NOTIFY_NONLINEAR;
522 leave_detail = GDK_NOTIFY_NONLINEAR;
526 if (_current_item && !_current_item->ignore_events ()) {
527 leave_event.detail = leave_detail;
528 _current_item->Event ((GdkEvent*)&leave_event);
529 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
532 leave_event.detail = GDK_NOTIFY_VIRTUAL;
533 enter_event.detail = GDK_NOTIFY_VIRTUAL;
535 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
536 if (!(*it)->ignore_events()) {
537 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
538 (*it)->Event ((GdkEvent*)&leave_event);
542 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
543 if (!(*it)->ignore_events()) {
544 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
545 (*it)->Event ((GdkEvent*)&enter_event);
546 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
550 if (_new_current_item && !_new_current_item->ignore_events()) {
551 enter_event.detail = enter_detail;
552 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
553 _new_current_item->Event ((GdkEvent*)&enter_event);
556 _current_item = _new_current_item;
560 /** Deliver an event to the appropriate item; either the grabbed item, or
561 * one of the items underneath the event.
562 * @param point Position that the event has occurred at, in canvas coordinates.
563 * @param event The event.
566 GtkCanvas::deliver_event (GdkEvent* event)
568 /* Point in in canvas coordinate space */
570 const Item* event_item;
573 /* we have a grabbed item, so everything gets sent there */
574 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
575 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
576 event_item = _grabbed_item;
578 event_item = _current_item;
585 /* run through the items from child to parent, until one claims the event */
587 Item* item = const_cast<Item*> (event_item);
591 Item* parent = item->parent ();
593 if (!item->ignore_events () &&
594 item->Event (event)) {
595 /* this item has just handled the event */
597 PBD::DEBUG::CanvasEvents,
598 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
604 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)));
606 if ((item = parent) == 0) {
615 /** Called when an item is being destroyed.
616 * @param item Item being destroyed.
617 * @param bounding_box Last known bounding box of the item.
620 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
623 queue_draw_item_area (item, bounding_box.get ());
626 if (_new_current_item == item) {
627 _new_current_item = 0;
630 if (_grabbed_item == item) {
634 if (_focused_item == item) {
638 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
640 scrollers.remove (sg);
643 if (_current_item == item) {
644 /* no need to send a leave event to this item, since it is going away
647 pick_current_item (0); // no mouse state
652 /** Handler for GDK expose events.
654 * @return true if the event was handled.
657 GtkCanvas::on_expose_event (GdkEventExpose* ev)
659 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
660 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
664 /** @return Our Cairo context, or 0 if we don't have one */
665 Cairo::RefPtr<Cairo::Context>
666 GtkCanvas::context ()
668 Glib::RefPtr<Gdk::Window> w = get_window ();
670 return Cairo::RefPtr<Cairo::Context> ();
673 return w->create_cairo_context ();
676 /** Handler for GDK button press events.
678 * @return true if the event was handled.
681 GtkCanvas::on_button_press_event (GdkEventButton* ev)
683 /* translate event coordinates from window to canvas */
685 GdkEvent copy = *((GdkEvent*)ev);
686 Duple winpos = Duple (ev->x, ev->y);
687 Duple where = window_to_canvas (winpos);
689 pick_current_item (winpos, 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 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
699 return deliver_event (reinterpret_cast<GdkEvent*>(©));
702 /** Handler for GDK button release events.
704 * @return true if the event was handled.
707 GtkCanvas::on_button_release_event (GdkEventButton* ev)
709 /* translate event coordinates from window to canvas */
711 GdkEvent copy = *((GdkEvent*)ev);
712 Duple winpos = Duple (ev->x, ev->y);
713 Duple where = window_to_canvas (winpos);
715 pick_current_item (winpos, ev->state);
717 copy.button.x = where.x;
718 copy.button.y = where.y;
720 /* Coordinates in the event will be canvas coordinates, correctly adjusted
721 for scroll if this GtkCanvas is in a GtkCanvasViewport.
724 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
725 return deliver_event (reinterpret_cast<GdkEvent*>(©));
728 /** Handler for GDK motion events.
730 * @return true if the event was handled.
733 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
735 /* translate event coordinates from window to canvas */
737 GdkEvent copy = *((GdkEvent*)ev);
738 Duple point (ev->x, ev->y);
739 Duple where = window_to_canvas (point);
741 copy.motion.x = where.x;
742 copy.motion.y = where.y;
744 /* Coordinates in "copy" will be canvas coordinates,
747 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));
749 pick_current_item (point, ev->state);
751 /* Now deliver the motion event. It may seem a little inefficient
752 to recompute the items under the event, but the enter notify/leave
753 events may have deleted canvas items so it is important to
754 recompute the list in deliver_event.
757 return deliver_event (reinterpret_cast<GdkEvent*> (©));
761 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
763 pick_current_item (Duple (ev->x, ev->y), ev->state);
768 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
770 _new_current_item = 0;
771 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
775 /** Called to request a redraw of our canvas.
776 * @param area Area to redraw, in window coordinates.
779 GtkCanvas::request_redraw (Rect const & request)
783 Coord const w = width ();
784 Coord const h = height ();
786 /* clamp area requested to actual visible window */
788 real_area.x0 = max (0.0, min (w, request.x0));
789 real_area.x1 = max (0.0, min (w, request.x1));
790 real_area.y0 = max (0.0, min (h, request.y0));
791 real_area.y1 = max (0.0, min (h, request.y1));
793 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
796 /** Called to request that we try to get a particular size for ourselves.
797 * @param size Size to request, in pixels.
800 GtkCanvas::request_size (Duple size)
804 if (req.x > INT_MAX) {
808 if (req.y > INT_MAX) {
812 set_size_request (req.x, req.y);
815 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
816 * This is typically used for dragging items around, so that they are grabbed during
818 * @param item Item to grab.
821 GtkCanvas::grab (Item* item)
823 /* XXX: should this be doing gdk_pointer_grab? */
824 _grabbed_item = item;
828 /** `Ungrab' any item that was previously grabbed */
832 /* XXX: should this be doing gdk_pointer_ungrab? */
836 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
838 * @param item Item to grab.
841 GtkCanvas::focus (Item* item)
843 _focused_item = item;
847 GtkCanvas::unfocus (Item* item)
849 if (item == _focused_item) {
854 /** @return The visible area of the canvas, in window coordinates */
856 GtkCanvas::visible_area () const
858 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
862 GtkCanvas::width() const
864 return get_allocation().get_width();
868 GtkCanvas::height() const
870 return get_allocation().get_height();
873 /** Create a GtkCanvaSViewport.
874 * @param hadj Adjustment to use for horizontal scrolling.
875 * @param vadj Adjustment to use for vertica scrolling.
877 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
878 : Alignment (0, 0, 1.0, 1.0)
884 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
885 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
889 GtkCanvasViewport::scrolled ()
891 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
895 /** Handler for when GTK asks us what minimum size we want.
896 * @param req Requsition to fill in.
899 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
901 /* force the canvas to size itself */
902 // _canvas.root()->bounding_box();