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