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