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 _tooltip = "This is a tooltip";
50 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
53 Item::Item (Item* parent)
56 , _canvas (parent->canvas())
60 , _bounding_box_dirty (true)
62 , _ignore_events (false)
64 _tooltip = "This is a tooltip";
66 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
72 find_scroll_parent ();
75 Item::Item (Item* parent, Duple const& p)
78 , _canvas (parent->canvas())
83 , _bounding_box_dirty (true)
85 , _ignore_events (false)
87 _tooltip = "This is a tooltip";
89 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
95 find_scroll_parent ();
102 _parent->remove (this);
106 _canvas->item_going_away (this, _bounding_box);
114 Item::canvas_origin () const
116 return item_to_canvas (Duple (0,0));
120 Item::window_origin () const
122 /* This is slightly subtle. Our _position is in the coordinate space of
123 our parent. So to find out where that is in window coordinates, we
124 have to ask our parent.
127 return _parent->item_to_window (_position);
134 Item::item_to_parent (ArdourCanvas::Rect const & r) const
136 return r.translate (_position);
140 Item::scroll_offset () const
142 if (_scroll_parent) {
143 return _scroll_parent->scroll_offset();
149 Item::position_offset() const
151 Item const * i = this;
155 offset = offset.translate (i->position());
163 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
165 return r.translate (position_offset());
169 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
171 return d.translate (position_offset());
175 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
177 return r.translate (-position_offset());
181 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
183 return r.translate (-position_offset());
187 Item::item_to_canvas (Coord& x, Coord& y) const
189 Duple d = item_to_canvas (Duple (x, y));
196 Item::canvas_to_item (Coord& x, Coord& y) const
198 Duple d = canvas_to_item (Duple (x, y));
206 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
208 Duple ret = item_to_canvas (d).translate (-scroll_offset());
211 ret.x = round (ret.x);
212 ret.y = round (ret.y);
219 Item::window_to_item (ArdourCanvas::Duple const & d) const
221 return canvas_to_item (d.translate (scroll_offset()));
225 Item::item_to_window (ArdourCanvas::Rect const & r) const
227 Rect ret = item_to_canvas (r).translate (-scroll_offset());
229 ret.x0 = round (ret.x0);
230 ret.x1 = round (ret.x1);
231 ret.y0 = round (ret.y0);
232 ret.y1 = round (ret.y1);
238 Item::window_to_item (ArdourCanvas::Rect const & r) const
240 return canvas_to_item (r.translate (scroll_offset()));
243 /** Set the position of this item in the parent's coordinates */
245 Item::set_position (Duple p)
247 if (p == _position) {
251 boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
252 boost::optional<ArdourCanvas::Rect> pre_change_parent_bounding_box;
255 /* see the comment in Canvas::item_moved() to understand
256 * why we use the parent's bounding box here.
258 pre_change_parent_bounding_box = item_to_parent (bbox.get());
263 _canvas->item_moved (this, pre_change_parent_bounding_box);
266 _parent->child_changed ();
271 Item::set_x_position (Coord x)
273 set_position (Duple (x, _position.y));
277 Item::set_y_position (Coord y)
279 set_position (Duple (_position.x, y));
283 Item::raise_to_top ()
286 _parent->raise_child_to_top (this);
291 Item::raise (int levels)
294 _parent->raise_child (this, levels);
299 Item::lower_to_bottom ()
302 _parent->lower_child_to_bottom (this);
312 /* recompute parent bounding box, which may alter now that this
317 _parent->child_changed ();
320 _canvas->item_shown_or_hidden (this);
330 /* bounding box may have changed while we were hidden */
333 _parent->child_changed ();
336 _canvas->item_shown_or_hidden (this);
341 Item::item_to_parent (Duple const & d) const
343 return d.translate (_position);
347 Item::parent_to_item (Duple const & d) const
349 return d.translate (- _position);
353 Item::parent_to_item (ArdourCanvas::Rect const & d) const
355 return d.translate (- _position);
366 Item::reparent (Item* new_parent)
368 if (new_parent == _parent) {
372 assert (_canvas == new_parent->canvas());
375 _parent->remove (this);
380 _parent = new_parent;
381 _canvas = _parent->canvas ();
383 find_scroll_parent ();
389 Item::find_scroll_parent ()
391 Item const * i = this;
392 ScrollGroup const * last_scroll_group = 0;
394 /* Don't allow a scroll group to find itself as its own scroll parent
400 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
402 last_scroll_group = sg;
407 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
411 Item::common_ancestor_within (uint32_t limit, const Item& other) const
413 uint32_t d1 = depth();
414 uint32_t d2 = other.depth();
415 const Item* i1 = this;
416 const Item* i2 = &other;
418 /* move towards root until we are at the same level
443 /* now see if there is a common parent */
463 Item::closest_ancestor_with (const Item& other) const
465 uint32_t d1 = depth();
466 uint32_t d2 = other.depth();
467 const Item* i1 = this;
468 const Item* i2 = &other;
470 /* move towards root until we are at the same level
490 /* now see if there is a common parent */
505 Item::is_descendant_of (const Item& candidate) const
507 Item const * i = _parent;
510 if (i == &candidate) {
525 /** @return Bounding box in this item's coordinates */
526 boost::optional<ArdourCanvas::Rect>
527 Item::bounding_box () const
529 if (_bounding_box_dirty) {
530 compute_bounding_box ();
531 assert (!_bounding_box_dirty);
532 add_child_bounding_boxes ();
535 return _bounding_box;
539 Item::height () const
541 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
544 return bb->height ();
552 boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
562 Item::redraw () const
564 if (_visible && _bounding_box && _canvas) {
565 _canvas->request_redraw (item_to_window (_bounding_box.get()));
570 Item::begin_change ()
572 _pre_change_bounding_box = bounding_box ();
579 _canvas->item_changed (this, _pre_change_bounding_box);
582 _parent->child_changed ();
588 Item::begin_visual_change ()
593 Item::end_visual_change ()
596 _canvas->item_visual_property_changed (this);
601 Item::move (Duple movement)
603 set_position (position() + movement);
610 _canvas->grab (this);
621 Item::set_data (string const & key, void* data)
627 Item::get_data (string const & key) const
629 map<string, void*>::const_iterator i = _data.find (key);
630 if (i == _data.end ()) {
638 Item::set_ignore_events (bool ignore)
640 _ignore_events = ignore;
644 Item::whatami () const
646 std::string type = demangle (typeid (*this).name());
647 return type.substr (type.find_last_of (':') + 1);
663 Item::covers (Duple const & point) const
665 Duple p = window_to_item (point);
667 if (_bounding_box_dirty) {
668 compute_bounding_box ();
671 boost::optional<Rect> r = bounding_box();
677 return r.get().contains (p);
680 /* nesting/grouping API */
683 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
685 if (_items.empty()) {
690 std::vector<Item*> items = _lut->get (area);
693 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
694 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
695 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
702 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
704 if (!(*i)->visible ()) {
706 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
707 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
713 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
717 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
718 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
724 Rect item = (*i)->item_to_window (item_bbox.get());
725 boost::optional<Rect> d = item.intersection (area);
729 if (draw.width() && draw.height()) {
731 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
732 if (dynamic_cast<Container*>(*i) == 0) {
733 cerr << _canvas->render_indent() << "render "
753 (*i)->render (area, context);
760 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
761 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
762 (*i)->name, item, area);
773 Item::add_child_bounding_boxes() const
775 boost::optional<Rect> self;
777 bool have_one = false;
780 bbox = _bounding_box.get();
784 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
786 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
792 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
794 bbox = bbox.extend (group_bbox);
802 _bounding_box = boost::optional<Rect> ();
804 _bounding_box = bbox;
811 /* XXX should really notify canvas about this */
813 _items.push_back (i);
816 _bounding_box_dirty = true;
820 Item::remove (Item* i)
823 if (i->parent() != this) {
827 /* we cannot call bounding_box() here because that will iterate over
828 _items, one of which (the argument, i) may be in the middle of
829 deletion, making it impossible to call compute_bounding_box()
834 _pre_change_bounding_box = _bounding_box;
836 _pre_change_bounding_box = Rect();
842 _bounding_box_dirty = true;
848 Item::clear (bool with_delete)
852 clear_items (with_delete);
855 _bounding_box_dirty = true;
861 Item::clear_items (bool with_delete)
863 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
865 list<Item*>::iterator tmp = i;
870 /* remove from list before doing anything else, because we
871 * don't want to find the item in _items during any activity
872 * driven by unparent-ing or deletion.
887 Item::raise_child_to_top (Item* i)
889 if (!_items.empty()) {
890 if (_items.back() == i) {
896 _items.push_back (i);
901 Item::raise_child (Item* i, int levels)
903 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
904 assert (j != _items.end ());
909 while (levels > 0 && j != _items.end ()) {
914 _items.insert (j, i);
919 Item::lower_child_to_bottom (Item* i)
921 if (!_items.empty()) {
922 if (_items.front() == i) {
927 _items.push_front (i);
932 Item::ensure_lut () const
935 _lut = new DumbLookupTable (*this);
940 Item::invalidate_lut () const
947 Item::child_changed ()
950 _bounding_box_dirty = true;
953 _parent->child_changed ();
958 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
960 boost::optional<Rect> const bbox = bounding_box ();
962 /* Point is in window coordinate system */
964 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
968 /* recurse and add any items within our group that contain point.
969 Our children are only considered visible if we are, and similarly
970 only if we do not ignore events.
973 vector<Item*> our_items;
975 if (!_items.empty() && visible() && !_ignore_events) {
977 our_items = _lut->items_at_point (point);
980 if (!our_items.empty() || covers (point)) {
981 /* this adds this item itself to the list of items at point */
982 items.push_back (this);
985 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
986 (*i)->add_items_at_point (point, items);
991 Item::set_tooltip (const std::string& s)
997 Item::start_tooltip_timeout ()
999 if (!_tooltip.empty()) {
1000 _canvas->start_tooltip_timeout (this);
1005 Item::stop_tooltip_timeout ()
1007 _canvas->stop_tooltip_timeout ();
1011 Item::dump (ostream& o) const
1013 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
1015 o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
1016 o << " @ " << position();
1019 if (!name.empty()) {
1025 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1026 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1033 if (!_items.empty()) {
1036 o << _canvas->indent();
1037 o << " @ " << position();
1038 o << " Items: " << _items.size();
1039 o << " Visible ? " << _visible;
1041 boost::optional<Rect> bb = bounding_box();
1044 o << endl << _canvas->indent() << " bbox: " << bb.get();
1045 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
1053 ArdourCanvas::dump_depth++;
1055 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1059 ArdourCanvas::dump_depth--;
1064 ArdourCanvas::operator<< (ostream& o, const Item& i)