emit per-Stateful PropertyChanged signal when Stripables become selected
[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/selection.h"
28 #include "ardour/session.h"
29 #include "ardour/stripable.h"
30
31 using namespace ARDOUR;
32 using namespace PBD;
33
34 void
35 CoreSelection::send_selection_change ()
36 {
37         PropertyChange pc;
38         pc.add (Properties::selected);
39         PresentationInfo::send_static_change (pc);
40 }
41
42 CoreSelection::CoreSelection (Session& s)
43         : session (s)
44         , selection_order (0)
45 {
46 }
47
48 CoreSelection::~CoreSelection ()
49 {
50 }
51
52 void
53 CoreSelection::toggle (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
54 {
55         DEBUG_TRACE (DEBUG::Selection, string_compose ("toggle: s %1 selected %2 c %3 selected %4\n",
56                                                        s, selected (s), c, selected (c)));
57         if ((c && selected (c)) || selected (s)) {
58                 remove (s, c);
59         } else {
60                 add (s, c);
61         }
62 }
63
64 void
65 CoreSelection::add (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
66 {
67         bool send = false;
68
69         {
70                 Glib::Threads::RWLock::WriterLock lm (_lock);
71
72                 SelectedStripable ss (s, c, g_atomic_int_add (&selection_order, 1));
73
74                 if (_stripables.insert (ss).second) {
75                         DEBUG_TRACE (DEBUG::Selection, string_compose ("added %1/%2 to s/c selection\n", s->name(), c));
76                         /* send per-object signal to notify interested parties
77                            the selection status has changed
78                         */
79                         if (s) {
80                                 PropertyChange pc (Properties::selected);
81                                 s->PropertyChanged (pc);
82                         }
83                         send = true;
84                 } else {
85                         DEBUG_TRACE (DEBUG::Selection, string_compose ("%1/%2 already in s/c selection\n", s->name(), c));
86                 }
87         }
88
89         if (send) {
90                 send_selection_change ();
91         }
92 }
93
94 void
95 CoreSelection::remove (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
96 {
97         bool send = false;
98         {
99                 Glib::Threads::RWLock::WriterLock lm (_lock);
100
101                 SelectedStripable ss (s, c, 0);
102
103                 SelectedStripables::iterator i = _stripables.find (ss);
104
105                 if (i != _stripables.end()) {
106                         _stripables.erase (i);
107                         DEBUG_TRACE (DEBUG::Selection, string_compose ("removed %1/%2 from s/c selection\n", s, c));
108                         /* send per-object signal to notify interested parties
109                            the selection status has changed
110                         */
111                         if (s) {
112                                 PropertyChange pc (Properties::selected);
113                                 s->PropertyChanged (pc);
114                         }
115                         send = true;
116                 }
117         }
118
119         if (send) {
120                 send_selection_change ();
121         }
122 }
123
124 void
125 CoreSelection::set (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c)
126 {
127         {
128                 Glib::Threads::RWLock::WriterLock lm (_lock);
129
130                 SelectedStripable ss (s, c, g_atomic_int_add (&selection_order, 1));
131
132                 if (_stripables.size() == 1 && _stripables.find (ss) != _stripables.end()) {
133                         return;
134                 }
135
136                 _stripables.clear ();
137                 _stripables.insert (ss);
138                 DEBUG_TRACE (DEBUG::Selection, string_compose ("set s/c selection to %1/%2\n", s->name(), c));
139                 /* send per-object signal to notify interested parties
140                    the selection status has changed
141                 */
142                 if (s) {
143                         PropertyChange pc (Properties::selected);
144                         s->PropertyChanged (pc);
145                 }
146         }
147
148         send_selection_change ();
149 }
150
151 void
152 CoreSelection::clear_stripables ()
153 {
154         bool send = false;
155         std::vector<boost::shared_ptr<Stripable> > s;
156
157         DEBUG_TRACE (DEBUG::Selection, "clearing s/c selection\n");
158         {
159                 Glib::Threads::RWLock::WriterLock lm (_lock);
160
161                 if (!_stripables.empty()) {
162
163                         s.reserve (_stripables.size());
164
165                         for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
166                                 boost::shared_ptr<Stripable> sp = session.stripable_by_id ((*x).stripable);
167                                 if (sp) {
168                                         s.push_back (sp);
169                                 }
170                         }
171
172                         _stripables.clear ();
173
174                         send = true;
175                         DEBUG_TRACE (DEBUG::Selection, "cleared s/c selection\n");
176                 }
177         }
178
179         if (send) {
180                 PropertyChange pc (Properties::selected);
181
182                 for (std::vector<boost::shared_ptr<Stripable> >::iterator ss = s.begin(); ss != s.end(); ++ss) {
183                         (*ss)->PropertyChanged (pc);
184                 }
185
186                 send_selection_change ();
187         }
188 }
189
190 bool
191 CoreSelection::selected (boost::shared_ptr<const Stripable> s) const
192 {
193         if (!s) {
194                 return false;
195         }
196
197         Glib::Threads::RWLock::ReaderLock lm (_lock);
198
199         for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
200
201                 if (!((*x).controllable == 0)) {
202                         /* selected automation control */
203                         continue;
204                 }
205
206                 /* stripable itself selected, not just a control belonging to
207                  * it
208                  */
209
210                 if ((*x).stripable == s->id()) {
211                         return true;
212                 }
213         }
214
215         return false;
216 }
217
218 bool
219 CoreSelection::selected (boost::shared_ptr<const AutomationControl> c) const
220 {
221         if (!c) {
222                 return false;
223         }
224
225         Glib::Threads::RWLock::ReaderLock lm (_lock);
226
227         for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
228                 if ((*x).controllable == c->id()) {
229                         return true;
230                 }
231         }
232
233         return false;
234 }
235
236 CoreSelection::SelectedStripable::SelectedStripable (boost::shared_ptr<Stripable> s, boost::shared_ptr<AutomationControl> c, int o)
237         : stripable (s ? s->id() : PBD::ID (0))
238         , controllable (c ? c->id() : PBD::ID (0))
239         , order (o)
240 {
241 }
242
243 struct StripableControllerSort {
244         bool operator() (CoreSelection::StripableAutomationControl const &a, CoreSelection::StripableAutomationControl const & b) const {
245                 return a.order < b.order;
246         }
247 };
248
249 void
250 CoreSelection::get_stripables (StripableAutomationControls& sc) const
251 {
252         Glib::Threads::RWLock::ReaderLock lm (_lock);
253
254         for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
255
256                 boost::shared_ptr<Stripable> s = session.stripable_by_id ((*x).stripable);
257                 boost::shared_ptr<AutomationControl> c;
258
259                 if (!s) {
260                         /* some global automation control, not owned by a Stripable */
261                         c = session.automation_control_by_id ((*x).controllable);
262                 } else {
263                         /* automation control owned by a Stripable or one of its children */
264                         c = s->automation_control_recurse ((*x).controllable);
265                 }
266
267                 if (s || c) {
268                         sc.push_back (StripableAutomationControl (s, c, (*x).order));
269                 }
270         }
271
272         StripableControllerSort cmp;
273         sort (sc.begin(), sc.end(), cmp);
274 }
275
276 void
277 CoreSelection::remove_control_by_id (PBD::ID const & id)
278 {
279         Glib::Threads::RWLock::WriterLock lm (_lock);
280
281         for (SelectedStripables::iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
282                 if ((*x).controllable == id) {
283                         _stripables.erase (x);
284                         return;
285                 }
286         }
287 }
288
289 void
290 CoreSelection::remove_stripable_by_id (PBD::ID const & id)
291 {
292         Glib::Threads::RWLock::WriterLock lm (_lock);
293
294         for (SelectedStripables::iterator x = _stripables.begin(); x != _stripables.end(); ) {
295                 if ((*x).stripable == id) {
296                         _stripables.erase (x++);
297                         /* keep going because there may be more than 1 pair of
298                            stripable/automation-control in the selection.
299                         */
300                 } else {
301                         ++x;
302                 }
303         }
304 }
305
306 XMLNode&
307 CoreSelection::get_state (void)
308 {
309         XMLNode* node = new XMLNode (X_("Selection"));
310
311         Glib::Threads::RWLock::WriterLock lm (_lock);
312
313         for (SelectedStripables::const_iterator x = _stripables.begin(); x != _stripables.end(); ++x) {
314                 XMLNode* child = new XMLNode (X_("StripableAutomationControl"));
315                 child->set_property (X_("stripable"), (*x).stripable.to_s());
316                 child->set_property (X_("control"), (*x).controllable.to_s());
317                 child->set_property (X_("order"), (*x).order);
318
319                 node->add_child_nocopy (*child);
320         }
321
322         return *node;
323 }
324 int
325 CoreSelection::set_state (const XMLNode& node, int /* version */)
326 {
327         XMLNodeList children (node.children());
328         Glib::Threads::RWLock::WriterLock lm (_lock);
329
330         _stripables.clear ();
331
332         for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
333                 if ((*i)->name() != X_("StripableAutomationControl")) {
334                         continue;
335                 }
336
337                 std::string s;
338
339                 if (!(*i)->get_property (X_("stripable"), s)) {
340                         continue;
341                 }
342
343                 std::string c;
344
345                 if (!(*i)->get_property (X_("control"), c)) {
346                         continue;
347                 }
348
349                 int order;
350
351                 if (!(*i)->get_property (X_("order"), order)) {
352                         continue;
353                 }
354
355                 SelectedStripable ss (PBD::ID (s), PBD::ID (c), order);
356                 _stripables.insert (ss);
357         }
358
359         std::cerr << "set state: " << _stripables.size() << std::endl;
360
361         return 0;
362 }
363
364 uint32_t
365 CoreSelection::selected () const
366 {
367         Glib::Threads::RWLock::ReaderLock lm (_lock);
368         return _stripables.size();
369 }