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