don't change waveform zoom level when waveform is hidden, to save peak reloads, remov...
[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 #include <pbd/stacktrace.h>
29
30 #include <ardour/automation_event.h>
31 #include <ardour/curve.h>
32 #include <ardour/dB.h>
33
34 #include "simplerect.h"
35 #include "automation_line.h"
36 #include "rgb_macros.h"
37 #include "ardour_ui.h"
38 #include "public_editor.h"
39 #include "utils.h"
40 #include "selection.h"
41 #include "time_axis_view.h"
42 #include "point_selection.h"
43 #include "automation_selectable.h"
44 #include "automation_time_axis.h"
45 #include "public_editor.h"
46
47 #include <ardour/session.h>
48
49 #include "i18n.h"
50
51 using namespace std;
52 using namespace sigc;
53 using namespace ARDOUR;
54 using namespace PBD;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
57
58 ControlPoint::ControlPoint (AutomationLine& al)
59         : line (al)
60 {
61         model = al.the_list().end();
62         view_index = 0;
63         can_slide = true;
64         _x = 0;
65         _y = 0;
66         _shape = Full;
67         _size = 4.0;
68         selected = false;
69
70         item = new Canvas::SimpleRect (line.canvas_group());
71         item->property_draw() = true;
72         item->property_fill() = false;
73         item->property_fill_color_rgba() =  color_map[cControlPointFill];
74         item->property_outline_color_rgba() = color_map[cControlPointOutline];
75         item->property_outline_pixels() = 1;
76         item->set_data ("control_point", this);
77         item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
78
79         hide ();
80         set_visible (false);
81 }
82
83 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
84         : line (other.line)
85 {
86         if (&other == this) {
87                 return;
88         }
89
90         model = other.model;
91         view_index = other.view_index;
92         can_slide = other.can_slide;
93         _x = other._x;
94         _y = other._y;
95         _shape = other._shape;
96         _size = other._size;
97         selected = false;
98
99         item = new Canvas::SimpleRect (line.canvas_group());
100         item->property_fill() = false;
101         item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
102         item->property_outline_pixels() = 1;
103         
104         /* NOTE: no event handling in copied ControlPoints */
105
106         hide ();
107         set_visible (false);
108 }
109
110 ControlPoint::~ControlPoint ()
111 {
112         delete item;
113 }
114
115 bool
116 ControlPoint::event_handler (GdkEvent* event)
117 {
118         return PublicEditor::instance().canvas_control_point_event (event, item, this);
119 }
120
121 void
122 ControlPoint::hide ()
123 {
124         item->hide();
125 }
126
127 void
128 ControlPoint::show()
129 {
130         item->show();
131 }
132
133 void
134 ControlPoint::set_visible (bool yn)
135 {
136         item->property_draw() = (gboolean) yn;
137 }
138
139 void
140 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
141 {
142         model = mi;
143         view_index = vi;
144         move_to (x, y, shape);
145 }
146
147 void
148 ControlPoint::show_color (bool entered, bool hide_too)
149 {
150         if (entered) {
151                 if (selected) {
152                         item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
153                         set_visible(true);
154                 } else {
155                         item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
156                         if (hide_too) {
157                                 set_visible(false);
158                         }
159                 }
160
161         } else {
162                 if (selected) {
163                         item->property_outline_color_rgba() = color_map[cControlPointSelected];
164                         set_visible(true);
165                 } else {
166                         item->property_outline_color_rgba() = color_map[cControlPoint];
167                         if (hide_too) {
168                                 set_visible(false);
169                         }
170                 }
171         }
172 }
173
174 void
175 ControlPoint::set_size (double sz)
176 {
177         _size = sz;
178
179 #if 0   
180         if (_size > 6.0) {
181                 item->property_fill() = (gboolean) TRUE;
182         } else {
183                 item->property_fill() = (gboolean) FALSE;
184         }
185 #endif
186
187         move_to (_x, _y, _shape);
188 }
189
190 void
191 ControlPoint::move_to (double x, double y, ShapeType shape)
192 {
193         double x1 = 0;
194         double x2 = 0;
195         double half_size = rint(_size/2.0);
196
197         switch (shape) {
198         case Full:
199                 x1 = x - half_size;
200                 x2 = x + half_size;
201                 break;
202         case Start:
203                 x1 = x;
204                 x2 = x + half_size;
205                 break;
206         case End:
207                 x1 = x - half_size;
208                 x2 = x;
209                 break;
210         }
211
212         item->property_x1() = x1;
213         item->property_x2() = x2;
214         item->property_y1() = y - half_size;
215         item->property_y2() = y + half_size;
216
217         _x = x;
218         _y = y;
219         _shape = shape;
220 }
221
222 /*****/
223
224 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
225         : trackview (tv),
226           _name (name),
227           alist (al),
228           _parent_group (parent)
229 {
230         points_visible = false;
231         update_pending = false;
232         _vc_uses_gain_mapping = false;
233         no_draw = false;
234         _visible = true;
235         terminal_points_can_slide = true;
236         _height = 0;
237
238         group = new ArdourCanvas::Group (parent);
239         group->property_x() = 0.0;
240         group->property_y() = 0.0;
241
242         line = new ArdourCanvas::Line (*group);
243         line->property_width_pixels() = (guint)1;
244         line->set_data ("line", this);
245
246         line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
247
248         alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
249
250         trackview.session().register_with_memento_command_factory(alist.id(), this);
251
252 }
253
254 AutomationLine::~AutomationLine ()
255 {
256         vector_delete (&control_points);
257         delete group;
258 }
259
260 bool
261 AutomationLine::event_handler (GdkEvent* event)
262 {
263         return PublicEditor::instance().canvas_line_event (event, line, this);
264 }
265
266 void
267 AutomationLine::queue_reset ()
268 {
269         if (!update_pending) {
270                 update_pending = true;
271                 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
272         }
273 }
274
275 void
276 AutomationLine::show () 
277 {
278         line->show();
279
280         if (points_visible) {
281                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
282                         (*i)->show ();
283                 }
284         }
285
286         _visible = true;
287 }
288
289 void
290 AutomationLine::hide () 
291 {
292         line->hide();
293         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
294                 (*i)->hide();
295         }
296         _visible = false;
297 }
298
299 double
300 AutomationLine::control_point_box_size ()
301 {
302         if (_height > TimeAxisView::hLarger) {
303                 return 8.0;
304         } else if (_height > (guint32) TimeAxisView::hNormal) {
305                 return 6.0;
306         } 
307         return 4.0;
308 }
309
310 void
311 AutomationLine::set_height (guint32 h)
312 {
313         if (h != _height) {
314                 _height = h;
315
316                 double bsz = control_point_box_size();
317
318                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
319                         (*i)->set_size (bsz);
320                 }
321
322                 reset ();
323         }
324 }
325
326 void
327 AutomationLine::set_line_color (uint32_t color)
328 {
329         _line_color = color;
330         line->property_fill_color_rgba() = color;
331 }
332
333 void
334 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
335 {
336         if (yn != _vc_uses_gain_mapping) {
337                 _vc_uses_gain_mapping = yn;
338                 reset ();
339         }
340 }
341
342 ControlPoint*
343 AutomationLine::nth (uint32_t n)
344 {
345         if (n < control_points.size()) {
346                 return control_points[n];
347         } else {
348                 return 0;
349         }
350 }
351
352 void
353 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
354 {
355         double delta = 0.0;
356         uint32_t last_movable = UINT_MAX;
357         double x_limit = DBL_MAX;
358
359         /* this just changes the current view. it does not alter
360            the model in any way at all.
361         */
362
363         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
364            and needs to be converted to a canvas unit distance.
365         */
366
367         y = max (0.0, y);
368         y = min (1.0, y);
369         y = _height - (y * _height);
370
371         if (cp.can_slide) {
372
373                 /* x-coord cannot move beyond adjacent points or the start/end, and is
374                    already in frames. it needs to be converted to canvas units.
375                 */
376                 
377                 x = trackview.editor.frame_to_unit (x);
378
379                 /* clamp x position using view coordinates */
380
381                 ControlPoint *before;
382                 ControlPoint *after;
383
384                 if (cp.view_index) {
385                         before = nth (cp.view_index - 1);
386                         x = max (x, before->get_x()+1.0);
387                 } else {
388                         before = &cp;
389                 }
390
391
392                 if (!with_push) {
393                         if (cp.view_index < control_points.size() - 1) {
394                 
395                                 after = nth (cp.view_index + 1);
396                 
397                                 /*if it is a "spike" leave the x alone */
398  
399                                 if (after->get_x() - before->get_x() < 2) {
400                                         x = cp.get_x();
401                                         
402                                 } else {
403                                         x = min (x, after->get_x()-1.0);
404                                 }
405                         } else {
406                                 after = &cp;
407                         }
408
409                 } else {
410
411                         ControlPoint* after;
412                         
413                         /* find the first point that can't move */
414
415                         for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
416                                 if (!after->can_slide) {
417                                         x_limit = after->get_x() - 1.0;
418                                         last_movable = after->view_index;
419                                         break;
420                                 }
421                         }
422                         
423                         delta = x - cp.get_x();
424                 }
425                         
426         } else {
427
428                 /* leave the x-coordinate alone */
429
430                 x = trackview.editor.frame_to_unit ((*cp.model)->when);
431
432         }
433
434         if (!with_push) {
435
436                 cp.move_to (x, y, ControlPoint::Full);
437                 reset_line_coords (cp);
438
439         } else {
440
441                 uint32_t limit = min (control_points.size(), (size_t)last_movable);
442                 
443                 /* move the current point to wherever the user told it to go, subject
444                    to x_limit.
445                 */
446                 
447                 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
448                 reset_line_coords (cp);
449                 
450                 /* now move all subsequent control points, to reflect the motion.
451                  */
452                 
453                 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
454                         ControlPoint *p = nth (i);
455                         double new_x;
456
457                         if (p->can_slide) {
458                                 new_x = min (p->get_x() + delta, x_limit);
459                                 p->move_to (new_x, p->get_y(), ControlPoint::Full);
460                                 reset_line_coords (*p);
461                         }
462                 }
463         }
464 }
465
466 void
467 AutomationLine::reset_line_coords (ControlPoint& cp)
468 {       
469         if (cp.view_index < line_points.size()) {
470                 line_points[cp.view_index].set_x (cp.get_x());
471                 line_points[cp.view_index].set_y (cp.get_y());
472         }
473 }
474
475 void
476 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
477 {
478
479         ControlPoint *p;
480
481         update_pending = true;
482
483         for (uint32_t i = start; i <= end; ++i) {
484                 p = nth(i);
485                 sync_model_with_view_point (*p, false, 0);
486         }
487 }
488
489 void
490 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
491 {
492         /* part one: find out where the visual control point is.
493            initial results are in canvas units. ask the
494            line to convert them to something relevant.
495         */
496         
497         mr.xval = (nframes_t) floor (cp.get_x());
498         mr.yval = 1.0 - (cp.get_y() / _height);
499
500         /* if xval has not changed, set it directly from the model to avoid rounding errors */
501
502         if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
503                 mr.xval = (nframes_t) (*cp.model)->when;
504         } else {
505                 mr.xval = trackview.editor.unit_to_frame (mr.xval);
506         }
507
508         /* virtual call: this will do the right thing
509            for whatever particular type of line we are.
510         */
511         
512         view_to_model_y (mr.yval);
513
514         /* part 2: find out where the model point is now
515          */
516
517         mr.xpos = (nframes_t) (*cp.model)->when;
518         mr.ypos = (*cp.model)->value;
519
520         /* part 3: get the position of the visual control
521            points before and after us.
522         */
523
524         ControlPoint* before;
525         ControlPoint* after;
526
527         if (cp.view_index) {
528                 before = nth (cp.view_index - 1);
529         } else {
530                 before = 0;
531         }
532
533         after = nth (cp.view_index + 1);
534
535         if (before) {
536                 mr.xmin = (nframes_t) (*before->model)->when;
537                 mr.ymin = (*before->model)->value;
538                 mr.start = before->model;
539                 ++mr.start;
540         } else {
541                 mr.xmin = mr.xpos;
542                 mr.ymin = mr.ypos;
543                 mr.start = cp.model;
544         }
545
546         if (after) {
547                 mr.end = after->model;
548         } else {
549                 mr.xmax = mr.xpos;
550                 mr.ymax = mr.ypos;
551                 mr.end = cp.model;
552                 ++mr.end;
553         }
554 }
555
556 void
557 AutomationLine::determine_visible_control_points (ALPoints& points)
558 {
559         uint32_t view_index, pi, n;
560         AutomationList::iterator model;
561         uint32_t npoints;
562         double last_control_point_x = 0.0;
563         double last_control_point_y = 0.0;
564         uint32_t this_rx = 0;
565         uint32_t prev_rx = 0;
566         uint32_t this_ry = 0;
567         uint32_t prev_ry = 0;   
568         double* slope;
569         uint32_t box_size;
570         uint32_t cpsize;
571
572         /* hide all existing points, and the line */
573
574         cpsize = 0;
575         
576         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
577                 (*i)->hide();
578                 ++cpsize;
579         }
580
581         line->hide ();
582
583         if (points.empty()) {
584                 return;
585         }
586
587         npoints = points.size();
588
589         /* compute derivative/slope for the entire line */
590
591         slope = new double[npoints];
592
593         for (n = 0; n < npoints - 1; ++n) {
594                 double xdelta = points[n+1].x - points[n].x;
595                 double ydelta = points[n+1].y - points[n].y;
596                 slope[n] = ydelta/xdelta;
597         }
598
599         box_size = (uint32_t) control_point_box_size ();
600
601         /* read all points and decide which ones to show as control points */
602
603         view_index = 0;
604
605         for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
606
607                 double tx = points[pi].x;
608                 double ty = points[pi].y;
609
610                 /* now ensure that the control_points vector reflects the current curve
611                    state, but don't plot control points too close together. also, don't
612                    plot a series of points all with the same value.
613
614                    always plot the first and last points, of course.
615                 */
616
617                 if (invalid_point (points, pi)) {
618                         /* for some reason, we are supposed to ignore this point,
619                            but still keep track of the model index.
620                         */
621                         continue;
622                 }
623
624                 if (pi > 0 && pi < npoints - 1) {
625                         if (slope[pi] == slope[pi-1]) {
626
627                                 /* no reason to display this point */
628                                 
629                                 continue;
630                         }
631                 }
632                 
633                 /* need to round here. the ultimate coordinates are integer
634                    pixels, so tiny deltas in the coords will be eliminated
635                    and we end up with "colinear" line segments. since the
636                    line rendering code in libart doesn't like this very
637                    much, we eliminate them here. don't do this for the first and last
638                    points.
639                 */
640
641                 this_rx = (uint32_t) rint (tx);
642                 this_ry = (uint32_t) rint (ty); 
643  
644                 if (view_index && pi != npoints && /* not the first, not the last */
645                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
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 }