#include "ardour/playlist.h"
#include "ardour/rc_configuration.h"
+#include "control_protocol/control_protocol.h"
+
+#include "audio_region_view.h"
+#include "debug.h"
#include "gui_thread.h"
#include "midi_cut_buffer.h"
+#include "region_gain_line.h"
#include "region_view.h"
#include "selection.h"
#include "selection_templates.h"
using namespace PBD;
struct AudioRangeComparator {
- bool operator()(AudioRange a, AudioRange b) {
- return a.start < b.start;
- }
+ bool operator()(AudioRange a, AudioRange b) {
+ return a.start < b.start;
+ }
};
Selection::Selection (const PublicEditor* e)
void (Selection::*track_remove)(TimeAxisView*) = &Selection::remove;
TimeAxisView::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (track_remove, this, _1), gui_context());
- void (Selection::*marker_remove)(Marker*) = &Selection::remove;
- Marker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
+ void (Selection::*marker_remove)(ArdourMarker*) = &Selection::remove;
+ ArdourMarker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
clear_midi_notes ();
clear_midi_regions ();
clear_markers ();
+ pending_midi_note_selection.clear();
}
void
-Selection::clear_objects ()
+Selection::clear_objects (bool with_signal)
{
- clear_regions ();
- clear_points ();
- clear_lines();
- clear_playlists ();
- clear_midi_notes ();
- clear_midi_regions ();
+ clear_regions (with_signal);
+ clear_points (with_signal);
+ clear_lines(with_signal);
+ clear_playlists (with_signal);
+ clear_midi_notes (with_signal);
+ clear_midi_regions (with_signal);
}
void
-Selection::clear_tracks ()
+Selection::clear_tracks (bool with_signal)
{
if (!tracks.empty()) {
for (TrackViewList::iterator x = tracks.begin(); x != tracks.end(); ++x) {
(*x)->set_selected (false);
}
tracks.clear ();
- if (!_no_tracks_changed) {
+ if (!_no_tracks_changed && with_signal) {
TracksChanged();
}
}
}
void
-Selection::clear_time ()
+Selection::clear_time (bool with_signal)
{
time.clear();
-
- TimeChanged ();
+ if (with_signal) {
+ TimeChanged ();
+ }
}
void
void
-Selection::clear_regions ()
+Selection::clear_regions (bool with_signal)
{
if (!regions.empty()) {
regions.clear_all ();
- RegionsChanged();
+ if (with_signal) {
+ RegionsChanged();
+ }
}
}
void
-Selection::clear_midi_notes ()
+Selection::clear_midi_notes (bool with_signal)
{
if (!midi_notes.empty()) {
for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
delete *x;
}
midi_notes.clear ();
- MidiNotesChanged ();
+ if (with_signal) {
+ MidiNotesChanged ();
+ }
+ }
+
+ // clear note selections for MRV's that have note selections
+ // this will cause the MRV to be removed from the list
+ for (MidiRegionSelection::iterator i = midi_regions.begin();
+ i != midi_regions.end();) {
+ MidiRegionSelection::iterator tmp = i;
+ ++tmp;
+ MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
+ if (mrv) {
+ mrv->clear_selection();
+ }
+ i = tmp;
}
}
void
-Selection::clear_midi_regions ()
+Selection::clear_midi_regions (bool with_signal)
{
if (!midi_regions.empty()) {
midi_regions.clear ();
- MidiRegionsChanged ();
+ if (with_signal) {
+ MidiRegionsChanged ();
+ }
}
}
void
-Selection::clear_playlists ()
+Selection::clear_playlists (bool with_signal)
{
/* Selections own their playlists */
if (!playlists.empty()) {
playlists.clear ();
- PlaylistsChanged();
+ if (with_signal) {
+ PlaylistsChanged();
+ }
}
}
void
-Selection::clear_lines ()
+Selection::clear_lines (bool with_signal)
{
if (!lines.empty()) {
lines.clear ();
- LinesChanged();
+ if (with_signal) {
+ LinesChanged();
+ }
}
}
void
-Selection::clear_markers ()
+Selection::clear_markers (bool with_signal)
{
if (!markers.empty()) {
markers.clear ();
- MarkersChanged();
+ if (with_signal) {
+ MarkersChanged();
+ }
}
}
{
clear_time(); //enforce object/range exclusivity
clear_tracks(); //enforce object/track exclusivity
-
+
PlaylistSelection::iterator i;
if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
{
clear_time(); //enforce object/range exclusivity
clear_tracks(); //enforce object/track exclusivity
-
+
RegionSelection::iterator i;
if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
{
clear_time(); //enforce object/range exclusivity
clear_tracks(); //enforce object/track exclusivity
-
+
if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
pl->use ();
playlists.push_back(pl);
{
clear_time(); //enforce object/range exclusivity
clear_tracks(); //enforce object/track exclusivity
-
+
bool changed = false;
for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
if (find (regions.begin(), regions.end(), r) == regions.end()) {
bool changed = regions.add (r);
- if (changed) {
- RegionsChanged ();
- }
+ if (changed) {
+ RegionsChanged ();
+ }
}
}
void
Selection::add (MidiRegionView* mrv)
{
+ DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::add MRV %1\n", mrv));
+
clear_time(); //enforce object/range exclusivity
clear_tracks(); //enforce object/track exclusivity
Selection::replace (uint32_t sid, framepos_t start, framepos_t end)
{
clear_objects(); //enforce object/range exclusivity
-
+
for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
if ((*i).id == sid) {
time.erase (i);
warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
return;
}
- if (find (lines.begin(), lines.end(), al) == lines.end()) {
- lines.push_back (al);
- LinesChanged();
- }
+
+ /* The original may change so we must store a copy (not a pointer) here.
+ * e.g AutomationLine rewrites the list with gain mapping.
+ * the downside is that we can't perfom duplicate checks.
+ * This code was changed in response to #6842
+ */
+ lines.push_back (boost::shared_ptr<ARDOUR::AutomationList> (new ARDOUR::AutomationList(*al)));
+ LinesChanged();
}
void
if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) {
track->set_selected (false);
tracks.erase (i);
+
if (!_no_tracks_changed) {
TracksChanged();
}
void
Selection::remove (MidiRegionView* mrv)
{
+ DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::remove MRV %1\n", mrv));
+
MidiRegionSelection::iterator x;
if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
}
}
-
void
Selection::remove (uint32_t selection_id)
{
void
Selection::set (TimeAxisView* track)
{
- clear_objects(); //enforce object/range exclusivity
- clear_tracks ();
+ clear_objects (); //enforce object/range exclusivity
+ clear_tracks (false);
add (track);
}
Selection::set (const TrackViewList& track_list)
{
clear_objects(); //enforce object/range exclusivity
- clear_tracks ();
+ clear_tracks (false);
add (track_list);
}
clear_time(); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
clear_objects();
-
+
add (ac);
}
bool
-Selection::selected (Marker* m)
+Selection::selected (ArdourMarker* m)
{
return find (markers.begin(), markers.end(), m) != markers.end();
}
bool
Selection::selected (TimeAxisView* tv)
{
- return tv->get_selected ();
+ return tv->selected ();
}
bool
as a cut buffer.
*/
- return object_level_empty && midi_notes.empty();
+ return object_level_empty && midi_notes.empty() && points.empty();
}
void
clear_time(); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
- cp->set_selected (!cp->get_selected ());
+ cp->set_selected (!cp->selected ());
PointSelection::iterator i = find (points.begin(), points.end(), cp);
if (i == points.end()) {
points.push_back (cp);
{
clear_time(); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
-
+
RegionView* rv;
ControlPoint* cp;
vector<RegionView*> rvs;
fatal << _("programming error: ")
<< X_("unknown selectable type passed to Selection::toggle()")
<< endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
}
fatal << _("programming error: ")
<< X_("unknown selectable type passed to Selection::add()")
<< endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
}
}
void
-Selection::clear_points ()
+Selection::clear_points (bool with_signal)
{
if (!points.empty()) {
points.clear ();
- PointsChanged ();
+ if (with_signal) {
+ PointsChanged ();
+ }
}
}
clear_time (); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
- if (cp->get_selected()) {
+ if (cp->selected () && points.size () == 1) {
return;
}
}
void
-Selection::set (Marker* m)
+Selection::set (ArdourMarker* m)
{
clear_time (); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
}
void
-Selection::toggle (Marker* m)
+Selection::toggle (ArdourMarker* m)
{
MarkerSelection::iterator i;
}
void
-Selection::remove (Marker* m)
+Selection::remove (ArdourMarker* m)
{
MarkerSelection::iterator i;
}
void
-Selection::add (Marker* m)
+Selection::add (ArdourMarker* m)
{
clear_time (); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
}
void
-Selection::add (const list<Marker*>& m)
+Selection::add (const list<ArdourMarker*>& m)
{
clear_time (); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
markers.insert (markers.end(), m.begin(), m.end());
markers.sort ();
markers.unique ();
-
+
MarkersChanged ();
}
so that re-opening plugin windows for editor mixer strips works
*/
+ char buf[32];
XMLNode* node = new XMLNode (X_("Selection"));
for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
}
}
+ for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ XMLNode* r = node->add_child (X_("Region"));
+ r->add_property (X_("id"), atoi ((*i)->region ()->id ().to_s ().c_str()));
+ }
+
+ /* midi region views have thir own internal selection. */
+ XMLNode* n = NULL;
+ list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > > rid_notes;
+ editor->get_per_region_note_selection (rid_notes);
+ if (!rid_notes.empty()) {
+ n = node->add_child (X_("MIDINote"));
+ }
+ list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > >::iterator rn_it;
+ for (rn_it = rid_notes.begin(); rn_it != rid_notes.end(); ++rn_it) {
+ assert(n); // hint for clang static analysis
+ n->add_property (X_("region_id"), atoi((*rn_it).first.to_s().c_str()));
+
+ for (std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > >::iterator i = (*rn_it).second.begin(); i != (*rn_it).second.end(); ++i) {
+ XMLNode* nc = n->add_child(X_("note"));
+ snprintf(buf, sizeof(buf), "%d", (*i)->channel());
+ nc->add_property(X_("channel"), string(buf));
+
+ snprintf(buf, sizeof(buf), "%f", (*i)->time().to_double());
+ nc->add_property(X_("time"), string(buf));
+
+ snprintf(buf, sizeof(buf), "%d", (*i)->note());
+ nc->add_property(X_("note"), string(buf));
+
+ snprintf(buf, sizeof(buf), "%f", (*i)->length().to_double());
+ nc->add_property(X_("length"), string(buf));
+
+ snprintf(buf, sizeof(buf), "%d", (*i)->velocity());
+ nc->add_property(X_("velocity"), string(buf));
+
+ snprintf(buf, sizeof(buf), "%d", (*i)->off_velocity());
+ nc->add_property(X_("off-velocity"), string(buf));
+ }
+ }
+
+ for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) {
+ AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (&(*i)->line().trackview);
+ if (atv) {
+
+ XMLNode* r = node->add_child (X_("ControlPoint"));
+ r->add_property (X_("type"), "track");
+ r->add_property (X_("route-id"), atoi (atv->parent_route()->id ().to_s ().c_str()));
+ r->add_property (X_("automation-list-id"), atoi ((*i)->line().the_list()->id ().to_s ().c_str()));
+ r->add_property (X_("parameter"), EventTypeMap::instance().to_symbol ((*i)->line().the_list()->parameter ()));
+
+ snprintf(buf, sizeof(buf), "%d", (*i)->view_index());
+ r->add_property (X_("view-index"), string(buf));
+ continue;
+ }
+
+ AudioRegionGainLine* argl = dynamic_cast<AudioRegionGainLine*> (&(*i)->line());
+ if (argl) {
+ XMLNode* r = node->add_child (X_("ControlPoint"));
+ r->add_property (X_("type"), "region");
+ r->add_property (X_("region-id"), atoi (argl->region_view ().region ()->id ().to_s ().c_str()));
+ snprintf(buf, sizeof(buf), "%d", (*i)->view_index());
+ r->add_property (X_("view-index"), string(buf));
+ }
+
+ }
+
+ for (TimeSelection::const_iterator i = time.begin(); i != time.end(); ++i) {
+ XMLNode* t = node->add_child (X_("AudioRange"));
+ snprintf(buf, sizeof(buf), "%" PRId64, (*i).start);
+ t->add_property (X_("start"), string(buf));
+ snprintf(buf, sizeof(buf), "%" PRId64, (*i).end);
+ t->add_property (X_("end"), string(buf));
+ }
+
for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
XMLNode* t = node->add_child (X_("Marker"));
return -1;
}
+ clear_regions ();
+ clear_midi_notes ();
+ clear_points ();
+ clear_time ();
+ clear_tracks ();
+ clear_markers ();
+
XMLNodeList children = node.children ();
for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
if ((*i)->name() == X_("RouteView")) {
- XMLProperty* prop_id = (*i)->property (X_("id"));
+ XMLProperty const * prop_id = (*i)->property (X_("id"));
assert (prop_id);
PBD::ID id (prop_id->value ());
RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
add (rtv);
}
+ } else if ((*i)->name() == X_("Region")) {
+ XMLProperty const * prop_id = (*i)->property (X_("id"));
+ assert (prop_id);
+ PBD::ID id (prop_id->value ());
+
+ RegionSelection rs;
+ editor->get_regionviews_by_id (id, rs);
+
+ if (!rs.empty ()) {
+ add (rs);
+ } else {
+ /*
+ regionviews haven't been constructed - stash the region IDs
+ so we can identify them in Editor::region_view_added ()
+ */
+ regions.pending.push_back (id);
+ }
+
+ } else if ((*i)->name() == X_("MIDINote")) {
+ XMLProperty const * prop_region_id = (*i)->property (X_("region-id"));
+
+ assert (prop_region_id);
+
+ PBD::ID const id (prop_region_id->value ());
+ RegionSelection rs;
+
+ editor->get_regionviews_by_id (id, rs); // there could be more than one
+
+ std::list<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > notes;
+ XMLNodeList children = (*i)->children ();
+
+ for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
+ XMLProperty const * prop_channel = (*ci)->property (X_("channel"));
+ XMLProperty const * prop_time = (*ci)->property (X_("time"));
+ XMLProperty const * prop_note = (*ci)->property (X_("note"));
+ XMLProperty const * prop_length = (*ci)->property (X_("length"));
+ XMLProperty const * prop_velocity = (*ci)->property (X_("velocity"));
+ XMLProperty const * prop_off_velocity = (*ci)->property (X_("off-velocity"));
+
+ assert (prop_channel);
+ assert (prop_time);
+ assert (prop_note);
+ assert (prop_length);
+ assert (prop_velocity);
+ assert (prop_off_velocity);
+
+ uint8_t channel = atoi(prop_channel->value());
+ Evoral::Beats time (atof(prop_time->value()));
+ Evoral::Beats length (atof(prop_length->value()));
+ uint8_t note = atoi(prop_note->value());
+ uint8_t velocity = atoi(prop_velocity->value());
+ uint8_t off_velocity = atoi(prop_off_velocity->value());
+ boost::shared_ptr<Evoral::Note<Evoral::Beats> > the_note
+ (new Evoral::Note<Evoral::Beats> (channel, time, length, note, velocity));
+ the_note->set_off_velocity (off_velocity);
+
+ notes.push_back (the_note);
+ }
+
+ for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
+ MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*rsi);
+ if (mrv) {
+ mrv->select_notes(notes);
+ }
+ }
+
+ if (rs.empty()) {
+ /* regionviews containing these notes don't yet exist on the canvas.*/
+ pending_midi_note_selection.push_back (make_pair (id, notes));
+ }
+
+ } else if ((*i)->name() == X_("ControlPoint")) {
+ XMLProperty const * prop_type = (*i)->property (X_("type"));
+
+ assert(prop_type);
+
+ if (prop_type->value () == "track") {
+
+ XMLProperty const * prop_route_id = (*i)->property (X_("route-id"));
+ XMLProperty const * prop_alist_id = (*i)->property (X_("automation-list-id"));
+ XMLProperty const * prop_parameter = (*i)->property (X_("parameter"));
+ XMLProperty const * prop_view_index = (*i)->property (X_("view-index"));
+
+ assert (prop_route_id);
+ assert (prop_alist_id);
+ assert (prop_parameter);
+ assert (prop_view_index);
+
+ PBD::ID route_id (prop_route_id->value ());
+ RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (route_id);
+ vector <ControlPoint *> cps;
+
+ if (rtv) {
+ boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().from_symbol (prop_parameter->value ()));
+ if (atv) {
+ list<boost::shared_ptr<AutomationLine> > lines = atv->lines();
+ for (list<boost::shared_ptr<AutomationLine> > ::iterator li = lines.begin(); li != lines.end(); ++li) {
+ if ((*li)->the_list()->id() == prop_alist_id->value()) {
+ ControlPoint* cp = (*li)->nth(atol(prop_view_index->value().c_str()));
+ if (cp) {
+ cps.push_back (cp);
+ cp->show();
+ }
+ }
+ }
+ }
+ }
+ if (!cps.empty()) {
+ add (cps);
+ }
+ } else if (prop_type->value () == "region") {
+ XMLProperty const * prop_region_id = (*i)->property (X_("region-id"));
+ XMLProperty const * prop_view_index = (*i)->property (X_("view-index"));
+
+ if (!prop_region_id || !prop_view_index) {
+ continue;
+ }
+
+ PBD::ID region_id (prop_region_id->value ());
+ RegionSelection rs;
+ editor->get_regionviews_by_id (region_id, rs);
+
+ if (!rs.empty ()) {
+ vector <ControlPoint *> cps;
+ for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
+ AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*rsi);
+ if (arv) {
+ boost::shared_ptr<AudioRegionGainLine> gl = arv->get_gain_line ();
+ ControlPoint* cp = gl->nth(atol(prop_view_index->value().c_str()));
+ if (cp) {
+ cps.push_back (cp);
+ cp->show();
+ }
+ }
+ }
+ if (!cps.empty()) {
+ add (cps);
+ }
+ }
+ }
+
+ } else if ((*i)->name() == X_("AudioRange")) {
+ XMLProperty const * prop_start = (*i)->property (X_("start"));
+ XMLProperty const * prop_end = (*i)->property (X_("end"));
+
+ assert (prop_start);
+ assert (prop_end);
+
+ framepos_t s (atol (prop_start->value ().c_str()));
+ framepos_t e (atol (prop_end->value ().c_str()));
+
+ set_preserving_all_ranges (s, e);
+
} else if ((*i)->name() == X_("AutomationView")) {
- XMLProperty* prop_id = (*i)->property (X_("id"));
- XMLProperty* prop_parameter = (*i)->property (X_("parameter"));
+ XMLProperty const * prop_id = (*i)->property (X_("id"));
+ XMLProperty const * prop_parameter = (*i)->property (X_("parameter"));
assert (prop_id);
assert (prop_parameter);
RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
if (rtv) {
- boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().new_parameter (prop_parameter->value ()));
+ boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().from_symbol (prop_parameter->value ()));
/* the automation could be for an entity that was never saved
in the session file. Don't freak out if we can't find
} else if ((*i)->name() == X_("Marker")) {
- XMLProperty* prop_id = (*i)->property (X_("id"));
- XMLProperty* prop_start = (*i)->property (X_("start"));
+ XMLProperty const * prop_id = (*i)->property (X_("id"));
+ XMLProperty const * prop_start = (*i)->property (X_("start"));
assert (prop_id);
assert (prop_start);
PBD::ID id (prop_id->value ());
- Marker* m = editor->find_marker_from_location_id (id, string_is_affirmative (prop_start->value ()));
+ ArdourMarker* m = editor->find_marker_from_location_id (id, string_is_affirmative (prop_start->value ()));
if (m) {
add (m);
}
-
+
}
-
+
}
return 0;