a radically new approach to sizing the track header layout that now allows ardour...
[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                 double dummy = 0.0;
530                 view_to_model_coord (dummy, fraction);
531                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
532                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
533                 } else {
534                         snprintf (buf, sizeof (buf), "%.2f", fraction);
535                 }
536         }
537
538         return buf;
539 }
540
541
542 /**
543  *  @param s Value string in the form as returned by fraction_to_string.
544  *  @return Corresponding y fraction.
545  */
546 double
547 AutomationLine::string_to_fraction (string const & s) const
548 {
549         if (s == "-inf") {
550                 return 0;
551         }
552
553         double v;
554         sscanf (s.c_str(), "%lf", &v);
555
556         if (_uses_gain_mapping) {
557                 v = gain_to_slider_position (dB_to_coefficient (v));
558         } else {
559                 double dummy = 0.0;
560                 model_to_view_coord (dummy, v);
561         }
562
563         return v;
564 }
565
566 bool
567 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
568 {
569         return p[index].x == max_framepos && p[index].y == DBL_MAX;
570 }
571
572 void
573 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
574 {
575         p[index].x = max_framepos;
576         p[index].y = DBL_MAX;
577 }
578
579 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
580  *  are other selected points.
581  *
582  *  @param cp Point to drag.
583  *  @param x Initial x position (units).
584  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
585  */
586 void
587 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
588 {
589         trackview.editor().session()->begin_reversible_command (_("automation event move"));
590         trackview.editor().session()->add_command (
591                 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
592                 );
593
594         _drag_points.clear ();
595         _drag_points.push_back (cp);
596
597         if (cp->get_selected ()) {
598                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
599                         if (*i != cp && (*i)->get_selected()) {
600                                 _drag_points.push_back (*i);
601                         }
602                 }
603         }
604         
605         start_drag_common (x, fraction);
606 }
607
608 /** Start dragging a line vertically (with no change in x)
609  *  @param i1 Control point index of the `left' point on the line.
610  *  @param i2 Control point index of the `right' point on the line.
611  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
612  */
613 void
614 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
615 {
616         trackview.editor().session()->begin_reversible_command (_("automation range move"));
617         trackview.editor().session()->add_command (
618                 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
619                 );
620
621         _drag_points.clear ();
622         for (uint32_t i = i1; i <= i2; i++) {
623                 _drag_points.push_back (nth (i));
624         }
625
626         start_drag_common (0, fraction);
627 }
628
629 /** Start dragging multiple points (with no change in x)
630  *  @param cp Points to drag.
631  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
632  */
633 void
634 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
635 {
636         trackview.editor().session()->begin_reversible_command (_("automation range move"));
637         trackview.editor().session()->add_command (
638                 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
639                 );
640
641         _drag_points = cp;
642         start_drag_common (0, fraction);
643 }
644
645
646 struct ControlPointSorter
647 {
648         bool operator() (ControlPoint const * a, ControlPoint const * b) {
649                 return a->get_x() < b->get_x();
650         }
651 };
652
653 /** Common parts of starting a drag.
654  *  @param x Starting x position in units, or 0 if x is being ignored.
655  *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
656  */
657 void
658 AutomationLine::start_drag_common (double x, float fraction)
659 {
660         _drag_x = x;
661         _drag_distance = 0;
662         _last_drag_fraction = fraction;
663         _drag_had_movement = false;
664         did_push = false;
665
666         _drag_points.sort (ControlPointSorter ());
667
668         /* find the additional points that will be dragged when the user is holding
669            the "push" modifier
670         */
671
672         uint32_t i = _drag_points.back()->view_index () + 1;
673         ControlPoint* p = 0;
674         _push_points.clear ();
675         while ((p = nth (i)) != 0 && p->can_slide()) {
676                 _push_points.push_back (p);
677                 ++i;
678         }
679 }
680
681 /** Should be called to indicate motion during a drag.
682  *  @param x New x position of the drag in units, or undefined if ignore_x == true.
683  *  @param fraction New y fraction.
684  *  @return x position and y fraction that were actually used (once clamped).
685  */
686 pair<double, float>
687 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
688 {
689         /* setup the points that are to be moved this time round */
690         list<ControlPoint*> points = _drag_points;
691         if (with_push) {
692                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
693                 points.sort (ControlPointSorter ());
694         }
695            
696         double dx = ignore_x ? 0 : (x - _drag_x);
697         double dy = fraction - _last_drag_fraction;
698
699         /* find x limits */
700         ControlPoint* before = 0;
701         ControlPoint* after = 0;
702
703         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
704                 if ((*i)->get_x() < points.front()->get_x()) {
705                         before = *i;
706                 }
707                 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
708                         after = *i;
709                 }
710         }
711
712         double const before_x = before ? before->get_x() : 0;
713         double const after_x = after ? after->get_x() : DBL_MAX;
714
715         /* clamp x */
716         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
717
718                 if ((*i)->can_slide() && !ignore_x) {
719
720                         /* clamp min x */
721                         double const a = (*i)->get_x() + dx;
722                         double const b = before_x + 1;
723                         if (a < b) {
724                                 dx += b - a;
725                         }
726
727                         /* clamp max x */
728                         if (after) {
729
730                                 if (after_x - before_x < 2) {
731                                         /* after and before are very close, so just leave this alone */
732                                         dx = 0;
733                                 } else {
734                                         double const a = (*i)->get_x() + dx;
735                                         double const b = after_x - 1;
736                                         if (a > b) {
737                                                 dx -= a - b;
738                                         }
739                                 }
740                         }
741                 }
742         }
743
744         /* clamp y */
745         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
746                 double const y = ((_height - (*i)->get_y()) / _height) + dy;
747                 if (y < 0) {
748                         dy -= y;
749                 }
750                 if (y > 1) {
751                         dy -= (y - 1);
752                 }
753         }
754
755         pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
756         _drag_distance += dx;
757         _drag_x = x;
758         _last_drag_fraction = fraction;
759
760         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
761                 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
762                 reset_line_coords (**i);
763         }
764
765         if (with_push) {
766                 /* move push points, preserving their y */
767                 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
768                         (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
769                         reset_line_coords (**i);
770                 }
771         }
772
773         if (line_points.size() > 1) {
774                 line->property_points() = line_points;
775         }
776
777         _drag_had_movement = true;
778         did_push = with_push;
779
780         return clamped;
781 }
782
783 /** Should be called to indicate the end of a drag */
784 void
785 AutomationLine::end_drag ()
786 {
787         if (!_drag_had_movement) {
788                 return;
789         }
790
791         alist->freeze ();
792
793         /* set up the points that were moved this time round */
794         list<ControlPoint*> points = _drag_points;
795         if (did_push) {
796                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
797                 points.sort (ControlPointSorter ());
798         }
799         
800         sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
801
802         alist->thaw ();
803
804         update_pending = false;
805
806         trackview.editor().session()->add_command (
807                 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
808                 );
809         
810         trackview.editor().session()->set_dirty ();
811 }
812
813 void
814 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
815 {
816         ModelRepresentation mr;
817         double ydelta;
818
819         model_representation (cp, mr);
820
821         /* how much are we changing the central point by */
822
823         ydelta = mr.yval - mr.ypos;
824
825         /*
826            apply the full change to the central point, and interpolate
827            on both axes to cover all model points represented
828            by the control point.
829         */
830
831         /* change all points before the primary point */
832
833         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
834
835                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
836                 double y_delta = ydelta * fract;
837                 double x_delta = distance * fract;
838
839                 /* interpolate */
840
841                 if (y_delta || x_delta) {
842                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
843                 }
844         }
845
846         /* change the primary point */
847
848         update_pending = true;
849         alist->modify (cp.model(), mr.xval, mr.yval);
850
851         /* change later points */
852
853         AutomationList::iterator i = cp.model();
854
855         ++i;
856
857         while (i != mr.end) {
858
859                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
860
861                 /* all later points move by the same distance along the x-axis as the main point */
862
863                 if (delta) {
864                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
865                 }
866
867                 ++i;
868         }
869
870         if (did_push) {
871
872                 /* move all points after the range represented by the view by the same distance
873                    as the main point moved.
874                 */
875
876                 alist->slide (mr.end, distance);
877         }
878 }
879
880 bool
881 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
882 {
883         ControlPoint *bcp = 0;
884         ControlPoint *acp = 0;
885         double unit_xval;
886
887         unit_xval = trackview.editor().frame_to_unit (xval);
888
889         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
890
891                 if ((*i)->get_x() <= unit_xval) {
892
893                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
894                                 bcp = *i;
895                                 before = bcp->view_index();
896                         }
897
898                 } else if ((*i)->get_x() > unit_xval) {
899                         acp = *i;
900                         after = acp->view_index();
901                         break;
902                 }
903         }
904
905         return bcp && acp;
906 }
907
908 bool
909 AutomationLine::is_last_point (ControlPoint& cp)
910 {
911         ModelRepresentation mr;
912
913         model_representation (cp, mr);
914
915         // If the list is not empty, and the point is the last point in the list
916
917         if (!alist->empty() && mr.end == alist->end()) {
918                 return true;
919         }
920
921         return false;
922 }
923
924 bool
925 AutomationLine::is_first_point (ControlPoint& cp)
926 {
927         ModelRepresentation mr;
928
929         model_representation (cp, mr);
930
931         // If the list is not empty, and the point is the first point in the list
932
933         if (!alist->empty() && mr.start == alist->begin()) {
934                 return true;
935         }
936
937         return false;
938 }
939
940 // This is copied into AudioRegionGainLine
941 void
942 AutomationLine::remove_point (ControlPoint& cp)
943 {
944         ModelRepresentation mr;
945
946         model_representation (cp, mr);
947
948         trackview.editor().session()->begin_reversible_command (_("remove control point"));
949         XMLNode &before = alist->get_state();
950
951         alist->erase (mr.start, mr.end);
952
953         trackview.editor().session()->add_command(
954                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
955                 );
956         
957         trackview.editor().session()->commit_reversible_command ();
958         trackview.editor().session()->set_dirty ();
959 }
960
961 /** Get selectable points within an area.
962  *  @param start Start position in session frames.
963  *  @param end End position in session frames.
964  *  @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
965  *  @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
966  *  @param result Filled in with selectable things; in this case, ControlPoints.
967  */
968 void
969 AutomationLine::get_selectables (
970         framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
971         )
972 {
973         /* convert fractions to display coordinates with 0 at the top of the track */
974         double const bot_track = (1 - topfrac) * trackview.current_height ();
975         double const top_track = (1 - botfrac) * trackview.current_height ();
976
977         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
978                 double const model_when = (*(*i)->model())->when;
979                 framepos_t const session_frames_when = _time_converter.to (model_when - _offset) + _time_converter.origin_b ();
980
981                 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
982                         results.push_back (*i);
983                 }
984         }
985 }
986
987 void
988 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
989 {
990         // hmmm ....
991 }
992
993 /** Take a PointSelection and find ControlPoints that fall within it */
994 list<ControlPoint*>
995 AutomationLine::point_selection_to_control_points (PointSelection const & s)
996 {
997         list<ControlPoint*> cp;
998         
999         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1000
1001                 if (i->track != &trackview) {
1002                         continue;
1003                 }
1004
1005                 double const bot = (1 - i->high_fract) * trackview.current_height ();
1006                 double const top = (1 - i->low_fract) * trackview.current_height ();
1007
1008                 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1009
1010                         double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1011                         double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1012
1013                         if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1014                                 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1015                                         cp.push_back (*j);
1016                                 }
1017                         }
1018                 }
1019
1020         }
1021
1022         return cp;
1023 }
1024
1025 void
1026 AutomationLine::set_selected_points (PointSelection& points)
1027 {
1028         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1029                 (*i)->set_selected (false);
1030         }
1031
1032         if (!points.empty()) {
1033                 list<ControlPoint*> cp = point_selection_to_control_points (points);
1034                 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1035                         (*i)->set_selected (true);
1036                 }
1037         }
1038
1039         set_colors ();
1040 }
1041
1042 void AutomationLine::set_colors ()
1043 {
1044         set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1045         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1046                 (*i)->set_color ();
1047         }
1048 }
1049
1050 void
1051 AutomationLine::list_changed ()
1052 {
1053         queue_reset ();
1054 }
1055
1056 void
1057 AutomationLine::reset_callback (const Evoral::ControlList& events)
1058 {
1059         ALPoints tmp_points;
1060         uint32_t npoints = events.size();
1061
1062         if (npoints == 0) {
1063                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1064                         delete *i;
1065                 }
1066                 control_points.clear ();
1067                 line->hide();
1068                 return;
1069         }
1070
1071         AutomationList::const_iterator ai;
1072
1073         for (ai = events.begin(); ai != events.end(); ++ai) {
1074
1075                 double translated_x = (*ai)->when;
1076                 double translated_y = (*ai)->value;
1077                 model_to_view_coord (translated_x, translated_y);
1078
1079                 if (translated_x >= 0 && translated_x < _maximum_time) {
1080                         tmp_points.push_back (ALPoint (
1081                                                       trackview.editor().frame_to_unit (translated_x),
1082                                                       _height - (translated_y * _height))
1083                                 );
1084                 }
1085         }
1086
1087         determine_visible_control_points (tmp_points);
1088 }
1089
1090 void
1091 AutomationLine::reset ()
1092 {
1093         update_pending = false;
1094
1095         if (no_draw) {
1096                 return;
1097         }
1098
1099         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1100 }
1101
1102 void
1103 AutomationLine::clear ()
1104 {
1105         /* parent must create and commit command */
1106         XMLNode &before = alist->get_state();
1107         alist->clear();
1108
1109         trackview.editor().session()->add_command (
1110                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1111                 );
1112 }
1113
1114 void
1115 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1116 {
1117 }
1118
1119 void
1120 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1121 {
1122         alist = list;
1123         queue_reset ();
1124         connect_to_list ();
1125 }
1126
1127 void
1128 AutomationLine::show_all_control_points ()
1129 {
1130         if (_is_boolean) {
1131                 // show the line but don't allow any control points
1132                 return;
1133         }
1134
1135         points_visible = true;
1136
1137         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1138                 if (!(*i)->visible()) {
1139                         (*i)->show ();
1140                         (*i)->set_visible (true);
1141                 }
1142         }
1143 }
1144
1145 void
1146 AutomationLine::hide_all_but_selected_control_points ()
1147 {
1148         if (alist->interpolation() == AutomationList::Discrete) {
1149                 return;
1150         }
1151
1152         points_visible = false;
1153
1154         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1155                 if (!(*i)->get_selected()) {
1156                         (*i)->set_visible (false);
1157                 }
1158         }
1159 }
1160
1161 void
1162 AutomationLine::track_entered()
1163 {
1164         if (alist->interpolation() != AutomationList::Discrete) {
1165                 show_all_control_points();
1166         }
1167 }
1168
1169 void
1170 AutomationLine::track_exited()
1171 {
1172         if (alist->interpolation() != AutomationList::Discrete) {
1173                 hide_all_but_selected_control_points();
1174         }
1175 }
1176
1177 XMLNode &
1178 AutomationLine::get_state (void)
1179 {
1180         /* function as a proxy for the model */
1181         return alist->get_state();
1182 }
1183
1184 int
1185 AutomationLine::set_state (const XMLNode &node, int version)
1186 {
1187         /* function as a proxy for the model */
1188         return alist->set_state (node, version);
1189 }
1190
1191 void
1192 AutomationLine::view_to_model_coord (double& x, double& y) const
1193 {
1194         x = _time_converter.from (x);
1195         view_to_model_coord_y (y);
1196 }
1197
1198 void
1199 AutomationLine::view_to_model_coord_y (double& y) const
1200 {
1201         /* TODO: This should be more generic ... */
1202         if (alist->parameter().type() == GainAutomation ||
1203             alist->parameter().type() == EnvelopeAutomation) {
1204                 y = slider_position_to_gain (y);
1205                 y = max (0.0, y);
1206                 y = min (2.0, y);
1207         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1208                    alist->parameter().type() == PanElevationAutomation ||
1209                    alist->parameter().type() == PanWidthAutomation) {
1210                 y = 1.0 - y;
1211         } else if (alist->parameter().type() == PluginAutomation) {
1212                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1213         } else {
1214                 y = rint (y * alist->parameter().max());
1215         }
1216 }
1217
1218 void
1219 AutomationLine::model_to_view_coord (double& x, double& y) const
1220 {
1221         /* TODO: This should be more generic ... */
1222         if (alist->parameter().type() == GainAutomation ||
1223             alist->parameter().type() == EnvelopeAutomation) {
1224                 y = gain_to_slider_position (y);
1225         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1226                    alist->parameter().type() == PanElevationAutomation ||
1227                    alist->parameter().type() == PanWidthAutomation) {
1228                 // vertical coordinate axis reversal
1229                 y = 1.0 - y;
1230         } else if (alist->parameter().type() == PluginAutomation) {
1231                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1232         } else {
1233                 y = y / (double)alist->parameter().max(); /* ... like this */
1234         }
1235
1236         x = _time_converter.to (x) - _offset;
1237 }
1238
1239 /** Called when our list has announced that its interpolation style has changed */
1240 void
1241 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1242 {
1243         if (style == AutomationList::Discrete) {
1244                 show_all_control_points();
1245                 line->hide();
1246         } else {
1247                 hide_all_but_selected_control_points();
1248                 line->show();
1249         }
1250 }
1251
1252 void
1253 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1254 {
1255         if (view_index >= control_points.size()) {
1256
1257                 /* make sure we have enough control points */
1258
1259                 ControlPoint* ncp = new ControlPoint (*this);
1260                 ncp->set_size (control_point_box_size ());
1261
1262                 control_points.push_back (ncp);
1263         }
1264
1265         ControlPoint::ShapeType shape;
1266
1267         if (!terminal_points_can_slide) {
1268                 if (pi == 0) {
1269                         control_points[view_index]->set_can_slide(false);
1270                         if (tx == 0) {
1271                                 shape = ControlPoint::Start;
1272                         } else {
1273                                 shape = ControlPoint::Full;
1274                         }
1275                 } else if (pi == npoints - 1) {
1276                         control_points[view_index]->set_can_slide(false);
1277                         shape = ControlPoint::End;
1278                 } else {
1279                         control_points[view_index]->set_can_slide(true);
1280                         shape = ControlPoint::Full;
1281                 }
1282         } else {
1283                 control_points[view_index]->set_can_slide(true);
1284                 shape = ControlPoint::Full;
1285         }
1286
1287         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1288
1289         /* finally, control visibility */
1290         
1291         if (_visible && points_visible) {
1292                 control_points[view_index]->show ();
1293                 control_points[view_index]->set_visible (true);
1294         } else {
1295                 if (!points_visible) {
1296                         control_points[view_index]->set_visible (false);
1297                 }
1298         }
1299 }
1300
1301 void
1302 AutomationLine::add_always_in_view (double x)
1303 {
1304         _always_in_view.push_back (x);
1305         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1306 }
1307
1308 void
1309 AutomationLine::clear_always_in_view ()
1310 {
1311         _always_in_view.clear ();
1312         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1313 }
1314
1315 void
1316 AutomationLine::connect_to_list ()
1317 {
1318         _list_connections.drop_connections ();
1319         
1320         alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1321         
1322         alist->InterpolationChanged.connect (
1323                 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1324                 );
1325 }
1326
1327 MementoCommandBinder<AutomationList>*
1328 AutomationLine::memento_command_binder ()
1329 {
1330         return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1331 }
1332
1333 /** Set the maximum time that points on this line can be at, relative
1334  *  to the start of the track or region that it is on.
1335  */
1336 void
1337 AutomationLine::set_maximum_time (framecnt_t t)
1338 {
1339         if (_maximum_time == t) {
1340                 return;
1341         }
1342
1343         _maximum_time = t;
1344         reset ();
1345 }
1346
1347
1348 /** @return min and max x positions of points that are in the list, in session frames */
1349 pair<framepos_t, framepos_t>
1350 AutomationLine::get_point_x_range () const
1351 {
1352         pair<framepos_t, framepos_t> r (max_framepos, 0);
1353
1354         for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1355                 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1356                 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1357         }
1358
1359         return r;
1360 }
1361
1362 void
1363 AutomationLine::set_offset (framepos_t off)
1364 {
1365         if (_offset == off) {
1366                 return;
1367         }
1368         
1369         _offset = off;
1370         reset ();
1371 }