Allow rubberband selection of MIDI automation points. Fixes
[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 void
951 AutomationLine::get_selectables (nframes_t start, nframes_t end,
952                 double botfrac, double topfrac, list<Selectable*>& results)
953 {
954
955         double top;
956         double bot;
957         sframes_t nstart;
958         sframes_t nend;
959         bool collecting = false;
960
961         /* Curse X11 and its inverted coordinate system! */
962
963         bot = (1.0 - topfrac) * _height;
964         top = (1.0 - botfrac) * _height;
965
966         nstart = max_frames;
967         nend = 0;
968
969         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
970                 sframes_t const when = _time_converter.to ((*(*i)->model())->when);
971
972                 if (when >= start && when <= end) {
973
974                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
975
976                                 (*i)->show();
977                                 (*i)->set_visible(true);
978                                 collecting = true;
979                                 nstart = min (nstart, when);
980                                 nend = max (nend, when);
981
982                         } else {
983
984                                 if (collecting) {
985
986                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
987                                         collecting = false;
988                                         nstart = max_frames;
989                                         nend = 0;
990                                 }
991                         }
992                 }
993         }
994
995         if (collecting) {
996                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
997         }
998
999 }
1000
1001 void
1002 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1003 {
1004         // hmmm ....
1005 }
1006
1007 /** Take a PointSelection and find ControlPoints that fall within it */
1008 list<ControlPoint*>
1009 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1010 {
1011         list<ControlPoint*> cp;
1012         
1013         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1014
1015                 if (i->track != &trackview) {
1016                         continue;
1017                 }
1018
1019                 /* Curse X11 and its inverted coordinate system! */
1020
1021                 double const bot = (1.0 - i->high_fract) * _height;
1022                 double const top = (1.0 - i->low_fract) * _height;
1023
1024                 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1025
1026                         double const rstart = trackview.editor().frame_to_unit (i->start);
1027                         double const rend = trackview.editor().frame_to_unit (i->end);
1028
1029                         if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1030                                 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1031                                         cp.push_back (*j);
1032                                 }
1033                         }
1034                 }
1035
1036         }
1037
1038         return cp;
1039 }
1040
1041 void
1042 AutomationLine::set_selected_points (PointSelection& points)
1043 {
1044         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1045                 (*i)->set_selected (false);
1046         }
1047
1048         if (!points.empty()) {
1049                 list<ControlPoint*> cp = point_selection_to_control_points (points);
1050                 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1051                         (*i)->set_selected (true);
1052                 }
1053         }
1054
1055         set_colors ();
1056 }
1057
1058 void AutomationLine::set_colors ()
1059 {
1060         set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1061         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1062                 (*i)->set_color ();
1063         }
1064 }
1065
1066 void
1067 AutomationLine::list_changed ()
1068 {
1069         queue_reset ();
1070 }
1071
1072 void
1073 AutomationLine::reset_callback (const Evoral::ControlList& events)
1074 {
1075         ALPoints tmp_points;
1076         uint32_t npoints = events.size();
1077
1078         if (npoints == 0) {
1079                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1080                         delete *i;
1081                 }
1082                 control_points.clear ();
1083                 line->hide();
1084                 return;
1085         }
1086
1087         AutomationList::const_iterator ai;
1088
1089         for (ai = events.begin(); ai != events.end(); ++ai) {
1090
1091                 double translated_x = (*ai)->when;
1092                 double translated_y = (*ai)->value;
1093                 model_to_view_coord (translated_x, translated_y);
1094
1095                 add_model_point (tmp_points, (*ai)->when, translated_y);
1096         }
1097
1098         determine_visible_control_points (tmp_points);
1099 }
1100
1101
1102 void
1103 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1104 {
1105         tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1106                                        _height - (yfract * _height)));
1107 }
1108
1109 void
1110 AutomationLine::reset ()
1111 {
1112         update_pending = false;
1113
1114         if (no_draw) {
1115                 return;
1116         }
1117
1118         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1119 }
1120
1121 void
1122 AutomationLine::clear ()
1123 {
1124         /* parent must create and commit command */
1125         XMLNode &before = alist->get_state();
1126         alist->clear();
1127
1128         trackview.editor().session()->add_command (
1129                 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1130                 );
1131 }
1132
1133 void
1134 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1135 {
1136 }
1137
1138 void
1139 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1140 {
1141         alist = list;
1142         queue_reset ();
1143         connect_to_list ();
1144 }
1145
1146 void
1147 AutomationLine::show_all_control_points ()
1148 {
1149         points_visible = true;
1150
1151         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1152                 if (!(*i)->visible()) {
1153                         (*i)->show ();
1154                         (*i)->set_visible (true);
1155                 }
1156         }
1157 }
1158
1159 void
1160 AutomationLine::hide_all_but_selected_control_points ()
1161 {
1162         if (alist->interpolation() == AutomationList::Discrete) {
1163                 return;
1164         }
1165
1166         points_visible = false;
1167
1168         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1169                 if (!(*i)->selected()) {
1170                         (*i)->set_visible (false);
1171                 }
1172         }
1173 }
1174
1175 void
1176 AutomationLine::track_entered()
1177 {
1178         if (alist->interpolation() != AutomationList::Discrete) {
1179                 show_all_control_points();
1180         }
1181 }
1182
1183 void
1184 AutomationLine::track_exited()
1185 {
1186         if (alist->interpolation() != AutomationList::Discrete) {
1187                 hide_all_but_selected_control_points();
1188         }
1189 }
1190
1191 XMLNode &
1192 AutomationLine::get_state (void)
1193 {
1194         /* function as a proxy for the model */
1195         return alist->get_state();
1196 }
1197
1198 int
1199 AutomationLine::set_state (const XMLNode &node, int version)
1200 {
1201         /* function as a proxy for the model */
1202         return alist->set_state (node, version);
1203 }
1204
1205 void
1206 AutomationLine::view_to_model_coord (double& x, double& y) const
1207 {
1208         view_to_model_coord_x (x);
1209         view_to_model_coord_y (y);
1210 }
1211
1212 void
1213 AutomationLine::view_to_model_coord_x (double& x) const
1214 {
1215         x = _time_converter.from(x);
1216 }
1217
1218 void
1219 AutomationLine::view_to_model_coord_y (double& y) const
1220 {
1221         /* TODO: This should be more generic ... */
1222         if (alist->parameter().type() == GainAutomation ||
1223             alist->parameter().type() == EnvelopeAutomation) {
1224                 y = slider_position_to_gain (y);
1225                 y = max (0.0, y);
1226                 y = min (2.0, y);
1227         } else if (alist->parameter().type() == PanAutomation) {
1228                 // vertical coordinate axis reversal
1229                 y = 1.0 - y;
1230         } else if (alist->parameter().type() == PluginAutomation) {
1231                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1232         } else {
1233                 y = (int)(y * alist->parameter().max());
1234         }
1235 }
1236
1237 void
1238 AutomationLine::model_to_view_coord (double& x, double& y) const
1239 {
1240         /* TODO: This should be more generic ... */
1241         if (alist->parameter().type() == GainAutomation ||
1242             alist->parameter().type() == EnvelopeAutomation) {
1243                 y = gain_to_slider_position (y);
1244         } else if (alist->parameter().type() == PanAutomation) {
1245                 // vertical coordinate axis reversal
1246                 y = 1.0 - y;
1247         } else if (alist->parameter().type() == PluginAutomation) {
1248                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1249         } else {
1250                 y = y / (double)alist->parameter().max(); /* ... like this */
1251         }
1252
1253         x = _time_converter.to(x);
1254 }
1255
1256 /** Called when our list has announced that its interpolation style has changed */
1257 void
1258 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1259 {
1260         if (style == AutomationList::Discrete) {
1261                 show_all_control_points();
1262                 line->hide();
1263         } else {
1264                 hide_all_but_selected_control_points();
1265                 line->show();
1266         }
1267 }
1268
1269 void
1270 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1271 {
1272         if (view_index >= control_points.size()) {
1273
1274                 /* make sure we have enough control points */
1275
1276                 ControlPoint* ncp = new ControlPoint (*this);
1277                 ncp->set_size (control_point_box_size ());
1278
1279                 control_points.push_back (ncp);
1280         }
1281
1282         ControlPoint::ShapeType shape;
1283
1284         if (!terminal_points_can_slide) {
1285                 if (pi == 0) {
1286                         control_points[view_index]->set_can_slide(false);
1287                         if (tx == 0) {
1288                                 shape = ControlPoint::Start;
1289                         } else {
1290                                 shape = ControlPoint::Full;
1291                         }
1292                 } else if (pi == npoints - 1) {
1293                         control_points[view_index]->set_can_slide(false);
1294                         shape = ControlPoint::End;
1295                 } else {
1296                         control_points[view_index]->set_can_slide(true);
1297                         shape = ControlPoint::Full;
1298                 }
1299         } else {
1300                 control_points[view_index]->set_can_slide(true);
1301                 shape = ControlPoint::Full;
1302         }
1303
1304         control_points[view_index]->reset (tx, ty, model, view_index, shape);
1305
1306         /* finally, control visibility */
1307         
1308         if (_visible && points_visible) {
1309                 control_points[view_index]->show ();
1310                 control_points[view_index]->set_visible (true);
1311         } else {
1312                 if (!points_visible) {
1313                         control_points[view_index]->set_visible (false);
1314                 }
1315         }
1316 }
1317
1318 void
1319 AutomationLine::add_always_in_view (double x)
1320 {
1321         _always_in_view.push_back (x);
1322         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1323 }
1324
1325 void
1326 AutomationLine::clear_always_in_view ()
1327 {
1328         _always_in_view.clear ();
1329         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1330 }
1331
1332 void
1333 AutomationLine::connect_to_list ()
1334 {
1335         _list_connections.drop_connections ();
1336         
1337         alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1338         
1339         alist->InterpolationChanged.connect (
1340                 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1341                 );
1342 }
1343
1344 MementoCommandBinder<AutomationList>*
1345 AutomationLine::memento_command_binder ()
1346 {
1347         return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1348 }