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