1 /*
2  * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
3  * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
4  * Copyright (C) 2006-2017 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
6  * Copyright (C) 2014-2017 Nick Mainsbridge <mainsbridge@gmail.com>
7  * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
8  * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
9  * Copyright (C) 2015-2017 Tim Mayberry <mojofunk@gmail.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 #include <algorithm>
27 #include <sigc++/bind.h>
28 
29 #include "pbd/error.h"
30 #include "pbd/types_convert.h"
31 
32 #include "ardour/evoral_types_convert.h"
33 #include "ardour/playlist.h"
34 #include "ardour/rc_configuration.h"
35 #include "ardour/selection.h"
36 
37 #include "control_protocol/control_protocol.h"
38 
39 #include "audio_region_view.h"
40 #include "debug.h"
41 #include "gui_thread.h"
42 #include "midi_cut_buffer.h"
43 #include "region_gain_line.h"
44 #include "region_view.h"
45 #include "selection.h"
46 #include "selection_templates.h"
47 #include "time_axis_view.h"
48 #include "automation_time_axis.h"
49 #include "public_editor.h"
50 #include "control_point.h"
51 #include "vca_time_axis.h"
52 
53 #include "pbd/i18n.h"
54 
55 using namespace std;
56 using namespace ARDOUR;
57 using namespace PBD;
58 
59 struct AudioRangeComparator {
operator ()AudioRangeComparator60 	bool operator()(AudioRange a, AudioRange b) {
61 		return a.start < b.start;
62 	}
63 };
64 
Selection(const PublicEditor * e,bool mls)65 Selection::Selection (const PublicEditor* e, bool mls)
66 	: editor (e)
67 	, next_time_id (0)
68 	, manage_libardour_selection (mls)
69 {
70 	clear ();
71 
72 	/* we have disambiguate which remove() for the compiler */
73 
74 	void (Selection::*marker_remove)(ArdourMarker*) = &Selection::remove;
75 	ArdourMarker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
76 
77 	void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
78 	ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
79 }
80 
81 #if 0
82 Selection&
83 Selection::operator= (const Selection& other)
84 {
85 	if (&other != this) {
86 		regions = other.regions;
87 		tracks = other.tracks;
88 		time = other.time;
89 		lines = other.lines;
90 		midi_regions = other.midi_regions;
91 		midi_notes = other.midi_notes;
92 	}
93 	return *this;
94 }
95 #endif
96 
97 bool
operator ==(const Selection & a,const Selection & b)98 operator== (const Selection& a, const Selection& b)
99 {
100 	return a.regions == b.regions &&
101 		a.tracks == b.tracks &&
102 		a.time == b.time &&
103 		a.lines == b.lines &&
104 		a.playlists == b.playlists &&
105 		a.midi_notes == b.midi_notes;
106 }
107 
108 /** Clear everything from the Selection */
109 void
clear()110 Selection::clear ()
111 {
112 	clear_tracks ();
113 	clear_regions ();
114 	clear_points ();
115 	clear_lines();
116 	clear_time ();
117 	clear_playlists ();
118 	clear_midi_notes ();
119 	clear_markers ();
120 	pending_midi_note_selection.clear();
121 }
122 
123 void
clear_objects(bool with_signal)124 Selection::clear_objects (bool with_signal)
125 {
126 	clear_regions (with_signal);
127 	clear_points (with_signal);
128 	clear_lines(with_signal);
129 	clear_playlists (with_signal);
130 	clear_midi_notes (with_signal);
131 }
132 
133 void
clear_time(bool with_signal)134 Selection::clear_time (bool with_signal)
135 {
136 	time.clear();
137 	if (with_signal) {
138 		TimeChanged ();
139 	}
140 }
141 
142 void
dump_region_layers()143 Selection::dump_region_layers()
144 {
145 	cerr << "region selection layer dump" << endl;
146 	for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
147 		cerr << "layer: " << (int)(*i)->region()->layer() << endl;
148 	}
149 }
150 
151 
152 void
clear_regions(bool with_signal)153 Selection::clear_regions (bool with_signal)
154 {
155 	if (!regions.empty()) {
156 		regions.clear_all ();
157 		if (with_signal) {
158 			RegionsChanged();
159 		}
160 	}
161 }
162 
163 void
clear_midi_notes(bool with_signal)164 Selection::clear_midi_notes (bool with_signal)
165 {
166 	/* Remmeber: MIDI notes are only stored here if we're using a Selection
167 	   object as a cut buffer.
168 	*/
169 
170 	if (!midi_notes.empty()) {
171 		for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
172 			delete *x;
173 		}
174 		midi_notes.clear ();
175 		if (with_signal) {
176 			MidiNotesChanged ();
177 		}
178 	}
179 }
180 
181 void
clear_playlists(bool with_signal)182 Selection::clear_playlists (bool with_signal)
183 {
184 	/* Selections own their playlists */
185 
186 	for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
187 		/* selections own their own regions, which are copies of the "originals". make them go away */
188 		(*i)->drop_regions ();
189 		(*i)->release ();
190 	}
191 
192 	if (!playlists.empty()) {
193 		playlists.clear ();
194 		if (with_signal) {
195 			PlaylistsChanged();
196 		}
197 	}
198 }
199 
200 void
clear_lines(bool with_signal)201 Selection::clear_lines (bool with_signal)
202 {
203 	if (!lines.empty()) {
204 		lines.clear ();
205 		if (with_signal) {
206 			LinesChanged();
207 		}
208 	}
209 }
210 
211 void
clear_markers(bool with_signal)212 Selection::clear_markers (bool with_signal)
213 {
214 	if (!markers.empty()) {
215 		markers.clear ();
216 		if (with_signal) {
217 			MarkersChanged();
218 		}
219 	}
220 }
221 
222 void
toggle(boost::shared_ptr<Playlist> pl)223 Selection::toggle (boost::shared_ptr<Playlist> pl)
224 {
225 	clear_time(); // enforce object/range exclusivity
226 	clear_tracks(); // enforce object/track exclusivity
227 
228 	PlaylistSelection::iterator i;
229 
230 	if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
231 		pl->use ();
232 		playlists.push_back(pl);
233 	} else {
234 		playlists.erase (i);
235 	}
236 
237 	PlaylistsChanged ();
238 }
239 
240 void
toggle(const MidiNoteSelection & midi_note_list)241 Selection::toggle (const MidiNoteSelection& midi_note_list)
242 {
243 	clear_time(); // enforce object/range exclusivity
244 	clear_tracks(); // enforce object/track exclusivity
245 
246 	for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
247 		toggle ((*i));
248 	}
249 }
250 
251 void
toggle(MidiCutBuffer * midi)252 Selection::toggle (MidiCutBuffer* midi)
253 {
254 	MidiNoteSelection::iterator i;
255 
256 	if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
257 		midi_notes.push_back (midi);
258 	} else {
259 		/* remember that we own the MCB */
260 		delete *i;
261 		midi_notes.erase (i);
262 	}
263 
264 	MidiNotesChanged();
265 }
266 
267 
268 void
toggle(RegionView * r)269 Selection::toggle (RegionView* r)
270 {
271 	clear_time(); // enforce object/range exclusivity
272 	clear_tracks(); // enforce object/track exclusivity
273 
274 	RegionSelection::iterator i;
275 
276 	if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
277 		add (r);
278 	} else {
279 		remove (*i);
280 	}
281 
282 	RegionsChanged ();
283 }
284 
285 void
toggle(vector<RegionView * > & r)286 Selection::toggle (vector<RegionView*>& r)
287 {
288 	clear_time(); // enforce object/range exclusivity
289 	clear_tracks(); // enforce object/track exclusivity
290 
291 	RegionSelection::iterator i;
292 
293 	for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
294 		if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
295 			add ((*x));
296 		} else {
297 			remove (*x);
298 		}
299 	}
300 
301 	RegionsChanged ();
302 }
303 
304 long
toggle(samplepos_t start,samplepos_t end)305 Selection::toggle (samplepos_t start, samplepos_t end)
306 {
307 	clear_objects(); // enforce object/range exclusivity
308 
309 	AudioRangeComparator cmp;
310 
311 	/* XXX this implementation is incorrect */
312 
313 	time.push_back (AudioRange (start, end, ++next_time_id));
314 	time.consolidate ();
315 	time.sort (cmp);
316 
317 	TimeChanged ();
318 
319 	return next_time_id;
320 }
321 
322 void
add(boost::shared_ptr<Playlist> pl)323 Selection::add (boost::shared_ptr<Playlist> pl)
324 {
325 
326 	if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
327 		clear_time(); // enforce object/range exclusivity
328 		clear_tracks(); // enforce object/track exclusivity
329 		pl->use ();
330 		playlists.push_back(pl);
331 		PlaylistsChanged ();
332 	}
333 }
334 
335 void
add(const list<boost::shared_ptr<Playlist>> & pllist)336 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
337 {
338 	bool changed = false;
339 
340 	for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
341 		if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
342 			(*i)->use ();
343 			playlists.push_back (*i);
344 			changed = true;
345 		}
346 	}
347 
348 	if (changed) {
349 		clear_time(); // enforce object/range exclusivity
350 		clear_tracks(); // enforce object/track exclusivity
351 		PlaylistsChanged ();
352 	}
353 }
354 
355 void
add(const MidiNoteSelection & midi_list)356 Selection::add (const MidiNoteSelection& midi_list)
357 {
358 	const MidiNoteSelection::const_iterator b = midi_list.begin();
359 	const MidiNoteSelection::const_iterator e = midi_list.end();
360 
361 	if (!midi_list.empty()) {
362 		clear_time(); // enforce object/range exclusivity
363 		clear_tracks(); // enforce object/track exclusivity
364 		midi_notes.insert (midi_notes.end(), b, e);
365 		MidiNotesChanged ();
366 	}
367 }
368 
369 void
add(MidiCutBuffer * midi)370 Selection::add (MidiCutBuffer* midi)
371 {
372 	/* we take ownership of the MCB */
373 
374 	if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
375 		midi_notes.push_back (midi);
376 		MidiNotesChanged ();
377 	}
378 }
379 
380 void
add(vector<RegionView * > & v)381 Selection::add (vector<RegionView*>& v)
382 {
383 	/* XXX This method or the add (const RegionSelection&) needs to go
384 	 */
385 
386 	bool changed = false;
387 
388 	for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
389 		if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
390 			changed = regions.add ((*i));
391 		}
392 	}
393 
394 	if (changed) {
395 		clear_time(); // enforce object/range exclusivity
396 		clear_tracks(); // enforce object/track exclusivity
397 		RegionsChanged ();
398 	}
399 }
400 
401 void
add(const RegionSelection & rs)402 Selection::add (const RegionSelection& rs)
403 {
404 	/* XXX This method or the add (const vector<RegionView*>&) needs to go
405 	 */
406 
407 	bool changed = false;
408 
409 	for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
410 		if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
411 			changed = regions.add ((*i));
412 		}
413 	}
414 
415 	if (changed) {
416 		clear_time(); // enforce object/range exclusivity
417 		clear_tracks(); // enforce object/track exclusivity
418 		RegionsChanged ();
419 	}
420 }
421 
422 void
add(RegionView * r)423 Selection::add (RegionView* r)
424 {
425 	if (find (regions.begin(), regions.end(), r) == regions.end()) {
426 		bool changed = regions.add (r);
427 		if (changed) {
428 			clear_time(); // enforce object/range exclusivity
429 			clear_tracks(); // enforce object/track exclusivity
430 			RegionsChanged ();
431 		}
432 	}
433 }
434 
435 long
add(samplepos_t start,samplepos_t end)436 Selection::add (samplepos_t start, samplepos_t end)
437 {
438 	clear_objects(); // enforce object/range exclusivity
439 
440 	AudioRangeComparator cmp;
441 
442 	/* XXX this implementation is incorrect */
443 
444 	time.push_back (AudioRange (start, end, ++next_time_id));
445 	time.consolidate ();
446 	time.sort (cmp);
447 
448 	TimeChanged ();
449 
450 	return next_time_id;
451 }
452 
453 void
move_time(samplecnt_t distance)454 Selection::move_time (samplecnt_t distance)
455 {
456 	if (distance == 0) {
457 		return;
458 	}
459 
460 	for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
461 		(*i).start += distance;
462 		(*i).end += distance;
463 	}
464 
465 	TimeChanged ();
466 }
467 
468 void
replace(uint32_t sid,samplepos_t start,samplepos_t end)469 Selection::replace (uint32_t sid, samplepos_t start, samplepos_t end)
470 {
471 	clear_objects(); // enforce object/range exclusivity
472 
473 	for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
474 		if ((*i).id == sid) {
475 			time.erase (i);
476 			time.push_back (AudioRange(start,end, sid));
477 
478 			/* don't consolidate here */
479 
480 
481 			AudioRangeComparator cmp;
482 			time.sort (cmp);
483 
484 			TimeChanged ();
485 			break;
486 		}
487 	}
488 }
489 
490 void
add(boost::shared_ptr<Evoral::ControlList> cl)491 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
492 {
493 	boost::shared_ptr<ARDOUR::AutomationList> al = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
494 
495 	if (!al) {
496 		warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
497 		return;
498 	}
499 
500 	if (!cl->empty()) {
501 		clear_time(); // enforce object/range exclusivity
502 		clear_tracks(); // enforce object/track exclusivity
503 	}
504 
505 	/* The original may change so we must store a copy (not a pointer) here.
506 	 * e.g AutomationLine rewrites the list with gain mapping.
507 	 * the downside is that we can't perfom duplicate checks.
508 	 * This code was changed in response to #6842
509 	 */
510 	lines.push_back (boost::shared_ptr<ARDOUR::AutomationList> (new ARDOUR::AutomationList(*al)));
511 	LinesChanged();
512 }
513 
514 void
remove(ControlPoint * p)515 Selection::remove (ControlPoint* p)
516 {
517 	PointSelection::iterator i = find (points.begin(), points.end(), p);
518 	if (i != points.end ()) {
519 		points.erase (i);
520 	}
521 }
522 
523 void
remove(const MidiNoteSelection & midi_list)524 Selection::remove (const MidiNoteSelection& midi_list)
525 {
526 	bool changed = false;
527 
528 	for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
529 
530 		MidiNoteSelection::iterator x;
531 
532 		if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
533 			midi_notes.erase (x);
534 			changed = true;
535 		}
536 	}
537 
538 	if (changed) {
539 		MidiNotesChanged();
540 	}
541 }
542 
543 void
remove(MidiCutBuffer * midi)544 Selection::remove (MidiCutBuffer* midi)
545 {
546 	MidiNoteSelection::iterator x;
547 
548 	if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
549 		/* remember that we own the MCB */
550 		delete *x;
551 		midi_notes.erase (x);
552 		MidiNotesChanged ();
553 	}
554 }
555 
556 void
remove(boost::shared_ptr<Playlist> track)557 Selection::remove (boost::shared_ptr<Playlist> track)
558 {
559 	list<boost::shared_ptr<Playlist> >::iterator i;
560 	if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
561 		playlists.erase (i);
562 		PlaylistsChanged();
563 	}
564 }
565 
566 void
remove(const list<boost::shared_ptr<Playlist>> & pllist)567 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
568 {
569 	bool changed = false;
570 
571 	for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
572 
573 		list<boost::shared_ptr<Playlist> >::iterator x;
574 
575 		if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
576 			playlists.erase (x);
577 			changed = true;
578 		}
579 	}
580 
581 	if (changed) {
582 		PlaylistsChanged();
583 	}
584 }
585 
586 void
remove(RegionView * r)587 Selection::remove (RegionView* r)
588 {
589 	if (regions.remove (r)) {
590 		RegionsChanged ();
591 	}
592 }
593 
594 void
remove(uint32_t selection_id)595 Selection::remove (uint32_t selection_id)
596 {
597 	if (time.empty()) {
598 		return;
599 	}
600 
601 	for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
602 		if ((*i).id == selection_id) {
603 			time.erase (i);
604 
605 			TimeChanged ();
606 			break;
607 		}
608 	}
609 }
610 
611 void
remove(samplepos_t,samplepos_t)612 Selection::remove (samplepos_t /*start*/, samplepos_t /*end*/)
613 {
614 }
615 
616 void
remove(boost::shared_ptr<ARDOUR::AutomationList> ac)617 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
618 {
619 	AutomationSelection::iterator i;
620 	if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
621 		lines.erase (i);
622 		LinesChanged();
623 	}
624 }
625 
626 void
set(const MidiNoteSelection & midi_list)627 Selection::set (const MidiNoteSelection& midi_list)
628 {
629 	if (!midi_list.empty()) {
630 		clear_time (); // enforce region/object exclusivity
631 		clear_tracks(); // enforce object/track exclusivity
632 	}
633 	clear_objects ();
634 	add (midi_list);
635 }
636 
637 void
set(boost::shared_ptr<Playlist> playlist)638 Selection::set (boost::shared_ptr<Playlist> playlist)
639 {
640 	if (playlist) {
641 		clear_time (); // enforce region/object exclusivity
642 		clear_tracks(); // enforce object/track exclusivity
643 	}
644 	clear_objects ();
645 	add (playlist);
646 }
647 
648 void
set(const list<boost::shared_ptr<Playlist>> & pllist)649 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
650 {
651 	if (!pllist.empty()) {
652 		clear_time(); // enforce region/object exclusivity
653 	}
654 	clear_objects ();
655 	add (pllist);
656 }
657 
658 void
set(const RegionSelection & rs)659 Selection::set (const RegionSelection& rs)
660 {
661 	if (!rs.empty()) {
662 		clear_time(); // enforce region/object exclusivity
663 		clear_tracks(); // enforce object/track exclusivity
664 	}
665 	clear_objects();
666 	regions = rs;
667 	RegionsChanged(); /* EMIT SIGNAL */
668 }
669 
670 void
set(RegionView * r,bool)671 Selection::set (RegionView* r, bool /*also_clear_tracks*/)
672 {
673 	if (r) {
674 		clear_time(); // enforce region/object exclusivity
675 		clear_tracks(); // enforce object/track exclusivity
676 	}
677 	clear_objects ();
678 	add (r);
679 }
680 
681 void
set(vector<RegionView * > & v)682 Selection::set (vector<RegionView*>& v)
683 {
684 	if (!v.empty()) {
685 		clear_time(); // enforce region/object exclusivity
686 		clear_tracks(); // enforce object/track exclusivity
687 	}
688 
689 	clear_objects();
690 
691 	add (v);
692 }
693 
694 /** Set the start and end time of the time selection, without changing
695  *  the list of tracks it applies to.
696  */
697 long
set(samplepos_t start,samplepos_t end)698 Selection::set (samplepos_t start, samplepos_t end)
699 {
700 	clear_objects(); // enforce region/object exclusivity
701 	clear_time();
702 
703 	if ((start == 0 && end == 0) || end < start) {
704 		return 0;
705 	}
706 
707 	if (time.empty()) {
708 		time.push_back (AudioRange (start, end, ++next_time_id));
709 	} else {
710 		/* reuse the first entry, and remove all the rest */
711 
712 		while (time.size() > 1) {
713 			time.pop_front();
714 		}
715 		time.front().start = start;
716 		time.front().end = end;
717 	}
718 
719 	time.consolidate ();
720 
721 	TimeChanged ();
722 
723 	return time.front().id;
724 }
725 
726 /** Set the start and end of the range selection.  If more than one range
727  *  is currently selected, the start of the earliest range and the end of the
728  *  latest range are set.  If no range is currently selected, this method
729  *  selects a single range from start to end.
730  *
731  *  @param start New start time.
732  *  @param end New end time.
733  */
734 void
set_preserving_all_ranges(samplepos_t start,samplepos_t end)735 Selection::set_preserving_all_ranges (samplepos_t start, samplepos_t end)
736 {
737 	clear_objects(); // enforce region/object exclusivity
738 
739 	if ((start == 0 && end == 0) || (end < start)) {
740 		return;
741 	}
742 
743 	if (time.empty ()) {
744 		time.push_back (AudioRange (start, end, ++next_time_id));
745 	} else {
746 		time.sort (AudioRangeComparator ());
747 		time.front().start = start;
748 		time.back().end = end;
749 	}
750 
751 	time.consolidate ();
752 
753 	TimeChanged ();
754 }
755 
756 void
set(boost::shared_ptr<Evoral::ControlList> ac)757 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
758 {
759 	clear_time(); // enforce region/object exclusivity
760 	clear_tracks(); // enforce object/track exclusivity
761 	clear_objects();
762 
763 	add (ac);
764 }
765 
766 bool
selected(ArdourMarker * m) const767 Selection::selected (ArdourMarker* m) const
768 {
769 	return find (markers.begin(), markers.end(), m) != markers.end();
770 }
771 
772 bool
selected(RegionView * rv) const773 Selection::selected (RegionView* rv) const
774 {
775 	return find (regions.begin(), regions.end(), rv) != regions.end();
776 }
777 
778 bool
selected(ControlPoint * cp) const779 Selection::selected (ControlPoint* cp) const
780 {
781 	return find (points.begin(), points.end(), cp) != points.end();
782 }
783 
784 bool
empty(bool internal_selection)785 Selection::empty (bool internal_selection)
786 {
787 	bool object_level_empty = regions.empty () &&
788 		tracks.empty () &&
789 		points.empty () &&
790 		playlists.empty () &&
791 		lines.empty () &&
792 		time.empty () &&
793 		playlists.empty () &&
794 		markers.empty()
795 		;
796 
797 	if (!internal_selection) {
798 		return object_level_empty;
799 	}
800 
801 	/* this is intended to really only apply when using a Selection
802 	   as a cut buffer.
803 	*/
804 
805 	return object_level_empty && midi_notes.empty() && points.empty();
806 }
807 
808 void
toggle(ControlPoint * cp)809 Selection::toggle (ControlPoint* cp)
810 {
811 	clear_time(); // enforce region/object exclusivity
812 	clear_tracks(); // enforce object/track exclusivity
813 
814 	cp->set_selected (!cp->selected ());
815 	PointSelection::iterator i = find (points.begin(), points.end(), cp);
816 	if (i == points.end()) {
817 		points.push_back (cp);
818 	} else {
819 		points.erase (i);
820 	}
821 
822 	PointsChanged (); /* EMIT SIGNAL */
823 }
824 
825 void
toggle(vector<ControlPoint * > const & cps)826 Selection::toggle (vector<ControlPoint*> const & cps)
827 {
828 	clear_time(); // enforce region/object exclusivity
829 	clear_tracks(); // enforce object/track exclusivity
830 
831 	for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
832 		toggle (*i);
833 	}
834 }
835 
836 void
toggle(list<Selectable * > const & selectables)837 Selection::toggle (list<Selectable*> const & selectables)
838 {
839 	clear_time(); // enforce region/object exclusivity
840 	clear_tracks(); // enforce object/track exclusivity
841 
842 	RegionView* rv;
843 	ControlPoint* cp;
844 	vector<RegionView*> rvs;
845 	vector<ControlPoint*> cps;
846 
847 	for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
848 		if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
849 			rvs.push_back (rv);
850 		} else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
851 			cps.push_back (cp);
852 		} else {
853 			fatal << _("programming error: ")
854 			      << X_("unknown selectable type passed to Selection::toggle()")
855 			      << endmsg;
856 			abort(); /*NOTREACHED*/
857 		}
858 	}
859 
860 	if (!rvs.empty()) {
861 		toggle (rvs);
862 	}
863 
864 	if (!cps.empty()) {
865 		toggle (cps);
866 	}
867 }
868 
869 void
set(list<Selectable * > const & selectables)870 Selection::set (list<Selectable*> const & selectables)
871 {
872 	clear_time (); // enforce region/object exclusivity
873 	clear_tracks(); // enforce object/track exclusivity
874 	clear_objects ();
875 
876 	add (selectables);
877 }
878 
879 void
add(PointSelection const & s)880 Selection::add (PointSelection const & s)
881 {
882 	clear_time (); // enforce region/object exclusivity
883 	clear_tracks(); // enforce object/track exclusivity
884 
885 	for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
886 		points.push_back (*i);
887 	}
888 }
889 
890 void
add(list<Selectable * > const & selectables)891 Selection::add (list<Selectable*> const & selectables)
892 {
893 	clear_time (); // enforce region/object exclusivity
894 	clear_tracks(); // enforce object/track exclusivity
895 
896 	RegionView* rv;
897 	ControlPoint* cp;
898 	vector<RegionView*> rvs;
899 	vector<ControlPoint*> cps;
900 
901 	for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
902 		if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
903 			rvs.push_back (rv);
904 		} else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
905 			cps.push_back (cp);
906 		} else {
907 			fatal << _("programming error: ")
908 			      << X_("unknown selectable type passed to Selection::add()")
909 			      << endmsg;
910 			abort(); /*NOTREACHED*/
911 		}
912 	}
913 
914 	if (!rvs.empty()) {
915 		add (rvs);
916 	}
917 
918 	if (!cps.empty()) {
919 		add (cps);
920 	}
921 }
922 
923 void
clear_points(bool with_signal)924 Selection::clear_points (bool with_signal)
925 {
926 	if (!points.empty()) {
927 		points.clear ();
928 		if (with_signal) {
929 			PointsChanged ();
930 		}
931 	}
932 }
933 
934 void
add(ControlPoint * cp)935 Selection::add (ControlPoint* cp)
936 {
937 	clear_time (); // enforce region/object exclusivity
938 	clear_tracks(); // enforce object/track exclusivity
939 
940 	cp->set_selected (true);
941 	points.push_back (cp);
942 	PointsChanged (); /* EMIT SIGNAL */
943 }
944 
945 void
add(vector<ControlPoint * > const & cps)946 Selection::add (vector<ControlPoint*> const & cps)
947 {
948 	clear_time (); // enforce region/object exclusivity
949 	clear_tracks(); // enforce object/track exclusivity
950 
951 	for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
952 		(*i)->set_selected (true);
953 		points.push_back (*i);
954 	}
955 	PointsChanged (); /* EMIT SIGNAL */
956 }
957 
958 void
set(ControlPoint * cp)959 Selection::set (ControlPoint* cp)
960 {
961 	clear_time (); // enforce region/object exclusivity
962 	clear_tracks(); // enforce object/track exclusivity
963 
964 	if (cp->selected () && points.size () == 1) {
965 		return;
966 	}
967 
968 	for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
969 		cp->line().nth (i)->set_selected (false);
970 	}
971 
972 	clear_objects ();
973 	add (cp);
974 }
975 
976 void
set(ArdourMarker * m)977 Selection::set (ArdourMarker* m)
978 {
979 	clear_time ();  // enforce region/object exclusivity
980 	clear_tracks();  // enforce object/track exclusivity
981 	markers.clear ();
982 
983 	add (m);
984 }
985 
986 void
toggle(ArdourMarker * m)987 Selection::toggle (ArdourMarker* m)
988 {
989 	MarkerSelection::iterator i;
990 
991 	if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
992 		add (m);
993 	} else {
994 		remove (m);
995 	}
996 }
997 
998 void
remove(ArdourMarker * m)999 Selection::remove (ArdourMarker* m)
1000 {
1001 	MarkerSelection::iterator i;
1002 
1003 	if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
1004 		markers.erase (i);
1005 		MarkersChanged();
1006 	}
1007 }
1008 
1009 void
add(ArdourMarker * m)1010 Selection::add (ArdourMarker* m)
1011 {
1012 	clear_time (); //enforce region/object exclusivity
1013 	clear_tracks(); //enforce object/track exclusivity
1014 
1015 	if (find (markers.begin(), markers.end(), m) == markers.end()) {
1016 		markers.push_back (m);
1017 		MarkersChanged();
1018 	}
1019 }
1020 
1021 void
add(const list<ArdourMarker * > & m)1022 Selection::add (const list<ArdourMarker*>& m)
1023 {
1024 	clear_time (); // enforce region/object exclusivity
1025 	clear_tracks(); // enforce object/track exclusivity
1026 
1027 	markers.insert (markers.end(), m.begin(), m.end());
1028 	markers.sort ();
1029 	markers.unique ();
1030 
1031 	MarkersChanged ();
1032 }
1033 
1034 void
range(samplepos_t & s,samplepos_t & e)1035 MarkerSelection::range (samplepos_t& s, samplepos_t& e)
1036 {
1037 	s = max_samplepos;
1038 	e = 0;
1039 
1040 	for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
1041 
1042 		if ((*i)->position() < s) {
1043 			s = (*i)->position();
1044 		}
1045 
1046 		if ((*i)->position() > e) {
1047 			e = (*i)->position();
1048 		}
1049 	}
1050 
1051 	s = std::min (s, e);
1052 	e = std::max (s, e);
1053 }
1054 
1055 XMLNode&
get_state() const1056 Selection::get_state () const
1057 {
1058 	/* XXX: not complete; just sufficient to get track selection state
1059 	   so that re-opening plugin windows for editor mixer strips works
1060 	*/
1061 
1062 	XMLNode* node = new XMLNode (X_("Selection"));
1063 
1064 	for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
1065 		StripableTimeAxisView* stv = dynamic_cast<StripableTimeAxisView*> (*i);
1066 		AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
1067 		if (stv) {
1068 			XMLNode* t = node->add_child (X_("StripableView"));
1069 			t->set_property (X_("id"), stv->stripable()->id ());
1070 		} else if (atv) {
1071 			XMLNode* t = node->add_child (X_("AutomationView"));
1072 			t->set_property (X_("id"), atv->parent_stripable()->id ());
1073 			t->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
1074 			t->set_property (X_("ctrl_id"), atv->control()->id());
1075 		}
1076 	}
1077 
1078 	if (!regions.empty()) {
1079 		XMLNode* parent = node->add_child (X_("Regions"));
1080 		for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) {
1081 			XMLNode* r = parent->add_child (X_("Region"));
1082 			r->set_property (X_("id"), (*i)->region ()->id ());
1083 		}
1084 	}
1085 
1086 	/* midi region views have thir own internal selection. */
1087 	list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > > > > rid_notes;
1088 	editor->get_per_region_note_selection (rid_notes);
1089 
1090 	list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > > > >::iterator rn_it;
1091 	for (rn_it = rid_notes.begin(); rn_it != rid_notes.end(); ++rn_it) {
1092 		XMLNode* n = node->add_child (X_("MIDINotes"));
1093 		n->set_property (X_("region-id"), (*rn_it).first);
1094 
1095 		for (std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > >::iterator i = (*rn_it).second.begin(); i != (*rn_it).second.end(); ++i) {
1096 			XMLNode* nc = n->add_child(X_("note"));
1097 			nc->set_property(X_("note-id"), (*i)->id());
1098 		}
1099 	}
1100 
1101 	for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) {
1102 		AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (&(*i)->line().trackview);
1103 		if (atv) {
1104 
1105 			XMLNode* r = node->add_child (X_("ControlPoint"));
1106 			r->set_property (X_("type"), "track");
1107 			r->set_property (X_("route-id"), atv->parent_stripable()->id ());
1108 			r->set_property (X_("automation-list-id"), (*i)->line().the_list()->id ());
1109 			r->set_property (X_("parameter"), EventTypeMap::instance().to_symbol ((*i)->line().the_list()->parameter ()));
1110 			r->set_property (X_("view-index"), (*i)->view_index());
1111 			continue;
1112 		}
1113 
1114 		AudioRegionGainLine* argl = dynamic_cast<AudioRegionGainLine*> (&(*i)->line());
1115 		if (argl) {
1116 			XMLNode* r = node->add_child (X_("ControlPoint"));
1117 			r->set_property (X_("type"), "region");
1118 			r->set_property (X_("region-id"), argl->region_view ().region ()->id ());
1119 			r->set_property (X_("view-index"), (*i)->view_index());
1120 		}
1121 
1122 	}
1123 
1124 	for (TimeSelection::const_iterator i = time.begin(); i != time.end(); ++i) {
1125 		XMLNode* t = node->add_child (X_("AudioRange"));
1126 		t->set_property (X_("start"), (*i).start);
1127 		t->set_property (X_("end"), (*i).end);
1128 	}
1129 
1130 	for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
1131 		XMLNode* t = node->add_child (X_("Marker"));
1132 
1133 		bool is_start;
1134 		Location* loc = editor->find_location_from_marker (*i, is_start);
1135 
1136 		t->set_property (X_("id"), loc->id());
1137 		t->set_property (X_("start"), is_start);
1138 	}
1139 
1140 	return *node;
1141 }
1142 
1143 int
set_state(XMLNode const & node,int)1144 Selection::set_state (XMLNode const & node, int)
1145 {
1146 	if (node.name() != X_("Selection")) {
1147 		return -1;
1148 	}
1149 
1150 	clear_regions ();
1151 	clear_midi_notes ();
1152 	clear_points ();
1153 	clear_time ();
1154 	clear_markers ();
1155 
1156 	/* NOTE: stripable/time-axis-view selection is saved/restored by
1157 	 * ARDOUR::CoreSelection, not this Selection object
1158 	 */
1159 
1160 	PBD::ID id;
1161 	XMLNodeList children = node.children ();
1162 
1163 	for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
1164 		if ((*i)->name() == X_("Regions")) {
1165 			RegionSelection selected_regions;
1166 			XMLNodeList children = (*i)->children ();
1167 			for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
1168 				PBD::ID id;
1169 
1170 				if (!(*ci)->get_property (X_("id"), id)) {
1171 					continue;
1172 				}
1173 
1174 				RegionSelection rs;
1175 				editor->get_regionviews_by_id (id, rs);
1176 
1177 				if (!rs.empty ()) {
1178 					for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
1179 						selected_regions.push_back (*i);
1180 					}
1181 				} else {
1182 					/*
1183 					  regionviews haven't been constructed - stash the region IDs
1184 					  so we can identify them in Editor::region_view_added ()
1185 					*/
1186 					regions.pending.push_back (id);
1187 				}
1188 			}
1189 
1190 			if (!selected_regions.empty()) {
1191 				add (selected_regions);
1192 			}
1193 
1194 		} else if ((*i)->name() == X_("MIDINotes")) {
1195 
1196 			if (!(*i)->get_property (X_("region-id"), id)) {
1197 				assert (false);
1198 			}
1199 
1200 			RegionSelection rs;
1201 
1202 			editor->get_regionviews_by_id (id, rs); // there could be more than one
1203 
1204 			std::list<Evoral::event_id_t> notes;
1205 			XMLNodeList children = (*i)->children ();
1206 
1207 			for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
1208 				Evoral::event_id_t id;
1209 				if ((*ci)->get_property (X_ ("note-id"), id)) {
1210 					notes.push_back (id);
1211 				}
1212 			}
1213 
1214 			for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1215 				MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*rsi);
1216 				if (mrv) {
1217 					mrv->select_notes(notes, false);
1218 				}
1219 			}
1220 
1221 			if (rs.empty()) {
1222 				/* regionviews containing these notes don't yet exist on the canvas.*/
1223 				pending_midi_note_selection.push_back (make_pair (id, notes));
1224 			}
1225 
1226 		} else if ((*i)->name() == X_("ControlPoint")) {
1227 			XMLProperty const * prop_type = (*i)->property (X_("type"));
1228 
1229 			assert(prop_type);
1230 
1231 			if (prop_type->value () == "track") {
1232 
1233 				PBD::ID route_id;
1234 				PBD::ID alist_id;
1235 				std::string param;
1236 				uint32_t view_index;
1237 
1238 				if (!(*i)->get_property (X_("route-id"), route_id) ||
1239 				    !(*i)->get_property (X_("automation-list-id"), alist_id) ||
1240 				    !(*i)->get_property (X_("parameter"), param) ||
1241 				    !(*i)->get_property (X_("view-index"), view_index)) {
1242 					assert(false);
1243 				}
1244 
1245 				StripableTimeAxisView* stv = editor->get_stripable_time_axis_by_id (route_id);
1246 				vector <ControlPoint *> cps;
1247 
1248 				if (stv) {
1249 					boost::shared_ptr<AutomationLine> li = stv->automation_child_by_alist_id (alist_id);
1250 					if (li) {
1251 						ControlPoint* cp = li->nth(view_index);
1252 						if (cp) {
1253 							cps.push_back (cp);
1254 							cp->show();
1255 						}
1256 					}
1257 				}
1258 				if (!cps.empty()) {
1259 					add (cps);
1260 				}
1261 			} else if (prop_type->value () == "region") {
1262 
1263 				PBD::ID region_id;
1264 				uint32_t view_index;
1265 				if (!(*i)->get_property (X_("region-id"), region_id) ||
1266 				    !(*i)->get_property (X_("view-index"), view_index)) {
1267 					continue;
1268 				}
1269 
1270 				RegionSelection rs;
1271 				editor->get_regionviews_by_id (region_id, rs);
1272 
1273 				if (!rs.empty ()) {
1274 					vector <ControlPoint *> cps;
1275 					for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1276 						AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*rsi);
1277 						if (arv) {
1278 							boost::shared_ptr<AudioRegionGainLine> gl = arv->get_gain_line ();
1279 							ControlPoint* cp = gl->nth(view_index);
1280 							if (cp) {
1281 								cps.push_back (cp);
1282 								cp->show();
1283 							}
1284 						}
1285 					}
1286 					if (!cps.empty()) {
1287 						add (cps);
1288 					}
1289 				}
1290 			}
1291 
1292 		} else if ((*i)->name() == X_("AudioRange")) {
1293 			samplepos_t start;
1294 			samplepos_t end;
1295 
1296 			if (!(*i)->get_property (X_("start"), start) || !(*i)->get_property (X_("end"), end)) {
1297 				assert(false);
1298 			}
1299 
1300 			add (start, end);
1301 
1302 		} else if ((*i)->name() == X_("AutomationView")) {
1303 
1304 			// XXX is this even used? -> StripableAutomationControl
1305 			std::string param;
1306 			PBD::ID ctrl_id (0);
1307 
1308 			if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("parameter"), param)) {
1309 				assert (false);
1310 			}
1311 
1312 			StripableTimeAxisView* stv = editor->get_stripable_time_axis_by_id (id);
1313 
1314 			if (stv && (*i)->get_property (X_("control_id"), ctrl_id)) {
1315 				boost::shared_ptr<AutomationTimeAxisView> atv = stv->automation_child (EventTypeMap::instance().from_symbol (param), ctrl_id);
1316 
1317 				/* the automation could be for an entity that was never saved
1318 				 * in the session file. Don't freak out if we can't find
1319 				 * it.
1320 				 */
1321 
1322 				if (atv) {
1323 					add (atv.get());
1324 				}
1325 			}
1326 
1327 		} else if ((*i)->name() == X_("Marker")) {
1328 
1329 			bool is_start;
1330 			if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("start"), is_start)) {
1331 				assert(false);
1332 			}
1333 
1334 			ArdourMarker* m = editor->find_marker_from_location_id (id, is_start);
1335 			if (m) {
1336 				add (m);
1337 			}
1338 
1339 		}
1340 
1341 	}
1342 
1343 	return 0;
1344 }
1345 
1346 void
remove_regions(TimeAxisView * t)1347 Selection::remove_regions (TimeAxisView* t)
1348 {
1349 	RegionSelection::iterator i = regions.begin();
1350 	while (i != regions.end ()) {
1351 		RegionSelection::iterator tmp = i;
1352 		++tmp;
1353 
1354 		if (&(*i)->get_time_axis_view() == t) {
1355 			remove (*i);
1356 		}
1357 
1358 		i = tmp;
1359 	}
1360 }
1361 
1362 /* TIME AXIS VIEW ... proxy for Stripable/Controllable
1363  *
1364  * public methods just modify the CoreSelection; PresentationInfo::Changed will
1365  * trigger Selection::core_selection_changed() and we will update our own data
1366  * structures there.
1367  */
1368 
1369 void
toggle(const TrackViewList & track_list)1370 Selection::toggle (const TrackViewList& track_list)
1371 {
1372 	TrackViewList t = add_grouped_tracks (track_list);
1373 
1374 	CoreSelection& selection (editor->session()->selection());
1375 	PresentationInfo::ChangeSuspender cs;
1376 
1377 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1378 		boost::shared_ptr<Stripable> s = (*i)->stripable ();
1379 		boost::shared_ptr<AutomationControl> c = (*i)->control ();
1380 		selection.toggle (s, c);
1381 	}
1382 }
1383 
1384 void
toggle(TimeAxisView * track)1385 Selection::toggle (TimeAxisView* track)
1386 {
1387 	TrackViewList tr;
1388 	tr.push_back (track);
1389 	toggle (tr);
1390 }
1391 
1392 void
add(TrackViewList const & track_list)1393 Selection::add (TrackViewList const & track_list)
1394 {
1395 	TrackViewList t = add_grouped_tracks (track_list);
1396 
1397 	CoreSelection& selection (editor->session()->selection());
1398 	PresentationInfo::ChangeSuspender cs;
1399 
1400 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1401 		boost::shared_ptr<Stripable> s = (*i)->stripable ();
1402 		boost::shared_ptr<AutomationControl> c = (*i)->control ();
1403 		selection.add (s, c);
1404 	}
1405 }
1406 
1407 void
add(TimeAxisView * track)1408 Selection::add (TimeAxisView* track)
1409 {
1410 	TrackViewList tr;
1411 	tr.push_back (track);
1412 	add (tr);
1413 }
1414 
1415 void
remove(TimeAxisView * track)1416 Selection::remove (TimeAxisView* track)
1417 {
1418 	TrackViewList tvl;
1419 	tvl.push_back (track);
1420 	remove (tvl);
1421 }
1422 
1423 void
remove(const TrackViewList & t)1424 Selection::remove (const TrackViewList& t)
1425 {
1426 	CoreSelection& selection (editor->session()->selection());
1427 	PresentationInfo::ChangeSuspender cs;
1428 
1429 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1430 		boost::shared_ptr<Stripable> s = (*i)->stripable ();
1431 		boost::shared_ptr<AutomationControl> c = (*i)->control ();
1432 		selection.remove (s, c);
1433 	}
1434 }
1435 
1436 void
set(TimeAxisView * track)1437 Selection::set (TimeAxisView* track)
1438 {
1439 	TrackViewList tvl;
1440 	tvl.push_back (track);
1441 	set (tvl);
1442 }
1443 
1444 void
set(const TrackViewList & track_list)1445 Selection::set (const TrackViewList& track_list)
1446 {
1447 	TrackViewList t = add_grouped_tracks (track_list);
1448 
1449 	CoreSelection& selection (editor->session()->selection());
1450 
1451 #if 1 // crazy optmization hack
1452 	/* check is the selection actually changed, ignore NO-OPs
1453 	 *
1454 	 * There are excessive calls from EditorRoutes::selection_changed():
1455 	 * Every click calls selection_changed() even if it doesn't change.
1456 	 * Also re-ordering tracks calls into this due to gtk's odd DnD signal
1457 	 * messaging (row removed, re-added).
1458 	 *
1459 	 * Re-ordering a row results in at least 2 calls to selection_changed()
1460 	 * without actual change. Calling selection.clear_stripables()
1461 	 * and re-adding the same tracks every time in turn emits changed signals.
1462 	 */
1463 	bool changed = false;
1464 	CoreSelection::StripableAutomationControls sac;
1465 	selection.get_stripables (sac);
1466 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1467 		boost::shared_ptr<Stripable> s = (*i)->stripable ();
1468 		boost::shared_ptr<AutomationControl> c = (*i)->control ();
1469 		bool found = false;
1470 		for (CoreSelection::StripableAutomationControls::iterator j = sac.begin (); j != sac.end (); ++j) {
1471 			if (j->stripable == s && j->controllable == c) {
1472 				found = true;
1473 				sac.erase (j);
1474 				break;
1475 			}
1476 		}
1477 		if (!found) {
1478 			changed = true;
1479 			break;
1480 		}
1481 	}
1482 	if (!changed && sac.size() == 0) {
1483 		return;
1484 	}
1485 #endif
1486 
1487 	PresentationInfo::ChangeSuspender cs;
1488 
1489 	selection.clear_stripables ();
1490 
1491 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1492 		boost::shared_ptr<Stripable> s = (*i)->stripable ();
1493 		boost::shared_ptr<AutomationControl> c = (*i)->control ();
1494 		selection.add (s, c);
1495 	}
1496 }
1497 
1498 void
clear_tracks(bool)1499 Selection::clear_tracks (bool)
1500 {
1501 	if (!manage_libardour_selection) {
1502 		return;
1503 	}
1504 
1505 	Session* s = editor->session();
1506 	if (s) {
1507 		CoreSelection& selection (s->selection());
1508 		selection.clear_stripables ();
1509 	}
1510 }
1511 
1512 bool
selected(TimeAxisView * tv) const1513 Selection::selected (TimeAxisView* tv) const
1514 {
1515 	Session* session = editor->session();
1516 
1517 	if (!session) {
1518 		return false;
1519 	}
1520 
1521 	CoreSelection& selection (session->selection());
1522 	boost::shared_ptr<Stripable> s = tv->stripable ();
1523 	boost::shared_ptr<AutomationControl> c = tv->control ();
1524 
1525 	if (c) {
1526 		return selection.selected (c);
1527 	}
1528 
1529 	return selection.selected (s);
1530 }
1531 
1532 TrackViewList
add_grouped_tracks(TrackViewList const & t)1533 Selection::add_grouped_tracks (TrackViewList const & t)
1534 {
1535 	TrackViewList added;
1536 
1537 	for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1538 		if (dynamic_cast<VCATimeAxisView*> (*i)) {
1539 			continue;
1540 		}
1541 
1542 		/* select anything in the same select-enabled route group */
1543 		ARDOUR::RouteGroup* rg = (*i)->route_group ();
1544 
1545 		if (rg && rg->is_active() && rg->is_select ()) {
1546 
1547 			TrackViewList tr = editor->axis_views_from_routes (rg->route_list ());
1548 
1549 			for (TrackViewList::iterator j = tr.begin(); j != tr.end(); ++j) {
1550 
1551 				/* Do not add the trackview passed in as an
1552 				 * argument, because we want that to be on the
1553 				 * end of the list.
1554 				 */
1555 
1556 				if (*j != *i) {
1557 					if (!added.contains (*j)) {
1558 						added.push_back (*j);
1559 					}
1560 				}
1561 			}
1562 		}
1563 	}
1564 
1565 	/* now add the the trackview's passed in as actual arguments */
1566 	added.insert (added.end(), t.begin(), t.end());
1567 
1568 	return added;
1569 }
1570 
1571 #if 0
1572 static void dump_tracks (Selection const & s)
1573 {
1574 	cerr << "--TRACKS [" << s.tracks.size() << ']' << ":\n";
1575 	for (TrackViewList::const_iterator x = s.tracks.begin(); x != s.tracks.end(); ++x) {
1576 		cerr << (*x)->name() << ' ' << (*x)->stripable() << " C = " << (*x)->control() << endl;
1577 	}
1578 	cerr << "///\n";
1579 }
1580 #endif
1581 
1582 void
core_selection_changed(PropertyChange const & what_changed)1583 Selection::core_selection_changed (PropertyChange const & what_changed)
1584 {
1585 	PropertyChange pc;
1586 
1587 	pc.add (Properties::selected);
1588 
1589 	if (!what_changed.contains (pc)) {
1590 		return;
1591 	}
1592 
1593 	CoreSelection& selection (editor->session()->selection());
1594 
1595 	if (selection.selected()) {
1596 		clear_objects(); // enforce object/range exclusivity
1597 	}
1598 
1599 	tracks.clear (); // clear stage for whatever tracks are now selected (maybe none)
1600 
1601 	CoreSelection::StripableAutomationControls sac;
1602 	selection.get_stripables (sac);
1603 
1604 	for (CoreSelection::StripableAutomationControls::const_iterator i = sac.begin(); i != sac.end(); ++i) {
1605 		AxisView* av;
1606 		TimeAxisView* tav;
1607 		if ((*i).controllable) {
1608 			av = editor->axis_view_by_control ((*i).controllable);
1609 		} else {
1610 			av = editor->axis_view_by_stripable ((*i).stripable);
1611 		}
1612 
1613 		tav = dynamic_cast<TimeAxisView*>(av);
1614 		if (tav) {
1615 			tracks.push_back (tav);
1616 		}
1617 	}
1618 
1619 	TracksChanged();
1620 }
1621 
1622 MidiRegionSelection
midi_regions()1623 Selection::midi_regions ()
1624 {
1625 	MidiRegionSelection ms;
1626 
1627 	for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
1628 		MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
1629 		if (mrv) {
1630 			ms.add (mrv);
1631 		}
1632 	}
1633 
1634 	return ms;
1635 }
1636