+
+XMLNode&
+Selection::get_state () const
+{
+ /* XXX: not complete; just sufficient to get track selection state
+ so that re-opening plugin windows for editor mixer strips works
+ */
+
+ XMLNode* node = new XMLNode (X_("Selection"));
+
+ for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
+ AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
+ if (rtv) {
+ XMLNode* t = node->add_child (X_("RouteView"));
+ t->set_property (X_("id"), rtv->route()->id ());
+ } else if (atv) {
+ XMLNode* t = node->add_child (X_("AutomationView"));
+ t->set_property (X_("id"), atv->parent_route()->id ());
+ t->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
+ }
+ }
+
+ for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ XMLNode* r = node->add_child (X_("Region"));
+ r->set_property (X_("id"), (*i)->region ()->id ());
+ }
+
+ /* midi region views have thir own internal selection. */
+ list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > > rid_notes;
+ editor->get_per_region_note_selection (rid_notes);
+
+ 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) {
+ XMLNode* n = node->add_child (X_("MIDINotes"));
+ n->set_property (X_("region-id"), (*rn_it).first);
+
+ 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"));
+ nc->set_property(X_("note-id"), (*i)->id());
+ }
+ }
+
+ 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->set_property (X_("type"), "track");
+ r->set_property (X_("route-id"), atv->parent_route()->id ());
+ r->set_property (X_("automation-list-id"), (*i)->line().the_list()->id ());
+ r->set_property (X_("parameter"), EventTypeMap::instance().to_symbol ((*i)->line().the_list()->parameter ()));
+ r->set_property (X_("view-index"), (*i)->view_index());
+ continue;
+ }
+
+ AudioRegionGainLine* argl = dynamic_cast<AudioRegionGainLine*> (&(*i)->line());
+ if (argl) {
+ XMLNode* r = node->add_child (X_("ControlPoint"));
+ r->set_property (X_("type"), "region");
+ r->set_property (X_("region-id"), argl->region_view ().region ()->id ());
+ r->set_property (X_("view-index"), (*i)->view_index());
+ }
+
+ }
+
+ for (TimeSelection::const_iterator i = time.begin(); i != time.end(); ++i) {
+ XMLNode* t = node->add_child (X_("AudioRange"));
+ t->set_property (X_("start"), (*i).start);
+ t->set_property (X_("end"), (*i).end);
+ }
+
+ for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
+ XMLNode* t = node->add_child (X_("Marker"));
+
+ bool is_start;
+ Location* loc = editor->find_location_from_marker (*i, is_start);
+
+ t->set_property (X_("id"), loc->id());
+ t->set_property (X_("start"), is_start);
+ }
+
+ return *node;
+}
+
+int
+Selection::set_state (XMLNode const & node, int)
+{
+ if (node.name() != X_("Selection")) {
+ return -1;
+ }
+
+ clear_regions ();
+ clear_midi_notes ();
+ clear_points ();
+ clear_time ();
+ clear_markers ();
+
+ RegionSelection selected_regions;
+
+ /* NOTE: stripable/time-axis-view selection is saved/restored by
+ * ARDOUR::CoreSelection, not this Selection object
+ */
+
+ PBD::ID id;
+ XMLNodeList children = node.children ();
+
+ for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
+ if ((*i)->name() == X_("Region")) {
+
+ if (!(*i)->get_property (X_("id"), id)) {
+ assert(false);
+ }
+
+ RegionSelection rs;
+ editor->get_regionviews_by_id (id, rs);
+
+ if (!rs.empty ()) {
+ selected_regions.insert (selected_regions.end(), rs.begin(), rs.end());
+ } 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_("MIDINotes")) {
+
+ if (!(*i)->get_property (X_("region-id"), id)) {
+ assert (false);
+ }
+
+ RegionSelection rs;
+
+ editor->get_regionviews_by_id (id, rs); // there could be more than one
+
+ std::list<Evoral::event_id_t> notes;
+ XMLNodeList children = (*i)->children ();
+
+ for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
+ Evoral::event_id_t id;
+ if ((*ci)->get_property (X_ ("note-id"), id)) {
+ notes.push_back (id);
+ }
+ }
+
+ 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") {
+
+ PBD::ID route_id;
+ PBD::ID alist_id;
+ std::string param;
+ uint32_t view_index;
+
+ if (!(*i)->get_property (X_("route-id"), route_id) ||
+ !(*i)->get_property (X_("automation-list-id"), alist_id) ||
+ !(*i)->get_property (X_("parameter"), param) ||
+ !(*i)->get_property (X_("view-index"), view_index)) {
+ assert(false);
+ }
+
+ 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 (param));
+ 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() == alist_id) {
+ ControlPoint* cp = (*li)->nth(view_index);
+ if (cp) {
+ cps.push_back (cp);
+ cp->show();
+ }
+ }
+ }
+ }
+ }
+ if (!cps.empty()) {
+ add (cps);
+ }
+ } else if (prop_type->value () == "region") {
+
+ PBD::ID region_id;
+ uint32_t view_index;
+ if (!(*i)->get_property (X_("region-id"), region_id) ||
+ !(*i)->get_property (X_("view-index"), view_index)) {
+ continue;
+ }
+
+ 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(view_index);
+ if (cp) {
+ cps.push_back (cp);
+ cp->show();
+ }
+ }
+ }
+ if (!cps.empty()) {
+ add (cps);
+ }
+ }
+ }
+
+ } else if ((*i)->name() == X_("AudioRange")) {
+ framepos_t start;
+ framepos_t end;
+
+ if (!(*i)->get_property (X_("start"), start) || !(*i)->get_property (X_("end"), end)) {
+ assert(false);
+ }
+ set_preserving_all_ranges (start, end);
+
+ } else if ((*i)->name() == X_("AutomationView")) {
+
+ std::string param;
+
+ if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("parameter"), param)) {
+ assert (false);
+ }
+
+ RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
+
+ if (rtv) {
+ boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().from_symbol (param));
+
+ /* the automation could be for an entity that was never saved
+ in the session file. Don't freak out if we can't find
+ it.
+ */
+
+ if (atv) {
+ add (atv.get());
+ }
+ }
+
+ } else if ((*i)->name() == X_("Marker")) {
+
+ bool is_start;
+ if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("start"), is_start)) {
+ assert(false);
+ }
+
+ ArdourMarker* m = editor->find_marker_from_location_id (id, is_start);
+ if (m) {
+ add (m);
+ }
+
+ }
+
+ }
+
+ // now add regions to selection at once
+ add (selected_regions);
+
+ return 0;
+}
+
+void
+Selection::remove_regions (TimeAxisView* t)
+{
+ RegionSelection::iterator i = regions.begin();
+ while (i != regions.end ()) {
+ RegionSelection::iterator tmp = i;
+ ++tmp;
+
+ if (&(*i)->get_time_axis_view() == t) {
+ remove (*i);
+ }
+
+ i = tmp;
+ }
+}
+
+/* TIME AXIS VIEW ... proxy for Stripable/Controllable
+ *
+ * public methods just modify the CoreSelection; PresentationInfo::Changed will
+ * trigger Selection::core_selection_changed() and we will update our own data
+ * structures there.
+ */
+
+void
+Selection::toggle (const TrackViewList& track_list)
+{
+ TrackViewList t = add_grouped_tracks (track_list);
+
+ CoreSelection& selection (editor->session()->selection());
+ PresentationInfo::ChangeSuspender cs;
+
+ for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
+ boost::shared_ptr<Stripable> s = (*i)->stripable ();
+ boost::shared_ptr<AutomationControl> c = (*i)->control ();
+ selection.toggle (s, c);
+ }
+}
+
+void
+Selection::toggle (TimeAxisView* track)
+{
+ if (dynamic_cast<VCATimeAxisView*> (track)) {
+ return;
+ }
+
+ TrackViewList tr;
+ tr.push_back (track);
+ toggle (tr);
+}
+
+void
+Selection::add (TrackViewList const & track_list)
+{
+ TrackViewList t = add_grouped_tracks (track_list);
+
+ CoreSelection& selection (editor->session()->selection());
+ PresentationInfo::ChangeSuspender cs;
+
+ for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
+ boost::shared_ptr<Stripable> s = (*i)->stripable ();
+ boost::shared_ptr<AutomationControl> c = (*i)->control ();
+ selection.add (s, c);
+ }
+}
+
+void
+Selection::add (TimeAxisView* track)
+{
+ if (dynamic_cast<VCATimeAxisView*> (track)) {
+ return;
+ }
+
+ TrackViewList tr;
+ tr.push_back (track);
+ add (tr);
+}
+
+void
+Selection::remove (TimeAxisView* track)
+{
+ if (dynamic_cast<VCATimeAxisView*> (track)) {
+ return;
+ }
+
+ TrackViewList tvl;
+ tvl.push_back (track);
+ remove (tvl);
+}
+
+void
+Selection::remove (const TrackViewList& t)
+{
+ CoreSelection& selection (editor->session()->selection());
+ PresentationInfo::ChangeSuspender cs;
+
+ for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
+ boost::shared_ptr<Stripable> s = (*i)->stripable ();
+ boost::shared_ptr<AutomationControl> c = (*i)->control ();
+ selection.remove (s, c);
+ }
+}
+
+void
+Selection::set (TimeAxisView* track)
+{
+ if (dynamic_cast<VCATimeAxisView*> (track)) {
+ return;
+ }
+
+ TrackViewList tvl;
+ tvl.push_back (track);
+ set (tvl);
+}
+
+void
+Selection::set (const TrackViewList& track_list)
+{
+ TrackViewList t = add_grouped_tracks (track_list);
+
+ CoreSelection& selection (editor->session()->selection());
+ PresentationInfo::ChangeSuspender cs;
+
+ selection.clear_stripables ();
+
+ for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
+ boost::shared_ptr<Stripable> s = (*i)->stripable ();
+ boost::shared_ptr<AutomationControl> c = (*i)->control ();
+ selection.add (s, c);
+ }
+}
+
+void
+Selection::clear_tracks (bool)
+{
+ Session* s = editor->session();
+ if (s) {
+ CoreSelection& selection (s->selection());
+ selection.clear_stripables ();
+ }
+}
+
+bool
+Selection::selected (TimeAxisView* tv) const
+{
+ Session* session = editor->session();
+
+ if (!session) {
+ return false;
+ }
+
+ CoreSelection& selection (session->selection());
+ boost::shared_ptr<Stripable> s = tv->stripable ();
+ boost::shared_ptr<AutomationControl> c = tv->control ();
+
+ if (c) {
+ return selection.selected (c);
+ }
+
+ return selection.selected (s);
+}
+
+TrackViewList
+Selection::add_grouped_tracks (TrackViewList const & t)
+{
+ TrackViewList added;
+
+ for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
+ if (dynamic_cast<VCATimeAxisView*> (*i)) {
+ continue;
+ }
+
+ /* select anything in the same select-enabled route group */
+ ARDOUR::RouteGroup* rg = (*i)->route_group ();
+
+ if (rg && rg->is_active() && rg->is_select ()) {
+
+ TrackViewList tr = editor->axis_views_from_routes (rg->route_list ());
+
+ for (TrackViewList::iterator j = tr.begin(); j != tr.end(); ++j) {
+
+ /* Do not add the trackview passed in as an
+ * argument, because we want that to be on the
+ * end of the list.
+ */
+
+ if (*j != *i) {
+ if (!added.contains (*j)) {
+ added.push_back (*j);
+ }
+ }
+ }
+ }
+ }
+
+ /* now add the the trackview's passed in as actual arguments */
+ added.insert (added.end(), t.begin(), t.end());
+
+ return added;
+}
+
+#if 0
+static void dump_tracks (Selection const & s)
+{
+ cerr << "--TRACKS [" << s.tracks.size() << ']' << ":\n";
+ for (TrackViewList::const_iterator x = s.tracks.begin(); x != s.tracks.end(); ++x) {
+ cerr << (*x)->name() << ' ' << (*x)->stripable() << " C = " << (*x)->control() << endl;
+ }
+ cerr << "///\n";
+}
+#endif
+
+void
+Selection::core_selection_changed (PropertyChange const & what_changed)
+{
+ PropertyChange pc;
+
+ pc.add (Properties::selected);
+
+ if (!what_changed.contains (pc)) {
+ return;
+ }
+
+ CoreSelection& selection (editor->session()->selection());
+
+ if (selection.selected()) {
+ clear_objects(); // enforce object/range exclusivity
+ }
+
+ tracks.clear (); // clear stage for whatever tracks are now selected (maybe none)
+
+ CoreSelection::StripableAutomationControls sac;
+ selection.get_stripables (sac);
+
+ for (CoreSelection::StripableAutomationControls::const_iterator i = sac.begin(); i != sac.end(); ++i) {
+ AxisView* av;
+ TimeAxisView* tav;
+ if ((*i).controllable) {
+ av = editor->axis_view_by_control ((*i).controllable);
+ } else {
+ av = editor->axis_view_by_stripable ((*i).stripable);
+ }
+
+ tav = dynamic_cast<TimeAxisView*>(av);
+ if (tav) {
+ tracks.push_back (tav);
+ }
+ }
+}