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