2 Copyright (C) 2002 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.
21 #include <sigc++/bind.h>
22 #include "pbd/error.h"
23 #include "pbd/stacktrace.h"
25 #include "ardour/playlist.h"
26 #include "ardour/rc_configuration.h"
28 #include "gui_thread.h"
29 #include "midi_cut_buffer.h"
30 #include "region_view.h"
31 #include "selection.h"
32 #include "selection_templates.h"
33 #include "time_axis_view.h"
34 #include "automation_time_axis.h"
35 #include "public_editor.h"
36 #include "control_point.h"
41 using namespace ARDOUR
;
44 struct AudioRangeComparator
{
45 bool operator()(AudioRange a
, AudioRange b
) {
46 return a
.start
< b
.start
;
50 Selection::Selection (const PublicEditor
* e
)
57 /* we have disambiguate which remove() for the compiler */
59 void (Selection::*track_remove
)(TimeAxisView
*) = &Selection::remove
;
60 TimeAxisView::CatchDeletion
.connect (*this, MISSING_INVALIDATOR
, ui_bind (track_remove
, this, _1
), gui_context());
62 void (Selection::*marker_remove
)(Marker
*) = &Selection::remove
;
63 Marker::CatchDeletion
.connect (*this, MISSING_INVALIDATOR
, ui_bind (marker_remove
, this, _1
), gui_context());
68 Selection::operator= (const Selection
& other
)
71 regions
= other
.regions
;
72 tracks
= other
.tracks
;
75 midi_regions
= other
.midi_regions
;
76 midi_notes
= other
.midi_notes
;
83 operator== (const Selection
& a
, const Selection
& b
)
85 return a
.regions
== b
.regions
&&
86 a
.tracks
== b
.tracks
&&
89 a
.playlists
== b
.playlists
&&
90 a
.midi_notes
== b
.midi_notes
&&
91 a
.midi_regions
== b
.midi_regions
;
94 /** Clear everything from the Selection */
105 clear_midi_regions ();
109 Selection::dump_region_layers()
111 cerr
<< "region selection layer dump" << endl
;
112 for (RegionSelection::iterator i
= regions
.begin(); i
!= regions
.end(); ++i
) {
113 cerr
<< "layer: " << (int)(*i
)->region()->layer() << endl
;
119 Selection::clear_regions ()
121 if (!regions
.empty()) {
122 regions
.clear_all ();
128 Selection::clear_tracks ()
130 if (!tracks
.empty()) {
137 Selection::clear_midi_notes ()
139 if (!midi_notes
.empty()) {
140 for (MidiNoteSelection::iterator x
= midi_notes
.begin(); x
!= midi_notes
.end(); ++x
) {
149 Selection::clear_midi_regions ()
151 if (!midi_regions
.empty()) {
152 midi_regions
.clear ();
153 MidiRegionsChanged ();
158 Selection::clear_time ()
166 Selection::clear_playlists ()
168 /* Selections own their playlists */
170 for (PlaylistSelection::iterator i
= playlists
.begin(); i
!= playlists
.end(); ++i
) {
171 /* selections own their own regions, which are copies of the "originals". make them go away */
172 (*i
)->drop_regions ();
176 if (!playlists
.empty()) {
183 Selection::clear_lines ()
185 if (!lines
.empty()) {
192 Selection::clear_markers ()
194 if (!markers
.empty()) {
201 Selection::toggle (boost::shared_ptr
<Playlist
> pl
)
203 PlaylistSelection::iterator i
;
205 if ((i
= find (playlists
.begin(), playlists
.end(), pl
)) == playlists
.end()) {
207 playlists
.push_back(pl
);
216 Selection::toggle (const TrackViewList
& track_list
)
218 for (TrackViewList::const_iterator i
= track_list
.begin(); i
!= track_list
.end(); ++i
) {
224 Selection::toggle (TimeAxisView
* track
)
226 TrackSelection::iterator i
;
228 if ((i
= find (tracks
.begin(), tracks
.end(), track
)) == tracks
.end()) {
229 tracks
.push_back (track
);
238 Selection::toggle (const MidiNoteSelection
& midi_note_list
)
240 for (MidiNoteSelection::const_iterator i
= midi_note_list
.begin(); i
!= midi_note_list
.end(); ++i
) {
246 Selection::toggle (MidiCutBuffer
* midi
)
248 MidiNoteSelection::iterator i
;
250 if ((i
= find (midi_notes
.begin(), midi_notes
.end(), midi
)) == midi_notes
.end()) {
251 midi_notes
.push_back (midi
);
253 /* remember that we own the MCB */
255 midi_notes
.erase (i
);
263 Selection::toggle (RegionView
* r
)
265 RegionSelection::iterator i
;
267 if ((i
= find (regions
.begin(), regions
.end(), r
)) == regions
.end()) {
277 Selection::toggle (MidiRegionView
* mrv
)
279 MidiRegionSelection::iterator i
;
281 if ((i
= find (midi_regions
.begin(), midi_regions
.end(), mrv
)) == midi_regions
.end()) {
284 midi_regions
.erase (i
);
287 MidiRegionsChanged ();
291 Selection::toggle (vector
<RegionView
*>& r
)
293 RegionSelection::iterator i
;
295 for (vector
<RegionView
*>::iterator x
= r
.begin(); x
!= r
.end(); ++x
) {
296 if ((i
= find (regions
.begin(), regions
.end(), (*x
))) == regions
.end()) {
307 Selection::toggle (framepos_t start
, framepos_t end
)
309 AudioRangeComparator cmp
;
311 /* XXX this implementation is incorrect */
313 time
.push_back (AudioRange (start
, end
, next_time_id
++));
319 return next_time_id
- 1;
323 Selection::add (boost::shared_ptr
<Playlist
> pl
)
325 if (find (playlists
.begin(), playlists
.end(), pl
) == playlists
.end()) {
327 playlists
.push_back(pl
);
333 Selection::add (const list
<boost::shared_ptr
<Playlist
> >& pllist
)
335 bool changed
= false;
337 for (list
<boost::shared_ptr
<Playlist
> >::const_iterator i
= pllist
.begin(); i
!= pllist
.end(); ++i
) {
338 if (find (playlists
.begin(), playlists
.end(), (*i
)) == playlists
.end()) {
340 playlists
.push_back (*i
);
351 Selection::add (const TrackViewList
& track_list
)
353 TrackViewList added
= tracks
.add (track_list
);
355 if (!added
.empty()) {
361 Selection::add (TimeAxisView
* track
)
364 tr
.push_back (track
);
369 Selection::add (const MidiNoteSelection
& midi_list
)
371 const MidiNoteSelection::const_iterator b
= midi_list
.begin();
372 const MidiNoteSelection::const_iterator e
= midi_list
.end();
374 if (!midi_list
.empty()) {
375 midi_notes
.insert (midi_notes
.end(), b
, e
);
381 Selection::add (MidiCutBuffer
* midi
)
383 /* we take ownership of the MCB */
385 if (find (midi_notes
.begin(), midi_notes
.end(), midi
) == midi_notes
.end()) {
386 midi_notes
.push_back (midi
);
392 Selection::add (vector
<RegionView
*>& v
)
394 /* XXX This method or the add (const RegionSelection&) needs to go
397 bool changed
= false;
399 for (vector
<RegionView
*>::iterator i
= v
.begin(); i
!= v
.end(); ++i
) {
400 if (find (regions
.begin(), regions
.end(), (*i
)) == regions
.end()) {
401 changed
= regions
.add ((*i
));
402 if (Config
->get_link_region_and_track_selection() && changed
) {
403 add (&(*i
)->get_time_axis_view());
414 Selection::add (const RegionSelection
& rs
)
416 /* XXX This method or the add (const vector<RegionView*>&) needs to go
419 bool changed
= false;
421 for (RegionSelection::const_iterator i
= rs
.begin(); i
!= rs
.end(); ++i
) {
422 if (find (regions
.begin(), regions
.end(), (*i
)) == regions
.end()) {
423 changed
= regions
.add ((*i
));
424 if (Config
->get_link_region_and_track_selection() && changed
) {
425 add (&(*i
)->get_time_axis_view());
436 Selection::add (RegionView
* r
)
438 if (find (regions
.begin(), regions
.end(), r
) == regions
.end()) {
439 bool changed
= regions
.add (r
);
440 if (Config
->get_link_region_and_track_selection() && changed
) {
441 add (&r
->get_time_axis_view());
450 Selection::add (MidiRegionView
* mrv
)
452 if (find (midi_regions
.begin(), midi_regions
.end(), mrv
) == midi_regions
.end()) {
453 midi_regions
.push_back (mrv
);
454 /* XXX should we do this? */
456 if (Config
->get_link_region_and_track_selection()) {
457 add (&mrv
->get_time_axis_view());
460 MidiRegionsChanged ();
465 Selection::add (framepos_t start
, framepos_t end
)
467 AudioRangeComparator cmp
;
469 /* XXX this implementation is incorrect */
471 time
.push_back (AudioRange (start
, end
, next_time_id
++));
477 return next_time_id
- 1;
481 Selection::replace (uint32_t sid
, framepos_t start
, framepos_t end
)
483 for (list
<AudioRange
>::iterator i
= time
.begin(); i
!= time
.end(); ++i
) {
484 if ((*i
).id
== sid
) {
486 time
.push_back (AudioRange(start
,end
, sid
));
488 /* don't consolidate here */
491 AudioRangeComparator cmp
;
501 Selection::add (boost::shared_ptr
<Evoral::ControlList
> cl
)
503 boost::shared_ptr
<ARDOUR::AutomationList
> al
504 = boost::dynamic_pointer_cast
<ARDOUR::AutomationList
>(cl
);
506 warning
<< "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg
;
510 if (find (lines
.begin(), lines
.end(), al
) == lines
.end()) {
511 lines
.push_back (al
);
517 Selection::remove (TimeAxisView
* track
)
519 list
<TimeAxisView
*>::iterator i
;
520 if ((i
= find (tracks
.begin(), tracks
.end(), track
)) != tracks
.end()) {
527 Selection::remove (const TrackViewList
& track_list
)
529 bool changed
= false;
531 for (TrackViewList::const_iterator i
= track_list
.begin(); i
!= track_list
.end(); ++i
) {
533 TrackViewList::iterator x
= find (tracks
.begin(), tracks
.end(), *i
);
534 if (x
!= tracks
.end()) {
546 Selection::remove (const MidiNoteSelection
& midi_list
)
548 bool changed
= false;
550 for (MidiNoteSelection::const_iterator i
= midi_list
.begin(); i
!= midi_list
.end(); ++i
) {
552 MidiNoteSelection::iterator x
;
554 if ((x
= find (midi_notes
.begin(), midi_notes
.end(), (*i
))) != midi_notes
.end()) {
555 midi_notes
.erase (x
);
566 Selection::remove (MidiCutBuffer
* midi
)
568 MidiNoteSelection::iterator x
;
570 if ((x
= find (midi_notes
.begin(), midi_notes
.end(), midi
)) != midi_notes
.end()) {
571 /* remember that we own the MCB */
573 midi_notes
.erase (x
);
579 Selection::remove (boost::shared_ptr
<Playlist
> track
)
581 list
<boost::shared_ptr
<Playlist
> >::iterator i
;
582 if ((i
= find (playlists
.begin(), playlists
.end(), track
)) != playlists
.end()) {
589 Selection::remove (const list
<boost::shared_ptr
<Playlist
> >& pllist
)
591 bool changed
= false;
593 for (list
<boost::shared_ptr
<Playlist
> >::const_iterator i
= pllist
.begin(); i
!= pllist
.end(); ++i
) {
595 list
<boost::shared_ptr
<Playlist
> >::iterator x
;
597 if ((x
= find (playlists
.begin(), playlists
.end(), (*i
))) != playlists
.end()) {
609 Selection::remove (RegionView
* r
)
611 if (regions
.remove (r
)) {
615 if (Config
->get_link_region_and_track_selection() && !regions
.involves (r
->get_time_axis_view())) {
616 remove (&r
->get_time_axis_view());
621 Selection::remove (MidiRegionView
* mrv
)
623 MidiRegionSelection::iterator x
;
625 if ((x
= find (midi_regions
.begin(), midi_regions
.end(), mrv
)) != midi_regions
.end()) {
626 midi_regions
.erase (x
);
627 MidiRegionsChanged ();
631 /* XXX fix this up ? */
632 if (Config
->get_link_region_and_track_selection() && !regions
.involves (r
->get_time_axis_view())) {
633 remove (&r
->get_time_axis_view());
640 Selection::remove (uint32_t selection_id
)
646 for (list
<AudioRange
>::iterator i
= time
.begin(); i
!= time
.end(); ++i
) {
647 if ((*i
).id
== selection_id
) {
657 Selection::remove (framepos_t
/*start*/, framepos_t
/*end*/)
662 Selection::remove (boost::shared_ptr
<ARDOUR::AutomationList
> ac
)
664 AutomationSelection::iterator i
;
665 if ((i
= find (lines
.begin(), lines
.end(), ac
)) != lines
.end()) {
672 Selection::set (TimeAxisView
* track
)
679 Selection::set (const TrackViewList
& track_list
)
686 Selection::set (const MidiNoteSelection
& midi_list
)
693 Selection::set (boost::shared_ptr
<Playlist
> playlist
)
700 Selection::set (const list
<boost::shared_ptr
<Playlist
> >& pllist
)
707 Selection::set (const RegionSelection
& rs
)
711 RegionsChanged(); /* EMIT SIGNAL */
715 Selection::set (MidiRegionView
* mrv
)
717 clear_midi_regions ();
722 Selection::set (RegionView
* r
, bool also_clear_tracks
)
725 if (also_clear_tracks
) {
732 Selection::set (vector
<RegionView
*>& v
)
735 if (Config
->get_link_region_and_track_selection()) {
737 // make sure to deselect any automation selections
743 /** Set the start and end time of the time selection, without changing
744 * the list of tracks it applies to.
747 Selection::set (framepos_t start
, framepos_t end
)
749 if ((start
== 0 && end
== 0) || end
< start
) {
754 time
.push_back (AudioRange (start
, end
, next_time_id
++));
756 /* reuse the first entry, and remove all the rest */
758 while (time
.size() > 1) {
761 time
.front().start
= start
;
762 time
.front().end
= end
;
769 return time
.front().id
;
773 Selection::set (boost::shared_ptr
<Evoral::ControlList
> ac
)
780 Selection::selected (Marker
* m
)
782 return find (markers
.begin(), markers
.end(), m
) != markers
.end();
786 Selection::selected (TimeAxisView
* tv
)
788 return find (tracks
.begin(), tracks
.end(), tv
) != tracks
.end();
792 Selection::selected (RegionView
* rv
)
794 return find (regions
.begin(), regions
.end(), rv
) != regions
.end();
798 Selection::empty (bool internal_selection
)
800 bool object_level_empty
= regions
.empty () &&
803 playlists
.empty () &&
806 playlists
.empty () &&
811 if (!internal_selection
) {
812 return object_level_empty
;
815 /* this is intended to really only apply when using a Selection
819 return object_level_empty
&& midi_notes
.empty();
823 Selection::toggle (ControlPoint
* cp
)
825 cp
->set_selected (!cp
->get_selected ());
826 set_point_selection_from_line (cp
->line ());
830 Selection::toggle (vector
<ControlPoint
*> const & cps
)
832 for (vector
<ControlPoint
*>::const_iterator i
= cps
.begin(); i
!= cps
.end(); ++i
) {
833 (*i
)->set_selected (!(*i
)->get_selected ());
836 set_point_selection_from_line (cps
.front()->line ());
840 Selection::toggle (list
<Selectable
*> const & selectables
)
844 vector
<RegionView
*> rvs
;
845 vector
<ControlPoint
*> cps
;
847 for (std::list
<Selectable
*>::const_iterator i
= selectables
.begin(); i
!= selectables
.end(); ++i
) {
848 if ((rv
= dynamic_cast<RegionView
*> (*i
)) != 0) {
850 } else if ((cp
= dynamic_cast<ControlPoint
*> (*i
)) != 0) {
853 fatal
<< _("programming error: ")
854 << X_("unknown selectable type passed to Selection::toggle()")
870 Selection::set (list
<Selectable
*> const & selectables
)
875 if (Config
->get_link_region_and_track_selection ()) {
884 Selection::add (list
<Selectable
*> const & selectables
)
888 vector
<RegionView
*> rvs
;
889 vector
<ControlPoint
*> cps
;
891 for (std::list
<Selectable
*>::const_iterator i
= selectables
.begin(); i
!= selectables
.end(); ++i
) {
892 if ((rv
= dynamic_cast<RegionView
*> (*i
)) != 0) {
894 } else if ((cp
= dynamic_cast<ControlPoint
*> (*i
)) != 0) {
897 fatal
<< _("programming error: ")
898 << X_("unknown selectable type passed to Selection::add()")
914 Selection::clear_points ()
916 if (!points
.empty()) {
923 Selection::add (ControlPoint
* cp
)
925 cp
->set_selected (true);
926 set_point_selection_from_line (cp
->line ());
930 Selection::add (vector
<ControlPoint
*> const & cps
)
932 for (vector
<ControlPoint
*>::const_iterator i
= cps
.begin(); i
!= cps
.end(); ++i
) {
933 (*i
)->set_selected (true);
936 set_point_selection_from_line (cps
.front()->line ());
940 Selection::set (ControlPoint
* cp
)
942 if (cp
->get_selected()) {
946 /* We're going to set up the PointSelection from the selected ControlPoints
947 on this point's line, so we need to deselect all ControlPoints before
951 for (uint32_t i
= 0; i
< cp
->line().npoints(); ++i
) {
952 cp
->line().nth (i
)->set_selected (false);
955 vector
<ControlPoint
*> cps
;
961 Selection::set (Marker
* m
)
968 Selection::toggle (Marker
* m
)
970 MarkerSelection::iterator i
;
972 if ((i
= find (markers
.begin(), markers
.end(), m
)) == markers
.end()) {
980 Selection::remove (Marker
* m
)
982 MarkerSelection::iterator i
;
984 if ((i
= find (markers
.begin(), markers
.end(), m
)) != markers
.end()) {
991 Selection::add (Marker
* m
)
993 if (find (markers
.begin(), markers
.end(), m
) == markers
.end()) {
994 markers
.push_back (m
);
1000 Selection::add (const list
<Marker
*>& m
)
1002 markers
.insert (markers
.end(), m
.begin(), m
.end());
1007 MarkerSelection::range (framepos_t
& s
, framepos_t
& e
)
1012 for (MarkerSelection::iterator i
= begin(); i
!= end(); ++i
) {
1014 if ((*i
)->position() < s
) {
1015 s
= (*i
)->position();
1018 if ((*i
)->position() > e
) {
1019 e
= (*i
)->position();
1023 s
= std::min (s
, e
);
1024 e
= std::max (s
, e
);
1027 /** Automation control point selection is mostly manipulated using the selected state
1028 * of the ControlPoints themselves. For example, to add a point to a selection, its
1029 * ControlPoint is marked as selected and then this method is called. It sets up
1030 * our PointSelection from the selected ControlPoints of a given AutomationLine.
1032 * We can't use ControlPoints directly in the selection, as we need to express a
1033 * selection of not just a visible ControlPoint but also (possibly) some invisible
1034 * points nearby. Hence the selection stores AutomationRanges, and these are synced
1035 * with ControlPoint selection state using AutomationLine::set_selected_points.
1039 Selection::set_point_selection_from_line (AutomationLine
const & line
)
1043 AutomationRange
current (DBL_MAX
, 0, 1, 0, &line
.trackview
);
1045 for (uint32_t i
= 0; i
< line
.npoints(); ++i
) {
1046 ControlPoint
const * cp
= line
.nth (i
);
1048 if (cp
->get_selected()) {
1049 /* x and y position of this control point in coordinates suitable for
1050 an AutomationRange (ie model time and fraction of track height)
1052 double const x
= (*(cp
->model()))->when
;
1053 double const y
= 1 - (cp
->get_y() / line
.trackview
.current_height ());
1055 /* work out the position of a rectangle the size of a control point centred
1059 double const size
= cp
->size ();
1060 double const x_size
= line
.time_converter().from (line
.trackview
.editor().pixel_to_frame (size
));
1061 double const y_size
= size
/ line
.trackview
.current_height ();
1063 double const x1
= x
- x_size
/ 2;
1064 double const x2
= x
+ x_size
/ 2;
1065 double const y1
= y
- y_size
/ 2;
1066 double const y2
= y
+ y_size
/ 2;
1068 /* extend the current AutomationRange to put this point in */
1069 current
.start
= min (current
.start
, x1
);
1070 current
.end
= max (current
.end
, x2
);
1071 current
.low_fract
= min (current
.low_fract
, y1
);
1072 current
.high_fract
= max (current
.high_fract
, y2
);
1075 /* this point isn't selected; if the current AutomationRange has some
1076 stuff in it, push it onto the list and make a new one
1078 if (current
.start
< DBL_MAX
) {
1079 points
.push_back (current
);
1080 current
= AutomationRange (DBL_MAX
, 0, 1, 0, &line
.trackview
);
1085 /* Maybe push the current AutomationRange, as above */
1086 if (current
.start
< DBL_MAX
) {
1087 points
.push_back (current
);
1088 current
= AutomationRange (DBL_MAX
, 0, 1, 0, &line
.trackview
);
1091 PointsChanged (); /* EMIT SIGNAL */
1095 Selection::get_state () const
1097 /* XXX: not complete; just sufficient to get track selection state
1098 so that re-opening plugin windows for editor mixer strips works
1101 XMLNode
* node
= new XMLNode (X_("Selection"));
1103 for (TrackSelection::const_iterator i
= tracks
.begin(); i
!= tracks
.end(); ++i
) {
1104 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (*i
);
1105 AutomationTimeAxisView
* atv
= dynamic_cast<AutomationTimeAxisView
*> (*i
);
1107 XMLNode
* t
= node
->add_child (X_("RouteView"));
1108 t
->add_property (X_("id"), atoi (rtv
->route()->id().to_s().c_str()));
1110 XMLNode
* t
= node
->add_child (X_("AutomationView"));
1111 t
->add_property (X_("id"), atoi (atv
->parent_route()->id().to_s().c_str()));
1112 t
->add_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv
->parameter ()));
1120 Selection::set_state (XMLNode
const & node
, int)
1122 if (node
.name() != X_("Selection")) {
1126 XMLNodeList children
= node
.children ();
1127 for (XMLNodeList::const_iterator i
= children
.begin(); i
!= children
.end(); ++i
) {
1128 if ((*i
)->name() == X_("RouteView")) {
1130 XMLProperty
* prop_id
= (*i
)->property (X_("id"));
1132 PBD::ID
id (prop_id
->value ());
1133 RouteTimeAxisView
* rtv
= editor
->get_route_view_by_route_id (id
);
1138 } else if ((*i
)->name() == X_("AutomationView")) {
1140 XMLProperty
* prop_id
= (*i
)->property (X_("id"));
1141 XMLProperty
* prop_parameter
= (*i
)->property (X_("parameter"));
1144 assert (prop_parameter
);
1146 PBD::ID
id (prop_id
->value ());
1147 RouteTimeAxisView
* rtv
= editor
->get_route_view_by_route_id (id
);
1150 boost::shared_ptr
<AutomationTimeAxisView
> atv
= rtv
->automation_child (EventTypeMap::instance().new_parameter (prop_parameter
->value ()));
1152 /* the automation could be for an entity that was never saved
1153 in the session file. Don't freak out if we can't find
1168 Selection::remove_regions (TimeAxisView
* t
)
1170 RegionSelection::iterator i
= regions
.begin();
1171 while (i
!= regions
.end ()) {
1172 RegionSelection::iterator tmp
= i
;
1175 if (&(*i
)->get_time_axis_view() == t
) {