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