commit 26b413633aef8727dcef1a3f1ccd5121d44e28f6
parent ce7cc01b895f1fedbcc06d0d76d8d3fdb9d49778
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Tue, 21 May 2024 01:00:04 +0200
support copy/paste of patches via ctrl+c/v in text mode for easy sharing in forums etc
Diffstat:
6 files changed, 230 insertions(+), 2 deletions(-)
diff --git a/source/jucePluginEditorLib/patchmanager/datasourcetreeitem.cpp b/source/jucePluginEditorLib/patchmanager/datasourcetreeitem.cpp
@@ -161,6 +161,20 @@ namespace jucePluginEditorLib::patchManager
}
}));
}
+
+ if(m_dataSource->type == pluginLib::patchDB::SourceType::LocalStorage)
+ {
+ const auto clipboardPatches = getPatchManager().getPatchesFromClipboard();
+
+ if(!clipboardPatches.empty())
+ {
+ menu.addSeparator();
+ menu.addItem("Paste from Clipboard", [this, clipboardPatches]
+ {
+ getPatchManager().copyPatchesToLocalStorage(m_dataSource, clipboardPatches, -1);
+ });
+ }
+ }
menu.showMenuAsync({});
}
diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp
@@ -14,6 +14,10 @@
#include "../../jucePluginLib/types.h"
+#include "../../synthLib/os.h"
+
+#include "dsp56kEmu/logging.h"
+
#include "juce_gui_extra/misc/juce_ColourSelector.h"
namespace jucePluginEditorLib::patchManager
@@ -872,4 +876,114 @@ namespace jucePluginEditorLib::patchManager
_item->getOwnerView()->scrollToKeepItemVisible(_item);
}
+
+ std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromString(const std::string& _text)
+ {
+ if(_text.empty())
+ return {};
+
+ auto text = synthLib::lowercase(_text);
+
+ while(true)
+ {
+ const auto pos = text.find_first_of(" \n\r\t");
+ if(pos == std::string::npos)
+ break;
+ text = text.substr(0,pos) + text.substr(pos+1);
+ }
+
+ const auto posF0 = text.find("f0");
+ if(posF0 == std::string::npos)
+ return {};
+
+ const auto posF7 = text.rfind("f7");
+ if(posF7 == std::string::npos)
+ return {};
+
+ if(posF7 <= posF0)
+ return {};
+
+ const auto dataString = text.substr(posF0, posF7 + 2 - posF0);
+
+ if(dataString.size() & 1)
+ return {};
+
+ std::vector<uint8_t> data;
+ data.reserve(dataString.size()>>1);
+
+ for(size_t i=0; i<dataString.size(); i+=2)
+ {
+ char temp[3]{0,0,0};
+ temp[0] = dataString[i];
+ temp[1] = dataString[i+1];
+
+ const auto c = strtoul(temp, nullptr, 16);
+ if(c < 0 || c > 255)
+ return {};
+ data.push_back(static_cast<uint8_t>(c));
+ }
+
+ pluginLib::patchDB::DataList results;
+ parseFileData(results, data);
+
+ if(results.empty())
+ return {};
+
+ std::vector<pluginLib::patchDB::PatchPtr> patches;
+
+ for (auto& result : results)
+ {
+ if(const auto patch = initializePatch(std::move(result)))
+ patches.push_back(patch);
+ }
+
+ return patches;
+ }
+
+ std::vector<pluginLib::patchDB::PatchPtr> PatchManager::getPatchesFromClipboard()
+ {
+ return getPatchesFromString(juce::SystemClipboard::getTextFromClipboard().toStdString());
+ }
+
+ bool PatchManager::activatePatchFromString(const std::string& _text)
+ {
+ const auto patches = getPatchesFromString(_text);
+
+ if(patches.size() != 1)
+ return false;
+
+ return activatePatch(patches.front(), getCurrentPart());
+ }
+
+ bool PatchManager::activatePatchFromClipboard()
+ {
+ return activatePatchFromString(juce::SystemClipboard::getTextFromClipboard().toStdString());
+ }
+
+ std::string PatchManager::toString(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _bytesPerLine/* = 32*/) const
+ {
+ if(!_patch)
+ return {};
+
+ const auto data = prepareSave(_patch);
+
+ if(data.empty())
+ return {};
+
+ std::stringstream ss;
+
+ for(size_t i=0; i<data.size();)
+ {
+ if(i)
+ ss << '\n';
+ for(size_t j=0; j<_bytesPerLine && i<data.size(); ++j, ++i)
+ {
+ if(j)
+ ss << ' ';
+ ss << HEXN(static_cast<uint32_t>(data[i]), 2);
+ }
+ }
+
+ return ss.str();
+ }
}
diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.h b/source/jucePluginEditorLib/patchmanager/patchmanager.h
@@ -85,6 +85,12 @@ namespace jucePluginEditorLib::patchManager
std::string getTagTypeName(pluginLib::patchDB::TagType _type) const;
void setTagTypeName(pluginLib::patchDB::TagType _type, const std::string& _name);
+ std::vector<pluginLib::patchDB::PatchPtr> getPatchesFromString(const std::string& _text);
+ std::vector<pluginLib::patchDB::PatchPtr> getPatchesFromClipboard();
+ bool activatePatchFromString(const std::string& _text);
+ bool activatePatchFromClipboard();
+ std::string toString(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _bytesPerLine = 32) const;
+
private:
bool selectPatch(uint32_t _part, int _offset);
diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp
@@ -227,6 +227,58 @@ namespace jucePluginEditorLib
return true;
}
+ void Editor::copyCurrentPatchToClipboard() const
+ {
+ // copy patch of current part to Clipboard
+ const auto p = m_patchManager->requestPatchForPart(m_patchManager->getCurrentPart());
+
+ if(!p)
+ return;
+
+ const auto patchAsString = m_patchManager->toString(p);
+
+ if(patchAsString.empty())
+ return;
+
+ const auto time = juce::Time::getCurrentTime();
+
+ std::stringstream ss;
+ ss << getProcessor().getProperties().name << " - Patch copied at " << time.formatted("%Y.%m.%d %H:%M") << time.getUTCOffsetString(true);
+ ss << '\n';
+ ss << "Patch '" << p->getName() << "' data:\n";
+ ss << "```\n";
+ ss << patchAsString << '\n';
+ ss << "```";
+ juce::SystemClipboard::copyTextToClipboard(ss.str());
+ }
+
+ bool Editor::replaceCurrentPatchFromClipboard() const
+ {
+ return m_patchManager->activatePatchFromClipboard();
+ }
+
+ bool Editor::keyPressed(const juce::KeyPress& _key)
+ {
+ if(_key.getModifiers().isCommandDown())
+ {
+ switch(_key.getKeyCode())
+ {
+ case 'c':
+ case 'C':
+ copyCurrentPatchToClipboard();
+ return true;
+ case 'v':
+ case 'V':
+ if(replaceCurrentPatchFromClipboard())
+ return true;
+ break;
+ default:
+ return genericUI::Editor::keyPressed(_key);
+ }
+ }
+ return genericUI::Editor::keyPressed(_key);
+ }
+
void Editor::onDisclaimerFinished() const
{
if(!synthLib::isRunningUnderRosetta())
diff --git a/source/jucePluginEditorLib/pluginEditor.h b/source/jucePluginEditorLib/pluginEditor.h
@@ -62,7 +62,12 @@ namespace jucePluginEditorLib
bool shouldDropFilesWhenDraggedExternally(const juce::DragAndDropTarget::SourceDetails& sourceDetails, juce::StringArray& files, bool& canMoveFiles) override;
+ void copyCurrentPatchToClipboard() const;
+ bool replaceCurrentPatchFromClipboard() const;
+
private:
+ bool keyPressed(const juce::KeyPress& _key) override;
+
void onDisclaimerFinished() const;
const char* getResourceByFilename(const std::string& _name, uint32_t& _dataSize) override;
diff --git a/source/jucePluginEditorLib/pluginEditorState.cpp b/source/jucePluginEditorLib/pluginEditorState.cpp
@@ -1,11 +1,15 @@
#include "pluginEditorState.h"
+#include "pluginEditor.h"
#include "pluginProcessor.h"
-#include "../synthLib/os.h"
-#include "dsp56kEmu/logging.h"
+#include "patchmanager/patchmanager.h"
+
+#include "../synthLib/os.h"
#include "../juceUiLib/editor.h"
+#include "dsp56kEmu/logging.h"
+
namespace jucePluginEditorLib
{
PluginEditorState::PluginEditorState(Processor& _processor, pluginLib::Controller& _controller, std::vector<Skin> _includedSkins)
@@ -322,6 +326,39 @@ void PluginEditorState::openMenu()
menu.addSubMenu("Panic", panicMenu);
}
+ if(auto* editor = dynamic_cast<Editor*>(getEditor()))
+ {
+ menu.addSeparator();
+
+#ifdef JUCE_MAC
+ const std::string ctrlName = "Cmd";
+#else
+ const std::string ctrlName = "Ctrl";
+#endif
+
+ {
+ juce::PopupMenu::Item item("Copy current Patch to Clipboard");
+ item.shortcutKeyDescription = ctrlName + "+C";
+ item.action = [editor]
+ {
+ editor->copyCurrentPatchToClipboard();
+ };
+ menu.addItem(item);
+ }
+
+ auto patches = editor->getPatchManager()->getPatchesFromClipboard();
+ if(!patches.empty())
+ {
+ juce::PopupMenu::Item item("Replace current Patch from Clipboard");
+ item.shortcutKeyDescription = ctrlName + "+V";
+ item.action = [editor]
+ {
+ editor->replaceCurrentPatchFromClipboard();
+ };
+ menu.addItem(item);
+ }
+ }
+
{
const auto allowAdvanced = config.getBoolValue("allow_advanced_options", false);