grouptreeitem.cpp (12184B)
1 #include "grouptreeitem.h" 2 3 #include "datasourcetreeitem.h" 4 #include "patchmanager.h" 5 #include "search.h" 6 #include "tagtreeitem.h" 7 8 #include "baseLib/filesystem.h" 9 10 namespace jucePluginEditorLib::patchManager 11 { 12 class DatasourceTreeItem; 13 14 GroupTreeItem::GroupTreeItem(PatchManager& _pm, const GroupType _type, const std::string& _groupName) : TreeItem(_pm, _groupName), m_type(_type) 15 { 16 GroupTreeItem::onParentSearchChanged({}); 17 } 18 19 void GroupTreeItem::updateFromTags(const std::set<std::string>& _tags) 20 { 21 for(auto it = m_itemsByTag.begin(); it != m_itemsByTag.end();) 22 { 23 const auto tag = it->first; 24 const auto* item = it->second; 25 26 if(_tags.find(tag) == _tags.end()) 27 { 28 item->removeFromParent(true); 29 m_itemsByTag.erase(it++); 30 } 31 else 32 { 33 ++it; 34 } 35 } 36 37 for (const auto& tag : _tags) 38 { 39 auto itExisting = m_itemsByTag.find(tag); 40 41 if (itExisting != m_itemsByTag.end()) 42 { 43 validateParent(itExisting->second); 44 continue; 45 } 46 47 const auto oldNumSubItems = getNumSubItems(); 48 createSubItem(tag); 49 50 if (getNumSubItems() == 1 && oldNumSubItems == 0) 51 setOpen(true); 52 } 53 54 setDeselectonSecondClick(true); 55 } 56 57 void GroupTreeItem::removeItem(const DatasourceTreeItem* _item) 58 { 59 if (!_item) 60 return; 61 62 for(auto it = m_itemsByDataSource.begin(); it != m_itemsByDataSource.end(); ++it) 63 { 64 if (it->second == _item) 65 { 66 m_itemsByDataSource.erase(it); 67 break; 68 } 69 } 70 71 while (_item->getNumSubItems()) 72 removeItem(dynamic_cast<DatasourceTreeItem*>(_item->getSubItem(0))); 73 74 _item->removeFromParent(true); 75 } 76 77 void GroupTreeItem::removeDataSource(const pluginLib::patchDB::DataSourceNodePtr& _ds) 78 { 79 const auto it = m_itemsByDataSource.find(_ds); 80 if (it == m_itemsByDataSource.end()) 81 return; 82 removeItem(it->second); 83 } 84 85 void GroupTreeItem::updateFromDataSources(const std::vector<pluginLib::patchDB::DataSourceNodePtr>& _dataSources) 86 { 87 const auto previousItems = m_itemsByDataSource; 88 89 for (const auto& previousItem : previousItems) 90 { 91 const auto& ds = previousItem.first; 92 93 if (std::find(_dataSources.begin(), _dataSources.end(), ds) == _dataSources.end()) 94 removeDataSource(ds); 95 } 96 97 for (const auto& d : _dataSources) 98 { 99 const auto itExisting = m_itemsByDataSource.find(d); 100 101 if (m_itemsByDataSource.find(d) != m_itemsByDataSource.end()) 102 { 103 validateParent(itExisting->first, itExisting->second); 104 itExisting->second->refresh(); 105 continue; 106 } 107 108 auto* item = createItemForDataSource(d); 109 110 m_itemsByDataSource.insert({ d, item }); 111 } 112 } 113 114 void GroupTreeItem::processDirty(const std::set<pluginLib::patchDB::SearchHandle>& _dirtySearches) 115 { 116 for (const auto& it : m_itemsByTag) 117 it.second->processDirty(_dirtySearches); 118 119 for (const auto& it : m_itemsByDataSource) 120 it.second->processDirty(_dirtySearches); 121 122 TreeItem::processDirty(_dirtySearches); 123 } 124 125 void GroupTreeItem::itemClicked(const juce::MouseEvent& _mouseEvent) 126 { 127 if(!_mouseEvent.mods.isPopupMenu()) 128 { 129 TreeItem::itemClicked(_mouseEvent); 130 return; 131 } 132 133 juce::PopupMenu menu; 134 135 const auto tagType = toTagType(m_type); 136 137 if(m_type == GroupType::DataSources) 138 { 139 menu.addItem("Add folders...", [this] 140 { 141 m_chooser.reset(new juce::FileChooser("Select Folders")); 142 143 m_chooser->launchAsync( 144 juce::FileBrowserComponent::openMode | 145 juce::FileBrowserComponent::canSelectDirectories | 146 juce::FileBrowserComponent::canSelectMultipleItems 147 , [this](const juce::FileChooser& _fileChooser) 148 { 149 for (const auto& r : _fileChooser.getResults()) 150 { 151 const auto result = r.getFullPathName().toStdString(); 152 pluginLib::patchDB::DataSource ds; 153 ds.type = pluginLib::patchDB::SourceType::Folder; 154 ds.name = result; 155 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 156 getPatchManager().addDataSource(ds); 157 } 158 159 m_chooser.reset(); 160 }); 161 }); 162 163 menu.addItem("Add files...", [this] 164 { 165 m_chooser.reset(new juce::FileChooser("Select Files")); 166 167 m_chooser->launchAsync( 168 juce::FileBrowserComponent::openMode | 169 juce::FileBrowserComponent::canSelectFiles | 170 juce::FileBrowserComponent::canSelectMultipleItems, 171 [this](const juce::FileChooser& _fileChooser) 172 { 173 for (const auto&r : _fileChooser.getResults()) 174 { 175 const auto result = r.getFullPathName().toStdString(); 176 pluginLib::patchDB::DataSource ds; 177 ds.type = pluginLib::patchDB::SourceType::File; 178 ds.name = result; 179 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 180 getPatchManager().addDataSource(ds); 181 } 182 183 m_chooser.reset(); 184 }); 185 }); 186 } 187 else if(m_type == GroupType::LocalStorage) 188 { 189 menu.addItem("Create...", [this] 190 { 191 beginEdit("Enter name...", [this](bool _success, const std::string& _newText) 192 { 193 pluginLib::patchDB::DataSource ds; 194 195 ds.name = _newText; 196 ds.type = pluginLib::patchDB::SourceType::LocalStorage; 197 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 198 ds.timestamp = std::chrono::system_clock::now(); 199 200 getPatchManager().addDataSource(ds); 201 }); 202 }); 203 } 204 if(tagType != pluginLib::patchDB::TagType::Invalid) 205 { 206 menu.addItem("Add...", [this] 207 { 208 beginEdit("Enter name...", [this](bool _success, const std::string& _newText) 209 { 210 if (!_newText.empty()) 211 getPatchManager().addTag(toTagType(m_type), _newText); 212 }); 213 }); 214 } 215 216 menu.showMenuAsync(juce::PopupMenu::Options()); 217 } 218 219 void GroupTreeItem::setFilter(const std::string& _filter) 220 { 221 if (m_filter == _filter) 222 return; 223 224 m_filter = _filter; 225 226 for (const auto& it : m_itemsByDataSource) 227 validateParent(it.first, it.second); 228 229 for (const auto& it : m_itemsByTag) 230 validateParent(it.second); 231 } 232 233 bool GroupTreeItem::isInterestedInDragSource(const juce::DragAndDropTarget::SourceDetails& _dragSourceDetails) 234 { 235 // if there are no favourites yet, allow to drag onto this group node and create a default tag automatically if patches are dropped 236 if(m_type == GroupType::Favourites && m_itemsByTag.empty() && TreeItem::isInterestedInDragSource(_dragSourceDetails)) 237 return true; 238 239 if (isOpen()) 240 return false; 241 242 if( (!m_itemsByDataSource.empty() && m_itemsByDataSource.begin()->second->isInterestedInDragSource(_dragSourceDetails)) || 243 (!m_itemsByTag.empty() && m_itemsByTag.begin()->second->isInterestedInDragSource(_dragSourceDetails))) 244 setOpen(true); 245 246 return false; 247 } 248 249 DatasourceTreeItem* GroupTreeItem::getItem(const pluginLib::patchDB::DataSource& _ds) const 250 { 251 for (const auto& [_, item] : m_itemsByDataSource) 252 { 253 if(*item->getDataSource() == _ds) 254 return item; 255 } 256 return nullptr; 257 } 258 259 void GroupTreeItem::setParentSearchRequest(const pluginLib::patchDB::SearchRequest& _parentSearch) 260 { 261 TreeItem::setParentSearchRequest(_parentSearch); 262 263 for (const auto& it : m_itemsByDataSource) 264 it.second->setParentSearchRequest(_parentSearch); 265 266 for (const auto& it : m_itemsByTag) 267 it.second->setParentSearchRequest(_parentSearch); 268 } 269 270 void GroupTreeItem::onParentSearchChanged(const pluginLib::patchDB::SearchRequest& _parentSearchRequest) 271 { 272 TreeItem::onParentSearchChanged(_parentSearchRequest); 273 274 const auto sourceType = toSourceType(m_type); 275 276 if(sourceType != pluginLib::patchDB::SourceType::Invalid) 277 { 278 pluginLib::patchDB::SearchRequest req = _parentSearchRequest; 279 req.sourceType = sourceType; 280 search(std::move(req)); 281 } 282 283 const auto tagType = toTagType(m_type); 284 285 if(tagType != pluginLib::patchDB::TagType::Invalid) 286 { 287 pluginLib::patchDB::SearchRequest req = _parentSearchRequest; 288 req.anyTagOfType.insert(tagType); 289 search(std::move(req)); 290 } 291 } 292 293 bool GroupTreeItem::isInterestedInPatchList(const ListModel* _list, const std::vector<pluginLib::patchDB::PatchPtr>& _patches) 294 { 295 if(m_type == GroupType::Favourites) 296 return true; 297 return TreeItem::isInterestedInPatchList(_list, _patches); 298 } 299 300 void GroupTreeItem::patchesDropped(const std::vector<pluginLib::patchDB::PatchPtr>& _patches, const SavePatchDesc* _savePatchDesc) 301 { 302 if(!m_itemsByTag.empty() || m_type != GroupType::Favourites) 303 { 304 TreeItem::patchesDropped(_patches, _savePatchDesc); 305 return; 306 } 307 308 const auto tagType = toTagType(m_type); 309 310 if(tagType == pluginLib::patchDB::TagType::Invalid) 311 { 312 TreeItem::patchesDropped(_patches, _savePatchDesc); 313 return; 314 } 315 316 constexpr const char* const tag = "Favourites"; 317 318 getPatchManager().addTag(tagType, tag); 319 TagTreeItem::modifyTags(getPatchManager(), tagType, tag, _patches); 320 } 321 322 bool GroupTreeItem::isInterestedInFileDrag(const juce::StringArray& _files) 323 { 324 if(_files.isEmpty()) 325 return TreeItem::isInterestedInFileDrag(_files); 326 327 switch (m_type) 328 { 329 case GroupType::DataSources: 330 { 331 // do not allow to add data sources from temporary directory 332 const auto tempDir = juce::File::getSpecialLocation(juce::File::tempDirectory).getFullPathName().toStdString(); 333 for (const auto& file : _files) 334 { 335 if(file.toStdString().find(tempDir) == 0) 336 return false; 337 } 338 return true; 339 } 340 case GroupType::LocalStorage: 341 return true; 342 default: 343 return TreeItem::isInterestedInFileDrag(_files); 344 } 345 } 346 347 void GroupTreeItem::filesDropped(const juce::StringArray& _files, const int _insertIndex) 348 { 349 if(m_type == GroupType::DataSources) 350 { 351 for (const auto& file : _files) 352 { 353 pluginLib::patchDB::DataSource ds; 354 ds.name = file.toStdString(); 355 ds.type = pluginLib::patchDB::SourceType::File; 356 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 357 358 getPatchManager().addDataSource(ds); 359 } 360 } 361 else if(m_type == GroupType::LocalStorage) 362 { 363 for (const auto& file : _files) 364 { 365 auto patches = getPatchManager().loadPatchesFromFiles(std::vector<std::string>{file.toStdString()}); 366 367 if(patches.empty()) 368 continue; 369 370 pluginLib::patchDB::DataSource ds; 371 ds.name = baseLib::filesystem::getFilenameWithoutPath(file.toStdString()); 372 ds.type = pluginLib::patchDB::SourceType::LocalStorage; 373 ds.origin = pluginLib::patchDB::DataSourceOrigin::Manual; 374 375 getPatchManager().addDataSource(ds, [this, patches](const bool _success, const std::shared_ptr<pluginLib::patchDB::DataSourceNode>& _ds) 376 { 377 if(_success) 378 getPatchManager().copyPatchesToLocalStorage(_ds, patches, -1); 379 }); 380 } 381 } 382 else 383 { 384 TreeItem::filesDropped(_files, _insertIndex); 385 } 386 } 387 388 DatasourceTreeItem* GroupTreeItem::createItemForDataSource(const pluginLib::patchDB::DataSourceNodePtr& _dataSource) 389 { 390 const auto it = m_itemsByDataSource.find(_dataSource); 391 392 if (it != m_itemsByDataSource.end()) 393 return it->second; 394 395 auto* item = new DatasourceTreeItem(getPatchManager(), _dataSource); 396 397 m_itemsByDataSource.insert({ _dataSource, item }); 398 399 validateParent(_dataSource, item); 400 401 return item; 402 } 403 404 TagTreeItem* GroupTreeItem::createSubItem(const std::string& _tag) 405 { 406 auto item = new TagTreeItem(getPatchManager(), m_type, _tag); 407 408 validateParent(item); 409 410 m_itemsByTag.insert({ _tag, item }); 411 412 item->onParentSearchChanged(getParentSearchRequest()); 413 414 return item; 415 } 416 417 bool GroupTreeItem::needsParentItem(const pluginLib::patchDB::DataSourceNodePtr& _ds) const 418 { 419 if (!m_filter.empty()) 420 return false; 421 return _ds->hasParent() && _ds->origin != pluginLib::patchDB::DataSourceOrigin::Manual; 422 } 423 424 void GroupTreeItem::validateParent(const pluginLib::patchDB::DataSourceNodePtr& _ds, DatasourceTreeItem* _item) 425 { 426 TreeViewItem* parentNeeded = nullptr; 427 428 if (needsParentItem(_ds)) 429 { 430 parentNeeded = createItemForDataSource(_ds->getParent()); 431 } 432 else if (_ds->type == pluginLib::patchDB::SourceType::Folder && !m_filter.empty()) 433 { 434 parentNeeded = nullptr; 435 } 436 else if (match(*_item)) 437 { 438 parentNeeded = this; 439 } 440 441 _item->setParent(parentNeeded, true); 442 } 443 444 void GroupTreeItem::validateParent(TagTreeItem* _item) 445 { 446 if (match(*_item)) 447 _item->setParent(this, true); 448 else 449 _item->setParent(nullptr, true); 450 } 451 452 bool GroupTreeItem::match(const TreeItem& _item) const 453 { 454 if (m_filter.empty()) 455 return true; 456 const auto t = Search::lowercase(_item.getText()); 457 return t.find(m_filter) != std::string::npos; 458 } 459 }