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