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