Part 1 of loading 2.X sessions; some things work, some things don't, hacks a-plenty.
[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", accurate_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         //check if one of the control points on the line is in a selected range
784         bool range_found = false;
785         ControlPoint *cp;
786
787         for (uint32_t i = i1 ; i <= i2; i++) {
788                 cp = nth (i);
789                 if (cp->selected()) {
790                         range_found = true;
791                 }
792         }
793
794         if (range_found) {
795                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
796                         if ((*i)->selected()) {
797                                 modify_view_point (*(*i), trackview.editor().unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push);
798                         }
799                 }
800         } else {
801                 ControlPoint *cp;
802                 for (uint32_t i = i1 ; i <= i2; i++) {
803                         cp = nth (i);
804                         modify_view_point (*cp, trackview.editor().unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
805                 }
806         }
807
808         if (line_points.size() > 1) {
809                 line->property_points() = line_points;
810         }
811
812         drags++;
813 }
814
815 void
816 AutomationLine::end_drag (ControlPoint* cp)
817 {
818         if (!drags) {
819                 return;
820         }
821
822         alist->freeze ();
823
824         if (cp) {
825                 sync_model_with_view_point (*cp, did_push, drag_distance);
826         } else {
827                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
828         }
829
830         alist->thaw ();
831
832         update_pending = false;
833
834         trackview.editor().current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
835         trackview.editor().current_session()->commit_reversible_command ();
836         trackview.editor().current_session()->set_dirty ();
837 }
838
839
840 void
841 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
842 {
843         ModelRepresentation mr;
844         double ydelta;
845
846         model_representation (cp, mr);
847
848         /* how much are we changing the central point by */
849
850         ydelta = mr.yval - mr.ypos;
851
852         /*
853            apply the full change to the central point, and interpolate
854            on both axes to cover all model points represented
855            by the control point.
856         */
857
858         /* change all points before the primary point */
859
860         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
861
862                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
863                 double y_delta = ydelta * fract;
864                 double x_delta = distance * fract;
865
866                 /* interpolate */
867
868                 if (y_delta || x_delta) {
869                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
870                 }
871         }
872
873         /* change the primary point */
874
875         update_pending = true;
876         alist->modify (cp.model(), mr.xval, mr.yval);
877
878
879         /* change later points */
880
881         AutomationList::iterator i = cp.model();
882
883         ++i;
884
885         while (i != mr.end) {
886
887                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
888
889                 /* all later points move by the same distance along the x-axis as the main point */
890
891                 if (delta) {
892                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
893                 }
894
895                 ++i;
896         }
897
898         if (did_push) {
899
900                 /* move all points after the range represented by the view by the same distance
901                    as the main point moved.
902                 */
903
904                 alist->slide (mr.end, drag_distance);
905         }
906
907 }
908
909 bool
910 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
911 {
912         ControlPoint *bcp = 0;
913         ControlPoint *acp = 0;
914         double unit_xval;
915
916         unit_xval = trackview.editor().frame_to_unit (xval);
917
918         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
919
920                 if ((*i)->get_x() <= unit_xval) {
921
922                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
923                                 bcp = *i;
924                                 before = bcp->view_index();
925                         }
926
927                 } else if ((*i)->get_x() > unit_xval) {
928                         acp = *i;
929                         after = acp->view_index();
930                         break;
931                 }
932         }
933
934         return bcp && acp;
935 }
936
937 bool
938 AutomationLine::is_last_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 last point in the list
945
946         if (!alist->empty() && mr.end == alist->end()) {
947                 return true;
948         }
949
950         return false;
951 }
952
953 bool
954 AutomationLine::is_first_point (ControlPoint& cp)
955 {
956         ModelRepresentation mr;
957
958         model_representation (cp, mr);
959
960         // If the list is not empty, and the point is the first point in the list
961
962         if (!alist->empty() && mr.start == alist->begin()) {
963                 return true;
964         }
965
966         return false;
967 }
968
969 // This is copied into AudioRegionGainLine
970 void
971 AutomationLine::remove_point (ControlPoint& cp)
972 {
973         ModelRepresentation mr;
974
975         model_representation (cp, mr);
976
977         trackview.editor().current_session()->begin_reversible_command (_("remove control point"));
978         XMLNode &before = alist->get_state();
979
980         alist->erase (mr.start, mr.end);
981
982         trackview.editor().current_session()->add_command(new MementoCommand<AutomationList>(
983                         *alist.get(), &before, &alist->get_state()));
984         trackview.editor().current_session()->commit_reversible_command ();
985         trackview.editor().current_session()->set_dirty ();
986 }
987
988 void
989 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
990                 double botfrac, double topfrac, list<Selectable*>& results)
991 {
992
993         double top;
994         double bot;
995         double nstart;
996         double nend;
997         bool collecting = false;
998
999         /* Curse X11 and its inverted coordinate system! */
1000
1001         bot = (1.0 - topfrac) * _height;
1002         top = (1.0 - botfrac) * _height;
1003
1004         nstart = max_frames;
1005         nend = 0;
1006
1007         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1008                 double when = (*(*i)->model())->when;
1009
1010                 if (when >= start && when <= end) {
1011
1012                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1013
1014                                 (*i)->show();
1015                                 (*i)->set_visible(true);
1016                                 collecting = true;
1017                                 nstart = min (nstart, when);
1018                                 nend = max (nend, when);
1019
1020                         } else {
1021
1022                                 if (collecting) {
1023
1024                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1025                                         collecting = false;
1026                                         nstart = max_frames;
1027                                         nend = 0;
1028                                 }
1029                         }
1030                 }
1031         }
1032
1033         if (collecting) {
1034                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1035         }
1036
1037 }
1038
1039 void
1040 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1041 {
1042         // hmmm ....
1043 }
1044
1045 void
1046 AutomationLine::set_selected_points (PointSelection& points)
1047 {
1048         double top;
1049         double bot;
1050
1051         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1052                         (*i)->set_selected(false);
1053         }
1054
1055         if (points.empty()) {
1056                 goto out;
1057         }
1058
1059         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1060
1061                 if (&(*r).track != &trackview) {
1062                         continue;
1063                 }
1064
1065                 /* Curse X11 and its inverted coordinate system! */
1066
1067                 bot = (1.0 - (*r).high_fract) * _height;
1068                 top = (1.0 - (*r).low_fract) * _height;
1069
1070                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1071
1072                         double rstart, rend;
1073
1074                         rstart = trackview.editor().frame_to_unit ((*r).start);
1075                         rend = trackview.editor().frame_to_unit ((*r).end);
1076
1077                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1078
1079                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1080
1081                                         (*i)->set_selected(true);
1082                                 }
1083                         }
1084
1085                 }
1086         }
1087
1088   out:
1089         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1090                 (*i)->show_color (false, !points_visible);
1091         }
1092
1093 }
1094
1095 void AutomationLine::set_colors() {
1096         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1097         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1098                 (*i)->show_color (false, !points_visible);
1099         }
1100 }
1101
1102 void
1103 AutomationLine::show_selection ()
1104 {
1105         TimeSelection& time (trackview.editor().get_selection().time);
1106
1107         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1108
1109                 (*i)->set_selected(false);
1110
1111                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1112                         double rstart, rend;
1113
1114                         rstart = trackview.editor().frame_to_unit ((*r).start);
1115                         rend = trackview.editor().frame_to_unit ((*r).end);
1116
1117                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1118                                 (*i)->set_selected(true);
1119                                 break;
1120                         }
1121                 }
1122
1123                 (*i)->show_color (false, !points_visible);
1124         }
1125 }
1126
1127 void
1128 AutomationLine::hide_selection ()
1129 {
1130 //      show_selection ();
1131 }
1132
1133 void
1134 AutomationLine::list_changed ()
1135 {
1136         queue_reset ();
1137 }
1138
1139 void
1140 AutomationLine::reset_callback (const Evoral::ControlList& events)
1141 {
1142         ALPoints tmp_points;
1143         uint32_t npoints = events.size();
1144
1145         if (npoints == 0) {
1146                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1147                         delete *i;
1148                 }
1149                 control_points.clear ();
1150                 line->hide();
1151                 return;
1152         }
1153
1154         AutomationList::const_iterator ai;
1155
1156         for (ai = events.begin(); ai != events.end(); ++ai) {
1157
1158                 double translated_x = (*ai)->when;
1159                 double translated_y = (*ai)->value;
1160                 model_to_view_coord (translated_x, translated_y);
1161
1162                 add_model_point (tmp_points, (*ai)->when, translated_y);
1163         }
1164
1165         determine_visible_control_points (tmp_points);
1166 }
1167
1168
1169 void
1170 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1171 {
1172         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1173                                        _height - (yfract * _height)));
1174 }
1175
1176 void
1177 AutomationLine::reset ()
1178 {
1179         update_pending = false;
1180
1181         if (no_draw) {
1182                 return;
1183         }
1184
1185         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1186 }
1187
1188 void
1189 AutomationLine::clear ()
1190 {
1191         /* parent must create command */
1192         XMLNode &before = get_state();
1193         alist->clear();
1194         trackview.editor().current_session()->add_command (
1195                         new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1196         trackview.editor().current_session()->commit_reversible_command ();
1197         trackview.editor().current_session()->set_dirty ();
1198 }
1199
1200 void
1201 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1202 {
1203 }
1204
1205 void
1206 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1207 {
1208         alist = list;
1209         queue_reset();
1210 }
1211
1212 void
1213 AutomationLine::show_all_control_points ()
1214 {
1215         points_visible = true;
1216
1217         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1218                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1219                 (*i)->show ();
1220                 (*i)->set_visible (true);
1221         }
1222 }
1223
1224 void
1225 AutomationLine::hide_all_but_selected_control_points ()
1226 {
1227         if (alist->interpolation() == AutomationList::Discrete) {
1228                 return;
1229         }
1230
1231         points_visible = false;
1232
1233         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1234                 if (!(*i)->selected()) {
1235                         (*i)->set_visible (false);
1236                 }
1237         }
1238 }
1239
1240 void
1241 AutomationLine::track_entered()
1242 {
1243         if (alist->interpolation() != AutomationList::Discrete) {
1244                 show_all_control_points();
1245         }
1246 }
1247
1248 void
1249 AutomationLine::track_exited()
1250 {
1251         if (alist->interpolation() != AutomationList::Discrete) {
1252                 hide_all_but_selected_control_points();
1253         }
1254 }
1255
1256 XMLNode &
1257 AutomationLine::get_state (void)
1258 {
1259         /* function as a proxy for the model */
1260         return alist->get_state();
1261 }
1262
1263 int 
1264 AutomationLine::set_state (const XMLNode &node, int version)
1265 {
1266         /* function as a proxy for the model */
1267         return alist->set_state (node);
1268 }
1269
1270 void
1271 AutomationLine::view_to_model_coord (double& x, double& y) const
1272 {
1273         /* TODO: This should be more generic ... */
1274         if (alist->parameter().type() == GainAutomation ||
1275             alist->parameter().type() == EnvelopeAutomation) {
1276                 y = slider_position_to_gain (y);
1277                 y = max (0.0, y);
1278                 y = min (2.0, y);
1279         } else if (alist->parameter().type() == PanAutomation) {
1280                 // vertical coordinate axis reversal
1281                 y = 1.0 - y;
1282         } else if (alist->parameter().type() == PluginAutomation) {
1283                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1284         } else {
1285                 y = (int)(y * alist->parameter().max());
1286         }
1287
1288         x = _time_converter.from(x);
1289 }
1290
1291 void
1292 AutomationLine::model_to_view_coord (double& x, double& y) const
1293 {
1294         /* TODO: This should be more generic ... */
1295         if (alist->parameter().type() == GainAutomation ||
1296             alist->parameter().type() == EnvelopeAutomation) {
1297                 y = gain_to_slider_position (y);
1298         } else if (alist->parameter().type() == PanAutomation) {
1299                 // vertical coordinate axis reversal
1300                 y = 1.0 - y;
1301         } else if (alist->parameter().type() == PluginAutomation) {
1302                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1303         } else {
1304                 y = y / (double)alist->parameter().max(); /* ... like this */
1305         }
1306
1307         x = _time_converter.to(x);
1308 }
1309
1310
1311 void
1312 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1313 {
1314         _interpolation = style;
1315
1316         if (style == AutomationList::Discrete) {
1317                 show_all_control_points();
1318                 line->hide();
1319         } else {
1320                 hide_all_but_selected_control_points();
1321                 line->show();
1322         }
1323 }
1324