Make the wiimote code appear in the source package
[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         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                 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", 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         ControlPoint *cp;
850
851         for (uint32_t i = i1 ; i <= i2; i++) {
852                 cp = nth (i);
853                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
854         }
855
856         if (line_points.size() > 1) {
857                 line->property_points() = line_points;
858         }
859
860         drags++;
861 }
862
863 void
864 AutomationLine::end_drag (ControlPoint* cp) 
865 {
866         if (!drags) {
867                 return;
868         }
869
870         alist.freeze ();
871
872         if (cp) {
873                 sync_model_with_view_point (*cp, did_push, drag_distance);
874         } else {
875                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
876         }
877         
878         alist.thaw ();
879
880         update_pending = false;
881
882         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
883         trackview.editor.current_session()->commit_reversible_command ();
884         trackview.editor.current_session()->set_dirty ();
885 }
886
887
888 void
889 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
890 {
891         ModelRepresentation mr;
892         double ydelta;
893
894         model_representation (cp, mr);
895
896         /* how much are we changing the central point by */ 
897
898         ydelta = mr.yval - mr.ypos;
899
900         /*
901            apply the full change to the central point, and interpolate
902            on both axes to cover all model points represented
903            by the control point.
904         */
905
906         /* change all points before the primary point */
907
908         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
909                 
910                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
911                 double y_delta = ydelta * fract;
912                 double x_delta = distance * fract;
913
914                 /* interpolate */
915                 
916                 if (y_delta || x_delta) {
917                         alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
918                 }
919         }
920
921         /* change the primary point */
922
923         update_pending = true;
924         alist.modify (cp.model, mr.xval, mr.yval);
925
926
927         /* change later points */
928         
929         AutomationList::iterator i = cp.model;
930         
931         ++i;
932         
933         while (i != mr.end) {
934                 
935                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
936
937                 /* all later points move by the same distance along the x-axis as the main point */
938                 
939                 if (delta) {
940                         alist.modify (i, (*i)->when + distance, (*i)->value + delta);
941                 }
942                 
943                 ++i;
944         }
945                 
946         if (did_push) {
947
948                 /* move all points after the range represented by the view by the same distance
949                    as the main point moved.
950                 */
951
952                 alist.slide (mr.end, drag_distance);
953         }
954
955 }
956
957 bool 
958 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
959 {
960         ControlPoint *bcp = 0;
961         ControlPoint *acp = 0;
962         double unit_xval;
963
964         /* xval is in frames */
965
966         unit_xval = trackview.editor.frame_to_unit (xval);
967
968         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
969                 
970                 if ((*i)->get_x() <= unit_xval) {
971
972                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
973                                 bcp = *i;
974                                 before = bcp->view_index;
975                         } 
976
977                 } else if ((*i)->get_x() > unit_xval) {
978                         acp = *i;
979                         after = acp->view_index;
980                         break;
981                 }
982         }
983
984         return bcp && acp;
985 }
986
987 bool
988 AutomationLine::is_last_point (ControlPoint& cp)
989 {
990         ModelRepresentation mr;
991
992         model_representation (cp, mr);
993
994         // If the list is not empty, and the point is the last point in the list
995
996         if (!alist.empty() && mr.end == alist.end()) {
997                 return true;
998         }
999         
1000         return false;
1001 }
1002
1003 bool
1004 AutomationLine::is_first_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 first point in the list
1011
1012         if (!alist.empty() && mr.start == alist.begin()) {
1013                 return true;
1014         }
1015         
1016         return false;
1017 }
1018
1019 // This is copied into AudioRegionGainLine
1020 void
1021 AutomationLine::remove_point (ControlPoint& cp)
1022 {
1023         ModelRepresentation mr;
1024
1025         model_representation (cp, mr);
1026
1027         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1028         XMLNode &before = alist.get_state();
1029
1030         alist.erase (mr.start, mr.end);
1031
1032         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1033         trackview.editor.current_session()->commit_reversible_command ();
1034         trackview.editor.current_session()->set_dirty ();
1035 }
1036
1037 void
1038 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1039                                  double botfrac, double topfrac, list<Selectable*>& results)
1040 {
1041
1042         double top;
1043         double bot;
1044         nframes_t nstart;
1045         nframes_t nend;
1046         bool collecting = false;
1047
1048         /* Curse X11 and its inverted coordinate system! */
1049         
1050         bot = (1.0 - topfrac) * _height;
1051         top = (1.0 - botfrac) * _height;
1052         
1053         nstart = max_frames;
1054         nend = 0;
1055
1056         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1057                 
1058                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1059
1060                 if (when >= start && when <= end) {
1061                         
1062                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1063
1064                                 (*i)->show();
1065                                 (*i)->set_visible(true);
1066                                 collecting = true;
1067                                 nstart = min (nstart, when);
1068                                 nend = max (nend, when);
1069
1070                         } else {
1071                                 
1072                                 if (collecting) {
1073
1074                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1075                                         collecting = false;
1076                                         nstart = max_frames;
1077                                         nend = 0;
1078                                 }
1079                         }
1080                 }
1081         }
1082
1083         if (collecting) {
1084                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1085         }
1086
1087 }
1088
1089 void
1090 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1091 {
1092         // hmmm ....
1093 }
1094
1095 void
1096 AutomationLine::set_selected_points (PointSelection& points)
1097 {
1098         double top;
1099         double bot;
1100
1101         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1102                         (*i)->selected = false;
1103         }
1104
1105         if (points.empty()) {
1106                 goto out;
1107         } 
1108         
1109         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1110                 
1111                 if (&(*r).track != &trackview) {
1112                         continue;
1113                 }
1114
1115                 /* Curse X11 and its inverted coordinate system! */
1116
1117                 bot = (1.0 - (*r).high_fract) * _height;
1118                 top = (1.0 - (*r).low_fract) * _height;
1119
1120                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1121                         
1122                         double rstart, rend;
1123                         
1124                         rstart = trackview.editor.frame_to_unit ((*r).start);
1125                         rend = trackview.editor.frame_to_unit ((*r).end);
1126                         
1127                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1128                                 
1129                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1130                                         
1131                                         (*i)->selected = true;
1132                                 }
1133                         }
1134
1135                 }
1136         }
1137
1138   out:
1139         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1140                 (*i)->show_color (false, !points_visible);
1141         }
1142
1143 }
1144
1145 void AutomationLine::set_colors() {
1146         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1147         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1148                 (*i)->show_color (false, !points_visible);
1149         }
1150 }
1151
1152 void
1153 AutomationLine::show_selection ()
1154 {
1155         TimeSelection& time (trackview.editor.get_selection().time);
1156
1157         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1158                 
1159                 (*i)->selected = false;
1160
1161                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1162                         double rstart, rend;
1163                         
1164                         rstart = trackview.editor.frame_to_unit ((*r).start);
1165                         rend = trackview.editor.frame_to_unit ((*r).end);
1166                         
1167                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1168                                 (*i)->selected = true;
1169                                 break;
1170                         }
1171                 }
1172                 
1173                 (*i)->show_color (false, !points_visible);
1174         }
1175 }
1176
1177 void
1178 AutomationLine::hide_selection ()
1179 {
1180 //      show_selection ();
1181 }
1182
1183 void
1184 AutomationLine::list_changed ()
1185 {
1186         queue_reset ();
1187 }
1188
1189 void
1190 AutomationLine::reset_callback (const AutomationList& events)
1191 {
1192         ALPoints tmp_points;
1193         uint32_t npoints = events.size();
1194
1195         if (npoints == 0) {
1196                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1197                         delete *i;
1198                 }
1199                 control_points.clear ();
1200                 line->hide();
1201                 return;
1202         }
1203
1204         AutomationList::const_iterator ai;
1205
1206         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1207                 
1208                 double translated_y = (*ai)->value;
1209                 model_to_view_y (translated_y);
1210
1211                 add_model_point (tmp_points, (*ai)->when, translated_y);
1212         }
1213         
1214         determine_visible_control_points (tmp_points);
1215 }
1216
1217 void
1218 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1219 {
1220         tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit (frame),
1221                                        _height - (yfract * _height)));
1222 }
1223
1224 void
1225 AutomationLine::reset ()
1226 {
1227         update_pending = false;
1228
1229         if (no_draw) {
1230                 return;
1231         }
1232
1233         alist.apply_to_points (*this, &AutomationLine::reset_callback);
1234 }
1235
1236 void
1237 AutomationLine::clear ()
1238 {
1239         /* parent must create command */
1240         XMLNode &before = get_state();
1241         alist.clear();
1242         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1243         trackview.editor.current_session()->commit_reversible_command ();
1244         trackview.editor.current_session()->set_dirty ();
1245 }
1246
1247 void
1248 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1249 {
1250 }
1251
1252 void
1253 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1254 {
1255         alist.move_range (start, end, xdelta, ydelta);
1256 }
1257
1258 void
1259 AutomationLine::show_all_control_points ()
1260 {
1261         points_visible = true;
1262
1263         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1264                 (*i)->show ();
1265                 (*i)->set_visible (true);
1266         }
1267 }
1268
1269 void
1270 AutomationLine::hide_all_but_selected_control_points ()
1271 {
1272         points_visible = false;
1273
1274         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1275                 if (!(*i)->selected) {
1276                         (*i)->set_visible (false);
1277                 }
1278         }
1279 }
1280
1281 XMLNode &
1282 AutomationLine::get_state (void)
1283 {
1284         /* function as a proxy for the model */
1285         return alist.get_state();
1286 }
1287
1288 int 
1289 AutomationLine::set_state (const XMLNode &node)
1290 {
1291         /* function as a proxy for the model */
1292         return alist.set_state (node);
1293 }