different approach to independent scrolling, involving ArdourCanvas::ScrollGroup
[ardour.git] / libs / canvas / item.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 "pbd/compose.h"
21 #include "pbd/stacktrace.h"
22 #include "pbd/convert.h"
23
24 #include "ardour/utils.h"
25
26 #include "canvas/group.h"
27 #include "canvas/item.h"
28 #include "canvas/canvas.h"
29 #include "canvas/debug.h"
30
31 using namespace std;
32 using namespace PBD;
33 using namespace ArdourCanvas;
34
35 Item::Item (Canvas* canvas)
36         : _canvas (canvas)
37         , _parent (0)
38 {
39         init ();
40 }
41
42 Item::Item (Group* parent)
43         : _canvas (parent->canvas ())
44         , _parent (parent)
45 {
46         init ();
47 }
48
49 Item::Item (Group* parent, Duple position)
50         : _canvas (parent->canvas())
51         , _parent (parent)
52         , _position (position)
53 {
54         init ();
55 }
56
57 void
58 Item::init ()
59 {
60         _visible = true;
61         _bounding_box_dirty = true;
62         _ignore_events = false;
63         
64         if (_parent) {
65                 _parent->add (this);
66         }
67
68         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
69 }       
70
71 Item::~Item ()
72 {
73         if (_parent) {
74                 _parent->remove (this);
75         }
76
77         if (_canvas) {
78                 _canvas->item_going_away (this, _bounding_box);
79         }
80 }
81
82 ArdourCanvas::Rect
83 Item::item_to_parent (ArdourCanvas::Rect const & r) const
84 {
85         return r.translate (_position);
86 }
87
88 ArdourCanvas::Rect
89 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
90 {
91         Item const * i = this;
92         Duple offset;
93
94         while (i) {
95                 offset = offset.translate (i->canvas_position());
96                 i = i->parent();
97         }
98
99         return r.translate (offset);
100 }
101
102 ArdourCanvas::Duple
103 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
104 {
105         Item const * i = this;
106         Duple offset;
107
108         while (i) {
109                 offset = offset.translate (i->canvas_position());
110                 i = i->parent();
111         }
112
113         return d.translate (offset);
114 }
115
116 ArdourCanvas::Duple
117 Item::canvas_to_item (ArdourCanvas::Duple const & d) const
118 {
119         Item const * i = this;
120         Duple offset;
121
122         while (i) {
123                 offset = offset.translate (-(i->canvas_position()));
124                 i = i->parent();
125         }
126
127         return d.translate (offset);
128 }
129
130 ArdourCanvas::Rect
131 Item::canvas_to_item (ArdourCanvas::Rect const & d) const
132 {
133         Item const * i = this;
134         Duple offset;
135
136         while (i) {
137                 offset = offset.translate (-(i->canvas_position()));
138                 i = i->parent();
139         }
140
141         return d.translate (offset);
142 }
143
144 void
145 Item::item_to_canvas (Coord& x, Coord& y) const
146 {
147         Duple d = item_to_canvas (Duple (x, y));
148                 
149         x = d.x;
150         y = d.y;
151 }
152
153 void
154 Item::canvas_to_item (Coord& x, Coord& y) const
155 {
156         Duple d = canvas_to_item (Duple (x, y));
157
158         x = d.x;
159         y = d.y;
160 }
161
162 Duple
163 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
164 {
165         Item const * i = this;
166         Duple offset;
167
168         while (i) {
169                 offset = offset.translate (i->scroll_offset());
170                 i = i->parent();
171         }
172         
173         return _canvas->canvas_to_window (d.translate (offset), rounded);
174 }
175
176 Duple
177 Item::window_to_item (ArdourCanvas::Duple const & d) const
178 {
179         return canvas_to_item (_canvas->window_to_canvas (d));
180 }
181
182 ArdourCanvas::Rect
183 Item::item_to_window (ArdourCanvas::Rect const & r) const
184 {
185         return _canvas->canvas_to_window (item_to_canvas (r));
186 }
187
188 ArdourCanvas::Rect
189 Item::window_to_item (ArdourCanvas::Rect const & r) const
190 {
191         return canvas_to_item (_canvas->window_to_canvas (r));
192 }
193
194 /** Set the position of this item in the parent's coordinates */
195 void
196 Item::set_position (Duple p)
197 {
198         if (p == _position) {
199                 return;
200         }
201
202         boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
203         boost::optional<ArdourCanvas::Rect> pre_change_parent_bounding_box;
204
205         if (bbox) {
206                 /* see the comment in Canvas::item_moved() to understand
207                  * why we use the parent's bounding box here.
208                  */
209                 pre_change_parent_bounding_box = item_to_parent (bbox.get());
210         }
211         
212         _position = p;
213
214         _canvas->item_moved (this, pre_change_parent_bounding_box);
215
216         if (_parent) {
217                 _parent->child_changed ();
218         }
219 }
220
221 void
222 Item::set_x_position (Coord x)
223 {
224         set_position (Duple (x, _position.y));
225 }
226
227 void
228 Item::set_y_position (Coord y)
229 {
230         set_position (Duple (_position.x, y));
231 }
232
233 void
234 Item::raise_to_top ()
235 {
236         assert (_parent);
237         _parent->raise_child_to_top (this);
238 }
239
240 void
241 Item::raise (int levels)
242 {
243         assert (_parent);
244         _parent->raise_child (this, levels);
245 }
246
247 void
248 Item::lower_to_bottom ()
249 {
250         assert (_parent);
251         _parent->lower_child_to_bottom (this);
252 }
253
254 void
255 Item::hide ()
256 {
257         if (_visible) {
258                 _visible = false;
259                 _canvas->item_shown_or_hidden (this);
260         }
261 }
262
263 void
264 Item::show ()
265 {
266         if (!_visible) {
267                 _visible = true;
268                 _canvas->item_shown_or_hidden (this);
269         }
270 }
271
272 Duple
273 Item::item_to_parent (Duple const & d) const
274 {
275         return d.translate (_position);
276 }
277
278 Duple
279 Item::parent_to_item (Duple const & d) const
280 {
281         return d.translate (- _position);
282 }
283
284 ArdourCanvas::Rect
285 Item::parent_to_item (ArdourCanvas::Rect const & d) const
286 {
287         return d.translate (- _position);
288 }
289
290 void
291 Item::unparent ()
292 {
293         _parent = 0;
294 }
295
296 void
297 Item::reparent (Group* new_parent)
298 {
299         assert (_canvas == _parent->canvas());
300
301         if (_parent) {
302                 _parent->remove (this);
303         }
304
305         assert (new_parent);
306
307         _parent = new_parent;
308         _canvas = _parent->canvas ();
309         _parent->add (this);
310 }
311
312 bool
313 Item::common_ancestor_within (uint32_t limit, const Item& other) const
314 {
315         uint32_t d1 = depth();
316         uint32_t d2 = other.depth();
317         const Item* i1 = this;
318         const Item* i2 = &other;
319         
320         /* move towards root until we are at the same level
321            for both items
322         */
323
324         while (d1 != d2) {
325                 if (d1 > d2) {
326                         i1 = i1->parent();
327                         d1--;
328                         limit--;
329                 } else {
330                         i2 = i2->parent();
331                         d2--;
332                         limit--;
333                 }
334                 if (limit == 0) {
335                         return false;
336                 }
337         }
338
339         /* now see if there is a common parent */
340
341         while (i1 != i2) {
342                 if (i1) {
343                         i1 = i1->parent();
344                 }
345                 if (i2) {
346                         i2 = i2->parent ();
347                 }
348
349                 limit--;
350                 if (limit == 0) {
351                         return false;
352                 }
353         }
354         
355         return true;
356 }
357
358 const Item*
359 Item::closest_ancestor_with (const Item& other) const
360 {
361         uint32_t d1 = depth();
362         uint32_t d2 = other.depth();
363         const Item* i1 = this;
364         const Item* i2 = &other;
365
366         /* move towards root until we are at the same level
367            for both items
368         */
369
370         while (d1 != d2) {
371                 if (d1 > d2) {
372                         i1 = i1->parent();
373                         d1--;
374                 } else {
375                         i2 = i2->parent();
376                         d2--;
377                 }
378         }
379
380         /* now see if there is a common parent */
381
382         while (i1 != i2) {
383                 if (i1) {
384                         i1 = i1->parent();
385                 }
386                 if (i2) {
387                         i2 = i2->parent ();
388                 }
389         }
390         
391         return i1;
392 }
393
394 bool
395 Item::is_descendant_of (const Item& candidate) const
396 {
397         Item const * i = _parent;
398
399         while (i) {
400                 if (i == &candidate) {
401                         return true;
402                 }
403                 i = i->parent();
404         }
405
406         return false;
407 }
408
409 void
410 Item::grab_focus ()
411 {
412         /* XXX */
413 }
414
415 /** @return Bounding box in this item's coordinates */
416 boost::optional<ArdourCanvas::Rect>
417 Item::bounding_box () const
418 {
419         if (_bounding_box_dirty) {
420                 compute_bounding_box ();
421                 assert (!_bounding_box_dirty);
422         }
423
424         return _bounding_box;
425 }
426
427 Coord
428 Item::height () const 
429 {
430         boost::optional<ArdourCanvas::Rect> bb  = bounding_box();
431
432         if (bb) {
433                 return bb->height ();
434         }
435         return 0;
436 }
437
438 Coord
439 Item::width () const 
440 {
441         boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
442
443         if (bb) {
444                 return bb->width ();
445         }
446
447         return 0;
448 }
449
450 void
451 Item::redraw () const
452 {
453         if (_visible && _bounding_box && _canvas) {
454                 _canvas->request_redraw (item_to_canvas (_bounding_box.get()));
455         }
456 }       
457
458 void
459 Item::begin_change ()
460 {
461         _pre_change_bounding_box = bounding_box ();
462 }
463
464 void
465 Item::end_change ()
466 {
467         _canvas->item_changed (this, _pre_change_bounding_box);
468         
469         if (_parent) {
470                 _parent->child_changed ();
471         }
472 }
473
474 void
475 Item::begin_visual_change ()
476 {
477 }
478
479 void
480 Item::end_visual_change ()
481 {
482         _canvas->item_visual_property_changed (this);
483 }
484
485 void
486 Item::move (Duple movement)
487 {
488         set_position (position() + movement);
489 }
490
491 void
492 Item::grab ()
493 {
494         assert (_canvas);
495         _canvas->grab (this);
496 }
497
498 void
499 Item::ungrab ()
500 {
501         assert (_canvas);
502         _canvas->ungrab ();
503 }
504
505 void
506 Item::set_data (string const & key, void* data)
507 {
508         _data[key] = data;
509 }
510
511 void *
512 Item::get_data (string const & key) const
513 {
514         map<string, void*>::const_iterator i = _data.find (key);
515         if (i == _data.end ()) {
516                 return 0;
517         }
518         
519         return i->second;
520 }
521
522 void
523 Item::set_ignore_events (bool ignore)
524 {
525         _ignore_events = ignore;
526 }
527
528 void
529 Item::dump (ostream& o) const
530 {
531         boost::optional<ArdourCanvas::Rect> bb = bounding_box();
532
533         o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
534         o << " @ " << position();
535         
536 #ifdef CANVAS_DEBUG
537         if (!name.empty()) {
538                 o << ' ' << name;
539         }
540 #endif
541
542         if (bb) {
543                 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
544                 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
545         } else {
546                 o << " bbox unset";
547         }
548
549         o << endl;
550 }
551
552 std::string
553 Item::whatami () const 
554 {
555         std::string type = demangle (typeid (*this).name());
556         return type.substr (type.find_last_of (':') + 1);
557 }
558
559 uint32_t
560 Item::depth () const
561 {
562         Item* i = _parent;
563         int d = 0;
564         while (i) {
565                 ++d;
566                 i = i->parent();
567         }
568         return d;
569 }
570
571 bool
572 Item::covers (Duple const & point) const
573 {
574         Duple p = canvas_to_item (point);
575
576         if (_bounding_box_dirty) {
577                 compute_bounding_box ();
578         }
579
580         boost::optional<Rect> r = bounding_box();
581
582         if (!r) {
583                 return false;
584         }
585
586         return r.get().contains (p);
587 }
588
589 ostream&
590 ArdourCanvas::operator<< (ostream& o, const Item& i)
591 {
592         i.dump (o);
593         return o;
594 }
595