s/ParamID/Parameter/
[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, boost::shared_ptr<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         _y_position = 0;
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         if (alist->parameter().type() == GainAutomation)
253                 set_verbose_cursor_uses_gain_mapping (true);
254 }
255
256 AutomationLine::~AutomationLine ()
257 {
258         vector_delete (&control_points);
259         delete group;
260 }
261
262 bool
263 AutomationLine::event_handler (GdkEvent* event)
264 {
265         return PublicEditor::instance().canvas_line_event (event, line, this);
266 }
267
268 void
269 AutomationLine::queue_reset ()
270 {
271         if (!update_pending) {
272                 update_pending = true;
273                 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
274         }
275 }
276
277 void
278 AutomationLine::show () 
279 {
280         line->show();
281
282         if (points_visible) {
283                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
284                         (*i)->show ();
285                 }
286         }
287
288         _visible = true;
289 }
290
291 void
292 AutomationLine::hide () 
293 {
294         line->hide();
295         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
296                 (*i)->hide();
297         }
298         _visible = false;
299 }
300
301 double
302 AutomationLine::control_point_box_size ()
303 {
304         if (_height > TimeAxisView::hLarger) {
305                 return 8.0;
306         } else if (_height > (guint32) TimeAxisView::hNormal) {
307                 return 6.0;
308         } 
309         return 4.0;
310 }
311
312 void
313 AutomationLine::set_y_position_and_height (guint32 y, guint32 h)
314 {
315         bool changed = false;
316         
317         if (y != _y_position) {
318                 _y_position = y;
319                 changed = true;
320         }
321                 
322         if (h != _height) {
323                 _height = h;
324
325                 double const bsz = control_point_box_size();
326
327                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
328                         (*i)->set_size (bsz);
329                 }
330                 changed = true;
331         }
332
333         if (changed) {
334                 reset ();
335         }
336 }
337
338 void
339 AutomationLine::set_line_color (uint32_t color)
340 {
341         _line_color = color;
342         line->property_fill_color_rgba() = color;
343 }
344
345 void
346 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
347 {
348         if (yn != _vc_uses_gain_mapping) {
349                 _vc_uses_gain_mapping = yn;
350                 reset ();
351         }
352 }
353
354 ControlPoint*
355 AutomationLine::nth (uint32_t n)
356 {
357         if (n < control_points.size()) {
358                 return control_points[n];
359         } else {
360                 return 0;
361         }
362 }
363
364 void
365 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
366 {
367         double delta = 0.0;
368         uint32_t last_movable = UINT_MAX;
369         double x_limit = DBL_MAX;
370
371         /* this just changes the current view. it does not alter
372            the model in any way at all.
373         */
374
375         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
376            and needs to be converted to a canvas unit distance.
377         */
378
379         y = max (0.0, y);
380         y = min (1.0, y);
381         y = _y_position + _height - (y * _height);
382
383         if (cp.can_slide) {
384
385                 /* x-coord cannot move beyond adjacent points or the start/end, and is
386                    already in frames. it needs to be converted to canvas units.
387                 */
388                 
389                 x = trackview.editor.frame_to_unit (x);
390
391                 /* clamp x position using view coordinates */
392
393                 ControlPoint *before;
394                 ControlPoint *after;
395
396                 if (cp.view_index) {
397                         before = nth (cp.view_index - 1);
398                         x = max (x, before->get_x()+1.0);
399                 } else {
400                         before = &cp;
401                 }
402
403
404                 if (!with_push) {
405                         if (cp.view_index < control_points.size() - 1) {
406                 
407                                 after = nth (cp.view_index + 1);
408                 
409                                 /*if it is a "spike" leave the x alone */
410  
411                                 if (after->get_x() - before->get_x() < 2) {
412                                         x = cp.get_x();
413                                         
414                                 } else {
415                                         x = min (x, after->get_x()-1.0);
416                                 }
417                         } else {
418                                 after = &cp;
419                         }
420
421                 } else {
422
423                         ControlPoint* after;
424                         
425                         /* find the first point that can't move */
426
427                         for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
428                                 if (!after->can_slide) {
429                                         x_limit = after->get_x() - 1.0;
430                                         last_movable = after->view_index;
431                                         break;
432                                 }
433                         }
434                         
435                         delta = x - cp.get_x();
436                 }
437                         
438         } else {
439
440                 /* leave the x-coordinate alone */
441
442                 x = trackview.editor.frame_to_unit ((*cp.model)->when);
443
444         }
445
446         if (!with_push) {
447
448                 cp.move_to (x, y, ControlPoint::Full);
449                 reset_line_coords (cp);
450
451         } else {
452
453                 uint32_t limit = min (control_points.size(), (size_t)last_movable);
454                 
455                 /* move the current point to wherever the user told it to go, subject
456                    to x_limit.
457                 */
458                 
459                 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
460                 reset_line_coords (cp);
461                 
462                 /* now move all subsequent control points, to reflect the motion.
463                  */
464                 
465                 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
466                         ControlPoint *p = nth (i);
467                         double new_x;
468
469                         if (p->can_slide) {
470                                 new_x = min (p->get_x() + delta, x_limit);
471                                 p->move_to (new_x, p->get_y(), ControlPoint::Full);
472                                 reset_line_coords (*p);
473                         }
474                 }
475         }
476 }
477
478 void
479 AutomationLine::reset_line_coords (ControlPoint& cp)
480 {       
481         if (cp.view_index < line_points.size()) {
482                 line_points[cp.view_index].set_x (cp.get_x());
483                 line_points[cp.view_index].set_y (cp.get_y());
484         }
485 }
486
487 void
488 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
489 {
490
491         ControlPoint *p;
492
493         update_pending = true;
494
495         for (uint32_t i = start; i <= end; ++i) {
496                 p = nth(i);
497                 sync_model_with_view_point (*p, false, 0);
498         }
499 }
500
501 void
502 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
503 {
504         /* part one: find out where the visual control point is.
505            initial results are in canvas units. ask the
506            line to convert them to something relevant.
507         */
508         
509         mr.xval = (nframes_t) floor (cp.get_x());
510         mr.yval = 1.0 - ( (cp.get_y() - _y_position) / _height);
511
512         /* if xval has not changed, set it directly from the model to avoid rounding errors */
513
514         if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
515                 mr.xval = (nframes_t) (*cp.model)->when;
516         } else {
517                 mr.xval = trackview.editor.unit_to_frame (mr.xval);
518         }
519
520         /* virtual call: this will do the right thing
521            for whatever particular type of line we are.
522         */
523         
524         view_to_model_y (mr.yval);
525
526         /* part 2: find out where the model point is now
527          */
528
529         mr.xpos = (nframes_t) (*cp.model)->when;
530         mr.ypos = (*cp.model)->value;
531
532         /* part 3: get the position of the visual control
533            points before and after us.
534         */
535
536         ControlPoint* before;
537         ControlPoint* after;
538
539         if (cp.view_index) {
540                 before = nth (cp.view_index - 1);
541         } else {
542                 before = 0;
543         }
544
545         after = nth (cp.view_index + 1);
546
547         if (before) {
548                 mr.xmin = (nframes_t) (*before->model)->when;
549                 mr.ymin = (*before->model)->value;
550                 mr.start = before->model;
551                 ++mr.start;
552         } else {
553                 mr.xmin = mr.xpos;
554                 mr.ymin = mr.ypos;
555                 mr.start = cp.model;
556         }
557
558         if (after) {
559                 mr.end = after->model;
560         } else {
561                 mr.xmax = mr.xpos;
562                 mr.ymax = mr.ypos;
563                 mr.end = cp.model;
564                 ++mr.end;
565         }
566 }
567
568 void
569 AutomationLine::determine_visible_control_points (ALPoints& points)
570 {
571         uint32_t view_index, pi, n;
572         AutomationList::iterator model;
573         uint32_t npoints;
574         double last_control_point_x = 0.0;
575         double last_control_point_y = 0.0;
576         uint32_t this_rx = 0;
577         uint32_t prev_rx = 0;
578         uint32_t this_ry = 0;
579         uint32_t prev_ry = 0;   
580         double* slope;
581         uint32_t box_size;
582         uint32_t cpsize;
583
584         /* hide all existing points, and the line */
585
586         cpsize = 0;
587         
588         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
589                 (*i)->hide();
590                 ++cpsize;
591         }
592
593         line->hide ();
594
595         if (points.empty()) {
596                 return;
597         }
598
599         npoints = points.size();
600
601         /* compute derivative/slope for the entire line */
602
603         slope = new double[npoints];
604
605         for (n = 0; n < npoints - 1; ++n) {
606                 double xdelta = points[n+1].x - points[n].x;
607                 double ydelta = points[n+1].y - points[n].y;
608                 slope[n] = ydelta/xdelta;
609         }
610
611         box_size = (uint32_t) control_point_box_size ();
612
613         /* read all points and decide which ones to show as control points */
614
615         view_index = 0;
616
617         for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
618
619                 double tx = points[pi].x;
620                 double ty = points[pi].y;
621
622                 /* now ensure that the control_points vector reflects the current curve
623                    state, but don't plot control points too close together. also, don't
624                    plot a series of points all with the same value.
625
626                    always plot the first and last points, of course.
627                 */
628
629                 if (invalid_point (points, pi)) {
630                         /* for some reason, we are supposed to ignore this point,
631                            but still keep track of the model index.
632                         */
633                         continue;
634                 }
635
636                 if (pi > 0 && pi < npoints - 1) {
637                         if (slope[pi] == slope[pi-1]) {
638
639                                 /* no reason to display this point */
640                                 
641                                 continue;
642                         }
643                 }
644                 
645                 /* need to round here. the ultimate coordinates are integer
646                    pixels, so tiny deltas in the coords will be eliminated
647                    and we end up with "colinear" line segments. since the
648                    line rendering code in libart doesn't like this very
649                    much, we eliminate them here. don't do this for the first and last
650                    points.
651                 */
652
653                 this_rx = (uint32_t) rint (tx);
654                 this_ry = (uint32_t) rint (ty); 
655  
656                 if (view_index && pi != npoints && /* not the first, not the last */
657                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
658                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
659                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
660                         continue;
661                 } 
662
663                 /* ok, we should display this point */
664
665                 if (view_index >= cpsize) {
666
667                         /* make sure we have enough control points */
668
669                         ControlPoint* ncp = new ControlPoint (*this);
670                         
671                         ncp->set_size (box_size); 
672
673                         control_points.push_back (ncp);
674                         ++cpsize;
675                 }
676
677                 ControlPoint::ShapeType shape;
678
679                 if (!terminal_points_can_slide) {
680                         if (pi == 0) {
681                                 control_points[view_index]->can_slide = false;
682                                 if (tx == 0) {
683                                         shape = ControlPoint::Start;
684                                 } else {
685                                         shape = ControlPoint::Full;
686                                 }
687                         } else if (pi == npoints - 1) {
688                                 control_points[view_index]->can_slide = false;
689                                 shape = ControlPoint::End;
690                         } else {
691                                 control_points[view_index]->can_slide = true;
692                                 shape = ControlPoint::Full;
693                         }
694                 } else {
695                         control_points[view_index]->can_slide = true;
696                         shape = ControlPoint::Full;
697                 }
698
699                 last_control_point_x = tx;
700                 last_control_point_y = ty;
701
702                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
703
704                 prev_rx = this_rx;
705                 prev_ry = this_ry;
706
707                 /* finally, control visibility */
708                 
709                 if (_visible && points_visible) {
710                         control_points[view_index]->show ();
711                         control_points[view_index]->set_visible (true);
712                 } else {
713                         if (!points_visible) {
714                                 control_points[view_index]->set_visible (false);
715                         }
716                 }
717
718                 view_index++;
719         }
720         
721         /* discard extra CP's to avoid confusing ourselves */
722
723         while (control_points.size() > view_index) {
724                 ControlPoint* cp = control_points.back();
725                 control_points.pop_back ();
726                 delete cp;
727         }
728
729         if (!terminal_points_can_slide) {
730                 control_points.back()->can_slide = false;
731         }
732
733         delete [] slope;
734
735         if (view_index > 1) {
736
737                 npoints = view_index;
738                 
739                 /* reset the line coordinates */
740
741                 while (line_points.size() < npoints) {
742                         line_points.push_back (Art::Point (0,0));
743                 }
744
745                 while (line_points.size() > npoints) {
746                         line_points.pop_back ();
747                 }
748
749                 for (view_index = 0; view_index < npoints; ++view_index) {
750                         line_points[view_index].set_x (control_points[view_index]->get_x());
751                         line_points[view_index].set_y (control_points[view_index]->get_y());
752                 }
753                 
754                 line->property_points() = line_points;
755
756                 if (_visible) {
757                         line->show ();
758                 }
759
760         } 
761
762         set_selected_points (trackview.editor.get_selection().points);
763
764 }
765
766 string
767 AutomationLine::get_verbose_cursor_string (float fraction)
768 {
769         char buf[32];
770
771         if (_vc_uses_gain_mapping) {
772                 if (fraction == 0.0) {
773                         snprintf (buf, sizeof (buf), "-inf dB");
774                 } else {
775                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
776                 }
777         } else {
778                 snprintf (buf, sizeof (buf), "%.2f", fraction);
779         }
780
781         return buf;
782 }
783
784 bool
785 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
786 {
787         return p[index].x == max_frames && p[index].y == DBL_MAX;
788 }
789
790 void
791 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
792 {
793         p[index].x = max_frames;
794         p[index].y = DBL_MAX;
795 }
796
797 void
798 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
799 {
800         if (trackview.editor.current_session() == 0) { /* how? */
801                 return;
802         }
803
804         string str;
805
806         if (cp) {
807                 str = _("automation event move");
808         } else {
809                 str = _("automation range drag");
810         }
811
812         trackview.editor.current_session()->begin_reversible_command (str);
813         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
814         
815         drag_x = x;
816         drag_distance = 0;
817         first_drag_fraction = fraction;
818         last_drag_fraction = fraction;
819         drags = 0;
820         did_push = false;
821 }
822
823 void
824 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
825 {
826         if (x > drag_x) {
827                 drag_distance += (x - drag_x);
828         } else {
829                 drag_distance -= (drag_x - x);
830         }
831
832         drag_x = x;
833
834         modify_view_point (cp, x, fraction, with_push);
835
836         if (line_points.size() > 1) {
837                 line->property_points() = line_points;
838         }
839
840         drags++;
841         did_push = with_push;
842 }
843
844 void
845 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
846 {
847         double const ydelta = fraction - last_drag_fraction;
848
849         did_push = with_push;
850         
851         last_drag_fraction = fraction;
852
853         line_drag_cp1 = i1;
854         line_drag_cp2 = i2;
855         
856         ControlPoint *cp;
857
858         for (uint32_t i = i1 ; i <= i2; i++) {
859                 cp = nth (i);
860                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
861         }
862
863         if (line_points.size() > 1) {
864                 line->property_points() = line_points;
865         }
866
867         drags++;
868 }
869
870 void
871 AutomationLine::end_drag (ControlPoint* cp) 
872 {
873         if (!drags) {
874                 return;
875         }
876
877         alist->freeze ();
878
879         if (cp) {
880                 sync_model_with_view_point (*cp, did_push, drag_distance);
881         } else {
882                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
883         }
884         
885         alist->thaw ();
886
887         update_pending = false;
888
889         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
890         trackview.editor.current_session()->commit_reversible_command ();
891         trackview.editor.current_session()->set_dirty ();
892 }
893
894
895 void
896 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
897 {
898         ModelRepresentation mr;
899         double ydelta;
900
901         model_representation (cp, mr);
902
903         /* how much are we changing the central point by */ 
904
905         ydelta = mr.yval - mr.ypos;
906
907         /*
908            apply the full change to the central point, and interpolate
909            on both axes to cover all model points represented
910            by the control point.
911         */
912
913         /* change all points before the primary point */
914
915         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
916                 
917                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
918                 double y_delta = ydelta * fract;
919                 double x_delta = distance * fract;
920
921                 /* interpolate */
922                 
923                 if (y_delta || x_delta) {
924                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
925                 }
926         }
927
928         /* change the primary point */
929
930         update_pending = true;
931         alist->modify (cp.model, mr.xval, mr.yval);
932
933
934         /* change later points */
935         
936         AutomationList::iterator i = cp.model;
937         
938         ++i;
939         
940         while (i != mr.end) {
941                 
942                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
943
944                 /* all later points move by the same distance along the x-axis as the main point */
945                 
946                 if (delta) {
947                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
948                 }
949                 
950                 ++i;
951         }
952                 
953         if (did_push) {
954
955                 /* move all points after the range represented by the view by the same distance
956                    as the main point moved.
957                 */
958
959                 alist->slide (mr.end, drag_distance);
960         }
961
962 }
963
964 bool 
965 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
966 {
967         ControlPoint *bcp = 0;
968         ControlPoint *acp = 0;
969         double unit_xval;
970
971         /* xval is in frames */
972
973         unit_xval = trackview.editor.frame_to_unit (xval);
974
975         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
976                 
977                 if ((*i)->get_x() <= unit_xval) {
978
979                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
980                                 bcp = *i;
981                                 before = bcp->view_index;
982                         } 
983
984                 } else if ((*i)->get_x() > unit_xval) {
985                         acp = *i;
986                         after = acp->view_index;
987                         break;
988                 }
989         }
990
991         return bcp && acp;
992 }
993
994 bool
995 AutomationLine::is_last_point (ControlPoint& cp)
996 {
997         ModelRepresentation mr;
998
999         model_representation (cp, mr);
1000
1001         // If the list is not empty, and the point is the last point in the list
1002
1003         if (!alist->empty() && mr.end == alist->end()) {
1004                 return true;
1005         }
1006         
1007         return false;
1008 }
1009
1010 bool
1011 AutomationLine::is_first_point (ControlPoint& cp)
1012 {
1013         ModelRepresentation mr;
1014
1015         model_representation (cp, mr);
1016
1017         // If the list is not empty, and the point is the first point in the list
1018
1019         if (!alist->empty() && mr.start == alist->begin()) {
1020                 return true;
1021         }
1022         
1023         return false;
1024 }
1025
1026 // This is copied into AudioRegionGainLine
1027 void
1028 AutomationLine::remove_point (ControlPoint& cp)
1029 {
1030         ModelRepresentation mr;
1031
1032         model_representation (cp, mr);
1033
1034         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1035         XMLNode &before = alist->get_state();
1036
1037         alist->erase (mr.start, mr.end);
1038
1039         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
1040                         *alist.get(), &before, &alist->get_state()));
1041         trackview.editor.current_session()->commit_reversible_command ();
1042         trackview.editor.current_session()->set_dirty ();
1043 }
1044
1045 void
1046 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1047                                  double botfrac, double topfrac, list<Selectable*>& results)
1048 {
1049
1050         double top;
1051         double bot;
1052         nframes_t nstart;
1053         nframes_t nend;
1054         bool collecting = false;
1055
1056         /* Curse X11 and its inverted coordinate system! */
1057         
1058         bot = _y_position + (1.0 - topfrac) * _height;
1059         top = _y_position + (1.0 - botfrac) * _height;
1060         
1061         nstart = max_frames;
1062         nend = 0;
1063
1064         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1065                 
1066                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1067
1068                 if (when >= start && when <= end) {
1069                         
1070                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1071
1072                                 (*i)->show();
1073                                 (*i)->set_visible(true);
1074                                 collecting = true;
1075                                 nstart = min (nstart, when);
1076                                 nend = max (nend, when);
1077
1078                         } else {
1079                                 
1080                                 if (collecting) {
1081
1082                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1083                                         collecting = false;
1084                                         nstart = max_frames;
1085                                         nend = 0;
1086                                 }
1087                         }
1088                 }
1089         }
1090
1091         if (collecting) {
1092                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1093         }
1094
1095 }
1096
1097 void
1098 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1099 {
1100         // hmmm ....
1101 }
1102
1103 void
1104 AutomationLine::set_selected_points (PointSelection& points)
1105 {
1106         double top;
1107         double bot;
1108
1109         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1110                         (*i)->selected = false;
1111         }
1112
1113         if (points.empty()) {
1114                 goto out;
1115         } 
1116         
1117         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1118                 
1119                 if (&(*r).track != &trackview) {
1120                         continue;
1121                 }
1122
1123                 /* Curse X11 and its inverted coordinate system! */
1124
1125                 bot = _y_position + (1.0 - (*r).high_fract) * _height;
1126                 top = _y_position + (1.0 - (*r).low_fract) * _height;
1127
1128                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1129                         
1130                         double rstart, rend;
1131                         
1132                         rstart = trackview.editor.frame_to_unit ((*r).start);
1133                         rend = trackview.editor.frame_to_unit ((*r).end);
1134                         
1135                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1136                                 
1137                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1138                                         
1139                                         (*i)->selected = true;
1140                                 }
1141                         }
1142
1143                 }
1144         }
1145
1146   out:
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
1153 void AutomationLine::set_colors() {
1154         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
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 void
1161 AutomationLine::show_selection ()
1162 {
1163         TimeSelection& time (trackview.editor.get_selection().time);
1164
1165         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1166                 
1167                 (*i)->selected = false;
1168
1169                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1170                         double rstart, rend;
1171                         
1172                         rstart = trackview.editor.frame_to_unit ((*r).start);
1173                         rend = trackview.editor.frame_to_unit ((*r).end);
1174                         
1175                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1176                                 (*i)->selected = true;
1177                                 break;
1178                         }
1179                 }
1180                 
1181                 (*i)->show_color (false, !points_visible);
1182         }
1183 }
1184
1185 void
1186 AutomationLine::hide_selection ()
1187 {
1188 //      show_selection ();
1189 }
1190
1191 void
1192 AutomationLine::list_changed ()
1193 {
1194         queue_reset ();
1195 }
1196
1197 void
1198 AutomationLine::reset_callback (const AutomationList& events)
1199 {
1200         ALPoints tmp_points;
1201         uint32_t npoints = events.size();
1202
1203         if (npoints == 0) {
1204                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1205                         delete *i;
1206                 }
1207                 control_points.clear ();
1208                 line->hide();
1209                 return;
1210         }
1211
1212         AutomationList::const_iterator ai;
1213
1214         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1215
1216                 double translated_y = (*ai)->value;
1217                 model_to_view_y (translated_y);
1218
1219                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1220                                                _y_position + _height - (translated_y * _height)));
1221         }
1222         
1223         determine_visible_control_points (tmp_points);
1224 }
1225
1226 void
1227 AutomationLine::reset ()
1228 {
1229         update_pending = false;
1230
1231         if (no_draw) {
1232                 return;
1233         }
1234
1235         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1236 }
1237
1238 void
1239 AutomationLine::clear ()
1240 {
1241         /* parent must create command */
1242         XMLNode &before = get_state();
1243         alist->clear();
1244         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1245         trackview.editor.current_session()->commit_reversible_command ();
1246         trackview.editor.current_session()->set_dirty ();
1247 }
1248
1249 void
1250 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1251 {
1252 }
1253
1254 void
1255 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1256 {
1257         alist->move_range (start, end, xdelta, ydelta);
1258 }
1259
1260 void
1261 AutomationLine::show_all_control_points ()
1262 {
1263         points_visible = true;
1264
1265         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1266                 (*i)->show ();
1267                 (*i)->set_visible (true);
1268         }
1269 }
1270
1271 void
1272 AutomationLine::hide_all_but_selected_control_points ()
1273 {
1274         points_visible = false;
1275
1276         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1277                 if (!(*i)->selected) {
1278                         (*i)->set_visible (false);
1279                 }
1280         }
1281 }
1282
1283 XMLNode &
1284 AutomationLine::get_state (void)
1285 {
1286         /* function as a proxy for the model */
1287         return alist->get_state();
1288 }
1289
1290 int 
1291 AutomationLine::set_state (const XMLNode &node)
1292 {
1293         /* function as a proxy for the model */
1294         return alist->set_state (node);
1295 }
1296
1297 void
1298 AutomationLine::view_to_model_y (double& y)
1299 {
1300         if (alist->parameter().type() == GainAutomation) {
1301                 y = slider_position_to_gain (y);
1302                 y = max (0.0, y);
1303                 y = min (2.0, y);
1304         } else if (alist->parameter().type() == PanAutomation) {
1305                 // vertical coordinate axis reversal
1306                 y = 1.0 - y;
1307         } else if (alist->parameter().type() == MidiCCAutomation) {
1308                 y = (int)(y * 127.0);
1309         }
1310 }
1311
1312 void
1313 AutomationLine::model_to_view_y (double& y)
1314 {
1315         if (alist->parameter().type() == GainAutomation) {
1316                 y = gain_to_slider_position (y);
1317         } else if (alist->parameter().type() == PanAutomation) {
1318                 // vertical coordinate axis reversal
1319                 y = 1.0 - y;
1320         } else if (alist->parameter().type() == MidiCCAutomation) {
1321                 y = y / 127.0;
1322         } else if (alist->parameter().type() == PluginAutomation) {
1323                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1324         }
1325 }
1326