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);
114 // This transparently colors the rect being rendered, after it has been drawn.
115 double r = (random() % 65536) /65536.0;
116 double g = (random() % 65536) /65536.0;
117 double b = (random() % 65536) /65536.0;
118 context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
119 context->set_source_rgba (r, g, b, 0.25);
128 operator<< (ostream& o, Canvas& c)
135 Canvas::indent() const
139 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
147 Canvas::render_indent() const
151 for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
159 Canvas::dump (ostream& o) const
165 /** Called when an item has been shown or hidden.
166 * @param item Item that has been shown or hidden.
169 Canvas::item_shown_or_hidden (Item* item)
171 boost::optional<Rect> bbox = item->bounding_box ();
173 queue_draw_item_area (item, bbox.get ());
177 /** Called when an item has a change to its visual properties
178 * that do NOT affect its bounding box.
179 * @param item Item that has been modified.
182 Canvas::item_visual_property_changed (Item* item)
184 boost::optional<Rect> bbox = item->bounding_box ();
186 queue_draw_item_area (item, bbox.get ());
190 /** Called when an item has changed, but not moved.
191 * @param item Item that has changed.
192 * @param pre_change_bounding_box The bounding box of item before the change,
193 * in the item's coordinates.
196 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
198 if (pre_change_bounding_box) {
199 /* request a redraw of the item's old bounding box */
200 queue_draw_item_area (item, pre_change_bounding_box.get ());
203 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
204 if (post_change_bounding_box) {
205 /* request a redraw of the item's new bounding box */
206 queue_draw_item_area (item, post_change_bounding_box.get ());
211 Canvas::window_to_canvas (Duple const & d) const
213 /* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
214 * as children of the root group, so we just scan its first level
215 * children and see what we can find.
218 std::list<Item*> const& root_children (_root.items());
221 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
222 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_window (d)) {
228 return d.translate (sg->scroll_offset());
235 Canvas::canvas_to_window (Duple const & d, bool rounded) const
237 /* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
238 * as children of the root group, so we just scan its first level
239 * children and see what we can find.
242 std::list<Item*> const& root_children (_root.items());
246 for (std::list<Item*>::const_iterator i = root_children.begin(); i != root_children.end(); ++i) {
247 if (((sg = dynamic_cast<ScrollGroup*>(*i)) != 0) && sg->covers_canvas (d)) {
254 wd = d.translate (-sg->scroll_offset());
259 /* Note that this intentionally almost always returns integer coordinates */
269 /** Called when an item has moved.
270 * @param item Item that has moved.
271 * @param pre_change_parent_bounding_box The bounding box of the item before
272 * the move, in its parent's coordinates.
275 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
277 if (pre_change_parent_bounding_box) {
278 /* request a redraw of where the item used to be. The box has
279 * to be in parent coordinate space since the bounding box of
280 * an item does not change when moved. If we use
281 * item->item_to_canvas() on the old bounding box, we will be
283 * using the item's new position, and so will compute the wrong
284 * invalidation area. If we use the parent (which has not
285 * moved, then this will work.
287 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
290 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
291 if (post_change_bounding_box) {
292 /* request a redraw of where the item now is */
293 queue_draw_item_area (item, post_change_bounding_box.get ());
297 /** Request a redraw of a particular area in an item's coordinates.
299 * @param area Area to redraw in the item's coordinates.
302 Canvas::queue_draw_item_area (Item* item, Rect area)
304 request_redraw (item->item_to_window (area));
307 /** Construct a GtkCanvas */
308 GtkCanvas::GtkCanvas ()
310 , _new_current_item (0)
314 /* these are the events we want to know about */
315 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
316 Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
320 GtkCanvas::pick_current_item (int state)
325 /* this version of ::pick_current_item() is called after an item is
326 * added or removed, so we have no coordinates to work from as is the
327 * case with a motion event. Find out where the mouse is and use that.
330 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
332 if (pointer_window != get_window()) {
336 pick_current_item (Duple (x, y), state);
339 /** Given @param point (a position in window coordinates)
340 * and mouse state @param state, check to see if _current_item
341 * (which will be used to deliver events) should change.
344 GtkCanvas::pick_current_item (Duple const & point, int state)
346 /* we do not enter/leave items during a drag/grab */
352 /* find the items at the given window position */
354 vector<Item const *> items;
355 _root.add_items_at_point (point, items);
357 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("%1 covers %2 items\n", point, items.size()));
360 if (DEBUG_ENABLED(PBD::DEBUG::CanvasEnterLeave)) {
361 for (vector<Item const*>::const_iterator it = items.begin(); it != items.end(); ++it) {
363 std::cerr << "\tItem " << (*it)->whatami() << '/' << (*it)->name << std::endl;
365 std::cerr << "\tItem " << (*it)->whatami() << std::endl;
371 /* put all items at point that are event-sensitive and visible and NOT
372 groups into within_items. Note that items is sorted from bottom to
373 top, but we're going to reverse that for within_items so that its
374 first item is the upper-most item that can be chosen as _current_item.
377 vector<Item const *>::const_iterator i;
378 list<Item const *> within_items;
380 for (i = items.begin(); i != items.end(); ++i) {
382 Item const * new_item = *i;
384 /* We ignore invisible items, groups and items that ignore events */
386 if (!new_item->visible() || new_item->ignore_events() || dynamic_cast<Group const *>(new_item) != 0) {
390 within_items.push_front (new_item);
393 if (within_items.empty()) {
395 /* no items at point, just send leave event below */
396 _new_current_item = 0;
400 if (within_items.front() == _current_item) {
401 /* uppermost item at point is already _current_item */
405 _new_current_item = const_cast<Item*> (within_items.front());
408 if (_new_current_item != _current_item) {
409 deliver_enter_leave (point, state);
413 /** Deliver a series of enter & leave events based on the pointer position being at window
414 * coordinate @param point, and pointer @param state (modifier keys, etc)
417 GtkCanvas::deliver_enter_leave (Duple const & point, int state)
419 /* setup enter & leave event structures */
421 GdkEventCrossing enter_event;
422 enter_event.type = GDK_ENTER_NOTIFY;
423 enter_event.window = get_window()->gobj();
424 enter_event.send_event = 0;
425 enter_event.subwindow = 0;
426 enter_event.mode = GDK_CROSSING_NORMAL;
427 enter_event.focus = FALSE;
428 enter_event.state = state;
430 /* Events delivered to canvas items are expected to be in canvas
431 * coordinates but @param point is in window coordinates.
434 Duple c = window_to_canvas (point);
438 GdkEventCrossing leave_event = enter_event;
439 leave_event.type = GDK_LEAVE_NOTIFY;
442 GdkNotifyType enter_detail;
443 GdkNotifyType leave_detail;
444 vector<Item*> items_to_leave_virtual;
445 vector<Item*> items_to_enter_virtual;
447 if (_new_current_item == 0) {
449 leave_detail = GDK_NOTIFY_UNKNOWN;
453 /* no current item, so also send virtual leave events to the
454 * entire heirarchy for the current item
457 for (i = _current_item->parent(); i ; i = i->parent()) {
458 items_to_leave_virtual.push_back (i);
462 } else if (_current_item == 0) {
464 enter_detail = GDK_NOTIFY_UNKNOWN;
466 /* no current item, so also send virtual enter events to the
467 * entire heirarchy for the new item
470 for (i = _new_current_item->parent(); i ; i = i->parent()) {
471 items_to_enter_virtual.push_back (i);
474 } else if (_current_item->is_descendant_of (*_new_current_item)) {
476 /* move from descendant to ancestor (X: "_current_item is an
477 * inferior ("child") of _new_current_item")
479 * Deliver "virtual" leave notifications to all items in the
480 * heirarchy between current and new_current.
483 for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
484 items_to_leave_virtual.push_back (i);
487 enter_detail = GDK_NOTIFY_INFERIOR;
488 leave_detail = GDK_NOTIFY_ANCESTOR;
491 } else if (_new_current_item->is_descendant_of (*_current_item)) {
492 /* move from ancestor to descendant (X: "_new_current_item is
493 * an inferior ("child") of _current_item")
495 * Deliver "virtual" enter notifications to all items in the
496 * heirarchy between current and new_current.
499 for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
500 items_to_enter_virtual.push_back (i);
503 enter_detail = GDK_NOTIFY_ANCESTOR;
504 leave_detail = GDK_NOTIFY_INFERIOR;
508 Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
510 /* deliver virtual leave events to everything between _current
511 * and common_ancestor.
514 for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
515 items_to_leave_virtual.push_back (i);
518 /* deliver virtual enter events to everything between
519 * _new_current and common_ancestor.
522 for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
523 items_to_enter_virtual.push_back (i);
526 enter_detail = GDK_NOTIFY_NONLINEAR;
527 leave_detail = GDK_NOTIFY_NONLINEAR;
531 if (_current_item && !_current_item->ignore_events ()) {
532 leave_event.detail = leave_detail;
533 _current_item->Event ((GdkEvent*)&leave_event);
534 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
537 leave_event.detail = GDK_NOTIFY_VIRTUAL;
538 enter_event.detail = GDK_NOTIFY_VIRTUAL;
540 for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
541 if (!(*it)->ignore_events()) {
542 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
543 (*it)->Event ((GdkEvent*)&leave_event);
547 for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
548 if (!(*it)->ignore_events()) {
549 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
550 (*it)->Event ((GdkEvent*)&enter_event);
551 // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
555 if (_new_current_item && !_new_current_item->ignore_events()) {
556 enter_event.detail = enter_detail;
557 DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
558 _new_current_item->Event ((GdkEvent*)&enter_event);
561 _current_item = _new_current_item;
565 /** Deliver an event to the appropriate item; either the grabbed item, or
566 * one of the items underneath the event.
567 * @param point Position that the event has occurred at, in canvas coordinates.
568 * @param event The event.
571 GtkCanvas::deliver_event (GdkEvent* event)
573 /* Point in in canvas coordinate space */
575 const Item* event_item;
578 /* we have a grabbed item, so everything gets sent there */
579 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
580 _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
581 event_item = _grabbed_item;
583 event_item = _current_item;
590 /* run through the items from child to parent, until one claims the event */
592 Item* item = const_cast<Item*> (event_item);
596 Item* parent = item->parent ();
598 if (!item->ignore_events () &&
599 item->Event (event)) {
600 /* this item has just handled the event */
602 PBD::DEBUG::CanvasEvents,
603 string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
609 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)));
611 if ((item = parent) == 0) {
620 /** Called when an item is being destroyed.
621 * @param item Item being destroyed.
622 * @param bounding_box Last known bounding box of the item.
625 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
628 queue_draw_item_area (item, bounding_box.get ());
631 if (_new_current_item == item) {
632 _new_current_item = 0;
635 if (_grabbed_item == item) {
639 if (_focused_item == item) {
643 ScrollGroup* sg = dynamic_cast<ScrollGroup*>(item);
645 scrollers.remove (sg);
648 if (_current_item == item) {
649 /* no need to send a leave event to this item, since it is going away
652 pick_current_item (0); // no mouse state
657 /** Handler for GDK expose events.
659 * @return true if the event was handled.
662 GtkCanvas::on_expose_event (GdkEventExpose* ev)
664 Cairo::RefPtr<Cairo::Context> cairo_context = get_window()->create_cairo_context ();
665 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), cairo_context);
669 /** @return Our Cairo context, or 0 if we don't have one */
670 Cairo::RefPtr<Cairo::Context>
671 GtkCanvas::context ()
673 Glib::RefPtr<Gdk::Window> w = get_window ();
675 return Cairo::RefPtr<Cairo::Context> ();
678 return w->create_cairo_context ();
681 /** Handler for GDK button press events.
683 * @return true if the event was handled.
686 GtkCanvas::on_button_press_event (GdkEventButton* ev)
688 /* translate event coordinates from window to canvas */
690 GdkEvent copy = *((GdkEvent*)ev);
691 Duple winpos = Duple (ev->x, ev->y);
692 Duple where = window_to_canvas (winpos);
694 pick_current_item (winpos, ev->state);
696 copy.button.x = where.x;
697 copy.button.y = where.y;
699 /* Coordinates in the event will be canvas coordinates, correctly adjusted
700 for scroll if this GtkCanvas is in a GtkCanvasViewport.
703 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
704 return deliver_event (reinterpret_cast<GdkEvent*>(©));
707 /** Handler for GDK button release events.
709 * @return true if the event was handled.
712 GtkCanvas::on_button_release_event (GdkEventButton* ev)
714 /* translate event coordinates from window to canvas */
716 GdkEvent copy = *((GdkEvent*)ev);
717 Duple winpos = Duple (ev->x, ev->y);
718 Duple where = window_to_canvas (winpos);
720 pick_current_item (winpos, ev->state);
722 copy.button.x = where.x;
723 copy.button.y = where.y;
725 /* Coordinates in the event will be canvas coordinates, correctly adjusted
726 for scroll if this GtkCanvas is in a GtkCanvasViewport.
729 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
730 return deliver_event (reinterpret_cast<GdkEvent*>(©));
733 /** Handler for GDK motion events.
735 * @return true if the event was handled.
738 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
740 /* translate event coordinates from window to canvas */
742 GdkEvent copy = *((GdkEvent*)ev);
743 Duple point (ev->x, ev->y);
744 Duple where = window_to_canvas (point);
746 copy.motion.x = where.x;
747 copy.motion.y = where.y;
749 /* Coordinates in "copy" will be canvas coordinates,
752 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));
754 pick_current_item (point, ev->state);
756 /* Now deliver the motion event. It may seem a little inefficient
757 to recompute the items under the event, but the enter notify/leave
758 events may have deleted canvas items so it is important to
759 recompute the list in deliver_event.
762 return deliver_event (reinterpret_cast<GdkEvent*> (©));
766 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
768 pick_current_item (Duple (ev->x, ev->y), ev->state);
773 GtkCanvas::on_leave_notify_event (GdkEventCrossing* ev)
775 _new_current_item = 0;
776 deliver_enter_leave (Duple (ev->x, ev->y), ev->state);
780 /** Called to request a redraw of our canvas.
781 * @param area Area to redraw, in window coordinates.
784 GtkCanvas::request_redraw (Rect const & request)
788 Coord const w = width ();
789 Coord const h = height ();
791 /* clamp area requested to actual visible window */
793 real_area.x0 = max (0.0, min (w, request.x0));
794 real_area.x1 = max (0.0, min (w, request.x1));
795 real_area.y0 = max (0.0, min (h, request.y0));
796 real_area.y1 = max (0.0, min (h, request.y1));
798 queue_draw_area (real_area.x0, real_area.y0, real_area.width(), real_area.height());
801 /** Called to request that we try to get a particular size for ourselves.
802 * @param size Size to request, in pixels.
805 GtkCanvas::request_size (Duple size)
809 if (req.x > INT_MAX) {
813 if (req.y > INT_MAX) {
817 set_size_request (req.x, req.y);
820 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
821 * This is typically used for dragging items around, so that they are grabbed during
823 * @param item Item to grab.
826 GtkCanvas::grab (Item* item)
828 /* XXX: should this be doing gdk_pointer_grab? */
829 _grabbed_item = item;
833 /** `Ungrab' any item that was previously grabbed */
837 /* XXX: should this be doing gdk_pointer_ungrab? */
841 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
843 * @param item Item to grab.
846 GtkCanvas::focus (Item* item)
848 _focused_item = item;
852 GtkCanvas::unfocus (Item* item)
854 if (item == _focused_item) {
859 /** @return The visible area of the canvas, in window coordinates */
861 GtkCanvas::visible_area () const
863 return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
867 GtkCanvas::width() const
869 return get_allocation().get_width();
873 GtkCanvas::height() const
875 return get_allocation().get_height();
878 /** Create a GtkCanvaSViewport.
879 * @param hadj Adjustment to use for horizontal scrolling.
880 * @param vadj Adjustment to use for vertica scrolling.
882 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
883 : Alignment (0, 0, 1.0, 1.0)
889 hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
890 vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
894 GtkCanvasViewport::scrolled ()
896 _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
900 /** Handler for when GTK asks us what minimum size we want.
901 * @param req Requsition to fill in.
904 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
906 /* force the canvas to size itself */
907 // _canvas.root()->bounding_box();