From d4b793ceb698a6a83e17c838a7e1724554c75c99 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Wed, 1 Jul 2020 00:16:43 +0200 Subject: [PATCH] Implementation of Main pannel selection of sessions from recent history AND from folder listing. Re-implementation in C++17 style of SystemToolkit. --- Mixer.cpp | 2 +- Settings.cpp | 29 ++++++- Settings.h | 4 + SystemToolkit.cpp | 146 ++++++++++++++++----------------- SystemToolkit.h | 18 +++-- UserInterfaceManager.cpp | 169 ++++++++++++++++++++++++++++++++++----- UserInterfaceManager.h | 2 +- 7 files changed, 264 insertions(+), 106 deletions(-) diff --git a/Mixer.cpp b/Mixer.cpp index 1831164..9f84c80 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -227,7 +227,7 @@ Source * Mixer::createSourceFile(std::string path) // test type of file by extension std::string ext = SystemToolkit::extension_filename(path); - if ( ext == "mix" ) + if ( ext == ".mix" ) { // create a session source SessionSource *ss = new SessionSource(); diff --git a/Settings.cpp b/Settings.cpp index 7d05cd1..471e810 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -56,6 +56,7 @@ void Settings::Save() applicationNode->SetAttribute("scale", application.scale); applicationNode->SetAttribute("accent_color", application.accent_color); applicationNode->SetAttribute("pannel_stick", application.pannel_stick); + applicationNode->SetAttribute("smooth_transition", application.smooth_transition); pRoot->InsertEndChild(applicationNode); // Widgets @@ -122,6 +123,16 @@ void Settings::Save() }; recent->InsertEndChild(recentsession); + XMLElement *recentfolder = xmlDoc.NewElement( "Folder" ); + for(auto it = application.recentFolders.filenames.begin(); + it != application.recentFolders.filenames.end(); it++) { + XMLElement *fileNode = xmlDoc.NewElement("path"); + XMLText *text = xmlDoc.NewText( (*it).c_str() ); + fileNode->InsertEndChild( text ); + recentfolder->InsertFirstChild(fileNode); + }; + recent->InsertEndChild(recentfolder); + XMLElement *recentmedia = xmlDoc.NewElement( "Import" ); recentmedia->SetAttribute("path", application.recentImport.path.c_str()); for(auto it = application.recentImport.filenames.begin(); @@ -139,7 +150,7 @@ void Settings::Save() // First save : create filename if (settingsFilename.empty()) - settingsFilename = SystemToolkit::settings_prepend_path(APP_SETTINGS); + settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS); XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str()); XMLResultError(eResult); @@ -149,7 +160,7 @@ void Settings::Load() { XMLDocument xmlDoc; if (settingsFilename.empty()) - settingsFilename = SystemToolkit::settings_prepend_path(APP_SETTINGS); + settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS); XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str()); // do not warn if non existing file @@ -171,6 +182,7 @@ void Settings::Load() applicationNode->QueryFloatAttribute("scale", &application.scale); applicationNode->QueryIntAttribute("accent_color", &application.accent_color); applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick); + applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition); } // Widgets @@ -272,6 +284,19 @@ void Settings::Load() application.recentSessions.push( std::string (p) ); } } + // recent session filenames + XMLElement * pFolder = pElement->FirstChildElement("Folder"); + if (pFolder) + { + application.recentFolders.filenames.clear(); + XMLElement* path = pFolder->FirstChildElement("path"); + for( ; path ; path = path->NextSiblingElement()) + { + const char *p = path->GetText(); + if (p) + application.recentFolders.push( std::string (p) ); + } + } // recent media uri XMLElement * pImport = pElement->FirstChildElement("Import"); if (pImport) diff --git a/Settings.h b/Settings.h index bc206bf..ca4efbf 100644 --- a/Settings.h +++ b/Settings.h @@ -64,6 +64,7 @@ struct History bool save_on_exit; History() { + path = "Recent Files"; load_at_start = false; save_on_exit = false; } @@ -98,6 +99,7 @@ struct Application float scale; int accent_color; bool pannel_stick; + bool smooth_transition; // Settings of widgets WidgetsConfig widget; @@ -116,12 +118,14 @@ struct Application // recent files histories History recentSessions; + History recentFolders; History recentImport; Application() : name(APP_NAME){ scale = 1.f; accent_color = 0; pannel_stick = false; + smooth_transition = true; current_view = 1; render_view_ar = 3; diff --git a/SystemToolkit.cpp b/SystemToolkit.cpp index 8e4a4ea..51a8d54 100644 --- a/SystemToolkit.cpp +++ b/SystemToolkit.cpp @@ -5,83 +5,75 @@ #include #include #include +#include using namespace std; #ifdef WIN32 #include -#define mkdir(dir, mode) _mkdir(dir) -#include -#define PATH_SEP '\\' #elif defined(LINUX) or defined(APPLE) -#include -#include #include -#include -#define PATH_SEP '/' -#endif - -#ifdef WIN32 -#define PATH_SETTINGS "\\\AppData\\Roaming\\" -#elif defined(APPLE) -#define PATH_SETTINGS "/Library/Application Support/" -#elif defined(LINUX) -#define PATH_SETTINGS "/.config/" #endif #include "defines.h" #include "SystemToolkit.h" - string SystemToolkit::date_time_string() { chrono::system_clock::time_point now = chrono::system_clock::now(); time_t t = chrono::system_clock::to_time_t(now); - std::tm* datetime = std::localtime(&t); + tm* datetime = localtime(&t); auto duration = now.time_since_epoch(); auto millis = chrono::duration_cast(duration).count() % 1000; ostringstream oss; - oss << setw(4) << setfill('0') << std::to_string(datetime->tm_year + 1900); - oss << setw(2) << setfill('0') << std::to_string(datetime->tm_mon + 1); - oss << setw(2) << setfill('0') << std::to_string(datetime->tm_mday ); - oss << setw(2) << setfill('0') << std::to_string(datetime->tm_hour ); - oss << setw(2) << setfill('0') << std::to_string(datetime->tm_min ); - oss << setw(2) << setfill('0') << std::to_string(datetime->tm_sec ); - oss << setw(3) << setfill('0') << std::to_string(millis); + oss << setw(4) << setfill('0') << to_string(datetime->tm_year + 1900); + oss << setw(2) << setfill('0') << to_string(datetime->tm_mon + 1); + oss << setw(2) << setfill('0') << to_string(datetime->tm_mday ); + oss << setw(2) << setfill('0') << to_string(datetime->tm_hour ); + oss << setw(2) << setfill('0') << to_string(datetime->tm_min ); + oss << setw(2) << setfill('0') << to_string(datetime->tm_sec ); + oss << setw(3) << setfill('0') << to_string(millis); // fixed length string (17 chars) YYYYMMDDHHmmssiii return oss.str(); } -std::string SystemToolkit::base_filename(const std::string& filename) +string SystemToolkit::filename(const string& path) { - std::string basefilename = filename.substr(filename.find_last_of(PATH_SEP) + 1); - const size_t period_idx = basefilename.rfind('.'); - if (std::string::npos != period_idx) - { - basefilename.erase(period_idx); + return filesystem::path(path).filename(); +} + +string SystemToolkit::base_filename(const string& path) +{ + return filesystem::path(path).stem(); +} + +string SystemToolkit::path_filename(const string& path) +{ + return filesystem::path(path).parent_path(); +} + +string SystemToolkit::trunc_filename(const string& path, int lenght) +{ + string trunc = path; + int l = path.size(); + if ( l > lenght ) { + trunc = string("...") + path.substr( l - lenght + 3 ); } - return basefilename; + return trunc; } -std::string SystemToolkit::path_filename(const std::string& filename) +string SystemToolkit::extension_filename(const string& filename) { - std::string path = filename.substr(0, filename.find_last_of(PATH_SEP) + 1); - return path; + return filesystem::path(filename).extension(); } -std::string SystemToolkit::extension_filename(const std::string& filename) +string SystemToolkit::home_path() { - std::string ext = filename.substr(filename.find_last_of(".") + 1); - return ext; -} - -std::string SystemToolkit::home_path() -{ - // 1. find home + // find home char *mHomePath; // try the system user info struct passwd* pwd = getpwuid(getuid()); @@ -93,61 +85,65 @@ std::string SystemToolkit::home_path() mHomePath = getenv("HOME"); } - return string(mHomePath) + PATH_SEP; + return filesystem::path(mHomePath).string(); } string SystemToolkit::settings_path() { - string home(home_path()); + filesystem::path settings(home_path()); - // 2. try to access user settings folder - string settingspath = home + PATH_SETTINGS; - if (SystemToolkit::file_exists(settingspath)) { - // good, we have a place to put the settings file - // settings should be in 'vmix' subfolder - settingspath += APP_NAME; + // platform dependent location of settings +#ifdef WIN32 + settings /= "AppData"; + settings /= "Roaming"; +#elif defined(APPLE) + settings /= "Library"; + settings /= "Application Support"; +#elif defined(LINUX) + settings /= ".config"; +#endif - // 3. create the vmix subfolder in settings folder if not existing already - if ( !SystemToolkit::file_exists(settingspath)) { - if ( !SystemToolkit::create_directory(settingspath) ) - // fallback to home if settings path cannot be created - settingspath = home; - } + // append folder location for vimix settings + settings /= APP_NAME; - return settingspath; - } - else { - // fallback to home if settings path does not exists - return home; + // create folder if not existint + if (!filesystem::exists(settings)) { + if (!filesystem::create_directories(settings) ) + return home_path(); } + return settings.string(); } -string SystemToolkit::settings_prepend_path(const string &basefilename) +string SystemToolkit::full_filename(const std::string& path, const string &filename) { - string path = SystemToolkit::settings_path(); - path += PATH_SEP; - path += basefilename; + filesystem::path fullfilename( path ); + fullfilename /= filename; - return path; + return fullfilename.string(); } bool SystemToolkit::file_exists(const string& path) { - return access(path.c_str(), R_OK) == 0; - - // TODO : WIN32 implementation + return filesystem::exists( filesystem::status(path) ); } - -bool SystemToolkit::create_directory(const string& path) +list SystemToolkit::list_directory(const string& path, const string& filter) { - return !mkdir(path.c_str(), 0755) || errno == EEXIST; - - // TODO : verify WIN32 implementation + list ls; + // loop over elements of the directory + for (const auto & entry : filesystem::directory_iterator(path)) { + // list only files, not directories + if ( filesystem::is_regular_file(entry)) { + // add the path if no filter, or if filter is matching + if (filter.empty() || entry.path().extension() == filter ) + ls.push_back( entry.path().string() ); + } + } + return ls; } -void SystemToolkit::open(const std::string& url) +void SystemToolkit::open(const string& url) { #ifdef WIN32 ShellExecuteA( nullptr, nullptr, url.c_str(), nullptr, nullptr, 0 ); diff --git a/SystemToolkit.h b/SystemToolkit.h index 8ccc3a1..0fa293a 100644 --- a/SystemToolkit.h +++ b/SystemToolkit.h @@ -22,23 +22,29 @@ namespace SystemToolkit std::string settings_path(); // builds the OS dependent complete file name for a settings file - std::string settings_prepend_path(const std::string& basefilename); + std::string full_filename(const std::string& path, const std::string& filename); + + // extract the filename from a full path / URI (e.g. file:://home/me/toto.mpg -> toto.mpg) + std::string filename(const std::string& path); // extract the base filename from a full path / URI (e.g. file:://home/me/toto.mpg -> toto) - std::string base_filename(const std::string& filename); + std::string base_filename(const std::string& path); // extract the path of a filename from a full URI (e.g. file:://home/me/toto.mpg -> file:://home/me/) - std::string path_filename(const std::string& filename); + std::string path_filename(const std::string& path); + + // Truncate a full filename to display the right part (e.g. file:://home/me/toto.mpg -> ...ome/me/toto.mpg) + std::string trunc_filename(const std::string& path, int lenght); // extract the extension of a filename std::string extension_filename(const std::string& filename); + // list all files of a directory mathing the given filter extension (if any) + std::list list_directory(const std::string& path, const std::string& filter = ""); + // true of file exists bool file_exists(const std::string& path); - // true if directory could be created - bool create_directory(const std::string& path); - // try to open the file with system void open(const std::string& path); } diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 8c76d5e..5ede5a9 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -116,7 +116,10 @@ static void ImportFileDialogOpen(char *filename, std::atomic *success, con char const * open_pattern[18] = { "*.mix", "*.mp4", "*.mpg", "*.avi", "*.mov", "*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg", "*.flv", "*.asf", "*.jpg", "*.png", "*.gif", "*.tif", "*.svg" }; char const * open_file_name; - open_file_name = tinyfd_openFileDialog( "Import a file", path.c_str(), 18, open_pattern, "All supported formats", 0); + if (SystemToolkit::file_exists(path)) + open_file_name = tinyfd_openFileDialog( "Import a file", path.c_str(), 18, open_pattern, "All supported formats", 0); + else + open_file_name = tinyfd_openFileDialog( "Import a file", SystemToolkit::home_path().c_str(), 18, open_pattern, "All supported formats", 0); if (open_file_name) { sprintf(filename, "%s", open_file_name); @@ -129,6 +132,30 @@ static void ImportFileDialogOpen(char *filename, std::atomic *success, con fileDialogPending_ = false; } +static void FolderDialogOpen(char *folder, std::atomic *success, const std::string &path) +{ + if (fileDialogPending_) + return; + fileDialogPending_ = true; + + char const * open_file_name; + if (SystemToolkit::file_exists(path)) + open_file_name = tinyfd_selectFolderDialog("Select a folder", path.c_str()); + else + open_file_name = tinyfd_selectFolderDialog("Select a folder", SystemToolkit::home_path().c_str()); + + if (open_file_name) { + sprintf(folder, "%s", open_file_name); + *success = true; + } + else { + *success = false; + } + + fileDialogPending_ = false; +} + + UserInterface::UserInterface() { show_about = false; @@ -195,7 +222,7 @@ bool UserInterface::Init() ImGui::SetClipboardText(""); // setup settings filename - std::string inifile = SystemToolkit::settings_prepend_path("imgui.ini"); + std::string inifile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "imgui.ini"); char *inifilepath = (char *) malloc( (inifile.size() + 1) * sizeof(char) ); std::sprintf(inifilepath, "%s", inifile.c_str() ); io.IniFilename = inifilepath; @@ -649,7 +676,7 @@ void UserInterface::handleScreenshot() case 3: { if ( Rendering::manager().currentScreenshot()->IsFull() ){ - std::string filename = SystemToolkit::home_path() + SystemToolkit::date_time_string() + "_vmixcapture.png"; + std::string filename = SystemToolkit::full_filename( SystemToolkit::home_path(), SystemToolkit::date_time_string() + "_vmixcapture.png" ); Rendering::manager().currentScreenshot()->SaveFile( filename.c_str() ); Rendering::manager().currentScreenshot()->Clear(); Log::Notify("Screenshot saved %s", filename.c_str() ); @@ -845,10 +872,11 @@ void UserInterface::RenderMediaPlayer() if (ImGui::IsItemHovered()) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(tooltip_pos, ImVec2(tooltip_pos.x + width, tooltip_pos.y + 2.f * ImGui::GetTextLineHeightWithSpacing()), IM_COL32(55, 55, 55, 200)); + draw_list->AddRectFilled(tooltip_pos, ImVec2(tooltip_pos.x + width, tooltip_pos.y + 3.f * ImGui::GetTextLineHeightWithSpacing()), IM_COL32(55, 55, 55, 200)); ImGui::SetCursorScreenPos(tooltip_pos); - ImGui::Text(" %s (%s)", SystemToolkit::base_filename(mp->uri()).c_str(), mp->codec().c_str()); + ImGui::Text(" %s", SystemToolkit::filename(mp->uri()).c_str()); + ImGui::Text(" %s", mp->codec().c_str()); if ( mp->frameRate() > 0.f ) ImGui::Text(" %d x %d px, %.2f / %.2f fps", mp->width(), mp->height(), mp->updateFrameRate() , mp->frameRate() ); else @@ -1090,7 +1118,7 @@ void Navigator::clearButtonSelection() selected_button[i] = false; // clear new source pannel - sprintf(new_source_filename_, " "); + sprintf(file_browser_path_, " "); new_source_preview_.setSource(); } @@ -1365,11 +1393,11 @@ void Navigator::RenderNewPannel() if ( ImGui::Button( ICON_FA_FILE_IMPORT " Open", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { // clear string before selection file_selected = false; - std::thread (ImportFileDialogOpen, new_source_filename_, &file_selected, Settings::application.recentImport.path).detach(); + std::thread (ImportFileDialogOpen, file_browser_path_, &file_selected, Settings::application.recentImport.path).detach(); } if ( file_selected ) { file_selected = false; - std::string open_filename(new_source_filename_); + std::string open_filename(file_browser_path_); // create a source with this file std::string label = open_filename.substr( open_filename.size() - MIN( 35, open_filename.size()) ); new_source_preview_.setSource( Mixer::manager().createSourceFile(open_filename), label); @@ -1472,24 +1500,123 @@ void Navigator::RenderMainPannel() ImGui::EndMenu(); } - // combo box with list of recent session files from Settings - static bool recentselected = false; - recentselected = false; + static bool selection_session_mode_changed = true; + static int selection_session_mode = 0; + + // + // Session quick selection pannel + // + + // return from thread for folder openning + static std::atomic folder_selected = false; + if (folder_selected) { + folder_selected = false; + selection_session_mode = 1; + selection_session_mode_changed = true; + Settings::application.recentFolders.push(file_browser_path_); + Settings::application.recentFolders.path.assign(file_browser_path_); + } + + // Show combo box of quick selection modes ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##Recent", "Open recent")) - { - std::for_each(Settings::application.recentSessions.filenames.begin(), - Settings::application.recentSessions.filenames.end(), [](std::string& filename) { - int right = MIN( 40, filename.size()); - if (ImGui::Selectable( filename.substr( filename.size() - right ).c_str() )) { - Mixer::manager().open( filename ); - recentselected = true; + if (ImGui::BeginCombo("##SelectionSession", SystemToolkit::trunc_filename(Settings::application.recentFolders.path, 25).c_str() )) { + + // Option 0 : recent files + if (ImGui::Selectable( ICON_FA_HISTORY " Recent Files") ) { + Settings::application.recentFolders.path = "Recent Files"; + selection_session_mode = 0; + selection_session_mode_changed = true; + } + // Options 1 : known folders + for(auto foldername = Settings::application.recentFolders.filenames.begin(); + foldername != Settings::application.recentFolders.filenames.end(); foldername++) { + std::string f = std::string(ICON_FA_FOLDER) + " " + SystemToolkit::trunc_filename( *foldername, 40); + if (ImGui::Selectable( f.c_str() )) { + // remember which path was selected + Settings::application.recentFolders.path.assign(*foldername); + // set mode + selection_session_mode = 1; + selection_session_mode_changed = true; } - }); + } + // Option 2 : add a folder + if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ){ + std::thread (FolderDialogOpen, file_browser_path_, &folder_selected, Settings::application.recentFolders.path).detach(); + } ImGui::EndCombo(); } - if (recentselected) + + // helper + ImGui::SameLine(); + ImGui::SetCursorPosX(pannel_width_ + IMGUI_RIGHT_ALIGN); + ImGuiToolkit::HelpMarker("Quick access of Session files.\nSelect from the history of recently\nopened files or from a folder.\nDouble clic on a filename to open it."); + + // fill the session list depending on the mode + static std::list sessions_list; + // change session list if changed + if (selection_session_mode_changed) { + + // selection MODE 0 ; RECENT sessions + if ( selection_session_mode == 0) { + // show list of recent sessions + sessions_list = Settings::application.recentSessions.filenames; + } + // selection MODE 1 : LIST FOLDER + else if ( selection_session_mode == 1) { + // show list of vimix files in folder + sessions_list = SystemToolkit::list_directory( Settings::application.recentFolders.path, ".mix"); + } + // indicate the list changed (do not change at every frame) + selection_session_mode_changed = false; + } + + // display the sessions list and detect if one was selected (double clic) + bool session_selected = false; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::ListBoxHeader("##Sessions", Settings::application.recentSessions.filenames.size()); + for(auto filename = sessions_list.begin(); filename != sessions_list.end(); filename++) { + if (ImGui::Selectable( SystemToolkit::filename(*filename).c_str(), false, ImGuiSelectableFlags_AllowDoubleClick )) { + if (ImGui::IsMouseDoubleClicked(0)) { + Mixer::manager().open( *filename ); + session_selected = true; + } + } + } + ImGui::ListBoxFooter(); + + // icon to remove this folder from the list + if ( selection_session_mode == 1) { + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); + bool reset = false; + ImGuiToolkit::IconToggle(12,14,11,14, &reset); + if (reset) { + Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path); + if (Settings::application.recentFolders.filenames.empty()) { + Settings::application.recentFolders.path.assign("Recent Files"); + selection_session_mode = 0; + } + else + Settings::application.recentFolders.path = Settings::application.recentFolders.filenames.front(); + // reload the list next time + selection_session_mode_changed = true; + } + ImGui::PopStyleVar(); + ImGui::SetCursorPos(pos); + } + + // done the selection ! + if (session_selected) { + // close pannel hidePannel(); + // reload the list next time + selection_session_mode_changed = true; + } + + // Continue Main pannel + ImGui::Text(" "); + ImGuiToolkit::ButtonSwitch( "Smooth transition", &Settings::application.smooth_transition); ImGuiToolkit::ButtonSwitch( "Load most recent on start", &Settings::application.recentSessions.load_at_start); ImGuiToolkit::ButtonSwitch( "Save on exit", &Settings::application.recentSessions.save_on_exit); diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 852cbaa..e2131ab 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -49,7 +49,7 @@ class Navigator void RenderNewPannel(); int new_source_type_; - char new_source_filename_[2048]; + char file_browser_path_[2048]; SourcePreview new_source_preview_;