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