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: 20 окт. 2015 г.
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 <core/debug.h>
23 #include <core/alloc.h>
24 #include <core/buffer.h>
25 #include <core/system.h>
26 #include <core/resource.h>
27 #include <core/io/Path.h>
28 #include <core/io/Dir.h>
29 #include <core/io/NativeFile.h>
30 
31 #include <ui/ui.h>
32 
33 #include <ui/serialize.h>
34 
35 #include <metadata/metadata.h>
36 #include <metadata/ports.h>
37 
38 #include <ctype.h>
39 #include <string.h>
40 
41 namespace lsp
42 {
~ConfigHandler()43     plugin_ui::ConfigHandler::~ConfigHandler()
44     {
45         for (size_t i=0, n=vNotify.size(); i<n; ++i)
46         {
47             char *p = vNotify.get(i);
48             if (p != NULL)
49                 ::free(p);
50         }
51         vNotify.flush();
52     }
53 
handle_parameter(const char * name,const char * value,size_t flags)54     status_t plugin_ui::ConfigHandler::handle_parameter(const char *name, const char *value, size_t flags)
55     {
56         add_notification(name);
57         pUI->apply_changes(name, value, hPorts, bPreset, pBasePath);
58         return STATUS_OK;
59     }
60 
handle_kvt_parameter(const char * name,const kvt_param_t * value,size_t flags)61     status_t plugin_ui::ConfigHandler::handle_kvt_parameter(const char *name, const kvt_param_t *value, size_t flags)
62     {
63         if (pKVT == NULL)
64             return STATUS_OK;
65 
66         pKVT->put(name, value, KVT_RX);
67         pUI->kvt_write(pKVT, name, value);
68 
69         return STATUS_OK;
70     }
71 
add_notification(const char * id)72     void plugin_ui::ConfigHandler::add_notification(const char *id)
73     {
74         char *notify = ::strdup(id);
75         if (notify == NULL)
76             return;
77 
78         if (!vNotify.add(notify))
79             ::free(notify);
80     }
81 
notify_all()82     void plugin_ui::ConfigHandler::notify_all()
83     {
84         for (size_t i=0, n=vNotify.size(); i<n; ++i)
85         {
86             char *p = vNotify.get(i);
87             if (p == NULL)
88                 continue;
89 
90             if (p[0] == '/') // KVT ?
91             {
92                 const kvt_param_t *param = NULL;
93                 if ((pKVT->get(p, &param) == STATUS_OK) && (param != NULL))
94                     pUI->kvt_write(pKVT, p, param);
95             }
96             else
97             {
98                 CtlPort *ctl = pUI->port(p);
99                 if (ctl != NULL)
100                     ctl->notify_all();
101             }
102             ::free(p);
103         }
104         vNotify.flush();
105     }
106 
get_head_comment(LSPString * comment)107     status_t plugin_ui::ConfigSource::get_head_comment(LSPString *comment)
108     {
109         return (comment->set(pComment)) ? STATUS_OK : STATUS_NO_MEM;
110     }
111 
get_parameter(LSPString * name,LSPString * value,LSPString * comment,int * flags)112     status_t plugin_ui::ConfigSource::get_parameter(LSPString *name, LSPString *value, LSPString *comment, int *flags)
113     {
114         // Get regular ports
115         size_t n_ports = hPorts.size();
116 
117         while (nPortID < n_ports)
118         {
119             // Get port
120             CtlPort *up         = hPorts.at(nPortID++);
121             if (up == NULL)
122                 continue;
123 
124             // Get metadata
125             const port_t *p    = up->metadata();
126             if (p == NULL)
127                 continue;
128 
129             // Skip output and proxy ports
130             if (!IS_IN_PORT(p))
131                 continue;
132 
133             // Try to format port value
134             status_t res = format_port_value(up, name, value, comment, flags, pBasePath);
135 
136             // Skip port if it has bad, non-serializable type
137             if (res == STATUS_BAD_TYPE)
138                 continue;
139 
140             return res;
141         }
142 
143         // Get KVT parameters
144         while ((pIter != NULL) && (pIter->next() == STATUS_OK))
145         {
146             const kvt_param_t *p;
147 
148             // Get KVT parameter
149             status_t res = pIter->get(&p);
150             if (res == STATUS_NOT_FOUND)
151                 continue;
152             else if (res != STATUS_OK)
153             {
154                 lsp_warn("Could not get parameter: code=%d", int(res));
155                 break;
156             }
157 
158             // Skip transient and private parameters
159             if ((pIter->is_transient()) || (pIter->is_private()))
160                 continue;
161 
162             // Get parameter name
163             const char *pname = pIter->name();
164             if (pname == NULL)
165                 continue;
166             if (!name->set_ascii(pname))
167             {
168                 lsp_warn("Failed to do set_ascii");
169                 continue;
170             }
171 
172             // Serialize state
173             bool fmt = false;
174             switch (p->type)
175             {
176                 case KVT_INT32:
177                     fmt     = value->fmt_ascii("%li", (signed long)(p->i32));
178                     *flags  = config::SF_TYPE_I32;
179                     break;
180                 case KVT_UINT32:
181                     fmt     = value->fmt_ascii("%lu", (unsigned long)(p->u32));
182                     *flags  = config::SF_TYPE_U32;
183                     break;
184                 case KVT_INT64:
185                     fmt = value->fmt_ascii("%lli", (signed long long)(p->i64));
186                     *flags  = config::SF_TYPE_I64;
187                     break;
188                 case KVT_UINT64:
189                     fmt = value->fmt_ascii("%llu", (unsigned long long)(p->u64));
190                     *flags  = config::SF_TYPE_U64;
191                     break;
192                 case KVT_FLOAT32:
193                     fmt = value->fmt_ascii("%f", p->f32);
194                     *flags  = config::SF_TYPE_F32;
195                     break;
196                 case KVT_FLOAT64:
197                     fmt = value->fmt_ascii("%f", p->f64);
198                     *flags  = config::SF_TYPE_F64;
199                     break;
200                 case KVT_STRING:
201                     fmt = value->set_utf8(p->str);
202                     *flags  = config::SF_TYPE_STR | config::SF_QUOTED;
203                     break;
204                 case KVT_BLOB:
205                 {
206                     fmt = value->fmt_ascii("%s:%ld:",
207                             (p->blob.ctype != NULL) ? p->blob.ctype : "",
208                             (long)(p->blob.size)
209                         );
210                     if ((p->blob.size > 0) && (p->blob.data == NULL))
211                         break;
212 
213                     // Base-64 encode blob value
214                     if (p->blob.size > 0)
215                     {
216                         size_t dst_size = 0x10 + ((p->blob.size * 4) / 3);
217                         char *base64 = reinterpret_cast<char *>(::malloc(dst_size));
218                         if (base64 == NULL)
219                             break;
220 
221                         size_t dst_left = dst_size, src_left = p->blob.size;
222                         dsp::base64_enc(base64, &dst_left, p->blob.data, &src_left);
223                         fmt = value->append_ascii(base64, dst_size - dst_left);
224                         ::free(base64);
225                     }
226                     else
227                         fmt = true;
228 
229                     if (fmt)
230                         *flags  = config::SF_TYPE_BLOB | config::SF_QUOTED;
231                     break;
232                 }
233                 default:
234                     break;
235             }
236 
237             if (!fmt)
238             {
239                 lsp_warn("Error formatting parameter %s", pname);
240                 continue;
241             }
242 
243             // All is OK
244             return STATUS_OK;
245         }
246 
247         return STATUS_NO_DATA;
248     }
249 
~ConfigSink()250     plugin_ui::ConfigSink::~ConfigSink()
251     {
252         unbind();
253     }
254 
unbind()255     void plugin_ui::ConfigSink::unbind()
256     {
257         if (pUI != NULL)
258             pUI->pConfigSink = NULL;
259         pUI = NULL;
260     }
261 
on_complete(status_t code,const LSPString * data)262     status_t plugin_ui::ConfigSink::on_complete(status_t code, const LSPString *data)
263     {
264         return ((code == STATUS_OK) && (pUI != NULL)) ? pUI->paste_from_clipboard(data) : STATUS_OK;
265     }
266 
267 
268     //--------------------------------------------------------------------------------------------------------
269 
270     const port_t plugin_ui::vConfigMetadata[] =
271     {
272         SWITCH(UI_MOUNT_STUD_PORT_ID, "Visibility of mount studs in the UI", 1.0f),
273         PATH(UI_LAST_VERSION_PORT_ID, "Last version of the product installed"),
274         PATH(UI_DLG_SAMPLE_PATH_ID, "Dialog path for selecting sample files"),
275         PATH(UI_DLG_IR_PATH_ID, "Dialog path for selecting impulse response files"),
276         PATH(UI_DLG_CONFIG_PATH_ID, "Dialog path for saving/loading configuration files"),
277         PATH(UI_DLG_REW_PATH_ID, "Dialog path for importing REW settings files"),
278         PATH(UI_DLG_HYDROGEN_PATH_ID, "Dialog path for importing Hydrogen drumkit files"),
279         PATH(UI_DLG_MODEL3D_PATH_ID, "Dialog for saving/loading 3D model files"),
280         PATH(UI_DLG_DEFAULT_PATH_ID, "Dialog default path for other files"),
281         PATH(UI_R3D_BACKEND_PORT_ID, "Identifier of selected backend for 3D rendering"),
282         PATH(UI_LANGUAGE_PORT_ID, "Selected language identifier for the UI interface"),
283         SWITCH(UI_REL_PATHS_PORT_ID, "Use relative paths when exporting configuration file", 0.0f),
284         PORTS_END
285     };
286 
287     const port_t plugin_ui::vTimeMetadata[] =
288     {
289         UNLIMITED_METER(TIME_SAMPLE_RATE_PORT, "Sample rate", U_HZ, DEFAULT_SAMPLE_RATE),
290         UNLIMITED_METER(TIME_SPEED_PORT, "Playback speed", U_NONE, 0.0f),
291         UNLIMITED_METER(TIME_FRAME_PORT, "Current frame", U_NONE, 0.0f),
292         UNLIMITED_METER(TIME_NUMERATOR_PORT, "Numerator", U_NONE, 4.0f),
293         UNLIMITED_METER(TIME_DENOMINATOR_PORT, "Denominator", U_NONE, 4.0f),
294         UNLIMITED_METER(TIME_BEATS_PER_MINUTE_PORT, "Beats per Minute", U_BPM, BPM_DEFAULT),
295         UNLIMITED_METER(TIME_TICK_PORT, "Current tick", U_NONE, 0.0f),
296         UNLIMITED_METER(TIME_TICKS_PER_BEAT_PORT, "Ticks per Bar", U_NONE, 960.0f),
297 
298         PORTS_END
299     };
300 
plugin_ui(const plugin_metadata_t * mdata,void * root_widget)301     plugin_ui::plugin_ui(const plugin_metadata_t *mdata, void *root_widget)
302     {
303         pMetadata       = mdata;
304         pWrapper        = NULL;
305         pRoot           = NULL;
306         pRootCtl        = NULL;
307         pRootWidget     = root_widget;
308         pConfigSink     = NULL;
309     }
310 
~plugin_ui()311     plugin_ui::~plugin_ui()
312     {
313         destroy();
314     }
315 
destroy()316     void plugin_ui::destroy()
317     {
318         // Unbind sink
319         if (pConfigSink != NULL)
320         {
321             pConfigSink->unbind();
322             pConfigSink = NULL;
323         }
324 
325         // Destroy registry
326         CtlRegistry::destroy();
327 
328         // Destroy widgets
329         for (size_t i=0, n=vWidgets.size(); i<n; ++i)
330         {
331             LSPWidget *widget = vWidgets.at(i);
332             if (widget != NULL)
333             {
334                 widget->destroy();
335                 delete widget;
336             }
337         }
338 
339         vWidgets.flush();
340         pRoot       = NULL;
341         pRootCtl    = NULL;
342 
343         // Destroy switched ports
344         for (size_t i=0, n=vSwitched.size(); i<n; ++i)
345         {
346             CtlSwitchedPort *p = vSwitched.at(i);
347             if (p != NULL)
348             {
349                 lsp_trace("Destroy switched port id=%s", p->id());
350                 delete p;
351             }
352         }
353 
354         // Destroy config ports
355         for (size_t i=0, n=vConfigPorts.size(); i<n; ++i)
356         {
357             CtlPort *p = vConfigPorts.at(i);
358             if (p != NULL)
359             {
360                 lsp_trace("Destroy configuration port id=%s", p->metadata()->id);
361                 delete p;
362             }
363         }
364 
365         // Destroy time ports
366         for (size_t i=0, n=vTimePorts.size(); i<n; ++i)
367         {
368             CtlPort *p = vTimePorts.at(i);
369             if (p != NULL)
370             {
371                 lsp_trace("Destroy timing port id=%s", p->metadata()->id);
372                 delete p;
373             }
374         }
375 
376         // Destroy custom ports
377         for (size_t i=0, n=vCustomPorts.size(); i<n; ++i)
378         {
379             CtlPort *p = vCustomPorts.at(i);
380             if (p != NULL)
381             {
382                 lsp_trace("Destroy timing port id=%s", p->metadata()->id);
383                 delete p;
384             }
385         }
386 
387         // Clear ports
388         vSortedPorts.clear();
389         vConfigPorts.clear();
390         vTimePorts.clear();
391         vPorts.clear();
392         vCustomPorts.clear();
393         vSwitched.clear();
394         vAliases.clear(); // Aliases will be destroyed as controllers
395         vKvtListeners.flush(); // Destroy references to KVT listeners
396 
397         // Destroy display
398         sDisplay.destroy();
399 
400         // Destroy list of presets
401         destroy_presets();
402     }
403 
create_widget(const char * w_ctl)404     CtlWidget *plugin_ui::create_widget(const char *w_ctl)
405     {
406         widget_ctl_t type = widget_ctl(w_ctl);
407         return (type != WC_UNKNOWN) ? create_widget(type) : NULL;
408     }
409 
create_widget(widget_ctl_t w_class)410     CtlWidget *plugin_ui::create_widget(widget_ctl_t w_class)
411     {
412         CtlWidget *w = build_widget(w_class);
413         if (w != NULL)
414             add_widget(w);
415         return w;
416     }
417 
set_title(const char * title)418     void plugin_ui::set_title(const char *title)
419     {
420         if (pRoot != NULL)
421             pRoot->title()->set(title);
422     }
423 
build_widget(widget_ctl_t w_class)424     CtlWidget *plugin_ui::build_widget(widget_ctl_t w_class)
425     {
426         switch (w_class)
427         {
428             // Main plugin window
429             case WC_PLUGIN:
430             {
431                 if (pRoot == NULL)
432                 {
433                     pRoot = new LSPWindow(&sDisplay, pRootWidget);
434                     pRoot->init();
435                     vWidgets.add(pRoot);
436                 }
437                 if (pRootCtl == NULL)
438                     pRootCtl = new CtlPluginWindow(this, pRoot);
439                 return pRootCtl;
440             }
441 
442             // Different kind of boxes and grids
443             case WC_HBOX:
444             {
445                 LSPBox *box = new LSPBox(&sDisplay, true);
446                 box->init();
447                 vWidgets.add(box);
448                 return new CtlBox(this, box, O_HORIZONTAL);
449             }
450             case WC_VBOX:
451             {
452                 LSPBox *box = new LSPBox(&sDisplay, false);
453                 box->init();
454                 vWidgets.add(box);
455                 return new CtlBox(this, box, O_VERTICAL);
456             }
457             case WC_BOX:
458             {
459                 LSPBox *box = new LSPBox(&sDisplay);
460                 box->init();
461                 vWidgets.add(box);
462                 return new CtlBox(this, box);
463             }
464             case WC_HSBOX:
465             {
466                 LSPScrollBox *box = new LSPScrollBox(&sDisplay, true);
467                 box->init();
468                 vWidgets.add(box);
469                 return new CtlScrollBox(this, box, O_HORIZONTAL);
470             }
471             case WC_VSBOX:
472             {
473                 LSPScrollBox *box = new LSPScrollBox(&sDisplay, false);
474                 box->init();
475                 vWidgets.add(box);
476                 return new CtlScrollBox(this, box, O_VERTICAL);
477             }
478             case WC_SBOX:
479             {
480                 LSPScrollBox *box = new LSPScrollBox(&sDisplay);
481                 box->init();
482                 vWidgets.add(box);
483                 return new CtlScrollBox(this, box);
484             }
485             case WC_HGRID:
486             {
487                 LSPGrid *grid = new LSPGrid(&sDisplay, true);
488                 grid->init();
489                 vWidgets.add(grid);
490                 return new CtlGrid(this, grid, O_HORIZONTAL);
491             }
492             case WC_VGRID:
493             {
494                 LSPGrid *grid = new LSPGrid(&sDisplay, false);
495                 grid->init();
496                 vWidgets.add(grid);
497                 return new CtlGrid(this, grid, O_VERTICAL);
498             }
499             case WC_GRID:
500             {
501                 LSPGrid *grid = new LSPGrid(&sDisplay);
502                 grid->init();
503                 vWidgets.add(grid);
504                 return new CtlGrid(this, grid);
505             }
506             case WC_CELL:
507                 return new CtlCell(this);
508             case WC_ALIGN:
509             {
510                 LSPAlign *align = new LSPAlign(&sDisplay);
511                 align->init();
512                 vWidgets.add(align);
513                 return new CtlAlign(this, align);
514             }
515             case WC_GROUP:
516             {
517                 LSPGroup *grp = new LSPGroup(&sDisplay);
518                 grp->init();
519                 vWidgets.add(grp);
520                 return new CtlGroup(this, grp);
521             }
522             case WC_CGROUP:
523             {
524                 LSPComboGroup *grp = new LSPComboGroup(&sDisplay);
525                 grp->init();
526                 vWidgets.add(grp);
527                 return new CtlComboGroup(this, grp);
528             }
529 
530             // Button, switches, knobs and other controllers
531             case WC_BUTTON:
532             {
533                 LSPButton *btn = new LSPButton(&sDisplay);
534                 btn->init();
535                 vWidgets.add(btn);
536                 return new CtlButton(this, btn);
537             }
538             case WC_TTAP:
539             {
540                 LSPButton *btn = new LSPButton(&sDisplay);
541                 btn->init();
542                 vWidgets.add(btn);
543                 return new CtlTempoTap(this, btn);
544             }
545             case WC_SWITCH:
546             {
547                 LSPSwitch *sw = new LSPSwitch(&sDisplay);
548                 sw->init();
549                 vWidgets.add(sw);
550                 return new CtlSwitch(this, sw);
551             }
552             case WC_KNOB:
553             {
554                 LSPKnob *knob = new LSPKnob(&sDisplay);
555                 knob->init();
556                 vWidgets.add(knob);
557                 return new CtlKnob(this, knob);
558             }
559             case WC_SBAR:
560             {
561                 LSPScrollBar *sbar = new LSPScrollBar(&sDisplay);
562                 sbar->init();
563                 vWidgets.add(sbar);
564                 return new CtlScrollBar(this, sbar);
565             }
566             case WC_VSBAR:
567             {
568                 LSPScrollBar *sbar = new LSPScrollBar(&sDisplay, false);
569                 sbar->init();
570                 vWidgets.add(sbar);
571                 return new CtlScrollBar(this, sbar);
572             }
573             case WC_HSBAR:
574             {
575                 LSPScrollBar *sbar = new LSPScrollBar(&sDisplay, true);
576                 sbar->init();
577                 vWidgets.add(sbar);
578                 return new CtlScrollBar(this, sbar);
579             }
580             case WC_FADER:
581             {
582                 LSPFader *fader = new LSPFader(&sDisplay);
583                 fader->init();
584                 vWidgets.add(fader);
585                 return new CtlFader(this, fader);
586             }
587             case WC_LISTBOX:
588             {
589                 LSPListBox *lbox = new LSPListBox(&sDisplay);
590                 lbox->init();
591                 vWidgets.add(lbox);
592                 return new CtlListBox(this, lbox);
593             }
594             case WC_COMBO:
595             {
596                 LSPComboBox *cbox = new LSPComboBox(&sDisplay);
597                 cbox->init();
598                 vWidgets.add(cbox);
599                 return new CtlComboBox(this, cbox);
600             }
601             case WC_THREADCOMBO:
602             {
603                 LSPComboBox *cbox = new LSPComboBox(&sDisplay);
604                 cbox->init();
605                 vWidgets.add(cbox);
606                 return new CtlThreadComboBox(this, cbox);
607             }
608             case WC_EDIT:
609             {
610                 LSPEdit *edit = new LSPEdit(&sDisplay);
611                 edit->init();
612                 vWidgets.add(edit);
613                 return new CtlEdit(this, edit);
614             }
615 
616             // Label
617             case WC_LABEL:
618             {
619                 LSPLabel *lbl = new LSPLabel(&sDisplay);
620                 lbl->init();
621                 vWidgets.add(lbl);
622                 return new CtlLabel(this, lbl, CTL_LABEL_TEXT);
623             }
624             case WC_PARAM:
625             {
626                 LSPLabel *lbl = new LSPLabel(&sDisplay);
627                 lbl->init();
628                 vWidgets.add(lbl);
629                 return new CtlLabel(this, lbl, CTL_LABEL_PARAM);
630             }
631             case WC_VALUE:
632             {
633                 LSPLabel *lbl = new LSPLabel(&sDisplay);
634                 lbl->init();
635                 vWidgets.add(lbl);
636                 return new CtlLabel(this, lbl, CTL_LABEL_VALUE);
637             }
638             case WC_STATUS:
639             {
640                 LSPLabel *lbl = new LSPLabel(&sDisplay);
641                 lbl->init();
642                 vWidgets.add(lbl);
643                 return new CtlLabel(this, lbl, CTL_STATUS_CODE);
644             }
645             case WC_HLINK:
646             {
647                 LSPHyperlink *hlink = new LSPHyperlink(&sDisplay);
648                 hlink->init();
649                 vWidgets.add(hlink);
650                 return new CtlHyperlink(this, hlink, CTL_LABEL_TEXT);
651             }
652 
653             // Indication
654             case WC_INDICATOR:
655             {
656                 LSPIndicator *ind = new LSPIndicator(&sDisplay);
657                 ind->init();
658                 vWidgets.add(ind);
659                 return new CtlIndicator(this, ind);
660             }
661             case WC_MIDINOTE:
662             {
663                 LSPIndicator *ind = new LSPIndicator(&sDisplay);
664                 ind->init();
665                 vWidgets.add(ind);
666                 return new CtlMidiNote(this, ind);
667             }
668             case WC_LED:
669             {
670                 LSPLed *led = new LSPLed(&sDisplay);
671                 led->init();
672                 vWidgets.add(led);
673                 return new CtlLed(this, led);
674             }
675             case WC_METER:
676             {
677                 LSPMeter *mtr = new LSPMeter(&sDisplay);
678                 mtr->init();
679                 vWidgets.add(mtr);
680                 return new CtlMeter(this, mtr);
681             }
682             case WC_PROGRESS:
683             {
684                 LSPProgressBar *bar = new LSPProgressBar(&sDisplay);
685                 bar->init();
686                 vWidgets.add(bar);
687                 return new CtlProgressBar(this, bar);
688             }
689 
690             // Separator
691             case WC_HSEP:
692             {
693                 LSPSeparator *sep = new LSPSeparator(&sDisplay, true);
694                 sep->init();
695                 vWidgets.add(sep);
696                 return new CtlSeparator(this, sep, O_HORIZONTAL);
697             }
698             case WC_VSEP:
699             {
700                 LSPSeparator *sep = new LSPSeparator(&sDisplay, false);
701                 sep->init();
702                 vWidgets.add(sep);
703                 return new CtlSeparator(this, sep, O_VERTICAL);
704             }
705             case WC_SEP:
706             {
707                 LSPSeparator *sep = new LSPSeparator(&sDisplay);
708                 sep->init();
709                 vWidgets.add(sep);
710                 return new CtlSeparator(this, sep);
711             }
712             case WC_VOID:
713             {
714                 LSPVoid *v = new LSPVoid(&sDisplay);
715                 v->init();
716                 vWidgets.add(v);
717                 return new CtlVoid(this, v);
718             }
719 
720             // File
721             case WC_FILE:
722             {
723                 LSPAudioFile *af = new LSPAudioFile(&sDisplay);
724                 af->init();
725                 vWidgets.add(af);
726                 return new CtlAudioFile(this, af);
727             }
728             case WC_SAVE:
729             {
730                 LSPSaveFile *save = new LSPSaveFile(&sDisplay);
731                 save->init();
732                 vWidgets.add(save);
733                 return new CtlSaveFile(this, save);
734             }
735             case WC_LOAD:
736             {
737                 LSPLoadFile *load = new LSPLoadFile(&sDisplay);
738                 load->init();
739                 vWidgets.add(load);
740                 return new CtlLoadFile(this, load);
741             }
742             case WC_SAMPLE:
743             {
744                 LSPAudioSample *sample = new LSPAudioSample(&sDisplay);
745                 sample->init();
746                 vWidgets.add(sample);
747                 return new CtlAudioSample(this, sample);
748             }
749 
750             // 3D viewer object
751             case WC_VIEWER3D:
752             {
753                 LSPArea3D *a = new LSPArea3D(&sDisplay);
754                 a->init();
755                 vWidgets.add(a);
756                 return new CtlViewer3D(this, a);
757             }
758             case WC_CAPTURE3D:
759             {
760                 LSPCapture3D *c = new LSPCapture3D(&sDisplay);
761                 c->init();
762                 vWidgets.add(c);
763                 return new CtlCapture3D(this, c);
764             }
765             case WC_SOURCE3D:
766             {
767                 LSPMesh3D *m = new LSPMesh3D(&sDisplay);
768                 m->init();
769                 vWidgets.add(m);
770                 return new CtlSource3D(this, m);
771             }
772 
773             // Graph object
774             case WC_GRAPH:
775             {
776                 LSPGraph *gr = new LSPGraph(&sDisplay);
777                 gr->init();
778                 vWidgets.add(gr);
779                 return new CtlGraph(this, gr);
780             }
781             case WC_AXIS:
782             {
783                 LSPAxis *axis = new LSPAxis(&sDisplay);
784                 axis->init();
785                 vWidgets.add(axis);
786                 return new CtlAxis(this, axis);
787             }
788             case WC_CENTER:
789             {
790                 LSPCenter *cnt = new LSPCenter(&sDisplay);
791                 cnt->init();
792                 vWidgets.add(cnt);
793                 return new CtlCenter(this, cnt);
794             }
795             case WC_MARKER:
796             {
797                 LSPMarker *mark = new LSPMarker(&sDisplay);
798                 mark->init();
799                 vWidgets.add(mark);
800                 return new CtlMarker(this, mark);
801             }
802             case WC_MESH:
803             {
804                 LSPMesh *mesh = new LSPMesh(&sDisplay);
805                 mesh->init();
806                 vWidgets.add(mesh);
807                 return new CtlMesh(this, mesh);
808             }
809             case WC_STREAM:
810             {
811                 LSPMesh *mesh = new LSPMesh(&sDisplay);
812                 mesh->init();
813                 vWidgets.add(mesh);
814                 return new CtlStream(this, mesh);
815             }
816             case WC_FBUFFER:
817             {
818                 LSPFrameBuffer *fb = new LSPFrameBuffer(&sDisplay);
819                 fb->init();
820                 vWidgets.add(fb);
821                 return new CtlFrameBuffer(this, fb);
822             }
823             case WC_TEXT:
824             {
825                 LSPText *text = new LSPText(&sDisplay);
826                 text->init();
827                 vWidgets.add(text);
828                 return new CtlText(this, text);
829             }
830             case WC_DOT:
831             {
832                 LSPDot *dot = new LSPDot(&sDisplay);
833                 dot->init();
834                 vWidgets.add(dot);
835                 return new CtlDot(this, dot);
836             }
837             case WC_FRAC:
838             {
839                 LSPFraction *frac = new LSPFraction(&sDisplay);
840                 frac->init();
841                 vWidgets.add(frac);
842                 return new CtlFraction(this, frac);
843             }
844 
845             case WC_PORT:
846             {
847                 CtlPortAlias *alias = new CtlPortAlias(this);
848                 vAliases.add(alias);
849                 return alias;
850             }
851 
852             default:
853                 return NULL;
854         }
855 
856         return NULL;
857     }
858 
init(IUIWrapper * wrapper,int argc,const char ** argv)859     status_t plugin_ui::init(IUIWrapper *wrapper, int argc, const char **argv)
860     {
861         // Store pointer to wrapper
862         pWrapper    = wrapper;
863 
864         // Initialize display
865         status_t result = sDisplay.init(argc, argv);
866         if (result != STATUS_OK)
867             return result;
868 
869         // Create additional ports (ui)
870         for (const port_t *p = vConfigMetadata; p->id != NULL; ++p)
871         {
872             switch (p->role)
873             {
874                 case R_CONTROL:
875                 {
876                     CtlPort *up = new CtlControlPort(p, this);
877                     if (up != NULL)
878                         vConfigPorts.add(up);
879                     break;
880                 }
881 
882                 case R_PATH:
883                 {
884                     CtlPort *up = new CtlPathPort(p, this);
885                     if (up != NULL)
886                         vConfigPorts.add(up);
887                     break;
888                 }
889 
890                 default:
891                     lsp_error("Could not instantiate configuration port id=%s", p->id);
892                     break;
893             }
894         }
895 
896         // Create additional ports (time)
897         for (const port_t *p = vTimeMetadata; p->id != NULL; ++p)
898         {
899             switch (p->role)
900             {
901                 case R_METER:
902                 {
903                     CtlValuePort *vp = new CtlValuePort(p);
904                     if (vp != NULL)
905                         vTimePorts.add(vp);
906                     break;
907                 }
908                 default:
909                     lsp_error("Could not instantiate time port id=%s", p->id);
910                     break;
911             }
912         }
913 
914         result = scan_presets();
915         if (result != STATUS_OK)
916             return result;
917 
918         // Return successful status
919         return STATUS_OK;
920     }
921 
slot_preset_select(LSPWidget * sender,void * ptr,void * data)922     status_t plugin_ui::slot_preset_select(LSPWidget *sender, void *ptr, void *data)
923     {
924         plugin_ui *_this = reinterpret_cast<plugin_ui *>(ptr);
925         if (_this == NULL)
926             return STATUS_BAD_STATE;
927 
928         for (size_t i=0, n=_this->vPresets.size(); i<n; ++i)
929         {
930             preset_t *p     = _this->vPresets.at(i);
931             if ((p == NULL) || (p->item != sender))
932                 continue;
933 
934             return _this->import_settings(p->path, true);
935         }
936 
937         return STATUS_OK;
938     }
939 
scan_presets()940     status_t plugin_ui::scan_presets()
941     {
942         char base[PATH_MAX + 1];
943 
944 #ifdef LSP_BUILTIN_RESOURCES
945         ::snprintf(base, PATH_MAX, "presets/%s/", pMetadata->ui_presets);
946 
947         base[PATH_MAX] = '\0';
948         size_t prefix_len = ::strlen(base);
949 
950         for (const resource::resource_t *r = resource::all(); (r->id != NULL); ++r)
951         {
952             // Check that resource matches
953             if (r->type != resource::RESOURCE_PRESET)
954                 continue;
955             if (::strstr(r->id, base) != r->id)
956                 continue;
957 
958             // Add preset
959             preset_t *p = vPresets.add();
960             if (p == NULL)
961             {
962                 destroy_presets();
963                 return STATUS_NO_MEM;
964             }
965             p->name     = NULL;
966             p->path     = NULL;
967             p->item     = NULL;
968 
969             int xres    = ::asprintf(&p->path, "builtin://%s", r->id);
970             if ((xres <= 0) || (p->path == NULL))
971             {
972                 destroy_presets();
973                 return STATUS_NO_MEM;
974             }
975 
976             if ((p->name = ::strdup(&r->id[prefix_len])) == NULL)
977             {
978                 destroy_presets();
979                 return STATUS_NO_MEM;
980             }
981 
982             // Remove '.preset' extension from name
983             size_t len = ::strlen(p->name);
984             if ((len >= 7) && (::strcasecmp(&p->name[len-7], ".preset") == 0))
985                 p->name[len-7]   = '\0';
986         }
987 #else
988         ::snprintf(base, PATH_MAX, "res" FILE_SEPARATOR_S "presets" FILE_SEPARATOR_S "%s", pMetadata->ui_presets);
989 
990         io::Dir dir;
991         io::Path entry;
992         io::fattr_t fattr;
993         LSPString extension, tmp;
994 
995         if (!extension.set_ascii(".preset"))
996             return STATUS_NO_MEM;
997 
998         status_t res = dir.open(base);
999         if (res != STATUS_OK)
1000             return STATUS_OK;
1001 
1002         while (true)
1003         {
1004             // Read entry
1005             res = dir.reads(&entry, &fattr, true);
1006             if (res == STATUS_EOF)
1007                 break;
1008             else if (res != STATUS_OK)
1009             {
1010                 destroy_presets();
1011                 dir.close();
1012                 return res;
1013             }
1014             if (fattr.type != io::fattr_t::FT_REGULAR)
1015                 continue;
1016             if (!entry.as_string()->ends_with_nocase(&extension))
1017                 continue;
1018 
1019             // Add preset
1020             preset_t *p = vPresets.add();
1021             if (p == NULL)
1022             {
1023                 destroy_presets();
1024                 return STATUS_NO_MEM;
1025             }
1026             p->name     = NULL;
1027             p->path     = NULL;
1028             p->item     = NULL;
1029 
1030             if ((p->path = ::strdup(entry.as_string()->get_utf8())) == NULL)
1031             {
1032                 destroy_presets();
1033                 return STATUS_NO_MEM;
1034             }
1035 
1036             res = entry.get_last(&tmp);
1037             if (res != STATUS_OK)
1038             {
1039                 destroy_presets();
1040                 return res;
1041             }
1042 
1043             ssize_t len = tmp.length() - extension.length();
1044             if (len >= 0)
1045                 tmp.set_length(len);
1046 
1047             if ((p->name = ::strdup(tmp.get_utf8())) == NULL)
1048             {
1049                 destroy_presets();
1050                 return STATUS_NO_MEM;
1051             }
1052         }
1053 
1054         dir.close();
1055 #endif
1056 
1057         // Sort presets in alphabetical order
1058         if (vPresets.size() > 0)
1059         {
1060             for (ssize_t i=0, n=vPresets.size(); i<n-1; ++i)
1061             {
1062                 preset_t *a = vPresets.at(i);
1063                 for (ssize_t j=i+1; j<n; ++j)
1064                 {
1065                     preset_t *b = vPresets.at(j);
1066                     if (strcmp(a->name, b->name) > 0)
1067                     {
1068                         swap(a->path, b->path);
1069                         swap(a->name, b->name);
1070                         swap(a->item, b->item);
1071                     }
1072                 }
1073             }
1074         }
1075 
1076         return STATUS_OK;
1077     }
1078 
destroy_presets()1079     void plugin_ui::destroy_presets()
1080     {
1081         for (size_t i=0, n=vPresets.size(); i<n; ++i)
1082         {
1083             preset_t *p = vPresets.at(i);
1084             if (p->name != NULL)
1085                 ::free(p->name);
1086             if (p->path != NULL)
1087                 ::free(p->path);
1088             p->item = NULL;
1089         }
1090         vPresets.flush();
1091     }
1092 
build()1093     status_t plugin_ui::build()
1094     {
1095         status_t result;
1096         LSPString path;
1097 
1098         // Load theme
1099         LSPTheme *theme = sDisplay.theme();
1100         if (theme == NULL)
1101             return STATUS_UNKNOWN_ERR;
1102 
1103         lsp_trace("Loading theme");
1104         result = load_theme(theme, "ui/theme.xml");
1105         if (result != STATUS_OK)
1106             return result;
1107 
1108         lsp_trace("Loading dictionary");
1109         IDictionary *dict = sDisplay.dictionary();
1110         #ifdef LSP_BUILTIN_RESOURCES
1111             result = dict->init(LSP_BUILTIN_PREFIX "i18n");
1112         #else
1113             result = dict->init(LSP_RESOURCE_PATH "/i18n");
1114         #endif
1115         if (result != STATUS_OK)
1116             return result;
1117 
1118         // Read global configuration
1119         result = load_global_config();
1120         if (result != STATUS_OK)
1121             lsp_error("Error while loading global configuration file");
1122 
1123         // Generate path to UI schema
1124         ui_builder bld(this);
1125         if (!path.fmt_utf8("ui/%s", pMetadata->ui_resource))
1126             return STATUS_NO_MEM;
1127         lsp_trace("Generating UI from URI %s", path.get_utf8());
1128         if ((result = bld.build(&path)) != STATUS_OK)
1129         {
1130             lsp_error("Could not build UI from URI %s", path.get_utf8());
1131             return result;
1132         }
1133         lsp_trace("UI has been generated");
1134 
1135         // Fetch main menu
1136         LSPMenu *menu       = widget_cast<LSPMenu>(resolve(WUID_MAIN_MENU));
1137         if (menu == NULL)
1138             return STATUS_NO_MEM;
1139 
1140         // Add presets if they are present
1141         if ((menu != NULL) && (vPresets.size() > 0))
1142         {
1143             // Get display
1144             LSPDisplay *dpy     = menu->display();
1145 
1146             // Create submenu item
1147             LSPMenuItem *item   = new LSPMenuItem(dpy);
1148             if (item == NULL)
1149                 return STATUS_NO_MEM;
1150             vWidgets.add(item);
1151             result = item->init();
1152             if (result != STATUS_OK)
1153                 return result;
1154 
1155             item->text()->set("actions.load_preset");
1156             menu->add(item);
1157 
1158             // Create submenu
1159             LSPMenu *submenu    = new LSPMenu(dpy);
1160             if (submenu == NULL)
1161                 return STATUS_NO_MEM;
1162             vWidgets.add(submenu);
1163             result = submenu->init();
1164             if (result != STATUS_OK)
1165                 return result;
1166             item->set_submenu(submenu);
1167 
1168             // Iterate all presets
1169             for (size_t i=0, n=vPresets.size(); i<n; ++i)
1170             {
1171                 preset_t *p     = vPresets.at(i);
1172                 if (p == NULL)
1173                     continue;
1174 
1175                 // Create menu item and bind handler
1176                 item        = new LSPMenuItem(dpy);
1177                 if (item == NULL)
1178                     return STATUS_NO_MEM;
1179                 vWidgets.add(item);
1180                 result = item->init();
1181                 if (result != STATUS_OK)
1182                     return result;
1183                 item->text()->set_raw(p->name);
1184                 p->item     = item;
1185 
1186                 item->slots()->bind(LSPSLOT_SUBMIT, slot_preset_select, this);
1187                 submenu->add(item);
1188             }
1189         }
1190 
1191         return STATUS_OK;
1192     }
1193 
position_updated(const position_t * pos)1194     void plugin_ui::position_updated(const position_t *pos)
1195     {
1196         size_t i = 0;
1197         vTimePorts[i++]->commitValue(pos->sampleRate);
1198         vTimePorts[i++]->commitValue(pos->speed);
1199         vTimePorts[i++]->commitValue(pos->frame);
1200         vTimePorts[i++]->commitValue(pos->numerator);
1201         vTimePorts[i++]->commitValue(pos->denominator);
1202         vTimePorts[i++]->commitValue(pos->beatsPerMinute);
1203         vTimePorts[i++]->commitValue(pos->tick);
1204         vTimePorts[i++]->commitValue(pos->ticksPerBeat);
1205     }
1206 
sync_meta_ports()1207     void plugin_ui::sync_meta_ports()
1208     {
1209         for (size_t i=0, count=vTimePorts.size(); i < count; ++i)
1210         {
1211             CtlValuePort *vp = vTimePorts.at(i);
1212             if (vp != NULL)
1213                 vp->sync();
1214         }
1215     };
1216 
kvt_write(KVTStorage * storage,const char * id,const kvt_param_t * value)1217     void plugin_ui::kvt_write(KVTStorage *storage, const char *id, const kvt_param_t *value)
1218     {
1219         for (size_t i=0, n=vKvtListeners.size(); i<n; ++i)
1220         {
1221             CtlKvtListener *l = vKvtListeners.at(i);
1222             if (l != NULL)
1223                 l->changed(storage, id, value);
1224         }
1225     }
1226 
kvt_lock()1227     KVTStorage *plugin_ui::kvt_lock()
1228     {
1229         return (pWrapper != NULL) ? pWrapper->kvt_lock() : NULL;
1230     }
1231 
kvt_trylock()1232     KVTStorage *plugin_ui::kvt_trylock()
1233     {
1234         return (pWrapper != NULL) ? pWrapper->kvt_trylock() : NULL;
1235     }
1236 
kvt_release()1237     void plugin_ui::kvt_release()
1238     {
1239         if (pWrapper != NULL)
1240             pWrapper->kvt_release();
1241     }
1242 
add_port(CtlPort * port)1243     status_t plugin_ui::add_port(CtlPort *port)
1244     {
1245         if (!vPorts.add(port))
1246             return STATUS_NO_MEM;
1247 
1248         lsp_trace("added port id=%s", port->metadata()->id);
1249         return STATUS_OK;
1250     }
1251 
add_custom_port(CtlPort * port)1252     status_t plugin_ui::add_custom_port(CtlPort *port)
1253     {
1254         if (!vCustomPorts.add(port))
1255             return STATUS_NO_MEM;
1256 
1257         lsp_trace("added custom port id=%s", port->metadata()->id);
1258         return STATUS_OK;
1259     }
1260 
add_kvt_listener(CtlKvtListener * listener)1261     status_t plugin_ui::add_kvt_listener(CtlKvtListener *listener)
1262     {
1263         if (!vKvtListeners.add(listener))
1264             return STATUS_NO_MEM;
1265         lsp_trace("added KVT listener id=%s", listener->name());
1266         return STATUS_OK;
1267     }
1268 
open_config_file(bool write)1269     io::File *plugin_ui::open_config_file(bool write)
1270     {
1271         io::Path cfg;
1272         status_t res = system::get_home_directory(&cfg);
1273         if (res == STATUS_OK)
1274             res = cfg.append_child(".config");
1275         if (res == STATUS_OK)
1276             res = cfg.append_child(LSP_ARTIFACT_ID);
1277         if (res == STATUS_OK)
1278             res = cfg.mkdir(true);
1279         if (res == STATUS_OK)
1280             res = cfg.append_child(LSP_ARTIFACT_ID ".cfg");
1281 
1282         if (res != STATUS_OK)
1283             return NULL;
1284 
1285         io::NativeFile *fd = new io::NativeFile();
1286         if (fd == NULL)
1287             return NULL;
1288         res = fd->open(&cfg, (write) ?
1289                 io::File::FM_WRITE | io::File::FM_TRUNC | io::File::FM_CREATE :
1290                 io::File::FM_READ);
1291 
1292         if (res == STATUS_OK)
1293             return fd;
1294 
1295         fd->close();
1296         delete fd;
1297         return NULL;
1298     }
1299 
build_config_header(LSPString & c)1300     void plugin_ui::build_config_header(LSPString &c)
1301     {
1302         c.append_utf8       ("This file contains configuration of the audio plugin.\n");
1303         c.fmt_append_utf8   ("  Plugin name:         %s (%s)\n", pMetadata->name, pMetadata->description);
1304         c.fmt_append_utf8   ("  Package version:     %s\n", LSP_MAIN_VERSION);
1305         c.fmt_append_utf8   ("  Plugin version:      %d.%d.%d\n",
1306                 int(LSP_VERSION_MAJOR(pMetadata->version)),
1307                 int(LSP_VERSION_MINOR(pMetadata->version)),
1308                 int(LSP_VERSION_MICRO(pMetadata->version))
1309             );
1310         if (pMetadata->lv2_uid != NULL)
1311             c.fmt_append_utf8   ("  LV2 URI:             %s%s\n", LSP_URI(lv2), pMetadata->lv2_uid);
1312         if (pMetadata->vst_uid != NULL)
1313             c.fmt_append_utf8   ("  VST identifier:      %s\n", pMetadata->vst_uid);
1314         if (pMetadata->ladspa_id > 0)
1315             c.fmt_append_utf8   ("  LADSPA identifier:   %d\n", pMetadata->ladspa_id);
1316         c.append            ('\n');
1317         c.append_utf8       ("(C) " LSP_FULL_NAME " \n");
1318         c.append_utf8       ("  " LSP_SITE_URL " \n");
1319     }
1320 
export_settings(const char * filename,bool relative)1321     status_t plugin_ui::export_settings(const char *filename, bool relative)
1322     {
1323         status_t res;
1324         LSPString c;
1325         build_config_header(c);
1326 
1327         // Form the base path of file
1328         io::Path base;
1329         if ((res = base.set(filename)) != STATUS_OK)
1330             return res;
1331         if ((res = base.remove_last()) != STATUS_OK)
1332             return res;
1333 
1334         // Serialize data to file
1335         KVTStorage *kvt = kvt_lock();
1336         ConfigSource cfg(this, vPorts, kvt, &c, (relative) ? &base : NULL);
1337         res = config::save(filename, &cfg, true);
1338         kvt->gc();
1339         kvt_release();
1340 
1341         return res;
1342     }
1343 
export_settings_to_clipboard()1344     status_t plugin_ui::export_settings_to_clipboard()
1345     {
1346         LSPString c, data;
1347         build_config_header(c);
1348 
1349         // Serialize data to string
1350         KVTStorage *kvt = kvt_lock();
1351         ConfigSource cfg(this, vPorts, kvt, &c, NULL);
1352         status_t res = config::serialize(&data, &cfg, true);
1353         kvt->gc();
1354         kvt_release();
1355 
1356         if (res != STATUS_OK)
1357             return res;
1358 
1359         // Put data to clipboard
1360         LSPTextDataSource *ds = new LSPTextDataSource();
1361         if (ds == NULL)
1362             return STATUS_NO_MEM;
1363         ds->acquire();
1364         res = ds->set_text(&data);
1365         if (res == STATUS_OK)
1366             res = sDisplay.set_clipboard(CBUF_CLIPBOARD, ds);
1367         ds->release();
1368 
1369         return res;
1370     }
1371 
import_settings_from_clipboard()1372     status_t plugin_ui::import_settings_from_clipboard()
1373     {
1374         // Unbind previous
1375         ConfigSink *ds = new ConfigSink(this);
1376         if (ds == NULL)
1377             return STATUS_NO_MEM;
1378 
1379         if (pConfigSink != NULL)
1380             pConfigSink->unbind();
1381         pConfigSink = ds;
1382 
1383         // Request clipboard data
1384         ds->acquire();
1385         status_t res = sDisplay.get_clipboard(CBUF_CLIPBOARD, pConfigSink);
1386         ds->release();
1387 
1388         return res;
1389     }
1390 
paste_from_clipboard(const LSPString * data)1391     status_t plugin_ui::paste_from_clipboard(const LSPString *data)
1392     {
1393         // Deserialize configuration data
1394         KVTStorage *kvt = kvt_lock();
1395         ConfigHandler handler(this, vPorts, kvt, false, NULL);
1396         status_t res = config::deserialize(data, &handler);
1397         handler.notify_all();
1398         if (kvt != NULL)
1399         {
1400             kvt->gc();
1401             kvt_release();
1402         }
1403 
1404         return res;
1405     }
1406 
import_settings(const char * filename,bool preset)1407     status_t plugin_ui::import_settings(const char *filename, bool preset)
1408     {
1409         // Form the base path of file
1410         status_t res;
1411         io::Path base;
1412         if ((res = base.set(filename)) != STATUS_OK)
1413             return res;
1414         if ((res = base.remove_last()) != STATUS_OK)
1415             return res;
1416 
1417         // Load configuration data
1418         KVTStorage *kvt = kvt_lock();
1419         ConfigHandler handler(this, vPorts, kvt, preset, &base);
1420         res = config::load(filename, &handler);
1421         handler.notify_all();
1422         if (kvt != NULL)
1423         {
1424             kvt->gc();
1425             kvt_release();
1426         }
1427 
1428         return res;
1429     }
1430 
save_global_config()1431     status_t plugin_ui::save_global_config()
1432     {
1433         io::File *fd    = open_config_file(true);
1434         if (fd == NULL)
1435             return STATUS_UNKNOWN_ERR;
1436 
1437         LSPString c;
1438 
1439         c.append_utf8       ("This file contains global configuration of plugins.\n");
1440         c.append            ('\n');
1441         c.append_utf8       ("(C) " LSP_FULL_NAME " \n");
1442         c.append_utf8       ("  " LSP_BASE_URI " \n");
1443 
1444         ConfigSource cfg(this, vConfigPorts, NULL, &c, NULL);
1445 
1446         status_t status = config::save(fd, &cfg, true);
1447 
1448         // Close file
1449         fd->close();
1450         delete fd;
1451 
1452         return status;
1453     }
1454 
load_global_config()1455     status_t plugin_ui::load_global_config()
1456     {
1457         io::File *fd    = open_config_file(false);
1458         if (fd == NULL)
1459             return STATUS_UNKNOWN_ERR;
1460 
1461         ConfigHandler handler(this, vConfigPorts, NULL, false, NULL);
1462         status_t status = config::load(fd, &handler);
1463 
1464         // Close file
1465         fd->close();
1466         delete fd;
1467 
1468         return status;
1469     }
1470 
apply_changes(const char * key,const char * value,cvector<CtlPort> & ports,bool preset,const io::Path * base)1471     bool plugin_ui::apply_changes(const char *key, const char *value, cvector<CtlPort> &ports, bool preset, const io::Path *base)
1472     {
1473         // Get UI port
1474         size_t n_ports  = ports.size();
1475         for (size_t i=0; i<n_ports; ++i)
1476         {
1477             CtlPort *p      = ports.at(i);
1478             if (p == NULL)
1479                 continue;
1480             const port_t *meta  = p->metadata();
1481             if ((meta == NULL) || (meta->id == NULL))
1482                 continue;
1483             if (!::strcmp(meta->id, key))
1484                 return set_port_value(p, value, (preset) ? PF_PRESET_IMPORT : PF_STATE_IMPORT, base);
1485         }
1486         return false;
1487     }
1488 
port_cmp(const void * va,const void * vb)1489     static int port_cmp(const void *va, const void *vb)
1490     {
1491         const CtlPort *const *a = reinterpret_cast<const CtlPort *const *>(va);
1492         const CtlPort *const *b = reinterpret_cast<const CtlPort *const *>(vb);
1493 
1494         const port_t *am    = (*a)->metadata();
1495         const port_t *bm    = (*b)->metadata();
1496 
1497         return ::strcmp(am->id, bm->id);
1498     }
1499 
rebuild_sorted_ports()1500     size_t plugin_ui::rebuild_sorted_ports()
1501     {
1502         size_t count = vPorts.size();
1503         vSortedPorts.clear();
1504 
1505         for (size_t i=0; i<count; ++i)
1506             vSortedPorts.add(vPorts.at(i));
1507 
1508         if (count <= 1)
1509             return count;
1510 
1511         count           = vSortedPorts.size();
1512         CtlPort **vp    = vSortedPorts.get_array();
1513 
1514         // Sort by port's ID
1515         ::qsort(vp, count, sizeof(CtlPort *), port_cmp);
1516 
1517 //        #ifdef LSP_TRACE
1518 //            for (size_t i=0; i<count; ++i)
1519 //                lsp_trace("sorted port idx=%d, id=%s", int(i), vSortedPorts[i]->metadata()->id);
1520 //        #endif /* LSP_TRACE */
1521 
1522         return count;
1523     }
1524 
port_by_index(size_t index)1525     CtlPort *plugin_ui::port_by_index(size_t index)
1526     {
1527         return vPorts.get(index);
1528     }
1529 
port(const char * name)1530     CtlPort *plugin_ui::port(const char *name)
1531     {
1532         // Check aliases
1533         size_t n_aliases = vAliases.size();
1534 
1535         for (size_t i=0; i<n_aliases; ++i)
1536         {
1537             CtlPortAlias *pa = vAliases.at(i);
1538             if ((pa->id() == NULL) || (pa->alias() == NULL))
1539                 continue;
1540 
1541             if (!strcmp(name, pa->id()))
1542             {
1543                 name    = pa->alias();
1544                 break;
1545             }
1546         }
1547 
1548         // Check that port name contains index
1549         if (strchr(name, '[') != NULL)
1550         {
1551             // Try to find switched port
1552             size_t count = vSwitched.size();
1553             for (size_t i=0; i<count; ++i)
1554             {
1555                 CtlSwitchedPort *p  = vSwitched.at(i);
1556                 if (p == NULL)
1557                     continue;
1558                 const char *p_id    = p->id();
1559                 if (p_id == NULL)
1560                     continue;
1561                 if (!strcmp(p_id, name))
1562                     return p;
1563             }
1564 
1565             // Create new switched port
1566             CtlSwitchedPort *s   = new CtlSwitchedPort(this);
1567             if (s == NULL)
1568                 return NULL;
1569 
1570             if (s->compile(name))
1571             {
1572                 if (vSwitched.add(s))
1573                     return s;
1574             }
1575 
1576             delete s;
1577             return NULL;
1578         }
1579 
1580         // Check that port name contains "ui:" prefix
1581         if (strstr(name, UI_CONFIG_PORT_PREFIX) == name)
1582         {
1583             const char *ui_id = &name[strlen(UI_CONFIG_PORT_PREFIX)];
1584 
1585             // Try to find configuration port
1586             size_t count = vConfigPorts.size();
1587             for (size_t i=0; i<count; ++i)
1588             {
1589                 CtlPort *p          = vConfigPorts.at(i);
1590                 if (p == NULL)
1591                     continue;
1592                 const char *p_id    = p->metadata()->id;
1593                 if (p_id == NULL)
1594                     continue;
1595                 if (!strcmp(p_id, ui_id))
1596                     return p;
1597             }
1598         }
1599 
1600         // Check that port name contains "time:" prefix
1601         if (strstr(name, TIME_PORT_PREFIX) == name)
1602         {
1603             const char *ui_id = &name[strlen(TIME_PORT_PREFIX)];
1604 
1605             // Try to find configuration port
1606             size_t count = vTimePorts.size();
1607             for (size_t i=0; i<count; ++i)
1608             {
1609                 CtlPort *p          = vTimePorts.at(i);
1610                 if (p == NULL)
1611                     continue;
1612                 const char *p_id    = p->metadata()->id;
1613                 if (p_id == NULL)
1614                     continue;
1615                 if (!strcmp(p_id, ui_id))
1616                     return p;
1617             }
1618         }
1619 
1620         // Look to custom ports
1621         for (size_t i=0, n=vCustomPorts.size(); i<n; ++i)
1622         {
1623             CtlPort *p = vCustomPorts.get(i);
1624             const port_t *ctl = (p != NULL) ? p->metadata() : NULL;
1625             if ((ctl != NULL) && (!strcmp(ctl->id, name)))
1626                 return p;
1627         }
1628 
1629         // Do usual stuff
1630         size_t count = vPorts.size();
1631         if (vSortedPorts.size() != count)
1632             count = rebuild_sorted_ports();
1633 
1634         // Try to find the corresponding port
1635         ssize_t first = 0, last = count - 1;
1636         while (first <= last)
1637         {
1638             size_t center       = (first + last) >> 1;
1639             CtlPort *p          = vSortedPorts.at(center);
1640             if (p == NULL)
1641                 break;
1642             const port_t *ctl   = p->metadata();
1643             if (ctl == NULL)
1644                 break;
1645 
1646             int cmp     = strcmp(name, ctl->id);
1647             if (cmp < 0)
1648                 last    = center - 1;
1649             else if (cmp > 0)
1650                 first   = center + 1;
1651             else
1652                 return p;
1653 
1654         }
1655         return NULL;
1656     }
1657 
request_state_dump()1658     void plugin_ui::request_state_dump()
1659     {
1660         if (pWrapper != NULL)
1661             pWrapper->dump_state_request();
1662     }
1663 
1664 
1665 } /* namespace lsp */
1666