fix message in startup wizard re: alpha
[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 "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
40 #include "utils.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.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 ARDOUR;
54 using namespace PBD;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
57
58 static const Evoral::IdentityConverter<double, framepos_t> default_converter;
59
60 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
61                 boost::shared_ptr<AutomationList> al,
62                 const Evoral::TimeConverter<double, framepos_t>* converter)
63         : trackview (tv)
64         , _name (name)
65         , alist (al)
66         , _parent_group (parent)
67         , _offset (0)
68         , _time_converter (converter ? (*converter) : default_converter)
69         , _maximum_time (max_framepos)
70 {
71         points_visible = false;
72         update_pending = false;
73         _uses_gain_mapping = false;
74         no_draw = false;
75         _visible = true;
76         _is_boolean = false;
77         terminal_points_can_slide = true;
78         _height = 0;
79
80         group = new ArdourCanvas::Group (parent);
81         group->property_x() = 0.0;
82         group->property_y() = 0.0;
83
84         line = new ArdourCanvas::Line (*group);
85         line->property_width_pixels() = (guint)1;
86         line->set_data ("line", this);
87
88         line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
89
90         trackview.session()->register_with_memento_command_factory(alist->id(), this);
91
92         if (alist->parameter().type() == GainAutomation ||
93             alist->parameter().type() == EnvelopeAutomation) {
94                 set_uses_gain_mapping (true);
95         }
96
97         interpolation_changed (alist->interpolation ());
98
99         connect_to_list ();
100 }
101
102 AutomationLine::~AutomationLine ()
103 {
104         vector_delete (&control_points);
105         delete group;
106 }
107
108 bool
109 AutomationLine::event_handler (GdkEvent* event)
110 {
111         return PublicEditor::instance().canvas_line_event (event, line, this);
112 }
113
114 void
115 AutomationLine::queue_reset ()
116 {
117         if (!update_pending) {
118                 update_pending = true;
119                 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
120         }
121 }
122
123 void
124 AutomationLine::show ()
125 {
126         if (alist->interpolation() != AutomationList::Discrete) {
127                 line->show();
128         }
129
130         if (points_visible) {
131                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
132                         (*i)->show ();
133                 }
134         }
135
136         _visible = true;
137 }
138
139 void
140 AutomationLine::hide ()
141 {
142         line->hide();
143         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
144                 (*i)->hide();
145         }
146         _visible = false;
147 }
148
149 double
150 AutomationLine::control_point_box_size ()
151 {
152         if (alist->interpolation() == AutomationList::Discrete) {
153                 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
154                                 4.0);
155         }
156
157         if (_height > TimeAxisView::preset_height (HeightLarger)) {
158                 return 8.0;
159         } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
160                 return 6.0;
161         }
162         return 4.0;
163 }
164
165 void
166 AutomationLine::set_height (guint32 h)
167 {
168         if (h != _height) {
169                 _height = h;
170
171                 double bsz = control_point_box_size();
172
173                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
174                         (*i)->set_size (bsz);
175                 }
176
177                 reset ();
178         }
179 }
180
181 void
182 AutomationLine::set_line_color (uint32_t color)
183 {
184         _line_color = color;
185         line->property_fill_color_rgba() = color;
186 }
187
188 void
189 AutomationLine::set_uses_gain_mapping (bool yn)
190 {
191         if (yn != _uses_gain_mapping) {
192                 _uses_gain_mapping = yn;
193                 reset ();
194         }
195 }
196
197 ControlPoint*
198 AutomationLine::nth (uint32_t n)
199 {
200         if (n < control_points.size()) {
201                 return control_points[n];
202         } else {
203                 return 0;
204         }
205 }
206
207 ControlPoint const *
208 AutomationLine::nth (uint32_t n) const
209 {
210         if (n < control_points.size()) {
211                 return control_points[n];
212         } else {
213                 return 0;
214         }
215 }
216
217 void
218 AutomationLine::modify_point_y (ControlPoint& cp, double y)
219 {
220         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
221            and needs to be converted to a canvas unit distance.
222         */
223
224         y = max (0.0, y);
225         y = min (1.0, y);
226         y = _height - (y * _height);
227
228         double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset);
229
230         trackview.editor().session()->begin_reversible_command (_("automation event move"));
231         trackview.editor().session()->add_command (
232                 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
233                 );
234
235         cp.move_to (x, y, ControlPoint::Full);
236
237         reset_line_coords (cp);
238
239         if (line_points.size() > 1) {
240                 line->property_points() = line_points;
241         }
242
243         alist->freeze ();
244         sync_model_with_view_point (cp, false, 0);
245         alist->thaw ();
246
247         update_pending = false;
248
249         trackview.editor().session()->add_command (
250                 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
251                 );
252
253         trackview.editor().session()->commit_reversible_command ();
254         trackview.editor().session()->set_dirty ();
255 }
256
257 void
258 AutomationLine::reset_line_coords (ControlPoint& cp)
259 {
260         if (cp.view_index() < line_points.size()) {
261                 line_points[cp.view_index()].set_x (cp.get_x());
262                 line_points[cp.view_index()].set_y (cp.get_y());
263         }
264 }
265
266 void
267 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
268 {
269         update_pending = true;
270
271         for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
272                 sync_model_with_view_point (**i, did_push, distance);
273         }
274 }
275
276 void
277 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
278 {
279         /* part one: find out where the visual control point is.
280            initial results are in canvas units. ask the
281            line to convert them to something relevant.
282         */
283
284         mr.xval = cp.get_x();
285         mr.yval = 1.0 - (cp.get_y() / _height);
286
287         /* if xval has not changed, set it directly from the model to avoid rounding errors */
288
289         if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) {
290                 mr.xval = (*cp.model())->when - _offset;
291         } else {
292                 mr.xval = trackview.editor().unit_to_frame (mr.xval);
293                 mr.xval = _time_converter.from (mr.xval + _offset);
294         }
295
296         /* convert y to model units; the x was already done above
297         */
298
299         view_to_model_coord_y (mr.yval);
300
301         /* part 2: find out where the model point is now
302          */
303
304         mr.xpos = (*cp.model())->when - _offset;
305         mr.ypos = (*cp.model())->value;
306
307         /* part 3: get the position of the visual control
308            points before and after us.
309         */
310
311         ControlPoint* before;
312         ControlPoint* after;
313
314         if (cp.view_index()) {
315                 before = nth (cp.view_index() - 1);
316         } else {
317                 before = 0;
318         }
319
320         after = nth (cp.view_index() + 1);
321
322         if (before) {
323                 mr.xmin = (*before->model())->when;
324                 mr.ymin = (*before->model())->value;
325                 mr.start = before->model();
326                 ++mr.start;
327         } else {
328                 mr.xmin = mr.xpos;
329                 mr.ymin = mr.ypos;
330                 mr.start = cp.model();
331         }
332
333         if (after) {
334                 mr.end = after->model();
335         } else {
336                 mr.xmax = mr.xpos;
337                 mr.ymax = mr.ypos;
338                 mr.end = cp.model();
339                 ++mr.end;
340         }
341 }
342
343 /** @param points AutomationLine points to consider.  These will correspond 1-to-1 to
344  *  points in the AutomationList, but will have been transformed so that they are in pixels;
345  *  the x coordinate being the pixel distance from the start of the line (0, or the start
346  *  of the AutomationRegionView if we are in one).
347  *
348  *  @param skipped Number of points in the AutomationList that were skipped before
349  *  `points' starts.
350  */
351
352 void
353 AutomationLine::determine_visible_control_points (ALPoints& points, int skipped)
354 {
355         uint32_t view_index, pi, n;
356         uint32_t npoints;
357         uint32_t this_rx = 0;
358         uint32_t prev_rx = 0;
359         uint32_t this_ry = 0;
360         uint32_t prev_ry = 0;
361         double* slope;
362         uint32_t box_size;
363
364         /* hide all existing points, and the line */
365
366         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
367                 (*i)->hide();
368         }
369
370         line->hide ();
371
372         if (points.empty()) {
373                 return;
374         }
375
376         npoints = points.size();
377
378         /* compute derivative/slope for the entire line */
379
380         slope = new double[npoints];
381
382         for (n = 0; n < npoints - 1; ++n) {
383                 double xdelta = points[n+1].x - points[n].x;
384                 double ydelta = points[n+1].y - points[n].y;
385                 slope[n] = ydelta/xdelta;
386         }
387
388         box_size = (uint32_t) control_point_box_size ();
389
390         /* read all points and decide which ones to show as control points */
391
392         view_index = 0;
393
394         /* skip over unused AutomationList points before we start */
395
396         AutomationList::iterator model = alist->begin ();
397         for (int i = 0; i < skipped; ++i) {
398                 ++model;
399         }
400
401         for (pi = 0; pi < npoints; ++model, ++pi) {
402
403                 /* If this line is in an AutomationRegionView, this is an offset from the region position, in pixels */
404                 double tx = points[pi].x;
405                 double ty = points[pi].y;
406
407                 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
408                         add_visible_control_point (view_index, pi, tx, ty, model, npoints);
409                         prev_rx = this_rx;
410                         prev_ry = this_ry;
411                         ++view_index;
412                         continue;
413                 }
414
415                 if (isnan (tx) || isnan (ty)) {
416                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
417                                                    _name) << endmsg;
418                         continue;
419                 }
420
421                 /* now ensure that the control_points vector reflects the current curve
422                    state, but don't plot control points too close together. also, don't
423                    plot a series of points all with the same value.
424
425                    always plot the first and last points, of course.
426                 */
427
428                 if (invalid_point (points, pi)) {
429                         /* for some reason, we are supposed to ignore this point,
430                            but still keep track of the model index.
431                         */
432                         continue;
433                 }
434
435                 if (pi > 0 && pi < npoints - 1) {
436                         if (slope[pi] == slope[pi-1]) {
437
438                                 /* no reason to display this point */
439
440                                 continue;
441                         }
442                 }
443
444                 /* need to round here. the ultimate coordinates are integer
445                    pixels, so tiny deltas in the coords will be eliminated
446                    and we end up with "colinear" line segments. since the
447                    line rendering code in libart doesn't like this very
448                    much, we eliminate them here. don't do this for the first and last
449                    points.
450                 */
451
452                 this_rx = (uint32_t) rint (tx);
453                 this_ry = (uint32_t) rint (ty);
454
455                 if (view_index && pi != npoints && /* not the first, not the last */
456                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
457                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
458                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
459                         continue;
460                 }
461
462                 /* ok, we should display this point */
463
464                 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
465
466                 prev_rx = this_rx;
467                 prev_ry = this_ry;
468
469                 view_index++;
470         }
471
472         /* discard extra CP's to avoid confusing ourselves */
473
474         while (control_points.size() > view_index) {
475                 ControlPoint* cp = control_points.back();
476                 control_points.pop_back ();
477                 delete cp;
478         }
479
480         if (!terminal_points_can_slide) {
481                 control_points.back()->set_can_slide(false);
482         }
483
484         delete [] slope;
485
486         if (view_index > 1) {
487
488                 npoints = view_index;
489
490                 /* reset the line coordinates */
491
492                 while (line_points.size() < npoints) {
493                         line_points.push_back (Art::Point (0,0));
494                 }
495
496                 while (line_points.size() > npoints) {
497                         line_points.pop_back ();
498                 }
499
500                 for (view_index = 0; view_index < npoints; ++view_index) {
501                         line_points[view_index].set_x (control_points[view_index]->get_x());
502                         line_points[view_index].set_y (control_points[view_index]->get_y());
503                 }
504
505                 line->property_points() = line_points;
506
507                 if (_visible && alist->interpolation() != AutomationList::Discrete) {
508                         line->show();
509                 }
510
511         }
512
513         set_selected_points (trackview.editor().get_selection().points);
514 }
515
516 string
517 AutomationLine::get_verbose_cursor_string (double fraction) const
518 {
519         std::string s = fraction_to_string (fraction);
520         if (_uses_gain_mapping) {
521                 s += " dB";
522         }
523
524         return s;
525 }
526
527 /**
528  *  @param fraction y fraction
529  *  @return string representation of this value, using dB if appropriate.
530  */
531 string
532 AutomationLine::fraction_to_string (double fraction) const
533 {
534         char buf[32];
535
536         if (_uses_gain_mapping) {
537                 if (fraction == 0.0) {
538                         snprintf (buf, sizeof (buf), "-inf");
539                 } else {
540                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
541                 }
542         } else {
543                 view_to_model_coord_y (fraction);
544                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
545                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
546                 } else {
547                         snprintf (buf, sizeof (buf), "%.2f", fraction);
548                 }
549         }
550
551         return buf;
552 }
553
554
555 /**
556  *  @param s Value string in the form as returned by fraction_to_string.
557  *  @return Corresponding y fraction.
558  */
559 double
560 AutomationLine::string_to_fraction (string const & s) const
561 {
562         if (s == "-inf") {
563                 return 0;
564         }
565
566         double v;
567         sscanf (s.c_str(), "%lf", &v);
568
569         if (_uses_gain_mapping) {
570                 v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain());
571         } else {
572                 double dummy = 0.0;
573                 model_to_view_coord (dummy, v);
574         }
575
576         return v;
577 }
578
579 bool
580 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
581 {
582         return p[index].x == max_framepos && p[index].y == DBL_MAX;
583 }
584
585 void
586 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
587 {
588         p[index].x = max_framepos;
589         p[index].y = DBL_MAX;
590 }
591
592 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
593  *  are other selected points.
594  *
595  *  @param cp Point to drag.
596  *  @param x Initial x position (units).
597  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
598  */
599 void
600 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
601 {
602         trackview.editor().session()->begin_reversible_command (_("automation event move"));
603         trackview.editor().session()->add_command (
604                 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
605                 );
606
607         _drag_points.clear ();
608         _drag_points.push_back (cp);
609
610         if (cp->get_selected ()) {
611                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
612                         if (*i != cp && (*i)->get_selected()) {
613                                 _drag_points.push_back (*i);
614                         }
615                 }
616         }
617
618         start_drag_common (x, fraction);
619 }
620
621 /** Start dragging a line vertically (with no change in x)
622  *  @param i1 Control point index of the `left' point on the line.
623  *  @param i2 Control point index of the `right' point on the line.
624  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
625  */
626 void
627 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
628 {
629         trackview.editor().session()->begin_reversible_command (_("automation range move"));
630         trackview.editor().session()->add_command (
631                 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
632                 );
633
634         _drag_points.clear ();
635         for (uint32_t i = i1; i <= i2; i++) {
636                 _drag_points.push_back (nth (i));
637         }
638
639         start_drag_common (0, fraction);
640 }
641
642 /** Start dragging multiple points (with no change in x)
643  *  @param cp Points to drag.
644  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
645  */
646 void
647 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
648 {
649         trackview.editor().session()->begin_reversible_command (_("automation range move"));
650         trackview.editor().session()->add_command (
651                 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
652                 );
653
654         _drag_points = cp;
655         start_drag_common (0, fraction);
656 }
657
658
659 struct ControlPointSorter
660 {
661         bool operator() (ControlPoint const * a, ControlPoint const * b) {
662                 return a->get_x() < b->get_x();
663         }
664 };
665
666 /** Common parts of starting a drag.
667  *  @param x Starting x position in units, or 0 if x is being ignored.
668  *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
669  */
670 void
671 AutomationLine::start_drag_common (double x, float fraction)
672 {
673         _drag_x = x;
674         _drag_distance = 0;
675         _last_drag_fraction = fraction;
676         _drag_had_movement = false;
677         did_push = false;
678
679         _drag_points.sort (ControlPointSorter ());
680
681         /* find the additional points that will be dragged when the user is holding
682            the "push" modifier
683         */
684
685         uint32_t i = _drag_points.back()->view_index () + 1;
686         ControlPoint* p = 0;
687         _push_points.clear ();
688         while ((p = nth (i)) != 0 && p->can_slide()) {
689                 _push_points.push_back (p);
690                 ++i;
691         }
692 }
693
694 /** Should be called to indicate motion during a drag.
695  *  @param x New x position of the drag in units, or undefined if ignore_x == true.
696  *  @param fraction New y fraction.
697  *  @return x position and y fraction that were actually used (once clamped).
698  */
699 pair<double, float>
700 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
701 {
702         /* setup the points that are to be moved this time round */
703         list<ControlPoint*> points = _drag_points;
704         if (with_push) {
705                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
706                 points.sort (ControlPointSorter ());
707         }
708
709         double dx = ignore_x ? 0 : (x - _drag_x);
710         double dy = fraction - _last_drag_fraction;
711
712         /* find x limits */
713         ControlPoint* before = 0;
714         ControlPoint* after = 0;
715
716         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
717                 if ((*i)->get_x() < points.front()->get_x()) {
718                         before = *i;
719                 }
720                 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
721                         after = *i;
722                 }
723         }
724
725         double const before_x = before ? before->get_x() : 0;
726         double const after_x = after ? after->get_x() : DBL_MAX;
727
728         /* clamp x */
729         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
730
731                 if ((*i)->can_slide() && !ignore_x) {
732
733                         /* clamp min x */
734                         double const a = (*i)->get_x() + dx;
735                         double const b = before_x + 1;
736                         if (a < b) {
737                                 dx += b - a;
738                         }
739
740                         /* clamp max x */
741                         if (after) {
742
743                                 if (after_x - before_x < 2) {
744                                         /* after and before are very close, so just leave this alone */
745                                         dx = 0;
746                                 } else {
747                                         double const a = (*i)->get_x() + dx;
748                                         double const b = after_x - 1;
749                                         if (a > b) {
750                                                 dx -= a - b;
751                                         }
752                                 }
753                         }
754                 }
755         }
756
757         /* clamp y */
758         for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
759                 double const y = ((_height - (*i)->get_y()) / _height) + dy;
760                 if (y < 0) {
761                         dy -= y;
762                 }
763                 if (y > 1) {
764                         dy -= (y - 1);
765                 }
766         }
767
768         pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
769         _drag_distance += dx;
770         _drag_x = x;
771         _last_drag_fraction = fraction;
772
773         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
774                 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
775                 reset_line_coords (**i);
776         }
777
778         if (with_push) {
779                 /* move push points, preserving their y */
780                 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
781                         (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
782                         reset_line_coords (**i);
783                 }
784         }
785
786         if (line_points.size() > 1) {
787                 line->property_points() = line_points;
788         }
789
790         _drag_had_movement = true;
791         did_push = with_push;
792
793         return clamped;
794 }
795
796 /** Should be called to indicate the end of a drag */
797 void
798 AutomationLine::end_drag ()
799 {
800         if (!_drag_had_movement) {
801                 return;
802         }
803
804         alist->freeze ();
805
806         /* set up the points that were moved this time round */
807         list<ControlPoint*> points = _drag_points;
808         if (did_push) {
809                 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
810                 points.sort (ControlPointSorter ());
811         }
812
813         sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
814
815         alist->thaw ();
816
817         update_pending = false;
818
819         trackview.editor().session()->add_command (
820                 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
821                 );
822
823         trackview.editor().session()->set_dirty ();
824 }
825
826 void
827 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
828 {
829         ModelRepresentation mr;
830         double ydelta;
831
832         model_representation (cp, mr);
833
834         /* how much are we changing the central point by */
835
836         ydelta = mr.yval - mr.ypos;
837
838         /*
839            apply the full change to the central point, and interpolate
840            on both axes to cover all model points represented
841            by the control point.
842         */
843
844         /* change all points before the primary point */
845
846         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
847
848                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
849                 double y_delta = ydelta * fract;
850                 double x_delta = distance * fract;
851
852                 /* interpolate */
853
854                 if (y_delta || x_delta) {
855                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
856                 }
857         }
858
859         /* change the primary point */
860
861         update_pending = true;
862         alist->modify (cp.model(), mr.xval, mr.yval);
863
864         /* change later points */
865
866         AutomationList::iterator i = cp.model();
867
868         ++i;
869
870         while (i != mr.end) {
871
872                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
873
874                 /* all later points move by the same distance along the x-axis as the main point */
875
876                 if (delta) {
877                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
878                 }
879
880                 ++i;
881         }
882
883         if (did_push) {
884
885                 /* move all points after the range represented by the view by the same distance
886                    as the main point moved.
887                 */
888
889                 alist->slide (mr.end, distance);
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().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().session()->add_command(
967                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
968                 );
969
970         trackview.editor().session()->commit_reversible_command ();
971         trackview.editor().session()->set_dirty ();
972 }
973
974 /** Get selectable points within an area.
975  *  @param start Start position in session frames.
976  *  @param end End position in session frames.
977  *  @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
978  *  @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
979  *  @param result Filled in with selectable things; in this case, ControlPoints.
980  */
981 void
982 AutomationLine::get_selectables (
983         framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
984         )
985 {
986         /* convert fractions to display coordinates with 0 at the top of the track */
987         double const bot_track = (1 - topfrac) * trackview.current_height ();
988         double const top_track = (1 - botfrac) * trackview.current_height ();
989
990         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
991                 double const model_when = (*(*i)->model())->when;
992
993                 /* model_when is relative to the start of the source, so we just need to add on the origin_b here
994                    (as it is the session frame position of the start of the source)
995                 */
996                 
997                 framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
998
999                 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
1000                         results.push_back (*i);
1001                 }
1002         }
1003 }
1004
1005 void
1006 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1007 {
1008         // hmmm ....
1009 }
1010
1011 /** Take a PointSelection and find ControlPoints that fall within it */
1012 list<ControlPoint*>
1013 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1014 {
1015         list<ControlPoint*> cp;
1016
1017         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1018
1019                 if (i->track != &trackview) {
1020                         continue;
1021                 }
1022
1023                 double const bot = (1 - i->high_fract) * trackview.current_height ();
1024                 double const top = (1 - i->low_fract) * trackview.current_height ();
1025
1026                 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1027
1028                         double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start) - _offset);
1029                         double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end) - _offset);
1030
1031                         if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1032                                 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1033                                         cp.push_back (*j);
1034                                 }
1035                         }
1036                 }
1037
1038         }
1039
1040         return cp;
1041 }
1042
1043 void
1044 AutomationLine::set_selected_points (PointSelection const & points)
1045 {
1046         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1047                 (*i)->set_selected (false);
1048         }
1049
1050         if (!points.empty()) {
1051                 list<ControlPoint*> cp = point_selection_to_control_points (points);
1052                 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1053                         (*i)->set_selected (true);
1054                 }
1055         }
1056
1057         set_colors ();
1058 }
1059
1060 void AutomationLine::set_colors ()
1061 {
1062         set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1063         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1064                 (*i)->set_color ();
1065         }
1066 }
1067
1068 void
1069 AutomationLine::list_changed ()
1070 {
1071         queue_reset ();
1072 }
1073
1074 void
1075 AutomationLine::reset_callback (const Evoral::ControlList& events)
1076 {
1077         ALPoints tmp_points;
1078         uint32_t npoints = events.size();
1079
1080         if (npoints == 0) {
1081                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1082                         delete *i;
1083                 }
1084                 control_points.clear ();
1085                 line->hide();
1086                 return;
1087         }
1088
1089         AutomationList::const_iterator ai;
1090         int skipped = 0;
1091
1092         for (ai = events.begin(); ai != events.end(); ++ai) {
1093
1094                 double translated_x = (*ai)->when;
1095                 double translated_y = (*ai)->value;
1096                 model_to_view_coord (translated_x, translated_y);
1097
1098                 if (translated_x >= 0 && translated_x < _maximum_time) {
1099                         tmp_points.push_back (ALPoint (
1100                                                       trackview.editor().frame_to_unit (translated_x),
1101                                                       _height - (translated_y * _height))
1102                                 );
1103                 } else if (translated_x < 0) {
1104                         ++skipped;
1105                 }
1106         }
1107
1108         determine_visible_control_points (tmp_points, skipped);
1109 }
1110
1111 void
1112 AutomationLine::reset ()
1113 {
1114         update_pending = false;
1115
1116         if (no_draw) {
1117                 return;
1118         }
1119
1120         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1121 }
1122
1123 void
1124 AutomationLine::clear ()
1125 {
1126         /* parent must create and commit command */
1127         XMLNode &before = alist->get_state();
1128         alist->clear();
1129
1130         trackview.editor().session()->add_command (
1131                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1132                 );
1133 }
1134
1135 void
1136 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1137 {
1138 }
1139
1140 void
1141 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1142 {
1143         alist = list;
1144         queue_reset ();
1145         connect_to_list ();
1146 }
1147
1148 void
1149 AutomationLine::show_all_control_points ()
1150 {
1151         if (_is_boolean) {
1152                 // show the line but don't allow any control points
1153                 return;
1154         }
1155
1156         points_visible = true;
1157
1158         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1159                 if (!(*i)->visible()) {
1160                         (*i)->show ();
1161                         (*i)->set_visible (true);
1162                 }
1163         }
1164 }
1165
1166 void
1167 AutomationLine::hide_all_but_selected_control_points ()
1168 {
1169         if (alist->interpolation() == AutomationList::Discrete) {
1170                 return;
1171         }
1172
1173         points_visible = false;
1174
1175         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1176                 if (!(*i)->get_selected()) {
1177                         (*i)->set_visible (false);
1178                 }
1179         }
1180 }
1181
1182 void
1183 AutomationLine::track_entered()
1184 {
1185         if (alist->interpolation() != AutomationList::Discrete) {
1186                 show_all_control_points();
1187         }
1188 }
1189
1190 void
1191 AutomationLine::track_exited()
1192 {
1193         if (alist->interpolation() != AutomationList::Discrete) {
1194                 hide_all_but_selected_control_points();
1195         }
1196 }
1197
1198 XMLNode &
1199 AutomationLine::get_state (void)
1200 {
1201         /* function as a proxy for the model */
1202         return alist->get_state();
1203 }
1204
1205 int
1206 AutomationLine::set_state (const XMLNode &node, int version)
1207 {
1208         /* function as a proxy for the model */
1209         return alist->set_state (node, version);
1210 }
1211
1212 void
1213 AutomationLine::view_to_model_coord (double& x, double& y) const
1214 {
1215         x = _time_converter.from (x);
1216         view_to_model_coord_y (y);
1217 }
1218
1219 void
1220 AutomationLine::view_to_model_coord_y (double& y) const
1221 {
1222         /* TODO: This should be more generic ... */
1223         if (alist->parameter().type() == GainAutomation ||
1224             alist->parameter().type() == EnvelopeAutomation) {
1225                 y = slider_position_to_gain_with_max (y, Config->get_max_gain());
1226                 y = max (0.0, y);
1227                 y = min (2.0, y);
1228         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1229                    alist->parameter().type() == PanElevationAutomation ||
1230                    alist->parameter().type() == PanWidthAutomation) {
1231                 y = 1.0 - y;
1232         } else if (alist->parameter().type() == PluginAutomation) {
1233                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1234         } else {
1235                 y = rint (y * alist->parameter().max());
1236         }
1237 }
1238
1239 void
1240 AutomationLine::model_to_view_coord (double& x, double& y) const
1241 {
1242         /* TODO: This should be more generic ... */
1243         if (alist->parameter().type() == GainAutomation ||
1244             alist->parameter().type() == EnvelopeAutomation) {
1245                 y = gain_to_slider_position_with_max (y, Config->get_max_gain());
1246         } else if (alist->parameter().type() == PanAzimuthAutomation ||
1247                    alist->parameter().type() == PanElevationAutomation ||
1248                    alist->parameter().type() == PanWidthAutomation) {
1249                 // vertical coordinate axis reversal
1250                 y = 1.0 - y;
1251         } else if (alist->parameter().type() == PluginAutomation) {
1252                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1253         } else {
1254                 y = y / (double)alist->parameter().max(); /* ... like this */
1255         }
1256
1257         x = _time_converter.to (x) - _offset;
1258 }
1259
1260 /** Called when our list has announced that its interpolation style has changed */
1261 void
1262 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1263 {
1264         if (style == AutomationList::Discrete) {
1265                 show_all_control_points();
1266                 line->hide();
1267         } else {
1268                 hide_all_but_selected_control_points();
1269                 line->show();
1270         }
1271 }
1272
1273 void
1274 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1275 {
1276         if (view_index >= control_points.size()) {
1277
1278                 /* make sure we have enough control points */
1279
1280                 ControlPoint* ncp = new ControlPoint (*this);
1281                 ncp->set_size (control_point_box_size ());
1282
1283                 control_points.push_back (ncp);
1284         }
1285
1286         ControlPoint::ShapeType shape;
1287
1288         if (!terminal_points_can_slide) {
1289                 if (pi == 0) {
1290                         control_points[view_index]->set_can_slide(false);
1291                         if (tx == 0) {
1292                                 shape = ControlPoint::Start;
1293                         } else {
1294                                 shape = ControlPoint::Full;
1295                         }
1296                 } else if (pi == npoints - 1) {
1297                         control_points[view_index]->set_can_slide(false);
1298                         shape = ControlPoint::End;
1299                 } else {
1300                         control_points[view_index]->set_can_slide(true);
1301                         shape = ControlPoint::Full;
1302                 }
1303         } else {
1304                 control_points[view_index]->set_can_slide(true);
1305                 shape = ControlPoint::Full;
1306         }
1307
1308         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1309
1310         /* finally, control visibility */
1311
1312         if (_visible && points_visible) {
1313                 control_points[view_index]->show ();
1314                 control_points[view_index]->set_visible (true);
1315         } else {
1316                 if (!points_visible) {
1317                         control_points[view_index]->set_visible (false);
1318                 }
1319         }
1320 }
1321
1322 void
1323 AutomationLine::add_always_in_view (double x)
1324 {
1325         _always_in_view.push_back (x);
1326         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1327 }
1328
1329 void
1330 AutomationLine::clear_always_in_view ()
1331 {
1332         _always_in_view.clear ();
1333         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1334 }
1335
1336 void
1337 AutomationLine::connect_to_list ()
1338 {
1339         _list_connections.drop_connections ();
1340
1341         alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1342
1343         alist->InterpolationChanged.connect (
1344                 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1345                 );
1346 }
1347
1348 MementoCommandBinder<AutomationList>*
1349 AutomationLine::memento_command_binder ()
1350 {
1351         return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1352 }
1353
1354 /** Set the maximum time that points on this line can be at, relative
1355  *  to the start of the track or region that it is on.
1356  */
1357 void
1358 AutomationLine::set_maximum_time (framecnt_t t)
1359 {
1360         if (_maximum_time == t) {
1361                 return;
1362         }
1363
1364         _maximum_time = t;
1365         reset ();
1366 }
1367
1368
1369 /** @return min and max x positions of points that are in the list, in session frames */
1370 pair<framepos_t, framepos_t>
1371 AutomationLine::get_point_x_range () const
1372 {
1373         pair<framepos_t, framepos_t> r (max_framepos, 0);
1374
1375         for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1376                 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1377                 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1378         }
1379
1380         return r;
1381 }
1382
1383 void
1384 AutomationLine::set_offset (framepos_t off)
1385 {
1386         if (_offset == off) {
1387                 return;
1388         }
1389
1390         _offset = off;
1391         reset ();
1392 }