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