128cfb5148e22ee758dec9d24123456aa6a4b3f9
[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_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
47
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
50
51 #include "i18n.h"
52
53 using namespace std;
54 using namespace ARDOUR;
55 using namespace PBD;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
58
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
60
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62                 boost::shared_ptr<AutomationList> al,
63                 const Evoral::TimeConverter<double, sframes_t>* converter)
64         : trackview (tv)
65         , _name (name)
66         , alist (al)
67         , _parent_group (parent)
68         , _time_converter (converter ? (*converter) : default_converter)
69 {
70         _interpolation = al->interpolation();
71         points_visible = false;
72         update_pending = false;
73         _uses_gain_mapping = false;
74         no_draw = false;
75         _visible = true;
76         terminal_points_can_slide = true;
77         _height = 0;
78
79         group = new ArdourCanvas::Group (parent);
80         group->property_x() = 0.0;
81         group->property_y() = 0.0;
82
83         line = new ArdourCanvas::Line (*group);
84         line->property_width_pixels() = (guint)1;
85         line->set_data ("line", this);
86
87         line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
88
89         alist->StateChanged.connect (_state_connection, boost::bind (&AutomationLine::list_changed, this), gui_context());
90
91         trackview.session()->register_with_memento_command_factory(alist->id(), this);
92
93         if (alist->parameter().type() == GainAutomation ||
94             alist->parameter().type() == EnvelopeAutomation) {
95                 set_uses_gain_mapping (true);
96         }
97
98         set_interpolation(alist->interpolation());
99 }
100
101 AutomationLine::~AutomationLine ()
102 {
103         vector_delete (&control_points);
104         delete group;
105 }
106
107 bool
108 AutomationLine::event_handler (GdkEvent* event)
109 {
110         return PublicEditor::instance().canvas_line_event (event, line, this);
111 }
112
113 void
114 AutomationLine::queue_reset ()
115 {
116         if (!update_pending) {
117                 update_pending = true;
118                 Gtkmm2ext::UI::instance()->call_slot (boost::bind (&AutomationLine::reset, this));
119         }
120 }
121
122 void
123 AutomationLine::show ()
124 {
125         if (_interpolation != AutomationList::Discrete) {
126                 line->show();
127         }
128
129         if (points_visible) {
130                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
131                         (*i)->show ();
132                 }
133         }
134
135         _visible = true;
136 }
137
138 void
139 AutomationLine::hide ()
140 {
141         line->hide();
142         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
143                 (*i)->hide();
144         }
145         _visible = false;
146 }
147
148 double
149 AutomationLine::control_point_box_size ()
150 {
151         if (_interpolation == AutomationList::Discrete) {
152                 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
153                                 4.0);
154         }
155
156         if (_height > TimeAxisView::hLarger) {
157                 return 8.0;
158         } else if (_height > (guint32) TimeAxisView::hNormal) {
159                 return 6.0;
160         }
161         return 4.0;
162 }
163
164 void
165 AutomationLine::set_height (guint32 h)
166 {
167         if (h != _height) {
168                 _height = h;
169
170                 double bsz = control_point_box_size();
171
172                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
173                         (*i)->set_size (bsz);
174                 }
175
176                 reset ();
177         }
178 }
179
180 void
181 AutomationLine::set_line_color (uint32_t color)
182 {
183         _line_color = color;
184         line->property_fill_color_rgba() = color;
185 }
186
187 void
188 AutomationLine::set_uses_gain_mapping (bool yn)
189 {
190         if (yn != _uses_gain_mapping) {
191                 _uses_gain_mapping = yn;
192                 reset ();
193         }
194 }
195
196 ControlPoint*
197 AutomationLine::nth (uint32_t n)
198 {
199         if (n < control_points.size()) {
200                 return control_points[n];
201         } else {
202                 return 0;
203         }
204 }
205
206 void
207 AutomationLine::modify_point_y (ControlPoint& cp, double y)
208 {
209         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210            and needs to be converted to a canvas unit distance.
211         */
212
213         y = max (0.0, y);
214         y = min (1.0, y);
215         y = _height - (y * _height);
216
217         double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
218
219         trackview.editor().session()->begin_reversible_command (_("automation event move"));
220         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
221
222         cp.move_to (x, y, ControlPoint::Full);
223         reset_line_coords (cp);
224
225         if (line_points.size() > 1) {
226                 line->property_points() = line_points;
227         }
228
229         alist->freeze ();
230         sync_model_with_view_point (cp, false, 0);
231         alist->thaw ();
232
233         update_pending = false;
234
235         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236         trackview.editor().session()->commit_reversible_command ();
237         trackview.editor().session()->set_dirty ();
238 }
239
240
241 void
242 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
243 {
244         double delta = 0.0;
245         uint32_t last_movable = UINT_MAX;
246         double x_limit = DBL_MAX;
247
248         /* this just changes the current view. it does not alter
249            the model in any way at all.
250         */
251
252         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
253            and needs to be converted to a canvas unit distance.
254         */
255
256         y = max (0.0, y);
257         y = min (1.0, y);
258         y = _height - (y * _height);
259
260         if (cp.can_slide()) {
261
262                 /* x-coord cannot move beyond adjacent points or the start/end, and is
263                    already in frames. it needs to be converted to canvas units.
264                 */
265
266                 x = trackview.editor().frame_to_unit (x);
267
268                 /* clamp x position using view coordinates */
269
270                 ControlPoint *before;
271                 ControlPoint *after;
272
273                 if (cp.view_index()) {
274                         before = nth (cp.view_index() - 1);
275                         x = max (x, before->get_x()+1.0);
276                 } else {
277                         before = &cp;
278                 }
279
280
281                 if (!with_push) {
282                         if (cp.view_index() < control_points.size() - 1) {
283
284                                 after = nth (cp.view_index() + 1);
285
286                                 /*if it is a "spike" leave the x alone */
287
288                                 if (after->get_x() - before->get_x() < 2) {
289                                         x = cp.get_x();
290
291                                 } else {
292                                         x = min (x, after->get_x()-1.0);
293                                 }
294                         } else {
295                                 after = &cp;
296                         }
297
298                 } else {
299
300                         ControlPoint* after;
301
302                         /* find the first point that can't move */
303
304                         for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) {
305                                 if (!after->can_slide()) {
306                                         x_limit = after->get_x() - 1.0;
307                                         last_movable = after->view_index();
308                                         break;
309                                 }
310                         }
311
312                         delta = x - cp.get_x();
313                 }
314
315         } else {
316
317                 /* leave the x-coordinate alone */
318
319                 x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
320
321         }
322
323         if (!with_push) {
324
325                 cp.move_to (x, y, ControlPoint::Full);
326                 reset_line_coords (cp);
327
328         } else {
329
330                 uint32_t limit = min (control_points.size(), (size_t)last_movable);
331
332                 /* move the current point to wherever the user told it to go, subject
333                    to x_limit.
334                 */
335
336                 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
337                 reset_line_coords (cp);
338
339                 /* now move all subsequent control points, to reflect the motion.
340                  */
341
342                 for (uint32_t i = cp.view_index() + 1; i < limit; ++i) {
343                         ControlPoint *p = nth (i);
344                         double new_x;
345
346                         if (p->can_slide()) {
347                                 new_x = min (p->get_x() + delta, x_limit);
348                                 p->move_to (new_x, p->get_y(), ControlPoint::Full);
349                                 reset_line_coords (*p);
350                         }
351                 }
352         }
353 }
354
355 void
356 AutomationLine::reset_line_coords (ControlPoint& cp)
357 {
358         if (cp.view_index() < line_points.size()) {
359                 line_points[cp.view_index()].set_x (cp.get_x());
360                 line_points[cp.view_index()].set_y (cp.get_y());
361         }
362 }
363
364 void
365 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
366 {
367         ControlPoint *p;
368
369         update_pending = true;
370
371         for (uint32_t i = start; i <= end; ++i) {
372                 p = nth(i);
373                 sync_model_with_view_point (*p, false, 0);
374         }
375 }
376
377 void
378 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
379 {
380         /* part one: find out where the visual control point is.
381            initial results are in canvas units. ask the
382            line to convert them to something relevant.
383         */
384
385         mr.xval = cp.get_x();
386         mr.yval = 1.0 - (cp.get_y() / _height);
387
388         /* if xval has not changed, set it directly from the model to avoid rounding errors */
389
390         if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
391                 mr.xval = (*cp.model())->when;
392         } else {
393                 mr.xval = trackview.editor().unit_to_frame (mr.xval);
394         }
395
396         /* convert to model units
397         */
398
399         view_to_model_coord (mr.xval, mr.yval);
400
401         /* part 2: find out where the model point is now
402          */
403
404         mr.xpos = (*cp.model())->when;
405         mr.ypos = (*cp.model())->value;
406
407         /* part 3: get the position of the visual control
408            points before and after us.
409         */
410
411         ControlPoint* before;
412         ControlPoint* after;
413
414         if (cp.view_index()) {
415                 before = nth (cp.view_index() - 1);
416         } else {
417                 before = 0;
418         }
419
420         after = nth (cp.view_index() + 1);
421
422         if (before) {
423                 mr.xmin = (*before->model())->when;
424                 mr.ymin = (*before->model())->value;
425                 mr.start = before->model();
426                 ++mr.start;
427         } else {
428                 mr.xmin = mr.xpos;
429                 mr.ymin = mr.ypos;
430                 mr.start = cp.model();
431         }
432
433         if (after) {
434                 mr.end = after->model();
435         } else {
436                 mr.xmax = mr.xpos;
437                 mr.ymax = mr.ypos;
438                 mr.end = cp.model();
439                 ++mr.end;
440         }
441 }
442
443 void
444 AutomationLine::determine_visible_control_points (ALPoints& points)
445 {
446         uint32_t view_index, pi, n;
447         AutomationList::iterator model;
448         uint32_t npoints;
449         uint32_t this_rx = 0;
450         uint32_t prev_rx = 0;
451         uint32_t this_ry = 0;
452         uint32_t prev_ry = 0;
453         double* slope;
454         uint32_t box_size;
455
456         /* hide all existing points, and the line */
457
458         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
459                 (*i)->hide();
460         }
461
462         line->hide ();
463
464         if (points.empty()) {
465                 return;
466         }
467
468         npoints = points.size();
469
470         /* compute derivative/slope for the entire line */
471
472         slope = new double[npoints];
473
474         for (n = 0; n < npoints - 1; ++n) {
475                 double xdelta = points[n+1].x - points[n].x;
476                 double ydelta = points[n+1].y - points[n].y;
477                 slope[n] = ydelta/xdelta;
478         }
479
480         box_size = (uint32_t) control_point_box_size ();
481
482         /* read all points and decide which ones to show as control points */
483
484         view_index = 0;
485
486         for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
487
488                 double tx = points[pi].x;
489                 double ty = points[pi].y;
490
491                 if (isnan (tx) || isnan (ty)) {
492                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
493                                                    _name) << endmsg;
494                         continue;
495                 }
496
497                 /* now ensure that the control_points vector reflects the current curve
498                    state, but don't plot control points too close together. also, don't
499                    plot a series of points all with the same value.
500
501                    always plot the first and last points, of course.
502                 */
503
504                 if (invalid_point (points, pi)) {
505                         /* for some reason, we are supposed to ignore this point,
506                            but still keep track of the model index.
507                         */
508                         continue;
509                 }
510
511                 if (pi > 0 && pi < npoints - 1) {
512                         if (slope[pi] == slope[pi-1]) {
513
514                                 /* no reason to display this point */
515
516                                 continue;
517                         }
518                 }
519
520                 /* need to round here. the ultimate coordinates are integer
521                    pixels, so tiny deltas in the coords will be eliminated
522                    and we end up with "colinear" line segments. since the
523                    line rendering code in libart doesn't like this very
524                    much, we eliminate them here. don't do this for the first and last
525                    points.
526                 */
527
528                 this_rx = (uint32_t) rint (tx);
529                 this_ry = (uint32_t) rint (ty);
530
531                 if (view_index && pi != npoints && /* not the first, not the last */
532                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
533                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
534                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
535                         continue;
536                 }
537
538                 /* ok, we should display this point */
539
540                 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
541                 
542                 prev_rx = this_rx;
543                 prev_ry = this_ry;
544
545                 view_index++;
546         }
547
548         /* discard extra CP's to avoid confusing ourselves */
549
550         while (control_points.size() > view_index) {
551                 ControlPoint* cp = control_points.back();
552                 control_points.pop_back ();
553                 delete cp;
554         }
555
556         if (!terminal_points_can_slide) {
557                 control_points.back()->set_can_slide(false);
558         }
559
560         delete [] slope;
561
562         if (view_index > 1) {
563
564                 npoints = view_index;
565
566                 /* reset the line coordinates */
567
568                 while (line_points.size() < npoints) {
569                         line_points.push_back (Art::Point (0,0));
570                 }
571
572                 while (line_points.size() > npoints) {
573                         line_points.pop_back ();
574                 }
575
576                 for (view_index = 0; view_index < npoints; ++view_index) {
577                         line_points[view_index].set_x (control_points[view_index]->get_x());
578                         line_points[view_index].set_y (control_points[view_index]->get_y());
579                 }
580
581                 line->property_points() = line_points;
582
583                 if (_visible && _interpolation != AutomationList::Discrete) {
584                         line->show();
585                 }
586
587         }
588
589         set_selected_points (trackview.editor().get_selection().points);
590
591 }
592
593 string
594 AutomationLine::get_verbose_cursor_string (double fraction) const
595 {
596         std::string s = fraction_to_string (fraction);
597         if (_uses_gain_mapping) {
598                 s += " dB";
599         }
600
601         return s;
602 }
603
604 /**
605  *  @param fraction y fraction
606  *  @return string representation of this value, using dB if appropriate.
607  */
608 string
609 AutomationLine::fraction_to_string (double fraction) const
610 {
611         char buf[32];
612
613         if (_uses_gain_mapping) {
614                 if (fraction == 0.0) {
615                         snprintf (buf, sizeof (buf), "-inf");
616                 } else {
617                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
618                 }
619         } else {
620                 double dummy = 0.0;
621                 view_to_model_coord (dummy, fraction);
622                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
623                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
624                 } else {
625                         snprintf (buf, sizeof (buf), "%.2f", fraction);
626                 }
627         }
628
629         return buf;
630 }
631
632
633 /**
634  *  @param s Value string in the form as returned by fraction_to_string.
635  *  @return Corresponding y fraction.
636  */
637 double
638 AutomationLine::string_to_fraction (string const & s) const
639 {
640         if (s == "-inf") {
641                 return 0;
642         }
643
644         double v;
645         sscanf (s.c_str(), "%lf", &v);
646
647         if (_uses_gain_mapping) {
648                 v = gain_to_slider_position (dB_to_coefficient (v));
649         } else {
650                 double dummy = 0.0;
651                 model_to_view_coord (dummy, v);
652         }
653
654         return v;
655 }
656
657 bool
658 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
659 {
660         return p[index].x == max_frames && p[index].y == DBL_MAX;
661 }
662
663 void
664 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
665 {
666         p[index].x = max_frames;
667         p[index].y = DBL_MAX;
668 }
669
670 void
671 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
672 {
673         if (trackview.editor().session() == 0) { /* how? */
674                 return;
675         }
676
677         string str;
678
679         if (cp) {
680                 str = _("automation event move");
681         } else {
682                 str = _("automation range drag");
683         }
684
685         trackview.editor().session()->begin_reversible_command (str);
686         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
687
688         drag_x = x;
689         drag_distance = 0;
690         first_drag_fraction = fraction;
691         last_drag_fraction = fraction;
692         drags = 0;
693         did_push = false;
694 }
695
696 void
697 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
698 {
699         if (x > drag_x) {
700                 drag_distance += (x - drag_x);
701         } else {
702                 drag_distance -= (drag_x - x);
703         }
704
705         drag_x = x;
706
707         modify_view_point (cp, x, fraction, with_push);
708
709         if (line_points.size() > 1) {
710                 line->property_points() = line_points;
711         }
712
713         drags++;
714         did_push = with_push;
715 }
716
717 void
718 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
719 {
720         double ydelta = fraction - last_drag_fraction;
721
722         did_push = with_push;
723
724         last_drag_fraction = fraction;
725
726         line_drag_cp1 = i1;
727         line_drag_cp2 = i2;
728
729         //check if one of the control points on the line is in a selected range
730         bool range_found = false;
731         ControlPoint *cp;
732
733         for (uint32_t i = i1 ; i <= i2; i++) {
734                 cp = nth (i);
735                 if (cp->selected()) {
736                         range_found = true;
737                 }
738         }
739
740         if (range_found) {
741                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
742                         if ((*i)->selected()) {
743                                 modify_view_point (*(*i), trackview.editor().unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push);
744                         }
745                 }
746         } else {
747                 ControlPoint *cp;
748                 for (uint32_t i = i1 ; i <= i2; i++) {
749                         cp = nth (i);
750                         modify_view_point (*cp, trackview.editor().unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
751                 }
752         }
753
754         if (line_points.size() > 1) {
755                 line->property_points() = line_points;
756         }
757
758         drags++;
759 }
760
761 void
762 AutomationLine::end_drag (ControlPoint* cp)
763 {
764         if (!drags) {
765                 return;
766         }
767
768         alist->freeze ();
769
770         if (cp) {
771                 sync_model_with_view_point (*cp, did_push, drag_distance);
772         } else {
773                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
774         }
775
776         alist->thaw ();
777
778         update_pending = false;
779
780         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
781         trackview.editor().session()->commit_reversible_command ();
782         trackview.editor().session()->set_dirty ();
783 }
784
785
786 void
787 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
788 {
789         ModelRepresentation mr;
790         double ydelta;
791
792         model_representation (cp, mr);
793
794         /* how much are we changing the central point by */
795
796         ydelta = mr.yval - mr.ypos;
797
798         /*
799            apply the full change to the central point, and interpolate
800            on both axes to cover all model points represented
801            by the control point.
802         */
803
804         /* change all points before the primary point */
805
806         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
807
808                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
809                 double y_delta = ydelta * fract;
810                 double x_delta = distance * fract;
811
812                 /* interpolate */
813
814                 if (y_delta || x_delta) {
815                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
816                 }
817         }
818
819         /* change the primary point */
820
821         update_pending = true;
822         alist->modify (cp.model(), mr.xval, mr.yval);
823
824
825         /* change later points */
826
827         AutomationList::iterator i = cp.model();
828
829         ++i;
830
831         while (i != mr.end) {
832
833                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
834
835                 /* all later points move by the same distance along the x-axis as the main point */
836
837                 if (delta) {
838                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
839                 }
840
841                 ++i;
842         }
843
844         if (did_push) {
845
846                 /* move all points after the range represented by the view by the same distance
847                    as the main point moved.
848                 */
849
850                 alist->slide (mr.end, drag_distance);
851         }
852
853 }
854
855 bool
856 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
857 {
858         ControlPoint *bcp = 0;
859         ControlPoint *acp = 0;
860         double unit_xval;
861
862         unit_xval = trackview.editor().frame_to_unit (xval);
863
864         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
865
866                 if ((*i)->get_x() <= unit_xval) {
867
868                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
869                                 bcp = *i;
870                                 before = bcp->view_index();
871                         }
872
873                 } else if ((*i)->get_x() > unit_xval) {
874                         acp = *i;
875                         after = acp->view_index();
876                         break;
877                 }
878         }
879
880         return bcp && acp;
881 }
882
883 bool
884 AutomationLine::is_last_point (ControlPoint& cp)
885 {
886         ModelRepresentation mr;
887
888         model_representation (cp, mr);
889
890         // If the list is not empty, and the point is the last point in the list
891
892         if (!alist->empty() && mr.end == alist->end()) {
893                 return true;
894         }
895
896         return false;
897 }
898
899 bool
900 AutomationLine::is_first_point (ControlPoint& cp)
901 {
902         ModelRepresentation mr;
903
904         model_representation (cp, mr);
905
906         // If the list is not empty, and the point is the first point in the list
907
908         if (!alist->empty() && mr.start == alist->begin()) {
909                 return true;
910         }
911
912         return false;
913 }
914
915 // This is copied into AudioRegionGainLine
916 void
917 AutomationLine::remove_point (ControlPoint& cp)
918 {
919         ModelRepresentation mr;
920
921         model_representation (cp, mr);
922
923         trackview.editor().session()->begin_reversible_command (_("remove control point"));
924         XMLNode &before = alist->get_state();
925
926         alist->erase (mr.start, mr.end);
927
928         trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
929                         *alist.get(), &before, &alist->get_state()));
930         trackview.editor().session()->commit_reversible_command ();
931         trackview.editor().session()->set_dirty ();
932 }
933
934 void
935 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
936                 double botfrac, double topfrac, list<Selectable*>& results)
937 {
938
939         double top;
940         double bot;
941         double nstart;
942         double nend;
943         bool collecting = false;
944
945         /* Curse X11 and its inverted coordinate system! */
946
947         bot = (1.0 - topfrac) * _height;
948         top = (1.0 - botfrac) * _height;
949
950         nstart = max_frames;
951         nend = 0;
952
953         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
954                 double when = (*(*i)->model())->when;
955
956                 if (when >= start && when <= end) {
957
958                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
959
960                                 (*i)->show();
961                                 (*i)->set_visible(true);
962                                 collecting = true;
963                                 nstart = min (nstart, when);
964                                 nend = max (nend, when);
965
966                         } else {
967
968                                 if (collecting) {
969
970                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
971                                         collecting = false;
972                                         nstart = max_frames;
973                                         nend = 0;
974                                 }
975                         }
976                 }
977         }
978
979         if (collecting) {
980                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
981         }
982
983 }
984
985 void
986 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
987 {
988         // hmmm ....
989 }
990
991 void
992 AutomationLine::set_selected_points (PointSelection& points)
993 {
994         double top;
995         double bot;
996
997         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
998                         (*i)->set_selected(false);
999         }
1000
1001         if (points.empty()) {
1002                 goto out;
1003         }
1004
1005         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1006
1007                 if ((*r).track != &trackview) {
1008                         continue;
1009                 }
1010
1011                 /* Curse X11 and its inverted coordinate system! */
1012
1013                 bot = (1.0 - (*r).high_fract) * _height;
1014                 top = (1.0 - (*r).low_fract) * _height;
1015
1016                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1017
1018                         double rstart, rend;
1019
1020                         rstart = trackview.editor().frame_to_unit ((*r).start);
1021                         rend = trackview.editor().frame_to_unit ((*r).end);
1022
1023                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1024
1025                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1026
1027                                         (*i)->set_selected(true);
1028                                 }
1029                         }
1030
1031                 }
1032         }
1033
1034   out:
1035         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1036                 (*i)->show_color (false, !points_visible);
1037         }
1038
1039 }
1040
1041 void AutomationLine::set_colors() {
1042         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1043         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1044                 (*i)->show_color (false, !points_visible);
1045         }
1046 }
1047
1048 void
1049 AutomationLine::show_selection ()
1050 {
1051         TimeSelection& time (trackview.editor().get_selection().time);
1052
1053         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1054
1055                 (*i)->set_selected(false);
1056
1057                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1058                         double rstart, rend;
1059
1060                         rstart = trackview.editor().frame_to_unit ((*r).start);
1061                         rend = trackview.editor().frame_to_unit ((*r).end);
1062
1063                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1064                                 (*i)->set_selected(true);
1065                                 break;
1066                         }
1067                 }
1068
1069                 (*i)->show_color (false, !points_visible);
1070         }
1071 }
1072
1073 void
1074 AutomationLine::hide_selection ()
1075 {
1076 //      show_selection ();
1077 }
1078
1079 void
1080 AutomationLine::list_changed ()
1081 {
1082         queue_reset ();
1083 }
1084
1085 void
1086 AutomationLine::reset_callback (const Evoral::ControlList& events)
1087 {
1088         ALPoints tmp_points;
1089         uint32_t npoints = events.size();
1090
1091         if (npoints == 0) {
1092                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1093                         delete *i;
1094                 }
1095                 control_points.clear ();
1096                 line->hide();
1097                 return;
1098         }
1099
1100         AutomationList::const_iterator ai;
1101
1102         for (ai = events.begin(); ai != events.end(); ++ai) {
1103
1104                 double translated_x = (*ai)->when;
1105                 double translated_y = (*ai)->value;
1106                 model_to_view_coord (translated_x, translated_y);
1107
1108                 add_model_point (tmp_points, (*ai)->when, translated_y);
1109         }
1110
1111         determine_visible_control_points (tmp_points);
1112 }
1113
1114
1115 void
1116 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1117 {
1118         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1119                                        _height - (yfract * _height)));
1120 }
1121
1122 void
1123 AutomationLine::reset ()
1124 {
1125         update_pending = false;
1126
1127         if (no_draw) {
1128                 return;
1129         }
1130
1131         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1132 }
1133
1134 void
1135 AutomationLine::clear ()
1136 {
1137         /* parent must create command */
1138         XMLNode &before = get_state();
1139         alist->clear();
1140         trackview.editor().session()->add_command (
1141                         new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1142         trackview.editor().session()->commit_reversible_command ();
1143         trackview.editor().session()->set_dirty ();
1144 }
1145
1146 void
1147 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1148 {
1149 }
1150
1151 void
1152 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1153 {
1154         alist = list;
1155         queue_reset();
1156 }
1157
1158 void
1159 AutomationLine::show_all_control_points ()
1160 {
1161         points_visible = true;
1162
1163         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1164                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1165                 (*i)->show ();
1166                 (*i)->set_visible (true);
1167         }
1168 }
1169
1170 void
1171 AutomationLine::hide_all_but_selected_control_points ()
1172 {
1173         if (alist->interpolation() == AutomationList::Discrete) {
1174                 return;
1175         }
1176
1177         points_visible = false;
1178
1179         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1180                 if (!(*i)->selected()) {
1181                         (*i)->set_visible (false);
1182                 }
1183         }
1184 }
1185
1186 void
1187 AutomationLine::track_entered()
1188 {
1189         if (alist->interpolation() != AutomationList::Discrete) {
1190                 show_all_control_points();
1191         }
1192 }
1193
1194 void
1195 AutomationLine::track_exited()
1196 {
1197         if (alist->interpolation() != AutomationList::Discrete) {
1198                 hide_all_but_selected_control_points();
1199         }
1200 }
1201
1202 XMLNode &
1203 AutomationLine::get_state (void)
1204 {
1205         /* function as a proxy for the model */
1206         return alist->get_state();
1207 }
1208
1209 int
1210 AutomationLine::set_state (const XMLNode &node, int version)
1211 {
1212         /* function as a proxy for the model */
1213         return alist->set_state (node, version);
1214 }
1215
1216 void
1217 AutomationLine::view_to_model_coord (double& x, double& y) const
1218 {
1219         /* TODO: This should be more generic ... */
1220         if (alist->parameter().type() == GainAutomation ||
1221             alist->parameter().type() == EnvelopeAutomation) {
1222                 y = slider_position_to_gain (y);
1223                 y = max (0.0, y);
1224                 y = min (2.0, y);
1225         } else if (alist->parameter().type() == PanAutomation) {
1226                 // vertical coordinate axis reversal
1227                 y = 1.0 - y;
1228         } else if (alist->parameter().type() == PluginAutomation) {
1229                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1230         } else {
1231                 y = (int)(y * alist->parameter().max());
1232         }
1233
1234         x = _time_converter.from(x);
1235 }
1236
1237 void
1238 AutomationLine::model_to_view_coord (double& x, double& y) const
1239 {
1240         /* TODO: This should be more generic ... */
1241         if (alist->parameter().type() == GainAutomation ||
1242             alist->parameter().type() == EnvelopeAutomation) {
1243                 y = gain_to_slider_position (y);
1244         } else if (alist->parameter().type() == PanAutomation) {
1245                 // vertical coordinate axis reversal
1246                 y = 1.0 - y;
1247         } else if (alist->parameter().type() == PluginAutomation) {
1248                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1249         } else {
1250                 y = y / (double)alist->parameter().max(); /* ... like this */
1251         }
1252
1253         x = _time_converter.to(x);
1254 }
1255
1256
1257 void
1258 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1259 {
1260         _interpolation = style;
1261
1262         if (style == AutomationList::Discrete) {
1263                 show_all_control_points();
1264                 line->hide();
1265         } else {
1266                 hide_all_but_selected_control_points();
1267                 line->show();
1268         }
1269 }
1270
1271 void
1272 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1273 {
1274         if (view_index >= control_points.size()) {
1275
1276                 /* make sure we have enough control points */
1277
1278                 ControlPoint* ncp = new ControlPoint (*this);
1279                 ncp->set_size (control_point_box_size ());
1280
1281                 control_points.push_back (ncp);
1282         }
1283
1284         ControlPoint::ShapeType shape;
1285
1286         if (!terminal_points_can_slide) {
1287                 if (pi == 0) {
1288                         control_points[view_index]->set_can_slide(false);
1289                         if (tx == 0) {
1290                                 shape = ControlPoint::Start;
1291                         } else {
1292                                 shape = ControlPoint::Full;
1293                         }
1294                 } else if (pi == npoints - 1) {
1295                         control_points[view_index]->set_can_slide(false);
1296                         shape = ControlPoint::End;
1297                 } else {
1298                         control_points[view_index]->set_can_slide(true);
1299                         shape = ControlPoint::Full;
1300                 }
1301         } else {
1302                 control_points[view_index]->set_can_slide(true);
1303                 shape = ControlPoint::Full;
1304         }
1305         
1306         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1307
1308         /* finally, control visibility */
1309         
1310         if (_visible && points_visible) {
1311                 control_points[view_index]->show ();
1312                 control_points[view_index]->set_visible (true);
1313         } else {
1314                 if (!points_visible) {
1315                         control_points[view_index]->set_visible (false);
1316                 }
1317         }
1318 }