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