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