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