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