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::add_child_bounding_boxes() const
679 boost::optional<Rect> self;
681 bool have_one = false;
684 bbox = _bounding_box.get();
688 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
690 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
696 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
698 bbox = bbox.extend (group_bbox);
706 _bounding_box = boost::optional<Rect> ();
708 _bounding_box = bbox;
715 /* XXX should really notify canvas about this */
717 _items.push_back (i);
720 _bounding_box_dirty = true;
724 Item::remove (Item* i)
727 if (i->parent() != this) {
731 /* we cannot call bounding_box() here because that will iterate over
732 _items, one of which (the argument, i) may be in the middle of
733 deletion, making it impossible to call compute_bounding_box()
738 _pre_change_bounding_box = _bounding_box;
740 _pre_change_bounding_box = Rect();
746 _bounding_box_dirty = true;
752 Item::clear (bool with_delete)
756 clear_items (with_delete);
759 _bounding_box_dirty = true;
765 Item::clear_items (bool with_delete)
767 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
769 list<Item*>::iterator tmp = i;
774 /* remove from list before doing anything else, because we
775 * don't want to find the item in _items during any activity
776 * driven by unparent-ing or deletion.
791 Item::raise_child_to_top (Item* i)
793 if (!_items.empty()) {
794 if (_items.back() == i) {
800 _items.push_back (i);
805 Item::raise_child (Item* i, int levels)
807 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
808 assert (j != _items.end ());
813 while (levels > 0 && j != _items.end ()) {
818 _items.insert (j, i);
823 Item::lower_child_to_bottom (Item* i)
825 if (!_items.empty()) {
826 if (_items.front() == i) {
831 _items.push_front (i);
836 Item::ensure_lut () const
839 _lut = new DumbLookupTable (*this);
844 Item::invalidate_lut () const
851 Item::child_changed ()
854 _bounding_box_dirty = true;
857 _parent->child_changed ();
862 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
864 boost::optional<Rect> const bbox = bounding_box ();
866 /* Point is in window coordinate system */
868 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
872 /* recurse and add any items within our group that contain point */
874 vector<Item*> our_items;
876 if (!_items.empty()) {
878 our_items = _lut->items_at_point (point);
881 if (!our_items.empty() || covers (point)) {
882 /* this adds this item itself to the list of items at point */
883 items.push_back (this);
886 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
887 (*i)->add_items_at_point (point, items);
892 Item::dump (ostream& o) const
894 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
896 o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
897 o << " @ " << position();
906 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
907 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
914 if (!_items.empty()) {
917 o << _canvas->indent();
918 o << " @ " << position();
919 o << " Items: " << _items.size();
920 o << " Visible ? " << _visible;
922 boost::optional<Rect> bb = bounding_box();
925 o << endl << _canvas->indent() << " bbox: " << bb.get();
926 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
934 ArdourCanvas::dump_depth++;
936 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
940 ArdourCanvas::dump_depth--;
945 ArdourCanvas::operator<< (ostream& o, const Item& i)