comment correction regarding Canvas::visible_area()
[ardour.git] / libs / canvas / group.cc
1 /*
2     Copyright (C) 2011-2013 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
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.
9
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.
14
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.
18 */
19
20 #include <iostream>
21 #include <cairomm/context.h>
22
23 #include "pbd/stacktrace.h"
24 #include "pbd/compose.h"
25
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"
31
32 using namespace std;
33 using namespace ArdourCanvas;
34
35 int Group::default_items_per_cell = 64;
36
37
38 Group::Group (Canvas* canvas)
39         : Item (canvas)
40         , _lut (0)
41 {
42 }
43
44 Group::Group (Group* group)
45         : Item (group)
46         , _lut (0)
47 {
48 }
49
50 Group::Group (Group* group, Duple const& p)
51         : Item (group, p)
52         , _lut (0)
53 {
54 }
55
56 Group::~Group ()
57 {
58         clear_items (true);
59 }
60
61 /** @param area Area to draw in window coordinates.
62  *  @param context Context, set up with its origin at this group's position.
63  */
64 void
65 Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
66 {
67         ensure_lut ();
68         vector<Item*> items = _lut->get (area);
69
70 #ifdef CANVAS_DEBUG
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);
74         }
75 #endif
76
77         ++render_depth;
78                 
79         for (vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
80
81                 if (!(*i)->visible ()) {
82 #ifdef CANVAS_DEBUG
83                         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
84                                 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
85                         }
86 #endif
87                         continue;
88                 }
89                 
90                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
91
92                 if (!item_bbox) {
93 #ifdef CANVAS_DEBUG
94                         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
95                                 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
96                         }
97 #endif
98                         continue;
99                 }
100                 
101                 Rect item = (*i)->item_to_window (item_bbox.get());
102                 boost::optional<Rect> d = item.intersection (area);
103                 
104                 if (d) {
105                         Rect draw = d.get();
106                         if (draw.width() && draw.height()) {
107 #ifdef CANVAS_DEBUG
108                                 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
109                                         if (dynamic_cast<Group*>(*i) == 0) {
110                                                 cerr << _canvas->render_indent() << "render "
111                                                      << ' ' 
112                                                      << (*i)
113                                                      << ' '
114                                                      << (*i)->whatami()
115                                                      << ' '
116                                                      << (*i)->name
117                                                      << " item "
118                                                      << item_bbox.get()
119                                                      << " window = " 
120                                                      << item
121                                                      << " intersect = "
122                                                      << draw
123                                                      << " @ " 
124                                                      << _position
125                                                      << endl;
126                                         }
127                                 }
128 #endif
129
130                                 (*i)->render (area, context);
131                                 ++render_count;
132                         }
133
134                 } else {
135
136 #ifdef CANVAS_DEBUG
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);
140                         }
141 #endif
142
143                 }
144         }
145
146         --render_depth;
147 }
148
149 void
150 Group::scroll_to (Duple const& d)
151 {
152         Item::scroll_to (d);
153         
154         for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
155                 (*i)->scroll_to (d);
156         }
157 }
158
159 void
160 Group::compute_bounding_box () const
161 {
162         Rect bbox;
163         bool have_one = false;
164
165         for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
166
167                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
168
169                 if (!item_bbox) {
170                         continue;
171                 }
172
173                 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
174                 if (have_one) {
175                         bbox = bbox.extend (group_bbox);
176                 } else {
177                         bbox = group_bbox;
178                         have_one = true;
179                 }
180         }
181
182         if (!have_one) {
183                 _bounding_box = boost::optional<Rect> ();
184         } else {
185                 _bounding_box = bbox;
186         }
187
188         _bounding_box_dirty = false;
189 }
190
191 void
192 Group::add (Item* i)
193 {
194         /* XXX should really notify canvas about this */
195
196         _items.push_back (i);
197         i->reparent (this);
198         invalidate_lut ();
199         _bounding_box_dirty = true;
200 }
201
202 void
203 Group::remove (Item* i)
204 {
205
206         if (i->parent() != this) {
207                 return;
208         }
209
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()
213            on it.
214         */
215
216         if (_bounding_box) {
217                 _pre_change_bounding_box = _bounding_box;
218         } else {
219                 _pre_change_bounding_box = Rect();
220         }
221
222         i->unparent ();
223         _items.remove (i);
224         invalidate_lut ();
225         _bounding_box_dirty = true;
226         
227         end_change ();
228 }
229
230 void
231 Group::clear (bool with_delete)
232 {
233         begin_change ();
234
235         clear_items (with_delete);
236
237         invalidate_lut ();
238         _bounding_box_dirty = true;
239
240         end_change ();
241 }
242
243 void
244 Group::clear_items (bool with_delete)
245 {
246         for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
247
248                 list<Item*>::iterator tmp = i;
249                 Item *item = *i;
250
251                 ++tmp;
252
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.
256                  */
257
258                 _items.erase (i);
259                 item->unparent ();
260                 
261                 if (with_delete) {
262                         delete item;
263                 }
264
265                 i = tmp;
266         }
267 }
268
269 void
270 Group::raise_child_to_top (Item* i)
271 {
272         if (!_items.empty()) {
273                 if (_items.back() == i) {
274                         return;
275                 }
276         }
277
278         _items.remove (i);
279         _items.push_back (i);
280         invalidate_lut ();
281 }
282
283 void
284 Group::raise_child (Item* i, int levels)
285 {
286         list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
287         assert (j != _items.end ());
288
289         ++j;
290         _items.remove (i);
291
292         while (levels > 0 && j != _items.end ()) {
293                 ++j;
294                 --levels;
295         }
296
297         _items.insert (j, i);
298         invalidate_lut ();
299 }
300
301 void
302 Group::lower_child_to_bottom (Item* i)
303 {
304         if (!_items.empty()) {
305                 if (_items.front() == i) {
306                         return;
307                 }
308         }
309         _items.remove (i);
310         _items.push_front (i);
311         invalidate_lut ();
312 }
313
314 void
315 Group::ensure_lut () const
316 {
317         if (!_lut) {
318                 _lut = new DumbLookupTable (*this);
319         }
320 }
321
322 void
323 Group::invalidate_lut () const
324 {
325         delete _lut;
326         _lut = 0;
327 }
328
329 void
330 Group::child_changed ()
331 {
332         invalidate_lut ();
333         _bounding_box_dirty = true;
334
335         if (_parent) {
336                 _parent->child_changed ();
337         }
338 }
339
340 void
341 Group::add_items_at_point (Duple const point, vector<Item const *>& items) const
342 {
343         boost::optional<Rect> const bbox = bounding_box ();
344
345         /* Point is in window coordinate system */
346
347         if (!bbox || !item_to_window (bbox.get()).contains (point)) {
348                 return;
349         }
350
351         /* now recurse and add any items within our group that contain point */
352
353         ensure_lut ();
354         vector<Item*> our_items = _lut->items_at_point (point);
355
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);
359         }
360
361         for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
362                 (*i)->add_items_at_point (point, items);
363         }
364 }
365
366 void
367 Group::dump (ostream& o) const
368 {
369 #ifdef CANVAS_DEBUG
370         o << _canvas->indent();
371         o << "Group " << this << " [" << name << ']';
372         o << " @ " << position();
373         o << " Items: " << _items.size();
374         o << " Visible ? " << _visible;
375
376         boost::optional<Rect> bb = bounding_box();
377
378         if (bb) {
379                 o << endl << _canvas->indent() << "  bbox: " << bb.get();
380                 o << endl << _canvas->indent() << "  CANVAS bbox: " << item_to_canvas (bb.get());
381         } else {
382                 o << "  bbox unset";
383         }
384
385         o << endl;
386 #endif
387
388         ArdourCanvas::dump_depth++;
389
390         for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
391                 o << **i;
392         }
393
394         ArdourCanvas::dump_depth--;
395 }