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