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.
21 #include <cairomm/context.h>
23 #include "pbd/stacktrace.h"
24 #include "pbd/compose.h"
26 #include "canvas/group.h"
27 #include "canvas/types.h"
28 #include "canvas/debug.h"
29 #include "canvas/item.h"
30 #include "canvas/canvas.h"
33 using namespace ArdourCanvas;
35 int Group::default_items_per_cell = 64;
38 Group::Group (Canvas* canvas)
44 Group::Group (Group* group)
50 Group::Group (Group* group, Duple const& p)
61 /** @param area Area to draw in window coordinates.
62 * @param context Context, set up with its origin at this group's position.
65 Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
68 vector<Item*> items = _lut->get (area);
71 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
72 cerr << string_compose ("%1GROUP %2 @ %7 render %5 @ %6 %3 items out of %4\n",
73 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this);
79 for (vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
81 if (!(*i)->visible ()) {
83 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
84 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
90 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
94 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
95 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
101 Rect item = (*i)->item_to_window (item_bbox.get());
102 boost::optional<Rect> d = item.intersection (area);
106 if (draw.width() && draw.height()) {
108 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
109 if (dynamic_cast<Group*>(*i) == 0) {
110 cerr << _canvas->render_indent() << "render "
130 (*i)->render (area, context);
137 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
138 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
139 (*i)->name, item, area);
150 Group::scroll_to (Duple const& d)
154 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
160 Group::compute_bounding_box () const
163 bool have_one = false;
165 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
167 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
173 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
175 bbox = bbox.extend (group_bbox);
183 _bounding_box = boost::optional<Rect> ();
185 _bounding_box = bbox;
188 _bounding_box_dirty = false;
194 /* XXX should really notify canvas about this */
196 _items.push_back (i);
199 _bounding_box_dirty = true;
203 Group::remove (Item* i)
206 if (i->parent() != this) {
210 /* we cannot call bounding_box() here because that will iterate over
211 _items, one of which (the argument, i) may be in the middle of
212 deletion, making it impossible to call compute_bounding_box()
217 _pre_change_bounding_box = _bounding_box;
219 _pre_change_bounding_box = Rect();
225 _bounding_box_dirty = true;
231 Group::clear (bool with_delete)
235 clear_items (with_delete);
238 _bounding_box_dirty = true;
244 Group::clear_items (bool with_delete)
246 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
248 list<Item*>::iterator tmp = i;
253 /* remove from list before doing anything else, because we
254 * don't want to find the item in _items during any activity
255 * driven by unparent-ing or deletion.
270 Group::raise_child_to_top (Item* i)
272 if (!_items.empty()) {
273 if (_items.back() == i) {
279 _items.push_back (i);
284 Group::raise_child (Item* i, int levels)
286 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
287 assert (j != _items.end ());
292 while (levels > 0 && j != _items.end ()) {
297 _items.insert (j, i);
302 Group::lower_child_to_bottom (Item* i)
304 if (!_items.empty()) {
305 if (_items.front() == i) {
310 _items.push_front (i);
315 Group::ensure_lut () const
318 _lut = new DumbLookupTable (*this);
323 Group::invalidate_lut () const
330 Group::child_changed ()
333 _bounding_box_dirty = true;
336 _parent->child_changed ();
341 Group::add_items_at_point (Duple const point, vector<Item const *>& items) const
343 boost::optional<Rect> const bbox = bounding_box ();
345 /* Point is in window coordinate system */
347 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
351 /* now recurse and add any items within our group that contain point */
354 vector<Item*> our_items = _lut->items_at_point (point);
356 if (!our_items.empty()) {
357 /* this adds this group itself to the list of items at point */
358 Item::add_items_at_point (point, items);
361 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
362 (*i)->add_items_at_point (point, items);
367 Group::dump (ostream& o) const
370 o << _canvas->indent();
371 o << "Group " << this << " [" << name << ']';
372 o << " @ " << position();
373 o << " Items: " << _items.size();
374 o << " Visible ? " << _visible;
376 boost::optional<Rect> bb = bounding_box();
379 o << endl << _canvas->indent() << " bbox: " << bb.get();
380 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
388 ArdourCanvas::dump_depth++;
390 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
394 ArdourCanvas::dump_depth--;