263fe29b7bc764897c79d2c27a481698ea02e3da
[ardour.git] / gtk2_ardour / automation_line.cc
1 /*
2     Copyright (C) 2002-2003 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
24
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
28
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
32
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
40 #include "utils.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_time_axis.h"
45 #include "public_editor.h"
46
47 #include "ardour/event_type_map.h"
48 #include "ardour/session.h"
49
50 #include "i18n.h"
51
52 using namespace std;
53 using namespace ARDOUR;
54 using namespace PBD;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
57
58 /** @param converter A TimeConverter whose origin_b is the start time of the AutomationList in session frames.
59  *  This will not be deleted by AutomationLine.
60  */
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62                 boost::shared_ptr<AutomationList> al,
63                 Evoral::TimeConverter<double, framepos_t>* converter)
64         : trackview (tv)
65         , _name (name)
66         , alist (al)
67         , _time_converter (converter ? converter : new Evoral::IdentityConverter<double, framepos_t>)
68         , _parent_group (parent)
69         , _offset (0)
70         , _maximum_time (max_framepos)
71 {
72         if (converter) {
73                 _time_converter = converter;
74                 _our_time_converter = false;
75         } else {
76                 _time_converter = new Evoral::IdentityConverter<double, framepos_t>;
77                 _our_time_converter = true;
78         }
79         
80         points_visible = false;
81         update_pending = false;
82         _uses_gain_mapping = false;
83         no_draw = false;
84         _visible = true;
85         _is_boolean = false;
86         terminal_points_can_slide = true;
87         _height = 0;
88
89         group = new ArdourCanvas::Group (parent);
90         group->property_x() = 0.0;
91         group->property_y() = 0.0;
92
93         line = new ArdourCanvas::Line (*group);
94         line->property_width_pixels() = (guint)1;
95         line->set_data ("line", this);
96
97         line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
98
99         trackview.session()->register_with_memento_command_factory(alist->id(), this);
100
101         if (alist->parameter().type() == GainAutomation ||
102             alist->parameter().type() == EnvelopeAutomation) {
103                 set_uses_gain_mapping (true);
104         }
105
106         interpolation_changed (alist->interpolation ());
107
108         connect_to_list ();
109 }
110
111 AutomationLine::~AutomationLine ()
112 {
113         vector_delete (&control_points);
114         delete group;
115
116         if (_our_time_converter) {
117                 delete _time_converter;
118         }
119 }
120
121 bool
122 AutomationLine::event_handler (GdkEvent* event)
123 {
124         return PublicEditor::instance().canvas_line_event (event, line, this);
125 }
126
127 void
128 AutomationLine::queue_reset ()
129 {
130         if (!update_pending) {
131                 update_pending = true;
132                 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
133         }
134 }
135
136 void
137 AutomationLine::show ()
138 {
139         if (alist->interpolation() != AutomationList::Discrete) {
140                 line->show();
141         }
142
143         if (points_visible) {
144                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
145                         (*i)->show ();
146                 }
147         }
148
149         _visible = true;
150 }
151
152 void
153 AutomationLine::hide ()
154 {
155         line->hide();
156         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
157                 (*i)->hide();
158         }
159         _visible = false;
160 }
161
162 double
163 AutomationLine::control_point_box_size ()
164 {
165         if (alist->interpolation() == AutomationList::Discrete) {
166                 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
167                                 4.0);
168         }
169
170         if (_height > TimeAxisView::preset_height (HeightLarger)) {
171                 return 8.0;
172         } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
173                 return 6.0;
174         }
175         return 4.0;
176 }
177
178 void
179 AutomationLine::set_height (guint32 h)
180 {
181         if (h != _height) {
182                 _height = h;
183
184                 double bsz = control_point_box_size();
185
186                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
187                         (*i)->set_size (bsz);
188                 }
189
190                 reset ();
191         }
192 }
193
194 void
195 AutomationLine::set_line_color (uint32_t color)
196 {
197         _line_color = color;
198         line->property_fill_color_rgba() = color;
199 }
200
201 void
202 AutomationLine::set_uses_gain_mapping (bool yn)
203 {
204         if (yn != _uses_gain_mapping) {
205                 _uses_gain_mapping = yn;
206                 reset ();
207         }
208 }
209
210 ControlPoint*
211 AutomationLine::nth (uint32_t n)
212 {
213         if (n < control_points.size()) {
214                 return control_points[n];
215         } else {
216                 return 0;
217         }
218 }
219
220 ControlPoint const *
221 AutomationLine::nth (uint32_t n) const
222 {
223         if (n < control_points.size()) {
224                 return control_points[n];
225         } else {
226                 return 0;
227         }
228 }
229
230 void
231 AutomationLine::modify_point_y (ControlPoint& cp, double y)
232 {
233         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
234            and needs to be converted to a canvas unit distance.
235         */
236
237         y = max (0.0, y);
238         y = min (1.0, y);
239         y = _height - (y * _height);
240
241         double const x = trackview.editor().frame_to_unit (_time_converter->to((*cp.model())->when) - _offset);
242
243         trackview.editor().session()->begin_reversible_command (_("automation event move"));
244         trackview.editor().session()->add_command (
245                 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
246                 );
247
248         cp.move_to (x, y, ControlPoint::Full);
249
250         reset_line_coords (cp);
251
252         if (line_points.size() > 1) {
253                 line->property_points() = line_points;
254         }
255
256         alist->freeze ();
257         sync_model_with_view_point (cp, false, 0);
258         alist->thaw ();
259
260         update_pending = false;
261
262         trackview.editor().session()->add_command (
263                 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
264                 );
265
266         trackview.editor().session()->commit_reversible_command ();
267         trackview.editor().session()->set_dirty ();
268 }
269
270 void
271 AutomationLine::reset_line_coords (ControlPoint& cp)
272 {
273         if (cp.view_index() < line_points.size()) {
274                 line_points[cp.view_index()].set_x (cp.get_x());
275                 line_points[cp.view_index()].set_y (cp.get_y());
276         }
277 }
278
279 void
280 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
281 {
282         update_pending = true;
283
284         for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
285                 sync_model_with_view_point (**i, did_push, distance);
286         }
287 }
288
289 string
290 AutomationLine::get_verbose_cursor_string (double fraction) const
291 {
292         std::string s = fraction_to_string (fraction);
293         if (_uses_gain_mapping) {
294                 s += " dB";
295         }
296
297         return s;
298 }
299
300 /**
301  *  @param fraction y fraction
302  *  @return string representation of this value, using dB if appropriate.
303  */
304 string
305 AutomationLine::fraction_to_string (double fraction) const
306 {
307         char buf[32];
308
309         if (_uses_gain_mapping) {
310                 if (fraction == 0.0) {
311                         snprintf (buf, sizeof (buf), "-inf");
312                 } else {
313                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
314                 }
315         } else {
316                 view_to_model_coord_y (fraction);
317                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
318                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
319                 } else {
320                         snprintf (buf, sizeof (buf), "%.2f", fraction);
321                 }
322         }
323
324         return buf;
325 }
326
327
328 /**
329  *  @param s Value string in the form as returned by fraction_to_string.
330  *  @return Corresponding y fraction.
331  */
332 double
333 AutomationLine::string_to_fraction (string const & s) const
334 {
335         if (s == "-inf") {
336                 return 0;
337         }
338
339         double v;
340         sscanf (s.c_str(), "%lf", &v);
341
342         if (_uses_gain_mapping) {
343                 v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain());
344         } else {
345                 double dummy = 0.0;
346                 model_to_view_coord (dummy, v);
347         }
348
349         return v;
350 }
351
352 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
353  *  are other selected points.
354  *
355  *  @param cp Point to drag.
356  *  @param x Initial x position (units).
357  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
358  */
359 void
360 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
361 {
362         trackview.editor().session()->begin_reversible_command (_("automation event move"));
363         trackview.editor().session()->add_command (
364                 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
365                 );
366
367         _drag_points.clear ();
368         _drag_points.push_back (cp);
369
370         if (cp->get_selected ()) {
371                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
372                         if (*i != cp && (*i)->get_selected()) {
373                                 _drag_points.push_back (*i);
374                         }
375                 }
376         }
377
378         start_drag_common (x, fraction);
379 }
380
381 /** Start dragging a line vertically (with no change in x)
382  *  @param i1 Control point index of the `left' point on the line.
383  *  @param i2 Control point index of the `right' point on the line.
384  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
385  */
386 void
387 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
388 {
389         trackview.editor().session()->begin_reversible_command (_("automation range move"));
390         trackview.editor().session()->add_command (
391                 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
392                 );
393
394         _drag_points.clear ();
395         for (uint32_t i = i1; i <= i2; i++) {
396                 _drag_points.push_back (nth (i));
397         }
398
399         start_drag_common (0, fraction);
400 }
401
402 /** Start dragging multiple points (with no change in x)
403  *  @param cp Points to drag.
404  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
405  */
406 void
407 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
408 {
409         trackview.editor().session()->begin_reversible_command (_("automation range move"));
410         trackview.editor().session()->add_command (
411                 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
412                 );
413
414         _drag_points = cp;
415         start_drag_common (0, fraction);
416 }
417
418
419 struct ControlPointSorter
420 {
421         bool operator() (ControlPoint const * a, ControlPoint const * b) {
422                 return a->get_x() < b->get_x();
423         }
424 };
425
426 /** Common parts of starting a drag.
427  *  @param x Starting x position in units, or 0 if x is being ignored.
428  *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
429  */
430 void
431 AutomationLine::start_drag_common (double x, float fraction)
432 {
433         _drag_x = x;
434         _drag_distance = 0;
435         _last_drag_fraction = fraction;
436         _drag_had_movement = false;
437         did_push = false;
438
439         _drag_points.sort (ControlPointSorter ());
440
441         /* find the additional points that will be dragged when the user is holding
442            the "push" modifier
443         */
444
445         uint32_t i = _drag_points.back()->view_index () + 1;
446         ControlPoint* p = 0;
447         _push_points.clear ();
448         while ((p = nth (i)) != 0 && p->can_slide()) {
449                 _push_points.push_back (p);
450                 ++i;
451         }
452 }
453
454 /** Should be called to indicate motion during a drag.
455  *  @param x New x position of the drag in units, or undefined if ignore_x == true.
456  *  @param fraction New y fraction.
457  *  @return x position and y fraction that were actually used (once clamped).
458  */
459 pair<double, float>
460 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
461 {
462         /* setup the points that are to be moved this time round */
463         list<ControlPoint*> points = _drag_points;
464         if (with_push) {
465                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
466                 points.sort (ControlPointSorter ());
467         }
468
469         double dx = ignore_x ? 0 : (x - _drag_x);
470         double dy = fraction - _last_drag_fraction;
471
472         /* find x limits */
473         ControlPoint* before = 0;
474         ControlPoint* after = 0;
475
476         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
477                 if ((*i)->get_x() < points.front()->get_x()) {
478                         before = *i;
479                 }
480                 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
481                         after = *i;
482                 }
483         }
484
485         double const before_x = before ? before->get_x() : 0;
486         double const after_x = after ? after->get_x() : DBL_MAX;
487
488         /* clamp x */
489         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
490
491                 if ((*i)->can_slide() && !ignore_x) {
492
493                         /* clamp min x */
494                         double const a = (*i)->get_x() + dx;
495                         double const b = before_x + 1;
496                         if (a < b) {
497                                 dx += b - a;
498                         }
499
500                         /* clamp max x */
501                         if (after) {
502
503                                 if (after_x - before_x < 2) {
504                                         /* after and before are very close, so just leave this alone */
505                                         dx = 0;
506                                 } else {
507                                         double const a = (*i)->get_x() + dx;
508                                         double const b = after_x - 1;
509                                         if (a > b) {
510                                                 dx -= a - b;
511                                         }
512                                 }
513                         }
514                 }
515         }
516
517         /* clamp y */
518         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
519                 double const y = ((_height - (*i)->get_y()) / _height) + dy;
520                 if (y < 0) {
521                         dy -= y;
522                 }
523                 if (y > 1) {
524                         dy -= (y - 1);
525                 }
526         }
527
528         pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
529         _drag_distance += dx;
530         _drag_x = x;
531         _last_drag_fraction = fraction;
532
533         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
534                 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
535                 reset_line_coords (**i);
536         }
537
538         if (with_push) {
539                 /* move push points, preserving their y */
540                 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
541                         (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
542                         reset_line_coords (**i);
543                 }
544         }
545
546         if (line_points.size() > 1) {
547                 line->property_points() = line_points;
548         }
549
550         _drag_had_movement = true;
551         did_push = with_push;
552
553         return clamped;
554 }
555
556 /** Should be called to indicate the end of a drag */
557 void
558 AutomationLine::end_drag ()
559 {
560         if (!_drag_had_movement) {
561                 return;
562         }
563
564         alist->freeze ();
565
566         /* set up the points that were moved this time round */
567         list<ControlPoint*> points = _drag_points;
568         if (did_push) {
569                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
570                 points.sort (ControlPointSorter ());
571         }
572
573         sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
574
575         alist->thaw ();
576
577         update_pending = false;
578
579         trackview.editor().session()->add_command (
580                 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
581                 );
582
583         trackview.editor().session()->set_dirty ();
584 }
585
586 void
587 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
588 {
589         /* find out where the visual control point is.
590            initial results are in canvas units. ask the
591            line to convert them to something relevant.
592         */
593
594         double view_x = cp.get_x();
595         double view_y = 1.0 - (cp.get_y() / _height);
596
597         /* if xval has not changed, set it directly from the model to avoid rounding errors */
598
599         if (view_x == trackview.editor().frame_to_unit (_time_converter->to ((*cp.model())->when)) - _offset) {
600                 view_x = (*cp.model())->when - _offset;
601         } else {
602                 view_x = trackview.editor().unit_to_frame (view_x);
603                 view_x = _time_converter->from (view_x + _offset);
604         }
605
606         update_pending = true;
607
608         view_to_model_coord_y (view_y);
609
610         alist->modify (cp.model(), view_x, view_y);
611
612         if (did_push) {
613
614                 /* move all points after cp by the same distance
615                 */
616
617                 alist->slide (cp.model()++, distance);
618         }
619 }
620
621 bool
622 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
623 {
624         ControlPoint *bcp = 0;
625         ControlPoint *acp = 0;
626         double unit_xval;
627
628         unit_xval = trackview.editor().frame_to_unit (xval);
629
630         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
631
632                 if ((*i)->get_x() <= unit_xval) {
633
634                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
635                                 bcp = *i;
636                                 before = bcp->view_index();
637                         }
638
639                 } else if ((*i)->get_x() > unit_xval) {
640                         acp = *i;
641                         after = acp->view_index();
642                         break;
643                 }
644         }
645
646         return bcp && acp;
647 }
648
649 bool
650 AutomationLine::is_last_point (ControlPoint& cp)
651 {
652         // If the list is not empty, and the point is the last point in the list
653
654         if (alist->empty()) {
655                 return false;
656         }
657
658         AutomationList::const_iterator i = alist->end();
659         --i;
660
661         if (cp.model() == i) {
662                 return true;
663         }
664
665         return false;
666 }
667
668 bool
669 AutomationLine::is_first_point (ControlPoint& cp)
670 {
671         // If the list is not empty, and the point is the first point in the list
672
673         if (!alist->empty() && cp.model() == alist->begin()) {
674                 return true;
675         }
676
677         return false;
678 }
679
680 // This is copied into AudioRegionGainLine
681 void
682 AutomationLine::remove_point (ControlPoint& cp)
683 {
684         trackview.editor().session()->begin_reversible_command (_("remove control point"));
685         XMLNode &before = alist->get_state();
686
687         alist->erase (cp.model());
688         
689         trackview.editor().session()->add_command(
690                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
691                 );
692
693         trackview.editor().session()->commit_reversible_command ();
694         trackview.editor().session()->set_dirty ();
695 }
696
697 /** Get selectable points within an area.
698  *  @param start Start position in session frames.
699  *  @param end End position in session frames.
700  *  @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
701  *  @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
702  *  @param result Filled in with selectable things; in this case, ControlPoints.
703  */
704 void
705 AutomationLine::get_selectables (
706         framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
707         )
708 {
709         /* convert fractions to display coordinates with 0 at the top of the track */
710         double const bot_track = (1 - topfrac) * trackview.current_height ();
711         double const top_track = (1 - botfrac) * trackview.current_height ();
712
713         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
714                 double const model_when = (*(*i)->model())->when;
715
716                 /* model_when is relative to the start of the source, so we just need to add on the origin_b here
717                    (as it is the session frame position of the start of the source)
718                 */
719                 
720                 framepos_t const session_frames_when = _time_converter->to (model_when) + _time_converter->origin_b ();
721
722                 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
723                         results.push_back (*i);
724                 }
725         }
726 }
727
728 void
729 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
730 {
731         // hmmm ....
732 }
733
734 /** Take a PointSelection and find ControlPoints that fall within it */
735 list<ControlPoint*>
736 AutomationLine::point_selection_to_control_points (PointSelection const & s)
737 {
738         list<ControlPoint*> cp;
739
740         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
741
742                 if (i->track != &trackview) {
743                         continue;
744                 }
745
746                 double const bot = (1 - i->high_fract) * trackview.current_height ();
747                 double const top = (1 - i->low_fract) * trackview.current_height ();
748
749                 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
750
751                         double const rstart = trackview.editor().frame_to_unit (_time_converter->to (i->start) - _offset);
752                         double const rend = trackview.editor().frame_to_unit (_time_converter->to (i->end) - _offset);
753
754                         if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
755                                 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
756                                         cp.push_back (*j);
757                                 }
758                         }
759                 }
760
761         }
762
763         return cp;
764 }
765
766 void
767 AutomationLine::set_selected_points (PointSelection const & points)
768 {
769         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
770                 (*i)->set_selected (false);
771         }
772
773         if (!points.empty()) {
774                 list<ControlPoint*> cp = point_selection_to_control_points (points);
775                 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
776                         (*i)->set_selected (true);
777                 }
778         }
779
780         set_colors ();
781 }
782
783 void AutomationLine::set_colors ()
784 {
785         set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
786         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
787                 (*i)->set_color ();
788         }
789 }
790
791 void
792 AutomationLine::list_changed ()
793 {
794         queue_reset ();
795 }
796
797 void
798 AutomationLine::reset_callback (const Evoral::ControlList& events)
799 {
800         uint32_t vp = 0;
801         uint32_t pi = 0;
802         uint32_t np;
803
804         if (events.empty()) {
805                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
806                         delete *i;
807                 }
808                 control_points.clear ();
809                 line->hide();
810                 return;
811         }
812
813         /* hide all existing points, and the line */
814
815         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
816                 (*i)->hide();
817         }
818
819         line->hide ();
820         np = events.size();
821
822         Evoral::ControlList& e = const_cast<Evoral::ControlList&> (events);
823         
824         for (AutomationList::iterator ai = e.begin(); ai != e.end(); ++ai, ++pi) {
825
826                 double tx = (*ai)->when;
827                 double ty = (*ai)->value;
828
829                 /* convert from model coordinates to canonical view coordinates */
830
831                 model_to_view_coord (tx, ty);
832
833                 if (isnan (tx) || isnan (ty)) {
834                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
835                                                    _name) << endmsg;
836                         continue;
837                 }
838                 
839                 if (tx >= max_framepos || tx < 0 || tx >= _maximum_time) {
840                         continue;
841                 }
842                 
843                 /* convert x-coordinate to a canvas unit coordinate (this takes
844                  * zoom and scroll into account).
845                  */
846                         
847                 tx = trackview.editor().frame_to_unit (tx);
848                 
849                 /* convert from canonical view height (0..1.0) to actual
850                  * height coordinates (using X11's top-left rooted system)
851                  */
852
853                 ty = _height - (ty * _height);
854
855                 add_visible_control_point (vp, pi, tx, ty, ai, np);
856                 vp++;
857         }
858
859         /* discard extra CP's to avoid confusing ourselves */
860
861         while (control_points.size() > vp) {
862                 ControlPoint* cp = control_points.back();
863                 control_points.pop_back ();
864                 delete cp;
865         }
866
867         if (!terminal_points_can_slide) {
868                 control_points.back()->set_can_slide(false);
869         }
870
871         if (vp > 1) {
872
873                 /* reset the line coordinates given to the CanvasLine */
874
875                 while (line_points.size() < vp) {
876                         line_points.push_back (Art::Point (0,0));
877                 }
878
879                 while (line_points.size() > vp) {
880                         line_points.pop_back ();
881                 }
882
883                 for (uint32_t n = 0; n < vp; ++n) {
884                         line_points[n].set_x (control_points[n]->get_x());
885                         line_points[n].set_y (control_points[n]->get_y());
886                 }
887
888                 line->property_points() = line_points;
889
890                 if (_visible && alist->interpolation() != AutomationList::Discrete) {
891                         line->show();
892                 }
893         }
894
895         set_selected_points (trackview.editor().get_selection().points);
896 }
897
898 void
899 AutomationLine::reset ()
900 {
901         update_pending = false;
902
903         if (no_draw) {
904                 return;
905         }
906
907         alist->apply_to_points (*this, &AutomationLine::reset_callback);
908 }
909
910 void
911 AutomationLine::clear ()
912 {
913         /* parent must create and commit command */
914         XMLNode &before = alist->get_state();
915         alist->clear();
916
917         trackview.editor().session()->add_command (
918                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
919                 );
920 }
921
922 void
923 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
924 {
925 }
926
927 void
928 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
929 {
930         alist = list;
931         queue_reset ();
932         connect_to_list ();
933 }
934
935 void
936 AutomationLine::show_all_control_points ()
937 {
938         if (_is_boolean) {
939                 // show the line but don't allow any control points
940                 return;
941         }
942
943         points_visible = true;
944
945         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
946                 if (!(*i)->visible()) {
947                         (*i)->show ();
948                         (*i)->set_visible (true);
949                 }
950         }
951 }
952
953 void
954 AutomationLine::hide_all_but_selected_control_points ()
955 {
956         if (alist->interpolation() == AutomationList::Discrete) {
957                 return;
958         }
959
960         points_visible = false;
961
962         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
963                 if (!(*i)->get_selected()) {
964                         (*i)->set_visible (false);
965                 }
966         }
967 }
968
969 void
970 AutomationLine::track_entered()
971 {
972         if (alist->interpolation() != AutomationList::Discrete) {
973                 show_all_control_points();
974         }
975 }
976
977 void
978 AutomationLine::track_exited()
979 {
980         if (alist->interpolation() != AutomationList::Discrete) {
981                 hide_all_but_selected_control_points();
982         }
983 }
984
985 XMLNode &
986 AutomationLine::get_state (void)
987 {
988         /* function as a proxy for the model */
989         return alist->get_state();
990 }
991
992 int
993 AutomationLine::set_state (const XMLNode &node, int version)
994 {
995         /* function as a proxy for the model */
996         return alist->set_state (node, version);
997 }
998
999 void
1000 AutomationLine::view_to_model_coord (double& x, double& y) const
1001 {
1002         x = _time_converter->from (x);
1003         view_to_model_coord_y (y);
1004 }
1005
1006 void
1007 AutomationLine::view_to_model_coord_y (double& y) const
1008 {
1009         /* TODO: This should be more generic ... */
1010         if (alist->parameter().type() == GainAutomation ||
1011             alist->parameter().type() == EnvelopeAutomation) {
1012                 y = slider_position_to_gain_with_max (y, Config->get_max_gain());
1013                 y = max (0.0, y);
1014                 y = min (2.0, y);
1015         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1016                    alist->parameter().type() == PanElevationAutomation ||
1017                    alist->parameter().type() == PanWidthAutomation) {
1018                 y = 1.0 - y;
1019         } else if (alist->parameter().type() == PluginAutomation) {
1020                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1021         } else {
1022                 y = rint (y * alist->parameter().max());
1023         }
1024 }
1025
1026 void
1027 AutomationLine::model_to_view_coord (double& x, double& y) const
1028 {
1029         /* TODO: This should be more generic ... */
1030         if (alist->parameter().type() == GainAutomation ||
1031             alist->parameter().type() == EnvelopeAutomation) {
1032                 y = gain_to_slider_position_with_max (y, Config->get_max_gain());
1033         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1034                    alist->parameter().type() == PanElevationAutomation ||
1035                    alist->parameter().type() == PanWidthAutomation) {
1036                 // vertical coordinate axis reversal
1037                 y = 1.0 - y;
1038         } else if (alist->parameter().type() == PluginAutomation) {
1039                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1040         } else {
1041                 y = y / (double)alist->parameter().max(); /* ... like this */
1042         }
1043
1044         x = _time_converter->to (x) - _offset;
1045 }
1046
1047 /** Called when our list has announced that its interpolation style has changed */
1048 void
1049 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1050 {
1051         if (style == AutomationList::Discrete) {
1052                 show_all_control_points();
1053                 line->hide();
1054         } else {
1055                 hide_all_but_selected_control_points();
1056                 line->show();
1057         }
1058 }
1059
1060 void
1061 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, 
1062                                            AutomationList::iterator model, uint32_t npoints)
1063 {
1064         ControlPoint::ShapeType shape;
1065
1066         if (view_index >= control_points.size()) {
1067
1068                 /* make sure we have enough control points */
1069
1070                 ControlPoint* ncp = new ControlPoint (*this);
1071                 ncp->set_size (control_point_box_size ());
1072
1073                 control_points.push_back (ncp);
1074         }
1075
1076         if (!terminal_points_can_slide) {
1077                 if (pi == 0) {
1078                         control_points[view_index]->set_can_slide (false);
1079                         if (tx == 0) {
1080                                 shape = ControlPoint::Start;
1081                         } else {
1082                                 shape = ControlPoint::Full;
1083                         }
1084                 } else if (pi == npoints - 1) {
1085                         control_points[view_index]->set_can_slide (false);
1086                         shape = ControlPoint::End;
1087                 } else {
1088                         control_points[view_index]->set_can_slide (true);
1089                         shape = ControlPoint::Full;
1090                 }
1091         } else {
1092                 control_points[view_index]->set_can_slide (true);
1093                 shape = ControlPoint::Full;
1094         }
1095
1096         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1097
1098         /* finally, control visibility */
1099
1100         if (_visible && points_visible) {
1101                 control_points[view_index]->show ();
1102                 control_points[view_index]->set_visible (true);
1103         } else {
1104                 if (!points_visible) {
1105                         control_points[view_index]->set_visible (false);
1106                 }
1107         }
1108 }
1109
1110 void
1111 AutomationLine::add_always_in_view (double x)
1112 {
1113         _always_in_view.push_back (x);
1114         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1115 }
1116
1117 void
1118 AutomationLine::clear_always_in_view ()
1119 {
1120         _always_in_view.clear ();
1121         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1122 }
1123
1124 void
1125 AutomationLine::connect_to_list ()
1126 {
1127         _list_connections.drop_connections ();
1128
1129         alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1130
1131         alist->InterpolationChanged.connect (
1132                 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1133                 );
1134 }
1135
1136 MementoCommandBinder<AutomationList>*
1137 AutomationLine::memento_command_binder ()
1138 {
1139         return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1140 }
1141
1142 /** Set the maximum time that points on this line can be at, relative
1143  *  to the start of the track or region that it is on.
1144  */
1145 void
1146 AutomationLine::set_maximum_time (framecnt_t t)
1147 {
1148         if (_maximum_time == t) {
1149                 return;
1150         }
1151
1152         _maximum_time = t;
1153         reset ();
1154 }
1155
1156
1157 /** @return min and max x positions of points that are in the list, in session frames */
1158 pair<framepos_t, framepos_t>
1159 AutomationLine::get_point_x_range () const
1160 {
1161         pair<framepos_t, framepos_t> r (max_framepos, 0);
1162
1163         for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1164                 r.first = min (r.first, _time_converter->to ((*i)->when) + _offset + _time_converter->origin_b ());
1165                 r.second = max (r.second, _time_converter->to ((*i)->when) + _offset + _time_converter->origin_b ());
1166         }
1167
1168         return r;
1169 }
1170
1171 void
1172 AutomationLine::set_offset (framepos_t off)
1173 {
1174         if (_offset == off) {
1175                 return;
1176         }
1177
1178         _offset = off;
1179         reset ();
1180 }