/* Copyright (C) 2011-2013 Paul Davis Author: Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include "pbd/stacktrace.h" #include "pbd/compose.h" #include "canvas/group.h" #include "canvas/types.h" #include "canvas/debug.h" #include "canvas/item.h" #include "canvas/canvas.h" using namespace std; using namespace ArdourCanvas; int Group::default_items_per_cell = 64; Group::Group (Canvas* canvas) : Item (canvas) , _lut (0) { } Group::Group (Group* parent) : Item (parent) , _lut (0) { } Group::Group (Group* parent, Duple position) : Item (parent, position) , _lut (0) { } Group::~Group () { clear_items (true); } /** @param area Area to draw in window coordinates. * @param context Context, set up with its origin at this group's position. */ void Group::render (Rect const & area, Cairo::RefPtr context) const { ensure_lut (); vector items = _lut->get (area); #ifdef CANVAS_DEBUG if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) { cerr << string_compose ("%1GROUP %2 @ %7 render %5 @ %6 %3 items out of %4\n", _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this); } #endif ++render_depth; for (vector::const_iterator i = items.begin(); i != items.end(); ++i) { if (!(*i)->visible ()) { #ifdef CANVAS_DEBUG if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) { cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n"; } #endif continue; } boost::optional item_bbox = (*i)->bounding_box (); if (!item_bbox) { #ifdef CANVAS_DEBUG if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) { cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n"; } #endif continue; } Rect item = (*i)->item_to_window (item_bbox.get()); boost::optional d = item.intersection (area); if (d) { Rect draw = d.get(); if (draw.width() && draw.height()) { #ifdef CANVAS_DEBUG if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) { if (dynamic_cast(*i) == 0) { cerr << _canvas->render_indent() << "render " << ' ' << (*i) << ' ' << (*i)->whatami() << ' ' << (*i)->name << " item " << item_bbox.get() << " window = " << item << " intersect = " << draw << " @ " << _position << endl; } } #endif (*i)->render (area, context); ++render_count; } } else { #ifdef CANVAS_DEBUG if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) { cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(), (*i)->name, item, area); } #endif } } --render_depth; } void Group::scroll_to (Duple const& d) { Item::scroll_to (d); for (list::iterator i = _items.begin(); i != _items.end(); ++i) { (*i)->scroll_to (d); } } void Group::compute_bounding_box () const { Rect bbox; bool have_one = false; for (list::const_iterator i = _items.begin(); i != _items.end(); ++i) { boost::optional item_bbox = (*i)->bounding_box (); if (!item_bbox) { continue; } Rect group_bbox = (*i)->item_to_parent (item_bbox.get ()); if (have_one) { bbox = bbox.extend (group_bbox); } else { bbox = group_bbox; have_one = true; } } if (!have_one) { _bounding_box = boost::optional (); } else { _bounding_box = bbox; } _bounding_box_dirty = false; } void Group::add (Item* i) { /* XXX should really notify canvas about this */ _items.push_back (i); invalidate_lut (); _bounding_box_dirty = true; } void Group::remove (Item* i) { if (i->parent() != this) { return; } /* we cannot call bounding_box() here because that will iterate over _items, one of which (the argument, i) may be in the middle of deletion, making it impossible to call compute_bounding_box() on it. */ if (_bounding_box) { _pre_change_bounding_box = _bounding_box; } else { _pre_change_bounding_box = Rect(); } i->unparent (); _items.remove (i); invalidate_lut (); _bounding_box_dirty = true; end_change (); } void Group::clear (bool with_delete) { begin_change (); clear_items (with_delete); invalidate_lut (); _bounding_box_dirty = true; end_change (); } void Group::clear_items (bool with_delete) { for (list::iterator i = _items.begin(); i != _items.end(); ) { list::iterator tmp = i; Item *item = *i; ++tmp; /* remove from list before doing anything else, because we * don't want to find the item in _items during any activity * driven by unparent-ing or deletion. */ _items.erase (i); item->unparent (); if (with_delete) { delete item; } i = tmp; } } void Group::raise_child_to_top (Item* i) { if (!_items.empty()) { if (_items.back() == i) { return; } } _items.remove (i); _items.push_back (i); invalidate_lut (); } void Group::raise_child (Item* i, int levels) { list::iterator j = find (_items.begin(), _items.end(), i); assert (j != _items.end ()); ++j; _items.remove (i); while (levels > 0 && j != _items.end ()) { ++j; --levels; } _items.insert (j, i); invalidate_lut (); } void Group::lower_child_to_bottom (Item* i) { if (!_items.empty()) { if (_items.front() == i) { return; } } _items.remove (i); _items.push_front (i); invalidate_lut (); } void Group::ensure_lut () const { if (!_lut) { _lut = new DumbLookupTable (*this); } } void Group::invalidate_lut () const { delete _lut; _lut = 0; } void Group::child_changed () { invalidate_lut (); _bounding_box_dirty = true; if (_parent) { _parent->child_changed (); } } void Group::add_items_at_point (Duple const point, vector& items) const { boost::optional const bbox = bounding_box (); /* Point is in window coordinate system */ if (!bbox || !item_to_window (bbox.get()).contains (point)) { return; } /* now recurse and add any items within our group that contain point */ ensure_lut (); vector our_items = _lut->items_at_point (point); if (!our_items.empty()) { /* this adds this group itself to the list of items at point */ Item::add_items_at_point (point, items); } for (vector::iterator i = our_items.begin(); i != our_items.end(); ++i) { (*i)->add_items_at_point (point, items); } } void Group::dump (ostream& o) const { #ifdef CANVAS_DEBUG o << _canvas->indent(); o << "Group " << this << " [" << name << ']'; o << " @ " << position(); o << " Items: " << _items.size(); o << " Visible ? " << _visible; boost::optional bb = bounding_box(); if (bb) { o << endl << _canvas->indent() << " bbox: " << bb.get(); o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get()); } else { o << " bbox unset"; } o << endl; #endif ArdourCanvas::dump_depth++; for (list::const_iterator i = _items.begin(); i != _items.end(); ++i) { o << **i; } ArdourCanvas::dump_depth--; }