remove all lines to avoid recompiles after commits
[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_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         if (cp.view_index < line_points.size()) {
469                 line_points[cp.view_index].set_x (cp.get_x());
470                 line_points[cp.view_index].set_y (cp.get_y());
471         }
472 }
473
474 void
475 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
476 {
477
478         ControlPoint *p;
479
480         update_pending = true;
481
482         for (uint32_t i = start; i <= end; ++i) {
483                 p = nth(i);
484                 sync_model_with_view_point (*p, false, 0);
485         }
486 }
487
488 void
489 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
490 {
491         /* part one: find out where the visual control point is.
492            initial results are in canvas units. ask the
493            line to convert them to something relevant.
494         */
495         
496         mr.xval = (nframes_t) floor (cp.get_x());
497         mr.yval = 1.0 - (cp.get_y() / _height);
498
499         /* if xval has not changed, set it directly from the model to avoid rounding errors */
500
501         if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
502                 mr.xval = (nframes_t) (*cp.model)->when;
503         } else {
504                 mr.xval = trackview.editor.unit_to_frame (mr.xval);
505         }
506
507         /* virtual call: this will do the right thing
508            for whatever particular type of line we are.
509         */
510         
511         view_to_model_y (mr.yval);
512
513         /* part 2: find out where the model point is now
514          */
515
516         mr.xpos = (nframes_t) (*cp.model)->when;
517         mr.ypos = (*cp.model)->value;
518
519         /* part 3: get the position of the visual control
520            points before and after us.
521         */
522
523         ControlPoint* before;
524         ControlPoint* after;
525
526         if (cp.view_index) {
527                 before = nth (cp.view_index - 1);
528         } else {
529                 before = 0;
530         }
531
532         after = nth (cp.view_index + 1);
533
534         if (before) {
535                 mr.xmin = (nframes_t) (*before->model)->when;
536                 mr.ymin = (*before->model)->value;
537                 mr.start = before->model;
538                 ++mr.start;
539         } else {
540                 mr.xmin = mr.xpos;
541                 mr.ymin = mr.ypos;
542                 mr.start = cp.model;
543         }
544
545         if (after) {
546                 mr.end = after->model;
547         } else {
548                 mr.xmax = mr.xpos;
549                 mr.ymax = mr.ypos;
550                 mr.end = cp.model;
551                 ++mr.end;
552         }
553 }
554
555 void
556 AutomationLine::determine_visible_control_points (ALPoints& points)
557 {
558         uint32_t view_index, pi, n;
559         AutomationList::iterator model;
560         uint32_t npoints;
561         double last_control_point_x = 0.0;
562         double last_control_point_y = 0.0;
563         uint32_t this_rx = 0;
564         uint32_t prev_rx = 0;
565         uint32_t this_ry = 0;
566         uint32_t prev_ry = 0;   
567         double* slope;
568         uint32_t box_size;
569         uint32_t cpsize;
570
571         /* hide all existing points, and the line */
572
573         cpsize = 0;
574         
575         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
576                 (*i)->hide();
577                 ++cpsize;
578         }
579
580         line->hide ();
581
582         if (points.empty()) {
583                 return;
584         }
585
586         npoints = points.size();
587
588         /* compute derivative/slope for the entire line */
589
590         slope = new double[npoints];
591
592         for (n = 0; n < npoints - 1; ++n) {
593                 double xdelta = points[n+1].x - points[n].x;
594                 double ydelta = points[n+1].y - points[n].y;
595                 slope[n] = ydelta/xdelta;
596         }
597
598         box_size = (uint32_t) control_point_box_size ();
599
600         /* read all points and decide which ones to show as control points */
601
602         view_index = 0;
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                 /* now ensure that the control_points vector reflects the current curve
610                    state, but don't plot control points too close together. also, don't
611                    plot a series of points all with the same value.
612
613                    always plot the first and last points, of course.
614                 */
615
616                 if (invalid_point (points, pi)) {
617                         /* for some reason, we are supposed to ignore this point,
618                            but still keep track of the model index.
619                         */
620                         continue;
621                 }
622
623                 if (pi > 0 && pi < npoints - 1) {
624                         if (slope[pi] == slope[pi-1]) {
625
626                                 /* no reason to display this point */
627                                 
628                                 continue;
629                         }
630                 }
631                 
632                 /* need to round here. the ultimate coordinates are integer
633                    pixels, so tiny deltas in the coords will be eliminated
634                    and we end up with "colinear" line segments. since the
635                    line rendering code in libart doesn't like this very
636                    much, we eliminate them here. don't do this for the first and last
637                    points.
638                 */
639
640                 this_rx = (uint32_t) rint (tx);
641                 this_ry = (uint32_t) rint (ty); 
642  
643                 if (view_index && pi != npoints && /* not the first, not the last */
644                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
645                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
646                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
647                         continue;
648                 } 
649
650                 /* ok, we should display this point */
651
652                 if (view_index >= cpsize) {
653
654                         /* make sure we have enough control points */
655
656                         ControlPoint* ncp = new ControlPoint (*this);
657                         
658                         ncp->set_size (box_size); 
659
660                         control_points.push_back (ncp);
661                         ++cpsize;
662                 }
663
664                 ControlPoint::ShapeType shape;
665
666                 if (!terminal_points_can_slide) {
667                         if (pi == 0) {
668                                 control_points[view_index]->can_slide = false;
669                                 if (tx == 0) {
670                                         shape = ControlPoint::Start;
671                                 } else {
672                                         shape = ControlPoint::Full;
673                                 }
674                         } else if (pi == npoints - 1) {
675                                 control_points[view_index]->can_slide = false;
676                                 shape = ControlPoint::End;
677                         } else {
678                                 control_points[view_index]->can_slide = true;
679                                 shape = ControlPoint::Full;
680                         }
681                 } else {
682                         control_points[view_index]->can_slide = true;
683                         shape = ControlPoint::Full;
684                 }
685
686                 last_control_point_x = tx;
687                 last_control_point_y = ty;
688
689                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
690
691                 prev_rx = this_rx;
692                 prev_ry = this_ry;
693
694                 /* finally, control visibility */
695                 
696                 if (_visible && points_visible) {
697                         control_points[view_index]->show ();
698                         control_points[view_index]->set_visible (true);
699                 } else {
700                         if (!points_visible) {
701                                 control_points[view_index]->set_visible (false);
702                         }
703                 }
704
705                 view_index++;
706         }
707         
708         /* discard extra CP's to avoid confusing ourselves */
709
710         while (control_points.size() > view_index) {
711                 ControlPoint* cp = control_points.back();
712                 control_points.pop_back ();
713                 delete cp;
714         }
715
716         if (!terminal_points_can_slide) {
717                 control_points.back()->can_slide = false;
718         }
719
720         delete [] slope;
721
722         if (view_index > 1) {
723
724                 npoints = view_index;
725                 
726                 /* reset the line coordinates */
727
728                 while (line_points.size() < npoints) {
729                         line_points.push_back (Art::Point (0,0));
730                 }
731
732                 while (line_points.size() > npoints) {
733                         line_points.pop_back ();
734                 }
735
736                 for (view_index = 0; view_index < npoints; ++view_index) {
737                         line_points[view_index].set_x (control_points[view_index]->get_x());
738                         line_points[view_index].set_y (control_points[view_index]->get_y());
739                 }
740                 
741                 line->property_points() = line_points;
742
743                 if (_visible) {
744                         line->show ();
745                 }
746
747         } 
748
749         set_selected_points (trackview.editor.get_selection().points);
750
751 }
752
753 string
754 AutomationLine::get_verbose_cursor_string (float fraction)
755 {
756         char buf[32];
757
758         if (_vc_uses_gain_mapping) {
759                 if (fraction == 0.0) {
760                         snprintf (buf, sizeof (buf), "-inf dB");
761                 } else {
762                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
763                 }
764         } else {
765                 snprintf (buf, sizeof (buf), "%.2f", fraction);
766         }
767
768         return buf;
769 }
770
771 bool
772 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
773 {
774         return p[index].x == max_frames && p[index].y == DBL_MAX;
775 }
776
777 void
778 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
779 {
780         p[index].x = max_frames;
781         p[index].y = DBL_MAX;
782 }
783
784 void
785 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
786 {
787         if (trackview.editor.current_session() == 0) { /* how? */
788                 return;
789         }
790
791         string str;
792
793         if (cp) {
794                 str = _("automation event move");
795         } else {
796                 str = _("automation range drag");
797         }
798
799         trackview.editor.current_session()->begin_reversible_command (str);
800         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
801         
802         drag_x = x;
803         drag_distance = 0;
804         first_drag_fraction = fraction;
805         last_drag_fraction = fraction;
806         drags = 0;
807         did_push = false;
808 }
809
810 void
811 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
812 {
813         if (x > drag_x) {
814                 drag_distance += (x - drag_x);
815         } else {
816                 drag_distance -= (drag_x - x);
817         }
818
819         drag_x = x;
820
821         modify_view_point (cp, x, fraction, with_push);
822
823         if (line_points.size() > 1) {
824                 line->property_points() = line_points;
825         }
826
827         drags++;
828         did_push = with_push;
829 }
830
831 void
832 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
833 {
834         double ydelta = fraction - last_drag_fraction;
835
836         did_push = with_push;
837         
838         last_drag_fraction = fraction;
839
840         line_drag_cp1 = i1;
841         line_drag_cp2 = i2;
842         
843         ControlPoint *cp;
844
845         for (uint32_t i = i1 ; i <= i2; i++) {
846                 cp = nth (i);
847                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
848         }
849
850         if (line_points.size() > 1) {
851                 line->property_points() = line_points;
852         }
853
854         drags++;
855 }
856
857 void
858 AutomationLine::end_drag (ControlPoint* cp) 
859 {
860         if (!drags) {
861                 return;
862         }
863
864         alist.freeze ();
865
866         if (cp) {
867                 sync_model_with_view_point (*cp, did_push, drag_distance);
868         } else {
869                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
870         }
871         
872         alist.thaw ();
873
874         update_pending = false;
875
876         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
877         trackview.editor.current_session()->commit_reversible_command ();
878         trackview.editor.current_session()->set_dirty ();
879 }
880
881
882 void
883 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
884 {
885         ModelRepresentation mr;
886         double ydelta;
887
888         model_representation (cp, mr);
889
890         /* how much are we changing the central point by */ 
891
892         ydelta = mr.yval - mr.ypos;
893
894         /*
895            apply the full change to the central point, and interpolate
896            on both axes to cover all model points represented
897            by the control point.
898         */
899
900         /* change all points before the primary point */
901
902         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
903                 
904                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
905                 double y_delta = ydelta * fract;
906                 double x_delta = distance * fract;
907
908                 /* interpolate */
909                 
910                 if (y_delta || x_delta) {
911                         alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
912                 }
913         }
914
915         /* change the primary point */
916
917         update_pending = true;
918         alist.modify (cp.model, mr.xval, mr.yval);
919
920
921         /* change later points */
922         
923         AutomationList::iterator i = cp.model;
924         
925         ++i;
926         
927         while (i != mr.end) {
928                 
929                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
930
931                 /* all later points move by the same distance along the x-axis as the main point */
932                 
933                 if (delta) {
934                         alist.modify (i, (*i)->when + distance, (*i)->value + delta);
935                 }
936                 
937                 ++i;
938         }
939                 
940         if (did_push) {
941
942                 /* move all points after the range represented by the view by the same distance
943                    as the main point moved.
944                 */
945
946                 alist.slide (mr.end, drag_distance);
947         }
948
949 }
950
951 bool 
952 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
953 {
954         ControlPoint *bcp = 0;
955         ControlPoint *acp = 0;
956         double unit_xval;
957
958         /* xval is in frames */
959
960         unit_xval = trackview.editor.frame_to_unit (xval);
961
962         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
963                 
964                 if ((*i)->get_x() <= unit_xval) {
965
966                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
967                                 bcp = *i;
968                                 before = bcp->view_index;
969                         } 
970
971                 } else if ((*i)->get_x() > unit_xval) {
972                         acp = *i;
973                         after = acp->view_index;
974                         break;
975                 }
976         }
977
978         return bcp && acp;
979 }
980
981 bool
982 AutomationLine::is_last_point (ControlPoint& cp)
983 {
984         ModelRepresentation mr;
985
986         model_representation (cp, mr);
987
988         // If the list is not empty, and the point is the last point in the list
989
990         if (!alist.empty() && mr.end == alist.end()) {
991                 return true;
992         }
993         
994         return false;
995 }
996
997 bool
998 AutomationLine::is_first_point (ControlPoint& cp)
999 {
1000         ModelRepresentation mr;
1001
1002         model_representation (cp, mr);
1003
1004         // If the list is not empty, and the point is the first point in the list
1005
1006         if (!alist.empty() && mr.start == alist.begin()) {
1007                 return true;
1008         }
1009         
1010         return false;
1011 }
1012
1013 // This is copied into AudioRegionGainLine
1014 void
1015 AutomationLine::remove_point (ControlPoint& cp)
1016 {
1017         ModelRepresentation mr;
1018
1019         model_representation (cp, mr);
1020
1021         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1022         XMLNode &before = alist.get_state();
1023
1024         alist.erase (mr.start, mr.end);
1025
1026         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1027         trackview.editor.current_session()->commit_reversible_command ();
1028         trackview.editor.current_session()->set_dirty ();
1029 }
1030
1031 void
1032 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1033                                  double botfrac, double topfrac, list<Selectable*>& results)
1034 {
1035
1036         double top;
1037         double bot;
1038         nframes_t nstart;
1039         nframes_t nend;
1040         bool collecting = false;
1041
1042         /* Curse X11 and its inverted coordinate system! */
1043         
1044         bot = (1.0 - topfrac) * _height;
1045         top = (1.0 - botfrac) * _height;
1046         
1047         nstart = max_frames;
1048         nend = 0;
1049
1050         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1051                 
1052                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1053
1054                 if (when >= start && when <= end) {
1055                         
1056                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1057
1058                                 (*i)->show();
1059                                 (*i)->set_visible(true);
1060                                 collecting = true;
1061                                 nstart = min (nstart, when);
1062                                 nend = max (nend, when);
1063
1064                         } else {
1065                                 
1066                                 if (collecting) {
1067
1068                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1069                                         collecting = false;
1070                                         nstart = max_frames;
1071                                         nend = 0;
1072                                 }
1073                         }
1074                 }
1075         }
1076
1077         if (collecting) {
1078                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1079         }
1080
1081 }
1082
1083 void
1084 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1085 {
1086         // hmmm ....
1087 }
1088
1089 void
1090 AutomationLine::set_selected_points (PointSelection& points)
1091 {
1092         double top;
1093         double bot;
1094
1095         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1096                         (*i)->selected = false;
1097         }
1098
1099         if (points.empty()) {
1100                 goto out;
1101         } 
1102         
1103         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1104                 
1105                 if (&(*r).track != &trackview) {
1106                         continue;
1107                 }
1108
1109                 /* Curse X11 and its inverted coordinate system! */
1110
1111                 bot = (1.0 - (*r).high_fract) * _height;
1112                 top = (1.0 - (*r).low_fract) * _height;
1113
1114                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1115                         
1116                         double rstart, rend;
1117                         
1118                         rstart = trackview.editor.frame_to_unit ((*r).start);
1119                         rend = trackview.editor.frame_to_unit ((*r).end);
1120                         
1121                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1122                                 
1123                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1124                                         
1125                                         (*i)->selected = true;
1126                                 }
1127                         }
1128
1129                 }
1130         }
1131
1132   out:
1133         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1134                 (*i)->show_color (false, !points_visible);
1135         }
1136
1137 }
1138
1139 void
1140 AutomationLine::show_selection ()
1141 {
1142         TimeSelection& time (trackview.editor.get_selection().time);
1143
1144         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1145                 
1146                 (*i)->selected = false;
1147
1148                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1149                         double rstart, rend;
1150                         
1151                         rstart = trackview.editor.frame_to_unit ((*r).start);
1152                         rend = trackview.editor.frame_to_unit ((*r).end);
1153                         
1154                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1155                                 (*i)->selected = true;
1156                                 break;
1157                         }
1158                 }
1159                 
1160                 (*i)->show_color (false, !points_visible);
1161         }
1162 }
1163
1164 void
1165 AutomationLine::hide_selection ()
1166 {
1167 //      show_selection ();
1168 }
1169
1170 void
1171 AutomationLine::list_changed ()
1172 {
1173         queue_reset ();
1174 }
1175
1176 void
1177 AutomationLine::reset_callback (const AutomationList& events)
1178 {
1179         ALPoints tmp_points;
1180         uint32_t npoints = events.size();
1181
1182         if (npoints == 0) {
1183                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1184                         delete *i;
1185                 }
1186                 control_points.clear ();
1187                 line->hide();
1188                 return;
1189         }
1190
1191         AutomationList::const_iterator ai;
1192
1193         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1194
1195                 double translated_y = (*ai)->value;
1196                 model_to_view_y (translated_y);
1197
1198                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1199                                                _height - (translated_y * _height)));
1200         }
1201         
1202         determine_visible_control_points (tmp_points);
1203 }
1204
1205 void
1206 AutomationLine::reset ()
1207 {
1208         update_pending = false;
1209
1210         if (no_draw) {
1211                 return;
1212         }
1213
1214         alist.apply_to_points (*this, &AutomationLine::reset_callback);
1215 }
1216
1217 void
1218 AutomationLine::clear ()
1219 {
1220         /* parent must create command */
1221         XMLNode &before = get_state();
1222         alist.clear();
1223         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1224         trackview.editor.current_session()->commit_reversible_command ();
1225         trackview.editor.current_session()->set_dirty ();
1226 }
1227
1228 void
1229 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1230 {
1231 }
1232
1233 void
1234 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1235 {
1236         alist.move_range (start, end, xdelta, ydelta);
1237 }
1238
1239 void
1240 AutomationLine::show_all_control_points ()
1241 {
1242         points_visible = true;
1243
1244         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1245                 (*i)->show ();
1246                 (*i)->set_visible (true);
1247         }
1248 }
1249
1250 void
1251 AutomationLine::hide_all_but_selected_control_points ()
1252 {
1253         points_visible = false;
1254
1255         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1256                 if (!(*i)->selected) {
1257                         (*i)->set_visible (false);
1258                 }
1259         }
1260 }
1261
1262 XMLNode &
1263 AutomationLine::get_state (void)
1264 {
1265         /* function as a proxy for the model */
1266         return alist.get_state();
1267 }
1268
1269 int 
1270 AutomationLine::set_state (const XMLNode &node)
1271 {
1272         /* function as a proxy for the model */
1273         return alist.set_state (node);
1274 }