1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 23 дек. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/ctl/ctl.h>
23 
24 namespace lsp
25 {
26     namespace ctl
27     {
28         typedef struct file_format_t
29         {
30             const char *id;
31             const char *filter;
32             const char *text;
33             const char *ext;
34             size_t flags;
35         } file_format_t;
36 
37         static const file_format_t file_formats[] =
38         {
39             { "wav", "*.wav", "files.audio.wave", ".wav", LSPFileMask::NONE },
40             { "lspc", "*.lspc", "files.config.lspc", ".lspc", LSPFileMask::NONE },
41             { "cfg", "*.cfg", "files.config.lsp", ".cfg", LSPFileMask::NONE },
42             { "audio", "*.wav", "files.audio.supported", ".wav", LSPFileMask::NONE },
43             { "audio_lspc", "*.wav|*.lspc", "files.audio.audio_lspc", ".wav", LSPFileMask::NONE },
44             { "obj3d", "*.obj", "files.3d.wavefont", ".obj", LSPFileMask::NONE },
45             { "all", "*", "files.all", "", LSPFileMask::NONE },
46             { NULL, NULL, NULL, 0 }
47         };
48 
add_format(LSPFileFilter * flt,const char * variable,size_t n)49         void add_format(LSPFileFilter *flt, const char *variable, size_t n)
50         {
51             for (const file_format_t *f = file_formats; f->id != NULL; ++f)
52             {
53                 if (!strncasecmp(f->id, variable, n))
54                 {
55                     LSPFileFilterItem ffi;
56                     ffi.pattern()->set(f->filter, f->flags);
57                     ffi.title()->set(f->text);
58                     ffi.set_extension(f->ext);
59 
60                     flt->add(&ffi);
61                     return;
62                 }
63             }
64         }
65 
parse_file_formats(const char * variable,LSPFileFilter * flt)66         bool parse_file_formats(const char *variable, LSPFileFilter *flt)
67         {
68             status_t res = flt->clear();
69             if (res != STATUS_OK)
70                 return res;
71 
72             while (true)
73             {
74                 while (*variable == ' ')
75                     variable ++;
76                 if (*variable == '\0')
77                     break;
78 
79                 const char *s = strchr(variable, ',');
80                 const char *end = (s == NULL) ? strchr(variable, '\0') : s;
81                 while ((end > variable) && (end[-1] == ' '))
82                     --end;
83 
84                 if (end > variable)
85                     add_format(flt, variable, end - variable);
86 
87                 if (s == NULL)
88                     break;
89                 variable = s + 1;
90             }
91 
92             return true;
93         }
94 
parse_relative_path(io::Path * path,const io::Path * base,const char * value,size_t len)95         bool parse_relative_path(io::Path *path, const io::Path *base, const char *value, size_t len)
96         {
97             if ((base == NULL) || (len <= 0))
98                 return false;
99 
100             LSPString svalue;
101             if (!svalue.set_utf8(value, len))
102                 return false;
103 
104             if (svalue.starts_with_ascii("builtin://"))
105                 return path->set(&svalue) == STATUS_OK;
106 
107             // This method won't accept absolute path stored in svalue
108             if (path->set(base, &svalue) != STATUS_OK)
109                 return false;
110 
111             return (path->canonicalize() == STATUS_OK);
112         }
113 
set_port_value(CtlPort * up,const char * value,size_t flags,const io::Path * base)114         bool set_port_value(CtlPort *up, const char *value, size_t flags, const io::Path *base)
115         {
116             if (up == NULL)
117                 return false;
118 
119             // Get metadata
120             const port_t *p = up->metadata();
121             if (p == NULL)
122                 return false;
123 
124             // Check that it's a control port
125             if (!IS_IN_PORT(p))
126                 return false;
127 
128             // Apply changes
129             switch (p->role)
130             {
131                 case R_PORT_SET:
132                 case R_CONTROL:
133                 {
134                     if (is_discrete_unit(p->unit))
135                     {
136                         if (p->unit == U_BOOL)
137                         {
138                             PARSE_BOOL(value, up->set_value(__, flags); );
139                         }
140                         else
141                         {
142                             PARSE_INT(value, up->set_value(__, flags); );
143                         }
144                     }
145                     else
146                     {
147                         PARSE_FLOAT(value, up->set_value(__, flags); );
148                     }
149                     break;
150                 }
151                 case R_PATH:
152                 {
153                     size_t len      = ::strlen(value);
154                     io::Path path;
155 
156                     if (parse_relative_path(&path, base, value, len))
157                     {
158                         // Update value and it's length
159                         value   = path.as_utf8();
160                         len     = strlen(value);
161                     }
162 
163                     up->write(value, len, flags);
164                     break;
165                 }
166                 default:
167                     return false;
168             }
169             return true;
170         }
171 
format_relative_path(LSPString * value,const char * path,const io::Path * base)172         bool format_relative_path(LSPString *value, const char *path, const io::Path *base)
173         {
174             if (base == NULL)
175                 return false;
176 
177             io::Path xpath;
178             if (xpath.set(path) != STATUS_OK)
179                 return false;
180             if (xpath.as_relative(base) != STATUS_OK)
181                 return false;
182 
183             return value->append(xpath.as_string());
184         }
185 
format_port_value(CtlPort * up,LSPString * name,LSPString * value,LSPString * comment,int * flags,const io::Path * base)186         status_t format_port_value(CtlPort *up, LSPString *name, LSPString *value, LSPString *comment, int *flags, const io::Path *base)
187         {
188             // Get metadata
189             const port_t *p    = up->metadata();
190             if (p == NULL)
191                 return STATUS_OK;
192 
193             switch (p->role)
194             {
195                 case R_PORT_SET:
196                 case R_CONTROL:
197                 {
198                     // Serialize meta information
199                     const char *unit = encode_unit(p->unit);
200                     if (unit != NULL)
201                         LSP_BOOL_ASSERT(comment->fmt_append_utf8("%s [%s]", p->name, unit), STATUS_NO_MEM)
202                     else if (p->unit == U_BOOL)
203                         LSP_BOOL_ASSERT(comment->fmt_append_utf8("%s [boolean]", p->name), STATUS_NO_MEM)
204                     else
205                         LSP_BOOL_ASSERT(comment->append_utf8(p->name), STATUS_NO_MEM);
206 
207                     if ((p->flags & (F_LOWER | F_UPPER)) || (p->unit == U_ENUM) || (p->unit == U_BOOL))
208                     {
209                         if (is_discrete_unit(p->unit) || (p->flags & F_INT))
210                         {
211                             if (p->unit != U_BOOL)
212                             {
213                                 if (p->unit == U_ENUM)
214                                 {
215                                     int value       = p->min + list_size(p->items) - 1;
216                                     LSP_BOOL_ASSERT(comment->fmt_append_utf8(": %d..%d", int(p->min), int(value)), STATUS_NO_MEM);
217                                 }
218                                 else
219                                     LSP_BOOL_ASSERT(comment->fmt_append_utf8(": %d..%d", int(p->min), int(p->max)), STATUS_NO_MEM);
220                             }
221                             else
222                                 LSP_BOOL_ASSERT(comment->append_utf8(": true/false"), STATUS_NO_MEM);
223                         }
224                         else if (!(p->flags & F_EXT))
225                         {
226                             LSP_BOOL_ASSERT(comment->fmt_append_utf8(": %.8f..%.8f", p->min, p->max), STATUS_NO_MEM);
227                         }
228                         else
229                         {
230                             LSP_BOOL_ASSERT(comment->fmt_append_utf8(": %.12f..%.12f", p->min, p->max), STATUS_NO_MEM);
231                         }
232                     }
233 
234                     // Describe enum
235                     if ((p->unit == U_ENUM) && (p->items != NULL))
236                     {
237                         int value   = p->min;
238                         for (const port_item_t *item = p->items; item->text != NULL; ++item)
239                             LSP_BOOL_ASSERT(comment->fmt_append_utf8("\n  %d: %s", value++, item->text), STATUS_NO_MEM);
240                     }
241 
242                     // Serialize name
243                     LSP_BOOL_ASSERT(name->append_utf8(p->id), STATUS_NO_MEM);
244 
245                     // Serialize value
246                     float v = up->get_value();
247                     if (is_discrete_unit(p->unit) || (p->flags & F_INT))
248                     {
249                         if (p->unit == U_BOOL)
250                             LSP_BOOL_ASSERT(value->append_utf8((v >= 0.5f) ? "true" : "false"), STATUS_NO_MEM)
251                         else
252                             LSP_BOOL_ASSERT(value->fmt_utf8("%d", int(v)), STATUS_NO_MEM);
253                     }
254                     else if (!(p->flags & F_EXT))
255                     {
256                         LSP_BOOL_ASSERT(value->fmt_utf8("%.8f", v), STATUS_NO_MEM);
257                     }
258                     else
259                     {
260                         LSP_BOOL_ASSERT(value->fmt_utf8("%.12f", v), STATUS_NO_MEM);
261                     }
262 
263                     // No flags
264                     *flags = 0;
265                     break;
266                 }
267                 case R_PATH:
268                 {
269                     LSP_BOOL_ASSERT(comment->fmt_append_utf8("%s [pathname]", p->name), STATUS_NO_MEM);
270                     LSP_BOOL_ASSERT(name->append_utf8(p->id), STATUS_NO_MEM);
271 
272                     const char *path    = up->get_buffer<const char>();
273                     if ((path != NULL) && (strlen(path) > 0))
274                     {
275                         if (!format_relative_path(value, path, base))
276                         {
277                             LSP_BOOL_ASSERT(value->append_utf8(path), STATUS_NO_MEM)
278                         }
279                     }
280                     else
281                         LSP_BOOL_ASSERT(value->append_utf8(""), STATUS_NO_MEM);
282 
283                     // No flags
284                     *flags = config::SF_QUOTED;
285                     break;
286                 }
287                 default:
288                     return STATUS_BAD_TYPE;
289             }
290             return STATUS_OK;
291         }
292     }
293 }
294 
295