Replace all use of PathScanner with equivalent functions from pbd/file_utils.h
[ardour.git] / libs / surfaces / mackie / device_profile.cc
1 /*
2         Copyright (C) 2006,2007 John Anderson
3         Copyright (C) 2012 Paul Davis
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
16         along with this program; if not, write to the Free Software
17         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <cerrno>
21 #include <cstdlib>
22 #include <cstring>
23 #include <glibmm/miscutils.h>
24
25 #include "pbd/xml++.h"
26 #include "pbd/error.h"
27 #include "pbd/file_utils.h"
28 #include "pbd/stl_delete.h"
29 #include "pbd/replace_all.h"
30
31 #include "ardour/filesystem_paths.h"
32
33 #include "mackie_control_protocol.h"
34 #include "device_profile.h"
35
36 #include "i18n.h"
37
38 using namespace Mackie;
39 using namespace PBD;
40 using namespace ARDOUR;
41 using std::string;
42 using std::vector;
43
44 std::map<std::string,DeviceProfile> DeviceProfile::device_profiles;
45
46 DeviceProfile::DeviceProfile (const string& n)
47         : _name (n)
48 {
49 }
50
51 DeviceProfile::~DeviceProfile()
52 {
53 }
54
55 static const char * const devprofile_env_variable_name = "ARDOUR_MCP_PATH";
56 static const char* const devprofile_dir_name = "mcp";
57 static const char* const devprofile_suffix = ".profile";
58
59 static Searchpath
60 devprofile_search_path ()
61 {
62         bool devprofile_path_defined = false;
63         std::string spath_env (Glib::getenv (devprofile_env_variable_name, devprofile_path_defined));
64
65         if (devprofile_path_defined) {
66                 return spath_env;
67         }
68
69         Searchpath spath (ardour_data_search_path());
70         spath.add_subdirectory_to_paths(devprofile_dir_name);
71
72         return spath;
73 }
74
75 static std::string
76 user_devprofile_directory ()
77 {
78         return Glib::build_filename (user_config_directory(), devprofile_dir_name);
79 }
80
81 static bool
82 devprofile_filter (const string &str, void* /*arg*/)
83 {
84         return (str.length() > strlen(devprofile_suffix) &&
85                 str.find (devprofile_suffix) == (str.length() - strlen (devprofile_suffix)));
86 }
87
88 void
89 DeviceProfile::reload_device_profiles ()
90 {
91         DeviceProfile dp;
92         vector<string> s;
93         vector<string> devprofiles;
94         Searchpath spath (devprofile_search_path());
95
96         find_files_matching_filter (devprofiles, spath.to_string(), devprofile_filter, 0, false, true);
97         device_profiles.clear ();
98
99         if (devprofiles.empty()) {
100                 error << "No MCP device info files found using " << spath.to_string() << endmsg;
101                 return;
102         }
103
104         for (vector<string>::iterator i = devprofiles.begin(); i != devprofiles.end(); ++i) {
105                 string fullpath = *i;
106
107                 XMLTree tree;
108
109                 if (!tree.read (fullpath.c_str())) {
110                         continue;
111                 }
112
113                 XMLNode* root = tree.root ();
114                 if (!root) {
115                         continue;
116                 }
117
118                 if (dp.set_state (*root, 3000) == 0) { /* version is ignored for now */
119                         dp.set_path (fullpath);
120                         device_profiles[dp.name()] = dp;
121                 }
122         }
123 }
124
125 int
126 DeviceProfile::set_state (const XMLNode& node, int /* version */)
127 {
128         const XMLProperty* prop;
129         const XMLNode* child;
130
131         if (node.name() != "MackieDeviceProfile") {
132                 return -1;
133         }
134
135         /* name is mandatory */
136  
137         if ((child = node.child ("Name")) == 0 || (prop = child->property ("value")) == 0) {
138                 return -1;
139         } else {
140                 _name = prop->value();
141         }
142
143         if ((child = node.child ("Buttons")) != 0) {
144                 XMLNodeConstIterator i;
145                 const XMLNodeList& nlist (child->children());
146
147                 for (i = nlist.begin(); i != nlist.end(); ++i) {
148
149                         if ((*i)->name() == "Button") {
150
151                                 if ((prop = (*i)->property ("name")) == 0) {
152                                         error << string_compose ("Button without name in device profile \"%1\" - ignored", _name) << endmsg;
153                                         continue;
154                                 }
155
156                                 int id = Button::name_to_id (prop->value());
157                                 if (id < 0) {
158                                         error << string_compose ("Unknow button ID \"%1\"", prop->value()) << endmsg;
159                                         continue;
160                                 }
161
162                                 Button::ID bid = (Button::ID) id;
163
164                                 ButtonActionMap::iterator b = _button_map.find (bid);
165
166                                 if (b == _button_map.end()) {
167                                         b = _button_map.insert (_button_map.end(), std::pair<Button::ID,ButtonActions> (bid, ButtonActions()));
168                                 }
169
170                                 if ((prop = (*i)->property ("plain")) != 0) {
171                                         b->second.plain = prop->value ();
172                                 }
173                                 if ((prop = (*i)->property ("control")) != 0) {
174                                         b->second.control = prop->value ();
175                                 }
176                                 if ((prop = (*i)->property ("shift")) != 0) {
177                                         b->second.shift = prop->value ();
178                                 }
179                                 if ((prop = (*i)->property ("option")) != 0) {
180                                         b->second.option = prop->value ();
181                                 }
182                                 if ((prop = (*i)->property ("cmdalt")) != 0) {
183                                         b->second.cmdalt = prop->value ();
184                                 }
185                                 if ((prop = (*i)->property ("shiftcontrol")) != 0) {
186                                         b->second.shiftcontrol = prop->value ();
187                                 }
188                         }
189                 }
190         }
191
192         return 0;
193 }
194
195 XMLNode&
196 DeviceProfile::get_state () const
197 {
198         XMLNode* node = new XMLNode ("MackieDeviceProfile");
199         XMLNode* child = new XMLNode ("Name");
200
201         child->add_property ("value", _name);
202         node->add_child_nocopy (*child);
203
204         if (_button_map.empty()) {
205                 return *node;
206         }
207
208         XMLNode* buttons = new XMLNode ("Buttons");
209         node->add_child_nocopy (*buttons);
210
211         for (ButtonActionMap::const_iterator b = _button_map.begin(); b != _button_map.end(); ++b) {
212                 XMLNode* n = new XMLNode ("Button");
213
214                 n->add_property ("name", Button::id_to_name (b->first));
215
216                 if (!b->second.plain.empty()) {
217                         n->add_property ("plain", b->second.plain);
218                 }
219                 if (!b->second.control.empty()) {
220                         n->add_property ("control", b->second.control);
221                 }
222                 if (!b->second.shift.empty()) {
223                         n->add_property ("shift", b->second.shift);
224                 }
225                 if (!b->second.option.empty()) {
226                         n->add_property ("option", b->second.option);
227                 }
228                 if (!b->second.cmdalt.empty()) {
229                         n->add_property ("cmdalt", b->second.cmdalt);
230                 }
231                 if (!b->second.shiftcontrol.empty()) {
232                         n->add_property ("shiftcontrol", b->second.shiftcontrol);
233                 }
234
235                 buttons->add_child_nocopy (*n);
236         }
237
238         return *node;
239 }
240
241 string
242 DeviceProfile::get_button_action (Button::ID id, int modifier_state) const
243 {
244         ButtonActionMap::const_iterator i = _button_map.find (id);
245
246         if (i == _button_map.end()) {
247                 return string();
248         }
249
250         if (modifier_state == MackieControlProtocol::MODIFIER_CONTROL) {
251                 return i->second.control;
252         } else if (modifier_state == MackieControlProtocol::MODIFIER_SHIFT) {
253                 return i->second.shift;
254         } else if (modifier_state == MackieControlProtocol::MODIFIER_OPTION) {
255                 return i->second.option;
256         } else if (modifier_state == MackieControlProtocol::MODIFIER_CMDALT) {
257                 return i->second.cmdalt;
258         } else if (modifier_state == (MackieControlProtocol::MODIFIER_CONTROL|MackieControlProtocol::MODIFIER_SHIFT)) {
259                 return i->second.shiftcontrol;
260         }
261
262         return i->second.plain;
263 }
264
265 void
266 DeviceProfile::set_button_action (Button::ID id, int modifier_state, const string& act)
267 {
268         ButtonActionMap::iterator i = _button_map.find (id);
269
270         if (i == _button_map.end()) {
271                 i = _button_map.insert (std::make_pair (id, ButtonActions())).first;
272         }
273
274         string action (act);
275         replace_all (action, "<Actions>/", "");
276
277         if (modifier_state == MackieControlProtocol::MODIFIER_CONTROL) {
278                 i->second.control = action;
279         } else if (modifier_state == MackieControlProtocol::MODIFIER_SHIFT) {
280                 i->second.shift = action;
281         } else if (modifier_state == MackieControlProtocol::MODIFIER_OPTION) {
282                 i->second.option = action;
283         } else if (modifier_state == MackieControlProtocol::MODIFIER_CMDALT) {
284                 i->second.cmdalt = action;
285         } else if (modifier_state == (MackieControlProtocol::MODIFIER_CONTROL|MackieControlProtocol::MODIFIER_SHIFT)) {
286                 i->second.shiftcontrol = action;
287         }
288
289         if (modifier_state == 0) {
290                 i->second.plain = action;
291         }
292
293         save ();
294 }
295
296 const string&
297 DeviceProfile::name() const
298 {
299         return _name;
300 }
301
302 void
303 DeviceProfile::set_path (const string& p)
304 {
305         _path = p;
306 }
307
308 /* XXX copied from libs/ardour/utils.cc */
309
310 static string
311 legalize_for_path (const string& str)
312 {
313         string::size_type pos;
314         string illegal_chars = "/\\"; /* DOS, POSIX. Yes, we're going to ignore HFS */
315         string legal;
316
317         legal = str;
318         pos = 0;
319
320         while ((pos = legal.find_first_of (illegal_chars, pos)) != string::npos) {
321                 legal.replace (pos, 1, "_");
322                 pos += 1;
323         }
324
325         return string (legal);
326 }
327
328
329 void
330 DeviceProfile::save ()
331 {
332         std::string fullpath = user_devprofile_directory();
333
334         if (g_mkdir_with_parents (fullpath.c_str(), 0755) < 0) {
335                 error << string_compose(_("Session: cannot create user MCP profile folder \"%1\" (%2)"), fullpath, strerror (errno)) << endmsg;
336                 return;
337         }
338
339         fullpath = Glib::build_filename (fullpath, legalize_for_path (_name) + ".profile");
340         
341         XMLTree tree;
342         tree.set_root (&get_state());
343
344         if (!tree.write (fullpath)) {
345                 error << string_compose ("MCP profile not saved to %1", fullpath) << endmsg;
346         }
347 }
348