From 9d77522efb73bcf956b49eb3c280e152570616af Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 27 Jul 2024 04:35:21 -0500 Subject: [PATCH] GUI: prepare to add "save all assets" options --- src/gui/dataList.cpp | 22 +++++++++ src/gui/doAction.cpp | 22 ++++++++- src/gui/fileDialog.cpp | 106 +++++++++++++++++++++++++++++++++++------ src/gui/fileDialog.h | 9 +++- src/gui/gui.cpp | 34 ++++++++++++- src/gui/gui.h | 6 +++ src/gui/guiConst.cpp | 3 ++ 7 files changed, 183 insertions(+), 19 deletions(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 54f4359ca..b4a475e77 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -441,10 +441,26 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::MenuItem(_("save raw sample..."))) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); } + + ImGui::Separator(); + + if (ImGui::MenuItem(_("save all instruments..."))) { + doAction(GUI_ACTION_INS_LIST_SAVE_ALL); + } + if (ImGui::MenuItem(_("save all wavetables..."))) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_ALL); + } + if (ImGui::MenuItem(_("save all samples..."))) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE_ALL); + } } else { if (ImGui::MenuItem(_("save as .dmp..."))) { doAction(GUI_ACTION_INS_LIST_SAVE_DMP); } + + if (ImGui::MenuItem(_("save all..."))) { + doAction(GUI_ACTION_INS_LIST_SAVE_ALL); + } } ImGui::EndPopup(); } @@ -750,6 +766,9 @@ void FurnaceGUI::drawWaveList(bool asChild) { if (ImGui::MenuItem(_("save raw..."))) { doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); } + if (ImGui::MenuItem(_("save all..."))) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_ALL); + } ImGui::EndPopup(); } } @@ -893,6 +912,9 @@ void FurnaceGUI::drawSampleList(bool asChild) { if (ImGui::MenuItem(_("save raw..."))) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); } + if (ImGui::MenuItem(_("save all..."))) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE_ALL); + } ImGui::EndPopup(); } ImGui::SameLine(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index d6d8ea434..fff5a85d9 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -781,8 +781,14 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_DIR_VIEW: insListDir=!insListDir; break; + case GUI_ACTION_INS_LIST_SAVE_ALL: + if (e->song.ins.empty()) { + showError(_("this song doesn't have any instruments.")); + } else { + openFileDialog(GUI_FILE_INS_SAVE_ALL); + } + break; - case GUI_ACTION_WAVE_LIST_ADD: { std::vector alreadyDone; waveSizeList.clear(); @@ -902,6 +908,13 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_DIR_VIEW: waveListDir=!waveListDir; break; + case GUI_ACTION_WAVE_LIST_SAVE_ALL: + if (e->song.wave.empty()) { + showError(_("this song doesn't have any wavetables.")); + } else { + openFileDialog(GUI_FILE_WAVE_SAVE_ALL); + } + break; case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); @@ -1056,6 +1069,13 @@ void FurnaceGUI::doAction(int what) { displayInsTypeListMakeInsSample=-2; break; } + case GUI_ACTION_SAMPLE_LIST_SAVE_ALL: + if (e->song.sample.empty()) { + showError(_("this song doesn't have any samples.")); + } else { + openFileDialog(GUI_FILE_SAMPLE_SAVE_ALL); + } + break; case GUI_ACTION_SAMPLE_SELECT: if (curSample<0 || curSample>=(int)e->song.sample.size()) break; diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 557c28d87..e07e9a91d 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -13,12 +13,13 @@ #ifdef USE_NFD struct NFDState { - bool isSave, allowMultiple; + unsigned char isSave; + bool allowMultiple; String header; std::vector filter; String path; FileDialogSelectCallback clickCallback; - NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc, bool multi): + NFDState(unsigned char save, String h, std::vector filt, String pa, FileDialogSelectCallback cc, bool multi): isSave(save), allowMultiple(multi), header(h), @@ -36,7 +37,9 @@ void _nfdThread(const NFDState state, std::atomic* ok, std::vector result->clear(); - if (state.isSave) { + if (state.isSave==2) { + ret=NFD_PickFolder(state.path.c_str(),&out); + } else if (state.isSave==1) { ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); } else { if (state.allowMultiple) { @@ -112,7 +115,7 @@ void FurnaceGUIFileDialog::convertFilterList(std::vector& filter) { bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale, FileDialogSelectCallback clickCallback, bool allowMultiple, String hint) { if (opened) return false; - saving=false; + dialogType=0; curPath=path; // strip excess directory separators @@ -128,9 +131,9 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, S #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); + _nfdThread(NFDState(0,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #else - dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); + dialogO=new std::thread(_nfdThread,NFDState(0,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #endif #elif defined(ANDROID) hasError=false; @@ -190,7 +193,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, S ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->mobileMode=mobileUI; ImGuiFileDialog::Instance()->homePath=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,hint,allowMultiple?999:1,nullptr,0,clickCallback); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,filter.empty()?NULL:noSysFilter,path,hint,allowMultiple?999:1,nullptr,0,clickCallback); } opened=true; return true; @@ -205,7 +208,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, S } #endif - saving=true; + dialogType=1; curPath=path; // strip excess directory separators @@ -220,9 +223,9 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, S #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); + _nfdThread(NFDState(1,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #else - dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); + dialogS=new std::thread(_nfdThread,NFDState(1,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #endif #elif defined(ANDROID) hasError=false; @@ -282,6 +285,54 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, S return true; } +bool FurnaceGUIFileDialog::openSelectDir(String header, String path, double dpiScale, String hint) { + if (opened) return false; + dialogType=2; + curPath=path; + + // strip excess directory separators + while (!curPath.empty()) { + if (curPath[curPath.size()-1]!=DIR_SEPARATOR) break; + curPath.erase(curPath.size()-1); + } + curPath+=DIR_SEPARATOR; + + logD("opening select dir dialog with curPath %s",curPath.c_str()); + if (sysDialog) { + curPath+=hint; +#ifdef USE_NFD + dialogOK=false; +#ifdef NFD_NON_THREADED + _nfdThread(NFDState(2,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); +#else + dialogF=new std::thread(_nfdThread,NFDState(2,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); +#endif +#elif defined(ANDROID) + hasError=true; + return false; +#else + dialogF=new pfd::select_folder(header,path); + hasError=!pfd::settings::available(); +#endif + } else { + hasError=false; + +#ifdef ANDROID + if (!SDL_AndroidRequestPermission("android.permission.READ_EXTERNAL_STORAGE")) { + return false; + } +#endif + + ImGuiFileDialog::Instance()->singleClickSel=mobileUI; + ImGuiFileDialog::Instance()->DpiScale=dpiScale; + ImGuiFileDialog::Instance()->mobileMode=mobileUI; + ImGuiFileDialog::Instance()->homePath=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,NULL,path,hint,1,nullptr,0); + } + opened=true; + return true; +} + bool FurnaceGUIFileDialog::accepted() { if (sysDialog) { return (!fileName.empty()); @@ -292,7 +343,17 @@ bool FurnaceGUIFileDialog::accepted() { void FurnaceGUIFileDialog::close() { if (sysDialog) { - if (saving) { + if (dialogType==2) { + if (dialogF!=NULL) { +#ifdef USE_NFD + dialogF->join(); +#endif +#ifndef ANDROID + delete dialogF; +#endif + dialogF=NULL; + } + } else if (dialogType==1) { if (dialogS!=NULL) { #ifdef USE_NFD dialogS->join(); @@ -302,7 +363,7 @@ void FurnaceGUIFileDialog::close() { #endif dialogS=NULL; } - } else { + } else if (dialogType==0) { if (dialogO!=NULL) { #ifdef USE_NFD dialogO->join(); @@ -312,6 +373,8 @@ void FurnaceGUIFileDialog::close() { #endif dialogO=NULL; } + } else { + logE("what..."); } #ifdef USE_NFD dialogOK=false; @@ -343,7 +406,18 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { // TODO: detect when file picker is closed return false; #else - if (saving) { + if (dialogType==2) { + if (dialogF!=NULL) { + if (dialogF->ready(0)) { + fileName.clear(); + fileName.push_back(dialogF->result()); + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + logD("returning %s",fileName[0]); + return true; + } + } + } else if (dialogType==1) { if (dialogS!=NULL) { if (dialogS->ready(0)) { fileName.clear(); @@ -354,7 +428,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { return true; } } - } else { + } else if (dialogType==0) { if (dialogO!=NULL) { if (dialogO->ready(0)) { if (dialogO->result().empty()) { @@ -375,6 +449,8 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { return true; } } + } else { + logE("what!"); } return false; #endif @@ -410,7 +486,7 @@ std::vector& FurnaceGUIFileDialog::getFileName() { return fileName; } else { fileName.clear(); - if (saving) { + if (dialogType==1) { fileName.push_back(ImGuiFileDialog::Instance()->GetFilePathName()); } else { for (auto& i: ImGuiFileDialog::Instance()->GetSelection()) { diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index d1584ce85..a8361b309 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -21,6 +21,7 @@ namespace pfd { class open_file; class save_file; + class select_folder; } #endif @@ -29,7 +30,7 @@ typedef std::function FileDialogSelectCallback; class FurnaceGUIFileDialog { bool sysDialog; bool opened; - bool saving; + unsigned char dialogType; bool hasError; char noSysFilter[4096]; String curPath; @@ -37,15 +38,18 @@ class FurnaceGUIFileDialog { #ifdef USE_NFD std::thread* dialogO; std::thread* dialogS; + std::thread* dialogF; std::atomic dialogOK; std::vector nfdResult; #elif defined(ANDROID) JNIEnv* jniEnv; void* dialogO; void* dialogS; + void* dialogF; #else pfd::open_file* dialogO; pfd::save_file* dialogS; + pfd::select_folder* dialogF; #endif void convertFilterList(std::vector& filter); @@ -53,6 +57,7 @@ class FurnaceGUIFileDialog { bool mobileUI; bool openLoad(String header, std::vector filter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL, bool allowMultiple=false, String hint=""); bool openSave(String header, std::vector filter, String path, double dpiScale, String hint=""); + bool openSelectDir(String header, String path, double dpiScale, String hint=""); bool accepted(); void close(); bool render(const ImVec2& min, const ImVec2& max); @@ -63,7 +68,7 @@ class FurnaceGUIFileDialog { explicit FurnaceGUIFileDialog(bool system): sysDialog(system), opened(false), - saving(false), + dialogType(0), hasError(false), #ifdef ANDROID jniEnv(NULL), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 156e26eab..2e9284967 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1794,6 +1794,14 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { (settings.autoFillSave)?e->getIns(curIns)->name:"" ); break; + case GUI_FILE_INS_SAVE_ALL: + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + hasOpened=fileDialog->openSelectDir( + _("Save All Instruments"), + workingDirIns, + dpiScale + ); + break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN_REPLACE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); @@ -1834,6 +1842,14 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_WAVE_SAVE_ALL: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSelectDir( + _("Save All Wavetables"), + workingDirWave, + dpiScale + ); + break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_OPEN_REPLACE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); @@ -1876,6 +1892,14 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { (settings.autoFillSave)?e->getSample(curSample)->name:"" ); break; + case GUI_FILE_SAMPLE_SAVE_ALL: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openSelectDir( + _("Save All Samples"), + workingDirSample, + dpiScale + ); + break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( @@ -4909,6 +4933,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: case GUI_FILE_INS_SAVE_DMP: + case GUI_FILE_INS_SAVE_ALL: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: @@ -4916,6 +4941,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_WAVE_SAVE: case GUI_FILE_WAVE_SAVE_DMW: case GUI_FILE_WAVE_SAVE_RAW: + case GUI_FILE_WAVE_SAVE_ALL: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: @@ -4924,6 +4950,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: case GUI_FILE_SAMPLE_SAVE: case GUI_FILE_SAMPLE_SAVE_RAW: + case GUI_FILE_SAMPLE_SAVE_ALL: workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_AUDIO_ONE: @@ -5132,6 +5159,11 @@ bool FurnaceGUI::loop() { } } break; + case GUI_FILE_INS_SAVE_ALL: + case GUI_FILE_WAVE_SAVE_ALL: + case GUI_FILE_SAMPLE_SAVE_ALL: + showError("Placeholder."); + break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { if (e->song.wave[curWave]->save(copyOfName.c_str())) { @@ -5162,7 +5194,7 @@ bool FurnaceGUI::loop() { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); - } else { + } else {; showError(e->getLastError()); } } else { diff --git a/src/gui/gui.h b/src/gui/gui.h index 11d7a37f9..515d77fc8 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -580,17 +580,20 @@ enum FurnaceGUIFileDialogs { GUI_FILE_INS_OPEN_REPLACE, GUI_FILE_INS_SAVE, GUI_FILE_INS_SAVE_DMP, + GUI_FILE_INS_SAVE_ALL, GUI_FILE_WAVE_OPEN, GUI_FILE_WAVE_OPEN_REPLACE, GUI_FILE_WAVE_SAVE, GUI_FILE_WAVE_SAVE_DMW, GUI_FILE_WAVE_SAVE_RAW, + GUI_FILE_WAVE_SAVE_ALL, GUI_FILE_SAMPLE_OPEN, GUI_FILE_SAMPLE_OPEN_RAW, GUI_FILE_SAMPLE_OPEN_REPLACE, GUI_FILE_SAMPLE_OPEN_REPLACE_RAW, GUI_FILE_SAMPLE_SAVE, GUI_FILE_SAMPLE_SAVE_RAW, + GUI_FILE_SAMPLE_SAVE_ALL, GUI_FILE_EXPORT_AUDIO_ONE, GUI_FILE_EXPORT_AUDIO_PER_SYS, GUI_FILE_EXPORT_AUDIO_PER_CHANNEL, @@ -831,6 +834,7 @@ enum FurnaceGUIActions { GUI_ACTION_INS_LIST_UP, GUI_ACTION_INS_LIST_DOWN, GUI_ACTION_INS_LIST_DIR_VIEW, + GUI_ACTION_INS_LIST_SAVE_ALL, GUI_ACTION_INS_LIST_MAX, GUI_ACTION_WAVE_LIST_MIN, @@ -848,6 +852,7 @@ enum FurnaceGUIActions { GUI_ACTION_WAVE_LIST_UP, GUI_ACTION_WAVE_LIST_DOWN, GUI_ACTION_WAVE_LIST_DIR_VIEW, + GUI_ACTION_WAVE_LIST_SAVE_ALL, GUI_ACTION_WAVE_LIST_MAX, GUI_ACTION_SAMPLE_LIST_MIN, @@ -869,6 +874,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW, GUI_ACTION_SAMPLE_LIST_DIR_VIEW, GUI_ACTION_SAMPLE_LIST_MAKE_MAP, + GUI_ACTION_SAMPLE_LIST_SAVE_ALL, GUI_ACTION_SAMPLE_LIST_MAX, GUI_ACTION_SAMPLE_MIN, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 2595827b0..09d0fefbb 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -703,6 +703,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_UP", _N("Instrument cursor up"), SDLK_UP), D("INS_LIST_DOWN", _N("Instrument cursor down"), SDLK_DOWN), D("INS_LIST_DIR_VIEW", _N("Instruments: toggle folders/standard view"), FURKMOD_CMD|SDLK_v), + D("INS_LIST_SAVE_ALL", _N("Save all instruments"), 0), D("INS_LIST_MAX", "", NOT_AN_ACTION), D("WAVE_LIST_MIN", _N("---Wavetable list"), NOT_AN_ACTION), @@ -720,6 +721,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_UP", _N("Wavetable cursor up"), SDLK_UP), D("WAVE_LIST_DOWN", _N("Wavetable cursor down"), SDLK_DOWN), D("WAVE_LIST_DIR_VIEW", _N("Wavetables: toggle folders/standard view"), FURKMOD_CMD|SDLK_v), + D("WAVE_LIST_SAVE_ALL", _N("Save all wavetables"), 0), D("WAVE_LIST_MAX", "", NOT_AN_ACTION), D("SAMPLE_LIST_MIN", _N("---Sample list"), NOT_AN_ACTION), @@ -741,6 +743,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_LIST_STOP_PREVIEW", _N("Stop sample preview"), 0), D("SAMPLE_LIST_DIR_VIEW", _N("Samples: Toggle folders/standard view"), FURKMOD_CMD|SDLK_v), D("SAMPLE_LIST_MAKE_MAP", _N("Samples: Make me a drum kit"), 0), + D("SAMPLE_LIST_SAVE_ALL", _N("Save all samples"), 0), D("SAMPLE_LIST_MAX", "", NOT_AN_ACTION), D("SAMPLE_MIN", _N("---Sample editor"), NOT_AN_ACTION),