diff --git a/plugins/editor/src/editor/EditIds.h b/plugins/editor/src/editor/EditIds.h index d9b43957..869d1c44 100644 --- a/plugins/editor/src/editor/EditIds.h +++ b/plugins/editor/src/editor/EditIds.h @@ -20,6 +20,7 @@ enum class EditId : int { StretchTuning, CanEditUserFilesDir, UserFilesDir, + FallbackFilesDir, // Controller0, ControllerLast = Controller0 + sfz::config::numCCs - 1, diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 0b5c6e5a..f56b7f0a 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { std::string currentSfzFile_; std::string currentScalaFile_; + std::string userFilesDir_; + std::string fallbackFilesDir_; enum { kPanelGeneral, @@ -56,6 +59,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { enum { kTagLoadSfzFile, kTagEditSfzFile, + kTagCreateNewSfzFile, + kTagOpenSfzFolder, kTagPreviousSfzFile, kTagNextSfzFile, kTagFileOperations, @@ -132,11 +137,13 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { } void chooseSfzFile(); + void createNewSfzFile(); void changeSfzFile(const std::string& filePath); void changeToNextSfzFile(long offset); void chooseScalaFile(); void changeScalaFile(const std::string& filePath); void chooseUserFilesDir(); + std::string getFileChooserInitialDir(const std::string& previousFilePath) const; static bool scanDirectoryFiles(const fs::path& dirPath, std::function filter, std::vector& fileNames); @@ -319,7 +326,13 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) } case EditId::UserFilesDir: { - updateUserFilesDirLabel(v.to_string()); + userFilesDir_ = v.to_string(); + updateUserFilesDirLabel(userFilesDir_); + break; + } + case EditId::FallbackFilesDir: + { + fallbackFilesDir_ = v.to_string(); break; } case EditId::UINumCurves: @@ -839,6 +852,8 @@ void Editor::Impl::createFrameContents() if (SActionMenu* menu = fileOperationsMenu_) { menu->addEntry("Load file", kTagLoadSfzFile); menu->addEntry("Edit file", kTagEditSfzFile); + menu->addEntry("Create new file", kTagCreateNewSfzFile); + menu->addEntry("Open SFZ folder", kTagOpenSfzFolder); } if (SPiano* piano = piano_) { @@ -891,10 +906,10 @@ void Editor::Impl::chooseSfzFile() fs->setTitle("Load SFZ file"); fs->setDefaultExtension(CFileExtension("SFZ", "sfz")); - if (!currentSfzFile_.empty()) { - std::string initialDir = fs::path(currentSfzFile_).parent_path().u8string() + '/'; + + std::string initialDir = getFileChooserInitialDir(currentSfzFile_); + if (!initialDir.empty()) fs->setInitialDirectory(initialDir.c_str()); - } if (fs->runModal()) { UTF8StringPtr file = fs->getSelectedFile(0); @@ -903,6 +918,44 @@ void Editor::Impl::chooseSfzFile() } } +/// +static const char defaultSfzText[] = + "sample=*sine" "\n" + "ampeg_attack=0.02 ampeg_release=0.1" "\n"; + +static void createDefaultSfzFileIfNotExisting(const fs::path& path) +{ + if (!fs::exists(path)) + fs::ofstream { path } << defaultSfzText; +} + +/// +void Editor::Impl::createNewSfzFile() +{ + SharedPointer fs = owned(CNewFileSelector::create(frame_, CNewFileSelector::kSelectSaveFile)); + + fs->setTitle("Create SFZ file"); + fs->setDefaultExtension(CFileExtension("SFZ", "sfz")); + + std::string initialDir = getFileChooserInitialDir(currentSfzFile_); + if (!initialDir.empty()) + fs->setInitialDirectory(initialDir.c_str()); + + if (fs->runModal()) { + UTF8StringPtr file = fs->getSelectedFile(0); + std::string fileStr; + if (file && !absl::EndsWithIgnoreCase(file, ".sfz")) { + fileStr = std::string(file) + ".sfz"; + file = fileStr.c_str(); + } + if (file) { + createDefaultSfzFileIfNotExisting(fs::u8path(file)); + changeSfzFile(file); + openFileInExternalEditor(file); + } + } +} + void Editor::Impl::changeSfzFile(const std::string& filePath) { ctrl_->uiSendValue(EditId::SfzFile, filePath); @@ -969,10 +1022,10 @@ void Editor::Impl::chooseScalaFile() fs->setTitle("Load Scala file"); fs->setDefaultExtension(CFileExtension("SCL", "scl")); - if (!currentScalaFile_.empty()) { - std::string initialDir = fs::path(currentScalaFile_).parent_path().u8string() + '/'; + + std::string initialDir = getFileChooserInitialDir(currentScalaFile_); + if (!initialDir.empty()) fs->setInitialDirectory(initialDir.c_str()); - } if (fs->runModal()) { UTF8StringPtr file = fs->getSelectedFile(0); @@ -998,12 +1051,31 @@ void Editor::Impl::chooseUserFilesDir() if (fs->runModal()) { UTF8StringPtr dir = fs->getSelectedFile(0); if (dir) { - updateUserFilesDirLabel(dir); - ctrl_->uiSendValue(EditId::UserFilesDir, std::string(dir)); + userFilesDir_ = std::string(dir); + updateUserFilesDirLabel(userFilesDir_); + ctrl_->uiSendValue(EditId::UserFilesDir, userFilesDir_); } } } +std::string Editor::Impl::getFileChooserInitialDir(const std::string& previousFilePath) const +{ + fs::path initialPath; + + if (!previousFilePath.empty()) + initialPath = fs::u8path(previousFilePath).parent_path(); + else if (!userFilesDir_.empty()) + initialPath = fs::u8path(userFilesDir_); + else if (!fallbackFilesDir_.empty()) + initialPath = fs::u8path(fallbackFilesDir_); + + std::string initialDir = initialPath.u8string(); + if (!initialDir.empty()) + initialDir.push_back('/'); + + return initialDir; +} + bool Editor::Impl::scanDirectoryFiles(const fs::path& dirPath, std::function filter, std::vector& fileNames) { std::error_code ec; @@ -1292,6 +1364,23 @@ void Editor::Impl::valueChanged(CControl* ctl) openFileInExternalEditor(currentSfzFile_.c_str()); break; + case kTagCreateNewSfzFile: + if (value != 1) + break; + + createNewSfzFile(); + break; + + case kTagOpenSfzFolder: + if (value != 1) + break; + + if (!userFilesDir_.empty()) + openDirectoryInExplorer(userFilesDir_.c_str()); + else if (!fallbackFilesDir_.empty()) + openDirectoryInExplorer(fallbackFilesDir_.c_str()); + break; + case kTagPreviousSfzFile: if (value != 1) break; diff --git a/plugins/editor/src/editor/NativeHelpers.cpp b/plugins/editor/src/editor/NativeHelpers.cpp index ab760d73..0a3ac516 100644 --- a/plugins/editor/src/editor/NativeHelpers.cpp +++ b/plugins/editor/src/editor/NativeHelpers.cpp @@ -11,9 +11,20 @@ #include #include +static WCHAR *stringToWideChar(const char *str, int strCch = -1) +{ + unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0); + if (strSize == 0) + return {}; + std::unique_ptr strW(new WCHAR[strSize]); + if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0) + return {}; + return strW.release(); +} + bool openFileInExternalEditor(const char *filename) { - std::wstring path = fs::u8path(filename).wstring(); + std::wstring path = stringToWideChar(filename); SHELLEXECUTEINFOW info; memset(&info, 0, sizeof(info)); @@ -27,14 +38,42 @@ bool openFileInExternalEditor(const char *filename) return ShellExecuteExW(&info); } + +bool openDirectoryInExplorer(const char *filename) +{ + std::wstring path = stringToWideChar(filename); + + SHELLEXECUTEINFOW info; + memset(&info, 0, sizeof(info)); + + info.cbSize = sizeof(info); + info.lpVerb = L"explore"; + info.lpFile = path.c_str(); + info.nShow = SW_SHOW; + + return ShellExecuteExW(&info); +} + +bool askQuestion(const char *text) +{ + int ret = MessageBoxW(nullptr, stringToWideChar(text), L"Question", MB_YESNO); + return ret == IDYES; +} #elif defined(__APPLE__) // implemented in NativeHelpers.mm #else #include +#include +#include +#include +#include +#include +#include +extern "C" { extern char **environ; } -bool openFileInExternalEditor(const char *filename) +static bool openFileByMimeType(const char *filename, const char *mimetype) { - GAppInfo* appinfo = g_app_info_get_default_for_type("text/plain", FALSE); + GAppInfo* appinfo = g_app_info_get_default_for_type(mimetype, FALSE); if (!appinfo) return 1; @@ -47,4 +86,63 @@ bool openFileInExternalEditor(const char *filename) g_object_unref(appinfo); return success == TRUE; } + +bool openFileInExternalEditor(const char *filename) +{ + return openFileByMimeType(filename, "text/plain"); +} + +bool openDirectoryInExplorer(const char *filename) +{ + return openFileByMimeType(filename, "inode/directory"); +} + +static std::vector createForkEnviron() +{ + std::vector newEnv; + newEnv.reserve(256); + for (char **envp = environ; *envp; ++envp) { + // ensure the process will link with system libraries, + // and not these from the Ardour bundle. + if (strncmp(*envp, "LD_LIBRARY_PATH=", 16) == 0) + continue; + newEnv.push_back(*envp); + } + newEnv.push_back(nullptr); + return newEnv; +} + +bool askQuestion(const char *text) +{ + char *argv[] = { + const_cast("/usr/bin/zenity"), + const_cast("--question"), + const_cast("--text"), + const_cast(text), + nullptr, + }; + + std::vector newEnv = createForkEnviron(); + char **envp = newEnv.data(); + + pid_t forkPid = vfork(); + if (forkPid == -1) + return false; + + if (forkPid == 0) { + execve(argv[0], argv, envp); + _exit(1); + } + + int wret; + int wstatus; + do { + wret = waitpid(forkPid, &wstatus, 0); + } while (wret == -1 && errno == EINTR); + + if (wret == -1 || !WIFEXITED(wstatus)) + return false; + + return WEXITSTATUS(wstatus) == 0; +} #endif diff --git a/plugins/editor/src/editor/NativeHelpers.h b/plugins/editor/src/editor/NativeHelpers.h index b9c29bb6..9ccb5410 100644 --- a/plugins/editor/src/editor/NativeHelpers.h +++ b/plugins/editor/src/editor/NativeHelpers.h @@ -7,3 +7,5 @@ #pragma once bool openFileInExternalEditor(const char *filename); +bool openDirectoryInExplorer(const char *filename); +bool askQuestion(const char *text); diff --git a/plugins/editor/src/editor/NativeHelpers.mm b/plugins/editor/src/editor/NativeHelpers.mm index 9cb8cd28..56c1143d 100644 --- a/plugins/editor/src/editor/NativeHelpers.mm +++ b/plugins/editor/src/editor/NativeHelpers.mm @@ -11,20 +11,34 @@ #import #import -bool openFileInExternalEditor(const char *fileNameUTF8) +static bool openFileWithApplication(const char *fileName, NSString *application) { - BOOL wasOpened = NO; + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSString* fileNameNs = [NSString stringWithUTF8String:fileName]; + return [workspace openFile:fileNameNs withApplication:application] == YES; +} +bool openFileInExternalEditor(const char *fileName) +{ NSURL* applicationURL = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType( kUTTypePlainText, kLSRolesEditor, nil); - if (!applicationURL) + if (!applicationURL || ![applicationURL isFileURL]) return false; - if ([applicationURL isFileURL]) { - NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; - NSString* fileName = [NSString stringWithUTF8String:fileNameUTF8]; - wasOpened = [workspace openFile:fileName withApplication:[applicationURL path]]; - } + return openFileWithApplication(fileName, [applicationURL path]); +} - return wasOpened == YES; +bool openDirectoryInExplorer(const char *fileName) +{ + return openFileWithApplication(fileName, @"Finder"); +} + +bool askQuestion(const char *text) +{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithUTF8String:text]]; + [alert addButtonWithTitle:@"OK"]; + [alert addButtonWithTitle:@"Cancel"]; + NSInteger button = [alert runModal]; + return button == NSAlertFirstButtonReturn; } #endif diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index d5788b48..8345c274 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -91,6 +91,7 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p absl::optional userFilesDir = SfizzPaths::getSfzConfigDefaultPath(); uiReceiveValue(EditId::CanEditUserFilesDir, 1); uiReceiveValue(EditId::UserFilesDir, userFilesDir.value_or(fs::path()).u8string()); + uiReceiveValue(EditId::FallbackFilesDir, SfizzPaths::getSfzFallbackDefaultPath().u8string()); return true; }