diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bab6c8d7c9..b58f9b72af8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,7 @@ set(SURGE_GUI_SOURCES src/common/gui/SkinImageMaps.h src/common/gui/CSurgeHyperlink.cpp src/common/ModulatorPresetManager.cpp - src/common/gui/CTextButtonWithHover.cpp src/common/gui/CTextButtonWithHover.h) + src/common/gui/CTextButtonWithHover.cpp src/common/gui/CTextButtonWithHover.h src/common/gui/RefreshableOverlay.h) set(SURGE_VST3_SOURCES src/vst3/SurgeVst3Processor.cpp diff --git a/src/common/gui/CLFOGui.cpp b/src/common/gui/CLFOGui.cpp index 7575f868cd1..f2668657732 100644 --- a/src/common/gui/CLFOGui.cpp +++ b/src/common/gui/CLFOGui.cpp @@ -1178,7 +1178,7 @@ void CLFOGui::openPopup(CPoint &where) auto sge = dynamic_cast(listener); - std::string openname = (sge->editorOverlayTag != "msegEditor") ? "Open MSEG Editor" : "Close MSEG Editor"; + std::string openname = (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) ? "Open MSEG Editor" : "Close MSEG Editor"; addCb(contextMenu, Surge::UI::toOSCaseForMenu(openname), [this, sge]() { if (sge) @@ -1190,7 +1190,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpoff = addCb(contextMenu, Surge::UI::toOSCaseForMenu("No Looping"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::ONESHOT; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); @@ -1202,7 +1202,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpon = addCb(contextMenu, Surge::UI::toOSCaseForMenu("Loop Always"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::LOOP; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); @@ -1214,7 +1214,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpgate = addCb(contextMenu, Surge::UI::toOSCaseForMenu("Loop Until Release"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::GATED_LOOP; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); diff --git a/src/common/gui/MSEGEditor.h b/src/common/gui/MSEGEditor.h index 18a376ed1f5..40e4b4f0a13 100644 --- a/src/common/gui/MSEGEditor.h +++ b/src/common/gui/MSEGEditor.h @@ -17,8 +17,9 @@ #include "vstcontrols.h" #include "SurgeStorage.h" #include "SkinSupport.h" +#include "RefreshableOverlay.h" -struct MSEGEditor : public VSTGUI::CViewContainer, public Surge::UI::SkinConsumingComponent { +struct MSEGEditor : public VSTGUI::CViewContainer, public Surge::UI::SkinConsumingComponent, public RefreshableOverlay { /* * Because this is 'late' in the build (in the gui) there is a copy of this structure in * the DawExtraState. If you add something to this state, add it there too. Revisit that diff --git a/src/common/gui/RefreshableOverlay.h b/src/common/gui/RefreshableOverlay.h new file mode 100644 index 00000000000..5e296b77f62 --- /dev/null +++ b/src/common/gui/RefreshableOverlay.h @@ -0,0 +1,24 @@ +/* +** Surge Synthesizer is Free and Open Source Software +** +** Surge is made available under the Gnu General Public License, v3.0 +** https://www.gnu.org/licenses/gpl-3.0.en.html +** +** Copyright 2004-2021 by various individuals as described by the Git transaction log +** +** All source at: https://github.com/surge-synthesizer/surge.git +** +** Surge was a commercial product from 2004-2018, with Copyright and ownership +** in that period held by Claes Johanson at Vember Audio. Claes made Surge +** open source in September 2018. +*/ + +#ifndef SURGE_REFRESHABLEOVERLAY_H +#define SURGE_REFRESHABLEOVERLAY_H + +class RefreshableOverlay { +public: + virtual void forceRefresh() = 0; +}; + +#endif // SURGE_REFRESHABLEOVERLAY_H diff --git a/src/common/gui/SurgeGUIEditor.cpp b/src/common/gui/SurgeGUIEditor.cpp index d2a4ec5b69c..ef1d9cff064 100644 --- a/src/common/gui/SurgeGUIEditor.cpp +++ b/src/common/gui/SurgeGUIEditor.cpp @@ -1064,7 +1064,7 @@ int32_t SurgeGUIEditor::onKeyDown(const VstKeyCode& code, CFrame* frame) return 1; break; case VKEY_TAB: - if ( (editorOverlay && editorOverlayTag == "storePatch" ) || (typeinDialog && typeinDialog->isVisible() ) ) + if ( (topmostEditorTag() == STORE_PATCH) || (typeinDialog && typeinDialog->isVisible() ) ) { /* ** SaveDialog gets access to the tab key to switch between fields if it is open @@ -1227,12 +1227,12 @@ void SurgeGUIEditor::openOrRecreateEditor() editorOverlayTagAtClose = ""; if (editor_open) { - if( editorOverlay != nullptr ) + editorOverlayTagAtClose = topmostEditorTag(); + for(auto el : editorOverlay) { - editorOverlay->remember(); - frame->removeView( editorOverlay ); - editorOverlayTagAtClose = editorOverlayTag; - } + el.second->remember(); + frame->removeView( el.second ); + } close_editor(); } @@ -1456,7 +1456,7 @@ void SurgeGUIEditor::openOrRecreateEditor() case Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_OPEN: { msegEditSwitch = layoutComponentForSkin( skinCtrl, tag_mseg_edit ); msegEditSwitch->setVisible( false ); - msegEditSwitch->setValue( editorOverlay != nullptr && editorOverlayTag == "msegEditor" ); + msegEditSwitch->setValue( isAnyOverlayPresent(MSEG_EDITOR) ); auto q = modsource_editor[current_scene]; if( ( q >= ms_lfo1 && q <= ms_lfo6 ) || ( q >= ms_slfo1 && q <= ms_slfo6 ) ) { @@ -1749,13 +1749,15 @@ void SurgeGUIEditor::openOrRecreateEditor() frame->addView(lb); debugLabel = lb; #endif - if( editorOverlay ) + for( auto el : editorOverlay ) { - frame->addView( editorOverlay ); + frame->addView( el.second ); + auto contents = editorOverlayContentsWeakReference[el.second]; + /* * This is a hack for 1.8 which we have to clean up in 1.9 when we do #3223 */ - auto mse = dynamic_cast(editorOverlayContentsWeakReference); + auto mse = dynamic_cast(contents); if (mse) { mse->forceRefresh(); @@ -1950,14 +1952,12 @@ bool PLUGIN_API SurgeGUIEditor::open(void* parent, const PlatformType& platformT void SurgeGUIEditor::close() { populateDawExtraState(synth); - if( editorOverlay ) + for (auto el: editorOverlay) { - frame->removeView( editorOverlay ); - editorOverlayOnClose(); - editorOverlayTagAtClose = editorOverlayTag; - editorOverlayTag = ""; - editorOverlay = nullptr; - editorOverlayContentsWeakReference = nullptr; + frame->removeView( el.second ); + auto f = editorOverlayOnClose[el.second]; + f(); + editorOverlayTagAtClose = el.first; } #if TARGET_VST2 // && WINDOWS // We may need this in other hosts also; but for now @@ -3722,7 +3722,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) } } - if(editorOverlay && editorOverlayTag == "msegEditor" ) + if(isAnyOverlayPresent(MSEG_EDITOR)) { auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[newsource-ms_lfo1]); if( ld->shape.val.i == lt_mseg ) @@ -3782,7 +3782,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) synth->storage.getPatch().scene_active.val.i = current_scene; // synth->storage.getPatch().param_ptr[scene_select_pid]->set_value_f01(control->getValue()); - if (editorOverlay && editorOverlayTag == "msegEditor") + if (isAnyOverlayPresent(MSEG_EDITOR)) { auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[modsource_editor[current_scene] - ms_lfo1]); if (ld->shape.val.i == lt_mseg) @@ -3811,7 +3811,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_mp_category: { - if( editorOverlay && editorOverlayTag == "storePatch" ) + if( isAnyOverlayPresent(STORE_PATCH)) { closeStorePatchDialog(); } @@ -3825,7 +3825,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_mp_patch: { - if( editorOverlay && editorOverlayTag == "storePatch" ) + if( isAnyOverlayPresent(STORE_PATCH) ) { closeStorePatchDialog(); } @@ -4017,7 +4017,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) { // prevent duplicate execution of savePatch() by detecting if the Store Patch dialog is displayed or not // FIXME: baconpaul will know a better and more correct way to fix this - if (editorOverlay && editorOverlayTag == "storePatch") + if (isAnyOverlayPresent(STORE_PATCH)) { /* ** Don't allow a blank patch @@ -4081,15 +4081,25 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_editor_overlay_close: { - if( editorOverlay != nullptr ) + // So can I find an editor overlay parent + VSTGUI::CView *p = control; + auto tagToNuke = NO_EDITOR; + while( p ) { - editorOverlay->setVisible(false); - removeFromFrame.push_back(editorOverlay); - editorOverlayOnClose(); - editorOverlay = nullptr; - editorOverlayTag = ""; - editorOverlayTagAtClose = ""; - editorOverlayContentsWeakReference = nullptr; + p = p->getParentView(); + for( auto el : editorOverlay ) + { + if( el.second == p ) + { + tagToNuke = el.first; + p = nullptr; + break; + } + } + } + if( tagToNuke != NO_EDITOR ) + { + dismissEditorOfType(tagToNuke); } } break; @@ -4361,9 +4371,9 @@ void SurgeGUIEditor::valueChanged(CControl* control) lfodisplay->setDirty(); lfodisplay->invalid(); } - if( editorOverlay ) + for(auto el: editorOverlay) { - editorOverlay->invalid(); + el.second->invalid(); } } if( p->ctrltype == ct_filtertype ) @@ -5090,7 +5100,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeLfoMenu(VSTGUI::CRect &menuRect) Surge::ModulatorPreset::loadPresetFrom(p.path, &(this->synth->storage), current_scene, currentLfoId ); auto newshape = this->synth->storage.getPatch().scene[current_scene].lfo[currentLfoId].shape.val.i; - if( editorOverlay && editorOverlayTag == "msegEditor" ) + if( isAnyOverlayPresent(MSEG_EDITOR) ) { closeMSEGEditor(); if( newshape == lt_mseg ) @@ -6102,13 +6112,13 @@ void SurgeGUIEditor::reloadFromSkin() clearOffscreenCachesAtZero = 1; // update MSEG editor if opened - if (editorOverlay && editorOverlayTag == "msegEditor") + if (isAnyOverlayPresent(MSEG_EDITOR)) { showMSEGEditor(); } // update Store Patch dialog if opened - if (editorOverlay && editorOverlayTag == "storePatch") + if (isAnyOverlayPresent(STORE_PATCH)) { auto pname = patchName->getText(); auto pcat = patchCategory->getText(); @@ -6792,24 +6802,35 @@ void SurgeGUIEditor::sliderHoverEnd( int tag ) } -void SurgeGUIEditor::dismissEditorOverlay() +void SurgeGUIEditor::dismissEditorOfType(OverlayTags ofType) { - if( editorOverlay != nullptr ) + if( editorOverlay.size() == 0 ) return; + + auto newO = editorOverlay; + newO.clear(); + for( auto el : editorOverlay ) { - editorOverlay->setVisible(false); - editorOverlayOnClose(); - removeFromFrame.push_back( editorOverlay ); - editorOverlay = nullptr; - editorOverlayTag = ""; - editorOverlayTagAtClose = ""; - editorOverlayContentsWeakReference = nullptr; + if( el.first == ofType ) + { + el.second->setVisible(false); + auto f = editorOverlayOnClose[el.second]; + f(); + + editorOverlayOnClose.erase(el.second); + editorOverlayContentsWeakReference.erase(el.second); + } + else + { + newO.push_back(el); + } } + editorOverlay = newO; } -void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, std::string editorTag, +void SurgeGUIEditor::addEditorOverlay(VSTGUI::CView *c, std::string editorTitle, OverlayTags editorTag, const VSTGUI::CPoint &topLeft, bool modalOverlay, bool hasCloseButton, std::function onClose) { - dismissEditorOverlay(); + dismissEditorOfType(editorTag); const int header = 18; const int buttonwidth = 18; @@ -6832,10 +6853,10 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, fs = containerSize; // add a screen size transparent thing into the editorOverlay - editorOverlay = new CViewContainer(fs); - editorOverlay->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background)); - editorOverlay->setVisible(true); - frame->addView(editorOverlay); + auto editorOverlayC = new CViewContainer(fs); + editorOverlayC->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background)); + editorOverlayC->setVisible(true); + frame->addView(editorOverlayC); if (modalOverlay) containerSize = containerSize.centerInside(fs); @@ -6847,13 +6868,13 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, auto outerc = new CViewContainer(containerSize); outerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Border)); - editorOverlay->addView(outerc); + editorOverlayC->addView(outerc); auto csz = containerSize; csz.bottom = csz.top + header; auto innerc = new CViewContainer(csz); innerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background)); - editorOverlay->addView(innerc); + editorOverlayC->addView(innerc); auto tl = new CTextLabel(csz, editorTitle.c_str()); tl->setBackColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background)); @@ -6931,13 +6952,12 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, c->setViewSize(containerSize); c->setMouseableArea(containerSize); // sigh - editorOverlay->addView(c); + editorOverlayC->addView(c); // save the onClose function - editorOverlayOnClose = onClose; - editorOverlayTag = editorTag; - editorOverlayTagAtClose = editorTag; - editorOverlayContentsWeakReference = c; + editorOverlay.push_back(std::make_pair(editorTag, editorOverlayC)); + editorOverlayOnClose[editorOverlayC] = onClose; + editorOverlayContentsWeakReference[editorOverlayC] = c; } std::string SurgeGUIEditor::getDisplayForTag( long tag ) @@ -7297,7 +7317,8 @@ void SurgeGUIEditor::makeStorePatchDialog() saveDialog->addView(cb); saveDialog->addView(kb); - setEditorOverlay(saveDialog, "Store Patch", "storePatch", CPoint(157, 57), false, false, [this]() {}); + addEditorOverlay(saveDialog, "Store Patch", STORE_PATCH, CPoint(157, 57), false, false, + [this]() {}); } VSTGUI::CControl *SurgeGUIEditor::layoutComponentForSkin( std::shared_ptr skinCtrl, @@ -7733,12 +7754,12 @@ void SurgeGUIEditor::lfoShapeChanged(int prior, int curr) } } - if( curr == lt_mseg && editorOverlay && editorOverlayTag == "msegEditor" ) + if( curr == lt_mseg && isAnyOverlayPresent(MSEG_EDITOR) ) { // We have the MSEGEditor open and have swapped to the MSEG here showMSEGEditor(); } - else if( prior == lt_mseg && curr != lt_mseg && editorOverlay && editorOverlayTag == "msegEditor" ) + else if( prior == lt_mseg && curr != lt_mseg && isAnyOverlayPresent(MSEG_EDITOR) ) { // We can choose to not do this too; if we do we are editing an MSEG which isn't used though closeMSEGEditor(); @@ -7752,10 +7773,8 @@ void SurgeGUIEditor::lfoShapeChanged(int prior, int curr) void SurgeGUIEditor::closeStorePatchDialog() { - if (editorOverlayTag == "storePatch") - { - dismissEditorOverlay(); - } + dismissEditorOfType(STORE_PATCH); + // Have to update all that state too for the newly orphaned items patchName = nullptr; patchCategory = nullptr; @@ -7766,24 +7785,19 @@ void SurgeGUIEditor::closeStorePatchDialog() void SurgeGUIEditor::showStorePatchDialog() { - if (editorOverlayTag == "msegEditor") - { - dismissEditorOverlay(); - } - makeStorePatchDialog(); } void SurgeGUIEditor::closeMSEGEditor() { - if( editorOverlayTag == "msegEditor" ) { + if( isAnyOverlayPresent(MSEG_EDITOR) ) { broadcastMSEGState(); - dismissEditorOverlay( ); + dismissEditorOfType(MSEG_EDITOR); } } void SurgeGUIEditor::toggleMSEGEditor() { - if( editorOverlayTag == "msegEditor" ) + if( isAnyOverlayPresent(MSEG_EDITOR) ) { closeMSEGEditor(); } @@ -7834,13 +7848,14 @@ void SurgeGUIEditor::showMSEGEditor() auto conn = Surge::Skin::Connector::connectorByNonParameterConnection(npc); auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn); - setEditorOverlay(mse, title, "msegEditor", CPoint(skinCtrl->x, skinCtrl->y), false, true, [this]() { - if (msegEditSwitch) - { - msegEditSwitch->setValue(0.0); - msegEditSwitch->invalid(); - } - }); + addEditorOverlay(mse, title, MSEG_EDITOR, CPoint(skinCtrl->x, skinCtrl->y), false, true, + [this]() { + if (msegEditSwitch) + { + msegEditSwitch->setValue(0.0); + msegEditSwitch->invalid(); + } + }); if( msegEditSwitch ) { diff --git a/src/common/gui/SurgeGUIEditor.h b/src/common/gui/SurgeGUIEditor.h index 5db68f92d88..bfeb8e09b89 100644 --- a/src/common/gui/SurgeGUIEditor.h +++ b/src/common/gui/SurgeGUIEditor.h @@ -332,14 +332,39 @@ class SurgeGUIEditor : public EditorType, int getWindowSizeX() const { return wsx; } int getWindowSizeY() const { return wsy; } - void setEditorOverlay( VSTGUI::CView *c, + /* + * We have an enumerated set of overlay tags which we can push + * to the UI. You *have* to give a new overlay type a tag in + * order for it to work. + */ + enum OverlayTags + { + NO_EDITOR, + MSEG_EDITOR, + STORE_PATCH + }; + + void addEditorOverlay( VSTGUI::CView *c, std::string editorTitle, // A window display title - whatever you want - std::string editorTag, // A tag by editor class. Please unique, no spaces. - const VSTGUI::CPoint &topleft = VSTGUI::CPoint( 0, 0 ), + OverlayTags editorTag, // A tag by editor class. Please unique, no spaces. + const VSTGUI::CPoint &topleft = VSTGUI::CPoint(0, 0), bool modalOverlay = true, bool hasCloseButton = true, - std::function onClose = [](){} ); - void dismissEditorOverlay(); + std::function onClose = []() {}); + void dismissEditorOfType(OverlayTags ofType); + OverlayTags topmostEditorTag() { + if( ! editorOverlay.size() ) return NO_EDITOR; + return editorOverlay.back().first; + } + bool isAnyOverlayPresent( OverlayTags tag ) + { + for( auto el : editorOverlay ) + { + if( el.first == tag ) return true; + } + return false; + } + std::string getDisplayForTag( long tag ); @@ -457,16 +482,16 @@ class SurgeGUIEditor : public EditorType, int typeinResetCounter = -1; std::string typeinResetLabel = ""; - VSTGUI::CViewContainer *editorOverlay = nullptr; - VSTGUI::CView* editorOverlayContentsWeakReference = - nullptr; // Use this very very carefully. It may hold a dangling ref until #3223 - std::function editorOverlayOnClose = [](){}; + // Data structures for a list of overlays and associated data witht hem + std::vector> editorOverlay; + std::unordered_map editorOverlayContentsWeakReference; + std::unordered_map> editorOverlayOnClose; VSTGUI::CViewContainer *minieditOverlay = nullptr; VSTGUI::CTextEdit *minieditTypein = nullptr; std::function minieditOverlayDone = [](const char *){}; public: - std::string editorOverlayTag, editorOverlayTagAtClose; + std::string editorOverlayTagAtClose; // FIXME what is this? void promptForMiniEdit(const std::string& value, const std::string& prompt, const std::string& title,