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