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