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