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