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