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