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