diff --git a/CMakeLists.txt b/CMakeLists.txt index 26247998926..43764ded4c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,7 +357,6 @@ if( APPLE ) ) set(SURGE_OS_GUI_SOURCES - src/mac/UserInteractionsMac.mm ) set(OS_INCLUDE_DIRECTORIES @@ -394,7 +393,6 @@ if( APPLE ) elseif( UNIX AND NOT APPLE ) set(SURGE_OS_GUI_SOURCES - src/linux/UserInteractionsLinux.cpp ) find_package(PkgConfig REQUIRED) @@ -469,7 +467,6 @@ elseif( UNIX AND NOT APPLE ) elseif( WIN32 ) set(SURGE_OS_GUI_SOURCES - src/windows/UserInteractionsWin.cpp ) set(OS_COMPILE_DEFINITIONS @@ -562,7 +559,6 @@ if( BUILD_HEADLESS ) ${SURGE_OS_SOURCES} ${SURGE_GENERATED_SOURCES} src/headless/main.cpp - src/headless/UserInteractionsHeadless.cpp src/headless/LinkFixesHeadless.cpp src/headless/HeadlessUtils.cpp src/headless/Player.cpp @@ -678,13 +674,6 @@ file(MAKE_DIRECTORY ${SURGE_PRODUCT_DIR}) add_custom_target(surge-staged-assets) if( BUILD_SURGE_EFFECTS_BANK ) - # LinkOrder on Linux matters so make a little lib which contains UIJUCE so I can link - # it AFTER surge-shared (since the libs come after the source in the JUCE build of course) - # FIXME of course that this should go away in surge-xt. These are also split now for - # XT vs FX since FX is production code in 19 and XT is not - add_library(surge-juce-userint-fx src/surge_effects_bank/UserInteractionsJUCE.cpp ) - target_include_directories(surge-juce-userint-fx PRIVATE ${SURGE_COMMON_INCLUDES}) - juce_add_plugin(surge-fx PRODUCT_NAME "Surge XT Effects" COMPANY_NAME "Surge Synth Team" @@ -759,7 +748,6 @@ if( BUILD_SURGE_EFFECTS_BANK ) surge::tinyxml surge-shared - surge-juce-userint-fx surge-fx-binary juce::juce_audio_utils @@ -878,11 +866,6 @@ if( BUILD_SURGE_XT ) ) endif() - # LinkOrder on Linux matters so make a little lib which contains UIJUCE so I can link - # it AFTER surge-shared (since the libs come after the source in the JUCE build of course) - add_library(surge-xt-userint src/surge_synth_juce/UserInteractionsJUCE_Synth_XT.cpp) - target_include_directories(surge-xt-userint PRIVATE ${SURGE_COMMON_INCLUDES}) - set(SURGE_SYNTH_JUCE_GUI_SOURCES ${SURGE_GUI_SOURCES} ) @@ -928,7 +911,6 @@ if( BUILD_SURGE_XT ) surge::tinyxml surge-shared - surge-xt-userint juce::juce_audio_utils juce::juce_audio_processors @@ -1046,7 +1028,6 @@ if( ${BUILD_SURGE_PYTHON_BINDINGS} ) ${PYSRCD}/surgepy.cpp src/headless/HeadlessUtils.cpp - src/headless/UserInteractionsHeadless.cpp ) target_compile_definitions(surgepy diff --git a/src/common/PatchDB.cpp b/src/common/PatchDB.cpp index cb9cd95a2fe..917b9899cab 100644 --- a/src/common/PatchDB.cpp +++ b/src/common/PatchDB.cpp @@ -17,7 +17,6 @@ #include "sqlite3.h" #include "SurgeStorage.h" -#include "UserInteractions.h" #include #include #include "vt_dsp_endian.h" @@ -70,7 +69,7 @@ CREATE TABLE PatchFeature ( std::ostringstream oss; oss << "An error occured opening sqlite file '" << dbname << "'. The error was '" << sqlite3_errmsg(dbh) << "'."; - Surge::UserInteractions::promptError(oss.str(), "Surge Patch Database Error"); + storage->reportError(oss.str(), "Surge Patch Database Error"); dbh = nullptr; } char *emsg; diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index a443a503679..272ec76737e 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -23,7 +23,6 @@ #include "DebugHelpers.h" #include "StringOps.h" #include "SkinModel.h" -#include "UserInteractions.h" #include "UserDefaults.h" #include "version.h" @@ -1109,7 +1108,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) << ". Features of the patch will not be available in your " << "session. You can always find the latest Surge at " "https://surge-synthesizer.github.io/"; - Surge::UserInteractions::promptError(oss.str(), "Surge Patch Version Mismatch"); + storage->reportError(oss.str(), "Surge Patch Version Mismatch"); } TiXmlElement *meta = TINYXML_SAFE_TO_ELEMENT(patch->FirstChild("meta")); diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index abe2a296cb6..46b61fef624 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -15,7 +15,6 @@ #include "DspUtilities.h" #include "SurgeStorage.h" -#include "UserInteractions.h" #include #include #include @@ -450,7 +449,7 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) if (!snapshotloader.LoadFile(snapshotmenupath)) // load snapshots (& config-stuff) { - Surge::UserInteractions::promptError("Cannot find 'configuration.xml' in path '" + + reportError("Cannot find 'configuration.xml' in path '" + datapath + "'. Please reinstall surge.", "Surge is not properly installed."); } @@ -462,9 +461,9 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) "\n"; if (!snapshotloader.Parse(cxmlData.c_str())) { - Surge::UserInteractions::promptError("Cannot parse 'configuration.xml' in path '" + - datapath + "'. Please reinstall surge.", - "Surge is not properly installed."); + reportError("Cannot parse 'configuration.xml' in path '" + datapath + + "'. Please reinstall surge.", + "Surge is not properly installed."); } load_midi_controllers(); @@ -494,7 +493,7 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) oss << "Unable to load 'windows.wt'. from memory. " << "This is a usually fatal internal software error in Surge XT which should" << " never occur!"; - Surge::UserInteractions::promptError(oss.str(), "Surge Resources Loading Error"); + reportError(oss.str(), "Surge Resources Loading Error"); } // Tunings Library Support @@ -529,7 +528,7 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) TiXmlElement *pdoc = TINYXML_SAFE_TO_ELEMENT(doc.FirstChild("param-doc")); if (!pdoc) { - Surge::UserInteractions::promptError( + reportError( "Unknown top element in paramdocumentation.xml - not a parameter documentation " "XML file!", "Error"); @@ -778,7 +777,7 @@ void SurgeStorage::refreshPatchOrWTListAddDir(bool userDir, string subdir, { std::ostringstream oss; oss << "Experienced file system error when building patches. " << e.what(); - Surge::UserInteractions::promptError(oss.str(), "FileSystem Error"); + reportError(oss.str(), "FileSystem Error"); } /* @@ -872,7 +871,7 @@ void SurgeStorage::refresh_wtlist() std::ostringstream ss; ss << "Surge was unable to load wavetables from '" << datapath << "'. Please reinstall Surge!"; - Surge::UserInteractions::promptError(ss.str(), "Surge Installation Error"); + reportError(ss.str(), "Surge Installation Error"); } firstThirdPartyWTCategory = wt_category.size(); @@ -1031,7 +1030,7 @@ void SurgeStorage::load_wt(string filename, Wavetable *wt, OscillatorStorage *os std::ostringstream oss; oss << "Unable to load file with extension " << extension << "! Surge only supports .wav and .wt wavetable files!"; - Surge::UserInteractions::promptError(oss.str(), "Error"); + reportError(oss.str(), "Error"); } if (osc && loaded) @@ -1092,7 +1091,7 @@ bool SurgeStorage::load_wt_wt(string filename, Wavetable *wt) << " If you would like, please attach the wavetable which caused this message to a new " "GitHub issue at " << " https://github.com/surge-synthesizer/surge/"; - Surge::UserInteractions::promptError(oss.str(), "Wavetable Loading Error"); + reportError(oss.str(), "Wavetable Loading Error"); } return wasBuilt; } @@ -1145,7 +1144,7 @@ bool SurgeStorage::load_wt_wt_mem(const char *data, size_t dataSize, Wavetable * << " If you would like, please attach the wavetable which caused this message to a new " "GitHub issue at " << " https://github.com/surge-synthesizer/surge/"; - Surge::UserInteractions::promptError(oss.str(), "Wavetable Loading Error"); + reportError(oss.str(), "Wavetable Loading Error"); } return wasBuilt; } @@ -1884,7 +1883,7 @@ void SurgeStorage::rescanUserMidiMappings() { std::ostringstream oss; oss << "Experienced file system error when loading MIDI settings. " << e.what(); - Surge::UserInteractions::promptError(oss.str(), "FileSystem Error"); + reportError(oss.str(), "FileSystem Error"); } } @@ -1902,8 +1901,8 @@ void SurgeStorage::loadMidiMappingByName(std::string name) if (!sm) { // Invalid XML Document. Show an error? - Surge::UserInteractions::promptError( - "Unable to locate surge-midi element in XML. Not a valid MIDI mapping!", "Surge MIDI"); + reportError("Unable to locate surge-midi element in XML. Not a valid MIDI mapping!", + "Surge MIDI"); return; } @@ -1995,7 +1994,7 @@ void SurgeStorage::storeMidiMappingToName(std::string name) { std::ostringstream oss; oss << "Unable to save MIDI settings to '" << fn << "'!"; - Surge::UserInteractions::promptError(oss.str(), "Error"); + reportError(oss.str(), "Error"); } } @@ -2049,6 +2048,19 @@ void SurgeStorage::toggleTuningToCache() } } +void SurgeStorage::reportError(const std::string &msg, const std::string &title) +{ + std::cout << "Surge Error [" << title << "]\n" << msg << std::endl; + if (errorListeners.empty()) + { + std::lock_guard g(preListenerErrorMutex); + preListenerErrors.emplace_back(msg, title); + } + + for (auto l : errorListeners) + l->onSurgeError(msg, title); +} + namespace Surge { namespace Storage diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 331ae35b63d..cb8b1acf363 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -38,6 +38,7 @@ #include "Tunings.h" #include "PatchDB.h" +#include #if WINDOWS #define PATH_SEPARATOR '\\' @@ -867,6 +868,43 @@ class alignas(16) SurgeStorage SurgeStorage(std::string suppliedDataPath = ""); + // With XT surgestorage can now keep a cache of errors it reports to the user + void reportError(const std::string &msg, const std::string &title); + struct ErrorListener + { + // This can be called from any thread. Beware. But it is called only + // when an error occurs so if you want to be sloppy and just lock thats OK + virtual void onSurgeError(const std::string &msg, const std::string &title) = 0; + }; + std::unordered_set errorListeners; + std::mutex preListenerErrorMutex; // this mutex is ONLY locked in the error path and + // when registering a listener (from the UI thread) + std::vector> preListenerErrors; + void addErrorListener(ErrorListener *l) + { + errorListeners.insert(l); + std::lock_guard g(preListenerErrorMutex); + for (auto p : preListenerErrors) + l->onSurgeError(p.first, p.second); + preListenerErrors.clear(); + } + void removeErrorListener(ErrorListener *l) { errorListeners.erase(l); } + + enum OkCancel + { + OK, + CANCEL + }; + std::function + okCancelProvider = + [](const std::string &, const std::string &, OkCancel def) { return def; }; + void clearOkCancelProvider() + { + okCancelProvider = [](const std::string &, const std::string &, OkCancel def) { + return def; + }; + } + static constexpr int tuning_table_size = 512; float table_pitch alignas(16)[tuning_table_size]; float table_pitch_inv alignas(16)[tuning_table_size]; @@ -920,7 +958,7 @@ class alignas(16) SurgeStorage bool load_wt_wt_mem(const char *data, const size_t dataSize, Wavetable *wt); // void load_wt_wav(std::string filename, Wavetable* wt); bool load_wt_wav_portable(std::string filename, Wavetable *wt); - void export_wt_wav_portable(std::string fbase, Wavetable *wt); + std::string export_wt_wav_portable(std::string fbase, Wavetable *wt); void clipboard_copy(int type, int scene, int entry); void clipboard_paste(int type, int scene, int entry); int get_clipboard_type(); diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index 675372ce3d0..cc20c91cc8a 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -3853,7 +3853,7 @@ void SurgeSynthesizer::loadFromDawExtraState() } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "Unable to restore tuning!"); + storage.reportError(e.what(), "Unable to restore tuning!"); storage.retuneTo12TETScale(); } } @@ -3879,7 +3879,7 @@ void SurgeSynthesizer::loadFromDawExtraState() } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "Unable to restore mapping!"); + storage.reportError(e.what(), "Unable to restore mapping!"); storage.remapToConcertCKeyboard(); } } diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index 0bc5246fc12..3038961a9bb 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -18,7 +18,6 @@ #include "SurgeVoice.h" #include "effect/Effect.h" #include "BiquadFilter.h" -#include "UserInteractions.h" struct QuadFilterChainState; diff --git a/src/common/SurgeSynthesizerIO.cpp b/src/common/SurgeSynthesizerIO.cpp index 6714f258d23..77c859d5906 100644 --- a/src/common/SurgeSynthesizerIO.cpp +++ b/src/common/SurgeSynthesizerIO.cpp @@ -23,15 +23,6 @@ #include #include #include -#include "UserInteractions.h" - -#if TARGET_AUDIOUNIT -#include "aulayer.h" -#endif - -#if AU -#include "aulayer.h" -#endif using namespace std; @@ -249,7 +240,7 @@ bool SurgeSynthesizer::loadPatchByPath(const char *fxpPath, int categoryId, cons //} oss << "This error usually occurs when you attempt to load an .fxp that belongs to another " "plugin into Surge."; - Surge::UserInteractions::promptError(oss.str(), "Unknown FXP File"); + storage.reportError(oss.str(), "Unknown FXP File"); return false; } @@ -309,20 +300,20 @@ bool SurgeSynthesizer::loadPatchByPath(const char *fxpPath, int categoryId, cons } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "Error restoring tuning!"); + storage.reportError(e.what(), "Error restoring tuning!"); storage.retuneTo12TETScaleC261Mapping(); } } else { - auto okc = Surge::UserInteractions::promptOKCancel( + auto okc = storage.okCancelProvider( std::string("Loaded patch contains a custom tuning, but there is ") + "already a user-selected tuning in place. Do you want to replace the currently " "loaded tuning " + "with the tuning stored in the patch? (The rest of the patch will load " "normally.)", - "Replace Tuning"); - if (okc == Surge::UserInteractions::MessageResult::OK) + "Replace Tuning", SurgeStorage::CANCEL); + if (okc == SurgeStorage::OK) { try { @@ -352,7 +343,7 @@ bool SurgeSynthesizer::loadPatchByPath(const char *fxpPath, int categoryId, cons } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "Error Restoring Tuning"); + storage.reportError(e.what(), "Error Restoring Tuning"); storage.retuneTo12TETScaleC261Mapping(); } } @@ -485,7 +476,7 @@ void SurgeSynthesizer::savePatch() if (!catPath.is_relative()) { - Surge::UserInteractions::promptError( + storage.reportError( "Please use relative paths when saving patches. Referring to drive names directly " "and using absolute paths is not allowed!", "Error"); @@ -497,7 +488,7 @@ void SurgeSynthesizer::savePatch() } catch (...) { - Surge::UserInteractions::promptError( + storage.reportError( "Exception occured while creating category folder! Most likely, invalid characters " "were used to name the category. Please remove suspicious characters and try again!", "Error"); @@ -507,18 +498,13 @@ void SurgeSynthesizer::savePatch() fs::path filename = savepath; filename /= string_to_path(storage.getPatch().name + ".fxp"); - bool checkExists = true; -#if LINUX - // Overwrite prompt hangs UI in Bitwig 3.3 - checkExists = (hostProgram.find("bitwig") != std::string::npos); -#endif - if (checkExists && fs::exists(filename)) + if (fs::exists(filename)) { - if (Surge::UserInteractions::promptOKCancel( + if (storage.okCancelProvider( std::string("The patch '" + storage.getPatch().name + "' already exists in '" + storage.getPatch().category + "'. Are you sure you want to overwrite it?"), - std::string("Overwrite patch")) == Surge::UserInteractions::CANCEL) + std::string("Overwrite patch"), SurgeStorage::OK) == SurgeStorage::CANCEL) return; } savePatchToPath(filename); @@ -530,7 +516,7 @@ void SurgeSynthesizer::savePatchToPath(fs::path filename) if (!f) { - Surge::UserInteractions::promptError( + storage.reportError( "Unable to save the patch to the specified path! Maybe it contains invalid characters?", "Error"); return; diff --git a/src/common/UserDefaults.cpp b/src/common/UserDefaults.cpp index 57ba70e0bd6..da47ef993b7 100644 --- a/src/common/UserDefaults.cpp +++ b/src/common/UserDefaults.cpp @@ -1,5 +1,4 @@ #include "UserDefaults.h" -#include "UserInteractions.h" #include "SurgeStorage.h" #include @@ -41,7 +40,7 @@ std::string defaultsFileName(SurgeStorage *storage) return fn; } -void readDefaultsFile(std::string fn, bool forceRead = false) +void readDefaultsFile(std::string fn, bool forceRead, SurgeStorage *storage) { if (!haveReadDefaultsFile || forceRead) { @@ -59,7 +58,7 @@ void readDefaultsFile(std::string fn, bool forceRead = false) oss << "This version of Surge only reads version 1 defaults. You user defaults " "version is " << version << ". Defaults ignored"; - Surge::UserInteractions::promptError(oss.str(), "File Version Error"); + storage->reportError(oss.str(), "File Version Error"); return; } @@ -86,7 +85,7 @@ bool storeUserDefaultValue(SurgeStorage *storage, const std::string &key, const UserDefaultValue::ValueType type) { // Re-read the file in case another surge has updated it - readDefaultsFile(defaultsFileName(storage), true); + readDefaultsFile(defaultsFileName(storage), true, storage); /* ** Surge has a habit of creating the user directories it needs. @@ -111,7 +110,7 @@ bool storeUserDefaultValue(SurgeStorage *storage, const std::string &key, const { std::ostringstream emsg; emsg << "Unable to open defaults file '" << defaultsFileName(storage) << "' for writing."; - Surge::UserInteractions::promptError(emsg.str(), "Defaults Not Saved"); + storage->reportError(emsg.str(), "Defaults Not Saved"); return false; } @@ -143,7 +142,7 @@ std::string getUserDefaultValue(SurgeStorage *storage, const std::string &key, return storage->userPrefOverrides[key].second; } - readDefaultsFile(defaultsFileName(storage)); + readDefaultsFile(defaultsFileName(storage), false, storage); if (defaultsFileContents.find(key) != defaultsFileContents.end()) { @@ -165,7 +164,7 @@ int getUserDefaultValue(SurgeStorage *storage, const std::string &key, int value return storage->userPrefOverrides[key].first; } - readDefaultsFile(defaultsFileName(storage)); + readDefaultsFile(defaultsFileName(storage), false, storage); if (defaultsFileContents.find(key) != defaultsFileContents.end()) { diff --git a/src/common/UserInteractions.h b/src/common/UserInteractions.h deleted file mode 100644 index b4ed560588b..00000000000 --- a/src/common/UserInteractions.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -** SurgeMessage provides basic functions for the synth to report messages back to the user -** with a platform-neutral API. A platform-specific version of each implementation is -** registered at startup. -** -** You can call this with -** -** Surge::UserInteractions::reportProblem() -** -** or what not. -*/ - -#pragma once - -#include -#include -#include - -class SurgeGUIEditor; - -namespace Surge -{ - -namespace UserInteractions -{ - -// Show the user an error dialog with an OK button; and wait for them to press it -void promptError(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor = nullptr); - -// Show the user an Info dialog with an OK button; and wait for them to press it -void promptInfo(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor = nullptr); - -// Prompt the user with an OK/Cance -typedef enum MessageResult -{ - OK, - CANCEL -} MessageResult; - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor = nullptr); - -// Open a URL in a user-appropriate fashion -void openURL(const std::string &url); -void showHTML(const std::string &html); - -// Open a folder in the system appropriate file browser (finder on macOS, explorer on win, -// etc) -void openFolderInFileBrowser(const std::string &folder); - -// Prompt for a file to open; call the callback if you pick one -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories = false, bool canCreateDirectories = false, - SurgeGUIEditor *guiEditor = nullptr); -}; // namespace UserInteractions - -}; // namespace Surge diff --git a/src/common/WavSupport.cpp b/src/common/WavSupport.cpp index 62475dc326c..c01b61ca3ce 100644 --- a/src/common/WavSupport.cpp +++ b/src/common/WavSupport.cpp @@ -21,7 +21,6 @@ #define WAV_STDOUT_INFO 0 #include -#include "UserInteractions.h" #include "SurgeStorage.h" #include #include @@ -56,7 +55,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) { std::ostringstream oss; oss << "Unable to open file '" << fn << "'!"; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); return false; } @@ -68,7 +67,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) { std::ostringstream oss; oss << "'" << fn << "' does not contain a valid RIFF header chunk!"; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); return false; } @@ -77,7 +76,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) std::ostringstream oss; oss << "'" << fn << "' is not a standard RIFF/WAVE file. Header is: " << riff[0] << riff[1] << riff[2] << riff[3] << " " << wav[0] << wav[1] << wav[2] << wav[3] << "."; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); return false; } @@ -170,7 +169,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) << " You provided a " << bitsPerSample << "-bit " << formname << " " << numChannels << "-channel file."; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); return false; } } @@ -330,7 +329,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) "for" << " information on .wav file metadata."; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); if (wavdata) free(wavdata); @@ -407,7 +406,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) "4096 samples per frame in power-of-two increments. You provided a wavetable with " << loopCount << (loopCount == 1 ? " frame" : " frames") << " of " << loopLen << " samples. '" << fn << "'"; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); if (wavdata) free(wavdata); @@ -450,7 +449,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) << " You provided a " << bitsPerSample << "-bit" << audioFormat << " " << numChannels << "-channel file."; - Surge::UserInteractions::promptError(oss.str(), uitag); + reportError(oss.str(), uitag); if (wavdata) free(wavdata); @@ -467,7 +466,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) return true; } -void SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) +std::string SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) { std::string path; path = Surge::Storage::appendDirectory(userDataPath, "Exported Wavetables"); @@ -492,8 +491,8 @@ void SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) errorMessage = "Unable to open file " + fname + "!"; errorMessage += std::strerror(errno); - Surge::UserInteractions::promptError(errorMessage, "Wavetable Export"); - return; + reportError(errorMessage, "Wavetable Export"); + return ""; } auto audioFormat = 3; @@ -575,5 +574,5 @@ void SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) refresh_wtlist(); - Surge::UserInteractions::promptInfo("Exported to " + fname, "Export Succeeded!"); + return fname; } diff --git a/src/common/gui/CAboutBox.cpp b/src/common/gui/CAboutBox.cpp index bd7b8499a2c..ae25e365796 100644 --- a/src/common/gui/CAboutBox.cpp +++ b/src/common/gui/CAboutBox.cpp @@ -5,7 +5,6 @@ #include #include "version.h" -#include "UserInteractions.h" #include "SkinColors.h" #include "CSurgeHyperlink.h" #include "SurgeGUIEditor.h" diff --git a/src/common/gui/CLFOGui.cpp b/src/common/gui/CLFOGui.cpp index 4c44507a928..7c882bac2b5 100644 --- a/src/common/gui/CLFOGui.cpp +++ b/src/common/gui/CLFOGui.cpp @@ -1277,7 +1277,7 @@ void CLFOGui::openPopup(CPoint &where) storage ? SurgeGUIEditor::helpURLForSpecial(storage, "mseg-editor") : std::string(); auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl); - addCb(contextMenu, "[?] MSEG Segment", [hurl]() { Surge::UserInteractions::openURL(hurl); }); + addCb(contextMenu, "[?] MSEG Segment", [hurl]() { juce::URL(hurl).launchInDefaultBrowser(); }); contextMenu->addSeparator(); diff --git a/src/common/gui/COscillatorDisplay.cpp b/src/common/gui/COscillatorDisplay.cpp index 5069cea91fb..767babba007 100644 --- a/src/common/gui/COscillatorDisplay.cpp +++ b/src/common/gui/COscillatorDisplay.cpp @@ -18,7 +18,6 @@ #include "Oscillator.h" #include #include "unitconversion.h" -#include "UserInteractions.h" #include "guihelpers.h" #include "SkinColors.h" #include "RuntimeFont.h" @@ -690,7 +689,12 @@ void COscillatorDisplay::populateMenu(COptionMenu *contextMenu, int selectedItem std::string baseName = storage->getPatch().name + "_osc" + std::to_string(oscNum + 1) + "_scene" + (scene == 0 ? "A" : "B"); - storage->export_wt_wav_portable(baseName, &(oscdata->wt)); + auto fn = storage->export_wt_wav_portable(baseName, &(oscdata->wt)); + if (!fn.empty()) + { + std::string message = "Wav file exported to '" + fn + "'"; + juce::AlertWindow::showMessageBox(juce::AlertWindow::InfoIcon, "Exported Wav", message); + } }; exportItem->setActions(exportAction, nullptr); contextMenu->addEntry(exportItem); @@ -698,8 +702,9 @@ void COscillatorDisplay::populateMenu(COptionMenu *contextMenu, int selectedItem auto omi = std::make_shared( CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Open Exported Wavetables Folder..."))); omi->setActions([this](CCommandMenuItem *i) { - Surge::UserInteractions::openFolderInFileBrowser( - Surge::Storage::appendDirectory(this->storage->userDataPath, "Exported Wavetables")); + juce::URL(juce::File(Surge::Storage::appendDirectory(this->storage->userDataPath, + "Exported Wavetables"))) + .launchInDefaultBrowser(); }); contextMenu->addEntry(omi); @@ -711,7 +716,7 @@ void COscillatorDisplay::populateMenu(COptionMenu *contextMenu, int selectedItem { auto lurl = sge->fullyResolvedHelpURL(hu); auto hi = std::make_shared(CCommandMenuItem::Desc("[?] Wavetables")); - auto ca = [lurl](CCommandMenuItem *i) { Surge::UserInteractions::openURL(lurl); }; + auto ca = [lurl](CCommandMenuItem *i) { juce::URL(lurl).launchInDefaultBrowser(); }; hi->setActions(ca, nullptr); contextMenu->addSeparator(); contextMenu->addEntry(hi); @@ -804,9 +809,14 @@ void COscillatorDisplay::loadWavetable(int id) void COscillatorDisplay::loadWavetableFromFile() { - Surge::UserInteractions::promptFileOpenDialog("", "", "", [this](std::string s) { - strncpy(this->oscdata->wt.queue_filename, s.c_str(), 255); - }); + juce::FileChooser c("Select Wavetable to Load"); + auto r = c.browseForFileToOpen(); + if (r) + { + auto res = c.getResult(); + auto rString = res.getFullPathName().toStdString(); + strncpy(this->oscdata->wt.queue_filename, rString.c_str(), 255); + } } CMouseEventResult COscillatorDisplay::onMouseUp(CPoint &where, const CButtonState &buttons) diff --git a/src/common/gui/CPatchBrowser.cpp b/src/common/gui/CPatchBrowser.cpp index e1e1469fb48..051ca0c13d4 100644 --- a/src/common/gui/CPatchBrowser.cpp +++ b/src/common/gui/CPatchBrowser.cpp @@ -15,7 +15,6 @@ #include "SurgeGUIEditor.h" #include "CPatchBrowser.h" -#include "UserInteractions.h" #include "SkinColors.h" #include "guihelpers.h" @@ -207,13 +206,16 @@ CMouseEventResult CPatchBrowser::onMouseDown(CPoint &where, const CButtonState & auto loadF = std::make_shared( CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Load Patch from File..."))); loadF->setActions([this](CCommandMenuItem *item) { - Surge::UserInteractions::promptFileOpenDialog( - "", "fxp", "Surge FXP Files", [this](std::string fn) { - auto sge = dynamic_cast(listener); - if (!sge) - return; - sge->queuePatchFileLoad(fn); - }); + juce::FileChooser c("Select Patch to Load", juce::File(storage->userDataPath), "*.fxp"); + auto r = c.browseForFileToOpen(); + if (r) + { + auto res = c.getResult(); + auto rString = res.getFullPathName().toStdString(); + auto sge = dynamic_cast(listener); + if (sge) + sge->queuePatchFileLoad(rString); + } }); contextMenu->addEntry(loadF); @@ -228,23 +230,25 @@ CMouseEventResult CPatchBrowser::onMouseDown(CPoint &where, const CButtonState & auto showU = std::make_shared( CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Open User Patches Folder..."))); showU->setActions([this](CCommandMenuItem *item) { - Surge::UserInteractions::openFolderInFileBrowser(this->storage->userDataPath); + juce::URL(juce::File(this->storage->userDataPath)).launchInDefaultBrowser(); }); contextMenu->addEntry(showU); auto showF = std::make_shared( CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Open Factory Patches Folder..."))); showF->setActions([this](CCommandMenuItem *item) { - Surge::UserInteractions::openFolderInFileBrowser( - Surge::Storage::appendDirectory(this->storage->datapath, "patches_factory")); + juce::URL( + juce::File(Surge::Storage::appendDirectory(this->storage->datapath, "patches_factory"))) + .launchInDefaultBrowser(); }); contextMenu->addEntry(showF); auto show3 = std::make_shared( CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Open Third Party Patches Folder..."))); show3->setActions([this](CCommandMenuItem *item) { - Surge::UserInteractions::openFolderInFileBrowser( - Surge::Storage::appendDirectory(this->storage->datapath, "patches_3rdparty")); + juce::URL(juce::File( + Surge::Storage::appendDirectory(this->storage->datapath, "patches_3rdparty"))) + .launchInDefaultBrowser(); }); contextMenu->addEntry(show3); @@ -261,7 +265,7 @@ CMouseEventResult CPatchBrowser::onMouseDown(CPoint &where, const CButtonState & auto lurl = sge->fullyResolvedHelpURL(hu); auto hi = std::make_shared(CCommandMenuItem::Desc("[?] Patch Browser")); - auto ca = [lurl](CCommandMenuItem *i) { Surge::UserInteractions::openURL(lurl); }; + auto ca = [lurl](CCommandMenuItem *i) { juce::URL(lurl).launchInDefaultBrowser(); }; hi->setActions(ca, nullptr); contextMenu->addEntry(hi); } diff --git a/src/common/gui/CSnapshotMenu.cpp b/src/common/gui/CSnapshotMenu.cpp index d53050d8ab5..447f09a1149 100644 --- a/src/common/gui/CSnapshotMenu.cpp +++ b/src/common/gui/CSnapshotMenu.cpp @@ -632,7 +632,9 @@ void CFxMenu::rescanUserPresets() { std::ostringstream oss; oss << "Experienced file system error when scanning user FX. " << e.what(); - Surge::UserInteractions::promptError(oss.str(), "FileSystem Error"); + + if (storage) + storage->reportError(oss.str(), "FileSystem Error"); } for (const auto &f : sfxfiles) @@ -902,8 +904,8 @@ void CFxMenu::saveFXIn(const std::string &s) std::ofstream pfile(fn, std::ios::out); if (!pfile.is_open()) { - Surge::UserInteractions::promptError( - std::string("Unable to open FX preset file '") + fn + "' for writing!", "Error"); + storage->reportError(std::string("Unable to open FX preset file '") + fn + "' for writing!", + "Error"); return; } diff --git a/src/common/gui/CSurgeHyperlink.h b/src/common/gui/CSurgeHyperlink.h index 5da25b79062..83084ad9e1f 100644 --- a/src/common/gui/CSurgeHyperlink.h +++ b/src/common/gui/CSurgeHyperlink.h @@ -15,13 +15,8 @@ #pragma once -#if ESCAPE_FROM_VSTGUI #include -#else -#include "vstgui/vstgui.h" -#endif #include -#include "UserInteractions.h" class CSurgeHyperlink : public VSTGUI::CControl { @@ -49,7 +44,7 @@ class CSurgeHyperlink : public VSTGUI::CControl VSTGUI::CMouseEventResult onMouseUp(VSTGUI::CPoint &where, const VSTGUI::CButtonState &buttons) override { - Surge::UserInteractions::openURL(url); + juce::URL(url).launchInDefaultBrowser(); return VSTGUI::kMouseEventHandled; } diff --git a/src/common/gui/CTextButtonWithHover.h b/src/common/gui/CTextButtonWithHover.h index 5799215d3c1..fc6672ae8a4 100644 --- a/src/common/gui/CTextButtonWithHover.h +++ b/src/common/gui/CTextButtonWithHover.h @@ -33,13 +33,11 @@ class CTextButtonWithHover : public VSTGUI::CTextButton { ~OptionalForget() { -#if TARGET_JUCE_UI if (isSet && item) { - item->forget(); + // item->forget(); item = nullptr; } -#endif } T item = T(); bool isSet = false; @@ -54,11 +52,11 @@ class CTextButtonWithHover : public VSTGUI::CTextButton { if (getGradient()) { - getGradient()->forget(); + // getGradient()->forget(); } if (getGradientHighlighted()) { - getGradientHighlighted()->forget(); + // getGradientHighlighted()->forget(); } } VSTGUI::CMouseEventResult onMouseEntered(VSTGUI::CPoint &where, diff --git a/src/common/gui/MSEGEditor.cpp b/src/common/gui/MSEGEditor.cpp index d760de6eeb1..48678e4d4e8 100644 --- a/src/common/gui/MSEGEditor.cpp +++ b/src/common/gui/MSEGEditor.cpp @@ -2161,7 +2161,7 @@ struct MSEGCanvas : public CControl, auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl); addCb(contextMenu, "[?] MSEG Segment", - [hurl]() { Surge::UserInteractions::openURL(hurl); }); + [hurl]() { juce::URL(hurl).launchInDefaultBrowser(); }); contextMenu->addSeparator(); @@ -2677,7 +2677,7 @@ int32_t MSEGControlRegion::controlModifierClicked(CControl *pControl, CButtonSta auto msurl = SurgeGUIEditor::helpURLForSpecial(storage, "mseg-editor"); auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl); - addcb("[?] " + menuName, [hurl]() { Surge::UserInteractions::openURL(hurl); }); + addcb("[?] " + menuName, [hurl]() { juce::URL(hurl).launchInDefaultBrowser(); }); com->addSeparator(); if (isOnOff) { diff --git a/src/common/gui/SkinSupport.cpp b/src/common/gui/SkinSupport.cpp index f390285293d..7d701de8636 100644 --- a/src/common/gui/SkinSupport.cpp +++ b/src/common/gui/SkinSupport.cpp @@ -5,7 +5,6 @@ #include "SkinSupport.h" #include "SurgeStorage.h" #include "SurgeBitmaps.h" -#include "UserInteractions.h" #include "UserDefaults.h" #include "UIInstrumentation.h" #include "CScalableBitmap.h" @@ -170,7 +169,7 @@ void SkinDB::rescanForSkins(SurgeStorage *storage) << "set of resources. Surge looked in '" << storage->datapath << "' and '" << storage->userDataPath << "'. " << "Please reinstall Surge or remove incompatible resources."; - Surge::UserInteractions::promptError(oss.str(), "Skin Loading Error"); + storage->reportError(oss.str(), "Skin Loading Error"); } // Run over the skins parsing the name @@ -1009,8 +1008,9 @@ VSTGUI::CColor Skin::getColor(const std::string &iid, const VSTGUI::CColor &def, "visited during traversal are: "; for (auto l : noLoops) oss << "'" << l << "' "; - // FIXME ERROR - Surge::UserInteractions::promptError(oss.str(), "Skin Configuration Error"); + // FIXME thread a storage here + std::cout << oss.str() << std::endl; + // storage->reportError(oss.str(), "Skin Configuration Error"); return def; } noLoops.insert(id); diff --git a/src/common/gui/SurgeBitmaps.cpp b/src/common/gui/SurgeBitmaps.cpp index debbed80033..0451b6e1355 100644 --- a/src/common/gui/SurgeBitmaps.cpp +++ b/src/common/gui/SurgeBitmaps.cpp @@ -1,7 +1,5 @@ #include "SurgeBitmaps.h" -#include "UserInteractions.h" -#include "UIInstrumentation.h" #include "CScalableBitmap.h" #include #include diff --git a/src/common/gui/SurgeGUIEditor.cpp b/src/common/gui/SurgeGUIEditor.cpp index e39f53323ea..dd8a29a57ec 100644 --- a/src/common/gui/SurgeGUIEditor.cpp +++ b/src/common/gui/SurgeGUIEditor.cpp @@ -35,7 +35,6 @@ #include "SurgeBitmaps.h" #include "CScalableBitmap.h" #include "CNumberField.h" -#include "UserInteractions.h" #include "UserDefaults.h" #include "SkinSupport.h" #include "SkinColors.h" @@ -242,6 +241,17 @@ bool SurgeGUIEditor::fromSynthGUITag(SurgeSynthesizer *synth, int tag, SurgeSynt SurgeGUIEditor::SurgeGUIEditor(PARENT_PLUGIN_TYPE *effect, SurgeSynthesizer *synth, void *userdata) : super(effect) { + synth->storage.addErrorListener(this); + synth->storage.okCancelProvider = [this](const std::string &msg, const std::string &title, + SurgeStorage::OkCancel def) { + // think about threading one day probably + auto res = juce::AlertWindow::showOkCancelBox(juce::AlertWindow::InfoIcon, title, msg, "OK", + "Cancel", nullptr, nullptr); + + if (res) + return SurgeStorage::OK; + return SurgeStorage::CANCEL; + }; #ifdef INSTRUMENT_UI Surge::Debug::record("SurgeGUIEditor::SurgeGUIEditor"); #endif @@ -345,6 +355,7 @@ SurgeGUIEditor::SurgeGUIEditor(PARENT_PLUGIN_TYPE *effect, SurgeSynthesizer *syn SurgeGUIEditor::~SurgeGUIEditor() { + synth->storage.clearOkCancelProvider(); auto isPop = synth->storage.getPatch().dawExtraState.isPopulated; populateDawExtraState(synth); // If I must die, leave my state for future generations synth->storage.getPatch().dawExtraState.isPopulated = isPop; @@ -367,6 +378,7 @@ SurgeGUIEditor::~SurgeGUIEditor() #if TARGET_JUCE_UI frame->forget(); #endif + synth->storage.removeErrorListener(this); } void SurgeGUIEditor::idle() @@ -377,6 +389,20 @@ void SurgeGUIEditor::idle() if (pause_idle_updates) return; + if (errorItemCount) + { + std::vector> cp; + { + std::lock_guard g(errorItemsMutex); + cp = errorItems; + errorItems.clear(); + } + for (const auto &p : cp) + { + juce::AlertWindow::showMessageBox(juce::AlertWindow::WarningIcon, p.second, p.first); + } + } + if (editor_open && frame && !synth->halt_engine) { /* @@ -438,7 +464,7 @@ void SurgeGUIEditor::idle() << "running in order to load Surge patches. If the audio system is working, " "you can probably" << " ignore this message and continue once Surge catches up."; - Surge::UserInteractions::promptError(oss.str(), "Patch Loading Error"); + synth->storage.reportError(oss.str(), "Patch Loading Error"); } } @@ -1048,7 +1074,7 @@ int32_t SurgeGUIEditor::onKeyDown(const VstKeyCode &code, CFrame *frame) db.getAndResetErrorString(); currentSkin = db.defaultSkin(&(synth->storage)); currentSkin->reloadSkin(bitmapStore); - Surge::UserInteractions::promptError(msg, "Skin Loading Error"); + synth->storage.reportError(msg, "Skin Loading Error"); } reloadFromSkin(); @@ -1579,7 +1605,7 @@ void SurgeGUIEditor::openOrRecreateEditor() if (i == n_paramslots) { // This would only happen if a dev added params. - Surge::UserInteractions::promptError( + synth->storage.reportError( "INTERNAL ERROR: List of parameters is larger than maximum number of parameter " "slots. Increase n_paramslots in SurgeGUIEditor.h!", "Error"); @@ -1913,7 +1939,7 @@ bool PLUGIN_API SurgeGUIEditor::open(void *parent, const PlatformType &platformT } if (l == nullptr) { - Surge::UserInteractions::promptError("Starting VST3 with null runloop", "Software Error"); + synth->storage.reportError("Starting VST3 with null runloop", "Software Error"); } LinuxVST3Init(l); #endif @@ -2217,7 +2243,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState b snprintf(txt, TXT_SIZE, "[?] Osc %i", a + 1); auto lurl = fullyResolvedHelpURL(hu); addCallbackMenu(contextMenu, txt, - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); eid++; } else @@ -2285,7 +2311,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState b { snprintf(txt, TXT_SIZE, "[?] Scene %c", 'A' + a); auto lurl = fullyResolvedHelpURL(hu); - contextMenu.addItem(txt, [lurl]() { Surge::UserInteractions::openURL(lurl); }); + contextMenu.addItem(txt, [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); } else { @@ -2362,7 +2388,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState b auto lurl = fullyResolvedHelpURL(hu); std::string hs = std::string("[?] ") + (char *)modsource_names[idOn]; addCallbackMenu(contextMenu, hs, - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); eid++; } else @@ -2389,7 +2415,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState b auto lurl = fullyResolvedHelpURL(hu); std::string hs = std::string("[?] ") + modulatorName(modsource, false); addCallbackMenu(contextMenu, hs, - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); eid++; } else @@ -2912,7 +2938,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState b std::string helpstr = "[?] "; auto lurl = fullyResolvedHelpURL(helpurl); addCallbackMenu(contextMenu, std::string(helpstr + p->get_full_name()).c_str(), - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); eid++; } @@ -4260,7 +4286,7 @@ void SurgeGUIEditor::effectSettingsBackgroundClick(int whichScene) std::string txt; addCallbackMenu(effmen, "[?] Effect Settings", - [hurl]() { Surge::UserInteractions::openURL(hurl); }); + [hurl]() { juce::URL(hurl).launchInDefaultBrowser(); }); effmen->addSeparator(); @@ -5506,7 +5532,7 @@ void SurgeGUIEditor::scaleFileDropped(std::string fn) } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "SCL Error"); + synth->storage.reportError(e.what(), "SCL Error"); } } @@ -5519,7 +5545,7 @@ void SurgeGUIEditor::mappingFileDropped(std::string fn) } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "KBM Error"); + synth->storage.reportError(e.what(), "KBM Error"); } } @@ -5641,7 +5667,7 @@ void SurgeGUIEditor::showMinimumZoomError() const std::ostringstream oss; oss << "The smallest zoom level possible on your platform is " << minimumZoom << "%. Sorry, you cannot make Surge any smaller!"; - Surge::UserInteractions::promptError(oss.str(), "Zoom Level Error"); + synth->storage.reportError(oss.str(), "Zoom Level Error"); } void SurgeGUIEditor::showTooLargeZoomError(double width, double height, float zf) const @@ -5661,7 +5687,7 @@ void SurgeGUIEditor::showTooLargeZoomError(double width, double height, float zf { msg << "Surge chose the largest fitting zoom level of " << zf << "%."; } - Surge::UserInteractions::promptError(msg.str(), "Zoom Level Adjusted"); + synth->storage.reportError(msg.str(), "Zoom Level Adjusted"); #endif } @@ -5719,29 +5745,30 @@ void SurgeGUIEditor::showSettingsMenu(CRect &menuRect) settingsMenu->addSeparator(eid++); addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Reach the Developers..."), []() { - Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/feedback"); + juce::URL("https://surge-synthesizer.github.io/feedback").launchInDefaultBrowser(); }); eid++; addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Read the Code..."), []() { - Surge::UserInteractions::openURL("https://github.com/surge-synthesizer/surge/"); + juce::URL("https://github.com/surge-synthesizer/surge/").launchInDefaultBrowser(); }); eid++; - addCallbackMenu( - settingsMenu, Surge::UI::toOSCaseForMenu("Download Additional Content..."), []() { - Surge::UserInteractions::openURL("https://github.com/surge-synthesizer/" - "surge-synthesizer.github.io/wiki/Additional-Content"); - }); + addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Download Additional Content..."), + []() { + juce::URL("https://github.com/surge-synthesizer/" + "surge-synthesizer.github.io/wiki/Additional-Content") + .launchInDefaultBrowser(); + }); eid++; addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Surge Website..."), []() { - Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/"); + juce::URL("https://surge-synthesizer.github.io/").launchInDefaultBrowser(); }); eid++; addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Surge Manual..."), []() { - Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/manual/"); + juce::URL("https://surge-synthesizer.github.io/manual/").launchInDefaultBrowser(); }); eid++; @@ -5778,7 +5805,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeLfoMenu(VSTGUI::CRect &menuRect) COptionMenu *lfoSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle); addCallbackMenu(lfoSubMenu, "[?] LFO Presets", - [hurl]() { Surge::UserInteractions::openURL(hurl); }), + [hurl]() { juce::URL(hurl).launchInDefaultBrowser(); }), lfoSubMenu->addSeparator(); @@ -5899,7 +5926,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeMpeMenu(VSTGUI::CRect &menuRect, bool s { auto lurl = fullyResolvedHelpURL(hu); addCallbackMenu(mpeSubMenu, "[?] MPE", - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); mpeSubMenu->addSeparator(); } @@ -5998,7 +6025,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo { auto lurl = fullyResolvedHelpURL(hu); addCallbackMenu(tuningSubMenu, "[?] Tuning ", - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); tid++; tuningSubMenu->addSeparator(); } @@ -6076,8 +6103,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo { if (sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0) { - Surge::UserInteractions::promptError("Please select only .scl files!", - "Invalid Choice"); + synth->storage.reportError("Please select only .scl files!", "Invalid Choice"); std::cout << "FILE is [" << sf << "]" << std::endl; return; } @@ -6088,65 +6114,75 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo if (!this->synth->storage.retuneToScale(sc)) { - Surge::UserInteractions::promptError("This .scl file is not valid!", - "File Format Error"); + synth->storage.reportError("This .scl file is not valid!", "File Format Error"); return; } this->synth->refresh_editor = true; } catch (Tunings::TuningError &e) { - Surge::UserInteractions::promptError(e.what(), "Loading Error"); + synth->storage.reportError(e.what(), "Loading Error"); } }; auto scl_path = Surge::Storage::appendDirectory(this->synth->storage.datapath, "tuning-library", "SCL"); - Surge::UserInteractions::promptFileOpenDialog(scl_path, ".scl", - "Scala microtuning files (*.scl)", cb); + juce::FileChooser c("Select SCL Scale", juce::File(scl_path), "*.scl"); + auto r = c.browseForFileToOpen(); + if (r) + { + auto res = c.getResult(); + auto rString = res.getFullPathName().toStdString(); + cb(rString); + } }); tid++; - addCallbackMenu( - tuningSubMenu, Surge::UI::toOSCaseForMenu("Load .kbm Keyboard Mapping..."), [this]() { - auto cb = [this](std::string sf) { - std::string sfx = ".kbm"; - if (sf.length() >= sfx.length()) - { - if (sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0) - { - Surge::UserInteractions::promptError("Please select only .kbm files!", - "Invalid Choice"); - std::cout << "FILE is [" << sf << "]" << std::endl; - return; - } - } - try - { - auto kb = Tunings::readKBMFile(sf); - - if (!this->synth->storage.remapToKeyboard(kb)) - { - Surge::UserInteractions::promptError("This .kbm file is not valid!", - "File Format Error"); - return; - } + addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("Load .kbm Keyboard Mapping..."), + [this]() { + auto cb = [this](std::string sf) { + std::string sfx = ".kbm"; + if (sf.length() >= sfx.length()) + { + if (sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0) + { + synth->storage.reportError("Please select only .kbm files!", + "Invalid Choice"); + std::cout << "FILE is [" << sf << "]" << std::endl; + return; + } + } + try + { + auto kb = Tunings::readKBMFile(sf); - this->synth->refresh_editor = true; - } - catch (Tunings::TuningError &e) - { - Surge::UserInteractions::promptError(e.what(), "Loading Error"); - } - }; + if (!this->synth->storage.remapToKeyboard(kb)) + { + synth->storage.reportError("This .kbm file is not valid!", + "File Format Error"); + return; + } - auto kbm_path = Surge::Storage::appendDirectory(this->synth->storage.datapath, - "tuning-library", "KBM Concert Pitch"); + this->synth->refresh_editor = true; + } + catch (Tunings::TuningError &e) + { + synth->storage.reportError(e.what(), "Loading Error"); + } + }; - Surge::UserInteractions::promptFileOpenDialog( - kbm_path, ".kbm", "Scala keyboard mapping files (*.kbm)", cb); - }); + auto kbm_path = Surge::Storage::appendDirectory( + this->synth->storage.datapath, "tuning-library", "KBM Concert Pitch"); + juce::FileChooser c("Select KBM Mapping", juce::File(kbm_path), "*.kbm"); + auto r = c.browseForFileToOpen(); + if (r) + { + auto res = c.getResult(); + auto rString = res.getFullPathName().toStdString(); + cb(rString); + } + }); tid++; int oct = 5 - Surge::Storage::getUserDefaultValue(&(this->synth->storage), "middleC", 1); @@ -6168,8 +6204,8 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo kb.name = "Note 69 Retuned 440 to " + std::to_string(freq); if (!this->synth->storage.remapToKeyboard(kb)) { - Surge::UserInteractions::promptError( - "This .kbm file is not valid!", "File Format Error"); + synth->storage.reportError("This .kbm file is not valid!", + "File Format Error"); return; } }); @@ -6258,9 +6294,9 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo tuningSubMenu->addSeparator(); tid++; - auto sct = addCallbackMenu( - tuningSubMenu, Surge::UI::toOSCaseForMenu("Show Current Tuning Information..."), - [this]() { Surge::UserInteractions::showHTML(this->tuningToHtml()); }); + auto sct = addCallbackMenu(tuningSubMenu, + Surge::UI::toOSCaseForMenu("Show Current Tuning Information..."), + [this]() { showHTML(this->tuningToHtml()); }); sct->setEnabled(!this->synth->storage.isStandardTuning); addCallbackMenu( @@ -6268,7 +6304,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, boo auto dpath = Surge::Storage::appendDirectory(this->synth->storage.datapath, "tuning-library"); - Surge::UserInteractions::openFolderInFileBrowser(dpath); + juce::URL(juce::File(dpath)).launchInDefaultBrowser(); }); return tuningSubMenu; @@ -6287,7 +6323,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeZoomMenu(VSTGUI::CRect &menuRect, bool { auto lurl = fullyResolvedHelpURL(hu); addCallbackMenu(zoomSubMenu, "[?] Zoom", - [lurl]() { Surge::UserInteractions::openURL(lurl); }); + [lurl]() { juce::URL(lurl).launchInDefaultBrowser(); }); zid++; zoomSubMenu->addSeparator(zid++); @@ -6801,7 +6837,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeSkinMenu(VSTGUI::CRect &menuRect) db.getAndResetErrorString(); this->currentSkin = db.defaultSkin(&(this->synth->storage)); this->currentSkin->reloadSkin(this->bitmapStore); - Surge::UserInteractions::promptError(msg, "Skin Loading Error"); + synth->storage.reportError(msg, "Skin Loading Error"); } reloadFromSkin(); @@ -6835,21 +6871,20 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeSkinMenu(VSTGUI::CRect &menuRect) addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Open Current Skin Folder..."), [this]() { - Surge::UserInteractions::openFolderInFileBrowser(this->currentSkin->root + - this->currentSkin->name); + juce::URL(juce::File(this->currentSkin->root + this->currentSkin->name)) + .launchInDefaultBrowser(); }); tid++; skinSubMenu->addSeparator(); addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Show Skin Inspector..."), - [this]() { Surge::UserInteractions::showHTML(skinInspectorHtml()); }); + [this]() { showHTML(skinInspectorHtml()); }); tid++; - addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Skin Development Guide..."), []() { - Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/skin-manual.html"); - }); + addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Skin Development Guide..."), + []() { juce::URL("https://surge-synthesizer.github.io/skin-manual.html"); }); tid++; @@ -6865,14 +6900,14 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeDataMenu(VSTGUI::CRect &menuRect) addCallbackMenu( dataSubMenu, Surge::UI::toOSCaseForMenu("Open Factory Data Folder..."), [this]() { - Surge::UserInteractions::openFolderInFileBrowser(this->synth->storage.datapath); + juce::URL(juce::File(this->synth->storage.datapath)).launchInDefaultBrowser(); }); did++; addCallbackMenu(dataSubMenu, Surge::UI::toOSCaseForMenu("Open User Data Folder..."), [this]() { // make it if it isn't there fs::create_directories(string_to_path(this->synth->storage.userDataPath)); - Surge::UserInteractions::openFolderInFileBrowser(this->synth->storage.userDataPath); + juce::URL(juce::File(this->synth->storage.userDataPath)).launchInDefaultBrowser(); }); did++; @@ -6885,8 +6920,11 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeDataMenu(VSTGUI::CRect &menuRect) this->synth->storage.refresh_wtlist(); this->synth->storage.refresh_patchlist(); }; + /* + * TODO: Implement JUCE direcotry picker Surge::UserInteractions::promptFileOpenDialog(this->synth->storage.userDataPath, "", "", cb, true, true); + */ }); did++; @@ -6998,7 +7036,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeMidiMenu(VSTGUI::CRect &menuRect) midiSubMenu->addSeparator(); addCallbackMenu(midiSubMenu, Surge::UI::toOSCaseForMenu("Show Current MIDI Mapping..."), - [this]() { Surge::UserInteractions::showHTML(this->midiMappingToHtml()); }); + [this]() { showHTML(this->midiMappingToHtml()); }); if (!scannedForMidiPresets) { @@ -7752,7 +7790,7 @@ void SurgeGUIEditor::setupSkinFromEntry(const Surge::UI::SkinDB::Entry &entry) auto msg = std::string(oss.str()); this->currentSkin = db.defaultSkin(&(this->synth->storage)); this->currentSkin->reloadSkin(this->bitmapStore); - Surge::UserInteractions::promptError(msg, "Skin Loading Error"); + synth->storage.reportError(msg, "Skin Loading Error"); } reloadFromSkin(); } @@ -9061,3 +9099,9 @@ void SurgeGUIEditor::toggleAlternateFor(VSTGUI::CControl *c) this->refresh_mod(); } } +void SurgeGUIEditor::onSurgeError(const string &msg, const string &title) +{ + std::lock_guard g(errorItemsMutex); + errorItems.emplace_back(msg, title); + errorItemCount++; +} diff --git a/src/common/gui/SurgeGUIEditor.h b/src/common/gui/SurgeGUIEditor.h index 0dc7e4016c0..d15c25ab36f 100644 --- a/src/common/gui/SurgeGUIEditor.h +++ b/src/common/gui/SurgeGUIEditor.h @@ -17,37 +17,11 @@ #include "globals.h" -#if TARGET_AUDIOUNIT -//#include "vstkeycode.h" -#include "vstgui/plugin-bindings/plugguieditor.h" -typedef VSTGUI::PluginGUIEditor EditorType; -#elif TARGET_VST3 -#include "public.sdk/source/vst/vstguieditor.h" -#include "pluginterfaces/gui/iplugviewcontentscalesupport.h" -typedef Steinberg::Vst::VSTGUIEditor EditorType; -#define PARENT_PLUGIN_TYPE SurgeVst3Processor -#elif TARGET_VST2 -#if LINUX -#include "../linux/linux-aeffguieditor.h" -typedef VSTGUI::LinuxAEffGUIEditor EditorType; -#else -#include "vstgui/plugin-bindings/aeffguieditor.h" -typedef VSTGUI::AEffGUIEditor EditorType; -#endif -#elif TARGET_JUCE_UI #include #include "efvg/escape_from_vstgui.h" #include "SurgeSynthEditor.h" typedef EscapeFromVSTGUI::JuceVSTGUIEditorAdapter EditorType; #define PARENT_PLUGIN_TYPE SurgeSynthEditor -#else -#include "vstgui/plugin-bindings/plugguieditor.h" -typedef VSTGUI::PluginGUIEditor EditorType; -#endif - -#ifndef PARENT_PLUGIN_TYPE -#define PARENT_PLUGIN_TYPE void -#endif #include "SurgeStorage.h" #include "SurgeBitmaps.h" @@ -60,6 +34,8 @@ typedef VSTGUI::PluginGUIEditor EditorType; #include #include "MSEGEditor.h" +#include +#include class CSurgeSlider; class CModulationSourceButton; @@ -80,7 +56,8 @@ struct SGEDropAdapter; class SurgeGUIEditor : public EditorType, public VSTGUI::IControlListener, - public VSTGUI::IKeyboardHook + public VSTGUI::IKeyboardHook, + public SurgeStorage::ErrorListener #if TARGET_VST3 , public Steinberg::IPlugViewContentScaleSupport @@ -93,6 +70,11 @@ class SurgeGUIEditor : public EditorType, SurgeGUIEditor(PARENT_PLUGIN_TYPE *effect, SurgeSynthesizer *synth, void *userdata = nullptr); virtual ~SurgeGUIEditor(); + std::atomic errorItemCount{0}; + std::vector> errorItems; + std::mutex errorItemsMutex; + void onSurgeError(const std::string &msg, const std::string &title) override; + static int start_paramtag_value; #if TARGET_AUDIOUNIT | TARGET_VST2 | TARGET_LV2 @@ -327,6 +309,8 @@ class SurgeGUIEditor : public EditorType, void showZoomMenu(VSTGUI::CPoint &where); void showLfoMenu(VSTGUI::CPoint &menuRect); + void showHTML(const std::string &s); + void toggleMPE(); void toggleTuning(); void scaleFileDropped(std::string fn); diff --git a/src/common/gui/SurgeGUIEditorHtmlGenerators.cpp b/src/common/gui/SurgeGUIEditorHtmlGenerators.cpp index d34be32f0b7..584a87687d1 100644 --- a/src/common/gui/SurgeGUIEditorHtmlGenerators.cpp +++ b/src/common/gui/SurgeGUIEditorHtmlGenerators.cpp @@ -10,6 +10,27 @@ namespace UI } } // namespace Surge +void SurgeGUIEditor::showHTML(const std::string &html) +{ + static struct filesToDelete : juce::DeletedAtShutdown + { + ~filesToDelete() + { + for (const auto &d : deleteThese) + { + d.deleteFile(); + } + } + std::vector deleteThese; + } *byebyeOnExit = new filesToDelete(); + + auto f = juce::File::createTempFile("_surge.html"); + f.replaceWithText(html); + auto U = juce::URL(f); + U.launchInDefaultBrowser(); + byebyeOnExit->deleteThese.push_back(f); +}; + std::string SurgeGUIEditor::tuningToHtml() { std::ostringstream htmls; diff --git a/src/common/gui/UIInstrumentation.cpp b/src/common/gui/UIInstrumentation.cpp index e72dc86735b..afdcdbecd69 100644 --- a/src/common/gui/UIInstrumentation.cpp +++ b/src/common/gui/UIInstrumentation.cpp @@ -1,5 +1,4 @@ #include "UIInstrumentation.h" -#include "UserInteractions.h" #include #include #include diff --git a/src/headless/UserInteractionsHeadless.cpp b/src/headless/UserInteractionsHeadless.cpp deleted file mode 100644 index 6d2d38c99ab..00000000000 --- a/src/headless/UserInteractionsHeadless.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "UserInteractions.h" -#include -#include - -#if MAC -#include -#endif - -void headlessStackDump() -{ -#if MAC - void *callstack[128]; - int i, frames = backtrace(callstack, 128); - char **strs = backtrace_symbols(callstack, frames); - for (i = 1; i < 6; ++i) - { - fprintf(stderr, "StackTrace[%3d]: %s\n", i, strs[i]); - } - free(strs); - -#endif -} - -namespace Surge -{ -namespace UserInteractions -{ - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge Error\n" << title << "\n" << message << "\n" << std::flush; - headlessStackDump(); -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge Info\n" << title << "\n" << message << "\n" << std::flush; - headlessStackDump(); -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge OkCancel\n" - << title << "\n" - << message << "\n" - << "Returning CANCEL" << std::flush; - headlessStackDump(); - return UserInteractions::CANCEL; -} - -void openURL(const std::string &url) {} -void showHTML(const std::string &html) { std::cerr << "SURGE HTML: " << html << std::endl; } - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, bool od, bool cd, - SurgeGUIEditor *guiEditor) -{ - UserInteractions::promptError( - "Open file dialog is not implemented in this version of Surge. Sorry!", - "Unimplemented Function", guiEditor); -} -}; // namespace UserInteractions - -}; // namespace Surge diff --git a/src/linux/UserInteractionsLinux.cpp b/src/linux/UserInteractionsLinux.cpp deleted file mode 100644 index 4edc2d75e97..00000000000 --- a/src/linux/UserInteractionsLinux.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "UserInteractions.h" -#include -#include -#include -#include -#include -#include -#include -#include - -/* -** In June 2019, @baconpaul chose to implement these with an attempt -** to fork/exec a zenity. (He also did this for mini edit). This is not -** ideal. Properly you would somehow asynchronously interact with -** vstgui and so on, which @jjs started. But we kinda gotta do something. -** -** If you want to come along and rip out these zenity calls and replace them -** with vstgui, please do so! This is purely tactical stuff to get us -** not silently failing. -*/ -namespace Surge -{ - -namespace UserInteractions -{ - -/* - * Setting this unsets LD_LIB_PATH for the zenity spawns for error and file open. - * The rest are left as an exercise for someone else to do! - */ -static bool isArdour = false; -void useWorkaroundsForArdoursAncientGTK(bool b) { isArdour = true; } - -// @jpcima: string quoting for shell commands to use wfith popen. -static std::string escapeForPosixShell(const std::string &str) -{ - std::string esc; - esc.reserve(2 * str.size()); - - // 1. if it's a newline, write it verbatim enclosed in single-quotes; - // 2. if it's a letter or digit, write it verbatim; - // 3. otherwise, prefix it with a backward slash. - - for (char c : str) - { - if (c == '\n') - esc.append("'\n'"); - else - { - bool isAlphaNum = - (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); - if (!isAlphaNum) - esc.push_back('\\'); - esc.push_back(c); - } - } - - return esc; -} - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - if (isArdour) - { - /* - You would think this would work! But it drops the display environment so it does not. - If you would like to improve this code, we welcome pull requests! - if (vfork() == 0) - { - const char *args[] = { "zenity", "--error", "--text", message.c_str(), "--title", - title.c_str(), nullptr }; const char *ardourEnv[] = { "LD_LIBRARY_PATH=", nullptr }; if - (execvpe("zenity", (char * const*)args, (char * const*)ardourEnv) < 0) - { - _exit(0); - } - }*/ - std::string zc = "LD_LIBRARY_PATH= zenity --error --text \""; - zc += message + "\" --title \"" + title + "\""; - std::cout << "About to run [" << zc << "]" << std::endl; - if (!system(zc.c_str())) - std::cout << "Can't run zenity. Oh well." << std::endl; - } - else - { - if (vfork() == 0) - { - if (execlp("zenity", "zenity", "--error", "--text", message.c_str(), "--title", - title.c_str(), (char *)nullptr) < 0) - { - _exit(0); - } - } - } - std::cerr << "Surge Error\n" << title << "\n" << message << "\n" << std::flush; -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - if (isArdour) - { - promptError(message, title, guiEditor); - } - else if (vfork() == 0) - { - if (execlp("zenity", "zenity", "--info", "--text", message.c_str(), "--title", - title.c_str(), (char *)nullptr) < 0) - { - _exit(0); - } - } - std::cerr << "Surge Error\n" << title << "\n" << message << "\n" << std::flush; -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - size_t pid = vfork(); - - if (pid < 0) - { - std::cerr << "Surge Error: vfork has failed\n"; - return UserInteractions::CANCEL; - } - else if (pid == 0) - { - execlp("zenity", "zenity", "--question", "--text", message.c_str(), "--title", - title.c_str(), (char *)nullptr); - _exit(65); - } - - int wret; - int wstatus; - while ((wret = waitpid(pid, &wstatus, 0)) == -1 && errno == EINTR) - /* wait interrupted by signal, try again */; - - if (wret == -1) - { - std::cerr << "Surge Error: waitpid has failed, \"" << strerror(errno) << "\"\n"; - return UserInteractions::CANCEL; - } - - auto status = WEXITSTATUS(wstatus); - switch (status) - { - case 0: - case 65: - // zenity worked or was not installed - return UserInteractions::OK; - default: - return UserInteractions::CANCEL; - } -} - -void openURL(const std::string &url) -{ - if (vfork() == 0) - { - if (execlp("xdg-open", "xdg-open", url.c_str(), (char *)nullptr) < 0) - { - _exit(0); - } - } -} - -void showHTML(const std::string &html) -{ - // FIXME - there's proper APIs for this that crash on MacOS - std::ostringstream fns; - fns << "/tmp/surge-data." << rand() << ".html"; - - FILE *f = fopen(fns.str().c_str(), "w"); - if (f) - { - fprintf(f, "%s", html.c_str()); - fclose(f); - std::string url = std::string("file://") + fns.str(); - openURL(url); - } -} - -void openFolderInFileBrowser(const std::string &folder) -{ - std::string url = "file://" + folder; - UserInteractions::openURL(url); -} - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories, bool canCreateDirectories, - SurgeGUIEditor *guiEditor) -{ - /* - ** This is a blocking model which will cause us problems I am sure - */ - - std::string zenityCommand; - zenityCommand.reserve(1024); - - if (isArdour) - zenityCommand.append("LD_LIBRARY_PATH= "); - - zenityCommand.append("zenity --file-selection"); - - if (!initialDirectory.empty()) - { - zenityCommand.append(" --filename="); - zenityCommand.append(escapeForPosixShell(initialDirectory)); - zenityCommand.push_back('/'); // makes it handled as directory - } - - if (!filterSuffix.empty()) - { - zenityCommand.append(" --file-filter="); - zenityCommand.append(escapeForPosixShell("*" + filterSuffix)); - } - - if (canSelectDirectories) - zenityCommand.append(" --directory"); - - // option not implemented - (void)canCreateDirectories; - - std::cout << "Zenity command is '" << zenityCommand << "'" << std::endl; - FILE *z = popen(zenityCommand.c_str(), "r"); - if (!z) - { - return; - } - - std::string result; // not forcing a fixed limit on length, depends on fs - result.reserve(1024); - - char buffer[1024]; - for (size_t count; (count = fread(buffer, 1, sizeof(buffer), z)) > 0;) - result.append(buffer, count); - - pclose(z); - - // eliminate the final newline - if (!result.empty() && result.back() == '\n') - result.pop_back(); - - if (!result.empty()) // it's empty if the user cancelled - callbackOnOpen(result); -} -}; // namespace UserInteractions - -}; // namespace Surge diff --git a/src/mac/UserInteractionsMac.mm b/src/mac/UserInteractionsMac.mm deleted file mode 100644 index b1d0425db59..00000000000 --- a/src/mac/UserInteractionsMac.mm +++ /dev/null @@ -1,150 +0,0 @@ -#include "UserInteractions.h" -#include -#include -#include -#include - -#include -#include -#include - -namespace Surge -{ - -namespace UserInteractions -{ - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - CFStringRef cfT = - CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8); - CFStringRef cfM = - CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8); - - SInt32 nRes = 0; - CFUserNotificationRef pDlg = NULL; - const void *keys[] = {kCFUserNotificationAlertHeaderKey, kCFUserNotificationAlertMessageKey}; - const void *vals[] = {cfT, cfM}; - - CFDictionaryRef dict = - CFDictionaryCreate(0, keys, vals, sizeof(keys) / sizeof(*keys), - &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - pDlg = CFUserNotificationCreate(kCFAllocatorDefault, 0, kCFUserNotificationStopAlertLevel, - &nRes, dict); - - CFRelease(cfT); - CFRelease(cfM); -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - CFStringRef cfT = - CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8); - CFStringRef cfM = - CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8); - - SInt32 nRes = 0; - CFUserNotificationRef pDlg = NULL; - const void *keys[] = {kCFUserNotificationAlertHeaderKey, kCFUserNotificationAlertMessageKey}; - const void *vals[] = {cfT, cfM}; - - CFDictionaryRef dict = - CFDictionaryCreate(0, keys, vals, sizeof(keys) / sizeof(*keys), - &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - pDlg = CFUserNotificationCreate(kCFAllocatorDefault, 0, kCFUserNotificationNoteAlertLevel, - &nRes, dict); - - CFRelease(cfT); - CFRelease(cfM); -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - CFStringRef cfT = - CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8); - CFStringRef cfM = - CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8); - - CFOptionFlags responseFlags; - CFUserNotificationDisplayAlert(0, kCFUserNotificationPlainAlertLevel, 0, 0, 0, cfT, cfM, - CFSTR("OK"), CFSTR("Cancel"), 0, &responseFlags); - - CFRelease(cfT); - CFRelease(cfM); - - if ((responseFlags & 0x3) != kCFUserNotificationDefaultResponse) - return UserInteractions::CANCEL; - return UserInteractions::OK; -} - -void openURL(const std::string &url_str) -{ - CFURLRef url = CFURLCreateWithBytes(NULL, // allocator - (UInt8 *)url_str.c_str(), // URLBytes - url_str.length(), // length - kCFStringEncodingASCII, // encoding - NULL // baseURL - ); - LSOpenCFURLRef(url, 0); - CFRelease(url); -} - -void showHTML(const std::string &html) -{ - // Why does mktemp crash on macos I wonder? - std::ostringstream fns; - fns << "/var/tmp/surge-data." << rand() << ".html"; - - FILE *f = fopen(fns.str().c_str(), "w"); - if (f) - { - fprintf(f, "%s", html.c_str()); - fclose(f); - std::string url = std::string("file://") + fns.str(); - openURL(url); - } -} - -void openFolderInFileBrowser(const std::string &folder) -{ - std::string url = "file://" + folder; - UserInteractions::openURL(url); -} - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories, bool canCreateDirectories, - SurgeGUIEditor *guiEditor) -{ - // FIXME TODO - support the filterSuffix and initialDirectory - NSOpenPanel *panel = [NSOpenPanel openPanel]; - [panel setCanChooseDirectories:canSelectDirectories]; - [panel setCanCreateDirectories:canCreateDirectories]; - [panel - setDirectoryURL:[NSURL - fileURLWithPath:[NSString - stringWithUTF8String:(initialDirectory.c_str())]]]; - - // Logic and others have wierd window ordering which can mean this somethimes pops behind. - // See #1001. - [panel makeKeyAndOrderFront:panel]; - [panel setLevel:CGShieldingWindowLevel()]; - - [panel beginWithCompletionHandler:^(NSInteger result) { - if (result == NSFileHandlingPanelOKButton) - { - NSURL *theDoc = [[panel URLs] objectAtIndex:0]; - NSString *path = [theDoc path]; - std::string pstring([path UTF8String]); - callbackOnOpen(pstring); - } - }]; -} - -}; - -}; diff --git a/src/surge_effects_bank/UserInteractionsJUCE.cpp b/src/surge_effects_bank/UserInteractionsJUCE.cpp deleted file mode 100644 index 6d2d38c99ab..00000000000 --- a/src/surge_effects_bank/UserInteractionsJUCE.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "UserInteractions.h" -#include -#include - -#if MAC -#include -#endif - -void headlessStackDump() -{ -#if MAC - void *callstack[128]; - int i, frames = backtrace(callstack, 128); - char **strs = backtrace_symbols(callstack, frames); - for (i = 1; i < 6; ++i) - { - fprintf(stderr, "StackTrace[%3d]: %s\n", i, strs[i]); - } - free(strs); - -#endif -} - -namespace Surge -{ -namespace UserInteractions -{ - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge Error\n" << title << "\n" << message << "\n" << std::flush; - headlessStackDump(); -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge Info\n" << title << "\n" << message << "\n" << std::flush; - headlessStackDump(); -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge OkCancel\n" - << title << "\n" - << message << "\n" - << "Returning CANCEL" << std::flush; - headlessStackDump(); - return UserInteractions::CANCEL; -} - -void openURL(const std::string &url) {} -void showHTML(const std::string &html) { std::cerr << "SURGE HTML: " << html << std::endl; } - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, bool od, bool cd, - SurgeGUIEditor *guiEditor) -{ - UserInteractions::promptError( - "Open file dialog is not implemented in this version of Surge. Sorry!", - "Unimplemented Function", guiEditor); -} -}; // namespace UserInteractions - -}; // namespace Surge diff --git a/src/surge_synth_juce/SurgeSynthEditor.cpp b/src/surge_synth_juce/SurgeSynthEditor.cpp index 1fb0767f245..75241ba7a5b 100644 --- a/src/surge_synth_juce/SurgeSynthEditor.cpp +++ b/src/surge_synth_juce/SurgeSynthEditor.cpp @@ -137,12 +137,4 @@ VSTGUI::CRect getScreenDimensions(VSTGUI::CFrame *) } } // namespace GUI -namespace UserInteractions -{ -/* - * This means I have a link wrong - */ -void openFolderInFileBrowser(const std::string &folder) {} - -} // namespace UserInteractions } // namespace Surge diff --git a/src/surge_synth_juce/SurgeSynthEditor.h b/src/surge_synth_juce/SurgeSynthEditor.h index af393928a60..cfb05fa1591 100644 --- a/src/surge_synth_juce/SurgeSynthEditor.h +++ b/src/surge_synth_juce/SurgeSynthEditor.h @@ -16,6 +16,8 @@ #include "SurgeSynthProcessor.h" +class SurgeGUIEditor; + //============================================================================== /** */ diff --git a/src/surge_synth_juce/SurgeSynthInteractionsImpl.h b/src/surge_synth_juce/SurgeSynthInteractionsImpl.h deleted file mode 100644 index 4899a3a5d72..00000000000 --- a/src/surge_synth_juce/SurgeSynthInteractionsImpl.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Created by Paul Walker on 4/10/21. -// - -#ifndef SURGE_SURGESYNTHINTERACTIONSIMPL_H -#define SURGE_SURGESYNTHINTERACTIONSIMPL_H - -#include -#include "SurgeSynthInteractionsInterface.h" -#include "SurgeGUIEditor.h" // bit of a hack - -struct SurgeSynthInteractionsImpl : public SurgeSynthInteractionsInterface -{ - void promptError(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) - { - juce::AlertWindow::showMessageBox(juce::AlertWindow::WarningIcon, title, message); - } - void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) - { - juce::AlertWindow::showMessageBox(juce::AlertWindow::InfoIcon, title, message); - } - virtual Surge::UserInteractions::MessageResult - promptOKCancel(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) - { - // return Surge::UserInteractions::CANCEL; - auto res = juce::AlertWindow::showOkCancelBox(juce::AlertWindow::InfoIcon, title, message, - "OK", "Cancel", nullptr, nullptr); - if (res) - return Surge::UserInteractions::OK; - else - return Surge::UserInteractions::CANCEL; - } - virtual void openURL(const std::string &url) { juce::URL(url).launchInDefaultBrowser(); }; - virtual void showHTML(const std::string &html) - { - static struct filesToDelete : juce::DeletedAtShutdown - { - ~filesToDelete() - { - for (const auto &d : deleteThese) - { - d.deleteFile(); - } - } - std::vector deleteThese; - } *byebyeOnExit = new filesToDelete(); - - auto f = juce::File::createTempFile("_surge.html"); - f.replaceWithText(html); - auto U = juce::URL(f); - U.launchInDefaultBrowser(); - byebyeOnExit->deleteThese.push_back(f); - }; - - virtual void promptFileOpenDialog(const std::string &initialDirectory, - const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories, bool canCreateDirectories, - SurgeGUIEditor *guiEditor) - { - juce::FileChooser c("Select File", juce::File(initialDirectory)); - auto r = c.browseForFileToOpen(); - if (r) - { - auto res = c.getResult(); - callbackOnOpen(res.getFullPathName().toStdString()); - } - } -}; - -#endif // SURGE_SURGESYNTHINTERACTIONSIMPL_H diff --git a/src/surge_synth_juce/SurgeSynthInteractionsInterface.h b/src/surge_synth_juce/SurgeSynthInteractionsInterface.h deleted file mode 100644 index 4348ea692c0..00000000000 --- a/src/surge_synth_juce/SurgeSynthInteractionsInterface.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Paul Walker on 4/10/21. -// - -#ifndef SURGE_SURGESYNTHINTERACTIONSINTERFACE_H -#define SURGE_SURGESYNTHINTERACTIONSINTERFACE_H - -#include -#include -#include "UserInteractions.h" - -class SurgeGUIEditor; -struct SurgeSynthInteractionsInterface -{ - static std::atomic impl; - - virtual ~SurgeSynthInteractionsInterface() = default; - virtual void promptError(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) = 0; - virtual void promptInfo(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) = 0; - virtual Surge::UserInteractions::MessageResult promptOKCancel(const std::string &message, - const std::string &title, - SurgeGUIEditor *guiEditor) = 0; - virtual void openURL(const std::string &url) = 0; - virtual void showHTML(const std::string &html) = 0; - virtual void promptFileOpenDialog(const std::string &initialDirectory, - const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories = false, - bool canCreateDirectories = false, - SurgeGUIEditor *guiEditor = nullptr) = 0; -}; - -#endif // SURGE_SURGESYNTHINTERACTIONSINTERFACE_H diff --git a/src/surge_synth_juce/SurgeSynthProcessor.cpp b/src/surge_synth_juce/SurgeSynthProcessor.cpp index 7b7d16aef97..3c2b0c1612c 100644 --- a/src/surge_synth_juce/SurgeSynthProcessor.cpp +++ b/src/surge_synth_juce/SurgeSynthProcessor.cpp @@ -13,7 +13,6 @@ #include "DebugHelpers.h" #include "SurgeSynthFlavorExtensions.h" #include "version.h" -#include "SurgeSynthInteractionsImpl.h" using namespace juce; @@ -26,11 +25,6 @@ SurgeSynthProcessor::SurgeSynthProcessor() .withOutput("Scene B", AudioChannelSet::stereo(), false)) { std::cout << "SurgeXT : Version " << Surge::Build::FullVersionStr << std::endl; - if (!SurgeSynthInteractionsInterface::impl.load()) - { - // FIXME - this leaks. We will rework this in XT anyway - SurgeSynthInteractionsImpl::impl = new SurgeSynthInteractionsImpl(); - } surge = std::make_unique(this); std::map>> parByGroup; diff --git a/src/surge_synth_juce/UserInteractionsJUCE_Synth_XT.cpp b/src/surge_synth_juce/UserInteractionsJUCE_Synth_XT.cpp deleted file mode 100644 index 66987f3c3c4..00000000000 --- a/src/surge_synth_juce/UserInteractionsJUCE_Synth_XT.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "UserInteractions.h" -#include "SurgeSynthInteractionsInterface.h" -#include - -#if MAC -#include -#endif - -std::atomic SurgeSynthInteractionsInterface::impl(0); - -void headlessStackDump() -{ -#if MAC - void *callstack[128]; - int i, frames = backtrace(callstack, 128); - char **strs = backtrace_symbols(callstack, frames); - for (i = 1; i < 6; ++i) - { - std::cout << "StackTrace[" << i << "]" << strs[i] << std::endl; - } - free(strs); - -#endif -} - -namespace Surge -{ -namespace UserInteractions -{ - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - if (SurgeSynthInteractionsInterface::impl.load()) - { - SurgeSynthInteractionsInterface::impl.load()->promptError(message, title, guiEditor); - } - else - { - std::cerr << "SurgeXT Error[" << title << "]\n" << message << std::endl; - headlessStackDump(); - } -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - if (SurgeSynthInteractionsInterface::impl.load()) - { - SurgeSynthInteractionsInterface::impl.load()->promptInfo(message, title, guiEditor); - } - else - { - std::cerr << "SurgeXT Info\n" << title << "\n" << message << "\n" << std::flush; - headlessStackDump(); - } -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - std::cerr << "Surge OkCancel\n" - << title << "\n" - << message << "\n" - << "Returning CANCEL" << std::flush; - headlessStackDump(); - return UserInteractions::CANCEL; -} - -void openURL(const std::string &url) -{ - if (SurgeSynthInteractionsInterface::impl.load()) - { - SurgeSynthInteractionsInterface::impl.load()->openURL(url); - } -} -void showHTML(const std::string &html) -{ - if (SurgeSynthInteractionsInterface::impl.load()) - { - SurgeSynthInteractionsInterface::impl.load()->showHTML(html); - } -} - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, bool od, bool cd, - SurgeGUIEditor *guiEditor) -{ - if (SurgeSynthInteractionsInterface::impl.load()) - { - SurgeSynthInteractionsInterface::impl.load()->promptFileOpenDialog( - initialDirectory, filterSuffix, filterDescription, callbackOnOpen, od, cd, guiEditor); - } -} - -}; // namespace UserInteractions - -}; // namespace Surge diff --git a/src/windows/UserInteractionsWin.cpp b/src/windows/UserInteractionsWin.cpp deleted file mode 100644 index 8645a5c9aa8..00000000000 --- a/src/windows/UserInteractionsWin.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "UserInteractions.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - -std::string wideToUtf8(const std::wstring &wide) -{ - if (wide.empty()) - return std::string(); - - const auto size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.c_str(), wide.size(), - nullptr, 0, nullptr, nullptr); - if (size) - { - std::string utf8; - utf8.resize(size); - if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.c_str(), wide.size(), &utf8[0], - utf8.size(), nullptr, nullptr)) - return utf8; - } - throw std::system_error(GetLastError(), std::system_category()); -} - -std::wstring utf8ToWide(const std::string &utf8) -{ - if (utf8.empty()) - return std::wstring(); - - const auto size = - MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), utf8.size(), nullptr, 0); - if (size) - { - std::wstring wide; - wide.resize(size); - if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), utf8.size(), &wide[0], - wide.size())) - return wide; - } - throw std::system_error(GetLastError(), std::system_category()); -} - -void browseFolder(const std::string &initialDirectory, - const std::function &callbackOnOpen) -{ - struct BrowseCallback - { - static int CALLBACK proc(HWND hwnd, UINT uMsg, LPARAM, LPARAM lpData) - { - if (uMsg == BFFM_INITIALIZED) - SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); - return 0; - } - }; - - auto path_param = utf8ToWide(initialDirectory); - - BROWSEINFO bi{}; - bi.lpszTitle = L"Browse for folder..."; - bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; - bi.lpfn = BrowseCallback::proc; - bi.lParam = LPARAM(path_param.c_str()); - - if (auto pidl = SHBrowseForFolder(&bi)) - { - WCHAR path[MAX_PATH]; - SHGetPathFromIDList(&*pidl, path); - CoTaskMemFree(pidl); - callbackOnOpen(wideToUtf8(path)); - } -} - -} // anonymous namespace - -namespace Surge::UserInteractions -{ - -void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(), - MB_OK | MB_ICONERROR); -} - -void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) -{ - MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(), - MB_OK | MB_ICONINFORMATION); -} - -MessageResult promptOKCancel(const std::string &message, const std::string &title, - SurgeGUIEditor *guiEditor) -{ - if (MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(), - MB_YESNO | MB_ICONQUESTION) != IDYES) - return UserInteractions::CANCEL; - - return UserInteractions::OK; -} - -void openURL(const std::string &url) -{ - ShellExecute(nullptr, L"open", utf8ToWide(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL); -} - -void showHTML(const std::string &html) -{ - WCHAR pathBuf[MAX_PATH]; - if (!GetTempPath(MAX_PATH, pathBuf)) - return; - - // Prepending file:// opens in the browser instead of the associated app for .html (which might - // be unset) - std::wostringstream fns; - fns << L"file://" << pathBuf << L"surge-data." << rand() << L".html"; - - auto fileName(fns.str()); - std::ofstream out(fileName.c_str() + 7); - if (out << html << std::flush) - ShellExecute(nullptr, L"open", fileName.c_str(), nullptr, nullptr, SW_SHOWNORMAL); -} - -void openFolderInFileBrowser(const std::string &folder) { openURL(folder); } - -void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix, - const std::string &filterDescription, - std::function callbackOnOpen, - bool canSelectDirectories, bool canCreateDirectories, - SurgeGUIEditor *guiEditor) -{ - if (canSelectDirectories) - { - browseFolder(initialDirectory, callbackOnOpen); - return; - } - - // With many thanks to - // https://www.daniweb.com/programming/software-development/code/217307/a-simple-getopenfilename-example - - // this also helped! - // https://stackoverflow.com/questions/34201213/c-lpstr-and-string-trouble-with-zero-terminated-strings - std::wstring fullFilter; - fullFilter.append(utf8ToWide(filterDescription)); - fullFilter.push_back(0); - fullFilter.append(utf8ToWide("*" + filterSuffix)); - fullFilter.push_back(0); - - std::wstring initialDirectoryNative(utf8ToWide(initialDirectory)); - WCHAR szFile[1024]; - OPENFILENAME ofn = {}; - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = nullptr; - ofn.lpstrFile = szFile; - ofn.lpstrFile[0] = 0; - ofn.nMaxFile = std::size(szFile); - ofn.lpstrFilter = fullFilter.c_str(); - ofn.nFilterIndex = 0; - ofn.lpstrFileTitle = nullptr; - ofn.nMaxFileTitle = 0; - ofn.lpstrInitialDir = initialDirectoryNative.c_str(); - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - - if (GetOpenFileName(&ofn)) - callbackOnOpen(wideToUtf8(ofn.lpstrFile)); -} - -} // namespace Surge::UserInteractions