gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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 }