2 Copyright (C) 2011-2013 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.
20 #include "pbd/compose.h"
21 #include "pbd/stacktrace.h"
22 #include "pbd/convert.h"
24 #include "ardour/utils.h"
26 #include "canvas/canvas.h"
27 #include "canvas/debug.h"
28 #include "canvas/item.h"
29 #include "canvas/scroll_group.h"
33 using namespace ArdourCanvas;
35 int Item::default_items_per_cell = 64;
37 Item::Item (Canvas* canvas)
44 , _bounding_box_dirty (true)
46 , _ignore_events (false)
48 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
51 Item::Item (Item* parent)
54 , _canvas (parent->canvas())
58 , _bounding_box_dirty (true)
60 , _ignore_events (false)
62 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
68 find_scroll_parent ();
71 Item::Item (Item* parent, Duple const& p)
74 , _canvas (parent->canvas())
79 , _bounding_box_dirty (true)
81 , _ignore_events (false)
83 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
89 find_scroll_parent ();
96 _parent->remove (this);
100 _canvas->item_going_away (this, _bounding_box);
108 Item::canvas_origin () const
110 return item_to_canvas (Duple (0,0));
114 Item::window_origin () const
116 /* This is slightly subtle. Our _position is in the coordinate space of
117 our parent. So to find out where that is in window coordinates, we
118 have to ask our parent.
121 return _parent->item_to_window (_position);
128 Item::item_to_parent (ArdourCanvas::Rect const & r) const
130 return r.translate (_position);
134 Item::scroll_offset () const
136 if (_scroll_parent) {
137 return _scroll_parent->scroll_offset();
143 Item::position_offset() const
145 Item const * i = this;
149 offset = offset.translate (i->position());
157 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
159 return r.translate (position_offset());
163 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
165 return d.translate (position_offset());
169 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
171 return r.translate (-position_offset());
175 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
177 return r.translate (-position_offset());
181 Item::item_to_canvas (Coord& x, Coord& y) const
183 Duple d = item_to_canvas (Duple (x, y));
190 Item::canvas_to_item (Coord& x, Coord& y) const
192 Duple d = canvas_to_item (Duple (x, y));
200 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
202 Duple ret = item_to_canvas (d).translate (-scroll_offset());
205 ret.x = round (ret.x);
206 ret.y = round (ret.y);
213 Item::window_to_item (ArdourCanvas::Duple const & d) const
215 return canvas_to_item (d.translate (scroll_offset()));
219 Item::item_to_window (ArdourCanvas::Rect const & r) const
221 Rect ret = item_to_canvas (r).translate (-scroll_offset());
223 ret.x0 = round (ret.x0);
224 ret.x1 = round (ret.x1);
225 ret.y0 = round (ret.y0);
226 ret.y1 = round (ret.y1);
232 Item::window_to_item (ArdourCanvas::Rect const & r) const
234 return canvas_to_item (r.translate (scroll_offset()));
237 /** Set the position of this item in the parent's coordinates */
239 Item::set_position (Duple p)
241 if (p == _position) {
245 boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
246 boost::optional<ArdourCanvas::Rect> pre_change_parent_bounding_box;
249 /* see the comment in Canvas::item_moved() to understand
250 * why we use the parent's bounding box here.
252 pre_change_parent_bounding_box = item_to_parent (bbox.get());
257 _canvas->item_moved (this, pre_change_parent_bounding_box);
260 _parent->child_changed ();
265 Item::set_x_position (Coord x)
267 set_position (Duple (x, _position.y));
271 Item::set_y_position (Coord y)
273 set_position (Duple (_position.x, y));
277 Item::raise_to_top ()
280 _parent->raise_child_to_top (this);
285 Item::raise (int levels)
288 _parent->raise_child (this, levels);
293 Item::lower_to_bottom ()
296 _parent->lower_child_to_bottom (this);
306 /* recompute parent bounding box, which may alter now that this
311 _parent->child_changed ();
314 _canvas->item_shown_or_hidden (this);
324 /* bounding box may have changed while we were hidden */
327 _parent->child_changed ();
330 _canvas->item_shown_or_hidden (this);
335 Item::item_to_parent (Duple const & d) const
337 return d.translate (_position);
341 Item::parent_to_item (Duple const & d) const
343 return d.translate (- _position);
347 Item::parent_to_item (ArdourCanvas::Rect const & d) const
349 return d.translate (- _position);
360 Item::reparent (Item* new_parent)
362 if (new_parent == _parent) {
366 assert (_canvas == new_parent->canvas());
369 _parent->remove (this);
374 _parent = new_parent;
375 _canvas = _parent->canvas ();
377 find_scroll_parent ();
383 Item::find_scroll_parent ()
385 Item const * i = this;
386 ScrollGroup const * last_scroll_group = 0;
388 /* Don't allow a scroll group to find itself as its own scroll parent
394 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
396 last_scroll_group = sg;
401 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
405 Item::common_ancestor_within (uint32_t limit, const Item& other) const
407 uint32_t d1 = depth();
408 uint32_t d2 = other.depth();
409 const Item* i1 = this;
410 const Item* i2 = &other;
412 /* move towards root until we are at the same level
437 /* now see if there is a common parent */
457 Item::closest_ancestor_with (const Item& other) const
459 uint32_t d1 = depth();
460 uint32_t d2 = other.depth();
461 const Item* i1 = this;
462 const Item* i2 = &other;
464 /* move towards root until we are at the same level
484 /* now see if there is a common parent */
499 Item::is_descendant_of (const Item& candidate) const
501 Item const * i = _parent;
504 if (i == &candidate) {
519 /** @return Bounding box in this item's coordinates */
520 boost::optional<ArdourCanvas::Rect>
521 Item::bounding_box () const
523 if (_bounding_box_dirty) {
524 compute_bounding_box ();
525 assert (!_bounding_box_dirty);
526 add_child_bounding_boxes ();
529 return _bounding_box;
533 Item::height () const
535 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
538 return bb->height ();
546 boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
556 Item::redraw () const
558 if (_visible && _bounding_box && _canvas) {
559 _canvas->request_redraw (item_to_window (_bounding_box.get()));
564 Item::begin_change ()
566 _pre_change_bounding_box = bounding_box ();
573 _canvas->item_changed (this, _pre_change_bounding_box);
576 _parent->child_changed ();
582 Item::begin_visual_change ()
587 Item::end_visual_change ()
590 _canvas->item_visual_property_changed (this);
595 Item::move (Duple movement)
597 set_position (position() + movement);
604 _canvas->grab (this);
615 Item::set_data (string const & key, void* data)
621 Item::get_data (string const & key) const
623 map<string, void*>::const_iterator i = _data.find (key);
624 if (i == _data.end ()) {
632 Item::set_ignore_events (bool ignore)
634 _ignore_events = ignore;
638 Item::whatami () const
640 std::string type = demangle (typeid (*this).name());
641 return type.substr (type.find_last_of (':') + 1);
657 Item::covers (Duple const & point) const
659 Duple p = window_to_item (point);
661 if (_bounding_box_dirty) {
662 compute_bounding_box ();
665 boost::optional<Rect> r = bounding_box();
671 return r.get().contains (p);
674 /* nesting/grouping API */
677 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
679 if (_items.empty()) {
684 std::vector<Item*> items = _lut->get (area);
687 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
688 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
689 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
696 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
698 if (!(*i)->visible ()) {
700 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
701 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
707 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
711 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
712 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
718 Rect item = (*i)->item_to_window (item_bbox.get());
719 boost::optional<Rect> d = item.intersection (area);
723 if (draw.width() && draw.height()) {
725 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
726 if (dynamic_cast<Container*>(*i) == 0) {
727 cerr << _canvas->render_indent() << "render "
747 (*i)->render (area, context);
754 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
755 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
756 (*i)->name, item, area);
767 Item::add_child_bounding_boxes() const
769 boost::optional<Rect> self;
771 bool have_one = false;
774 bbox = _bounding_box.get();
778 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
780 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
786 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
788 bbox = bbox.extend (group_bbox);
796 _bounding_box = boost::optional<Rect> ();
798 _bounding_box = bbox;
805 /* XXX should really notify canvas about this */
807 _items.push_back (i);
810 _bounding_box_dirty = true;
814 Item::remove (Item* i)
817 if (i->parent() != this) {
821 /* we cannot call bounding_box() here because that will iterate over
822 _items, one of which (the argument, i) may be in the middle of
823 deletion, making it impossible to call compute_bounding_box()
828 _pre_change_bounding_box = _bounding_box;
830 _pre_change_bounding_box = Rect();
836 _bounding_box_dirty = true;
842 Item::clear (bool with_delete)
846 clear_items (with_delete);
849 _bounding_box_dirty = true;
855 Item::clear_items (bool with_delete)
857 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
859 list<Item*>::iterator tmp = i;
864 /* remove from list before doing anything else, because we
865 * don't want to find the item in _items during any activity
866 * driven by unparent-ing or deletion.
881 Item::raise_child_to_top (Item* i)
883 if (!_items.empty()) {
884 if (_items.back() == i) {
890 _items.push_back (i);
895 Item::raise_child (Item* i, int levels)
897 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
898 assert (j != _items.end ());
903 while (levels > 0 && j != _items.end ()) {
908 _items.insert (j, i);
913 Item::lower_child_to_bottom (Item* i)
915 if (!_items.empty()) {
916 if (_items.front() == i) {
921 _items.push_front (i);
926 Item::ensure_lut () const
929 _lut = new DumbLookupTable (*this);
934 Item::invalidate_lut () const
941 Item::child_changed ()
944 _bounding_box_dirty = true;
947 _parent->child_changed ();
952 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
954 boost::optional<Rect> const bbox = bounding_box ();
956 /* Point is in window coordinate system */
958 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
962 /* recurse and add any items within our group that contain point.
963 Our children are only considered visible if we are, and similarly
964 only if we do not ignore events.
967 vector<Item*> our_items;
969 if (!_items.empty() && visible() && !_ignore_events) {
971 our_items = _lut->items_at_point (point);
974 if (!our_items.empty() || covers (point)) {
975 /* this adds this item itself to the list of items at point */
976 items.push_back (this);
979 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
980 (*i)->add_items_at_point (point, items);
985 Item::dump (ostream& o) const
987 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
989 o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
990 o << " @ " << position();
999 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1000 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1007 if (!_items.empty()) {
1010 o << _canvas->indent();
1011 o << " @ " << position();
1012 o << " Items: " << _items.size();
1013 o << " Visible ? " << _visible;
1015 boost::optional<Rect> bb = bounding_box();
1018 o << endl << _canvas->indent() << " bbox: " << bb.get();
1019 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
1027 ArdourCanvas::dump_depth++;
1029 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1033 ArdourCanvas::dump_depth--;
1038 ArdourCanvas::operator<< (ostream& o, const Item& i)