e58411f17ff327b523438dad3fd2564ecd7c4003
[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/item.h"
29 #include "canvas/scroll_group.h"
30
31 using namespace std;
32 using namespace PBD;
33 using namespace ArdourCanvas;
34
35 int Item::default_items_per_cell = 64;
36
37 Item::Item (Canvas* canvas)
38         : Fill (*this)
39         , Outline (*this)
40         ,  _canvas (canvas)
41         , _parent (0)
42         , _scroll_parent (0)
43         , _visible (true)
44         , _bounding_box_dirty (true)
45         , _lut (0)
46         , _ignore_events (false)
47 {
48         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
49 }       
50
51 Item::Item (Item* parent)
52         : Fill (*this)
53         , Outline (*this)
54         ,  _canvas (parent->canvas())
55         , _parent (parent)
56         , _scroll_parent (0)
57         , _visible (true)
58         , _bounding_box_dirty (true)
59         , _lut (0)
60         , _ignore_events (false)
61 {
62         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
63
64         if (parent) {
65                 _parent->add (this);
66         }
67
68         find_scroll_parent ();
69 }       
70
71 Item::Item (Item* parent, Duple const& p)
72         : Fill (*this)
73         , Outline (*this)
74         ,  _canvas (parent->canvas())
75         , _parent (parent)
76         , _scroll_parent (0)
77         , _position (p)
78         , _visible (true)
79         , _bounding_box_dirty (true)
80         , _lut (0)
81         , _ignore_events (false)
82 {
83         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
84
85         if (parent) {
86                 _parent->add (this);
87         }
88
89         find_scroll_parent ();
90
91 }       
92
93 Item::~Item ()
94 {
95         if (_parent) {
96                 _parent->remove (this);
97         }
98
99         if (_canvas) {
100                 _canvas->item_going_away (this, _bounding_box);
101         }
102
103         clear_items (true);
104         delete _lut;
105 }
106
107 Duple
108 Item::canvas_origin () const
109 {
110         return item_to_canvas (Duple (0,0));
111 }
112
113 Duple
114 Item::window_origin () const 
115 {
116         /* This is slightly subtle. Our _position is in the coordinate space of 
117            our parent. So to find out where that is in window coordinates, we
118            have to ask our parent.
119         */
120         if (_parent) {
121                 return _parent->item_to_window (_position);
122         } else {
123                 return _position;
124         }
125 }
126
127 ArdourCanvas::Rect
128 Item::item_to_parent (ArdourCanvas::Rect const & r) const
129 {
130         return r.translate (_position);
131 }
132
133 Duple
134 Item::scroll_offset () const
135 {
136         if (_scroll_parent) {
137                 return _scroll_parent->scroll_offset();
138         } 
139         return Duple (0,0);
140 }
141
142 Duple
143 Item::position_offset() const
144 {
145         Item const * i = this;
146         Duple offset;
147
148         while (i) {
149                 offset = offset.translate (i->position());
150                 i = i->parent();
151         }
152
153         return offset;
154 }
155
156 ArdourCanvas::Rect
157 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
158 {
159         return r.translate (position_offset());
160 }
161
162 ArdourCanvas::Duple
163 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
164 {
165         return d.translate (position_offset());
166 }
167
168 ArdourCanvas::Duple
169 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
170 {
171         return r.translate (-position_offset());
172 }
173
174 ArdourCanvas::Rect
175 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
176 {
177         return r.translate (-position_offset());
178 }
179
180 void
181 Item::item_to_canvas (Coord& x, Coord& y) const
182 {
183         Duple d = item_to_canvas (Duple (x, y));
184                 
185         x = d.x;
186         y = d.y;
187 }
188
189 void
190 Item::canvas_to_item (Coord& x, Coord& y) const
191 {
192         Duple d = canvas_to_item (Duple (x, y));
193
194         x = d.x;
195         y = d.y;
196 }
197
198
199 Duple
200 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
201 {
202         Duple ret = item_to_canvas (d).translate (-scroll_offset());
203
204         if (rounded) {
205                 ret.x = round (ret.x);
206                 ret.y = round (ret.y);
207         }
208
209         return ret;
210 }
211
212 Duple
213 Item::window_to_item (ArdourCanvas::Duple const & d) const
214 {
215         return canvas_to_item (d.translate (scroll_offset()));
216 }
217
218 ArdourCanvas::Rect
219 Item::item_to_window (ArdourCanvas::Rect const & r) const
220 {
221         Rect ret = item_to_canvas (r).translate (-scroll_offset());
222
223         ret.x0 = round (ret.x0);
224         ret.x1 = round (ret.x1);
225         ret.y0 = round (ret.y0);
226         ret.y1 = round (ret.y1);
227
228         return ret;
229 }
230
231 ArdourCanvas::Rect
232 Item::window_to_item (ArdourCanvas::Rect const & r) const
233 {
234         return canvas_to_item (r.translate (scroll_offset()));
235 }
236
237 /** Set the position of this item in the parent's coordinates */
238 void
239 Item::set_position (Duple p)
240 {
241         if (p == _position) {
242                 return;
243         }
244
245         boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
246         boost::optional<ArdourCanvas::Rect> pre_change_parent_bounding_box;
247
248         if (bbox) {
249                 /* see the comment in Canvas::item_moved() to understand
250                  * why we use the parent's bounding box here.
251                  */
252                 pre_change_parent_bounding_box = item_to_parent (bbox.get());
253         }
254         
255         _position = p;
256
257         _canvas->item_moved (this, pre_change_parent_bounding_box);
258
259         if (_parent) {
260                 _parent->child_changed ();
261         }
262 }
263
264 void
265 Item::set_x_position (Coord x)
266 {
267         set_position (Duple (x, _position.y));
268 }
269
270 void
271 Item::set_y_position (Coord y)
272 {
273         set_position (Duple (_position.x, y));
274 }
275
276 void
277 Item::raise_to_top ()
278 {
279         if (_parent) {
280                 _parent->raise_child_to_top (this);
281         }
282 }
283
284 void
285 Item::raise (int levels)
286 {
287         if (_parent) {
288                 _parent->raise_child (this, levels);
289         }
290 }
291
292 void
293 Item::lower_to_bottom ()
294 {
295         if (_parent) {
296                 _parent->lower_child_to_bottom (this);
297         }
298 }
299
300 void
301 Item::hide ()
302 {
303         if (_visible) {
304                 _visible = false;
305
306                 /* recompute parent bounding box, which may alter now that this
307                  * child is hidden.
308                  */
309
310                 if (_parent) {
311                         _parent->child_changed ();
312                 }
313
314                 _canvas->item_shown_or_hidden (this);
315         }
316 }
317
318 void
319 Item::show ()
320 {
321         if (!_visible) {
322                 _visible = true;
323
324                 /* bounding box may have changed while we were hidden */
325
326                 if (_parent) {
327                         _parent->child_changed ();
328                 }
329
330                 _canvas->item_shown_or_hidden (this);
331         }
332 }
333
334 Duple
335 Item::item_to_parent (Duple const & d) const
336 {
337         return d.translate (_position);
338 }
339
340 Duple
341 Item::parent_to_item (Duple const & d) const
342 {
343         return d.translate (- _position);
344 }
345
346 ArdourCanvas::Rect
347 Item::parent_to_item (ArdourCanvas::Rect const & d) const
348 {
349         return d.translate (- _position);
350 }
351
352 void
353 Item::unparent ()
354 {
355         _parent = 0;
356         _scroll_parent = 0;
357 }
358
359 void
360 Item::reparent (Item* new_parent)
361 {
362         if (new_parent == _parent) {
363                 return;
364         }
365
366         assert (_canvas == new_parent->canvas());
367
368         if (_parent) {
369                 _parent->remove (this);
370         }
371
372         assert (new_parent);
373
374         _parent = new_parent;
375         _canvas = _parent->canvas ();
376
377         find_scroll_parent ();
378
379         _parent->add (this);
380 }
381
382 void
383 Item::find_scroll_parent ()
384 {
385         Item const * i = this;
386         ScrollGroup const * last_scroll_group = 0;
387
388         /* Don't allow a scroll group to find itself as its own scroll parent
389          */
390
391         i = i->parent ();
392
393         while (i) {
394                 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
395                 if (sg) {
396                         last_scroll_group = sg;
397                 }
398                 i = i->parent();
399         }
400         
401         _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
402 }
403
404 bool
405 Item::common_ancestor_within (uint32_t limit, const Item& other) const
406 {
407         uint32_t d1 = depth();
408         uint32_t d2 = other.depth();
409         const Item* i1 = this;
410         const Item* i2 = &other;
411         
412         /* move towards root until we are at the same level
413            for both items
414         */
415
416         while (d1 != d2) {
417                 if (d1 > d2) {
418                         if (!i1) {
419                                 return false;
420                         }
421                         i1 = i1->parent();
422                         d1--;
423                         limit--;
424                 } else {
425                         if (!i2) {
426                                 return false;
427                         }
428                         i2 = i2->parent();
429                         d2--;
430                         limit--;
431                 }
432                 if (limit == 0) {
433                         return false;
434                 }
435         }
436
437         /* now see if there is a common parent */
438
439         while (i1 != i2) {
440                 if (i1) {
441                         i1 = i1->parent();
442                 }
443                 if (i2) {
444                         i2 = i2->parent ();
445                 }
446
447                 limit--;
448                 if (limit == 0) {
449                         return false;
450                 }
451         }
452         
453         return true;
454 }
455
456 const Item*
457 Item::closest_ancestor_with (const Item& other) const
458 {
459         uint32_t d1 = depth();
460         uint32_t d2 = other.depth();
461         const Item* i1 = this;
462         const Item* i2 = &other;
463
464         /* move towards root until we are at the same level
465            for both items
466         */
467
468         while (d1 != d2) {
469                 if (d1 > d2) {
470                         if (!i1) {
471                                 return 0;
472                         }
473                         i1 = i1->parent();
474                         d1--;
475                 } else {
476                         if (!i2) {
477                                 return 0;
478                         }
479                         i2 = i2->parent();
480                         d2--;
481                 }
482         }
483
484         /* now see if there is a common parent */
485
486         while (i1 != i2) {
487                 if (i1) {
488                         i1 = i1->parent();
489                 }
490                 if (i2) {
491                         i2 = i2->parent ();
492                 }
493         }
494         
495         return i1;
496 }
497
498 bool
499 Item::is_descendant_of (const Item& candidate) const
500 {
501         Item const * i = _parent;
502
503         while (i) {
504                 if (i == &candidate) {
505                         return true;
506                 }
507                 i = i->parent();
508         }
509
510         return false;
511 }
512
513 void
514 Item::grab_focus ()
515 {
516         /* XXX */
517 }
518
519 /** @return Bounding box in this item's coordinates */
520 boost::optional<ArdourCanvas::Rect>
521 Item::bounding_box () const
522 {
523         if (_bounding_box_dirty) {
524                 compute_bounding_box ();
525                 assert (!_bounding_box_dirty);
526                 add_child_bounding_boxes ();
527         }
528
529         return _bounding_box;
530 }
531
532 Coord
533 Item::height () const 
534 {
535         boost::optional<ArdourCanvas::Rect> bb  = bounding_box();
536
537         if (bb) {
538                 return bb->height ();
539         }
540         return 0;
541 }
542
543 Coord
544 Item::width () const 
545 {
546         boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
547
548         if (bb) {
549                 return bb->width ();
550         }
551
552         return 0;
553 }
554
555 void
556 Item::redraw () const
557 {
558         if (_visible && _bounding_box && _canvas) {
559                 _canvas->request_redraw (item_to_window (_bounding_box.get()));
560         }
561 }       
562
563 void
564 Item::begin_change ()
565 {
566         _pre_change_bounding_box = bounding_box ();
567 }
568
569 void
570 Item::end_change ()
571 {
572         if (_visible) {
573                 _canvas->item_changed (this, _pre_change_bounding_box);
574                 
575                 if (_parent) {
576                         _parent->child_changed ();
577                 }
578         }
579 }
580
581 void
582 Item::begin_visual_change ()
583 {
584 }
585
586 void
587 Item::end_visual_change ()
588 {
589         if (_visible) {
590                 _canvas->item_visual_property_changed (this);
591         }
592 }
593
594 void
595 Item::move (Duple movement)
596 {
597         set_position (position() + movement);
598 }
599
600 void
601 Item::grab ()
602 {
603         assert (_canvas);
604         _canvas->grab (this);
605 }
606
607 void
608 Item::ungrab ()
609 {
610         assert (_canvas);
611         _canvas->ungrab ();
612 }
613
614 void
615 Item::set_data (string const & key, void* data)
616 {
617         _data[key] = data;
618 }
619
620 void *
621 Item::get_data (string const & key) const
622 {
623         map<string, void*>::const_iterator i = _data.find (key);
624         if (i == _data.end ()) {
625                 return 0;
626         }
627         
628         return i->second;
629 }
630
631 void
632 Item::set_ignore_events (bool ignore)
633 {
634         _ignore_events = ignore;
635 }
636
637 std::string
638 Item::whatami () const 
639 {
640         std::string type = demangle (typeid (*this).name());
641         return type.substr (type.find_last_of (':') + 1);
642 }
643
644 uint32_t
645 Item::depth () const
646 {
647         Item* i = _parent;
648         int d = 0;
649         while (i) {
650                 ++d;
651                 i = i->parent();
652         }
653         return d;
654 }
655
656 bool
657 Item::covers (Duple const & point) const
658 {
659         Duple p = window_to_item (point);
660
661         if (_bounding_box_dirty) {
662                 compute_bounding_box ();
663         }
664
665         boost::optional<Rect> r = bounding_box();
666
667         if (!r) {
668                 return false;
669         }
670
671         return r.get().contains (p);
672 }
673
674 /* nesting/grouping API */
675
676 void
677 Item::add_child_bounding_boxes() const
678 {
679         boost::optional<Rect> self;
680         Rect bbox;
681         bool have_one = false;
682
683         if (_bounding_box) {
684                 bbox = _bounding_box.get();
685                 have_one = true;
686         }
687
688         for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
689
690                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
691
692                 if (!item_bbox) {
693                         continue;
694                 }
695
696                 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
697                 if (have_one) {
698                         bbox = bbox.extend (group_bbox);
699                 } else {
700                         bbox = group_bbox;
701                         have_one = true;
702                 }
703         }
704
705         if (!have_one) {
706                 _bounding_box = boost::optional<Rect> ();
707         } else {
708                 _bounding_box = bbox;
709         }
710 }
711
712 void
713 Item::add (Item* i)
714 {
715         /* XXX should really notify canvas about this */
716
717         _items.push_back (i);
718         i->reparent (this);
719         invalidate_lut ();
720         _bounding_box_dirty = true;
721 }
722
723 void
724 Item::remove (Item* i)
725 {
726
727         if (i->parent() != this) {
728                 return;
729         }
730
731         /* we cannot call bounding_box() here because that will iterate over
732            _items, one of which (the argument, i) may be in the middle of
733            deletion, making it impossible to call compute_bounding_box()
734            on it.
735         */
736
737         if (_bounding_box) {
738                 _pre_change_bounding_box = _bounding_box;
739         } else {
740                 _pre_change_bounding_box = Rect();
741         }
742
743         i->unparent ();
744         _items.remove (i);
745         invalidate_lut ();
746         _bounding_box_dirty = true;
747         
748         end_change ();
749 }
750
751 void
752 Item::clear (bool with_delete)
753 {
754         begin_change ();
755
756         clear_items (with_delete);
757
758         invalidate_lut ();
759         _bounding_box_dirty = true;
760
761         end_change ();
762 }
763
764 void
765 Item::clear_items (bool with_delete)
766 {
767         for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
768
769                 list<Item*>::iterator tmp = i;
770                 Item *item = *i;
771
772                 ++tmp;
773
774                 /* remove from list before doing anything else, because we
775                  * don't want to find the item in _items during any activity
776                  * driven by unparent-ing or deletion.
777                  */
778
779                 _items.erase (i);
780                 item->unparent ();
781                 
782                 if (with_delete) {
783                         delete item;
784                 }
785
786                 i = tmp;
787         }
788 }
789
790 void
791 Item::raise_child_to_top (Item* i)
792 {
793         if (!_items.empty()) {
794                 if (_items.back() == i) {
795                         return;
796                 }
797         }
798
799         _items.remove (i);
800         _items.push_back (i);
801         invalidate_lut ();
802 }
803
804 void
805 Item::raise_child (Item* i, int levels)
806 {
807         list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
808         assert (j != _items.end ());
809
810         ++j;
811         _items.remove (i);
812
813         while (levels > 0 && j != _items.end ()) {
814                 ++j;
815                 --levels;
816         }
817
818         _items.insert (j, i);
819         invalidate_lut ();
820 }
821
822 void
823 Item::lower_child_to_bottom (Item* i)
824 {
825         if (!_items.empty()) {
826                 if (_items.front() == i) {
827                         return;
828                 }
829         }
830         _items.remove (i);
831         _items.push_front (i);
832         invalidate_lut ();
833 }
834
835 void
836 Item::ensure_lut () const
837 {
838         if (!_lut) {
839                 _lut = new DumbLookupTable (*this);
840         }
841 }
842
843 void
844 Item::invalidate_lut () const
845 {
846         delete _lut;
847         _lut = 0;
848 }
849
850 void
851 Item::child_changed ()
852 {
853         invalidate_lut ();
854         _bounding_box_dirty = true;
855
856         if (_parent) {
857                 _parent->child_changed ();
858         }
859 }
860
861 void
862 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
863 {
864         boost::optional<Rect> const bbox = bounding_box ();
865
866         /* Point is in window coordinate system */
867
868         if (!bbox || !item_to_window (bbox.get()).contains (point)) {
869                 return;
870         }
871
872         /* recurse and add any items within our group that contain point */
873
874         vector<Item*> our_items;
875
876         if (!_items.empty()) {
877                 ensure_lut ();
878                 our_items = _lut->items_at_point (point);
879         }
880
881         if (!our_items.empty() || covers (point)) {
882                 /* this adds this item itself to the list of items at point */
883                 items.push_back (this);
884         }
885
886         for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
887                 (*i)->add_items_at_point (point, items);
888         }
889 }
890
891 void
892 Item::dump (ostream& o) const
893 {
894         boost::optional<ArdourCanvas::Rect> bb = bounding_box();
895
896         o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
897         o << " @ " << position();
898         
899 #ifdef CANVAS_DEBUG
900         if (!name.empty()) {
901                 o << ' ' << name;
902         }
903 #endif
904
905         if (bb) {
906                 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
907                 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
908         } else {
909                 o << " bbox unset";
910         }
911
912         o << endl;
913
914         if (!_items.empty()) {
915
916 #ifdef CANVAS_DEBUG
917                 o << _canvas->indent();
918                 o << " @ " << position();
919                 o << " Items: " << _items.size();
920                 o << " Visible ? " << _visible;
921                 
922                 boost::optional<Rect> bb = bounding_box();
923                 
924                 if (bb) {
925                         o << endl << _canvas->indent() << "  bbox: " << bb.get();
926                         o << endl << _canvas->indent() << "  CANVAS bbox: " << item_to_canvas (bb.get());
927                 } else {
928                         o << "  bbox unset";
929                 }
930                 
931                 o << endl;
932 #endif
933                 
934                 ArdourCanvas::dump_depth++;
935                 
936                 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
937                         o << **i;
938                 }
939                 
940                 ArdourCanvas::dump_depth--;
941         }
942 }
943
944 ostream&
945 ArdourCanvas::operator<< (ostream& o, const Item& i)
946 {
947         i.dump (o);
948         return o;
949 }
950