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