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, ¶m) == 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