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