the "final" (??) fixes for transport state stuff before 2.8.4. y'all let me know...
[ardour.git] / gtk2_ardour / automation_line.cc
1 /*
2     Copyright (C) 2002-2003 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
24
25 #include <pbd/stl_delete.h>
26 #include <pbd/memento_command.h>
27 #include <pbd/stacktrace.h>
28
29 #include <ardour/automation_event.h>
30 #include <ardour/curve.h>
31 #include <ardour/dB.h>
32
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "rgb_macros.h"
36 #include "ardour_ui.h"
37 #include "public_editor.h"
38 #include "utils.h"
39 #include "selection.h"
40 #include "time_axis_view.h"
41 #include "point_selection.h"
42 #include "automation_selectable.h"
43 #include "automation_time_axis.h"
44 #include "public_editor.h"
45
46 #include <ardour/session.h>
47
48 #include "i18n.h"
49
50 using namespace std;
51 using namespace sigc;
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Editing;
55 using namespace Gnome; // for Canvas
56
57 ControlPoint::ControlPoint (AutomationLine& al)
58         : line (al)
59 {
60         model = al.the_list().end();
61         view_index = 0;
62         can_slide = true;
63         _x = 0;
64         _y = 0;
65         _shape = Full;
66         _size = 4.0;
67         selected = false;
68
69         item = new Canvas::SimpleRect (line.canvas_group());
70         item->property_draw() = true;
71         item->property_fill() = false;
72         item->property_fill_color_rgba() =  ARDOUR_UI::config()->canvasvar_ControlPointFill.get();
73         item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPointOutline.get();
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() = ARDOUR_UI::config()->canvasvar_EnteredControlPointOutline.get();
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() = ARDOUR_UI::config()->canvasvar_EnteredControlPointSelected.get();
152                         set_visible(true);
153                 } else {
154                         item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPoint.get();
155                         if (hide_too) {
156                                 set_visible(false);
157                         }
158                 }
159
160         } else {
161                 if (selected) {
162                         item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPointSelected.get();
163                         set_visible(true);
164                 } else {
165                         item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPoint.get();
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         auto_is_boolean = 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                 if (isnan (tx) || isnan (ty)) {
610                         warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
611                                                    _name) << endmsg;
612                         continue;
613                 }
614
615                 /* now ensure that the control_points vector reflects the current curve
616                    state, but don't plot control points too close together. also, don't
617                    plot a series of points all with the same value.
618
619                    always plot the first and last points, of course.
620                 */
621
622                 if (invalid_point (points, pi)) {
623                         /* for some reason, we are supposed to ignore this point,
624                            but still keep track of the model index.
625                         */
626                         continue;
627                 }
628
629                 if (pi > 0 && pi < npoints - 1) {
630                         if (slope[pi] == slope[pi-1]) {
631
632                                 /* no reason to display this point */
633                                 
634                                 continue;
635                         }
636                 }
637                 
638                 /* need to round here. the ultimate coordinates are integer
639                    pixels, so tiny deltas in the coords will be eliminated
640                    and we end up with "colinear" line segments. since the
641                    line rendering code in libart doesn't like this very
642                    much, we eliminate them here. don't do this for the first and last
643                    points.
644                 */
645
646                 this_rx = (uint32_t) rint (tx);
647                 this_ry = (uint32_t) rint (ty); 
648  
649                 if (view_index && pi != npoints && /* not the first, not the last */
650                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
651                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
652                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
653                         continue;
654                 } 
655
656                 /* ok, we should display this point */
657
658                 if (view_index >= cpsize) {
659
660                         /* make sure we have enough control points */
661
662                         ControlPoint* ncp = new ControlPoint (*this);
663                         
664                         ncp->set_size (box_size); 
665
666                         control_points.push_back (ncp);
667                         ++cpsize;
668                 }
669
670                 ControlPoint::ShapeType shape;
671
672                 if (!terminal_points_can_slide) {
673                         if (pi == 0) {
674                                 control_points[view_index]->can_slide = false;
675                                 if (tx == 0) {
676                                         shape = ControlPoint::Start;
677                                 } else {
678                                         shape = ControlPoint::Full;
679                                 }
680                         } else if (pi == npoints - 1) {
681                                 control_points[view_index]->can_slide = false;
682                                 shape = ControlPoint::End;
683                         } else {
684                                 control_points[view_index]->can_slide = true;
685                                 shape = ControlPoint::Full;
686                         }
687                 } else {
688                         control_points[view_index]->can_slide = true;
689                         shape = ControlPoint::Full;
690                 }
691
692                 last_control_point_x = tx;
693                 last_control_point_y = ty;
694
695                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
696
697                 prev_rx = this_rx;
698                 prev_ry = this_ry;
699
700                 /* finally, control visibility */
701                 
702                 if (_visible && points_visible) {
703                         control_points[view_index]->show ();
704                         control_points[view_index]->set_visible (true);
705                 } else {
706                         if (!points_visible) {
707                                 control_points[view_index]->set_visible (false);
708                         }
709                 }
710
711                 view_index++;
712         }
713         
714         /* discard extra CP's to avoid confusing ourselves */
715
716         while (control_points.size() > view_index) {
717                 ControlPoint* cp = control_points.back();
718                 control_points.pop_back ();
719                 delete cp;
720         }
721
722         if (!terminal_points_can_slide) {
723                 control_points.back()->can_slide = false;
724         }
725
726         delete [] slope;
727
728         if (view_index > 1) {
729
730                 npoints = view_index;
731                 
732                 /* reset the line coordinates */
733
734                 while (line_points.size() < npoints) {
735                         line_points.push_back (Art::Point (0,0));
736                 }
737
738                 while (line_points.size() > npoints) {
739                         line_points.pop_back ();
740                 }
741
742                 for (view_index = 0; view_index < npoints; ++view_index) {
743                         line_points[view_index].set_x (control_points[view_index]->get_x());
744                         line_points[view_index].set_y (control_points[view_index]->get_y());
745                 }
746                 
747                 line->property_points() = line_points;
748
749                 if (_visible) {
750                         line->show ();
751                 }
752
753         } 
754
755         set_selected_points (trackview.editor.get_selection().points);
756
757 }
758
759 string
760 AutomationLine::get_verbose_cursor_string (float fraction)
761 {
762         char buf[32];
763
764         if (_vc_uses_gain_mapping) {
765                 if (fraction == 0.0) {
766                         snprintf (buf, sizeof (buf), "-inf dB");
767                 } else {
768                         snprintf (buf, sizeof (buf), "%.1fdB", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
769                 }
770         } else {
771                 snprintf (buf, sizeof (buf), "%.2f", fraction);
772         }
773
774         return buf;
775 }
776
777 bool
778 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
779 {
780         return p[index].x == max_frames && p[index].y == DBL_MAX;
781 }
782
783 void
784 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
785 {
786         p[index].x = max_frames;
787         p[index].y = DBL_MAX;
788 }
789
790 void
791 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
792 {
793         if (trackview.editor.current_session() == 0) { /* how? */
794                 return;
795         }
796
797         string str;
798
799         if (cp) {
800                 str = _("automation event move");
801         } else {
802                 str = _("automation range drag");
803         }
804
805         trackview.editor.current_session()->begin_reversible_command (str);
806         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
807         
808         drag_x = x;
809         drag_distance = 0;
810         first_drag_fraction = fraction;
811         last_drag_fraction = fraction;
812         drags = 0;
813         did_push = false;
814 }
815
816 void
817 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
818 {
819         if (x > drag_x) {
820                 drag_distance += (x - drag_x);
821         } else {
822                 drag_distance -= (drag_x - x);
823         }
824
825         drag_x = x;
826
827         modify_view_point (cp, x, fraction, with_push);
828
829         if (line_points.size() > 1) {
830                 line->property_points() = line_points;
831         }
832
833         drags++;
834         did_push = with_push;
835 }
836
837 void
838 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
839 {
840         double ydelta = fraction - last_drag_fraction;
841
842         did_push = with_push;
843         
844         last_drag_fraction = fraction;
845
846         line_drag_cp1 = i1;
847         line_drag_cp2 = i2;
848         
849         //check if one of the control points on the line is in a selected range
850         bool range_found = false;
851         ControlPoint *cp;
852         for (uint32_t i = i1 ; i <= i2; i++) {
853                 cp = nth (i);
854                 if ( cp->selected )
855                         range_found = true;
856         }
857         
858         if (range_found) {
859                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
860                         if ( (*i)->selected ) {
861                                 modify_view_point (*(*i), trackview.editor.unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push);
862                         }
863                 }
864         } else {
865                 ControlPoint *cp;
866                 for (uint32_t i = i1 ; i <= i2; i++) {
867                         cp = nth (i);
868                         modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
869                 }
870         }
871         
872         if (line_points.size() > 1) {
873                 line->property_points() = line_points;
874         }
875
876         drags++;
877 }
878
879 void
880 AutomationLine::end_drag (ControlPoint* cp) 
881 {
882         if (!drags) {
883                 return;
884         }
885
886         alist.freeze ();
887
888         if (cp) {
889                 sync_model_with_view_point (*cp, did_push, drag_distance);
890         } else {
891                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
892         }
893         
894         alist.thaw ();
895
896         update_pending = false;
897
898         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
899         trackview.editor.current_session()->commit_reversible_command ();
900         trackview.editor.current_session()->set_dirty ();
901 }
902
903
904 void
905 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
906 {
907         ModelRepresentation mr;
908         double ydelta;
909
910         model_representation (cp, mr);
911
912         /* how much are we changing the central point by */ 
913
914         ydelta = mr.yval - mr.ypos;
915
916         /*
917            apply the full change to the central point, and interpolate
918            on both axes to cover all model points represented
919            by the control point.
920         */
921
922         /* change all points before the primary point */
923
924         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
925                 
926                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
927                 double y_delta = ydelta * fract;
928                 double x_delta = distance * fract;
929
930                 /* interpolate */
931                 
932                 if (y_delta || x_delta) {
933                         alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
934                 }
935         }
936
937         /* change the primary point */
938
939         update_pending = true;
940         alist.modify (cp.model, mr.xval, mr.yval);
941
942
943         /* change later points */
944         
945         AutomationList::iterator i = cp.model;
946         
947         ++i;
948         
949         while (i != mr.end) {
950                 
951                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
952
953                 /* all later points move by the same distance along the x-axis as the main point */
954                 
955                 if (delta) {
956                         alist.modify (i, (*i)->when + distance, (*i)->value + delta);
957                 }
958                 
959                 ++i;
960         }
961                 
962         if (did_push) {
963
964                 /* move all points after the range represented by the view by the same distance
965                    as the main point moved.
966                 */
967
968                 alist.slide (mr.end, drag_distance);
969         }
970
971 }
972
973 bool 
974 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
975 {
976         ControlPoint *bcp = 0;
977         ControlPoint *acp = 0;
978         double unit_xval;
979
980         /* xval is in frames */
981
982         unit_xval = trackview.editor.frame_to_unit (xval);
983
984         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
985                 
986                 if ((*i)->get_x() <= unit_xval) {
987
988                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
989                                 bcp = *i;
990                                 before = bcp->view_index;
991                         } 
992
993                 } else if ((*i)->get_x() > unit_xval) {
994                         acp = *i;
995                         after = acp->view_index;
996                         break;
997                 }
998         }
999
1000         return bcp && acp;
1001 }
1002
1003 bool
1004 AutomationLine::is_last_point (ControlPoint& cp)
1005 {
1006         ModelRepresentation mr;
1007
1008         model_representation (cp, mr);
1009
1010         // If the list is not empty, and the point is the last point in the list
1011
1012         if (!alist.empty() && mr.end == alist.end()) {
1013                 return true;
1014         }
1015         
1016         return false;
1017 }
1018
1019 bool
1020 AutomationLine::is_first_point (ControlPoint& cp)
1021 {
1022         ModelRepresentation mr;
1023
1024         model_representation (cp, mr);
1025
1026         // If the list is not empty, and the point is the first point in the list
1027
1028         if (!alist.empty() && mr.start == alist.begin()) {
1029                 return true;
1030         }
1031         
1032         return false;
1033 }
1034
1035 // This is copied into AudioRegionGainLine
1036 void
1037 AutomationLine::remove_point (ControlPoint& cp)
1038 {
1039         ModelRepresentation mr;
1040
1041         model_representation (cp, mr);
1042
1043         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1044         XMLNode &before = alist.get_state();
1045
1046         alist.erase (mr.start, mr.end);
1047
1048         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1049         trackview.editor.current_session()->commit_reversible_command ();
1050         trackview.editor.current_session()->set_dirty ();
1051 }
1052
1053 void
1054 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1055                                  double botfrac, double topfrac, list<Selectable*>& results)
1056 {
1057
1058         double top;
1059         double bot;
1060         nframes_t nstart;
1061         nframes_t nend;
1062         bool collecting = false;
1063
1064         /* Curse X11 and its inverted coordinate system! */
1065         
1066         bot = (1.0 - topfrac) * _height;
1067         top = (1.0 - botfrac) * _height;
1068         
1069         nstart = max_frames;
1070         nend = 0;
1071
1072         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1073                 
1074                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1075
1076                 if (when >= start && when <= end) {
1077                         
1078                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1079
1080                                 (*i)->show();
1081                                 (*i)->set_visible(true);
1082                                 collecting = true;
1083                                 nstart = min (nstart, when);
1084                                 nend = max (nend, when);
1085
1086                         } else {
1087                                 
1088                                 if (collecting) {
1089
1090                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1091                                         collecting = false;
1092                                         nstart = max_frames;
1093                                         nend = 0;
1094                                 }
1095                         }
1096                 }
1097         }
1098
1099         if (collecting) {
1100                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1101         }
1102
1103 }
1104
1105 void
1106 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1107 {
1108         // hmmm ....
1109 }
1110
1111 void
1112 AutomationLine::set_selected_points (PointSelection& points)
1113 {
1114         double top;
1115         double bot;
1116
1117         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1118                         (*i)->selected = false;
1119         }
1120
1121         if (points.empty()) {
1122                 goto out;
1123         } 
1124         
1125         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1126                 
1127                 if (&(*r).track != &trackview) {
1128                         continue;
1129                 }
1130
1131                 /* Curse X11 and its inverted coordinate system! */
1132
1133                 bot = (1.0 - (*r).high_fract) * _height;
1134                 top = (1.0 - (*r).low_fract) * _height;
1135
1136                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1137                         
1138                         double rstart, rend;
1139                         
1140                         rstart = trackview.editor.frame_to_unit ((*r).start);
1141                         rend = trackview.editor.frame_to_unit ((*r).end);
1142                         
1143                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1144                                 
1145                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1146                                         
1147                                         (*i)->selected = true;
1148                                 }
1149                         }
1150
1151                 }
1152         }
1153
1154   out:
1155         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1156                 (*i)->show_color (false, !points_visible);
1157         }
1158
1159 }
1160
1161 void AutomationLine::set_colors() {
1162         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1163         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1164                 (*i)->show_color (false, !points_visible);
1165         }
1166 }
1167
1168 void
1169 AutomationLine::show_selection ()
1170 {
1171         TimeSelection& time (trackview.editor.get_selection().time);
1172
1173         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1174                 
1175                 (*i)->selected = false;
1176
1177                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1178                         double rstart, rend;
1179                         
1180                         rstart = trackview.editor.frame_to_unit ((*r).start);
1181                         rend = trackview.editor.frame_to_unit ((*r).end);
1182                         
1183                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1184                                 (*i)->selected = true;
1185                                 break;
1186                         }
1187                 }
1188                 
1189                 (*i)->show_color (false, !points_visible);
1190         }
1191 }
1192
1193 void
1194 AutomationLine::hide_selection ()
1195 {
1196 //      show_selection ();
1197 }
1198
1199 void
1200 AutomationLine::list_changed ()
1201 {
1202         queue_reset ();
1203 }
1204
1205 void
1206 AutomationLine::reset_callback (const AutomationList& events)
1207 {
1208         ALPoints tmp_points;
1209         uint32_t npoints = events.size();
1210
1211         if (npoints == 0) {
1212                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1213                         delete *i;
1214                 }
1215                 control_points.clear ();
1216                 line->hide();
1217                 return;
1218         }
1219
1220         AutomationList::const_iterator ai;
1221
1222         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1223                 
1224                 double translated_y = (*ai)->value;
1225                 model_to_view_y (translated_y);
1226
1227                 add_model_point (tmp_points, (*ai)->when, translated_y);
1228         }
1229         
1230         determine_visible_control_points (tmp_points);
1231 }
1232
1233 void
1234 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1235 {
1236         tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit (frame),
1237                                        _height - (yfract * _height)));
1238 }
1239
1240 void
1241 AutomationLine::reset ()
1242 {
1243         update_pending = false;
1244
1245         alist.apply_to_points (*this, &AutomationLine::reset_callback);
1246 }
1247
1248 void
1249 AutomationLine::clear ()
1250 {
1251         /* parent must create command */
1252         XMLNode &before = get_state();
1253         alist.clear();
1254         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1255         trackview.editor.current_session()->commit_reversible_command ();
1256         trackview.editor.current_session()->set_dirty ();
1257 }
1258
1259 void
1260 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1261 {
1262 }
1263
1264 void
1265 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1266 {
1267         alist.move_range (start, end, xdelta, ydelta);
1268 }
1269
1270 void
1271 AutomationLine::show_all_control_points ()
1272 {
1273         if (auto_is_boolean) {  //show the automation line but don't allow any control points
1274                 return;
1275         }
1276
1277         points_visible = true;
1278
1279         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1280                 (*i)->show ();
1281                 (*i)->set_visible (true);
1282         }
1283 }
1284
1285 void
1286 AutomationLine::hide_all_but_selected_control_points ()
1287 {
1288         points_visible = false;
1289
1290         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1291                 if (!(*i)->selected) {
1292                         (*i)->set_visible (false);
1293                 }
1294         }
1295 }
1296
1297 XMLNode &
1298 AutomationLine::get_state (void)
1299 {
1300         /* function as a proxy for the model */
1301         return alist.get_state();
1302 }
1303
1304 int 
1305 AutomationLine::set_state (const XMLNode &node)
1306 {
1307         /* function as a proxy for the model */
1308         return alist.set_state (node);
1309 }