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