8754bbc21ab62a36c18662a7d208e30ee4c53673
[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 (((ARDOUR::Parameter)alist->parameter()).is_integer())
618                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
619                 else
620                         snprintf (buf, sizeof (buf), "%.2f", fraction);
621         }
622
623         return buf;
624 }
625
626 bool
627 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
628 {
629         return p[index].x == max_frames && p[index].y == DBL_MAX;
630 }
631
632 void
633 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
634 {
635         p[index].x = max_frames;
636         p[index].y = DBL_MAX;
637 }
638
639 void
640 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
641 {
642         if (trackview.editor.current_session() == 0) { /* how? */
643                 return;
644         }
645
646         string str;
647
648         if (cp) {
649                 str = _("automation event move");
650         } else {
651                 str = _("automation range drag");
652         }
653
654         trackview.editor.current_session()->begin_reversible_command (str);
655         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
656         
657         drag_x = x;
658         drag_distance = 0;
659         first_drag_fraction = fraction;
660         last_drag_fraction = fraction;
661         drags = 0;
662         did_push = false;
663 }
664
665 void
666 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
667 {
668         if (x > drag_x) {
669                 drag_distance += (x - drag_x);
670         } else {
671                 drag_distance -= (drag_x - x);
672         }
673
674         drag_x = x;
675
676         modify_view_point (cp, x, fraction, with_push);
677
678         if (line_points.size() > 1) {
679                 line->property_points() = line_points;
680         }
681
682         drags++;
683         did_push = with_push;
684 }
685
686 void
687 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
688 {
689         double ydelta = fraction - last_drag_fraction;
690
691         did_push = with_push;
692         
693         last_drag_fraction = fraction;
694
695         line_drag_cp1 = i1;
696         line_drag_cp2 = i2;
697         
698         ControlPoint *cp;
699
700         for (uint32_t i = i1 ; i <= i2; i++) {
701                 cp = nth (i);
702                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
703         }
704
705         if (line_points.size() > 1) {
706                 line->property_points() = line_points;
707         }
708
709         drags++;
710 }
711
712 void
713 AutomationLine::end_drag (ControlPoint* cp) 
714 {
715         if (!drags) {
716                 return;
717         }
718
719         alist->freeze ();
720
721         if (cp) {
722                 sync_model_with_view_point (*cp, did_push, drag_distance);
723         } else {
724                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
725         }
726         
727         alist->thaw ();
728
729         update_pending = false;
730
731         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
732         trackview.editor.current_session()->commit_reversible_command ();
733         trackview.editor.current_session()->set_dirty ();
734 }
735
736
737 void
738 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
739 {
740         ModelRepresentation mr;
741         double ydelta;
742
743         model_representation (cp, mr);
744
745         /* how much are we changing the central point by */ 
746
747         ydelta = mr.yval - mr.ypos;
748
749         /*
750            apply the full change to the central point, and interpolate
751            on both axes to cover all model points represented
752            by the control point.
753         */
754
755         /* change all points before the primary point */
756
757         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
758                 
759                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
760                 double y_delta = ydelta * fract;
761                 double x_delta = distance * fract;
762
763                 /* interpolate */
764                 
765                 if (y_delta || x_delta) {
766                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
767                 }
768         }
769
770         /* change the primary point */
771
772         update_pending = true;
773         alist->modify (cp.model(), mr.xval, mr.yval);
774
775
776         /* change later points */
777         
778         AutomationList::iterator i = cp.model();
779         
780         ++i;
781         
782         while (i != mr.end) {
783                 
784                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
785
786                 /* all later points move by the same distance along the x-axis as the main point */
787                 
788                 if (delta) {
789                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
790                 }
791                 
792                 ++i;
793         }
794                 
795         if (did_push) {
796
797                 /* move all points after the range represented by the view by the same distance
798                    as the main point moved.
799                 */
800
801                 alist->slide (mr.end, drag_distance);
802         }
803
804 }
805
806 bool 
807 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
808 {
809         ControlPoint *bcp = 0;
810         ControlPoint *acp = 0;
811         double unit_xval;
812
813         /* xval is in frames */
814
815         unit_xval = trackview.editor.frame_to_unit (xval);
816
817         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
818                 
819                 if ((*i)->get_x() <= unit_xval) {
820
821                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
822                                 bcp = *i;
823                                 before = bcp->view_index();
824                         } 
825
826                 } else if ((*i)->get_x() > unit_xval) {
827                         acp = *i;
828                         after = acp->view_index();
829                         break;
830                 }
831         }
832
833         return bcp && acp;
834 }
835
836 bool
837 AutomationLine::is_last_point (ControlPoint& cp)
838 {
839         ModelRepresentation mr;
840
841         model_representation (cp, mr);
842
843         // If the list is not empty, and the point is the last point in the list
844
845         if (!alist->empty() && mr.end == alist->end()) {
846                 return true;
847         }
848         
849         return false;
850 }
851
852 bool
853 AutomationLine::is_first_point (ControlPoint& cp)
854 {
855         ModelRepresentation mr;
856
857         model_representation (cp, mr);
858
859         // If the list is not empty, and the point is the first point in the list
860
861         if (!alist->empty() && mr.start == alist->begin()) {
862                 return true;
863         }
864         
865         return false;
866 }
867
868 // This is copied into AudioRegionGainLine
869 void
870 AutomationLine::remove_point (ControlPoint& cp)
871 {
872         ModelRepresentation mr;
873
874         model_representation (cp, mr);
875
876         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
877         XMLNode &before = alist->get_state();
878
879         alist->erase (mr.start, mr.end);
880
881         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
882                         *alist.get(), &before, &alist->get_state()));
883         trackview.editor.current_session()->commit_reversible_command ();
884         trackview.editor.current_session()->set_dirty ();
885 }
886
887 void
888 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
889                                  double botfrac, double topfrac, list<Selectable*>& results)
890 {
891
892         double top;
893         double bot;
894         nframes_t nstart;
895         nframes_t nend;
896         bool collecting = false;
897
898         /* Curse X11 and its inverted coordinate system! */
899         
900         bot = (1.0 - topfrac) * _height;
901         top = (1.0 - botfrac) * _height;
902         
903         nstart = max_frames;
904         nend = 0;
905
906         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
907                 
908                 nframes_t when = (nframes_t) (*(*i)->model())->when;
909
910                 if (when >= start && when <= end) {
911                         
912                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
913
914                                 (*i)->show();
915                                 (*i)->set_visible(true);
916                                 collecting = true;
917                                 nstart = min (nstart, when);
918                                 nend = max (nend, when);
919
920                         } else {
921                                 
922                                 if (collecting) {
923
924                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
925                                         collecting = false;
926                                         nstart = max_frames;
927                                         nend = 0;
928                                 }
929                         }
930                 }
931         }
932
933         if (collecting) {
934                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
935         }
936
937 }
938
939 void
940 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
941 {
942         // hmmm ....
943 }
944
945 void
946 AutomationLine::set_selected_points (PointSelection& points)
947 {
948         double top;
949         double bot;
950
951         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
952                         (*i)->set_selected(false);
953         }
954
955         if (points.empty()) {
956                 goto out;
957         } 
958         
959         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
960                 
961                 if (&(*r).track != &trackview) {
962                         continue;
963                 }
964
965                 /* Curse X11 and its inverted coordinate system! */
966
967                 bot = (1.0 - (*r).high_fract) * _height;
968                 top = (1.0 - (*r).low_fract) * _height;
969
970                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
971                         
972                         double rstart, rend;
973                         
974                         rstart = trackview.editor.frame_to_unit ((*r).start);
975                         rend = trackview.editor.frame_to_unit ((*r).end);
976                         
977                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
978                                 
979                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
980                                         
981                                         (*i)->set_selected(true);
982                                 }
983                         }
984
985                 }
986         }
987
988   out:
989         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
990                 (*i)->show_color (false, !points_visible);
991         }
992
993 }
994
995 void AutomationLine::set_colors() {
996         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
997         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
998                 (*i)->show_color (false, !points_visible);
999         }
1000 }
1001
1002 void
1003 AutomationLine::show_selection ()
1004 {
1005         TimeSelection& time (trackview.editor.get_selection().time);
1006
1007         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1008                 
1009                 (*i)->set_selected(false);
1010
1011                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1012                         double rstart, rend;
1013                         
1014                         rstart = trackview.editor.frame_to_unit ((*r).start);
1015                         rend = trackview.editor.frame_to_unit ((*r).end);
1016                         
1017                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1018                                 (*i)->set_selected(true);
1019                                 break;
1020                         }
1021                 }
1022                 
1023                 (*i)->show_color (false, !points_visible);
1024         }
1025 }
1026
1027 void
1028 AutomationLine::hide_selection ()
1029 {
1030 //      show_selection ();
1031 }
1032
1033 void
1034 AutomationLine::list_changed ()
1035 {
1036         queue_reset ();
1037 }
1038
1039 void
1040 AutomationLine::reset_callback (const Evoral::ControlList& events)
1041 {
1042         ALPoints tmp_points;
1043         uint32_t npoints = events.size();
1044
1045         if (npoints == 0) {
1046                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1047                         delete *i;
1048                 }
1049                 control_points.clear ();
1050                 line->hide();
1051                 return;
1052         }
1053
1054         AutomationList::const_iterator ai;
1055
1056         for (ai = events.begin(); ai != events.end(); ++ai) {
1057                 
1058                 double translated_y = (*ai)->value;
1059                 model_to_view_y (translated_y);
1060
1061                 add_model_point (tmp_points, (*ai)->when, translated_y);
1062         }
1063         
1064         determine_visible_control_points (tmp_points);
1065 }
1066
1067
1068 void
1069 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1070 {
1071         tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit (frame),
1072                                        _height - (yfract * _height)));
1073 }
1074
1075 void
1076 AutomationLine::reset ()
1077 {
1078         update_pending = false;
1079
1080         if (no_draw) {
1081                 return;
1082         }
1083
1084         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1085 }
1086
1087 void
1088 AutomationLine::clear ()
1089 {
1090         /* parent must create command */
1091         XMLNode &before = get_state();
1092         alist->clear();
1093         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1094         trackview.editor.current_session()->commit_reversible_command ();
1095         trackview.editor.current_session()->set_dirty ();
1096 }
1097
1098 void
1099 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1100 {
1101 }
1102
1103 void
1104 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1105 {
1106         alist->move_range (start, end, xdelta, ydelta);
1107 }
1108
1109 void
1110 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1111 {
1112         alist = list;
1113         queue_reset();
1114 }
1115
1116 void
1117 AutomationLine::show_all_control_points ()
1118 {
1119         points_visible = true;
1120
1121         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1122                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1123                 (*i)->show ();
1124                 (*i)->set_visible (true);
1125         }
1126 }
1127
1128 void
1129 AutomationLine::hide_all_but_selected_control_points ()
1130 {
1131         if (alist->interpolation() == AutomationList::Discrete)
1132                 return;
1133
1134         points_visible = false;
1135
1136         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1137                 if (!(*i)->selected()) {
1138                         (*i)->set_visible (false);
1139                 }
1140         }
1141 }
1142         
1143 void
1144 AutomationLine::track_entered()
1145 {
1146         if (alist->interpolation() != AutomationList::Discrete)
1147                 show_all_control_points();
1148 }
1149
1150 void
1151 AutomationLine::track_exited()
1152 {
1153         if (alist->interpolation() != AutomationList::Discrete) {
1154                 hide_all_but_selected_control_points();
1155         }
1156 }
1157
1158 XMLNode &
1159 AutomationLine::get_state (void)
1160 {
1161         /* function as a proxy for the model */
1162         return alist->get_state();
1163 }
1164
1165 int 
1166 AutomationLine::set_state (const XMLNode &node)
1167 {
1168         /* function as a proxy for the model */
1169         return alist->set_state (node);
1170 }
1171
1172 void
1173 AutomationLine::view_to_model_y (double& y)
1174 {
1175         /* TODO: This should be more generic ... */
1176         if (alist->parameter().type() == GainAutomation) {
1177                 y = slider_position_to_gain (y);
1178                 y = max (0.0, y);
1179                 y = min (2.0, y);
1180         } else if (alist->parameter().type() == PanAutomation) {
1181                 // vertical coordinate axis reversal
1182                 y = 1.0 - y;
1183         } else if (alist->parameter().type() == PluginAutomation) {
1184                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1185         } else {
1186                 y = (int)(y * alist->parameter().max());
1187         }
1188 }
1189
1190 void
1191 AutomationLine::model_to_view_y (double& y)
1192 {
1193         /* TODO: This should be more generic ... */
1194         if (alist->parameter().type() == GainAutomation) {
1195                 y = gain_to_slider_position (y);
1196         } else if (alist->parameter().type() == PanAutomation) {
1197                 // vertical coordinate axis reversal
1198                 y = 1.0 - y;
1199         } else if (alist->parameter().type() == PluginAutomation) {
1200                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1201         } else {
1202                 y = y / (double)alist->parameter().max(); /* ... like this */
1203         }
1204 }
1205
1206         
1207 void
1208 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1209 {
1210         _interpolation = style;
1211
1212         if (style == AutomationList::Discrete) {
1213                 show_all_control_points();
1214                 line->hide();
1215         } else {
1216                 hide_all_but_selected_control_points();
1217                 line->show();
1218         }
1219 }
1220