Implementation of Main pannel selection of sessions from recent history

AND from folder listing. Re-implementation in C++17 style of
SystemToolkit.
This commit is contained in:
brunoherbelin
2020-07-01 00:16:43 +02:00
parent 1309a479b5
commit d4b793ceb6
7 changed files with 264 additions and 106 deletions

View File

@@ -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();

View File

@@ -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)

View File

@@ -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;

View File

@@ -5,83 +5,75 @@
#include <iomanip>
#include <ctime>
#include <chrono>
#include <filesystem>
using namespace std;
#ifdef WIN32
#include <windows.h>
#define mkdir(dir, mode) _mkdir(dir)
#include <include/dirent.h>
#define PATH_SEP '\\'
#elif defined(LINUX) or defined(APPLE)
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <dirent.h>
#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<chrono::milliseconds>(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 basefilename;
return filesystem::path(path).filename();
}
std::string SystemToolkit::path_filename(const std::string& filename)
string SystemToolkit::base_filename(const string& path)
{
std::string path = filename.substr(0, filename.find_last_of(PATH_SEP) + 1);
return path;
return filesystem::path(path).stem();
}
std::string SystemToolkit::extension_filename(const std::string& filename)
string SystemToolkit::path_filename(const string& path)
{
std::string ext = filename.substr(filename.find_last_of(".") + 1);
return ext;
return filesystem::path(path).parent_path();
}
std::string SystemToolkit::home_path()
string SystemToolkit::trunc_filename(const string& path, int lenght)
{
// 1. find home
string trunc = path;
int l = path.size();
if ( l > lenght ) {
trunc = string("...") + path.substr( l - lenght + 3 );
}
return trunc;
}
string SystemToolkit::extension_filename(const string& filename)
{
return filesystem::path(filename).extension();
}
string SystemToolkit::home_path()
{
// 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;
// create folder if not existint
if (!filesystem::exists(settings)) {
if (!filesystem::create_directories(settings) )
return home_path();
}
return settingspath;
}
else {
// fallback to home if settings path does not exists
return home;
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<string> SystemToolkit::list_directory(const string& path, const string& filter)
{
return !mkdir(path.c_str(), 0755) || errno == EEXIST;
// TODO : verify WIN32 implementation
list<string> 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 );

View File

@@ -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<std::string> 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);
}

View File

@@ -116,7 +116,10 @@ static void ImportFileDialogOpen(char *filename, std::atomic<bool> *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;
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<bool> *success, con
fileDialogPending_ = false;
}
static void FolderDialogOpen(char *folder, std::atomic<bool> *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;
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;
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<bool> 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("##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<std::string> 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);

View File

@@ -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_;