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