1b20d4c2871657e4d0e73b56e10076c0fe0db3b2
[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, sframes_t> default_converter;
59
60 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
61                 boost::shared_ptr<AutomationList> al,
62                 const Evoral::TimeConverter<double, sframes_t>* converter)
63         : trackview (tv)
64         , _name (name)
65         , alist (al)
66         , _parent_group (parent)
67         , _time_converter (converter ? (*converter) : default_converter)
68         , _maximum_time (max_frames)
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_frames && p[index].y == DBL_MAX;
569 }
570
571 void
572 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
573 {
574         p[index].x = max_frames;
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()->commit_reversible_command ();
810         trackview.editor().session()->set_dirty ();
811 }
812
813 void
814 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
815 {
816         ModelRepresentation mr;
817         double ydelta;
818
819         model_representation (cp, mr);
820
821         /* how much are we changing the central point by */
822
823         ydelta = mr.yval - mr.ypos;
824
825         /*
826            apply the full change to the central point, and interpolate
827            on both axes to cover all model points represented
828            by the control point.
829         */
830
831         /* change all points before the primary point */
832
833         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
834
835                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
836                 double y_delta = ydelta * fract;
837                 double x_delta = distance * fract;
838
839                 /* interpolate */
840
841                 if (y_delta || x_delta) {
842                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
843                 }
844         }
845
846         /* change the primary point */
847
848         update_pending = true;
849         alist->modify (cp.model(), mr.xval, mr.yval);
850
851         /* change later points */
852
853         AutomationList::iterator i = cp.model();
854
855         ++i;
856
857         while (i != mr.end) {
858
859                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
860
861                 /* all later points move by the same distance along the x-axis as the main point */
862
863                 if (delta) {
864                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
865                 }
866
867                 ++i;
868         }
869
870         if (did_push) {
871
872                 /* move all points after the range represented by the view by the same distance
873                    as the main point moved.
874                 */
875
876                 alist->slide (mr.end, distance);
877         }
878 }
879
880 bool
881 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
882 {
883         ControlPoint *bcp = 0;
884         ControlPoint *acp = 0;
885         double unit_xval;
886
887         unit_xval = trackview.editor().frame_to_unit (xval);
888
889         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
890
891                 if ((*i)->get_x() <= unit_xval) {
892
893                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
894                                 bcp = *i;
895                                 before = bcp->view_index();
896                         }
897
898                 } else if ((*i)->get_x() > unit_xval) {
899                         acp = *i;
900                         after = acp->view_index();
901                         break;
902                 }
903         }
904
905         return bcp && acp;
906 }
907
908 bool
909 AutomationLine::is_last_point (ControlPoint& cp)
910 {
911         ModelRepresentation mr;
912
913         model_representation (cp, mr);
914
915         // If the list is not empty, and the point is the last point in the list
916
917         if (!alist->empty() && mr.end == alist->end()) {
918                 return true;
919         }
920
921         return false;
922 }
923
924 bool
925 AutomationLine::is_first_point (ControlPoint& cp)
926 {
927         ModelRepresentation mr;
928
929         model_representation (cp, mr);
930
931         // If the list is not empty, and the point is the first point in the list
932
933         if (!alist->empty() && mr.start == alist->begin()) {
934                 return true;
935         }
936
937         return false;
938 }
939
940 // This is copied into AudioRegionGainLine
941 void
942 AutomationLine::remove_point (ControlPoint& cp)
943 {
944         ModelRepresentation mr;
945
946         model_representation (cp, mr);
947
948         trackview.editor().session()->begin_reversible_command (_("remove control point"));
949         XMLNode &before = alist->get_state();
950
951         alist->erase (mr.start, mr.end);
952
953         trackview.editor().session()->add_command(
954                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
955                 );
956         
957         trackview.editor().session()->commit_reversible_command ();
958         trackview.editor().session()->set_dirty ();
959 }
960
961 /** Get selectable points within an area.
962  *  @param start Start position in session frames.
963  *  @param end End position in session frames.
964  *  @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
965  *  @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
966  *  @param result Filled in with selectable things; in this case, ControlPoints.
967  */
968 void
969 AutomationLine::get_selectables (
970         framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
971         )
972 {
973         /* convert fractions to display coordinates with 0 at the top of the track */
974         double const bot_track = (1 - topfrac) * trackview.current_height ();
975         double const top_track = (1 - botfrac) * trackview.current_height ();
976
977         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
978                 double const model_when = (*(*i)->model())->when;
979                 framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
980
981                 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
982                         results.push_back (*i);
983                 }
984         }
985 }
986
987 void
988 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
989 {
990         // hmmm ....
991 }
992
993 /** Take a PointSelection and find ControlPoints that fall within it */
994 list<ControlPoint*>
995 AutomationLine::point_selection_to_control_points (PointSelection const & s)
996 {
997         list<ControlPoint*> cp;
998         
999         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1000
1001                 if (i->track != &trackview) {
1002                         continue;
1003                 }
1004
1005                 double const bot = (1 - i->high_fract) * trackview.current_height ();
1006                 double const top = (1 - i->low_fract) * trackview.current_height ();
1007
1008                 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1009
1010                         double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1011                         double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1012
1013                         if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1014                                 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1015                                         cp.push_back (*j);
1016                                 }
1017                         }
1018                 }
1019
1020         }
1021
1022         return cp;
1023 }
1024
1025 void
1026 AutomationLine::set_selected_points (PointSelection& points)
1027 {
1028         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1029                 (*i)->set_selected (false);
1030         }
1031
1032         if (!points.empty()) {
1033                 list<ControlPoint*> cp = point_selection_to_control_points (points);
1034                 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1035                         (*i)->set_selected (true);
1036                 }
1037         }
1038
1039         set_colors ();
1040 }
1041
1042 void AutomationLine::set_colors ()
1043 {
1044         set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1045         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1046                 (*i)->set_color ();
1047         }
1048 }
1049
1050 void
1051 AutomationLine::list_changed ()
1052 {
1053         queue_reset ();
1054 }
1055
1056 void
1057 AutomationLine::reset_callback (const Evoral::ControlList& events)
1058 {
1059         ALPoints tmp_points;
1060         uint32_t npoints = events.size();
1061
1062         if (npoints == 0) {
1063                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1064                         delete *i;
1065                 }
1066                 control_points.clear ();
1067                 line->hide();
1068                 return;
1069         }
1070
1071         AutomationList::const_iterator ai;
1072
1073         for (ai = events.begin(); ai != events.end(); ++ai) {
1074
1075                 double translated_x = (*ai)->when;
1076                 double translated_y = (*ai)->value;
1077                 model_to_view_coord (translated_x, translated_y);
1078
1079                 add_model_point (tmp_points, (*ai)->when, translated_y);
1080         }
1081
1082         determine_visible_control_points (tmp_points);
1083 }
1084
1085
1086 void
1087 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1088 {
1089         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1090                                        _height - (yfract * _height)));
1091 }
1092
1093 void
1094 AutomationLine::reset ()
1095 {
1096         update_pending = false;
1097
1098         if (no_draw) {
1099                 return;
1100         }
1101
1102         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1103 }
1104
1105 void
1106 AutomationLine::clear ()
1107 {
1108         /* parent must create and commit command */
1109         XMLNode &before = alist->get_state();
1110         alist->clear();
1111
1112         trackview.editor().session()->add_command (
1113                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1114                 );
1115 }
1116
1117 void
1118 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1119 {
1120 }
1121
1122 void
1123 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1124 {
1125         alist = list;
1126         queue_reset ();
1127         connect_to_list ();
1128 }
1129
1130 void
1131 AutomationLine::show_all_control_points ()
1132 {
1133         if (_is_boolean) {
1134                 // show the line but don't allow any control points
1135                 return;
1136         }
1137
1138         points_visible = true;
1139
1140         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1141                 if (!(*i)->visible()) {
1142                         (*i)->show ();
1143                         (*i)->set_visible (true);
1144                 }
1145         }
1146 }
1147
1148 void
1149 AutomationLine::hide_all_but_selected_control_points ()
1150 {
1151         if (alist->interpolation() == AutomationList::Discrete) {
1152                 return;
1153         }
1154
1155         points_visible = false;
1156
1157         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1158                 if (!(*i)->get_selected()) {
1159                         (*i)->set_visible (false);
1160                 }
1161         }
1162 }
1163
1164 void
1165 AutomationLine::track_entered()
1166 {
1167         if (alist->interpolation() != AutomationList::Discrete) {
1168                 show_all_control_points();
1169         }
1170 }
1171
1172 void
1173 AutomationLine::track_exited()
1174 {
1175         if (alist->interpolation() != AutomationList::Discrete) {
1176                 hide_all_but_selected_control_points();
1177         }
1178 }
1179
1180 XMLNode &
1181 AutomationLine::get_state (void)
1182 {
1183         /* function as a proxy for the model */
1184         return alist->get_state();
1185 }
1186
1187 int
1188 AutomationLine::set_state (const XMLNode &node, int version)
1189 {
1190         /* function as a proxy for the model */
1191         return alist->set_state (node, version);
1192 }
1193
1194 void
1195 AutomationLine::view_to_model_coord (double& x, double& y) const
1196 {
1197         x = _time_converter.from (x);
1198         view_to_model_coord_y (y);
1199 }
1200
1201 void
1202 AutomationLine::view_to_model_coord_y (double& y) const
1203 {
1204         /* TODO: This should be more generic ... */
1205         if (alist->parameter().type() == GainAutomation ||
1206             alist->parameter().type() == EnvelopeAutomation) {
1207                 y = slider_position_to_gain (y);
1208                 y = max (0.0, y);
1209                 y = min (2.0, y);
1210         } else if (alist->parameter().type() == PanAutomation) {
1211                 // vertical coordinate axis reversal
1212                 y = 1.0 - y;
1213         } else if (alist->parameter().type() == PluginAutomation) {
1214                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1215         } else {
1216                 y = (int)(y * alist->parameter().max());
1217         }
1218 }
1219
1220 void
1221 AutomationLine::model_to_view_coord (double& x, double& y) const
1222 {
1223         /* TODO: This should be more generic ... */
1224         if (alist->parameter().type() == GainAutomation ||
1225             alist->parameter().type() == EnvelopeAutomation) {
1226                 y = gain_to_slider_position (y);
1227         } else if (alist->parameter().type() == PanAutomation) {
1228                 // vertical coordinate axis reversal
1229                 y = 1.0 - y;
1230         } else if (alist->parameter().type() == PluginAutomation) {
1231                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1232         } else {
1233                 y = y / (double)alist->parameter().max(); /* ... like this */
1234         }
1235
1236         x = _time_converter.to(x);
1237 }
1238
1239 /** Called when our list has announced that its interpolation style has changed */
1240 void
1241 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1242 {
1243         if (style == AutomationList::Discrete) {
1244                 show_all_control_points();
1245                 line->hide();
1246         } else {
1247                 hide_all_but_selected_control_points();
1248                 line->show();
1249         }
1250 }
1251
1252 void
1253 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1254 {
1255         if (view_index >= control_points.size()) {
1256
1257                 /* make sure we have enough control points */
1258
1259                 ControlPoint* ncp = new ControlPoint (*this);
1260                 ncp->set_size (control_point_box_size ());
1261
1262                 control_points.push_back (ncp);
1263         }
1264
1265         ControlPoint::ShapeType shape;
1266
1267         if (!terminal_points_can_slide) {
1268                 if (pi == 0) {
1269                         control_points[view_index]->set_can_slide(false);
1270                         if (tx == 0) {
1271                                 shape = ControlPoint::Start;
1272                         } else {
1273                                 shape = ControlPoint::Full;
1274                         }
1275                 } else if (pi == npoints - 1) {
1276                         control_points[view_index]->set_can_slide(false);
1277                         shape = ControlPoint::End;
1278                 } else {
1279                         control_points[view_index]->set_can_slide(true);
1280                         shape = ControlPoint::Full;
1281                 }
1282         } else {
1283                 control_points[view_index]->set_can_slide(true);
1284                 shape = ControlPoint::Full;
1285         }
1286
1287         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1288
1289         /* finally, control visibility */
1290         
1291         if (_visible && points_visible) {
1292                 control_points[view_index]->show ();
1293                 control_points[view_index]->set_visible (true);
1294         } else {
1295                 if (!points_visible) {
1296                         control_points[view_index]->set_visible (false);
1297                 }
1298         }
1299 }
1300
1301 void
1302 AutomationLine::add_always_in_view (double x)
1303 {
1304         _always_in_view.push_back (x);
1305         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1306 }
1307
1308 void
1309 AutomationLine::clear_always_in_view ()
1310 {
1311         _always_in_view.clear ();
1312         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1313 }
1314
1315 void
1316 AutomationLine::connect_to_list ()
1317 {
1318         _list_connections.drop_connections ();
1319         
1320         alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1321         
1322         alist->InterpolationChanged.connect (
1323                 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1324                 );
1325 }
1326
1327 MementoCommandBinder<AutomationList>*
1328 AutomationLine::memento_command_binder ()
1329 {
1330         return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1331 }
1332
1333 /** Set the maximum time that points on this line can be at, relative
1334  *  to the start of the track or region that it is on.
1335  */
1336 void
1337 AutomationLine::set_maximum_time (framepos_t t)
1338 {
1339         _maximum_time = t;
1340 }