From d1b7073ff957f719bb9356977fe7dffbe8d01d8d Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 6 Aug 2021 13:21:16 +0200 Subject: [PATCH] Reimplementation of Dialogs Cleanup code to integrate multithreading process for dialogs into the DialogToolkit (avoid poluting UserInterfaceManager and improves reliability) --- DialogToolkit.cpp | 239 +++++++++++++++++++++++++++++++++++---- DialogToolkit.h | 77 +++++++++++-- Mixer.cpp | 4 - Settings.cpp | 43 +++++-- Settings.h | 2 +- UserInterfaceManager.cpp | 234 ++++++++++++++------------------------ UserInterfaceManager.h | 6 + 7 files changed, 409 insertions(+), 196 deletions(-) diff --git a/DialogToolkit.cpp b/DialogToolkit.cpp index a6df73d..5a1864b 100644 --- a/DialogToolkit.cpp +++ b/DialogToolkit.cpp @@ -6,16 +6,17 @@ // because of 'zenity' access rights nightmare :( // Thus this re-implementation of native GTK+ dialogs for linux +#include "defines.h" +#include "Settings.h" +#include "SystemToolkit.h" +#include "DialogToolkit.h" + #if defined(LINUX) #define USE_TINYFILEDIALOG 0 #else #define USE_TINYFILEDIALOG 1 #endif -#include "defines.h" -#include "SystemToolkit.h" -#include "DialogToolkit.h" - #if USE_TINYFILEDIALOG #include "tinyfiledialogs.h" #else @@ -61,7 +62,140 @@ bool gtk_init() } #endif -std::string DialogToolkit::saveSessionFileDialog(const std::string &path) + +// globals +const std::chrono::milliseconds timeout = std::chrono::milliseconds(4); +bool DialogToolkit::FileDialog::pending = false; + +// +// FileDialog common functions +// +DialogToolkit::FileDialog::FileDialog(const std::string &name) : id_(name) +{ + if ( Settings::application.dialogRecentFolder.count(id_) == 0 ) + Settings::application.dialogRecentFolder[id_] = SystemToolkit::home_path(); +} + +bool DialogToolkit::FileDialog::closed() +{ + if ( !promises_.empty() ) { + // check that file dialog thread finished + if (promises_.back().wait_for(timeout) == std::future_status::ready ) { + // get the filename from this file dialog + std::string string = promises_.back().get(); + if (!string.empty()) { + // selected a filename + path_ = string; + // save path location + Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(string); + } + // done with this file dialog + promises_.pop_back(); + pending = false; + return true; + } + } + return false; +} + +// +// type specific implementations +// +std::string openImageFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::OpenImageDialog::open() +{ + if ( !pending && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, openImageFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +std::string openSessionFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::OpenSessionDialog::open() +{ + if ( !pending && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, openSessionFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +std::string openMediaFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::OpenMediaDialog::open() +{ + if ( !pending && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, openMediaFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +std::string saveSessionFileDialog(const std::string &label, const std::string &path); +void DialogToolkit::SaveSessionDialog::open() +{ + if ( !pending && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, saveSessionFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +std::string openFolderDialog(const std::string &label, const std::string &path); +void DialogToolkit::OpenFolderDialog::open() +{ + if ( !pending && promises_.empty() ) { + promises_.emplace_back( std::async(std::launch::async, openFolderDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +std::list selectImagesFileDialog(const std::string &label,const std::string &path); +void DialogToolkit::MultipleImagesDialog::open() +{ + if ( !pending && promisedlist_.empty() ) { + promisedlist_.emplace_back( std::async(std::launch::async, selectImagesFileDialog, id_, + Settings::application.dialogRecentFolder[id_]) ); + pending = true; + } +} + +bool DialogToolkit::MultipleImagesDialog::closed() +{ + if ( !promisedlist_.empty() ) { + // check that file dialog thread finished + if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) { + // get the filename from this file dialog + std::list list = promisedlist_.back().get(); + if (!list.empty()) { + // selected a filenames + pathlist_ = list; + path_ = list.front(); + // save path location + Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_); + } + else { + pathlist_.clear(); + path_.clear(); + } + // done with this file dialog + promisedlist_.pop_back(); + pending = false; + return true; + } + } + return false; +} + + +// +// +// CALLBACKS +// +// + +std::string saveSessionFileDialog(const std::string &label, const std::string &path) { std::string filename = ""; char const * save_pattern[1] = { "*.mix" }; @@ -69,17 +203,17 @@ std::string DialogToolkit::saveSessionFileDialog(const std::string &path) #if USE_TINYFILEDIALOG char const * save_file_name; - save_file_name = tinyfd_saveFileDialog( "Save a session file", path.c_str(), 1, save_pattern, "vimix session"); + save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session"); if (save_file_name) filename = std::string(save_file_name); #else if (!gtk_init()) { - ErrorDialog("Could not initialize GTK+ for dialog"); + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Save Session File", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL ); @@ -122,7 +256,7 @@ std::string DialogToolkit::saveSessionFileDialog(const std::string &path) } -std::string DialogToolkit::openSessionFileDialog(const std::string &path) +std::string openSessionFileDialog(const std::string &label, const std::string &path) { std::string filename = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); @@ -130,17 +264,17 @@ std::string DialogToolkit::openSessionFileDialog(const std::string &path) #if USE_TINYFILEDIALOG char const * open_file_name; - open_file_name = tinyfd_openFileDialog( "Import a file", startpath.c_str(), 1, open_pattern, "vimix session", 0); + open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0); if (open_file_name) filename = std::string(open_file_name); #else if (!gtk_init()) { - ErrorDialog("Could not initialize GTK+ for dialog"); + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Session File", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); @@ -178,7 +312,7 @@ std::string DialogToolkit::openSessionFileDialog(const std::string &path) } -std::string DialogToolkit::openMediaFileDialog(const std::string &path) +std::string openMediaFileDialog(const std::string &label, const std::string &path) { std::string filename = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); @@ -190,18 +324,18 @@ std::string DialogToolkit::openMediaFileDialog(const std::string &path) "*.gif", "*.tif", "*.svg" }; #if USE_TINYFILEDIALOG char const * open_file_name; - open_file_name = tinyfd_openFileDialog( "Open Media File", startpath.c_str(), 18, open_pattern, "All supported formats", 0); + open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 18, open_pattern, "All supported formats", 0); if (open_file_name) filename = std::string(open_file_name); #else if (!gtk_init()) { - ErrorDialog("Could not initialize GTK+ for dialog"); + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Media File", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); @@ -238,24 +372,81 @@ std::string DialogToolkit::openMediaFileDialog(const std::string &path) return filename; } -std::string DialogToolkit::openFolderDialog(const std::string &path) + +std::string openImageFileDialog(const std::string &label, const std::string &path) +{ + std::string filename = ""; + std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); + char const * open_pattern[2] = { "*.jpg", "*.png"}; +#if USE_TINYFILEDIALOG + char const * open_file_name; + open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 2, open_pattern, "Image JPG or PNG", 0); + + if (open_file_name) + filename = std::string(open_file_name); +#else + + if (!gtk_init()) { + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); + return filename; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, NULL ); + + // set file filters + add_filter_file_dialog(dialog, 2, open_pattern, "Image JPG or PNG"); + add_filter_any_file_dialog(dialog); + + // Set the default path + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); + + // ensure front and centered + gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); + if (window_x > 0 && window_y > 0) + gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); + + // display and get filename + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + + char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + if (open_file_name) + filename = std::string(open_file_name); + g_free( open_file_name ); + } + + // remember position + gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); + + // done + gtk_widget_destroy(dialog); + wait_for_event(); +#endif + + return filename; +} + + +std::string openFolderDialog(const std::string &label, const std::string &path) { std::string foldername = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); #if USE_TINYFILEDIALOG char const * open_folder_name; - open_folder_name = tinyfd_selectFolderDialog("Select folder", startpath.c_str()); + open_folder_name = tinyfd_selectFolderDialog(label.c_str(), startpath.c_str()); if (open_folder_name) foldername = std::string(open_folder_name); #else if (!gtk_init()) { - ErrorDialog("Could not initialize GTK+ for dialog"); + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); return foldername; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select folder", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "_Cancel", GTK_RESPONSE_CANCEL, "_Select", GTK_RESPONSE_ACCEPT, NULL ); @@ -290,7 +481,7 @@ std::string DialogToolkit::openFolderDialog(const std::string &path) } -std::list DialogToolkit::selectImagesFileDialog(const std::string &path) +std::list selectImagesFileDialog(const std::string &label,const std::string &path) { std::list files; @@ -299,7 +490,7 @@ std::list DialogToolkit::selectImagesFileDialog(const std::string & #if USE_TINYFILEDIALOG char const * open_file_names; - open_file_names = tinyfd_openFileDialog( "Select images", startpath.c_str(), 6, open_pattern, "Images", 1); + open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 6, open_pattern, "Images", 1); if (open_file_names) { @@ -325,11 +516,11 @@ std::list DialogToolkit::selectImagesFileDialog(const std::string & #else if (!gtk_init()) { - ErrorDialog("Could not initialize GTK+ for dialog"); + DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog"); return files; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select images", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); diff --git a/DialogToolkit.h b/DialogToolkit.h index d486d1c..00bc59f 100644 --- a/DialogToolkit.h +++ b/DialogToolkit.h @@ -3,23 +3,80 @@ #include #include +#include +#include +#include namespace DialogToolkit { -std::string saveSessionFileDialog(const std::string &path); - -std::string openSessionFileDialog(const std::string &path); - -std::string openMediaFileDialog(const std::string &path); - -std::string openFolderDialog(const std::string &path); - -std::list selectImagesFileDialog(const std::string &path); - void ErrorDialog(const char* message); +class FileDialog +{ +protected: + std::string id_; + std::string directory_; + std::string path_; + std::vector< std::future >promises_; + +public: + FileDialog(const std::string &name); + + virtual void open() = 0; + virtual bool closed(); + inline std::string path() const { return path_; } + + static bool pending; +}; + +class OpenImageDialog : public FileDialog +{ +public: + OpenImageDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class OpenSessionDialog : public FileDialog +{ +public: + OpenSessionDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class OpenMediaDialog : public FileDialog +{ +public: + OpenMediaDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class SaveSessionDialog : public FileDialog +{ +public: + SaveSessionDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class OpenFolderDialog : public FileDialog +{ +public: + OpenFolderDialog(const std::string &name) : FileDialog(name) {} + void open(); +}; + +class MultipleImagesDialog : public FileDialog +{ + std::list pathlist_; + std::vector< std::future< std::list > > promisedlist_; +public: + MultipleImagesDialog(const std::string &name) : FileDialog(name) {} + void open(); + bool closed() override; + inline std::list images() const { return pathlist_; } +}; + } diff --git a/Mixer.cpp b/Mixer.cpp index e93a218..6547861 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -238,7 +238,6 @@ Source * Mixer::createSourceFile(const std::string &path) // remember in recent media Settings::application.recentImport.push(path); - Settings::application.recentImport.path = SystemToolkit::path_filename(path); // propose a new name based on uri s->setName(SystemToolkit::base_filename(path)); @@ -269,9 +268,6 @@ Source * Mixer::createSourceMultifile(const std::list &list_files, mfs->setSequence(sequence, fps); s = mfs; - // remember in recent media - Settings::application.recentImport.path = SystemToolkit::path_filename(list_files.front()); - // propose a new name s->setName( SystemToolkit::base_filename( BaseToolkit::common_prefix(list_files) ) ); } diff --git a/Settings.cpp b/Settings.cpp index d6830b1..141adfb 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -173,13 +173,14 @@ void Settings::Save() { XMLElement *recent = xmlDoc.NewElement( "Recent" ); + // recent session filenames XMLElement *recentsession = xmlDoc.NewElement( "Session" ); recentsession->SetAttribute("path", application.recentSessions.path.c_str()); recentsession->SetAttribute("autoload", application.recentSessions.load_at_start); recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit); recentsession->SetAttribute("valid", application.recentSessions.front_is_valid); - for(auto it = application.recentSessions.filenames.begin(); - it != application.recentSessions.filenames.end(); ++it) { + for(auto it = application.recentSessions.filenames.cbegin(); + it != application.recentSessions.filenames.cend(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -187,9 +188,10 @@ void Settings::Save() }; recent->InsertEndChild(recentsession); + // recent session folders XMLElement *recentfolder = xmlDoc.NewElement( "Folder" ); - for(auto it = application.recentFolders.filenames.begin(); - it != application.recentFolders.filenames.end(); ++it) { + for(auto it = application.recentFolders.filenames.cbegin(); + it != application.recentFolders.filenames.cend(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -197,10 +199,11 @@ void Settings::Save() }; recent->InsertEndChild(recentfolder); + // recent media uri XMLElement *recentmedia = xmlDoc.NewElement( "Import" ); recentmedia->SetAttribute("path", application.recentImport.path.c_str()); - for(auto it = application.recentImport.filenames.begin(); - it != application.recentImport.filenames.end(); ++it) { + for(auto it = application.recentImport.filenames.cbegin(); + it != application.recentImport.filenames.cend(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -208,10 +211,21 @@ void Settings::Save() } recent->InsertEndChild(recentmedia); + // recent dialog path + XMLElement *recentdialogpath = xmlDoc.NewElement( "Dialog" ); + for(auto it = application.dialogRecentFolder.cbegin(); + it != application.dialogRecentFolder.cend(); ++it) { + XMLElement *pathNode = xmlDoc.NewElement("path"); + pathNode->SetAttribute("label", (*it).first.c_str() ); + XMLText *text = xmlDoc.NewText( (*it).second.c_str() ); + pathNode->InsertEndChild( text ); + recentdialogpath->InsertFirstChild(pathNode); + } + recent->InsertEndChild(recentdialogpath); + pRoot->InsertEndChild(recent); } - // First save : create filename if (settingsFilename.empty()) settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS); @@ -459,6 +473,21 @@ void Settings::Load() application.recentImport.push( std::string (p) ); } } + // recent dialog path + XMLElement * pDialog = pElement->FirstChildElement("Dialog"); + if (pDialog) + { + application.dialogRecentFolder.clear(); + XMLElement* path = pDialog->FirstChildElement("path"); + for( ; path ; path = path->NextSiblingElement()) + { + const char *l = path->Attribute("label"); + const char *p = path->GetText(); + if (l && p) + application.dialogRecentFolder[ std::string(l)] = std::string (p); + } + } + } } diff --git a/Settings.h b/Settings.h index 6a51c9b..dcc133e 100644 --- a/Settings.h +++ b/Settings.h @@ -70,7 +70,6 @@ struct ViewConfig }; -#define RECORD_MAX_TIMEOUT 301000 struct RecordConfig { @@ -211,6 +210,7 @@ struct Application History recentSessions; History recentFolders; History recentImport; + std::map< std::string, std::string > dialogRecentFolder; Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) { scale = 1.f; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 4fb91d3..dcc60de 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -81,17 +81,8 @@ void ShowSandbox(bool* p_open); void SetMouseCursor(ImVec2 mousepos, View::Cursor c = View::Cursor()); void SetNextWindowVisible(ImVec2 pos, ImVec2 size, float margin = 180.f); -// static objects for multithreaded file dialog +// globals const std::chrono::milliseconds timeout = std::chrono::milliseconds(4); -static std::atomic fileDialogPending_ = false; - -std::vector< std::future > saveSessionFileDialogs; -std::vector< std::future > openSessionFileDialogs; -std::vector< std::future > importSessionFileDialogs; -std::vector< std::future > fileImportFileDialogs; -std::vector< std::future > recentFolderFileDialogs; -std::vector< std::future > recordFolderFileDialogs; - std::vector< std::future > _video_recorders; FrameGrabber *delayTrigger(FrameGrabber *g, std::chrono::milliseconds delay) { std::this_thread::sleep_for (delay); @@ -119,6 +110,10 @@ UserInterface::UserInterface() #if defined(LINUX) webcam_emulator_ = nullptr; #endif + + sessionopendialog = nullptr; + sessionimportdialog = nullptr; + sessionsavedialog = nullptr; } bool UserInterface::Init() @@ -181,6 +176,11 @@ bool UserInterface::Init() std::sprintf(inifilepath, "%s", inifile.c_str() ); io.IniFilename = inifilepath; + // init dialogs + sessionopendialog = new DialogToolkit::OpenSessionDialog("Open Session"); + sessionsavedialog = new DialogToolkit::SaveSessionDialog("Save Session"); + sessionimportdialog = new DialogToolkit::OpenSessionDialog("Import Session") ; + return true; } @@ -664,21 +664,18 @@ void UserInterface::handleMouse() void UserInterface::selectSaveFilename() { - // launch file dialog to select a session filename to save - if ( saveSessionFileDialogs.empty()) { - saveSessionFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::saveSessionFileDialog, Settings::application.recentSessions.path) ); - fileDialogPending_ = true; - } + if (sessionsavedialog) + sessionsavedialog->open(); + navigator.hidePannel(); } void UserInterface::selectOpenFilename() { // launch file dialog to select a session filename to open - if ( openSessionFileDialogs.empty()) { - openSessionFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openSessionFileDialog, Settings::application.recentSessions.path) ); - fileDialogPending_ = true; - } + if (sessionopendialog) + sessionopendialog->open(); + navigator.hidePannel(); } @@ -694,54 +691,18 @@ void UserInterface::NewFrame() handleMouse(); handleScreenshot(); - // handle FileDialog + // handle FileDialogs + if (sessionopendialog && sessionopendialog->closed() && !sessionopendialog->path().empty()) + Mixer::manager().open(sessionopendialog->path()); - // if a file dialog future was registered - if ( !openSessionFileDialogs.empty() ) { - // check that file dialog thread finished - if (openSessionFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string open_filename = openSessionFileDialogs.back().get(); - if (!open_filename.empty()) { - Mixer::manager().open(open_filename); - Settings::application.recentSessions.path = SystemToolkit::path_filename(open_filename); - } - // done with this file dialog - openSessionFileDialogs.pop_back(); - fileDialogPending_ = false; - } - } - if ( !importSessionFileDialogs.empty() ) { - // check that file dialog thread finished - if (importSessionFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string open_filename = importSessionFileDialogs.back().get(); - if (!open_filename.empty()) { - Mixer::manager().import(open_filename); - Settings::application.recentSessions.path = SystemToolkit::path_filename(open_filename); - } - // done with this file dialog - importSessionFileDialogs.pop_back(); - fileDialogPending_ = false; - } - } - if ( !saveSessionFileDialogs.empty() ) { - // check that file dialog thread finished - if (saveSessionFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string save_filename = saveSessionFileDialogs.back().get(); - if (!save_filename.empty()) { - Mixer::manager().saveas(save_filename); - Settings::application.recentSessions.path = SystemToolkit::path_filename(save_filename); - } - // done with this file dialog - saveSessionFileDialogs.pop_back(); - fileDialogPending_ = false; - } - } + if (sessionimportdialog && sessionimportdialog->closed() && !sessionimportdialog->path().empty()) + Mixer::manager().import(sessionimportdialog->path()); + + if (sessionsavedialog && sessionsavedialog->closed() && !sessionsavedialog->path().empty()) + Mixer::manager().saveas(sessionsavedialog->path()); // overlay to ensure file dialog is modal - if (fileDialogPending_){ + if (DialogToolkit::FileDialog::pending){ ImGui::OpenPopup("Busy"); if (ImGui::BeginPopupModal("Busy", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { @@ -898,10 +859,11 @@ void UserInterface::showMenuFile() if (ImGui::MenuItem( ICON_FA_FILE_EXPORT " Import")) { // launch file dialog to open a session file - if ( importSessionFileDialogs.empty()) { - importSessionFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openSessionFileDialog, Settings::application.recentSessions.path) ); - fileDialogPending_ = true; - } + sessionimportdialog->open(); +// if ( importSessionFileDialogs.empty()) { +// importSessionFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openSessionFileDialog, Settings::application.recentSessions.path) ); +// fileDialogPending_ = true; +// } navigator.hidePannel(); } if (ImGui::MenuItem( ICON_FA_FILE_DOWNLOAD " Save", CTRL_MOD "S")) { @@ -1029,7 +991,11 @@ void UserInterface::RenderPreview() bool openInitializeSystemLoopback = false; #endif - struct CustomConstraints // Helper functions for aspect-ratio constraints + // recording location + static DialogToolkit::OpenFolderDialog recordFolderDialog("Recording Location"); + + // Helper functions for aspect-ratio constraints + struct CustomConstraints { static void AspectRatio(ImGuiSizeCallbackData* data) { float *ar = (float*) data->UserData; @@ -1063,16 +1029,9 @@ void UserInterface::RenderPreview() preview_window_size = ImGui::GetWindowSize(); // return from thread for folder openning - if ( !recordFolderFileDialogs.empty() ) { - // check that file dialog thread finished - if (recordFolderFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the folder from this file dialog - Settings::application.record.path = recordFolderFileDialogs.back().get(); - // done with this file dialog - recordFolderFileDialogs.pop_back(); - fileDialogPending_ = false; - } - } + if (recordFolderDialog.closed() && !recordFolderDialog.path().empty()) + // get the folder from this file dialog + Settings::application.record.path = recordFolderDialog.path(); // menu (no title bar) if (ImGui::BeginMenuBar()) @@ -1169,12 +1128,8 @@ void UserInterface::RenderPreview() int selected_path = 0; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::Combo("Path", &selected_path, name_path, 4); - if (selected_path > 2) { - if (recordFolderFileDialogs.empty()) { - recordFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openFolderDialog, Settings::application.record.path) ); - fileDialogPending_ = true; - } - } + if (selected_path > 2) + recordFolderDialog.open(); else if (selected_path > 1) Settings::application.record.path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); else if (selected_path > 0) @@ -3709,36 +3664,27 @@ void Navigator::RenderNewPannel() // File Source creation if (Settings::application.source.new_type == 0) { + static DialogToolkit::OpenMediaDialog fileimportdialog("Open Media"); + // clic button to load file - if ( ImGui::Button( ICON_FA_FILE_EXPORT " Open media", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { - // launch async call to file dialog and get its future. - if (fileImportFileDialogs.empty()) { - fileImportFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openMediaFileDialog, Settings::application.recentImport.path) ); - fileDialogPending_ = true; - } - } + if ( ImGui::Button( ICON_FA_FILE_EXPORT " Open media", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) + fileimportdialog.open(); // Indication ImGui::SameLine(); ImGuiToolkit::HelpMarker("Create a source from a file:\n- video (*.mpg, *mov, *.avi, etc.)\n- image (*.jpg, *.png, etc.)\n- vector graphics (*.svg)\n- vimix session (*.mix)\n\n(Equivalent to dropping the file in the workspace)"); - // if a file dialog future was registered - if ( !fileImportFileDialogs.empty() ) { - // check that file dialog thread finished - if (fileImportFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string open_filename = fileImportFileDialogs.back().get(); - // done with this file dialog - fileImportFileDialogs.pop_back(); - fileDialogPending_ = false; - // create a source with this file - if (open_filename.empty()) { - Log::Notify("No file selected."); - } else { - std::string label = BaseToolkit::transliterate( open_filename ); - label = BaseToolkit::trunc_string(label, 35); - new_source_preview_.setSource( Mixer::manager().createSourceFile(open_filename), label); - } + // get media file if dialog finished + if (fileimportdialog.closed()){ + // get the filename from this file dialog + std::string open_filename = fileimportdialog.path(); + // create a source with this file + if (open_filename.empty()) { + Log::Notify("No file selected."); + } else { + std::string label = BaseToolkit::transliterate( open_filename ); + label = BaseToolkit::trunc_string(label, 35); + new_source_preview_.setSource( Mixer::manager().createSourceFile(open_filename), label); } } @@ -3765,15 +3711,12 @@ void Navigator::RenderNewPannel() else if (Settings::application.source.new_type == 1){ bool update_new_source = false; - static std::vector< std::future< std::list > > _selectedImagesFileDialogs; + static DialogToolkit::MultipleImagesDialog _selectImagesDialog("Select Images"); // clic button to load file if ( ImGui::Button( ICON_FA_IMAGES " Open images", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { _selectedFiles.clear(); - if (_selectedImagesFileDialogs.empty()) { - _selectedImagesFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::selectImagesFileDialog, Settings::application.recentImport.path) ); - fileDialogPending_ = true; - } + _selectImagesDialog.open(); } // Indication @@ -3781,20 +3724,12 @@ void Navigator::RenderNewPannel() ImGuiToolkit::HelpMarker("Create a source from a sequence of numbered images."); // return from thread for folder openning - if ( !_selectedImagesFileDialogs.empty() ) { - // check that file dialog thread finished - if (_selectedImagesFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filenames from this file dialog - _selectedFiles = _selectedImagesFileDialogs.back().get(); - if (_selectedFiles.empty()) { - Log::Notify("No file selected."); - } - // done with this file dialog - _selectedImagesFileDialogs.pop_back(); - fileDialogPending_ = false; - // ask to reload the preview - update_new_source = true; - } + if (_selectImagesDialog.closed()) { + _selectedFiles = _selectImagesDialog.images(); + if (_selectedFiles.empty()) + Log::Notify("No file selected."); + // ask to reload the preview + update_new_source = true; } // multiple files selected @@ -3980,6 +3915,7 @@ void Navigator::RenderMainPannelVimix() ImGui::Text("Sessions"); static bool selection_session_mode_changed = true; static int selection_session_mode = 0; + static DialogToolkit::OpenFolderDialog customFolder("Open Folder"); // Show combo box of quick selection modes ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); @@ -4004,32 +3940,17 @@ void Navigator::RenderMainPannelVimix() } } // Option 2 : add a folder - if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ){ - if (recentFolderFileDialogs.empty()) { - recentFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openFolderDialog, Settings::application.recentFolders.path) ); - fileDialogPending_ = true; - } - } + if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ) + customFolder.open(); ImGui::EndCombo(); } // return from thread for folder openning - if ( !recentFolderFileDialogs.empty() ) { - // check that file dialog thread finished - if (recentFolderFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string foldername = recentFolderFileDialogs.back().get(); - if (!foldername.empty()) { - Settings::application.recentFolders.push(foldername); - Settings::application.recentFolders.path.assign(foldername); - selection_session_mode = 1; - selection_session_mode_changed = true; - } - // done with this file dialog - recentFolderFileDialogs.pop_back(); - fileDialogPending_ = false; - - } + if (customFolder.closed() && !customFolder.path().empty()) { + Settings::application.recentFolders.push(customFolder.path()); + Settings::application.recentFolders.path.assign(customFolder.path()); + selection_session_mode = 1; + selection_session_mode_changed = true; } // icon to clear list @@ -4949,6 +4870,19 @@ void ShowSandbox(bool* p_open) ImGui::Text("Testing sandox"); ImGui::Separator(); + ImGui::Text("IMAGE of Font"); + + ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_DEFAULT, 'v'); + ImGui::SameLine(); + ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_BOLD, 'i'); + ImGui::SameLine(); + ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_ITALIC, 'm'); + ImGui::SameLine(); + ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_MONO, 'i'); + ImGui::SameLine(); + ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_LARGE, 'x'); + + ImGui::Separator(); ImGui::Text("Source list"); Session *se = Mixer::manager().session(); for (auto sit = se->begin(); sit != se->end(); ++sit) { diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 92cb0bf..c9ba054 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -12,6 +12,7 @@ #include "SourceList.h" #include "InfoVisitor.h" +#include "DialogToolkit.h" struct ImVec2; class MediaPlayer; @@ -183,6 +184,11 @@ class UserInterface FrameGrabber *webcam_emulator_; #endif + // Dialogs + DialogToolkit::OpenSessionDialog *sessionopendialog; + DialogToolkit::OpenSessionDialog *sessionimportdialog; + DialogToolkit::SaveSessionDialog *sessionsavedialog; + // Private Constructor UserInterface(); UserInterface(UserInterface const& copy) = delete;