2 Copyright (C) 2002-2003 Paul Davis
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.
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.
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.
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
54 using namespace ARDOUR
;
56 using namespace Editing
;
57 using namespace Gnome
; // for Canvas
59 static const Evoral::IdentityConverter
<double, sframes_t
> default_converter
;
61 AutomationLine::AutomationLine (const string
& name
, TimeAxisView
& tv
, ArdourCanvas::Group
& parent
,
62 boost::shared_ptr
<AutomationList
> al
,
63 const Evoral::TimeConverter
<double, sframes_t
>* converter
)
67 , _parent_group (parent
)
68 , _time_converter (converter
? (*converter
) : default_converter
)
70 _interpolation
= al
->interpolation();
71 points_visible
= false;
72 update_pending
= false;
73 _uses_gain_mapping
= false;
76 terminal_points_can_slide
= true;
79 group
= new ArdourCanvas::Group (parent
);
80 group
->property_x() = 0.0;
81 group
->property_y() = 0.0;
83 line
= new ArdourCanvas::Line (*group
);
84 line
->property_width_pixels() = (guint
)1;
85 line
->set_data ("line", this);
87 line
->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler
));
89 alist
->StateChanged
.connect (_state_connection
, invalidator (*this), boost::bind (&AutomationLine::list_changed
, this), gui_context());
91 trackview
.session()->register_with_memento_command_factory(alist
->id(), this);
93 if (alist
->parameter().type() == GainAutomation
||
94 alist
->parameter().type() == EnvelopeAutomation
) {
95 set_uses_gain_mapping (true);
98 set_interpolation(alist
->interpolation());
101 AutomationLine::~AutomationLine ()
103 vector_delete (&control_points
);
108 AutomationLine::event_handler (GdkEvent
* event
)
110 return PublicEditor::instance().canvas_line_event (event
, line
, this);
114 AutomationLine::queue_reset ()
116 if (!update_pending
) {
117 update_pending
= true;
118 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset
, this));
123 AutomationLine::show ()
125 if (_interpolation
!= AutomationList::Discrete
) {
129 if (points_visible
) {
130 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
139 AutomationLine::hide ()
142 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
149 AutomationLine::control_point_box_size ()
151 if (_interpolation
== AutomationList::Discrete
) {
152 return max((_height
*4.0) / (double)(alist
->parameter().max() - alist
->parameter().min()),
156 if (_height
> TimeAxisView::preset_height (HeightLarger
)) {
158 } else if (_height
> (guint32
) TimeAxisView::preset_height (HeightNormal
)) {
165 AutomationLine::set_height (guint32 h
)
170 double bsz
= control_point_box_size();
172 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
173 (*i
)->set_size (bsz
);
181 AutomationLine::set_line_color (uint32_t color
)
184 line
->property_fill_color_rgba() = color
;
188 AutomationLine::set_uses_gain_mapping (bool yn
)
190 if (yn
!= _uses_gain_mapping
) {
191 _uses_gain_mapping
= yn
;
197 AutomationLine::nth (uint32_t n
)
199 if (n
< control_points
.size()) {
200 return control_points
[n
];
207 AutomationLine::modify_point_y (ControlPoint
& cp
, double y
)
209 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210 and needs to be converted to a canvas unit distance.
215 y
= _height
- (y
* _height
);
217 double const x
= trackview
.editor().frame_to_unit (_time_converter
.to((*cp
.model())->when
));
219 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
220 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), &get_state(), 0));
222 cp
.move_to (x
, y
, ControlPoint::Full
);
223 reset_line_coords (cp
);
225 if (line_points
.size() > 1) {
226 line
->property_points() = line_points
;
230 sync_model_with_view_point (cp
, false, 0);
233 update_pending
= false;
235 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), 0, &alist
->get_state()));
236 trackview
.editor().session()->commit_reversible_command ();
237 trackview
.editor().session()->set_dirty ();
241 AutomationLine::reset_line_coords (ControlPoint
& cp
)
243 if (cp
.view_index() < line_points
.size()) {
244 line_points
[cp
.view_index()].set_x (cp
.get_x());
245 line_points
[cp
.view_index()].set_y (cp
.get_y());
250 AutomationLine::sync_model_with_view_points (list
<ControlPoint
*> cp
, bool did_push
, int64_t distance
)
252 update_pending
= true;
254 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
255 sync_model_with_view_point (**i
, did_push
, distance
);
260 AutomationLine::model_representation (ControlPoint
& cp
, ModelRepresentation
& mr
)
262 /* part one: find out where the visual control point is.
263 initial results are in canvas units. ask the
264 line to convert them to something relevant.
267 mr
.xval
= cp
.get_x();
268 mr
.yval
= 1.0 - (cp
.get_y() / _height
);
270 /* if xval has not changed, set it directly from the model to avoid rounding errors */
272 if (mr
.xval
== trackview
.editor().frame_to_unit(_time_converter
.to((*cp
.model())->when
))) {
273 mr
.xval
= (*cp
.model())->when
;
275 mr
.xval
= trackview
.editor().unit_to_frame (mr
.xval
);
278 /* convert to model units
281 view_to_model_coord (mr
.xval
, mr
.yval
);
283 /* part 2: find out where the model point is now
286 mr
.xpos
= (*cp
.model())->when
;
287 mr
.ypos
= (*cp
.model())->value
;
289 /* part 3: get the position of the visual control
290 points before and after us.
293 ControlPoint
* before
;
296 if (cp
.view_index()) {
297 before
= nth (cp
.view_index() - 1);
302 after
= nth (cp
.view_index() + 1);
305 mr
.xmin
= (*before
->model())->when
;
306 mr
.ymin
= (*before
->model())->value
;
307 mr
.start
= before
->model();
312 mr
.start
= cp
.model();
316 mr
.end
= after
->model();
326 AutomationLine::determine_visible_control_points (ALPoints
& points
)
328 uint32_t view_index
, pi
, n
;
329 AutomationList::iterator model
;
331 uint32_t this_rx
= 0;
332 uint32_t prev_rx
= 0;
333 uint32_t this_ry
= 0;
334 uint32_t prev_ry
= 0;
338 /* hide all existing points, and the line */
340 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
346 if (points
.empty()) {
350 npoints
= points
.size();
352 /* compute derivative/slope for the entire line */
354 slope
= new double[npoints
];
356 for (n
= 0; n
< npoints
- 1; ++n
) {
357 double xdelta
= points
[n
+1].x
- points
[n
].x
;
358 double ydelta
= points
[n
+1].y
- points
[n
].y
;
359 slope
[n
] = ydelta
/xdelta
;
362 box_size
= (uint32_t) control_point_box_size ();
364 /* read all points and decide which ones to show as control points */
368 for (model
= alist
->begin(), pi
= 0; pi
< npoints
; ++model
, ++pi
) {
370 double tx
= points
[pi
].x
;
371 double ty
= points
[pi
].y
;
373 if (find (_always_in_view
.begin(), _always_in_view
.end(), (*model
)->when
) != _always_in_view
.end()) {
374 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
381 if (isnan (tx
) || isnan (ty
)) {
382 warning
<< string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
387 /* now ensure that the control_points vector reflects the current curve
388 state, but don't plot control points too close together. also, don't
389 plot a series of points all with the same value.
391 always plot the first and last points, of course.
394 if (invalid_point (points
, pi
)) {
395 /* for some reason, we are supposed to ignore this point,
396 but still keep track of the model index.
401 if (pi
> 0 && pi
< npoints
- 1) {
402 if (slope
[pi
] == slope
[pi
-1]) {
404 /* no reason to display this point */
410 /* need to round here. the ultimate coordinates are integer
411 pixels, so tiny deltas in the coords will be eliminated
412 and we end up with "colinear" line segments. since the
413 line rendering code in libart doesn't like this very
414 much, we eliminate them here. don't do this for the first and last
418 this_rx
= (uint32_t) rint (tx
);
419 this_ry
= (uint32_t) rint (ty
);
421 if (view_index
&& pi
!= npoints
&& /* not the first, not the last */
422 (((this_rx
== prev_rx
) && (this_ry
== prev_ry
)) || /* same point */
423 (((this_rx
- prev_rx
) < (box_size
+ 2)) && /* not identical, but still too close horizontally */
424 (abs ((int)(this_ry
- prev_ry
)) < (int) (box_size
+ 2))))) { /* too close vertically */
428 /* ok, we should display this point */
430 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
438 /* discard extra CP's to avoid confusing ourselves */
440 while (control_points
.size() > view_index
) {
441 ControlPoint
* cp
= control_points
.back();
442 control_points
.pop_back ();
446 if (!terminal_points_can_slide
) {
447 control_points
.back()->set_can_slide(false);
452 if (view_index
> 1) {
454 npoints
= view_index
;
456 /* reset the line coordinates */
458 while (line_points
.size() < npoints
) {
459 line_points
.push_back (Art::Point (0,0));
462 while (line_points
.size() > npoints
) {
463 line_points
.pop_back ();
466 for (view_index
= 0; view_index
< npoints
; ++view_index
) {
467 line_points
[view_index
].set_x (control_points
[view_index
]->get_x());
468 line_points
[view_index
].set_y (control_points
[view_index
]->get_y());
471 line
->property_points() = line_points
;
473 if (_visible
&& _interpolation
!= AutomationList::Discrete
) {
479 set_selected_points (trackview
.editor().get_selection().points
);
483 AutomationLine::get_verbose_cursor_string (double fraction
) const
485 std::string s
= fraction_to_string (fraction
);
486 if (_uses_gain_mapping
) {
494 * @param fraction y fraction
495 * @return string representation of this value, using dB if appropriate.
498 AutomationLine::fraction_to_string (double fraction
) const
502 if (_uses_gain_mapping
) {
503 if (fraction
== 0.0) {
504 snprintf (buf
, sizeof (buf
), "-inf");
506 snprintf (buf
, sizeof (buf
), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction
)));
510 view_to_model_coord (dummy
, fraction
);
511 if (EventTypeMap::instance().is_integer (alist
->parameter())) {
512 snprintf (buf
, sizeof (buf
), "%d", (int)fraction
);
514 snprintf (buf
, sizeof (buf
), "%.2f", fraction
);
523 * @param s Value string in the form as returned by fraction_to_string.
524 * @return Corresponding y fraction.
527 AutomationLine::string_to_fraction (string
const & s
) const
534 sscanf (s
.c_str(), "%lf", &v
);
536 if (_uses_gain_mapping
) {
537 v
= gain_to_slider_position (dB_to_coefficient (v
));
540 model_to_view_coord (dummy
, v
);
547 AutomationLine::invalid_point (ALPoints
& p
, uint32_t index
)
549 return p
[index
].x
== max_frames
&& p
[index
].y
== DBL_MAX
;
553 AutomationLine::invalidate_point (ALPoints
& p
, uint32_t index
)
555 p
[index
].x
= max_frames
;
556 p
[index
].y
= DBL_MAX
;
559 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
560 * are other selected points.
562 * @param cp Point to drag.
563 * @param x Initial x position (units).
564 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
567 AutomationLine::start_drag_single (ControlPoint
* cp
, double x
, float fraction
)
569 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
570 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), &get_state(), 0));
572 _drag_points
.clear ();
573 _drag_points
.push_back (cp
);
575 if (cp
->selected ()) {
576 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
577 if (*i
!= cp
&& (*i
)->selected()) {
578 _drag_points
.push_back (*i
);
583 start_drag_common (x
, fraction
);
586 /** Start dragging a line vertically (with no change in x)
587 * @param i1 Control point index of the `left' point on the line.
588 * @param i2 Control point index of the `right' point on the line.
589 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
592 AutomationLine::start_drag_line (uint32_t i1
, uint32_t i2
, float fraction
)
594 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
595 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), &get_state(), 0));
597 _drag_points
.clear ();
598 for (uint32_t i
= i1
; i
<= i2
; i
++) {
599 _drag_points
.push_back (nth (i
));
602 start_drag_common (0, fraction
);
605 /** Start dragging multiple points (with no change in x)
606 * @param cp Points to drag.
607 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
610 AutomationLine::start_drag_multiple (list
<ControlPoint
*> cp
, float fraction
, XMLNode
* state
)
612 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
613 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), state
, 0));
616 start_drag_common (0, fraction
);
620 struct ControlPointSorter
622 bool operator() (ControlPoint
const * a
, ControlPoint
const * b
) {
623 return a
->get_x() < b
->get_x();
627 /** Common parts of starting a drag.
628 * @param x Starting x position in units, or 0 if x is being ignored.
629 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
632 AutomationLine::start_drag_common (double x
, float fraction
)
636 _last_drag_fraction
= fraction
;
637 _drag_had_movement
= false;
640 _drag_points
.sort (ControlPointSorter ());
642 /* find the additional points that will be dragged when the user is holding
646 uint32_t i
= _drag_points
.back()->view_index () + 1;
648 _push_points
.clear ();
649 while ((p
= nth (i
)) != 0 && p
->can_slide()) {
650 _push_points
.push_back (p
);
655 /** Should be called to indicate motion during a drag.
656 * @param x New x position of the drag in units, or undefined if ignore_x == true.
657 * @param fraction New y fraction.
658 * @return x position and y fraction that were actually used (once clamped).
661 AutomationLine::drag_motion (double x
, float fraction
, bool ignore_x
, bool with_push
)
663 /* setup the points that are to be moved this time round */
664 list
<ControlPoint
*> points
= _drag_points
;
666 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
667 points
.sort (ControlPointSorter ());
670 double dx
= ignore_x
? 0 : (x
- _drag_x
);
671 double dy
= fraction
- _last_drag_fraction
;
674 ControlPoint
* before
= 0;
675 ControlPoint
* after
= 0;
677 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
678 if ((*i
)->get_x() < points
.front()->get_x()) {
681 if ((*i
)->get_x() > points
.back()->get_x() && after
== 0) {
686 double const before_x
= before
? before
->get_x() : 0;
687 double const after_x
= after
? after
->get_x() : DBL_MAX
;
690 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
692 if ((*i
)->can_slide() && !ignore_x
) {
695 double const a
= (*i
)->get_x() + dx
;
696 double const b
= before_x
+ 1;
704 if (after_x
- before_x
< 2) {
705 /* after and before are very close, so just leave this alone */
708 double const a
= (*i
)->get_x() + dx
;
709 double const b
= after_x
- 1;
719 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
720 double const y
= ((_height
- (*i
)->get_y()) / _height
) + dy
;
729 pair
<double, float> const clamped (_drag_x
+ dx
, _last_drag_fraction
+ dy
);
730 _drag_distance
+= dx
;
732 _last_drag_fraction
= fraction
;
734 for (list
<ControlPoint
*>::iterator i
= _drag_points
.begin(); i
!= _drag_points
.end(); ++i
) {
735 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y() - _height
* dy
, ControlPoint::Full
);
736 reset_line_coords (**i
);
740 /* move push points, preserving their y */
741 for (list
<ControlPoint
*>::iterator i
= _push_points
.begin(); i
!= _push_points
.end(); ++i
) {
742 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y(), ControlPoint::Full
);
743 reset_line_coords (**i
);
747 if (line_points
.size() > 1) {
748 line
->property_points() = line_points
;
751 _drag_had_movement
= true;
752 did_push
= with_push
;
757 /** Should be called to indicate the end of a drag */
759 AutomationLine::end_drag ()
761 if (!_drag_had_movement
) {
767 /* set up the points that were moved this time round */
768 list
<ControlPoint
*> points
= _drag_points
;
770 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
771 points
.sort (ControlPointSorter ());
774 sync_model_with_view_points (points
, did_push
, rint (_drag_distance
* trackview
.editor().get_current_zoom ()));
778 update_pending
= false;
780 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*alist
.get(), 0, &alist
->get_state()));
781 trackview
.editor().session()->commit_reversible_command ();
782 trackview
.editor().session()->set_dirty ();
786 AutomationLine::sync_model_with_view_point (ControlPoint
& cp
, bool did_push
, int64_t distance
)
788 ModelRepresentation mr
;
791 model_representation (cp
, mr
);
793 /* how much are we changing the central point by */
795 ydelta
= mr
.yval
- mr
.ypos
;
798 apply the full change to the central point, and interpolate
799 on both axes to cover all model points represented
800 by the control point.
803 /* change all points before the primary point */
805 for (AutomationList::iterator i
= mr
.start
; i
!= cp
.model(); ++i
) {
807 double fract
= ((*i
)->when
- mr
.xmin
) / (mr
.xpos
- mr
.xmin
);
808 double y_delta
= ydelta
* fract
;
809 double x_delta
= distance
* fract
;
813 if (y_delta
|| x_delta
) {
814 alist
->modify (i
, (*i
)->when
+ x_delta
, mr
.ymin
+ y_delta
);
818 /* change the primary point */
820 update_pending
= true;
821 alist
->modify (cp
.model(), mr
.xval
, mr
.yval
);
824 /* change later points */
826 AutomationList::iterator i
= cp
.model();
830 while (i
!= mr
.end
) {
832 double delta
= ydelta
* (mr
.xmax
- (*i
)->when
) / (mr
.xmax
- mr
.xpos
);
834 /* all later points move by the same distance along the x-axis as the main point */
837 alist
->modify (i
, (*i
)->when
+ distance
, (*i
)->value
+ delta
);
845 /* move all points after the range represented by the view by the same distance
846 as the main point moved.
849 alist
->slide (mr
.end
, distance
);
854 AutomationLine::control_points_adjacent (double xval
, uint32_t & before
, uint32_t& after
)
856 ControlPoint
*bcp
= 0;
857 ControlPoint
*acp
= 0;
860 unit_xval
= trackview
.editor().frame_to_unit (xval
);
862 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
864 if ((*i
)->get_x() <= unit_xval
) {
866 if (!bcp
|| (*i
)->get_x() > bcp
->get_x()) {
868 before
= bcp
->view_index();
871 } else if ((*i
)->get_x() > unit_xval
) {
873 after
= acp
->view_index();
882 AutomationLine::is_last_point (ControlPoint
& cp
)
884 ModelRepresentation mr
;
886 model_representation (cp
, mr
);
888 // If the list is not empty, and the point is the last point in the list
890 if (!alist
->empty() && mr
.end
== alist
->end()) {
898 AutomationLine::is_first_point (ControlPoint
& cp
)
900 ModelRepresentation mr
;
902 model_representation (cp
, mr
);
904 // If the list is not empty, and the point is the first point in the list
906 if (!alist
->empty() && mr
.start
== alist
->begin()) {
913 // This is copied into AudioRegionGainLine
915 AutomationLine::remove_point (ControlPoint
& cp
)
917 ModelRepresentation mr
;
919 model_representation (cp
, mr
);
921 trackview
.editor().session()->begin_reversible_command (_("remove control point"));
922 XMLNode
&before
= alist
->get_state();
924 alist
->erase (mr
.start
, mr
.end
);
926 trackview
.editor().session()->add_command(new MementoCommand
<AutomationList
>(
927 *alist
.get(), &before
, &alist
->get_state()));
928 trackview
.editor().session()->commit_reversible_command ();
929 trackview
.editor().session()->set_dirty ();
933 AutomationLine::get_selectables (nframes_t
& start
, nframes_t
& end
,
934 double botfrac
, double topfrac
, list
<Selectable
*>& results
)
941 bool collecting
= false;
943 /* Curse X11 and its inverted coordinate system! */
945 bot
= (1.0 - topfrac
) * _height
;
946 top
= (1.0 - botfrac
) * _height
;
951 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
952 double when
= (*(*i
)->model())->when
;
954 if (when
>= start
&& when
<= end
) {
956 if ((*i
)->get_y() >= bot
&& (*i
)->get_y() <= top
) {
959 (*i
)->set_visible(true);
961 nstart
= min (nstart
, when
);
962 nend
= max (nend
, when
);
968 results
.push_back (new AutomationSelectable (nstart
, nend
, botfrac
, topfrac
, &trackview
));
978 results
.push_back (new AutomationSelectable (nstart
, nend
, botfrac
, topfrac
, &trackview
));
984 AutomationLine::get_inverted_selectables (Selection
&, list
<Selectable
*>& /*results*/)
989 /** Take a PointSelection and find ControlPoints that fall within it */
991 AutomationLine::point_selection_to_control_points (PointSelection
const & s
)
993 list
<ControlPoint
*> cp
;
995 for (PointSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
997 if (i
->track
!= &trackview
) {
1001 /* Curse X11 and its inverted coordinate system! */
1003 double const bot
= (1.0 - i
->high_fract
) * _height
;
1004 double const top
= (1.0 - i
->low_fract
) * _height
;
1006 for (vector
<ControlPoint
*>::iterator j
= control_points
.begin(); j
!= control_points
.end(); ++j
) {
1008 double const rstart
= trackview
.editor().frame_to_unit (i
->start
);
1009 double const rend
= trackview
.editor().frame_to_unit (i
->end
);
1011 if ((*j
)->get_x() >= rstart
&& (*j
)->get_x() <= rend
) {
1012 if ((*j
)->get_y() >= bot
&& (*j
)->get_y() <= top
) {
1024 AutomationLine::set_selected_points (PointSelection
& points
)
1026 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1027 (*i
)->set_selected (false);
1030 if (!points
.empty()) {
1031 list
<ControlPoint
*> cp
= point_selection_to_control_points (points
);
1032 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
1033 (*i
)->set_selected (true);
1040 void AutomationLine::set_colors ()
1042 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine
.get());
1043 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1049 AutomationLine::list_changed ()
1055 AutomationLine::reset_callback (const Evoral::ControlList
& events
)
1057 ALPoints tmp_points
;
1058 uint32_t npoints
= events
.size();
1061 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1064 control_points
.clear ();
1069 AutomationList::const_iterator ai
;
1071 for (ai
= events
.begin(); ai
!= events
.end(); ++ai
) {
1073 double translated_x
= (*ai
)->when
;
1074 double translated_y
= (*ai
)->value
;
1075 model_to_view_coord (translated_x
, translated_y
);
1077 add_model_point (tmp_points
, (*ai
)->when
, translated_y
);
1080 determine_visible_control_points (tmp_points
);
1085 AutomationLine::add_model_point (ALPoints
& tmp_points
, double frame
, double yfract
)
1087 tmp_points
.push_back (ALPoint (trackview
.editor().frame_to_unit (_time_converter
.to(frame
)),
1088 _height
- (yfract
* _height
)));
1092 AutomationLine::reset ()
1094 update_pending
= false;
1100 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1104 AutomationLine::clear ()
1106 /* parent must create command */
1107 XMLNode
&before
= alist
->get_state();
1109 trackview
.editor().session()->add_command (new MementoCommand
<AutomationList
>(*(alist
.get()), &before
, &alist
->get_state()));
1110 trackview
.editor().session()->commit_reversible_command ();
1111 trackview
.editor().session()->set_dirty ();
1115 AutomationLine::change_model (AutomationList::iterator
/*i*/, double /*x*/, double /*y*/)
1120 AutomationLine::set_list(boost::shared_ptr
<ARDOUR::AutomationList
> list
)
1127 AutomationLine::show_all_control_points ()
1129 points_visible
= true;
1131 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1132 if (!(*i
)->visible()) {
1134 (*i
)->set_visible (true);
1140 AutomationLine::hide_all_but_selected_control_points ()
1142 if (alist
->interpolation() == AutomationList::Discrete
) {
1146 points_visible
= false;
1148 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1149 if (!(*i
)->selected()) {
1150 (*i
)->set_visible (false);
1156 AutomationLine::track_entered()
1158 if (alist
->interpolation() != AutomationList::Discrete
) {
1159 show_all_control_points();
1164 AutomationLine::track_exited()
1166 if (alist
->interpolation() != AutomationList::Discrete
) {
1167 hide_all_but_selected_control_points();
1172 AutomationLine::get_state (void)
1174 /* function as a proxy for the model */
1175 return alist
->get_state();
1179 AutomationLine::set_state (const XMLNode
&node
, int version
)
1181 /* function as a proxy for the model */
1182 return alist
->set_state (node
, version
);
1186 AutomationLine::view_to_model_coord (double& x
, double& y
) const
1188 /* TODO: This should be more generic ... */
1189 if (alist
->parameter().type() == GainAutomation
||
1190 alist
->parameter().type() == EnvelopeAutomation
) {
1191 y
= slider_position_to_gain (y
);
1194 } else if (alist
->parameter().type() == PanAutomation
) {
1195 // vertical coordinate axis reversal
1197 } else if (alist
->parameter().type() == PluginAutomation
) {
1198 y
= y
* (double)(alist
->get_max_y()- alist
->get_min_y()) + alist
->get_min_y();
1200 y
= (int)(y
* alist
->parameter().max());
1203 x
= _time_converter
.from(x
);
1207 AutomationLine::model_to_view_coord (double& x
, double& y
) const
1209 /* TODO: This should be more generic ... */
1210 if (alist
->parameter().type() == GainAutomation
||
1211 alist
->parameter().type() == EnvelopeAutomation
) {
1212 y
= gain_to_slider_position (y
);
1213 } else if (alist
->parameter().type() == PanAutomation
) {
1214 // vertical coordinate axis reversal
1216 } else if (alist
->parameter().type() == PluginAutomation
) {
1217 y
= (y
- alist
->get_min_y()) / (double)(alist
->get_max_y()- alist
->get_min_y());
1219 y
= y
/ (double)alist
->parameter().max(); /* ... like this */
1222 x
= _time_converter
.to(x
);
1227 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style
)
1229 _interpolation
= style
;
1231 if (style
== AutomationList::Discrete
) {
1232 show_all_control_points();
1235 hide_all_but_selected_control_points();
1241 AutomationLine::add_visible_control_point (uint32_t view_index
, uint32_t pi
, double tx
, double ty
, AutomationList::iterator model
, uint32_t npoints
)
1243 if (view_index
>= control_points
.size()) {
1245 /* make sure we have enough control points */
1247 ControlPoint
* ncp
= new ControlPoint (*this);
1248 ncp
->set_size (control_point_box_size ());
1250 control_points
.push_back (ncp
);
1253 ControlPoint::ShapeType shape
;
1255 if (!terminal_points_can_slide
) {
1257 control_points
[view_index
]->set_can_slide(false);
1259 shape
= ControlPoint::Start
;
1261 shape
= ControlPoint::Full
;
1263 } else if (pi
== npoints
- 1) {
1264 control_points
[view_index
]->set_can_slide(false);
1265 shape
= ControlPoint::End
;
1267 control_points
[view_index
]->set_can_slide(true);
1268 shape
= ControlPoint::Full
;
1271 control_points
[view_index
]->set_can_slide(true);
1272 shape
= ControlPoint::Full
;
1275 control_points
[view_index
]->reset (tx
, ty
, model
, view_index
, shape
);
1277 /* finally, control visibility */
1279 if (_visible
&& points_visible
) {
1280 control_points
[view_index
]->show ();
1281 control_points
[view_index
]->set_visible (true);
1283 if (!points_visible
) {
1284 control_points
[view_index
]->set_visible (false);
1290 AutomationLine::add_always_in_view (double x
)
1292 _always_in_view
.push_back (x
);
1293 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1297 AutomationLine::clear_always_in_view ()
1299 _always_in_view
.clear ();
1300 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);