Display correct value for verbose control point cursor for Midi CC (int) and plugin...
[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 (double 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                 view_to_model_y(fraction);
779                 if (alist->parameter().type() == MidiCCAutomation)
780                         snprintf (buf, sizeof (buf), "%d", (int)fraction);
781                 else
782                         snprintf (buf, sizeof (buf), "%.2f", fraction);
783         }
784
785         return buf;
786 }
787
788 bool
789 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
790 {
791         return p[index].x == max_frames && p[index].y == DBL_MAX;
792 }
793
794 void
795 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
796 {
797         p[index].x = max_frames;
798         p[index].y = DBL_MAX;
799 }
800
801 void
802 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
803 {
804         if (trackview.editor.current_session() == 0) { /* how? */
805                 return;
806         }
807
808         string str;
809
810         if (cp) {
811                 str = _("automation event move");
812         } else {
813                 str = _("automation range drag");
814         }
815
816         trackview.editor.current_session()->begin_reversible_command (str);
817         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
818         
819         drag_x = x;
820         drag_distance = 0;
821         first_drag_fraction = fraction;
822         last_drag_fraction = fraction;
823         drags = 0;
824         did_push = false;
825 }
826
827 void
828 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
829 {
830         if (x > drag_x) {
831                 drag_distance += (x - drag_x);
832         } else {
833                 drag_distance -= (drag_x - x);
834         }
835
836         drag_x = x;
837
838         modify_view_point (cp, x, fraction, with_push);
839
840         if (line_points.size() > 1) {
841                 line->property_points() = line_points;
842         }
843
844         drags++;
845         did_push = with_push;
846 }
847
848 void
849 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
850 {
851         double const ydelta = fraction - last_drag_fraction;
852
853         did_push = with_push;
854         
855         last_drag_fraction = fraction;
856
857         line_drag_cp1 = i1;
858         line_drag_cp2 = i2;
859         
860         ControlPoint *cp;
861
862         for (uint32_t i = i1 ; i <= i2; i++) {
863                 cp = nth (i);
864                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
865         }
866
867         if (line_points.size() > 1) {
868                 line->property_points() = line_points;
869         }
870
871         drags++;
872 }
873
874 void
875 AutomationLine::end_drag (ControlPoint* cp) 
876 {
877         if (!drags) {
878                 return;
879         }
880
881         alist->freeze ();
882
883         if (cp) {
884                 sync_model_with_view_point (*cp, did_push, drag_distance);
885         } else {
886                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
887         }
888         
889         alist->thaw ();
890
891         update_pending = false;
892
893         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
894         trackview.editor.current_session()->commit_reversible_command ();
895         trackview.editor.current_session()->set_dirty ();
896 }
897
898
899 void
900 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
901 {
902         ModelRepresentation mr;
903         double ydelta;
904
905         model_representation (cp, mr);
906
907         /* how much are we changing the central point by */ 
908
909         ydelta = mr.yval - mr.ypos;
910
911         /*
912            apply the full change to the central point, and interpolate
913            on both axes to cover all model points represented
914            by the control point.
915         */
916
917         /* change all points before the primary point */
918
919         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
920                 
921                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
922                 double y_delta = ydelta * fract;
923                 double x_delta = distance * fract;
924
925                 /* interpolate */
926                 
927                 if (y_delta || x_delta) {
928                         alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
929                 }
930         }
931
932         /* change the primary point */
933
934         update_pending = true;
935         alist->modify (cp.model, mr.xval, mr.yval);
936
937
938         /* change later points */
939         
940         AutomationList::iterator i = cp.model;
941         
942         ++i;
943         
944         while (i != mr.end) {
945                 
946                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
947
948                 /* all later points move by the same distance along the x-axis as the main point */
949                 
950                 if (delta) {
951                         alist->modify (i, (*i)->when + distance, (*i)->value + delta);
952                 }
953                 
954                 ++i;
955         }
956                 
957         if (did_push) {
958
959                 /* move all points after the range represented by the view by the same distance
960                    as the main point moved.
961                 */
962
963                 alist->slide (mr.end, drag_distance);
964         }
965
966 }
967
968 bool 
969 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
970 {
971         ControlPoint *bcp = 0;
972         ControlPoint *acp = 0;
973         double unit_xval;
974
975         /* xval is in frames */
976
977         unit_xval = trackview.editor.frame_to_unit (xval);
978
979         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
980                 
981                 if ((*i)->get_x() <= unit_xval) {
982
983                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
984                                 bcp = *i;
985                                 before = bcp->view_index;
986                         } 
987
988                 } else if ((*i)->get_x() > unit_xval) {
989                         acp = *i;
990                         after = acp->view_index;
991                         break;
992                 }
993         }
994
995         return bcp && acp;
996 }
997
998 bool
999 AutomationLine::is_last_point (ControlPoint& cp)
1000 {
1001         ModelRepresentation mr;
1002
1003         model_representation (cp, mr);
1004
1005         // If the list is not empty, and the point is the last point in the list
1006
1007         if (!alist->empty() && mr.end == alist->end()) {
1008                 return true;
1009         }
1010         
1011         return false;
1012 }
1013
1014 bool
1015 AutomationLine::is_first_point (ControlPoint& cp)
1016 {
1017         ModelRepresentation mr;
1018
1019         model_representation (cp, mr);
1020
1021         // If the list is not empty, and the point is the first point in the list
1022
1023         if (!alist->empty() && mr.start == alist->begin()) {
1024                 return true;
1025         }
1026         
1027         return false;
1028 }
1029
1030 // This is copied into AudioRegionGainLine
1031 void
1032 AutomationLine::remove_point (ControlPoint& cp)
1033 {
1034         ModelRepresentation mr;
1035
1036         model_representation (cp, mr);
1037
1038         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1039         XMLNode &before = alist->get_state();
1040
1041         alist->erase (mr.start, mr.end);
1042
1043         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
1044                         *alist.get(), &before, &alist->get_state()));
1045         trackview.editor.current_session()->commit_reversible_command ();
1046         trackview.editor.current_session()->set_dirty ();
1047 }
1048
1049 void
1050 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1051                                  double botfrac, double topfrac, list<Selectable*>& results)
1052 {
1053
1054         double top;
1055         double bot;
1056         nframes_t nstart;
1057         nframes_t nend;
1058         bool collecting = false;
1059
1060         /* Curse X11 and its inverted coordinate system! */
1061         
1062         bot = _y_position + (1.0 - topfrac) * _height;
1063         top = _y_position + (1.0 - botfrac) * _height;
1064         
1065         nstart = max_frames;
1066         nend = 0;
1067
1068         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1069                 
1070                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1071
1072                 if (when >= start && when <= end) {
1073                         
1074                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1075
1076                                 (*i)->show();
1077                                 (*i)->set_visible(true);
1078                                 collecting = true;
1079                                 nstart = min (nstart, when);
1080                                 nend = max (nend, when);
1081
1082                         } else {
1083                                 
1084                                 if (collecting) {
1085
1086                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1087                                         collecting = false;
1088                                         nstart = max_frames;
1089                                         nend = 0;
1090                                 }
1091                         }
1092                 }
1093         }
1094
1095         if (collecting) {
1096                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1097         }
1098
1099 }
1100
1101 void
1102 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1103 {
1104         // hmmm ....
1105 }
1106
1107 void
1108 AutomationLine::set_selected_points (PointSelection& points)
1109 {
1110         double top;
1111         double bot;
1112
1113         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1114                         (*i)->selected = false;
1115         }
1116
1117         if (points.empty()) {
1118                 goto out;
1119         } 
1120         
1121         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1122                 
1123                 if (&(*r).track != &trackview) {
1124                         continue;
1125                 }
1126
1127                 /* Curse X11 and its inverted coordinate system! */
1128
1129                 bot = _y_position + (1.0 - (*r).high_fract) * _height;
1130                 top = _y_position + (1.0 - (*r).low_fract) * _height;
1131
1132                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1133                         
1134                         double rstart, rend;
1135                         
1136                         rstart = trackview.editor.frame_to_unit ((*r).start);
1137                         rend = trackview.editor.frame_to_unit ((*r).end);
1138                         
1139                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1140                                 
1141                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1142                                         
1143                                         (*i)->selected = true;
1144                                 }
1145                         }
1146
1147                 }
1148         }
1149
1150   out:
1151         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1152                 (*i)->show_color (false, !points_visible);
1153         }
1154
1155 }
1156
1157 void AutomationLine::set_colors() {
1158         set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1159         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1160                 (*i)->show_color (false, !points_visible);
1161         }
1162 }
1163
1164 void
1165 AutomationLine::show_selection ()
1166 {
1167         TimeSelection& time (trackview.editor.get_selection().time);
1168
1169         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1170                 
1171                 (*i)->selected = false;
1172
1173                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1174                         double rstart, rend;
1175                         
1176                         rstart = trackview.editor.frame_to_unit ((*r).start);
1177                         rend = trackview.editor.frame_to_unit ((*r).end);
1178                         
1179                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1180                                 (*i)->selected = true;
1181                                 break;
1182                         }
1183                 }
1184                 
1185                 (*i)->show_color (false, !points_visible);
1186         }
1187 }
1188
1189 void
1190 AutomationLine::hide_selection ()
1191 {
1192 //      show_selection ();
1193 }
1194
1195 void
1196 AutomationLine::list_changed ()
1197 {
1198         queue_reset ();
1199 }
1200
1201 void
1202 AutomationLine::reset_callback (const AutomationList& events)
1203 {
1204         ALPoints tmp_points;
1205         uint32_t npoints = events.size();
1206
1207         if (npoints == 0) {
1208                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1209                         delete *i;
1210                 }
1211                 control_points.clear ();
1212                 line->hide();
1213                 return;
1214         }
1215
1216         AutomationList::const_iterator ai;
1217
1218         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1219
1220                 double translated_y = (*ai)->value;
1221                 model_to_view_y (translated_y);
1222
1223                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1224                                                _y_position + _height - (translated_y * _height)));
1225         }
1226         
1227         determine_visible_control_points (tmp_points);
1228 }
1229
1230 void
1231 AutomationLine::reset ()
1232 {
1233         update_pending = false;
1234
1235         if (no_draw) {
1236                 return;
1237         }
1238
1239         alist->apply_to_points (*this, &AutomationLine::reset_callback);
1240 }
1241
1242 void
1243 AutomationLine::clear ()
1244 {
1245         /* parent must create command */
1246         XMLNode &before = get_state();
1247         alist->clear();
1248         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1249         trackview.editor.current_session()->commit_reversible_command ();
1250         trackview.editor.current_session()->set_dirty ();
1251 }
1252
1253 void
1254 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1255 {
1256 }
1257
1258 void
1259 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1260 {
1261         alist->move_range (start, end, xdelta, ydelta);
1262 }
1263
1264 void
1265 AutomationLine::show_all_control_points ()
1266 {
1267         points_visible = true;
1268
1269         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1270                 (*i)->show ();
1271                 (*i)->set_visible (true);
1272         }
1273 }
1274
1275 void
1276 AutomationLine::hide_all_but_selected_control_points ()
1277 {
1278         points_visible = false;
1279
1280         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1281                 if (!(*i)->selected) {
1282                         (*i)->set_visible (false);
1283                 }
1284         }
1285 }
1286
1287 XMLNode &
1288 AutomationLine::get_state (void)
1289 {
1290         /* function as a proxy for the model */
1291         return alist->get_state();
1292 }
1293
1294 int 
1295 AutomationLine::set_state (const XMLNode &node)
1296 {
1297         /* function as a proxy for the model */
1298         return alist->set_state (node);
1299 }
1300
1301 void
1302 AutomationLine::view_to_model_y (double& y)
1303 {
1304         if (alist->parameter().type() == GainAutomation) {
1305                 y = slider_position_to_gain (y);
1306                 y = max (0.0, y);
1307                 y = min (2.0, y);
1308         } else if (alist->parameter().type() == PanAutomation) {
1309                 // vertical coordinate axis reversal
1310                 y = 1.0 - y;
1311         } else if (alist->parameter().type() == MidiCCAutomation) {
1312                 y = (int)(y * 127.0);
1313         } else if (alist->parameter().type() == PluginAutomation) {
1314                 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1315         }
1316 }
1317
1318 void
1319 AutomationLine::model_to_view_y (double& y)
1320 {
1321         if (alist->parameter().type() == GainAutomation) {
1322                 y = gain_to_slider_position (y);
1323         } else if (alist->parameter().type() == PanAutomation) {
1324                 // vertical coordinate axis reversal
1325                 y = 1.0 - y;
1326         } else if (alist->parameter().type() == MidiCCAutomation) {
1327                 y = y / 127.0;
1328         } else if (alist->parameter().type() == PluginAutomation) {
1329                 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1330         }
1331 }
1332