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