diff --git a/cmake.plugin.txt b/cmake.plugin.txt index 53977c6..3cf4854 100644 --- a/cmake.plugin.txt +++ b/cmake.plugin.txt @@ -102,6 +102,8 @@ function(add_new_target target_name is_synth) "plugin/components/tokenizer.h" "plugin/components/tokenizer_functions.h" "plugin/components/tokenizer.cpp" + "plugin/components/ysfx_document.h" + "plugin/components/ysfx_document.cpp" "plugin/utility/audio_processor_suspender.h" "plugin/utility/functional_timer.h" "plugin/utility/async_updater.cpp" diff --git a/plugin/components/ide_view.cpp b/plugin/components/ide_view.cpp index 28e0a65..b38f315 100644 --- a/plugin/components/ide_view.cpp +++ b/plugin/components/ide_view.cpp @@ -20,31 +20,14 @@ #include "utility/functional_timer.h" #include "tokenizer.h" #include +#include "ysfx_document.h" -class YSFXCodeEditor : public juce::CodeEditorComponent -{ - public: - YSFXCodeEditor(juce::CodeDocument& doc, juce::CodeTokeniser* tokenizer, std::function keyPressCallback): CodeEditorComponent(doc, tokenizer), m_keyPressCallback{keyPressCallback} {} - - bool keyPressed(const juce::KeyPress &key) override - { - if (!m_keyPressCallback(key)) { - return juce::CodeEditorComponent::keyPressed(key); - } else { - return true; - }; - } - - private: - std::function m_keyPressCallback; -}; struct YsfxIDEView::Impl { YsfxIDEView *m_self = nullptr; ysfx_u m_fx; - juce::Time m_changeTime; - bool m_reloadDialogGuard = false; - std::unique_ptr m_document; + + std::vector> m_documents; std::unique_ptr m_tokenizer; std::unique_ptr m_editor; std::unique_ptr m_btnSave; @@ -68,17 +51,14 @@ struct YsfxIDEView::Impl { juce::Array m_vars; std::unique_ptr m_varsUpdateTimer; - juce::File m_file{}; - bool m_forceUpdate{false}; + int m_currentDocumentIndex{0}; //========================================================================== void setupNewFx(); void saveCurrentFile(); - void saveFile(juce::File filename); void saveAs(); void search(juce::String text, bool reverse); - void checkFileForModifications(); //========================================================================== void createUI(); @@ -91,8 +71,7 @@ YsfxIDEView::YsfxIDEView() : m_impl(new Impl) { m_impl->m_self = this; - - m_impl->m_document.reset(new juce::CodeDocument); + m_impl->m_documents.push_back(std::shared_ptr(new YSFXCodeDocument())); m_impl->m_tokenizer.reset(new JSFXTokenizer()); m_impl->createUI(); @@ -122,7 +101,7 @@ void YsfxIDEView::setEffect(ysfx_t *fx, juce::Time timeStamp) if (fx) ysfx_add_ref(fx); - m_impl->m_changeTime = timeStamp; + (void) timeStamp; m_impl->setupNewFx(); m_impl->m_btnSave->setEnabled(true); } @@ -150,7 +129,9 @@ void YsfxIDEView::focusOfChildComponentChanged(FocusChangeType cause) juce::Component *focus = getCurrentlyFocusedComponent(); if (focus == m_impl->m_editor.get()) { - juce::Timer *timer = FunctionalTimer::create([this]() { m_impl->checkFileForModifications(); }); + juce::Timer *timer = FunctionalTimer::create([this]() { + for (auto& document : m_impl->m_documents) document->checkFileForModifications(); + }); m_impl->m_fileCheckTimer.reset(timer); timer->startTimer(100); } @@ -168,24 +149,12 @@ void YsfxIDEView::Impl::setupNewFx() if (!fx) { // - m_document->replaceAllContent(juce::String{}); + m_documents[0]->reset(); m_editor->setReadOnly(true); } else { juce::File file{juce::CharPointer_UTF8{ysfx_get_file_path(fx)}}; - if (file != juce::File{}) m_file = file; - - { - juce::MemoryBlock memBlock; - if (m_file.loadFileAsData(memBlock)) { - juce::String newContent = memBlock.toString(); - memBlock = {}; - if (newContent != m_document->getAllContent()) { - m_document->replaceAllContent(newContent); - m_editor->moveCaretToTop(false); - } - } - } + m_documents[0]->loadFile(file); m_vars.ensureStorageAllocated(64); @@ -233,16 +202,15 @@ void YsfxIDEView::Impl::setupNewFx() void YsfxIDEView::Impl::saveAs() { if (m_fileChooserActive) return; + if (m_currentDocumentIndex >= m_documents.size()) return; - juce::File initialPath; - if (m_file != juce::File{}) { - initialPath = m_file.getParentDirectory(); - } + auto document = m_documents[m_currentDocumentIndex]; + juce::File initialPath = document->getPath().getParentDirectory(); m_fileChooser.reset(new juce::FileChooser(TRANS("Choose filename to save JSFX to"), initialPath)); m_fileChooser->launchAsync( juce::FileBrowserComponent::saveMode|juce::FileBrowserComponent::canSelectFiles, - [this](const juce::FileChooser &chooser) { + [this, document](const juce::FileChooser &chooser) { juce::File chosenFile = chooser.getResult(); if (chosenFile != juce::File()) { if (chosenFile.exists()) { @@ -254,16 +222,16 @@ void YsfxIDEView::Impl::saveAs() .withButton(TRANS("Yes")) .withButton(TRANS("No")) .withMessage(TRANS("File already exists! Overwrite?")), - [this, chosenFile](int result) { + [this, chosenFile, document](int result) { if (result == 1) { - this->saveFile(chosenFile); - m_self->onFileSaved(chosenFile); + document->saveFile(chosenFile); + if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath()); }; } ); } else { - saveFile(chosenFile); - m_self->onFileSaved(chosenFile); + document->saveFile(chosenFile); + if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath()); } } m_fileChooserActive = false; @@ -271,94 +239,38 @@ void YsfxIDEView::Impl::saveAs() ); } -void YsfxIDEView::Impl::saveFile(juce::File file) -{ - const juce::String content = m_document->getAllContent(); - - bool success = file.replaceWithData(content.toRawUTF8(), content.getNumBytesAsUTF8()); - if (!success) { - juce::AlertWindow::showAsync( - juce::MessageBoxOptions{} - .withParentComponent(m_self) - .withIconType(juce::MessageBoxIconType::WarningIcon) - .withTitle(TRANS("Error")) - .withButton(TRANS("OK")) - .withMessage(TRANS("Could not save the JSFX document.")), - nullptr); - return; - } - - m_changeTime = juce::Time::getCurrentTime(); - - if (m_self->onFileSaved) - m_self->onFileSaved(file); -} - void YsfxIDEView::Impl::saveCurrentFile() { ysfx_t *fx = m_fx.get(); if (!fx) return; - if (m_file.existsAsFile()) { - m_btnSave->setEnabled(false); - saveFile(m_file); + if (m_currentDocumentIndex >= m_documents.size()) return; + + if (m_documents[m_currentDocumentIndex]->getPath().existsAsFile()) { + m_documents[m_currentDocumentIndex]->saveFile(); } else { saveAs(); } -} - -void YsfxIDEView::Impl::checkFileForModifications() -{ - ysfx_t *fx = m_fx.get(); - if (!fx) - return; + m_btnSave->setEnabled(false); - if (m_file == juce::File{}) - return; - - juce::Time newMtime = m_file.getLastModificationTime(); - if (newMtime == juce::Time{}) - return; - - if (m_changeTime == juce::Time{} || newMtime > m_changeTime) { - m_changeTime = newMtime; - - if (!m_reloadDialogGuard) { - m_reloadDialogGuard = true; - - auto callback = [this](int result) { - m_reloadDialogGuard = false; - if (result != 0) { - if (m_self->onReloadRequested) - m_self->onReloadRequested(m_file); - } - }; - - juce::AlertWindow::showAsync( - juce::MessageBoxOptions{} - .withAssociatedComponent(m_self) - .withIconType(juce::MessageBoxIconType::QuestionIcon) - .withTitle(TRANS("Reload?")) - .withButton(TRANS("Yes")) - .withButton(TRANS("No")) - .withMessage(TRANS("The file has been modified outside this editor. Reload it?")), - callback); - } - } + if (m_self->onFileSaved) + m_self->onFileSaved(m_documents[0]->getPath()); } void YsfxIDEView::Impl::search(juce::String text, bool reverse=false) { if (text.isNotEmpty()) { - auto currentPosition = juce::CodeDocument::Position(*m_document, m_editor->getCaretPosition()); + if (m_currentDocumentIndex >= m_documents.size()) return; + std::shared_ptr document = m_documents[m_currentDocumentIndex]; + auto currentPosition = juce::CodeDocument::Position(*document, m_editor->getCaretPosition()); - auto chunk = [this, currentPosition](bool before) { + auto chunk = [this, currentPosition, document](bool before) { if (before) { - return m_document->getTextBetween(juce::CodeDocument::Position(*m_document, 0), currentPosition.movedBy(-1)); + return document->getTextBetween(juce::CodeDocument::Position(*document, 0), currentPosition.movedBy(-1)); } else { - return m_document->getTextBetween(currentPosition, juce::CodeDocument::Position(*m_document, m_document->getNumCharacters())); + return document->getTextBetween(currentPosition, juce::CodeDocument::Position(*document, document->getNumCharacters())); } }; @@ -370,7 +282,7 @@ void YsfxIDEView::Impl::search(juce::String text, bool reverse=false) if (position == -1) { // Not found at all -> stop - if (text.compare(m_document->getTextBetween(currentPosition.movedBy(- text.length()), currentPosition)) != 0) { + if (text.compare(document->getTextBetween(currentPosition.movedBy(- text.length()), currentPosition)) != 0) { m_lblStatus->setText(TRANS("Didn't find search string ") + text, juce::NotificationType::dontSendNotification); } else { m_lblStatus->setText(TRANS("Didn't find other copies of search string ") + text, juce::NotificationType::dontSendNotification); @@ -378,13 +290,13 @@ void YsfxIDEView::Impl::search(juce::String text, bool reverse=false) m_editor->grabKeyboardFocus(); return; } - searchPosition = juce::CodeDocument::Position(*m_document, reverse ? currentPosition.getPosition() + position : position); + searchPosition = juce::CodeDocument::Position(*document, reverse ? currentPosition.getPosition() + position : position); } else { // Found it! - searchPosition = juce::CodeDocument::Position(*m_document, reverse ? position : currentPosition.getPosition() + position); + searchPosition = juce::CodeDocument::Position(*document, reverse ? position : currentPosition.getPosition() + position); } - auto pos = juce::CodeDocument::Position(*m_document, searchPosition.getPosition()); + auto pos = juce::CodeDocument::Position(*document, searchPosition.getPosition()); m_editor->grabKeyboardFocus(); m_editor->moveCaretTo(pos, false); m_editor->moveCaretTo(pos.movedBy(text.length()), true); @@ -434,7 +346,7 @@ void YsfxIDEView::Impl::createUI() return false; }; - m_editor.reset(new YSFXCodeEditor(*m_document, m_tokenizer.get(), keyPressCallback)); + m_editor.reset(new YSFXCodeEditor(*(m_documents[0]), m_tokenizer.get(), keyPressCallback)); m_self->addAndMakeVisible(*m_editor); m_btnSave.reset(new juce::TextButton(TRANS("Save"))); m_btnSave->addShortcut(juce::KeyPress('s', juce::ModifierKeys::ctrlModifier, 0)); diff --git a/plugin/components/ysfx_document.cpp b/plugin/components/ysfx_document.cpp new file mode 100644 index 0000000..b6a6aa6 --- /dev/null +++ b/plugin/components/ysfx_document.cpp @@ -0,0 +1,84 @@ +#include "ysfx_document.h" +#include "modal_textinputbox.h" + + +YSFXCodeDocument::YSFXCodeDocument() : CodeDocument() +{ + reset(); +}; + +void YSFXCodeDocument::reset() +{ + replaceAllContent(juce::String{}); +} + +void YSFXCodeDocument::loadFile(juce::File file) +{ + if (file != juce::File{}) m_file = file; + if (!m_file.existsAsFile()) return; + + { + juce::MemoryBlock memBlock; + if (m_file.loadFileAsData(memBlock)) { + juce::String newContent = memBlock.toString(); + memBlock = {}; + if (newContent != getAllContent()) { + replaceAllContent(newContent); + // m_editor->moveCaretToTop(false); + } + } + } +} + +bool YSFXCodeDocument::loaded(void) { + return bool(m_file != juce::File{}); +} + +juce::File YSFXCodeDocument::getPath(void) { + return m_file; +} + +void YSFXCodeDocument::checkFileForModifications() +{ + if (m_file == juce::File{}) + return; + + juce::Time newMtime = m_file.getLastModificationTime(); + if (newMtime == juce::Time{}) + return; + + if (m_changeTime == juce::Time{} || newMtime > m_changeTime) { + m_changeTime = newMtime; + + if (!m_reloadDialogGuard) { + m_reloadDialogGuard = true; + + auto callback = [this](int result) { + m_reloadDialogGuard = false; + if (result != 0) { + loadFile(m_file); + } + }; + + m_alertWindow.reset(show_option_window(TRANS("Reload?"), TRANS("The file ") + m_file.getFileNameWithoutExtension() + TRANS(" has been modified outside this editor. Reload it?"), std::vector{"Yes", "No"}, callback)); + } + } +} + +bool YSFXCodeDocument::saveFile(juce::File path) +{ + const juce::String content = getAllContent(); + + if (path == juce::File{}) { + saveFile(m_file); + } + + bool success = m_file.replaceWithData(content.toRawUTF8(), content.getNumBytesAsUTF8()); + if (!success) { + m_alertWindow.reset(show_option_window(TRANS("Error"), TRANS("Could not save ") + m_file.getFileNameWithoutExtension() + TRANS("."), std::vector{"OK"}, [](int result){ (void) result; })); + return false; + } + + m_changeTime = juce::Time::getCurrentTime(); + return true; +} diff --git a/plugin/components/ysfx_document.h b/plugin/components/ysfx_document.h new file mode 100644 index 0000000..bdf360a --- /dev/null +++ b/plugin/components/ysfx_document.h @@ -0,0 +1,45 @@ +#pragma once +#include "modal_textinputbox.h" +#include +#include + + +class YSFXCodeDocument : public juce::CodeDocument { + public: + YSFXCodeDocument(); + ~YSFXCodeDocument() {}; + void reset(void); + void loadFile(juce::File file); + bool saveFile(juce::File file=juce::File{}); + void checkFileForModifications(); + + bool loaded(); + juce::File getPath(); + + private: + juce::File m_file{}; + juce::Time m_changeTime{0}; + bool m_reloadDialogGuard{false}; + + bool m_fileChooserActive{false}; + std::unique_ptr m_fileChooser; + std::unique_ptr m_alertWindow; +}; + +class YSFXCodeEditor : public juce::CodeEditorComponent +{ + public: + YSFXCodeEditor(juce::CodeDocument& doc, juce::CodeTokeniser* tokenizer, std::function keyPressCallback): CodeEditorComponent(doc, tokenizer), m_keyPressCallback{keyPressCallback} {} + + bool keyPressed(const juce::KeyPress &key) override + { + if (!m_keyPressCallback(key)) { + return juce::CodeEditorComponent::keyPressed(key); + } else { + return true; + }; + } + + private: + std::function m_keyPressCallback; +}; diff --git a/plugin/editor.cpp b/plugin/editor.cpp index 3ddeb04..30e4e44 100644 --- a/plugin/editor.cpp +++ b/plugin/editor.cpp @@ -988,7 +988,6 @@ void YsfxEditor::Impl::connectUI() }; m_ideView->onFileSaved = [this](const juce::File &file) { loadFile(file, true); }; - m_ideView->onReloadRequested = [this](const juce::File &file) { loadFile(file, true); }; m_infoTimer.reset(FunctionalTimer::create([this]() { grabInfoAndUpdate(); })); m_infoTimer->startTimer(100);