patchmanager.cpp (30011B)
1 #include "patchmanager.h" 2 3 #include "datasourcetree.h" 4 #include "datasourcetreeitem.h" 5 #include "grid.h" 6 #include "info.h" 7 #include "list.h" 8 #include "listmodel.h" 9 #include "searchlist.h" 10 #include "searchtree.h" 11 #include "status.h" 12 #include "tagstree.h" 13 #include "tree.h" 14 15 #include "../pluginEditor.h" 16 #include "../pluginProcessor.h" 17 18 #include "baseLib/filesystem.h" 19 20 #include "jucePluginLib/clipboard.h" 21 #include "jucePluginLib/filetype.h" 22 #include "jucePluginLib/types.h" 23 24 #include "dsp56kEmu/logging.h" 25 26 #if JUCE_MAJOR_VERSION < 8 // they forgot this include but fixed it in version 8+ 27 #include "juce_gui_extra/misc/juce_ColourSelector.h" 28 #endif 29 30 namespace jucePluginEditorLib::patchManager 31 { 32 constexpr int g_scale = 2; 33 constexpr auto g_searchBarHeight = 32; 34 constexpr int g_padding = 4; 35 36 PatchManager::PatchManager(Editor& _editor, Component* _root, const std::initializer_list<GroupType>& _groupTypes) 37 : DB(juce::File(_editor.getProcessor().getPatchManagerDataFolder(false))) 38 , m_editor(_editor) 39 , m_state(*this) 40 { 41 setTagTypeName(pluginLib::patchDB::TagType::Category, "Category"); 42 setTagTypeName(pluginLib::patchDB::TagType::Tag, "Tag"); 43 setTagTypeName(pluginLib::patchDB::TagType::Favourites, "Favourite"); 44 45 const auto rootW = _root->getWidth() / g_scale; 46 const auto rootH = _root->getHeight() / g_scale; 47 const auto scale = juce::AffineTransform::scale(g_scale); 48 49 setSize(rootW, rootH); 50 setTransform(scale); 51 52 _root->addAndMakeVisible(this); 53 54 auto weight = [&](int _weight) 55 { 56 return rootW * _weight / 100; 57 }; 58 59 // 1st column 60 auto w = weight(33); 61 m_treeDS = new DatasourceTree(*this, _groupTypes); 62 m_treeDS->setSize(w - g_padding, rootH - g_searchBarHeight - g_padding); 63 64 m_searchTreeDS = new SearchTree(*m_treeDS); 65 m_searchTreeDS->setSize(m_treeDS->getWidth(), g_searchBarHeight); 66 m_searchTreeDS->setTopLeftPosition(m_treeDS->getX(), m_treeDS->getHeight() + g_padding); 67 68 addAndMakeVisible(m_treeDS); 69 addAndMakeVisible(m_searchTreeDS); 70 71 // 2nd column 72 w = weight(20); 73 m_treeTags = new TagsTree(*this); 74 m_treeTags->setTopLeftPosition(m_treeDS->getRight() + g_padding, 0); 75 m_treeTags->setSize(w - g_padding, rootH - g_searchBarHeight - g_padding); 76 77 m_searchTreeTags = new SearchTree(*m_treeTags); 78 m_searchTreeTags->setTopLeftPosition(m_treeTags->getX(), m_treeTags->getHeight() + g_padding); 79 m_searchTreeTags->setSize(m_treeTags->getWidth(), g_searchBarHeight); 80 81 addAndMakeVisible(m_treeTags); 82 addAndMakeVisible(m_searchTreeTags); 83 84 // 3rd column 85 w = weight(15); 86 m_list = new List(*this); 87 m_list->setTopLeftPosition(m_treeTags->getRight() + g_padding, 0); 88 m_list->setSize(w - g_padding, rootH - g_searchBarHeight - g_padding); 89 90 m_grid = new Grid(*this); 91 m_grid->setTopLeftPosition(m_list->getPosition()); 92 m_grid->setSize(m_list->getWidth(), m_list->getHeight()); 93 94 m_grid->setLookAndFeel(&m_list->getLookAndFeel()); 95 m_grid->setColour(juce::ListBox::backgroundColourId, m_list->findColour(juce::ListBox::backgroundColourId)); 96 m_grid->setColour(juce::ListBox::textColourId, m_list->findColour(juce::ListBox::textColourId)); 97 m_grid->setColour(juce::ListBox::outlineColourId, m_list->findColour(juce::ListBox::outlineColourId)); 98 99 m_searchList = new SearchList(*m_list); 100 m_searchList->setTopLeftPosition(m_list->getX(), m_list->getHeight() + g_padding); 101 m_searchList->setSize(m_list->getWidth(), g_searchBarHeight); 102 103 addAndMakeVisible(m_list); 104 addChildComponent(m_grid); 105 addAndMakeVisible(m_searchList); 106 107 // 4th column 108 m_info = new Info(*this); 109 m_info->setTopLeftPosition(m_list->getRight() + g_padding, 0); 110 m_info->setSize(getWidth() - m_info->getX(), rootH - g_searchBarHeight - g_padding); 111 112 m_status = new Status(); 113 m_status->setTopLeftPosition(m_info->getX(), m_info->getHeight() + g_padding); 114 m_status->setSize(m_info->getWidth(), g_searchBarHeight); 115 116 addAndMakeVisible(m_info); 117 addAndMakeVisible(m_status); 118 119 if(const auto t = getTemplate("pm_search")) 120 { 121 t->apply(getEditor(), *m_searchList); 122 t->apply(getEditor(), *m_searchTreeDS); 123 t->apply(getEditor(), *m_searchTreeTags); 124 } 125 126 m_searchList->setTextToShowWhenEmpty("Search...", m_searchList->findColour(juce::TextEditor::textColourId).withAlpha(0.5f)); 127 m_searchTreeDS->setTextToShowWhenEmpty("Search...", m_searchTreeDS->findColour(juce::TextEditor::textColourId).withAlpha(0.5f)); 128 m_searchTreeTags->setTextToShowWhenEmpty("Search...", m_searchTreeTags->findColour(juce::TextEditor::textColourId).withAlpha(0.5f)); 129 130 if(const auto t = getTemplate("pm_status_label")) 131 { 132 t->apply(getEditor(), *m_status); 133 } 134 135 juce::StretchableLayoutManager lm; 136 137 m_stretchableManager.setItemLayout(0, 100, rootW * 0.5, m_treeDS->getWidth()); m_stretchableManager.setItemLayout(1, 5, 5, 5); 138 m_stretchableManager.setItemLayout(2, 100, rootW * 0.5, m_treeTags->getWidth()); m_stretchableManager.setItemLayout(3, 5, 5, 5); 139 m_stretchableManager.setItemLayout(4, 100, rootW * 0.5, m_list->getWidth()); m_stretchableManager.setItemLayout(5, 5, 5, 5); 140 m_stretchableManager.setItemLayout(6, 100, rootW * 0.5, m_info->getWidth()); 141 142 m_resizerBarA.setSize(5, rootH); 143 m_resizerBarB.setSize(5, rootH); 144 m_resizerBarC.setSize(5, rootH); 145 146 addAndMakeVisible(m_resizerBarA); 147 addAndMakeVisible(m_resizerBarB); 148 addAndMakeVisible(m_resizerBarC); 149 150 PatchManager::resized(); 151 152 const auto& config = m_editor.getProcessor().getConfig(); 153 154 if(config.getIntValue("pm_layout", static_cast<int>(LayoutType::List)) == static_cast<int>(LayoutType::Grid)) 155 setLayout(LayoutType::Grid); 156 else 157 PatchManager::resized(); 158 159 startTimer(200); 160 } 161 162 PatchManager::~PatchManager() 163 { 164 stopTimer(); 165 166 delete m_status; 167 delete m_info; 168 delete m_searchList; 169 delete m_list; 170 delete m_grid; 171 172 // trees emit onSelectionChanged, be sure to guard it 173 m_list = nullptr; 174 m_grid = nullptr; 175 176 delete m_searchTreeTags; 177 delete m_treeTags; 178 delete m_searchTreeDS; 179 delete m_treeDS; 180 } 181 182 void PatchManager::timerCallback() 183 { 184 uiProcess(); 185 } 186 187 void PatchManager::processDirty(const pluginLib::patchDB::Dirty& _dirty) const 188 { 189 m_treeDS->processDirty(_dirty); 190 m_treeTags->processDirty(_dirty); 191 getListModel()->processDirty(_dirty); 192 193 m_status->setScanning(isScanning()); 194 195 m_info->processDirty(_dirty); 196 197 if(!_dirty.errors.empty()) 198 { 199 std::string msg = "Patch Manager encountered errors:\n\n"; 200 for(size_t i=0; i<_dirty.errors.size(); ++i) 201 { 202 msg += _dirty.errors[i]; 203 if(i < _dirty.errors.size() - 1) 204 msg += "\n"; 205 } 206 207 genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, "Patch Manager Error", msg); 208 } 209 } 210 211 void PatchManager::setSelectedItem(Tree* _tree, const TreeItem* _item) 212 { 213 m_selectedItems[_tree] = std::set{_item}; 214 215 if(_tree == m_treeDS) 216 m_treeTags->onParentSearchChanged(_item->getSearchRequest()); 217 218 onSelectedItemsChanged(); 219 } 220 221 void PatchManager::addSelectedItem(Tree* _tree, const TreeItem* _item) 222 { 223 const auto oldCount = m_selectedItems[_tree].size(); 224 m_selectedItems[_tree].insert(_item); 225 const auto newCount = m_selectedItems[_tree].size(); 226 if(newCount > oldCount) 227 onSelectedItemsChanged(); 228 } 229 230 // ReSharper disable once CppParameterMayBeConstPtrOrRef - wrong 231 void PatchManager::removeSelectedItem(Tree* _tree, const TreeItem* _item) 232 { 233 const auto it = m_selectedItems.find(_tree); 234 if(it == m_selectedItems.end()) 235 return; 236 if(!it->second.erase(_item)) 237 return; 238 onSelectedItemsChanged(); 239 } 240 241 bool PatchManager::setSelectedPatch(const pluginLib::patchDB::PatchPtr& _patch, const pluginLib::patchDB::SearchHandle _fromSearch) 242 { 243 return setSelectedPatch(getCurrentPart(), _patch, _fromSearch); 244 } 245 246 void PatchManager::setLayout(const LayoutType _layout) 247 { 248 if(m_layout == _layout) 249 return; 250 251 auto* oldModel = getListModel(); 252 253 m_layout = _layout; 254 255 auto* newModel = getListModel(); 256 257 m_searchList->setListModel(newModel); 258 259 newModel->setContent(oldModel->getSearchHandle()); 260 newModel->setSelectedEntries(oldModel->getSelectedEntries()); 261 262 dynamic_cast<Component*>(newModel)->setVisible(true); 263 dynamic_cast<Component*>(oldModel)->setVisible(false); 264 265 if(m_firstTimeGridLayout && _layout == LayoutType::Grid) 266 { 267 m_firstTimeGridLayout = false; 268 setGridLayout128(); 269 } 270 else 271 { 272 resized(); 273 } 274 275 auto& config = m_editor.getProcessor().getConfig(); 276 config.setValue("pm_layout", static_cast<int>(_layout)); 277 config.saveIfNeeded(); 278 } 279 280 bool PatchManager::setGridLayout128() 281 { 282 if(m_layout != LayoutType::Grid) 283 return false; 284 285 const auto columnCount = 128.0f / static_cast<float>(m_grid->getSuggestedItemsPerRow()); 286 287 const auto pos = static_cast<int>(static_cast<float>(getWidth()) - m_grid->getItemWidth() * columnCount - static_cast<float>(m_resizerBarB.getWidth())); 288 289 m_stretchableManager.setItemPosition(3, pos - 1); // prevent rounding issues 290 291 resized(); 292 293 return true; 294 } 295 296 void PatchManager::setCustomSearch(const pluginLib::patchDB::SearchHandle _sh) const 297 { 298 m_treeDS->clearSelectedItems(); 299 m_treeTags->clearSelectedItems(); 300 301 getListModel()->setContent(_sh); 302 } 303 304 void PatchManager::bringToFront() const 305 { 306 m_editor.selectTabWithComponent(this); 307 } 308 309 bool PatchManager::selectPatch(const uint32_t _part, const int _offset) 310 { 311 auto [patch, _] = m_state.getNeighbourPreset(_part, _offset); 312 313 if(!patch) 314 return false; 315 316 if(!setSelectedPatch(_part, patch, m_state.getSearchHandle(_part))) 317 return false; 318 319 if(_part == getCurrentPart()) 320 getListModel()->setSelectedPatches({patch}); 321 322 return true; 323 } 324 325 bool PatchManager::setSelectedPatch(const uint32_t _part, const pluginLib::patchDB::PatchPtr& _patch, pluginLib::patchDB::SearchHandle _fromSearch) 326 { 327 if(!activatePatch(_patch, _part)) 328 return false; 329 330 m_state.setSelectedPatch(_part, pluginLib::patchDB::PatchKey(*_patch), _fromSearch); 331 332 if(_part == getCurrentPart()) 333 m_info->setPatch(_patch); 334 335 return true; 336 } 337 338 bool PatchManager::setSelectedDataSource(const pluginLib::patchDB::DataSourceNodePtr& _ds) const 339 { 340 if(auto* item = m_treeDS->getItem(*_ds)) 341 { 342 selectTreeItem(item); 343 return true; 344 } 345 return false; 346 } 347 348 pluginLib::patchDB::DataSourceNodePtr PatchManager::getSelectedDataSource() const 349 { 350 const auto* item = dynamic_cast<DatasourceTreeItem*>(m_treeDS->getSelectedItem(0)); 351 if(!item) 352 return {}; 353 return item->getDataSource(); 354 } 355 356 TreeItem* PatchManager::getSelectedDataSourceTreeItem() const 357 { 358 if (!m_treeDS) 359 return nullptr; 360 auto ds = getSelectedDataSource(); 361 if (!ds) 362 return nullptr; 363 return m_treeDS->getItem(*ds); 364 } 365 366 bool PatchManager::setSelectedPatch(const uint32_t _part, const pluginLib::patchDB::PatchPtr& _patch) 367 { 368 if(!isValid(_patch)) 369 return false; 370 371 const auto patchDs = _patch->source.lock(); 372 373 if(!patchDs) 374 return false; 375 376 if(!setSelectedPatch(_part, pluginLib::patchDB::PatchKey(*_patch))) 377 return false; 378 379 return true; 380 } 381 382 bool PatchManager::setSelectedPatch(const uint32_t _part, const pluginLib::patchDB::PatchKey& _patch) 383 { 384 // we've got a patch, but we do not know its search handle, i.e. which list it is part of, find the missing information 385 386 if(!_patch.isValid()) 387 return false; 388 389 const auto searchHandle = getSearchHandle(*_patch.source, _part == getCurrentPart()); 390 391 if(searchHandle == pluginLib::patchDB::g_invalidSearchHandle) 392 return false; 393 394 m_state.setSelectedPatch(_part, _patch, searchHandle); 395 396 if(getCurrentPart() == _part) 397 getListModel()->setSelectedPatches({_patch}); 398 399 onSelectedPatchChanged(_part, _patch); 400 401 return true; 402 } 403 404 void PatchManager::copyPatchesToLocalStorage(const pluginLib::patchDB::DataSourceNodePtr& _ds, const std::vector<pluginLib::patchDB::PatchPtr>& _patches, int _part) 405 { 406 copyPatchesTo(_ds, _patches, -1, [this, _part](const std::vector<pluginLib::patchDB::PatchPtr>& _savedPatches) 407 { 408 if(_part == -1) 409 return; 410 411 juce::MessageManager::callAsync([this, _part, _savedPatches] 412 { 413 setSelectedPatch(_part, _savedPatches.front()); 414 }); 415 }); 416 } 417 418 uint32_t PatchManager::createSaveMenuEntries(juce::PopupMenu& _menu, uint32_t _part, const std::string& _name/* = "patch"*/, uint64_t _userData/* = 0*/) 419 { 420 const auto& state = getState(); 421 const auto key = state.getPatch(_part); 422 423 uint32_t countAdded = 0; 424 425 if(key.isValid() && key.source->type == pluginLib::patchDB::SourceType::LocalStorage) 426 { 427 // the key that is stored in the state might not contain patches, find the real data source in the DB 428 const auto ds = getDataSource(*key.source); 429 430 if(ds) 431 { 432 if(const auto p = ds->getPatch(key)) 433 { 434 if(*p == key) 435 { 436 ++countAdded; 437 _menu.addItem("Overwrite " + _name + " '" + p->getName() + "' in user bank '" + ds->name + "'", true, false, [this, p, _part, _userData] 438 { 439 const auto newPatch = requestPatchForPart(_part, _userData); 440 if(newPatch) 441 { 442 replacePatch(p, newPatch); 443 } 444 }); 445 } 446 } 447 } 448 } 449 450 const auto existingLocalDS = getDataSourcesOfSourceType(pluginLib::patchDB::SourceType::LocalStorage); 451 452 if(!existingLocalDS.empty()) 453 { 454 if(countAdded) 455 _menu.addSeparator(); 456 457 for (const auto& ds : existingLocalDS) 458 { 459 ++countAdded; 460 _menu.addItem("Add " + _name + " to user bank '" + ds->name + "'", true, false, [this, ds, _part, _userData] 461 { 462 const auto newPatch = requestPatchForPart(_part, _userData); 463 464 if(!newPatch) 465 return; 466 467 copyPatchesToLocalStorage(ds, {newPatch}, static_cast<int>(_part)); 468 }); 469 } 470 } 471 else 472 { 473 ++countAdded; 474 _menu.addItem("Create new user bank and add " + _name, true, false, [this, _part, _userData] 475 { 476 const auto newPatch = requestPatchForPart(_part, _userData); 477 478 if(!newPatch) 479 return; 480 481 pluginLib::patchDB::DataSource ds; 482 483 ds.name = "User Bank"; 484 ds.type = pluginLib::patchDB::SourceType::LocalStorage; 485 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 486 ds.timestamp = std::chrono::system_clock::now(); 487 addDataSource(ds, false, [newPatch, _part, this](const bool _success, const std::shared_ptr<pluginLib::patchDB::DataSourceNode>& _ds) 488 { 489 if(_success) 490 copyPatchesToLocalStorage(_ds, {newPatch}, static_cast<int>(_part)); 491 }); 492 }); 493 } 494 495 return countAdded; 496 } 497 498 std::string PatchManager::getTagTypeName(const pluginLib::patchDB::TagType _type) const 499 { 500 const auto it = m_tagTypeNames.find(_type); 501 if(it == m_tagTypeNames.end()) 502 { 503 return {}; 504 } 505 return it->second; 506 } 507 508 void PatchManager::setTagTypeName(const pluginLib::patchDB::TagType _type, const std::string& _name) 509 { 510 if(_name.empty()) 511 { 512 m_tagTypeNames.erase(_type); 513 return; 514 } 515 516 m_tagTypeNames[_type] = _name; 517 } 518 519 bool PatchManager::selectPrevPreset(const uint32_t _part) 520 { 521 return selectPatch(_part, -1); 522 } 523 524 bool PatchManager::selectNextPreset(const uint32_t _part) 525 { 526 return selectPatch(_part, 1); 527 } 528 529 bool PatchManager::selectPatch(const uint32_t _part, const pluginLib::patchDB::DataSource& _ds, const uint32_t _program) 530 { 531 const auto searchHandle = getSearchHandle(_ds, _part == getCurrentPart()); 532 533 if(searchHandle == pluginLib::patchDB::g_invalidSearchHandle) 534 return false; 535 536 const auto s = getSearch(searchHandle); 537 if(!s) 538 return false; 539 540 pluginLib::patchDB::PatchPtr p; 541 542 std::shared_lock lockResults(s->resultsMutex); 543 for (const auto& patch : s->results) 544 { 545 if(patch->program == _program) 546 { 547 p = patch; 548 break; 549 } 550 } 551 552 if(!p) 553 return false; 554 555 if(!activatePatch(p, _part)) 556 return false; 557 558 setSelectedPatch(_part, p, s->handle); 559 560 if(_part == getCurrentPart()) 561 getListModel()->setSelectedPatches({p}); 562 563 return true; 564 } 565 566 void PatchManager::setListStatus(uint32_t _selected, uint32_t _total) const 567 { 568 m_status->setListStatus(_selected, _total); 569 } 570 571 pluginLib::patchDB::Color PatchManager::getPatchColor(const pluginLib::patchDB::PatchPtr& _patch) const 572 { 573 // we want to prevent that a whole list is colored with one color just because that list is based on a tag, prefer other tags instead 574 pluginLib::patchDB::TypedTags ignoreTags; 575 576 for (const auto& selectedItem : m_selectedItems) 577 { 578 for (const auto& item : selectedItem.second) 579 { 580 const auto& s = item->getSearchRequest(); 581 ignoreTags.add(s.tags); 582 } 583 } 584 return DB::getPatchColor(_patch, ignoreTags); 585 } 586 587 bool PatchManager::addGroupTreeItemForTag(const pluginLib::patchDB::TagType _type) const 588 { 589 return addGroupTreeItemForTag(_type, getTagTypeName(_type)); 590 } 591 592 bool PatchManager::addGroupTreeItemForTag(const pluginLib::patchDB::TagType _type, const std::string& _name) const 593 { 594 const auto groupType = toGroupType(_type); 595 if(groupType == GroupType::Invalid) 596 return false; 597 if(_name.empty()) 598 return false; 599 if(m_treeTags->getItem(groupType)) 600 return false; 601 m_treeTags->addGroup(groupType, _name); 602 return true; 603 } 604 605 void PatchManager::paint(juce::Graphics& g) 606 { 607 g.fillAll(juce::Colour(0,0,0)); 608 } 609 610 void PatchManager::exportPresets(const juce::File& _file, const std::vector<pluginLib::patchDB::PatchPtr>& _patches, const pluginLib::FileType& _fileType) const 611 { 612 #if SYNTHLIB_DEMO_MODE 613 getEditor().showDemoRestrictionMessageBox(); 614 #else 615 pluginLib::FileType type = _fileType; 616 const auto name = Editor::createValidFilename(type, _file); 617 618 std::vector<pluginLib::patchDB::Data> patchData; 619 for (const auto& patch : _patches) 620 { 621 const auto patchSysex = applyModifications(patch, type, pluginLib::ExportType::File); 622 623 if(!patchSysex.empty()) 624 patchData.push_back(patchSysex); 625 } 626 627 if(!Editor::savePresets(type, name, patchData)) 628 genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, "Save failed", "Failed to write data to " + _file.getFullPathName().toStdString()); 629 #endif 630 } 631 632 bool PatchManager::exportPresets(std::vector<pluginLib::patchDB::PatchPtr>&& _patches, const pluginLib::FileType& _fileType) const 633 { 634 const auto patchCount = _patches.size(); 635 636 auto exportPatches = [p = std::move(_patches), this, _fileType] 637 { 638 auto patches = p; 639 ListModel::sortPatches(patches, pluginLib::patchDB::SourceType::LocalStorage); 640 getEditor().savePreset(_fileType, [this, p = std::move(patches), _fileType](const juce::File& _file) 641 { 642 exportPresets(_file, p, _fileType); 643 }); 644 }; 645 646 if(patchCount > 128) 647 { 648 genericUI::MessageBox::showOkCancel( 649 juce::MessageBoxIconType::WarningIcon, 650 "Patch Manager", 651 "You are trying to export more than 128 presets into a single file. Note that this dump exceeds the size of one bank and may not be compatible with your hardware", 652 [this, exportPatches](const genericUI::MessageBox::Result _result) 653 { 654 if (_result != genericUI::MessageBox::Result::Ok) 655 return; 656 657 exportPatches(); 658 }); 659 } 660 else 661 { 662 exportPatches(); 663 } 664 665 return true; 666 } 667 668 void PatchManager::resized() 669 { 670 if(!m_treeDS) 671 return; 672 673 m_info->setVisible(m_layout == LayoutType::List); 674 m_resizerBarC.setVisible(m_layout == LayoutType::List); 675 676 std::vector<Component*> comps = {m_treeDS, &m_resizerBarA, m_treeTags, &m_resizerBarB, dynamic_cast<juce::Component*>(getListModel())}; 677 if(m_layout == LayoutType::List) 678 { 679 comps.push_back(&m_resizerBarC); 680 comps.push_back(m_info); 681 } 682 683 m_stretchableManager.layOutComponents(comps.data(), static_cast<int>(comps.size()), 0, 0, getWidth(), getHeight(), false, false); 684 685 auto layoutXAxis = [](Component* _target, const Component* _source) 686 { 687 _target->setTopLeftPosition(_source->getX(), _target->getY()); 688 _target->setSize(_source->getWidth(), _target->getHeight()); 689 }; 690 691 layoutXAxis(m_searchTreeDS, m_treeDS); 692 layoutXAxis(m_searchTreeTags, m_treeTags); 693 694 if(m_layout == LayoutType::List) 695 { 696 layoutXAxis(m_searchList, dynamic_cast<Component*>(getListModel())); 697 layoutXAxis(m_status, m_info); 698 } 699 else 700 { 701 m_searchList->setTopLeftPosition(m_grid->getX(), m_searchList->getY()); 702 m_searchList->setSize(m_grid->getWidth()/3 - g_padding, m_searchList->getHeight()); 703 704 m_status->setTopLeftPosition(m_searchList->getX() + m_searchList->getWidth() + g_padding, m_searchList->getY()); 705 m_status->setSize(m_grid->getWidth()/3*2, m_status->getHeight()); 706 } 707 } 708 709 juce::Colour PatchManager::getResizerBarColor() const 710 { 711 return m_treeDS->findColour(juce::TreeView::ColourIds::selectedItemBackgroundColourId); 712 } 713 714 bool PatchManager::copyPart(const uint8_t _target, const uint8_t _source) 715 { 716 if(_target == _source) 717 return false; 718 719 const auto source = requestPatchForPart(_source); 720 if(!source) 721 return false; 722 723 if(!activatePatch(source, _target)) 724 return false; 725 726 m_state.copy(_target, _source); 727 728 if(getCurrentPart() == _target) 729 setSelectedPatch(_target, m_state.getPatch(_target)); 730 731 return true; 732 } 733 734 std::shared_ptr<genericUI::UiObject> PatchManager::getTemplate(const std::string& _name) const 735 { 736 return m_editor.getTemplate(_name); 737 } 738 739 bool PatchManager::activatePatch(const std::string& _filename, const uint32_t _part) 740 { 741 if(_part >= m_state.getPartCount() || _part > m_editor.getProcessor().getController().getPartCount()) 742 return false; 743 744 const auto patches = loadPatchesFromFiles(std::vector<std::string>{_filename}); 745 746 if(patches.empty()) 747 return false; 748 749 const auto& patch = patches.front(); 750 751 if(!activatePatch(patch, _part)) 752 return false; 753 754 if(getCurrentPart() == _part) 755 getListModel()->setSelectedPatches(std::set<pluginLib::patchDB::PatchKey>{}); 756 757 return true; 758 } 759 760 std::vector<pluginLib::patchDB::PatchPtr> PatchManager::loadPatchesFromFiles(const juce::StringArray& _files) 761 { 762 std::vector<std::string> files; 763 764 for (const auto& file : _files) 765 files.push_back(file.toStdString()); 766 767 return loadPatchesFromFiles(files); 768 } 769 770 std::vector<pluginLib::patchDB::PatchPtr> PatchManager::loadPatchesFromFiles(const std::vector<std::string>& _files) 771 { 772 std::vector<pluginLib::patchDB::PatchPtr> patches; 773 774 for (const auto& file : _files) 775 { 776 pluginLib::patchDB::DataList results; 777 if(!loadFile(results, file) || results.empty()) 778 continue; 779 780 const auto defaultName = results.size() == 1 ? baseLib::filesystem::stripExtension(baseLib::filesystem::getFilenameWithoutPath(file)) : ""; 781 782 for (auto& result : results) 783 { 784 if(const auto patch = initializePatch(std::move(result), defaultName)) 785 patches.push_back(patch); 786 } 787 } 788 return patches; 789 } 790 791 void PatchManager::onLoadFinished() 792 { 793 DB::onLoadFinished(); 794 795 for(uint32_t i=0; i<std::min(m_editor.getProcessor().getController().getPartCount(), static_cast<uint8_t>(m_state.getPartCount())); ++i) 796 { 797 const auto p = m_state.getPatch(i); 798 799 // If the state has been deserialized, the patch key is valid but the search handle is not. Only restore if that is the case 800 if(p.isValid() && m_state.getSearchHandle(i) == pluginLib::patchDB::g_invalidSearchHandle) 801 { 802 if(!setSelectedPatch(i, p)) 803 m_state.clear(i); 804 } 805 else if(!m_state.isValid(i)) 806 { 807 // otherwise, try to restore from the currently loaded patch 808 updateStateAsync(i, requestPatchForPart(i)); 809 } 810 } 811 } 812 813 void PatchManager::setPerInstanceConfig(const std::vector<uint8_t>& _data) 814 { 815 if(_data.empty()) 816 return; 817 try 818 { 819 pluginLib::PluginStream s(_data); 820 const auto version = s.read<uint32_t>(); 821 if(version != 1) 822 return; 823 m_state.setConfig(s); 824 } 825 catch(std::range_error& e) 826 { 827 LOG("Failed to to load per instance config: " << e.what()); 828 } 829 } 830 831 void PatchManager::getPerInstanceConfig(std::vector<uint8_t>& _data) const 832 { 833 pluginLib::PluginStream s; 834 s.write<uint32_t>(1); // version 835 m_state.getConfig(s); 836 s.toVector(_data); 837 } 838 839 void PatchManager::onProgramChanged(const uint32_t _part) 840 { 841 if(isLoading()) 842 return; 843 return; 844 pluginLib::patchDB::Data data; 845 if(!requestPatchForPart(data, _part, 0)) 846 return; 847 const auto patch = initializePatch(std::move(data), {}); 848 if(!patch) 849 return; 850 updateStateAsync(_part, patch); 851 } 852 853 void PatchManager::setCurrentPart(uint32_t _part) 854 { 855 if(!m_state.isValid(_part)) 856 return; 857 858 setSelectedPatch(_part, m_state.getPatch(_part)); 859 } 860 861 void PatchManager::updateStateAsync(const uint32_t _part, const pluginLib::patchDB::PatchPtr& _patch) 862 { 863 if(!isValid(_patch)) 864 return; 865 866 const auto patchDs = _patch->source.lock(); 867 868 if(patchDs) 869 { 870 setSelectedPatch(_part, _patch); 871 return; 872 } 873 874 // we've got a patch, but we do not know its datasource and search handle, find the data source by executing a search 875 876 findDatasourceForPatch(_patch, [this, _part](const pluginLib::patchDB::Search& _search) 877 { 878 const auto handle = _search.handle; 879 880 std::vector<pluginLib::patchDB::PatchPtr> results; 881 results.assign(_search.results.begin(), _search.results.end()); 882 883 if(results.empty()) 884 return; 885 886 if(results.size() > 1) 887 { 888 // if there are multiple results, sort them, we prefer ROM results over other results 889 890 std::sort(results.begin(), results.end(), [](const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) 891 { 892 const auto dsA = _a->source.lock(); 893 const auto dsB = _b->source.lock(); 894 895 if(!dsA || !dsB) 896 return true; 897 898 if(dsA->type < dsB->type) 899 return true; 900 if(dsA->type > dsB->type) 901 return false; 902 if(dsA->name < dsB->name) 903 return true; 904 if(dsA->name > dsB->name) 905 return false; 906 if(_a->program < _b->program) 907 return true; 908 return false; 909 }); 910 } 911 912 const auto currentPatch = results.front(); 913 914 const auto key = pluginLib::patchDB::PatchKey(*currentPatch); 915 916 runOnUiThread([this, _part, key, handle] 917 { 918 cancelSearch(handle); 919 setSelectedPatch(_part, key); 920 }); 921 }); 922 } 923 924 ListModel* PatchManager::getListModel() const 925 { 926 if(m_layout == LayoutType::List) 927 return m_list; 928 return m_grid; 929 } 930 931 void PatchManager::startLoaderThread(const juce::File& _migrateFromDir/* = {}*/) 932 { 933 if(_migrateFromDir.getFullPathName().isEmpty()) 934 { 935 const auto& configOptions = m_editor.getProcessor().getConfigOptions(); 936 DB::startLoaderThread(configOptions.getDefaultFile().getParentDirectory()); 937 return; 938 } 939 DB::startLoaderThread(_migrateFromDir); 940 } 941 942 pluginLib::patchDB::SearchHandle PatchManager::getSearchHandle(const pluginLib::patchDB::DataSource& _ds, bool _selectTreeItem) 943 { 944 if(auto* item = m_treeDS->getItem(_ds)) 945 { 946 const auto searchHandle = item->getSearchHandle(); 947 948 // select the tree item that contains the data source and expand all parents to make it visible 949 if(_selectTreeItem) 950 { 951 selectTreeItem(item); 952 } 953 954 return searchHandle; 955 } 956 957 const auto search = getSearch(_ds); 958 959 if(!search) 960 return pluginLib::patchDB::g_invalidSearchHandle; 961 962 return search->handle; 963 } 964 965 void PatchManager::onSelectedItemsChanged() 966 { 967 // trees emit onSelectionChanged in destructor, be sure to guard it 968 if(!getListModel()) 969 return; 970 971 const auto selectedTags = m_selectedItems[m_treeTags]; 972 973 auto selectItem = [&](const TreeItem* _item) 974 { 975 if(_item->getSearchHandle() != pluginLib::patchDB::g_invalidSearchHandle) 976 { 977 getListModel()->setContent(_item->getSearchHandle()); 978 return true; 979 } 980 return false; 981 }; 982 983 if(!selectedTags.empty()) 984 { 985 if(selectedTags.size() == 1) 986 { 987 if(selectItem(*selectedTags.begin())) 988 return; 989 } 990 else 991 { 992 pluginLib::patchDB::SearchRequest search = (*selectedTags.begin())->getSearchRequest(); 993 for (const auto& selectedTag : selectedTags) 994 search.tags.add(selectedTag->getSearchRequest().tags); 995 getListModel()->setContent(std::move(search)); 996 return; 997 } 998 } 999 1000 const auto selectedDataSources = m_selectedItems[m_treeDS]; 1001 1002 if(!selectedDataSources.empty()) 1003 { 1004 const auto* item = *selectedDataSources.begin(); 1005 selectItem(item); 1006 } 1007 } 1008 1009 void PatchManager::changeListenerCallback(juce::ChangeBroadcaster* _source) 1010 { 1011 auto* cs = dynamic_cast<juce::ColourSelector*>(_source); 1012 1013 if(cs) 1014 { 1015 const auto tagType = static_cast<pluginLib::patchDB::TagType>(static_cast<int>(cs->getProperties()["tagType"])); 1016 const auto tag = cs->getProperties()["tag"].toString().toStdString(); 1017 1018 if(tagType != pluginLib::patchDB::TagType::Invalid && !tag.empty()) 1019 { 1020 const auto color = cs->getCurrentColour(); 1021 setTagColor(tagType, tag, color.getARGB()); 1022 1023 repaint(); 1024 } 1025 } 1026 } 1027 1028 void PatchManager::selectTreeItem(TreeItem* _item) 1029 { 1030 if(!_item) 1031 return; 1032 1033 _item->setSelected(true, true); 1034 1035 auto* parent = _item->getParentItem(); 1036 while(parent) 1037 { 1038 parent->setOpen(true); 1039 parent = parent->getParentItem(); 1040 } 1041 1042 _item->getOwnerView()->scrollToKeepItemVisible(_item); 1043 } 1044 1045 std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromString(const std::string& _text) 1046 { 1047 auto data = pluginLib::Clipboard::getDataFromString(m_editor.getProcessor(), _text); 1048 1049 if(data.sysex.empty()) 1050 return {}; 1051 1052 pluginLib::patchDB::DataList results; 1053 1054 if (!parseFileData(results, data.sysex)) 1055 return {}; 1056 1057 std::vector<pluginLib::patchDB::PatchPtr> patches; 1058 1059 for (auto& result : results) 1060 { 1061 if(const auto patch = initializePatch(std::move(result), {})) 1062 patches.push_back(patch); 1063 } 1064 1065 return patches; 1066 } 1067 1068 std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromClipboard() 1069 { 1070 return getPatchesFromString(juce::SystemClipboard::getTextFromClipboard().toStdString()); 1071 } 1072 1073 bool PatchManager::activatePatchFromString(const std::string& _text) 1074 { 1075 const auto patches = getPatchesFromString(_text); 1076 1077 if(patches.size() != 1) 1078 return false; 1079 1080 return activatePatch(patches.front(), getCurrentPart()); 1081 } 1082 1083 bool PatchManager::activatePatchFromClipboard() 1084 { 1085 return activatePatchFromString(juce::SystemClipboard::getTextFromClipboard().toStdString()); 1086 } 1087 1088 std::string PatchManager::toString(const pluginLib::patchDB::PatchPtr& _patch, const pluginLib::FileType& _fileType, const pluginLib::ExportType _exportType) const 1089 { 1090 if(!_patch) 1091 return {}; 1092 1093 const auto data = applyModifications(_patch, _fileType, _exportType); 1094 1095 return pluginLib::Clipboard::createJsonString(m_editor.getProcessor(), {}, {}, data); 1096 } 1097 }