0c568ee55aac6ff330dd56315cc0bffb570da3b5
[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 keep_x, 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() && !keep_x) {
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_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
366 {
367         update_pending = true;
368
369         for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
370                 sync_model_with_view_point (**i, did_push, distance);
371         }
372 }
373
374 void
375 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
376 {
377         /* part one: find out where the visual control point is.
378            initial results are in canvas units. ask the
379            line to convert them to something relevant.
380         */
381
382         mr.xval = cp.get_x();
383         mr.yval = 1.0 - (cp.get_y() / _height);
384
385         /* if xval has not changed, set it directly from the model to avoid rounding errors */
386
387         if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
388                 mr.xval = (*cp.model())->when;
389         } else {
390                 mr.xval = trackview.editor().unit_to_frame (mr.xval);
391         }
392
393         /* convert to model units
394         */
395
396         view_to_model_coord (mr.xval, mr.yval);
397
398         /* part 2: find out where the model point is now
399          */
400
401         mr.xpos = (*cp.model())->when;
402         mr.ypos = (*cp.model())->value;
403
404         /* part 3: get the position of the visual control
405            points before and after us.
406         */
407
408         ControlPoint* before;
409         ControlPoint* after;
410
411         if (cp.view_index()) {
412                 before = nth (cp.view_index() - 1);
413         } else {
414                 before = 0;
415         }
416
417         after = nth (cp.view_index() + 1);
418
419         if (before) {
420                 mr.xmin = (*before->model())->when;
421                 mr.ymin = (*before->model())->value;
422                 mr.start = before->model();
423                 ++mr.start;
424         } else {
425                 mr.xmin = mr.xpos;
426                 mr.ymin = mr.ypos;
427                 mr.start = cp.model();
428         }
429
430         if (after) {
431                 mr.end = after->model();
432         } else {
433                 mr.xmax = mr.xpos;
434                 mr.ymax = mr.ypos;
435                 mr.end = cp.model();
436                 ++mr.end;
437         }
438 }
439
440 void
441 AutomationLine::determine_visible_control_points (ALPoints& points)
442 {
443         uint32_t view_index, pi, n;
444         AutomationList::iterator model;
445         uint32_t npoints;
446         uint32_t this_rx = 0;
447         uint32_t prev_rx = 0;
448         uint32_t this_ry = 0;
449         uint32_t prev_ry = 0;
450         double* slope;
451         uint32_t box_size;
452
453         /* hide all existing points, and the line */
454
455         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
456                 (*i)->hide();
457         }
458
459         line->hide ();
460
461         if (points.empty()) {
462                 return;
463         }
464
465         npoints = points.size();
466
467         /* compute derivative/slope for the entire line */
468
469         slope = new double[npoints];
470
471         for (n = 0; n < npoints - 1; ++n) {
472                 double xdelta = points[n+1].x - points[n].x;
473                 double ydelta = points[n+1].y - points[n].y;
474                 slope[n] = ydelta/xdelta;
475         }
476
477         box_size = (uint32_t) control_point_box_size ();
478
479         /* read all points and decide which ones to show as control points */
480
481         view_index = 0;
482
483         for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
484
485                 double tx = points[pi].x;
486                 double ty = points[pi].y;
487
488                 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
489                         add_visible_control_point (view_index, pi, tx, ty, model, npoints);
490                         prev_rx = this_rx;
491                         prev_ry = this_ry;
492                         ++view_index;
493                         continue;
494                 }
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                 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
546                 
547                 prev_rx = this_rx;
548                 prev_ry = this_ry;
549
550                 view_index++;
551         }
552
553         /* discard extra CP's to avoid confusing ourselves */
554
555         while (control_points.size() > view_index) {
556                 ControlPoint* cp = control_points.back();
557                 control_points.pop_back ();
558                 delete cp;
559         }
560
561         if (!terminal_points_can_slide) {
562                 control_points.back()->set_can_slide(false);
563         }
564
565         delete [] slope;
566
567         if (view_index > 1) {
568
569                 npoints = view_index;
570
571                 /* reset the line coordinates */
572
573                 while (line_points.size() < npoints) {
574                         line_points.push_back (Art::Point (0,0));
575                 }
576
577                 while (line_points.size() > npoints) {
578                         line_points.pop_back ();
579                 }
580
581                 for (view_index = 0; view_index < npoints; ++view_index) {
582                         line_points[view_index].set_x (control_points[view_index]->get_x());
583                         line_points[view_index].set_y (control_points[view_index]->get_y());
584                 }
585
586                 line->property_points() = line_points;
587
588                 if (_visible && _interpolation != AutomationList::Discrete) {
589                         line->show();
590                 }
591
592         }
593
594         set_selected_points (trackview.editor().get_selection().points);
595
596 }
597
598 string
599 AutomationLine::get_verbose_cursor_string (double fraction) const
600 {
601         std::string s = fraction_to_string (fraction);
602         if (_uses_gain_mapping) {
603                 s += " dB";
604         }
605
606         return s;
607 }
608
609 /**
610  *  @param fraction y fraction
611  *  @return string representation of this value, using dB if appropriate.
612  */
613 string
614 AutomationLine::fraction_to_string (double fraction) const
615 {
616         char buf[32];
617
618         if (_uses_gain_mapping) {
619                 if (fraction == 0.0) {
620                         snprintf (buf, sizeof (buf), "-inf");
621                 } else {
622                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
623                 }
624         } else {
625                 double dummy = 0.0;
626                 view_to_model_coord (dummy, fraction);
627                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
628                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
629                 } else {
630                         snprintf (buf, sizeof (buf), "%.2f", fraction);
631                 }
632         }
633
634         return buf;
635 }
636
637
638 /**
639  *  @param s Value string in the form as returned by fraction_to_string.
640  *  @return Corresponding y fraction.
641  */
642 double
643 AutomationLine::string_to_fraction (string const & s) const
644 {
645         if (s == "-inf") {
646                 return 0;
647         }
648
649         double v;
650         sscanf (s.c_str(), "%lf", &v);
651
652         if (_uses_gain_mapping) {
653                 v = gain_to_slider_position (dB_to_coefficient (v));
654         } else {
655                 double dummy = 0.0;
656                 model_to_view_coord (dummy, v);
657         }
658
659         return v;
660 }
661
662 bool
663 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
664 {
665         return p[index].x == max_frames && p[index].y == DBL_MAX;
666 }
667
668 void
669 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
670 {
671         p[index].x = max_frames;
672         p[index].y = DBL_MAX;
673 }
674
675 /** Start dragging a single point.
676  *  @param cp Point to drag.
677  *  @param x Initial x position (frames).
678  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
679  */
680 void
681 AutomationLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
682 {
683         trackview.editor().session()->begin_reversible_command (_("automation event move"));
684         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
685
686         _drag_points.clear ();
687         _drag_points.push_back (cp);
688         start_drag_common (x, fraction);
689 }
690
691 /** Start dragging a line vertically (with no change in x)
692  *  @param i1 Control point index of the `left' point on the line.
693  *  @param i2 Control point index of the `right' point on the line.
694  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
695  */
696 void
697 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
698 {
699         trackview.editor().session()->begin_reversible_command (_("automation range move"));
700         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
701
702         _drag_points.clear ();
703
704         // check if one of the control points on the line is in a selected range
705         bool range_found = false;
706         ControlPoint *cp;
707
708         for (uint32_t i = i1 ; i <= i2; i++) {
709                 cp = nth (i);
710                 if (cp->selected()) {
711                         range_found = true;
712                 }
713         }
714
715         if (range_found) {
716                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
717                         if ((*i)->selected()) {
718                                 _drag_points.push_back (*i);
719                         }
720                 }
721         } else {
722                 for (uint32_t i = i1 ; i <= i2; i++) {
723                         _drag_points.push_back (nth (i));
724                 }
725         }
726
727         start_drag_common (0, fraction);
728 }
729
730 /** Start dragging multiple points (with no change in x)
731  *  @param cp Points to drag.
732  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
733  */
734 void
735 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
736 {
737         trackview.editor().session()->begin_reversible_command (_("automation range move"));
738         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
739
740         _drag_points = cp;
741         start_drag_common (0, fraction);
742 }
743
744 /** Common parts of starting a drag.
745  *  @param d Description of the drag.
746  *  @param x Starting x position in frames, or 0 if x is being ignored.
747  *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
748  */
749 void
750 AutomationLine::start_drag_common (nframes_t x, float fraction)
751 {
752         drag_x = x;
753         drag_distance = 0;
754         _last_drag_fraction = fraction;
755         _drag_had_movement = false;
756         did_push = false;
757 }
758
759 /** Should be called to indicate motion during a drag.
760  *  @param x New x position of the drag in frames, or 0 if x is being ignored.
761  *  @param fraction New y fraction.
762  */
763 void
764 AutomationLine::drag_motion (nframes_t x, float fraction, bool with_push)
765 {
766         int64_t const dx = x - drag_x;
767         drag_distance += dx;
768         drag_x = x;
769
770         double const dy = fraction - _last_drag_fraction;
771
772         _last_drag_fraction = fraction;
773
774         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
775
776                 modify_view_point (
777                         **i,
778                         trackview.editor().unit_to_frame ((*i)->get_x()) + dx,
779                         ((_height - (*i)->get_y()) / _height) + dy,
780                         (x == 0),
781                         with_push
782                         );
783         }
784
785         if (line_points.size() > 1) {
786                 line->property_points() = line_points;
787         }
788
789         _drag_had_movement = true;
790         did_push = with_push;
791 }
792
793 /** Should be called to indicate the end of a drag */
794 void
795 AutomationLine::end_drag ()
796 {
797         if (!_drag_had_movement) {
798                 return;
799         }
800
801         alist->freeze ();
802
803         sync_model_with_view_points (_drag_points, did_push, drag_distance);
804
805         alist->thaw ();
806
807         update_pending = false;
808
809         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
810         trackview.editor().session()->commit_reversible_command ();
811         trackview.editor().session()->set_dirty ();
812 }
813
814 void
815 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
816 {
817         ModelRepresentation mr;
818         double ydelta;
819
820         model_representation (cp, mr);
821
822         /* how much are we changing the central point by */
823
824         ydelta = mr.yval - mr.ypos;
825
826         /*
827            apply the full change to the central point, and interpolate
828            on both axes to cover all model points represented
829            by the control point.
830         */
831
832         /* change all points before the primary point */
833
834         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
835
836                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
837                 double y_delta = ydelta * fract;
838                 double x_delta = distance * fract;
839
840                 /* interpolate */
841
842                 if (y_delta || x_delta) {
843                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
844                 }
845         }
846
847         /* change the primary point */
848
849         update_pending = true;
850         alist->modify (cp.model(), mr.xval, mr.yval);
851
852
853         /* change later points */
854
855         AutomationList::iterator i = cp.model();
856
857         ++i;
858
859         while (i != mr.end) {
860
861                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
862
863                 /* all later points move by the same distance along the x-axis as the main point */
864
865                 if (delta) {
866                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
867                 }
868
869                 ++i;
870         }
871
872         if (did_push) {
873
874                 /* move all points after the range represented by the view by the same distance
875                    as the main point moved.
876                 */
877
878                 alist->slide (mr.end, drag_distance);
879         }
880 }
881
882 bool
883 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
884 {
885         ControlPoint *bcp = 0;
886         ControlPoint *acp = 0;
887         double unit_xval;
888
889         unit_xval = trackview.editor().frame_to_unit (xval);
890
891         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
892
893                 if ((*i)->get_x() <= unit_xval) {
894
895                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
896                                 bcp = *i;
897                                 before = bcp->view_index();
898                         }
899
900                 } else if ((*i)->get_x() > unit_xval) {
901                         acp = *i;
902                         after = acp->view_index();
903                         break;
904                 }
905         }
906
907         return bcp && acp;
908 }
909
910 bool
911 AutomationLine::is_last_point (ControlPoint& cp)
912 {
913         ModelRepresentation mr;
914
915         model_representation (cp, mr);
916
917         // If the list is not empty, and the point is the last point in the list
918
919         if (!alist->empty() && mr.end == alist->end()) {
920                 return true;
921         }
922
923         return false;
924 }
925
926 bool
927 AutomationLine::is_first_point (ControlPoint& cp)
928 {
929         ModelRepresentation mr;
930
931         model_representation (cp, mr);
932
933         // If the list is not empty, and the point is the first point in the list
934
935         if (!alist->empty() && mr.start == alist->begin()) {
936                 return true;
937         }
938
939         return false;
940 }
941
942 // This is copied into AudioRegionGainLine
943 void
944 AutomationLine::remove_point (ControlPoint& cp)
945 {
946         ModelRepresentation mr;
947
948         model_representation (cp, mr);
949
950         trackview.editor().session()->begin_reversible_command (_("remove control point"));
951         XMLNode &before = alist->get_state();
952
953         alist->erase (mr.start, mr.end);
954
955         trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
956                         *alist.get(), &before, &alist->get_state()));
957         trackview.editor().session()->commit_reversible_command ();
958         trackview.editor().session()->set_dirty ();
959 }
960
961 void
962 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
963                 double botfrac, double topfrac, list<Selectable*>& results)
964 {
965
966         double top;
967         double bot;
968         double nstart;
969         double nend;
970         bool collecting = false;
971
972         /* Curse X11 and its inverted coordinate system! */
973
974         bot = (1.0 - topfrac) * _height;
975         top = (1.0 - botfrac) * _height;
976
977         nstart = max_frames;
978         nend = 0;
979
980         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
981                 double when = (*(*i)->model())->when;
982
983                 if (when >= start && when <= end) {
984
985                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
986
987                                 (*i)->show();
988                                 (*i)->set_visible(true);
989                                 collecting = true;
990                                 nstart = min (nstart, when);
991                                 nend = max (nend, when);
992
993                         } else {
994
995                                 if (collecting) {
996
997                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
998                                         collecting = false;
999                                         nstart = max_frames;
1000                                         nend = 0;
1001                                 }
1002                         }
1003                 }
1004         }
1005
1006         if (collecting) {
1007                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1008         }
1009
1010 }
1011
1012 void
1013 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1014 {
1015         // hmmm ....
1016 }
1017
1018 void
1019 AutomationLine::set_selected_points (PointSelection& points)
1020 {
1021         double top;
1022         double bot;
1023
1024         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1025                         (*i)->set_selected(false);
1026         }
1027
1028         if (points.empty()) {
1029                 goto out;
1030         }
1031
1032         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1033
1034                 if ((*r).track != &trackview) {
1035                         continue;
1036                 }
1037
1038                 /* Curse X11 and its inverted coordinate system! */
1039
1040                 bot = (1.0 - (*r).high_fract) * _height;
1041                 top = (1.0 - (*r).low_fract) * _height;
1042
1043                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1044
1045                         double rstart, rend;
1046
1047                         rstart = trackview.editor().frame_to_unit ((*r).start);
1048                         rend = trackview.editor().frame_to_unit ((*r).end);
1049
1050                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1051
1052                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1053
1054                                         (*i)->set_selected(true);
1055                                 }
1056                         }
1057
1058                 }
1059         }
1060
1061   out:
1062         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1063                 (*i)->show_color (false, !points_visible);
1064         }
1065
1066 }
1067
1068 void AutomationLine::set_colors() {
1069         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1070         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1071                 (*i)->show_color (false, !points_visible);
1072         }
1073 }
1074
1075 void
1076 AutomationLine::show_selection ()
1077 {
1078         TimeSelection& time (trackview.editor().get_selection().time);
1079
1080         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1081
1082                 (*i)->set_selected(false);
1083
1084                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1085                         double rstart, rend;
1086
1087                         rstart = trackview.editor().frame_to_unit ((*r).start);
1088                         rend = trackview.editor().frame_to_unit ((*r).end);
1089
1090                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1091                                 (*i)->set_selected(true);
1092                                 break;
1093                         }
1094                 }
1095
1096                 (*i)->show_color (false, !points_visible);
1097         }
1098 }
1099
1100 void
1101 AutomationLine::hide_selection ()
1102 {
1103 //      show_selection ();
1104 }
1105
1106 void
1107 AutomationLine::list_changed ()
1108 {
1109         queue_reset ();
1110 }
1111
1112 void
1113 AutomationLine::reset_callback (const Evoral::ControlList& events)
1114 {
1115         ALPoints tmp_points;
1116         uint32_t npoints = events.size();
1117
1118         if (npoints == 0) {
1119                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1120                         delete *i;
1121                 }
1122                 control_points.clear ();
1123                 line->hide();
1124                 return;
1125         }
1126
1127         AutomationList::const_iterator ai;
1128
1129         for (ai = events.begin(); ai != events.end(); ++ai) {
1130
1131                 double translated_x = (*ai)->when;
1132                 double translated_y = (*ai)->value;
1133                 model_to_view_coord (translated_x, translated_y);
1134
1135                 add_model_point (tmp_points, (*ai)->when, translated_y);
1136         }
1137
1138         determine_visible_control_points (tmp_points);
1139 }
1140
1141
1142 void
1143 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1144 {
1145         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1146                                        _height - (yfract * _height)));
1147 }
1148
1149 void
1150 AutomationLine::reset ()
1151 {
1152         update_pending = false;
1153
1154         if (no_draw) {
1155                 return;
1156         }
1157
1158         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1159 }
1160
1161 void
1162 AutomationLine::clear ()
1163 {
1164         /* parent must create command */
1165         XMLNode &before = get_state();
1166         alist->clear();
1167         trackview.editor().session()->add_command (
1168                         new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1169         trackview.editor().session()->commit_reversible_command ();
1170         trackview.editor().session()->set_dirty ();
1171 }
1172
1173 void
1174 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1175 {
1176 }
1177
1178 void
1179 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1180 {
1181         alist = list;
1182         queue_reset();
1183 }
1184
1185 void
1186 AutomationLine::show_all_control_points ()
1187 {
1188         points_visible = true;
1189
1190         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1191                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1192                 (*i)->show ();
1193                 (*i)->set_visible (true);
1194         }
1195 }
1196
1197 void
1198 AutomationLine::hide_all_but_selected_control_points ()
1199 {
1200         if (alist->interpolation() == AutomationList::Discrete) {
1201                 return;
1202         }
1203
1204         points_visible = false;
1205
1206         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1207                 if (!(*i)->selected()) {
1208                         (*i)->set_visible (false);
1209                 }
1210         }
1211 }
1212
1213 void
1214 AutomationLine::track_entered()
1215 {
1216         if (alist->interpolation() != AutomationList::Discrete) {
1217                 show_all_control_points();
1218         }
1219 }
1220
1221 void
1222 AutomationLine::track_exited()
1223 {
1224         if (alist->interpolation() != AutomationList::Discrete) {
1225                 hide_all_but_selected_control_points();
1226         }
1227 }
1228
1229 XMLNode &
1230 AutomationLine::get_state (void)
1231 {
1232         /* function as a proxy for the model */
1233         return alist->get_state();
1234 }
1235
1236 int
1237 AutomationLine::set_state (const XMLNode &node, int version)
1238 {
1239         /* function as a proxy for the model */
1240         return alist->set_state (node, version);
1241 }
1242
1243 void
1244 AutomationLine::view_to_model_coord (double& x, double& y) const
1245 {
1246         /* TODO: This should be more generic ... */
1247         if (alist->parameter().type() == GainAutomation ||
1248             alist->parameter().type() == EnvelopeAutomation) {
1249                 y = slider_position_to_gain (y);
1250                 y = max (0.0, y);
1251                 y = min (2.0, y);
1252         } else if (alist->parameter().type() == PanAutomation) {
1253                 // vertical coordinate axis reversal
1254                 y = 1.0 - y;
1255         } else if (alist->parameter().type() == PluginAutomation) {
1256                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1257         } else {
1258                 y = (int)(y * alist->parameter().max());
1259         }
1260
1261         x = _time_converter.from(x);
1262 }
1263
1264 void
1265 AutomationLine::model_to_view_coord (double& x, double& y) const
1266 {
1267         /* TODO: This should be more generic ... */
1268         if (alist->parameter().type() == GainAutomation ||
1269             alist->parameter().type() == EnvelopeAutomation) {
1270                 y = gain_to_slider_position (y);
1271         } else if (alist->parameter().type() == PanAutomation) {
1272                 // vertical coordinate axis reversal
1273                 y = 1.0 - y;
1274         } else if (alist->parameter().type() == PluginAutomation) {
1275                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1276         } else {
1277                 y = y / (double)alist->parameter().max(); /* ... like this */
1278         }
1279
1280         x = _time_converter.to(x);
1281 }
1282
1283
1284 void
1285 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1286 {
1287         _interpolation = style;
1288
1289         if (style == AutomationList::Discrete) {
1290                 show_all_control_points();
1291                 line->hide();
1292         } else {
1293                 hide_all_but_selected_control_points();
1294                 line->show();
1295         }
1296 }
1297
1298 void
1299 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1300 {
1301         if (view_index >= control_points.size()) {
1302
1303                 /* make sure we have enough control points */
1304
1305                 ControlPoint* ncp = new ControlPoint (*this);
1306                 ncp->set_size (control_point_box_size ());
1307
1308                 control_points.push_back (ncp);
1309         }
1310
1311         ControlPoint::ShapeType shape;
1312
1313         if (!terminal_points_can_slide) {
1314                 if (pi == 0) {
1315                         control_points[view_index]->set_can_slide(false);
1316                         if (tx == 0) {
1317                                 shape = ControlPoint::Start;
1318                         } else {
1319                                 shape = ControlPoint::Full;
1320                         }
1321                 } else if (pi == npoints - 1) {
1322                         control_points[view_index]->set_can_slide(false);
1323                         shape = ControlPoint::End;
1324                 } else {
1325                         control_points[view_index]->set_can_slide(true);
1326                         shape = ControlPoint::Full;
1327                 }
1328         } else {
1329                 control_points[view_index]->set_can_slide(true);
1330                 shape = ControlPoint::Full;
1331         }
1332
1333         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1334
1335         /* finally, control visibility */
1336         
1337         if (_visible && points_visible) {
1338                 control_points[view_index]->show ();
1339                 control_points[view_index]->set_visible (true);
1340         } else {
1341                 if (!points_visible) {
1342                         control_points[view_index]->set_visible (false);
1343                 }
1344         }
1345 }
1346
1347 void
1348 AutomationLine::add_always_in_view (double x)
1349 {
1350         _always_in_view.push_back (x);
1351         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1352 }
1353
1354 void
1355 AutomationLine::clear_always_in_view ()
1356 {
1357         _always_in_view.clear ();
1358         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1359 }
1360