1 /*
2  * Copyright (C) 2017-2018 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2017-2018 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include <vector>
21 
22 #include "pbd/compose.h"
23 #include "pbd/signals.h"
24 
25 #include "ardour/automation_control.h"
26 #include "ardour/debug.h"
27 #include "ardour/route.h"
28 #include "ardour/route_group.h"
29 #include "ardour/selection.h"
30 #include "ardour/session.h"
31 #include "ardour/stripable.h"
32 
33 #include "pbd/i18n.h"
34 
35 using namespace ARDOUR;
36 using namespace PBD;
37 
38 void
send_selection_change()39 CoreSelection::send_selection_change ()
40 {
41 	PropertyChange pc;
42 	pc.add (Properties::selected);
43 	PresentationInfo::send_static_change (pc);
44 }
45 
CoreSelection(Session & s)46 CoreSelection::CoreSelection (Session& s)
47 	: session (s)
48 {
49 	g_atomic_int_set (&_selection_order, 0);
50 }
51 
~CoreSelection()52 CoreSelection::~CoreSelection ()
53 {
54 }
55 
56 template<typename IterTypeCore>
57 void
select_adjacent_stripable(bool mixer_order,bool routes_only,IterTypeCore (StripableList::* begin_method)(),IterTypeCore (StripableList::* end_method)())58 CoreSelection::select_adjacent_stripable (bool mixer_order, bool routes_only,
59                                           IterTypeCore (StripableList::*begin_method)(),
60                                           IterTypeCore (StripableList::*end_method)())
61 {
62 	if (_stripables.empty()) {
63 
64 		/* Pick first acceptable */
65 
66 		StripableList stripables;
67 		session.get_stripables (stripables);
68 		stripables.sort (ARDOUR::Stripable::Sorter (mixer_order));
69 
70 		for (StripableList::iterator s = stripables.begin(); s != stripables.end(); ++s) {
71 			if (select_stripable_and_maybe_group (*s, true, routes_only, 0)) {
72 				break;
73 			}
74 		}
75 
76 		return;
77 	}
78 
79 	/* fetch the current selection so that we can get the most recently selected */
80 	StripableAutomationControls selected;
81 	get_stripables (selected);
82 	boost::shared_ptr<Stripable> last_selected =
83 	  selected.empty () ? boost::shared_ptr<Stripable> ()
84 	                    : selected.back ().stripable;
85 
86 	/* Get all stripables and sort into the appropriate ordering */
87 	StripableList stripables;
88 	session.get_stripables (stripables);
89 	stripables.sort (ARDOUR::Stripable::Sorter (mixer_order));
90 
91 
92 	/* Check for a possible selection-affecting route group */
93 
94 	RouteGroup* group = 0;
95 	boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (last_selected);
96 
97 	if (r && r->route_group() && r->route_group()->is_select() && r->route_group()->is_active()) {
98 		group = r->route_group();
99 	}
100 
101 	bool select_me = false;
102 
103 	for (IterTypeCore i = (stripables.*begin_method)(); i != (stripables.*end_method)(); ++i) {
104 
105 		if (select_me) {
106 
107 			if (!this->selected (*i)) { /* not currently selected */
108 				if (select_stripable_and_maybe_group (*i, true, routes_only, group)) {
109 					return;
110 				}
111 			}
112 		}
113 
114 		if ((*i) == last_selected) {
115 			select_me = true;
116 		}
117 	}
118 
119 	/* no next/previous, wrap around ... find first usable stripable from
120 	 * the appropriate end.
121 	*/
122 
123 	for (IterTypeCore s = (stripables.*begin_method)(); s != (stripables.*end_method)(); ++s) {
124 
125 		r = boost::dynamic_pointer_cast<Route> (*s);
126 
127 		/* monitor is never selectable anywhere. for now, anyway */
128 
129 		if (!routes_only || r) {
130 			if (select_stripable_and_maybe_group (*s, true, routes_only, 0)) {
131 				return;
132 			}
133 		}
134 	}
135 }
136 
137 void
select_next_stripable(bool mixer_order,bool routes_only)138 CoreSelection::select_next_stripable (bool mixer_order, bool routes_only)
139 {
140 	select_adjacent_stripable<StripableList::iterator> (mixer_order, routes_only, &StripableList::begin, &StripableList::end);
141 }
142 
143 void
select_prev_stripable(bool mixer_order,bool routes_only)144 CoreSelection::select_prev_stripable (bool mixer_order, bool routes_only)
145 {
146 	select_adjacent_stripable<StripableList::reverse_iterator> (mixer_order, routes_only, &StripableList::rbegin, &StripableList::rend);
147 }
148 
149 
150 bool
select_stripable_and_maybe_group(boost::shared_ptr<Stripable> s,bool with_group,bool routes_only,RouteGroup * not_allowed_in_group)151 CoreSelection::select_stripable_and_maybe_group (boost::shared_ptr<Stripable> s, bool with_group, bool routes_only, RouteGroup* not_allowed_in_group)
152 {
153 	boost::shared_ptr<Route> r;
154 	StripableList sl;
155 
156 	/* no selection of hidden stripables (though they can be selected and
157 	 * then hidden
158 	 */
159 
160 	if (s->is_hidden()) {
161 		return false;
162 	}
163 
164 	/* monitor is never selectable */
165 
166 	if (s->is_monitor()) {
167 		return false;
168 	}
169 
170 	if ((r = boost::dynamic_pointer_cast<Route> (s))) {
171 
172 		/* no selection of inactive routes, though they can be selected
173 		 * and made inactive.
174 		 */
175 
176 		if (!r->active()) {
177 			return false;
178 		}
179 
180 		if (with_group) {
181 
182 			if (!not_allowed_in_group || !r->route_group() || r->route_group() != not_allowed_in_group) {
183 
184 				if (r->route_group() && r->route_group()->is_select() && r->route_group()->is_active()) {
185 					boost::shared_ptr<RouteList> rl = r->route_group()->route_list ();
186 					for (RouteList::iterator ri = rl->begin(); ri != rl->end(); ++ri) {
187 						if (*ri != r) {
188 							sl.push_back (*ri);
189 						}
190 					}
191 				}
192 
193 				/* it is important to make the "primary" stripable being selected the last in this
194 				 * list
195 				 */
196 
197 				sl.push_back (s);
198 				set (sl);
199 				return true;
200 			}
201 
202 		} else {
203 			set (s, boost::shared_ptr<AutomationControl>());
204 			return true;
205 		}
206 
207 	} else if (!routes_only) {
208 		set (s, boost::shared_ptr<AutomationControl>());
209 		return true;
210 	}
211 
212 	return false;
213 }
214 
215 void
toggle(boost::shared_ptr<Stripable> s,boost::shared_ptr<AutomationControl> c)216 CoreSelection::toggle (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
217 {
218 	DEBUG_TRACE (DEBUG::Selection, string_compose ("toggle: s %1 selected %2 c %3 selected %4\n",
219 	                                               s, selected (s), c, selected (c)));
220 	if ((c && selected (c)) || selected (s)) {
221 		remove (s, c);
222 	} else {
223 		add (s, c);
224 	}
225 }
226 
227 void
set(StripableList & sl)228 CoreSelection::set (StripableList& sl)
229 {
230 	bool send = false;
231 	boost::shared_ptr<AutomationControl> no_control;
232 
233 	std::vector<boost::shared_ptr<Stripable> > removed;
234 
235 	{
236 		Glib::Threads::RWLock::WriterLock lm (_lock);
237 
238 		removed.reserve (_stripables.size());
239 
240 		for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
241 			boost::shared_ptr<Stripable> sp = session.stripable_by_id ((*x).stripable);
242 			if (sp) {
243 				removed.push_back (sp);
244 			}
245 		}
246 
247 		_stripables.clear ();
248 
249 		for (StripableList::iterator s = sl.begin(); s != sl.end(); ++s) {
250 
251 			SelectedStripable ss (*s, no_control, g_atomic_int_add (&_selection_order, 1));
252 
253 			if (_stripables.insert (ss).second) {
254 				DEBUG_TRACE (DEBUG::Selection, string_compose ("set:added %1 to s/c selection\n", (*s)->name()));
255 				send = true;
256 			} else {
257 				DEBUG_TRACE (DEBUG::Selection, string_compose ("%1 already in s/c selection\n", (*s)->name()));
258 			}
259 		}
260 
261 		if (sl.size () > 0) {
262 			_first_selected_stripable = sl.back ();
263 		} else {
264 			_first_selected_stripable.reset ();
265 		}
266 	}
267 
268 	if (send || !removed.empty()) {
269 
270 		send_selection_change ();
271 
272 		/* send per-object signal to notify interested parties
273 		   the selection status has changed
274 		*/
275 
276 		PropertyChange pc (Properties::selected);
277 
278 		for (std::vector<boost::shared_ptr<Stripable> >::iterator s = removed.begin(); s != removed.end(); ++s) {
279 			(*s)->presentation_info().PropertyChanged (pc);
280 		}
281 
282 		for (StripableList::iterator s = sl.begin(); s != sl.end(); ++s) {
283 			(*s)->presentation_info().PropertyChanged (pc);
284 		}
285 
286 	}
287 
288 }
289 
290 void
add(boost::shared_ptr<Stripable> s,boost::shared_ptr<AutomationControl> c)291 CoreSelection::add (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
292 {
293 	bool send = false;
294 
295 	{
296 		Glib::Threads::RWLock::WriterLock lm (_lock);
297 
298 		SelectedStripable ss (s, c, g_atomic_int_add (&_selection_order, 1));
299 
300 		if (_stripables.insert (ss).second) {
301 			DEBUG_TRACE (DEBUG::Selection, string_compose ("added %1/%2 to s/c selection\n", s->name(), c));
302 			send = true;
303 		} else {
304 			DEBUG_TRACE (DEBUG::Selection, string_compose ("%1/%2 already in s/c selection\n", s->name(), c));
305 		}
306 		_first_selected_stripable = s;
307 	}
308 
309 	if (send) {
310 		send_selection_change ();
311 		/* send per-object signal to notify interested parties
312 		   the selection status has changed
313 		*/
314 		if (s) {
315 			PropertyChange pc (Properties::selected);
316 			s->presentation_info().PropertyChanged (pc);
317 		}
318 	}
319 }
320 
321 void
remove(boost::shared_ptr<Stripable> s,boost::shared_ptr<AutomationControl> c)322 CoreSelection::remove (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
323 {
324 	bool send = false;
325 	{
326 		Glib::Threads::RWLock::WriterLock lm (_lock);
327 
328 		SelectedStripable ss (s, c, 0);
329 
330 		SelectedStripables::iterator i = _stripables.find (ss);
331 
332 		if (i != _stripables.end()) {
333 			_stripables.erase (i);
334 			DEBUG_TRACE (DEBUG::Selection, string_compose ("removed %1/%2 from s/c selection\n", s, c));
335 			send = true;
336 		}
337 		if (s == _first_selected_stripable.lock ()) {
338 			_first_selected_stripable.reset ();
339 		}
340 	}
341 
342 	if (send) {
343 		send_selection_change ();
344 		/* send per-object signal to notify interested parties
345 		   the selection status has changed
346 		*/
347 		if (s) {
348 			PropertyChange pc (Properties::selected);
349 			s->presentation_info().PropertyChanged (pc);
350 		}
351 	}
352 }
353 
354 void
set(boost::shared_ptr<Stripable> s,boost::shared_ptr<AutomationControl> c)355 CoreSelection::set (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
356 {
357 	{
358 		Glib::Threads::RWLock::WriterLock lm (_lock);
359 
360 		SelectedStripable ss (s, c, g_atomic_int_add (&_selection_order, 1));
361 
362 		if (_stripables.size() == 1 && _stripables.find (ss) != _stripables.end()) {
363 			return;
364 		}
365 
366 		_stripables.clear ();
367 		_stripables.insert (ss);
368 		_first_selected_stripable = s;
369 		DEBUG_TRACE (DEBUG::Selection, string_compose ("set s/c selection to %1/%2\n", s->name(), c));
370 	}
371 
372 	send_selection_change ();
373 
374 	/* send per-object signal to notify interested parties
375 	   the selection status has changed
376 	*/
377 	if (s) {
378 		PropertyChange pc (Properties::selected);
379 		s->presentation_info().PropertyChanged (pc);
380 	}
381 }
382 
383 void
clear_stripables()384 CoreSelection::clear_stripables ()
385 {
386 	bool send = false;
387 	std::vector<boost::shared_ptr<Stripable> > s;
388 
389 	DEBUG_TRACE (DEBUG::Selection, "clearing s/c selection\n");
390 	{
391 		Glib::Threads::RWLock::WriterLock lm (_lock);
392 
393 		if (!_stripables.empty()) {
394 
395 			s.reserve (_stripables.size());
396 
397 			for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
398 				boost::shared_ptr<Stripable> sp = session.stripable_by_id ((*x).stripable);
399 				if (sp) {
400 					s.push_back (sp);
401 				}
402 			}
403 
404 			_stripables.clear ();
405 
406 			send = true;
407 			DEBUG_TRACE (DEBUG::Selection, "cleared s/c selection\n");
408 		}
409 
410 		_first_selected_stripable.reset ();
411 	}
412 
413 	if (send) {
414 		send_selection_change ();
415 
416 		PropertyChange pc (Properties::selected);
417 
418 		for (std::vector<boost::shared_ptr<Stripable> >::iterator ss = s.begin(); ss != s.end(); ++ss) {
419 			(*ss)->presentation_info().PropertyChanged (pc);
420 		}
421 
422 	}
423 }
424 
425 boost::shared_ptr<Stripable>
first_selected_stripable() const426 CoreSelection::first_selected_stripable () const
427 {
428 	Glib::Threads::RWLock::ReaderLock lm (_lock);
429   return _first_selected_stripable.lock();
430 }
431 
432 bool
selected(boost::shared_ptr<const Stripable> s) const433 CoreSelection::selected (boost::shared_ptr<const Stripable> s) const
434 {
435 	if (!s) {
436 		return false;
437 	}
438 
439 	Glib::Threads::RWLock::ReaderLock lm (_lock);
440 
441 	for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
442 
443 		if (!((*x).controllable == 0)) {
444 			/* selected automation control */
445 			continue;
446 		}
447 
448 		/* stripable itself selected, not just a control belonging to
449 		 * it
450 		 */
451 
452 		if ((*x).stripable == s->id()) {
453 			return true;
454 		}
455 	}
456 
457 	return false;
458 }
459 
460 bool
selected(boost::shared_ptr<const AutomationControl> c) const461 CoreSelection::selected (boost::shared_ptr<const AutomationControl> c) const
462 {
463 	if (!c) {
464 		return false;
465 	}
466 
467 	Glib::Threads::RWLock::ReaderLock lm (_lock);
468 
469 	for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
470 		if ((*x).controllable == c->id()) {
471 			return true;
472 		}
473 	}
474 
475 	return false;
476 }
477 
SelectedStripable(boost::shared_ptr<Stripable> s,boost::shared_ptr<AutomationControl> c,int o)478 CoreSelection::SelectedStripable::SelectedStripable (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c, int o)
479 	: stripable (s ? s->id() : PBD::ID (0))
480 	, controllable (c ? c->id() : PBD::ID (0))
481 	, order (o)
482 {
483 }
484 
485 struct StripableControllerSort {
operator ()StripableControllerSort486 	bool operator() (CoreSelection::StripableAutomationControl const &a, CoreSelection::StripableAutomationControl const & b) const {
487 		return a.order < b.order;
488 	}
489 };
490 
491 void
get_stripables(StripableAutomationControls & sc) const492 CoreSelection::get_stripables (StripableAutomationControls& sc) const
493 {
494 	Glib::Threads::RWLock::ReaderLock lm (_lock);
495 
496 	for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
497 
498 		boost::shared_ptr<Stripable> s = session.stripable_by_id ((*x).stripable);
499 		boost::shared_ptr<AutomationControl> c;
500 
501 		if (!s) {
502 			/* some global automation control, not owned by a Stripable */
503 			c = session.automation_control_by_id ((*x).controllable);
504 		} else {
505 			/* automation control owned by a Stripable or one of its children */
506 			c = s->automation_control_recurse ((*x).controllable);
507 		}
508 
509 		if (s || c) {
510 			sc.push_back (StripableAutomationControl (s, c, (*x).order));
511 		}
512 	}
513 
514 	StripableControllerSort cmp;
515 	sort (sc.begin(), sc.end(), cmp);
516 }
517 
518 void
remove_control_by_id(PBD::ID const & id)519 CoreSelection::remove_control_by_id (PBD::ID const & id)
520 {
521 	Glib::Threads::RWLock::WriterLock lm (_lock);
522 
523 	for (SelectedStripables::iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
524 		if ((*x).controllable == id) {
525 			_stripables.erase (x);
526 			return;
527 		}
528 	}
529 }
530 
531 void
remove_stripable_by_id(PBD::ID const & id)532 CoreSelection::remove_stripable_by_id (PBD::ID const & id)
533 {
534 	Glib::Threads::RWLock::WriterLock lm (_lock);
535 
536 	for (SelectedStripables::iterator x = _stripables.begin(); x != _stripables.end(); ) {
537 		if ((*x).stripable == id) {
538 			if (_first_selected_stripable.lock ()) {
539 				if (session.stripable_by_id (id) == _first_selected_stripable.lock ()) {
540 					_first_selected_stripable.reset ();
541 				}
542 			}
543 
544 			_stripables.erase (x++);
545 			/* keep going because there may be more than 1 pair of
546 			   stripable/automation-control in the selection.
547 			*/
548 		} else {
549 			++x;
550 		}
551 	}
552 }
553 
554 XMLNode&
get_state(void)555 CoreSelection::get_state (void)
556 {
557 	XMLNode* node = new XMLNode (X_("Selection"));
558 
559 	Glib::Threads::RWLock::WriterLock lm (_lock);
560 
561 	for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
562 		XMLNode* child = new XMLNode (X_("StripableAutomationControl"));
563 		child->set_property (X_("stripable"), (*x).stripable.to_s());
564 		child->set_property (X_("control"), (*x).controllable.to_s());
565 		child->set_property (X_("order"), (*x).order);
566 
567 		node->add_child_nocopy (*child);
568 	}
569 
570 	return *node;
571 }
572 int
set_state(const XMLNode & node,int)573 CoreSelection::set_state (const XMLNode& node, int /* version */)
574 {
575 	XMLNodeList children (node.children());
576 	Glib::Threads::RWLock::WriterLock lm (_lock);
577 
578 	_stripables.clear ();
579 
580 	for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
581 		if ((*i)->name() != X_("StripableAutomationControl")) {
582 			continue;
583 		}
584 
585 		std::string s;
586 
587 		if (!(*i)->get_property (X_("stripable"), s)) {
588 			continue;
589 		}
590 
591 		std::string c;
592 
593 		if (!(*i)->get_property (X_("control"), c)) {
594 			continue;
595 		}
596 
597 		int order;
598 
599 		if (!(*i)->get_property (X_("order"), order)) {
600 			continue;
601 		}
602 
603 		SelectedStripable ss (PBD::ID (s), PBD::ID (c), order);
604 		_stripables.insert (ss);
605 	}
606 
607 	return 0;
608 }
609 
610 uint32_t
selected() const611 CoreSelection::selected () const
612 {
613 	Glib::Threads::RWLock::ReaderLock lm (_lock);
614 	return _stripables.size();
615 }
616