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