From 890594973143ae1c17032b15103a90337e00dcae Mon Sep 17 00:00:00 2001 From: Joep Vanlier Date: Sun, 15 Dec 2024 22:02:49 +0100 Subject: [PATCH] ide: add multi-file support --- cmake.plugin.txt | 2 + plugin/components/ide_view.cpp | 295 ++++++++++++---------------- plugin/components/ysfx_document.cpp | 100 ++++++++++ plugin/components/ysfx_document.h | 177 +++++++++++++++++ plugin/editor.cpp | 1 - 5 files changed, 408 insertions(+), 167 deletions(-) create mode 100644 plugin/components/ysfx_document.cpp create mode 100644 plugin/components/ysfx_document.h 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..538cb96 100644 --- a/plugin/components/ide_view.cpp +++ b/plugin/components/ide_view.cpp @@ -20,33 +20,16 @@ #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_editors; std::unique_ptr m_tokenizer; - std::unique_ptr m_editor; + // std::unique_ptr m_editor; std::unique_ptr m_btnSave; std::unique_ptr m_btnUpdate; std::unique_ptr m_lblVariablesHeading; @@ -57,6 +40,9 @@ struct YsfxIDEView::Impl { std::unique_ptr m_relayoutTimer; std::unique_ptr m_fileCheckTimer; std::unique_ptr m_fileChooser; + + std::unique_ptr m_tabs; + bool m_fileChooserActive{false}; struct VariableUI { @@ -68,17 +54,18 @@ struct YsfxIDEView::Impl { juce::Array m_vars; std::unique_ptr m_varsUpdateTimer; - juce::File m_file{}; - bool m_forceUpdate{false}; + int m_currentEditorIndex{0}; //========================================================================== void setupNewFx(); void saveCurrentFile(); - void saveFile(juce::File filename); void saveAs(); + std::shared_ptr addEditor(); + void openDocument(juce::File file); + void setCurrentEditor(int idx); + std::shared_ptr getCurrentEditor(); void search(juce::String text, bool reverse); - void checkFileForModifications(); //========================================================================== void createUI(); @@ -91,8 +78,6 @@ YsfxIDEView::YsfxIDEView() : m_impl(new Impl) { m_impl->m_self = this; - - m_impl->m_document.reset(new juce::CodeDocument); m_impl->m_tokenizer.reset(new JSFXTokenizer()); m_impl->createUI(); @@ -110,7 +95,7 @@ YsfxIDEView::~YsfxIDEView() void YsfxIDEView::setColourScheme(std::map> colormap) { m_impl->m_tokenizer->setColours(colormap); - m_impl->m_editor->setColourScheme(m_impl->m_tokenizer->getDefaultColourScheme()); + m_impl->m_editors[0]->setColourScheme(m_impl->m_tokenizer->getDefaultColourScheme()); } void YsfxIDEView::setEffect(ysfx_t *fx, juce::Time timeStamp) @@ -122,7 +107,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); } @@ -143,14 +128,23 @@ void YsfxIDEView::focusOnCodeEditor() m_impl->m_forceUpdate = true; } +std::shared_ptr YsfxIDEView::Impl::getCurrentEditor() +{ + if (m_currentEditorIndex >= m_editors.size()) { + setCurrentEditor(0); + } + + return m_editors[m_currentEditorIndex]; +} + void YsfxIDEView::focusOfChildComponentChanged(FocusChangeType cause) { (void)cause; - juce::Component *focus = getCurrentlyFocusedComponent(); - - if (focus == m_impl->m_editor.get()) { - juce::Timer *timer = FunctionalTimer::create([this]() { m_impl->checkFileForModifications(); }); + if (m_impl->getCurrentEditor()->hasFocus()) { + juce::Timer *timer = FunctionalTimer::create([this]() { + m_impl->getCurrentEditor()->checkFileForModifications(); + }); m_impl->m_fileCheckTimer.reset(timer); timer->startTimer(100); } @@ -168,24 +162,12 @@ void YsfxIDEView::Impl::setupNewFx() if (!fx) { // - m_document->replaceAllContent(juce::String{}); - m_editor->setReadOnly(true); + getCurrentEditor()->reset(); + getCurrentEditor()->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_editors[0]->loadFile(file); m_vars.ensureStorageAllocated(64); @@ -224,7 +206,7 @@ void YsfxIDEView::Impl::setupNewFx() m_varsUpdateTimer->startTimer(100); } - m_editor->setReadOnly(false); + m_editors[0]->setReadOnly(false); relayoutUILater(); } @@ -233,16 +215,15 @@ void YsfxIDEView::Impl::setupNewFx() void YsfxIDEView::Impl::saveAs() { if (m_fileChooserActive) return; + if (m_currentEditorIndex >= m_editors.size()) return; - juce::File initialPath; - if (m_file != juce::File{}) { - initialPath = m_file.getParentDirectory(); - } + auto editor = m_editors[m_currentEditorIndex]; + juce::File initialPath = editor->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, editor](const juce::FileChooser &chooser) { juce::File chosenFile = chooser.getResult(); if (chosenFile != juce::File()) { if (chosenFile.exists()) { @@ -254,16 +235,16 @@ void YsfxIDEView::Impl::saveAs() .withButton(TRANS("Yes")) .withButton(TRANS("No")) .withMessage(TRANS("File already exists! Overwrite?")), - [this, chosenFile](int result) { + [this, chosenFile, editor](int result) { if (result == 1) { - this->saveFile(chosenFile); - m_self->onFileSaved(chosenFile); + editor->saveFile(chosenFile); + if (m_self->onFileSaved) m_self->onFileSaved(m_editors[0]->getPath()); }; } ); } else { - saveFile(chosenFile); - m_self->onFileSaved(chosenFile); + m_editors[0]->saveFile(chosenFile); + if (m_self->onFileSaved) m_self->onFileSaved(m_editors[0]->getPath()); } } m_fileChooserActive = false; @@ -271,128 +252,54 @@ 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_currentEditorIndex >= m_editors.size()) return; + + if (getCurrentEditor()->getPath().existsAsFile()) { + getCurrentEditor()->saveFile(); } else { saveAs(); } + m_btnSave->setEnabled(false); + + if (m_self->onFileSaved) + m_self->onFileSaved(m_editors[0]->getPath()); } -void YsfxIDEView::Impl::checkFileForModifications() +void YsfxIDEView::Impl::openDocument(juce::File file) { - ysfx_t *fx = m_fx.get(); - if (!fx) - return; - - 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); - } + int idx = 0; + auto fn = file.getFileName(); + for (const auto& editor : m_editors) { + if (fn.compareIgnoreCase(editor->getName()) == 0) { + setCurrentEditor(idx); + return; + }; + idx += 1; } + + auto editor = addEditor(); + editor->loadFile(file); + setCurrentEditor(m_editors.size() - 1); } -void YsfxIDEView::Impl::search(juce::String text, bool reverse=false) +void YsfxIDEView::Impl::setCurrentEditor(int editorIndex) { - if (text.isNotEmpty()) - { - auto currentPosition = juce::CodeDocument::Position(*m_document, m_editor->getCaretPosition()); - - auto chunk = [this, currentPosition](bool before) { - if (before) { - return m_document->getTextBetween(juce::CodeDocument::Position(*m_document, 0), currentPosition.movedBy(-1)); - } else { - return m_document->getTextBetween(currentPosition, juce::CodeDocument::Position(*m_document, m_document->getNumCharacters())); - } - }; + if (editorIndex >= m_editors.size()) return; - int position = reverse ? chunk(true).lastIndexOfIgnoreCase(text) : chunk(false).indexOfIgnoreCase(text); - juce::CodeDocument::Position searchPosition; - if (position == -1) { - // We didn't find it! Start from the other end! - position = reverse ? chunk(false).lastIndexOfIgnoreCase(text) : chunk(true).indexOfIgnoreCase(text); + m_editors[m_currentEditorIndex]->setVisible(false); + m_currentEditorIndex = editorIndex; + m_editors[m_currentEditorIndex]->setVisible(true); - if (position == -1) { - // Not found at all -> stop - if (text.compare(m_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); - } - m_editor->grabKeyboardFocus(); - return; - } - searchPosition = juce::CodeDocument::Position(*m_document, reverse ? currentPosition.getPosition() + position : position); - } else { - // Found it! - searchPosition = juce::CodeDocument::Position(*m_document, reverse ? position : currentPosition.getPosition() + position); - } - - auto pos = juce::CodeDocument::Position(*m_document, searchPosition.getPosition()); - m_editor->grabKeyboardFocus(); - m_editor->moveCaretTo(pos, false); - m_editor->moveCaretTo(pos.movedBy(text.length()), true); - m_lblStatus->setText(TRANS("Found ") + text + TRANS(". (SHIFT +) CTRL/CMD + G to repeat search (backwards)."), juce::NotificationType::dontSendNotification); - } + relayoutUILater(); } -void YsfxIDEView::Impl::createUI() +std::shared_ptr YsfxIDEView::Impl::addEditor() { auto keyPressCallback = [this](const juce::KeyPress& key) -> bool { if (key.getModifiers().isCommandDown()) { @@ -405,7 +312,14 @@ void YsfxIDEView::Impl::createUI() m_searchEditor->grabKeyboardFocus(); m_searchEditor->setEscapeAndReturnKeysConsumed(true); m_searchEditor->onReturnKey = [this]() { - search(m_searchEditor->getText()); + auto searchResult = getCurrentEditor()->search(m_searchEditor->getText()); + + if (searchResult) { + m_lblStatus->setText(TRANS("Found ") + m_searchEditor->getText() + TRANS(". (SHIFT +) CTRL/CMD + G to repeat search (backwards)."), juce::NotificationType::dontSendNotification); + } else { + m_lblStatus->setText(TRANS("Didn't find search string ") + m_searchEditor->getText(), juce::NotificationType::dontSendNotification); + } + m_searchEditor->setWantsKeyboardFocus(false); m_searchEditor->setVisible(false); m_lblStatus->setVisible(true); @@ -426,7 +340,14 @@ void YsfxIDEView::Impl::createUI() if (key.isKeyCurrentlyDown('g')) { m_lblStatus->setText("", juce::NotificationType::dontSendNotification); - search(m_searchEditor->getText(), key.getModifiers().isShiftDown()); + int searchResult = getCurrentEditor()->search(m_searchEditor->getText(), key.getModifiers().isShiftDown()); + + if (searchResult) { + m_lblStatus->setText(TRANS("Found ") + m_searchEditor->getText() + TRANS(". (SHIFT +) CTRL/CMD + G to repeat search (backwards)."), juce::NotificationType::dontSendNotification); + } else { + m_lblStatus->setText(TRANS("Didn't find search string ") + m_searchEditor->getText(), juce::NotificationType::dontSendNotification); + } + return true; } } @@ -434,8 +355,34 @@ void YsfxIDEView::Impl::createUI() return false; }; - m_editor.reset(new YSFXCodeEditor(*m_document, m_tokenizer.get(), keyPressCallback)); - m_self->addAndMakeVisible(*m_editor); + auto dblClickCallback = [this](int x, int y) -> bool { + if (m_currentEditorIndex >= m_editors.size()) return false; + + auto line = getCurrentEditor()->getLineAt(x, y); + if (line.startsWithIgnoreCase("import ")) { + // Import statement! + auto potentialFilename = line.substring(7).trimEnd(); + if (this->m_fx) { + char *pathString = ysfx_resolve_path_and_allocate(this->m_fx.get(), potentialFilename.toStdString().c_str(), getCurrentEditor()->getPath().getFullPathName().toStdString().c_str()); + if (pathString) { + auto path = juce::File(juce::CharPointer_UTF8(pathString)); + openDocument(path); + ysfx_free_resolved_path(pathString); + return true; + }; + } + } + return false; + }; + + m_editors.push_back(std::make_shared(m_tokenizer.get(), keyPressCallback, dblClickCallback)); + m_self->addAndMakeVisible(m_editors.back()->getVisibleComponent()); + return m_editors.back(); +} + +void YsfxIDEView::Impl::createUI() +{ + addEditor(); m_btnSave.reset(new juce::TextButton(TRANS("Save"))); m_btnSave->addShortcut(juce::KeyPress('s', juce::ModifierKeys::ctrlModifier, 0)); m_self->addAndMakeVisible(*m_btnSave); @@ -457,6 +404,8 @@ void YsfxIDEView::Impl::createUI() m_self->addAndMakeVisible(*m_searchEditor); m_self->addAndMakeVisible(*m_lblStatus); m_searchEditor->setVisible(false); + m_tabs.reset(new YSFXTabbedButtonBar(juce::TabbedButtonBar::Orientation::TabsAtBottom, [this](int newCurrentTabIndex) { setCurrentEditor(newCurrentTabIndex); })); + m_self->addAndMakeVisible(*m_tabs); } void YsfxIDEView::Impl::connectUI() @@ -473,6 +422,20 @@ void YsfxIDEView::Impl::relayoutUI() temp = bounds; const juce::Rectangle debugArea = temp.removeFromRight(300); const juce::Rectangle topRow = temp.removeFromTop(50); + + if (m_editors.size() > 1) { + const juce::Rectangle tabRow = temp.removeFromTop(30); + m_tabs->setBounds(tabRow); + auto updateBlock = ScopedUpdateBlocker(*m_tabs); + m_tabs->clearTabs(); + int idx = 0; + for (const auto m : m_editors) { + m_tabs->addTab(m->getName(), m_self->getLookAndFeel().findColour(m_btnSave->buttonColourId), idx); + ++idx; + } + m_tabs->setCurrentTabIndex(m_currentEditorIndex, false); + } + const juce::Rectangle statusArea = temp.removeFromBottom(20); const juce::Rectangle editArea = temp; @@ -503,7 +466,7 @@ void YsfxIDEView::Impl::relayoutUI() m_lblStatus->setBounds(statusArea); m_searchEditor->setBounds(statusArea); - m_editor->setBounds(editArea); + getCurrentEditor()->setBounds(editArea); if (m_relayoutTimer) m_relayoutTimer->stopTimer(); diff --git a/plugin/components/ysfx_document.cpp b/plugin/components/ysfx_document.cpp new file mode 100644 index 0000000..2548461 --- /dev/null +++ b/plugin/components/ysfx_document.cpp @@ -0,0 +1,100 @@ +#include "ysfx_document.h" +#include "modal_textinputbox.h" + + +YSFXCodeDocument::YSFXCodeDocument() : CodeDocument() +{ + reset(); +}; + +void YSFXCodeDocument::reset() +{ + replaceAllContent(juce::String{}); +} + +void YSFXCodeDocument::loadFile(juce::File file) +{ + bool clearUndo = false; + if (file != juce::File{}) { + if (m_file != file) { + clearUndo = true; + m_file = file; + } + } + if (!m_file.existsAsFile()) return; + + { + m_changeTime = m_file.getLastModificationTime(); + juce::MemoryBlock memBlock; + if (m_file.loadFileAsData(memBlock)) { + juce::String newContent = memBlock.toString(); + memBlock = {}; + if (newContent != getAllContent()) { + replaceAllContent(newContent); + if (clearUndo) { + clearUndoHistory(); + setSavePoint(); + } + } + } + } +} + +bool YSFXCodeDocument::loaded(void) { + return bool(m_file != juce::File{}); +} + +juce::File YSFXCodeDocument::getPath(void) { + return m_file; +} + +juce::String YSFXCodeDocument::getName(void) { + if (!m_file.existsAsFile()) return juce::String("Untitled"); + + return m_file.getFileName(); +} + +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..28ddbe6 --- /dev/null +++ b/plugin/components/ysfx_document.h @@ -0,0 +1,177 @@ +#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(); + juce::String getName(void); + + 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 CodeEditor : public juce::CodeEditorComponent +{ + public: + CodeEditor( + juce::CodeDocument& doc, juce::CodeTokeniser* tokenizer, std::function keyPressCallback, std::function dblClickCallback + ) : CodeEditorComponent(doc, tokenizer), m_keyPressCallback{keyPressCallback}, m_dblClickCallback{dblClickCallback} {}; + + bool keyPressed(const juce::KeyPress &key) override + { + if (!m_keyPressCallback(key)) { + return juce::CodeEditorComponent::keyPressed(key); + } else { + return true; + }; + } + + void mouseDoubleClick(const juce::MouseEvent &e) override + { + if (!m_dblClickCallback(e.x, e.y)) { + return juce::CodeEditorComponent::mouseDoubleClick(e); + }; + } + + juce::String getLineAt(int x, int y) const { + juce::CodeDocument::Position position(getPositionAt(x, y)); + auto& doc = getDocument(); + juce::CodeDocument::Position start(doc, 0); + juce::CodeDocument::Position stop(doc, 0); + doc.findLineContaining(position, start, stop); + return doc.getTextBetween(start, stop); + }; + + int search(juce::String text, bool reverse=false) + { + if (text.isEmpty()) return 0; + + auto currentPosition = juce::CodeDocument::Position(getDocument(), getCaretPosition()); + + auto chunk = [this, currentPosition](bool before) { + if (before) { + return getDocument().getTextBetween(juce::CodeDocument::Position(getDocument(), 0), currentPosition.movedBy(-1)); + } else { + return getDocument().getTextBetween(currentPosition, juce::CodeDocument::Position(getDocument(), getDocument().getNumCharacters())); + } + }; + + int position = reverse ? chunk(true).lastIndexOfIgnoreCase(text) : chunk(false).indexOfIgnoreCase(text); + + juce::CodeDocument::Position searchPosition; + if (position == -1) { + // We didn't find it! Start from the other end! + position = reverse ? chunk(false).lastIndexOfIgnoreCase(text) : chunk(true).indexOfIgnoreCase(text); + + if (position == -1) { + grabKeyboardFocus(); + return 0; + } + searchPosition = juce::CodeDocument::Position(getDocument(), reverse ? currentPosition.getPosition() + position : position); + } else { + // Found it! + searchPosition = juce::CodeDocument::Position(getDocument(), reverse ? position : currentPosition.getPosition() + position); + } + + auto pos = juce::CodeDocument::Position(getDocument(), searchPosition.getPosition()); + grabKeyboardFocus(); + moveCaretTo(pos, false); + moveCaretTo(pos.movedBy(text.length()), true); + return 1; + } + + private: + std::function m_keyPressCallback; + std::function m_dblClickCallback; +}; + + +class YSFXCodeEditor +{ + public: + YSFXCodeEditor(juce::CodeTokeniser* tokenizer, std::function keyPressCallback, std::function dblClickCallback) { + m_document = std::make_unique(); + m_editor = std::make_unique(*m_document, tokenizer, keyPressCallback, dblClickCallback); + m_editor->setVisible(false); + }; + ~YSFXCodeEditor() { + // Make sure we kill the editor first since it may be referencing the document! + m_editor.reset(); + m_document.reset(); + } + + void setColourScheme(juce::CodeEditorComponent::ColourScheme colourScheme) { m_editor->setColourScheme(colourScheme); }; + void checkFileForModifications() { m_document->checkFileForModifications(); }; + void reset() { m_document->reset(); }; + void setReadOnly(bool readOnly) { m_editor->setReadOnly(readOnly); }; + + juce::File getPath() { return m_document->getPath(); }; + juce::String getName() { return m_document->getName(); }; + void loadFile(juce::File file) { m_document->loadFile(file); }; + bool saveFile(juce::File file = juce::File{}) { return m_document->saveFile(file); }; + int search(juce::String text, bool reverse = false) { return m_editor->search(text, reverse); }; + + bool hasFocus() { + juce::Component *focus = m_editor->getCurrentlyFocusedComponent(); + return focus == m_editor.get(); + }; + + juce::String getLineAt(int x, int y) const { return m_editor->getLineAt(x, y); }; + CodeEditor* getVisibleComponent() { return m_editor.get(); }; + + void setVisible(bool visible) { m_editor->setVisible(visible); }; + template + void setBounds(T&& arg) { m_editor->setBounds(std::forward(arg)); }; + + private: + std::unique_ptr m_editor; + std::unique_ptr m_document; +}; + + +class YSFXTabbedButtonBar : public juce::TabbedButtonBar +{ + public: + YSFXTabbedButtonBar(TabbedButtonBar::Orientation orientation, std::function changeCallback): TabbedButtonBar(orientation), m_changeCallback(changeCallback) {}; + + private: + void currentTabChanged(int newCurrentTabIndex, const juce::String &newCurrentTabName) override + { + (void) newCurrentTabName; + if (m_emitChange) m_changeCallback(newCurrentTabIndex); + } + + std::function m_changeCallback = [](int idx){ (void) idx; }; + bool m_emitChange{true}; + + friend class ScopedUpdateBlocker; +}; + + +class ScopedUpdateBlocker +{ + public: + ScopedUpdateBlocker(YSFXTabbedButtonBar& buttonBar): m_bar(buttonBar) { m_bar.m_emitChange = false; }; + ~ScopedUpdateBlocker() { m_bar.m_emitChange = true; }; + + private: + YSFXTabbedButtonBar& m_bar; +}; 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);