Fix range problems for pitch wheel controller.
[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                 if (isnan (tx) || isnan (ty)) {
467                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
468                                                    _name) << endmsg;
469                         continue;
470                 }
471
472                 /* now ensure that the control_points vector reflects the current curve
473                    state, but don't plot control points too close together. also, don't
474                    plot a series of points all with the same value.
475
476                    always plot the first and last points, of course.
477                 */
478
479                 if (invalid_point (points, pi)) {
480                         /* for some reason, we are supposed to ignore this point,
481                            but still keep track of the model index.
482                         */
483                         continue;
484                 }
485
486                 if (pi > 0 && pi < npoints - 1) {
487                         if (slope[pi] == slope[pi-1]) {
488
489                                 /* no reason to display this point */
490                                 
491                                 continue;
492                         }
493                 }
494                 
495                 /* need to round here. the ultimate coordinates are integer
496                    pixels, so tiny deltas in the coords will be eliminated
497                    and we end up with "colinear" line segments. since the
498                    line rendering code in libart doesn't like this very
499                    much, we eliminate them here. don't do this for the first and last
500                    points.
501                 */
502
503                 this_rx = (uint32_t) rint (tx);
504                 this_ry = (uint32_t) rint (ty); 
505  
506                 if (view_index && pi != npoints && /* not the first, not the last */
507                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
508                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
509                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
510                         continue;
511                 } 
512
513                 /* ok, we should display this point */
514
515                 if (view_index >= cpsize) {
516
517                         /* make sure we have enough control points */
518
519                         ControlPoint* ncp = new ControlPoint (*this);
520                         
521                         ncp->set_size (box_size); 
522
523                         control_points.push_back (ncp);
524                         ++cpsize;
525                 }
526
527                 ControlPoint::ShapeType shape;
528
529                 if (!terminal_points_can_slide) {
530                         if (pi == 0) {
531                                 control_points[view_index]->set_can_slide(false);
532                                 if (tx == 0) {
533                                         shape = ControlPoint::Start;
534                                 } else {
535                                         shape = ControlPoint::Full;
536                                 }
537                         } else if (pi == npoints - 1) {
538                                 control_points[view_index]->set_can_slide(false);
539                                 shape = ControlPoint::End;
540                         } else {
541                                 control_points[view_index]->set_can_slide(true);
542                                 shape = ControlPoint::Full;
543                         }
544                 } else {
545                         control_points[view_index]->set_can_slide(true);
546                         shape = ControlPoint::Full;
547                 }
548
549                 last_control_point_x = tx;
550                 last_control_point_y = ty;
551
552                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
553
554                 prev_rx = this_rx;
555                 prev_ry = this_ry;
556
557                 /* finally, control visibility */
558                 
559                 if (_visible && points_visible) {
560                         control_points[view_index]->show ();
561                         control_points[view_index]->set_visible (true);
562                 } else {
563                         if (!points_visible) {
564                                 control_points[view_index]->set_visible (false);
565                         }
566                 }
567
568                 view_index++;
569         }
570         
571         /* discard extra CP's to avoid confusing ourselves */
572
573         while (control_points.size() > view_index) {
574                 ControlPoint* cp = control_points.back();
575                 control_points.pop_back ();
576                 delete cp;
577         }
578
579         if (!terminal_points_can_slide) {
580                 control_points.back()->set_can_slide(false);
581         }
582
583         delete [] slope;
584
585         if (view_index > 1) {
586
587                 npoints = view_index;
588                 
589                 /* reset the line coordinates */
590
591                 while (line_points.size() < npoints) {
592                         line_points.push_back (Art::Point (0,0));
593                 }
594
595                 while (line_points.size() > npoints) {
596                         line_points.pop_back ();
597                 }
598
599                 for (view_index = 0; view_index < npoints; ++view_index) {
600                         line_points[view_index].set_x (control_points[view_index]->get_x());
601                         line_points[view_index].set_y (control_points[view_index]->get_y());
602                 }
603                 
604                 line->property_points() = line_points;
605
606                 if (_visible && _interpolation != AutomationList::Discrete)
607                         line->show();
608
609         } 
610
611         set_selected_points (trackview.editor.get_selection().points);
612
613 }
614
615 string
616 AutomationLine::get_verbose_cursor_string (double fraction)
617 {
618         char buf[32];
619
620         if (_vc_uses_gain_mapping) {
621                 if (fraction == 0.0) {
622                         snprintf (buf, sizeof (buf), "-inf dB");
623                 } else {
624                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
625                 }
626         } else {
627                 view_to_model_y(fraction);
628                 if (alist->parameter().type() == MidiCCAutomation)
629                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
630                 else
631                         snprintf (buf, sizeof (buf), "%.2f", fraction);
632         }
633
634         return buf;
635 }
636
637 bool
638 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
639 {
640         return p[index].x == max_frames && p[index].y == DBL_MAX;
641 }
642
643 void
644 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
645 {
646         p[index].x = max_frames;
647         p[index].y = DBL_MAX;
648 }
649
650 void
651 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
652 {
653         if (trackview.editor.current_session() == 0) { /* how? */
654                 return;
655         }
656
657         string str;
658
659         if (cp) {
660                 str = _("automation event move");
661         } else {
662                 str = _("automation range drag");
663         }
664
665         trackview.editor.current_session()->begin_reversible_command (str);
666         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
667         
668         drag_x = x;
669         drag_distance = 0;
670         first_drag_fraction = fraction;
671         last_drag_fraction = fraction;
672         drags = 0;
673         did_push = false;
674 }
675
676 void
677 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
678 {
679         if (x > drag_x) {
680                 drag_distance += (x - drag_x);
681         } else {
682                 drag_distance -= (drag_x - x);
683         }
684
685         drag_x = x;
686
687         modify_view_point (cp, x, fraction, with_push);
688
689         if (line_points.size() > 1) {
690                 line->property_points() = line_points;
691         }
692
693         drags++;
694         did_push = with_push;
695 }
696
697 void
698 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
699 {
700         double const ydelta = fraction - last_drag_fraction;
701
702         did_push = with_push;
703         
704         last_drag_fraction = fraction;
705
706         line_drag_cp1 = i1;
707         line_drag_cp2 = i2;
708         
709         ControlPoint *cp;
710
711         for (uint32_t i = i1 ; i <= i2; i++) {
712                 cp = nth (i);
713                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
714         }
715
716         if (line_points.size() > 1) {
717                 line->property_points() = line_points;
718         }
719
720         drags++;
721 }
722
723 void
724 AutomationLine::end_drag (ControlPoint* cp) 
725 {
726         if (!drags) {
727                 return;
728         }
729
730         alist->freeze ();
731
732         if (cp) {
733                 sync_model_with_view_point (*cp, did_push, drag_distance);
734         } else {
735                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
736         }
737         
738         alist->thaw ();
739
740         update_pending = false;
741
742         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
743         trackview.editor.current_session()->commit_reversible_command ();
744         trackview.editor.current_session()->set_dirty ();
745 }
746
747
748 void
749 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
750 {
751         ModelRepresentation mr;
752         double ydelta;
753
754         model_representation (cp, mr);
755
756         /* how much are we changing the central point by */ 
757
758         ydelta = mr.yval - mr.ypos;
759
760         /*
761            apply the full change to the central point, and interpolate
762            on both axes to cover all model points represented
763            by the control point.
764         */
765
766         /* change all points before the primary point */
767
768         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
769                 
770                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
771                 double y_delta = ydelta * fract;
772                 double x_delta = distance * fract;
773
774                 /* interpolate */
775                 
776                 if (y_delta || x_delta) {
777                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
778                 }
779         }
780
781         /* change the primary point */
782
783         update_pending = true;
784         alist->modify (cp.model(), mr.xval, mr.yval);
785
786
787         /* change later points */
788         
789         AutomationList::iterator i = cp.model();
790         
791         ++i;
792         
793         while (i != mr.end) {
794                 
795                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
796
797                 /* all later points move by the same distance along the x-axis as the main point */
798                 
799                 if (delta) {
800                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
801                 }
802                 
803                 ++i;
804         }
805                 
806         if (did_push) {
807
808                 /* move all points after the range represented by the view by the same distance
809                    as the main point moved.
810                 */
811
812                 alist->slide (mr.end, drag_distance);
813         }
814
815 }
816
817 bool 
818 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
819 {
820         ControlPoint *bcp = 0;
821         ControlPoint *acp = 0;
822         double unit_xval;
823
824         /* xval is in frames */
825
826         unit_xval = trackview.editor.frame_to_unit (xval);
827
828         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
829                 
830                 if ((*i)->get_x() <= unit_xval) {
831
832                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
833                                 bcp = *i;
834                                 before = bcp->view_index();
835                         } 
836
837                 } else if ((*i)->get_x() > unit_xval) {
838                         acp = *i;
839                         after = acp->view_index();
840                         break;
841                 }
842         }
843
844         return bcp && acp;
845 }
846
847 bool
848 AutomationLine::is_last_point (ControlPoint& cp)
849 {
850         ModelRepresentation mr;
851
852         model_representation (cp, mr);
853
854         // If the list is not empty, and the point is the last point in the list
855
856         if (!alist->empty() && mr.end == alist->end()) {
857                 return true;
858         }
859         
860         return false;
861 }
862
863 bool
864 AutomationLine::is_first_point (ControlPoint& cp)
865 {
866         ModelRepresentation mr;
867
868         model_representation (cp, mr);
869
870         // If the list is not empty, and the point is the first point in the list
871
872         if (!alist->empty() && mr.start == alist->begin()) {
873                 return true;
874         }
875         
876         return false;
877 }
878
879 // This is copied into AudioRegionGainLine
880 void
881 AutomationLine::remove_point (ControlPoint& cp)
882 {
883         ModelRepresentation mr;
884
885         model_representation (cp, mr);
886
887         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
888         XMLNode &before = alist->get_state();
889
890         alist->erase (mr.start, mr.end);
891
892         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
893                         *alist.get(), &before, &alist->get_state()));
894         trackview.editor.current_session()->commit_reversible_command ();
895         trackview.editor.current_session()->set_dirty ();
896 }
897
898 void
899 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
900                                  double botfrac, double topfrac, list<Selectable*>& results)
901 {
902
903         double top;
904         double bot;
905         nframes_t nstart;
906         nframes_t nend;
907         bool collecting = false;
908
909         /* Curse X11 and its inverted coordinate system! */
910         
911         bot = _y_position + (1.0 - topfrac) * _height;
912         top = _y_position + (1.0 - botfrac) * _height;
913         
914         nstart = max_frames;
915         nend = 0;
916
917         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
918                 
919                 nframes_t when = (nframes_t) (*(*i)->model())->when;
920
921                 if (when >= start && when <= end) {
922                         
923                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
924
925                                 (*i)->show();
926                                 (*i)->set_visible(true);
927                                 collecting = true;
928                                 nstart = min (nstart, when);
929                                 nend = max (nend, when);
930
931                         } else {
932                                 
933                                 if (collecting) {
934
935                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
936                                         collecting = false;
937                                         nstart = max_frames;
938                                         nend = 0;
939                                 }
940                         }
941                 }
942         }
943
944         if (collecting) {
945                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
946         }
947
948 }
949
950 void
951 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
952 {
953         // hmmm ....
954 }
955
956 void
957 AutomationLine::set_selected_points (PointSelection& points)
958 {
959         double top;
960         double bot;
961
962         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
963                         (*i)->set_selected(false);
964         }
965
966         if (points.empty()) {
967                 goto out;
968         } 
969         
970         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
971                 
972                 if (&(*r).track != &trackview) {
973                         continue;
974                 }
975
976                 /* Curse X11 and its inverted coordinate system! */
977
978                 bot = _y_position + (1.0 - (*r).high_fract) * _height;
979                 top = _y_position + (1.0 - (*r).low_fract) * _height;
980
981                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
982                         
983                         double rstart, rend;
984                         
985                         rstart = trackview.editor.frame_to_unit ((*r).start);
986                         rend = trackview.editor.frame_to_unit ((*r).end);
987                         
988                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
989                                 
990                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
991                                         
992                                         (*i)->set_selected(true);
993                                 }
994                         }
995
996                 }
997         }
998
999   out:
1000         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1001                 (*i)->show_color (false, !points_visible);
1002         }
1003
1004 }
1005
1006 void AutomationLine::set_colors() {
1007         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1008         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1009                 (*i)->show_color (false, !points_visible);
1010         }
1011 }
1012
1013 void
1014 AutomationLine::show_selection ()
1015 {
1016         TimeSelection& time (trackview.editor.get_selection().time);
1017
1018         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1019                 
1020                 (*i)->set_selected(false);
1021
1022                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1023                         double rstart, rend;
1024                         
1025                         rstart = trackview.editor.frame_to_unit ((*r).start);
1026                         rend = trackview.editor.frame_to_unit ((*r).end);
1027                         
1028                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1029                                 (*i)->set_selected(true);
1030                                 break;
1031                         }
1032                 }
1033                 
1034                 (*i)->show_color (false, !points_visible);
1035         }
1036 }
1037
1038 void
1039 AutomationLine::hide_selection ()
1040 {
1041 //      show_selection ();
1042 }
1043
1044 void
1045 AutomationLine::list_changed ()
1046 {
1047         queue_reset ();
1048 }
1049
1050 void
1051 AutomationLine::reset_callback (const AutomationList& events)
1052 {
1053         ALPoints tmp_points;
1054         uint32_t npoints = events.size();
1055
1056         if (npoints == 0) {
1057                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1058                         delete *i;
1059                 }
1060                 control_points.clear ();
1061                 line->hide();
1062                 return;
1063         }
1064
1065         AutomationList::const_iterator ai;
1066
1067         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1068                 
1069                 double translated_y = (*ai)->value;
1070                 model_to_view_y (translated_y);
1071
1072                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1073                                                _y_position + _height - (translated_y * _height)));
1074         }
1075         
1076         determine_visible_control_points (tmp_points);
1077 }
1078
1079 void
1080 AutomationLine::reset ()
1081 {
1082         update_pending = false;
1083
1084         if (no_draw) {
1085                 return;
1086         }
1087
1088         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1089 }
1090
1091 void
1092 AutomationLine::clear ()
1093 {
1094         /* parent must create command */
1095         XMLNode &before = get_state();
1096         alist->clear();
1097         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1098         trackview.editor.current_session()->commit_reversible_command ();
1099         trackview.editor.current_session()->set_dirty ();
1100 }
1101
1102 void
1103 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1104 {
1105 }
1106
1107 void
1108 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1109 {
1110         alist->move_range (start, end, xdelta, ydelta);
1111 }
1112
1113 void
1114 AutomationLine::show_all_control_points ()
1115 {
1116         points_visible = true;
1117
1118         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1119                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1120                 (*i)->show ();
1121                 (*i)->set_visible (true);
1122         }
1123 }
1124
1125 void
1126 AutomationLine::hide_all_but_selected_control_points ()
1127 {
1128         if (alist->interpolation() == AutomationList::Discrete)
1129                 return;
1130
1131         points_visible = false;
1132
1133         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1134                 if (!(*i)->selected()) {
1135                         (*i)->set_visible (false);
1136                 }
1137         }
1138 }
1139         
1140 void
1141 AutomationLine::track_entered()
1142 {
1143         if (alist->interpolation() != AutomationList::Discrete)
1144                 show_all_control_points();
1145 }
1146
1147 void
1148 AutomationLine::track_exited()
1149 {
1150         if (alist->interpolation() != AutomationList::Discrete) {
1151                 hide_all_but_selected_control_points();
1152         }
1153 }
1154
1155 XMLNode &
1156 AutomationLine::get_state (void)
1157 {
1158         /* function as a proxy for the model */
1159         return alist->get_state();
1160 }
1161
1162 int 
1163 AutomationLine::set_state (const XMLNode &node)
1164 {
1165         /* function as a proxy for the model */
1166         return alist->set_state (node);
1167 }
1168
1169 void
1170 AutomationLine::view_to_model_y (double& y)
1171 {
1172         /* TODO: This should be more generic ... */
1173         if (alist->parameter().type() == GainAutomation) {
1174                 y = slider_position_to_gain (y);
1175                 y = max (0.0, y);
1176                 y = min (2.0, y);
1177         } else if (alist->parameter().type() == PanAutomation) {
1178                 // vertical coordinate axis reversal
1179                 y = 1.0 - y;
1180         } else if (alist->parameter().type() == PluginAutomation) {
1181                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1182         } else {
1183                 y = (int)(y * alist->parameter().max());
1184         }
1185 }
1186
1187 void
1188 AutomationLine::model_to_view_y (double& y)
1189 {
1190         /* TODO: This should be more generic ... */
1191         if (alist->parameter().type() == GainAutomation) {
1192                 y = gain_to_slider_position (y);
1193         } else if (alist->parameter().type() == PanAutomation) {
1194                 // vertical coordinate axis reversal
1195                 y = 1.0 - y;
1196         } else if (alist->parameter().type() == PluginAutomation) {
1197                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1198         } else {
1199                 y = y / (double)alist->parameter().max(); /* ... like this */
1200         }
1201 }
1202
1203         
1204 void
1205 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1206 {
1207         _interpolation = style;
1208
1209         if (style == AutomationList::Discrete) {
1210                 show_all_control_points();
1211                 line->hide();
1212         } else {
1213                 hide_all_but_selected_control_points();
1214                 line->show();
1215         }
1216 }
1217