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