No-op: rename a few variables and add/fix some comments.
[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::hLarger) {
157                 return 8.0;
158         } else if (_height > (guint32) TimeAxisView::hNormal) {
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 = get_state();
1108         alist->clear();
1109         trackview.editor().session()->add_command (
1110                         new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1111         trackview.editor().session()->commit_reversible_command ();
1112         trackview.editor().session()->set_dirty ();
1113 }
1114
1115 void
1116 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1117 {
1118 }
1119
1120 void
1121 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1122 {
1123         alist = list;
1124         queue_reset();
1125 }
1126
1127 void
1128 AutomationLine::show_all_control_points ()
1129 {
1130         points_visible = true;
1131
1132         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1133                 if (!(*i)->visible()) {
1134                         (*i)->show ();
1135                         (*i)->set_visible (true);
1136                 }
1137         }
1138 }
1139
1140 void
1141 AutomationLine::hide_all_but_selected_control_points ()
1142 {
1143         if (alist->interpolation() == AutomationList::Discrete) {
1144                 return;
1145         }
1146
1147         points_visible = false;
1148
1149         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1150                 if (!(*i)->selected()) {
1151                         (*i)->set_visible (false);
1152                 }
1153         }
1154 }
1155
1156 void
1157 AutomationLine::track_entered()
1158 {
1159         if (alist->interpolation() != AutomationList::Discrete) {
1160                 show_all_control_points();
1161         }
1162 }
1163
1164 void
1165 AutomationLine::track_exited()
1166 {
1167         if (alist->interpolation() != AutomationList::Discrete) {
1168                 hide_all_but_selected_control_points();
1169         }
1170 }
1171
1172 XMLNode &
1173 AutomationLine::get_state (void)
1174 {
1175         /* function as a proxy for the model */
1176         return alist->get_state();
1177 }
1178
1179 int
1180 AutomationLine::set_state (const XMLNode &node, int version)
1181 {
1182         /* function as a proxy for the model */
1183         return alist->set_state (node, version);
1184 }
1185
1186 void
1187 AutomationLine::view_to_model_coord (double& x, double& y) const
1188 {
1189         /* TODO: This should be more generic ... */
1190         if (alist->parameter().type() == GainAutomation ||
1191             alist->parameter().type() == EnvelopeAutomation) {
1192                 y = slider_position_to_gain (y);
1193                 y = max (0.0, y);
1194                 y = min (2.0, y);
1195         } else if (alist->parameter().type() == PanAutomation) {
1196                 // vertical coordinate axis reversal
1197                 y = 1.0 - y;
1198         } else if (alist->parameter().type() == PluginAutomation) {
1199                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1200         } else {
1201                 y = (int)(y * alist->parameter().max());
1202         }
1203
1204         x = _time_converter.from(x);
1205 }
1206
1207 void
1208 AutomationLine::model_to_view_coord (double& x, double& y) const
1209 {
1210         /* TODO: This should be more generic ... */
1211         if (alist->parameter().type() == GainAutomation ||
1212             alist->parameter().type() == EnvelopeAutomation) {
1213                 y = gain_to_slider_position (y);
1214         } else if (alist->parameter().type() == PanAutomation) {
1215                 // vertical coordinate axis reversal
1216                 y = 1.0 - y;
1217         } else if (alist->parameter().type() == PluginAutomation) {
1218                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1219         } else {
1220                 y = y / (double)alist->parameter().max(); /* ... like this */
1221         }
1222
1223         x = _time_converter.to(x);
1224 }
1225
1226
1227 void
1228 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1229 {
1230         _interpolation = style;
1231
1232         if (style == AutomationList::Discrete) {
1233                 show_all_control_points();
1234                 line->hide();
1235         } else {
1236                 hide_all_but_selected_control_points();
1237                 line->show();
1238         }
1239 }
1240
1241 void
1242 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1243 {
1244         if (view_index >= control_points.size()) {
1245
1246                 /* make sure we have enough control points */
1247
1248                 ControlPoint* ncp = new ControlPoint (*this);
1249                 ncp->set_size (control_point_box_size ());
1250
1251                 control_points.push_back (ncp);
1252         }
1253
1254         ControlPoint::ShapeType shape;
1255
1256         if (!terminal_points_can_slide) {
1257                 if (pi == 0) {
1258                         control_points[view_index]->set_can_slide(false);
1259                         if (tx == 0) {
1260                                 shape = ControlPoint::Start;
1261                         } else {
1262                                 shape = ControlPoint::Full;
1263                         }
1264                 } else if (pi == npoints - 1) {
1265                         control_points[view_index]->set_can_slide(false);
1266                         shape = ControlPoint::End;
1267                 } else {
1268                         control_points[view_index]->set_can_slide(true);
1269                         shape = ControlPoint::Full;
1270                 }
1271         } else {
1272                 control_points[view_index]->set_can_slide(true);
1273                 shape = ControlPoint::Full;
1274         }
1275
1276         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1277
1278         /* finally, control visibility */
1279         
1280         if (_visible && points_visible) {
1281                 control_points[view_index]->show ();
1282                 control_points[view_index]->set_visible (true);
1283         } else {
1284                 if (!points_visible) {
1285                         control_points[view_index]->set_visible (false);
1286                 }
1287         }
1288 }
1289
1290 void
1291 AutomationLine::add_always_in_view (double x)
1292 {
1293         _always_in_view.push_back (x);
1294         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1295 }
1296
1297 void
1298 AutomationLine::clear_always_in_view ()
1299 {
1300         _always_in_view.clear ();
1301         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1302 }
1303