Stop automation drags when they hit their lowest point, so as to prevent automation...
[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_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
47
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
50
51 #include "i18n.h"
52
53 using namespace std;
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 (sigc::mem_fun (*this, &AutomationLine::event_handler));
88
89         alist->StateChanged.connect (_state_connection, boost::bind (&AutomationLine::list_changed, this), gui_context());
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 (boost::bind (&AutomationLine::reset, this));
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().session()->begin_reversible_command (_("automation event move"));
220         trackview.editor().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().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236         trackview.editor().session()->commit_reversible_command ();
237         trackview.editor().session()->set_dirty ();
238 }
239
240
241 void
242 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool keep_x, 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() && !keep_x) {
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_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
366 {
367         update_pending = true;
368
369         for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
370                 sync_model_with_view_point (**i, did_push, distance);
371         }
372 }
373
374 void
375 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
376 {
377         /* part one: find out where the visual control point is.
378            initial results are in canvas units. ask the
379            line to convert them to something relevant.
380         */
381
382         mr.xval = cp.get_x();
383         mr.yval = 1.0 - (cp.get_y() / _height);
384
385         /* if xval has not changed, set it directly from the model to avoid rounding errors */
386
387         if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
388                 mr.xval = (*cp.model())->when;
389         } else {
390                 mr.xval = trackview.editor().unit_to_frame (mr.xval);
391         }
392
393         /* convert to model units
394         */
395
396         view_to_model_coord (mr.xval, mr.yval);
397
398         /* part 2: find out where the model point is now
399          */
400
401         mr.xpos = (*cp.model())->when;
402         mr.ypos = (*cp.model())->value;
403
404         /* part 3: get the position of the visual control
405            points before and after us.
406         */
407
408         ControlPoint* before;
409         ControlPoint* after;
410
411         if (cp.view_index()) {
412                 before = nth (cp.view_index() - 1);
413         } else {
414                 before = 0;
415         }
416
417         after = nth (cp.view_index() + 1);
418
419         if (before) {
420                 mr.xmin = (*before->model())->when;
421                 mr.ymin = (*before->model())->value;
422                 mr.start = before->model();
423                 ++mr.start;
424         } else {
425                 mr.xmin = mr.xpos;
426                 mr.ymin = mr.ypos;
427                 mr.start = cp.model();
428         }
429
430         if (after) {
431                 mr.end = after->model();
432         } else {
433                 mr.xmax = mr.xpos;
434                 mr.ymax = mr.ypos;
435                 mr.end = cp.model();
436                 ++mr.end;
437         }
438 }
439
440 void
441 AutomationLine::determine_visible_control_points (ALPoints& points)
442 {
443         uint32_t view_index, pi, n;
444         AutomationList::iterator model;
445         uint32_t npoints;
446         uint32_t this_rx = 0;
447         uint32_t prev_rx = 0;
448         uint32_t this_ry = 0;
449         uint32_t prev_ry = 0;
450         double* slope;
451         uint32_t box_size;
452
453         /* hide all existing points, and the line */
454
455         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
456                 (*i)->hide();
457         }
458
459         line->hide ();
460
461         if (points.empty()) {
462                 return;
463         }
464
465         npoints = points.size();
466
467         /* compute derivative/slope for the entire line */
468
469         slope = new double[npoints];
470
471         for (n = 0; n < npoints - 1; ++n) {
472                 double xdelta = points[n+1].x - points[n].x;
473                 double ydelta = points[n+1].y - points[n].y;
474                 slope[n] = ydelta/xdelta;
475         }
476
477         box_size = (uint32_t) control_point_box_size ();
478
479         /* read all points and decide which ones to show as control points */
480
481         view_index = 0;
482
483         for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
484
485                 double tx = points[pi].x;
486                 double ty = points[pi].y;
487
488                 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
489                         add_visible_control_point (view_index, pi, tx, ty, model, npoints);
490                         prev_rx = this_rx;
491                         prev_ry = this_ry;
492                         ++view_index;
493                         continue;
494                 }
495
496                 if (isnan (tx) || isnan (ty)) {
497                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
498                                                    _name) << endmsg;
499                         continue;
500                 }
501
502                 /* now ensure that the control_points vector reflects the current curve
503                    state, but don't plot control points too close together. also, don't
504                    plot a series of points all with the same value.
505
506                    always plot the first and last points, of course.
507                 */
508
509                 if (invalid_point (points, pi)) {
510                         /* for some reason, we are supposed to ignore this point,
511                            but still keep track of the model index.
512                         */
513                         continue;
514                 }
515
516                 if (pi > 0 && pi < npoints - 1) {
517                         if (slope[pi] == slope[pi-1]) {
518
519                                 /* no reason to display this point */
520
521                                 continue;
522                         }
523                 }
524
525                 /* need to round here. the ultimate coordinates are integer
526                    pixels, so tiny deltas in the coords will be eliminated
527                    and we end up with "colinear" line segments. since the
528                    line rendering code in libart doesn't like this very
529                    much, we eliminate them here. don't do this for the first and last
530                    points.
531                 */
532
533                 this_rx = (uint32_t) rint (tx);
534                 this_ry = (uint32_t) rint (ty);
535
536                 if (view_index && pi != npoints && /* not the first, not the last */
537                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
538                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
539                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
540                         continue;
541                 }
542
543                 /* ok, we should display this point */
544
545                 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
546                 
547                 prev_rx = this_rx;
548                 prev_ry = this_ry;
549
550                 view_index++;
551         }
552
553         /* discard extra CP's to avoid confusing ourselves */
554
555         while (control_points.size() > view_index) {
556                 ControlPoint* cp = control_points.back();
557                 control_points.pop_back ();
558                 delete cp;
559         }
560
561         if (!terminal_points_can_slide) {
562                 control_points.back()->set_can_slide(false);
563         }
564
565         delete [] slope;
566
567         if (view_index > 1) {
568
569                 npoints = view_index;
570
571                 /* reset the line coordinates */
572
573                 while (line_points.size() < npoints) {
574                         line_points.push_back (Art::Point (0,0));
575                 }
576
577                 while (line_points.size() > npoints) {
578                         line_points.pop_back ();
579                 }
580
581                 for (view_index = 0; view_index < npoints; ++view_index) {
582                         line_points[view_index].set_x (control_points[view_index]->get_x());
583                         line_points[view_index].set_y (control_points[view_index]->get_y());
584                 }
585
586                 line->property_points() = line_points;
587
588                 if (_visible && _interpolation != AutomationList::Discrete) {
589                         line->show();
590                 }
591
592         }
593
594         set_selected_points (trackview.editor().get_selection().points);
595
596 }
597
598 string
599 AutomationLine::get_verbose_cursor_string (double fraction) const
600 {
601         std::string s = fraction_to_string (fraction);
602         if (_uses_gain_mapping) {
603                 s += " dB";
604         }
605
606         return s;
607 }
608
609 /**
610  *  @param fraction y fraction
611  *  @return string representation of this value, using dB if appropriate.
612  */
613 string
614 AutomationLine::fraction_to_string (double fraction) const
615 {
616         char buf[32];
617
618         if (_uses_gain_mapping) {
619                 if (fraction == 0.0) {
620                         snprintf (buf, sizeof (buf), "-inf");
621                 } else {
622                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
623                 }
624         } else {
625                 double dummy = 0.0;
626                 view_to_model_coord (dummy, fraction);
627                 if (EventTypeMap::instance().is_integer (alist->parameter())) {
628                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
629                 } else {
630                         snprintf (buf, sizeof (buf), "%.2f", fraction);
631                 }
632         }
633
634         return buf;
635 }
636
637
638 /**
639  *  @param s Value string in the form as returned by fraction_to_string.
640  *  @return Corresponding y fraction.
641  */
642 double
643 AutomationLine::string_to_fraction (string const & s) const
644 {
645         if (s == "-inf") {
646                 return 0;
647         }
648
649         double v;
650         sscanf (s.c_str(), "%lf", &v);
651
652         if (_uses_gain_mapping) {
653                 v = gain_to_slider_position (dB_to_coefficient (v));
654         } else {
655                 double dummy = 0.0;
656                 model_to_view_coord (dummy, v);
657         }
658
659         return v;
660 }
661
662 bool
663 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
664 {
665         return p[index].x == max_frames && p[index].y == DBL_MAX;
666 }
667
668 void
669 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
670 {
671         p[index].x = max_frames;
672         p[index].y = DBL_MAX;
673 }
674
675 /** Start dragging a single point.
676  *  @param cp Point to drag.
677  *  @param x Initial x position (frames).
678  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
679  */
680 void
681 AutomationLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
682 {
683         trackview.editor().session()->begin_reversible_command (_("automation event move"));
684         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
685
686         _drag_points.clear ();
687         _drag_points.push_back (cp);
688         start_drag_common (x, fraction);
689 }
690
691 /** Start dragging a line vertically (with no change in x)
692  *  @param i1 Control point index of the `left' point on the line.
693  *  @param i2 Control point index of the `right' point on the line.
694  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
695  */
696 void
697 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
698 {
699         trackview.editor().session()->begin_reversible_command (_("automation range move"));
700         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
701
702         _drag_points.clear ();
703
704         // check if one of the control points on the line is in a selected range
705         bool range_found = false;
706         ControlPoint *cp;
707
708         for (uint32_t i = i1 ; i <= i2; i++) {
709                 cp = nth (i);
710                 if (cp->selected()) {
711                         range_found = true;
712                 }
713         }
714
715         if (range_found) {
716                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
717                         if ((*i)->selected()) {
718                                 _drag_points.push_back (*i);
719                         }
720                 }
721         } else {
722                 for (uint32_t i = i1 ; i <= i2; i++) {
723                         _drag_points.push_back (nth (i));
724                 }
725         }
726
727         start_drag_common (0, fraction);
728 }
729
730 /** Start dragging multiple points (with no change in x)
731  *  @param cp Points to drag.
732  *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
733  */
734 void
735 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
736 {
737         trackview.editor().session()->begin_reversible_command (_("automation range move"));
738         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
739
740         _drag_points = cp;
741         start_drag_common (0, fraction);
742 }
743
744 /** Common parts of starting a drag.
745  *  @param d Description of the drag.
746  *  @param x Starting x position in frames, or 0 if x is being ignored.
747  *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
748  */
749 void
750 AutomationLine::start_drag_common (nframes_t x, float fraction)
751 {
752         drag_x = x;
753         drag_distance = 0;
754         _last_drag_fraction = fraction;
755         _drag_had_movement = false;
756         did_push = false;
757 }
758
759 /** Should be called to indicate motion during a drag.
760  *  @param x New x position of the drag in frames, or 0 if x is being ignored.
761  *  @param fraction New y fraction.
762  */
763 void
764 AutomationLine::drag_motion (nframes_t x, float fraction, bool with_push)
765 {
766         int64_t const dx = x - drag_x;
767         drag_distance += dx;
768         drag_x = x;
769
770         double dy = fraction - _last_drag_fraction;
771         _last_drag_fraction = fraction;
772
773         /* clamp y so that the "lowest" point hits the bottom but goes no further */
774         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
775                 double const y = ((_height - (*i)->get_y()) / _height) + dy;
776                 if (y < 0) {
777                         dy -= y;
778                 }
779         }
780
781         for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
782
783                 modify_view_point (
784                         **i,
785                         trackview.editor().unit_to_frame ((*i)->get_x()) + dx,
786                         ((_height - (*i)->get_y()) / _height) + dy,
787                         (x == 0),
788                         with_push
789                         );
790         }
791
792         if (line_points.size() > 1) {
793                 line->property_points() = line_points;
794         }
795
796         _drag_had_movement = true;
797         did_push = with_push;
798 }
799
800 /** Should be called to indicate the end of a drag */
801 void
802 AutomationLine::end_drag ()
803 {
804         if (!_drag_had_movement) {
805                 return;
806         }
807
808         alist->freeze ();
809
810         sync_model_with_view_points (_drag_points, did_push, drag_distance);
811
812         alist->thaw ();
813
814         update_pending = false;
815
816         trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
817         trackview.editor().session()->commit_reversible_command ();
818         trackview.editor().session()->set_dirty ();
819 }
820
821 void
822 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
823 {
824         ModelRepresentation mr;
825         double ydelta;
826
827         model_representation (cp, mr);
828
829         /* how much are we changing the central point by */
830
831         ydelta = mr.yval - mr.ypos;
832
833         /*
834            apply the full change to the central point, and interpolate
835            on both axes to cover all model points represented
836            by the control point.
837         */
838
839         /* change all points before the primary point */
840
841         for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
842
843                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
844                 double y_delta = ydelta * fract;
845                 double x_delta = distance * fract;
846
847                 /* interpolate */
848
849                 if (y_delta || x_delta) {
850                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
851                 }
852         }
853
854         /* change the primary point */
855
856         update_pending = true;
857         alist->modify (cp.model(), mr.xval, mr.yval);
858
859
860         /* change later points */
861
862         AutomationList::iterator i = cp.model();
863
864         ++i;
865
866         while (i != mr.end) {
867
868                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
869
870                 /* all later points move by the same distance along the x-axis as the main point */
871
872                 if (delta) {
873                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
874                 }
875
876                 ++i;
877         }
878
879         if (did_push) {
880
881                 /* move all points after the range represented by the view by the same distance
882                    as the main point moved.
883                 */
884
885                 alist->slide (mr.end, drag_distance);
886         }
887 }
888
889 bool
890 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
891 {
892         ControlPoint *bcp = 0;
893         ControlPoint *acp = 0;
894         double unit_xval;
895
896         unit_xval = trackview.editor().frame_to_unit (xval);
897
898         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
899
900                 if ((*i)->get_x() <= unit_xval) {
901
902                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
903                                 bcp = *i;
904                                 before = bcp->view_index();
905                         }
906
907                 } else if ((*i)->get_x() > unit_xval) {
908                         acp = *i;
909                         after = acp->view_index();
910                         break;
911                 }
912         }
913
914         return bcp && acp;
915 }
916
917 bool
918 AutomationLine::is_last_point (ControlPoint& cp)
919 {
920         ModelRepresentation mr;
921
922         model_representation (cp, mr);
923
924         // If the list is not empty, and the point is the last point in the list
925
926         if (!alist->empty() && mr.end == alist->end()) {
927                 return true;
928         }
929
930         return false;
931 }
932
933 bool
934 AutomationLine::is_first_point (ControlPoint& cp)
935 {
936         ModelRepresentation mr;
937
938         model_representation (cp, mr);
939
940         // If the list is not empty, and the point is the first point in the list
941
942         if (!alist->empty() && mr.start == alist->begin()) {
943                 return true;
944         }
945
946         return false;
947 }
948
949 // This is copied into AudioRegionGainLine
950 void
951 AutomationLine::remove_point (ControlPoint& cp)
952 {
953         ModelRepresentation mr;
954
955         model_representation (cp, mr);
956
957         trackview.editor().session()->begin_reversible_command (_("remove control point"));
958         XMLNode &before = alist->get_state();
959
960         alist->erase (mr.start, mr.end);
961
962         trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
963                         *alist.get(), &before, &alist->get_state()));
964         trackview.editor().session()->commit_reversible_command ();
965         trackview.editor().session()->set_dirty ();
966 }
967
968 void
969 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
970                 double botfrac, double topfrac, list<Selectable*>& results)
971 {
972
973         double top;
974         double bot;
975         double nstart;
976         double nend;
977         bool collecting = false;
978
979         /* Curse X11 and its inverted coordinate system! */
980
981         bot = (1.0 - topfrac) * _height;
982         top = (1.0 - botfrac) * _height;
983
984         nstart = max_frames;
985         nend = 0;
986
987         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
988                 double when = (*(*i)->model())->when;
989
990                 if (when >= start && when <= end) {
991
992                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
993
994                                 (*i)->show();
995                                 (*i)->set_visible(true);
996                                 collecting = true;
997                                 nstart = min (nstart, when);
998                                 nend = max (nend, when);
999
1000                         } else {
1001
1002                                 if (collecting) {
1003
1004                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1005                                         collecting = false;
1006                                         nstart = max_frames;
1007                                         nend = 0;
1008                                 }
1009                         }
1010                 }
1011         }
1012
1013         if (collecting) {
1014                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1015         }
1016
1017 }
1018
1019 void
1020 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1021 {
1022         // hmmm ....
1023 }
1024
1025 void
1026 AutomationLine::set_selected_points (PointSelection& points)
1027 {
1028         double top;
1029         double bot;
1030
1031         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1032                         (*i)->set_selected(false);
1033         }
1034
1035         if (points.empty()) {
1036                 goto out;
1037         }
1038
1039         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1040
1041                 if ((*r).track != &trackview) {
1042                         continue;
1043                 }
1044
1045                 /* Curse X11 and its inverted coordinate system! */
1046
1047                 bot = (1.0 - (*r).high_fract) * _height;
1048                 top = (1.0 - (*r).low_fract) * _height;
1049
1050                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1051
1052                         double rstart, rend;
1053
1054                         rstart = trackview.editor().frame_to_unit ((*r).start);
1055                         rend = trackview.editor().frame_to_unit ((*r).end);
1056
1057                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1058
1059                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1060
1061                                         (*i)->set_selected(true);
1062                                 }
1063                         }
1064
1065                 }
1066         }
1067
1068   out:
1069         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1070                 (*i)->show_color (false, !points_visible);
1071         }
1072
1073 }
1074
1075 void AutomationLine::set_colors() {
1076         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1077         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1078                 (*i)->show_color (false, !points_visible);
1079         }
1080 }
1081
1082 void
1083 AutomationLine::show_selection ()
1084 {
1085         TimeSelection& time (trackview.editor().get_selection().time);
1086
1087         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1088
1089                 (*i)->set_selected(false);
1090
1091                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1092                         double rstart, rend;
1093
1094                         rstart = trackview.editor().frame_to_unit ((*r).start);
1095                         rend = trackview.editor().frame_to_unit ((*r).end);
1096
1097                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1098                                 (*i)->set_selected(true);
1099                                 break;
1100                         }
1101                 }
1102
1103                 (*i)->show_color (false, !points_visible);
1104         }
1105 }
1106
1107 void
1108 AutomationLine::hide_selection ()
1109 {
1110 //      show_selection ();
1111 }
1112
1113 void
1114 AutomationLine::list_changed ()
1115 {
1116         queue_reset ();
1117 }
1118
1119 void
1120 AutomationLine::reset_callback (const Evoral::ControlList& events)
1121 {
1122         ALPoints tmp_points;
1123         uint32_t npoints = events.size();
1124
1125         if (npoints == 0) {
1126                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1127                         delete *i;
1128                 }
1129                 control_points.clear ();
1130                 line->hide();
1131                 return;
1132         }
1133
1134         AutomationList::const_iterator ai;
1135
1136         for (ai = events.begin(); ai != events.end(); ++ai) {
1137
1138                 double translated_x = (*ai)->when;
1139                 double translated_y = (*ai)->value;
1140                 model_to_view_coord (translated_x, translated_y);
1141
1142                 add_model_point (tmp_points, (*ai)->when, translated_y);
1143         }
1144
1145         determine_visible_control_points (tmp_points);
1146 }
1147
1148
1149 void
1150 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1151 {
1152         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1153                                        _height - (yfract * _height)));
1154 }
1155
1156 void
1157 AutomationLine::reset ()
1158 {
1159         update_pending = false;
1160
1161         if (no_draw) {
1162                 return;
1163         }
1164
1165         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1166 }
1167
1168 void
1169 AutomationLine::clear ()
1170 {
1171         /* parent must create command */
1172         XMLNode &before = get_state();
1173         alist->clear();
1174         trackview.editor().session()->add_command (
1175                         new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1176         trackview.editor().session()->commit_reversible_command ();
1177         trackview.editor().session()->set_dirty ();
1178 }
1179
1180 void
1181 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1182 {
1183 }
1184
1185 void
1186 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1187 {
1188         alist = list;
1189         queue_reset();
1190 }
1191
1192 void
1193 AutomationLine::show_all_control_points ()
1194 {
1195         points_visible = true;
1196
1197         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1198                 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1199                 (*i)->show ();
1200                 (*i)->set_visible (true);
1201         }
1202 }
1203
1204 void
1205 AutomationLine::hide_all_but_selected_control_points ()
1206 {
1207         if (alist->interpolation() == AutomationList::Discrete) {
1208                 return;
1209         }
1210
1211         points_visible = false;
1212
1213         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1214                 if (!(*i)->selected()) {
1215                         (*i)->set_visible (false);
1216                 }
1217         }
1218 }
1219
1220 void
1221 AutomationLine::track_entered()
1222 {
1223         if (alist->interpolation() != AutomationList::Discrete) {
1224                 show_all_control_points();
1225         }
1226 }
1227
1228 void
1229 AutomationLine::track_exited()
1230 {
1231         if (alist->interpolation() != AutomationList::Discrete) {
1232                 hide_all_but_selected_control_points();
1233         }
1234 }
1235
1236 XMLNode &
1237 AutomationLine::get_state (void)
1238 {
1239         /* function as a proxy for the model */
1240         return alist->get_state();
1241 }
1242
1243 int
1244 AutomationLine::set_state (const XMLNode &node, int version)
1245 {
1246         /* function as a proxy for the model */
1247         return alist->set_state (node, version);
1248 }
1249
1250 void
1251 AutomationLine::view_to_model_coord (double& x, double& y) const
1252 {
1253         /* TODO: This should be more generic ... */
1254         if (alist->parameter().type() == GainAutomation ||
1255             alist->parameter().type() == EnvelopeAutomation) {
1256                 y = slider_position_to_gain (y);
1257                 y = max (0.0, y);
1258                 y = min (2.0, y);
1259         } else if (alist->parameter().type() == PanAutomation) {
1260                 // vertical coordinate axis reversal
1261                 y = 1.0 - y;
1262         } else if (alist->parameter().type() == PluginAutomation) {
1263                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1264         } else {
1265                 y = (int)(y * alist->parameter().max());
1266         }
1267
1268         x = _time_converter.from(x);
1269 }
1270
1271 void
1272 AutomationLine::model_to_view_coord (double& x, double& y) const
1273 {
1274         /* TODO: This should be more generic ... */
1275         if (alist->parameter().type() == GainAutomation ||
1276             alist->parameter().type() == EnvelopeAutomation) {
1277                 y = gain_to_slider_position (y);
1278         } else if (alist->parameter().type() == PanAutomation) {
1279                 // vertical coordinate axis reversal
1280                 y = 1.0 - y;
1281         } else if (alist->parameter().type() == PluginAutomation) {
1282                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1283         } else {
1284                 y = y / (double)alist->parameter().max(); /* ... like this */
1285         }
1286
1287         x = _time_converter.to(x);
1288 }
1289
1290
1291 void
1292 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1293 {
1294         _interpolation = style;
1295
1296         if (style == AutomationList::Discrete) {
1297                 show_all_control_points();
1298                 line->hide();
1299         } else {
1300                 hide_all_but_selected_control_points();
1301                 line->show();
1302         }
1303 }
1304
1305 void
1306 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1307 {
1308         if (view_index >= control_points.size()) {
1309
1310                 /* make sure we have enough control points */
1311
1312                 ControlPoint* ncp = new ControlPoint (*this);
1313                 ncp->set_size (control_point_box_size ());
1314
1315                 control_points.push_back (ncp);
1316         }
1317
1318         ControlPoint::ShapeType shape;
1319
1320         if (!terminal_points_can_slide) {
1321                 if (pi == 0) {
1322                         control_points[view_index]->set_can_slide(false);
1323                         if (tx == 0) {
1324                                 shape = ControlPoint::Start;
1325                         } else {
1326                                 shape = ControlPoint::Full;
1327                         }
1328                 } else if (pi == npoints - 1) {
1329                         control_points[view_index]->set_can_slide(false);
1330                         shape = ControlPoint::End;
1331                 } else {
1332                         control_points[view_index]->set_can_slide(true);
1333                         shape = ControlPoint::Full;
1334                 }
1335         } else {
1336                 control_points[view_index]->set_can_slide(true);
1337                 shape = ControlPoint::Full;
1338         }
1339
1340         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1341
1342         /* finally, control visibility */
1343         
1344         if (_visible && points_visible) {
1345                 control_points[view_index]->show ();
1346                 control_points[view_index]->set_visible (true);
1347         } else {
1348                 if (!points_visible) {
1349                         control_points[view_index]->set_visible (false);
1350                 }
1351         }
1352 }
1353
1354 void
1355 AutomationLine::add_always_in_view (double x)
1356 {
1357         _always_in_view.push_back (x);
1358         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1359 }
1360
1361 void
1362 AutomationLine::clear_always_in_view ()
1363 {
1364         _always_in_view.clear ();
1365         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1366 }
1367