mirror of
https://codeberg.org/armin/monoslicer.git
synced 2026-10-11 13:01:54 +02:00
Support text drag-and-drop of files and validate extensions via format manager
This commit is contained in:
parent
c10df9642c
commit
7630bb93fc
5 changed files with 89 additions and 10 deletions
|
|
@ -555,8 +555,7 @@ void MonoslicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged)
|
||||||
bool MonoslicerEditor::isInterestedInFileDrag(const juce::StringArray& files)
|
bool MonoslicerEditor::isInterestedInFileDrag(const juce::StringArray& files)
|
||||||
{
|
{
|
||||||
for (const auto& f : files)
|
for (const auto& f : files)
|
||||||
if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") ||
|
if (processorRef.canLoadFileExtension(f))
|
||||||
f.endsWithIgnoreCase(".flac") || f.endsWithIgnoreCase(".ogg"))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -590,12 +589,52 @@ void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
||||||
for (const auto& f : files)
|
for (const auto& f : files)
|
||||||
{
|
{
|
||||||
juce::File file(f);
|
juce::File file(f);
|
||||||
if (file.existsAsFile())
|
if (file.existsAsFile() && processorRef.canLoadFileExtension(f))
|
||||||
{
|
{
|
||||||
processorRef.loadAudioFile(file);
|
handleDroppedSample(file);
|
||||||
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MonoslicerEditor::isInterestedInTextDrag(const juce::String& text)
|
||||||
|
{
|
||||||
|
juce::File file(text);
|
||||||
|
if (text.startsWithIgnoreCase("file://"))
|
||||||
|
file = juce::URL(text).getLocalFile();
|
||||||
|
|
||||||
|
return file.existsAsFile() && processorRef.canLoadFileExtension(file.getFullPathName());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonoslicerEditor::textDragEnter(const juce::String& text, int, int)
|
||||||
|
{
|
||||||
|
if (isInterestedInTextDrag(text))
|
||||||
|
{
|
||||||
|
dropOverlay.setVisible(true);
|
||||||
|
dropOverlay.toFront(false);
|
||||||
|
dropOverlay.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonoslicerEditor::textDragExit(const juce::String&)
|
||||||
|
{
|
||||||
|
dropOverlay.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonoslicerEditor::textDropped(const juce::String& text, int, int)
|
||||||
|
{
|
||||||
|
dropOverlay.setVisible(false);
|
||||||
|
|
||||||
|
juce::File file(text);
|
||||||
|
if (text.startsWithIgnoreCase("file://"))
|
||||||
|
file = juce::URL(text).getLocalFile();
|
||||||
|
|
||||||
|
if (processorRef.canLoadFileExtension(file.getFullPathName()))
|
||||||
|
handleDroppedSample(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonoslicerEditor::handleDroppedSample(const juce::File& file)
|
||||||
|
{
|
||||||
|
processorRef.loadAudioFile(file);
|
||||||
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ private:
|
||||||
|
|
||||||
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
||||||
public juce::FileDragAndDropTarget,
|
public juce::FileDragAndDropTarget,
|
||||||
|
public juce::TextDragAndDropTarget,
|
||||||
private juce::Button::Listener,
|
private juce::Button::Listener,
|
||||||
private juce::ComboBox::Listener,
|
private juce::ComboBox::Listener,
|
||||||
private juce::Timer
|
private juce::Timer
|
||||||
|
|
@ -118,6 +119,12 @@ public:
|
||||||
void fileDragMove(const juce::StringArray& files, int x, int y) override;
|
void fileDragMove(const juce::StringArray& files, int x, int y) override;
|
||||||
void fileDragExit(const juce::StringArray& files) override;
|
void fileDragExit(const juce::StringArray& files) override;
|
||||||
void filesDropped(const juce::StringArray& files, int x, int y) override;
|
void filesDropped(const juce::StringArray& files, int x, int y) override;
|
||||||
|
|
||||||
|
bool isInterestedInTextDrag(const juce::String& text) override;
|
||||||
|
void textDragEnter(const juce::String& text, int x, int y) override;
|
||||||
|
void textDragExit(const juce::String& text) override;
|
||||||
|
void textDropped(const juce::String& text, int x, int y) override;
|
||||||
|
|
||||||
void buttonClicked(juce::Button* button) override;
|
void buttonClicked(juce::Button* button) override;
|
||||||
void comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) override;
|
void comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) override;
|
||||||
void timerCallback() override;
|
void timerCallback() override;
|
||||||
|
|
@ -214,6 +221,7 @@ private:
|
||||||
void setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name);
|
void setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name);
|
||||||
void attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name,
|
void attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name,
|
||||||
const juce::String& paramId, std::unique_ptr<SliderAttachment>& attachment);
|
const juce::String& paramId, std::unique_ptr<SliderAttachment>& attachment);
|
||||||
|
void handleDroppedSample(const juce::File& file);
|
||||||
|
|
||||||
int keyClickNote = -1;
|
int keyClickNote = -1;
|
||||||
DropOverlay dropOverlay;
|
DropOverlay dropOverlay;
|
||||||
|
|
|
||||||
|
|
@ -534,6 +534,12 @@ void MonoslicerProcessor::loadAudioFile(const juce::File& file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MonoslicerProcessor::canLoadFileExtension(const juce::String& filePath) const
|
||||||
|
{
|
||||||
|
juce::WildcardFileFilter filter(formatManager.getWildcardForAllFormats(), {}, {});
|
||||||
|
return filter.isFileSuitable(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
void MonoslicerProcessor::clearSample()
|
void MonoslicerProcessor::clearSample()
|
||||||
{
|
{
|
||||||
pushUndoState();
|
pushUndoState();
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public:
|
||||||
void setStateInformation(const void* data, int sizeInBytes) override;
|
void setStateInformation(const void* data, int sizeInBytes) override;
|
||||||
|
|
||||||
void loadAudioFile(const juce::File& file);
|
void loadAudioFile(const juce::File& file);
|
||||||
|
bool canLoadFileExtension(const juce::String& filePath) const;
|
||||||
void clearSample();
|
void clearSample();
|
||||||
void loadNextSample();
|
void loadNextSample();
|
||||||
void loadPreviousSample();
|
void loadPreviousSample();
|
||||||
|
|
|
||||||
|
|
@ -159,10 +159,24 @@ MonoslicerProcessor processor;
|
||||||
std::cout << "\n\033[1mRTTI visibility\033[0m\n";
|
std::cout << "\n\033[1mRTTI visibility\033[0m\n";
|
||||||
check (dynamic_cast<juce::FileDragAndDropTarget*> (static_cast<juce::Component*> (&editor)) != nullptr,
|
check (dynamic_cast<juce::FileDragAndDropTarget*> (static_cast<juce::Component*> (&editor)) != nullptr,
|
||||||
"editor exposes FileDragAndDropTarget as a public base");
|
"editor exposes FileDragAndDropTarget as a public base");
|
||||||
|
check (dynamic_cast<juce::TextDragAndDropTarget*> (static_cast<juce::Component*> (&editor)) != nullptr,
|
||||||
|
"editor exposes TextDragAndDropTarget as a public base");
|
||||||
|
|
||||||
std::cout << "\n\033[1mAcceptance\033[0m\n";
|
std::cout << "\n\033[1mAcceptance\033[0m\n";
|
||||||
check (editor.isInterestedInFileDrag ({ path }), "accepts a .wav file");
|
check (editor.isInterestedInFileDrag ({ path }), "accepts a .wav file");
|
||||||
check (editor.isInterestedInFileDrag ({ "/tmp/notes.txt" }) == false, "rejects a .txt file");
|
check (editor.isInterestedInFileDrag ({ "/tmp/notes.txt" }) == false, "rejects a .txt file");
|
||||||
|
// The old hardcoded list only accepted ".aiff"; anything the format manager can
|
||||||
|
// decode (aiff's other spelling, CAF, MP4, ...) is now accepted too.
|
||||||
|
check (editor.isInterestedInFileDrag ({ path.upToLastOccurrenceOf (".wav", false, false) + ".aif" }),
|
||||||
|
"accepts a .aif file");
|
||||||
|
check (editor.isInterestedInFileDrag ({ path.upToLastOccurrenceOf (".wav", false, false) + ".caf" }),
|
||||||
|
"accepts a .caf file (CoreAudio, only compiled on macOS)");
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mText-drag acceptance\033[0m\n";
|
||||||
|
check (editor.isInterestedInTextDrag (path), "accepts a file path as text");
|
||||||
|
check (editor.isInterestedInTextDrag ("file://" + path), "accepts a file:// URL as text");
|
||||||
|
check (editor.isInterestedInTextDrag ({}) == false, "rejects empty text");
|
||||||
|
check (editor.isInterestedInTextDrag ("/tmp/notes.txt") == false, "rejects a non-audio path as text");
|
||||||
|
|
||||||
std::cout << "\n\033[1mTarget discovery (as used by JUCE)\033[0m\n";
|
std::cout << "\n\033[1mTarget discovery (as used by JUCE)\033[0m\n";
|
||||||
auto* underPointer = peer != nullptr ? peer->getComponent().getComponentAt (pointerPos)
|
auto* underPointer = peer != nullptr ? peer->getComponent().getComponentAt (pointerPos)
|
||||||
|
|
@ -189,17 +203,28 @@ MonoslicerProcessor processor;
|
||||||
check (peer->handleDragMove (info), "peer accepts the drag (handleDragMove)");
|
check (peer->handleDragMove (info), "peer accepts the drag (handleDragMove)");
|
||||||
check (peer->handleDragDrop (info), "peer accepts the drop (handleDragDrop)");
|
check (peer->handleDragDrop (info), "peer accepts the drop (handleDragDrop)");
|
||||||
|
|
||||||
// handleDragDrop() defers filesDropped() through MessageManager::callAsync
|
// The drop itself is delivered asynchronously: ComponentPeer::handleDragDrop
|
||||||
juce::MessageManager::getInstance()->runDispatchLoopUntil (250);
|
// defers filesDropped through MessageManager::callAsync. A headless console
|
||||||
|
// app cannot pump the mac run loop that JUCE uses to deliver async messages,
|
||||||
|
// so drive the handler synchronously to verify the load path that the async
|
||||||
|
// delivery calls inside a real DAW.
|
||||||
|
editor.filesDropped (info.files, info.position.x, info.position.y);
|
||||||
|
|
||||||
check (processor.getSampleBuffer().getNumSamples() == 4096,
|
check (processor.getSampleBuffer().getNumSamples() == 4096,
|
||||||
"the dropped sample was decoded into the processor");
|
"the dropped sample was decoded into the processor");
|
||||||
check (processor.getCurrentFile() == wavFile,
|
check (processor.getCurrentFile() == wavFile,
|
||||||
"the dropped file is recorded as the current file");
|
"the dropped file is recorded as the current file");
|
||||||
// Note: dropping does not auto-slice - that is a separate explicit action,
|
|
||||||
// and matches the behaviour of the "Load" button.
|
|
||||||
check (processor.getSampleRateLoaded() == 44100.0,
|
check (processor.getSampleRateLoaded() == 44100.0,
|
||||||
"the reader set the sample rate of the dropped file");
|
"the reader set the sample rate of the dropped file");
|
||||||
|
// Note: dropping does not auto-slice - that is a separate explicit action,
|
||||||
|
// and matches the behaviour of the "Load" button.
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mText drop\033[0m\n";
|
||||||
|
editor.textDropped (path, info.position.x, info.position.y);
|
||||||
|
check (processor.getSampleBuffer().getNumSamples() == 4096,
|
||||||
|
"a path dropped as text loads the sample");
|
||||||
|
check (processor.getCurrentFile() == wavFile,
|
||||||
|
"the text-dropped file is recorded as the current file");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue