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