diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 575021b..cf768bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,30 +7,112 @@ on: branches: [ "main" ] jobs: - build-linux: - name: Build Linux Source + build-linux-x86_64-gcc14-cpp17: + name: Build Linux Source x86_64 C++17 runs-on: ubuntu-latest env: - CC: gcc-10 - CXX: g++-10 + CC: gcc-14 + CXX: g++-14 steps: - uses: actions/checkout@v3 - - name: Install g++-multilib - run: sudo apt-get install -y g++-10-multilib + - name: Install g++-14-multilib + run: sudo apt-get install -y g++-14-multilib - name: Set Execute flags run: chmod +x GeneratorScripts/GenerateProjectMake.sh && chmod +x libs/premake5/linux/premake5 - name: Generate Make files - run: cd GeneratorScripts/ && ./GenerateProjectMake.sh && cd .. + run: cd GeneratorScripts/ && ./GenerateProjectMake.sh --std=C++17 && cd .. - name: Compile code - run: make all - build-windows: - name: Build Windows Source + run: make config=release_x86_64 all + build-linux-x86_64-gcc14-cpp20: + name: Build Linux Source x86_64 C++20 + runs-on: ubuntu-latest + env: + CC: gcc-14 + CXX: g++-14 + steps: + - uses: actions/checkout@v3 + - name: Install g++-14-multilib + run: sudo apt-get install -y g++-14-multilib + - name: Set Execute flags + run: chmod +x GeneratorScripts/GenerateProjectMake.sh && chmod +x libs/premake5/linux/premake5 + - name: Generate Make files + run: cd GeneratorScripts/ && ./GenerateProjectMake.sh --std=C++17 && cd .. + - name: Compile code + run: make config=release_x86_64 all + build-linux-x86-gcc14-cpp17: + name: Build Linux Source x86 C++17 + runs-on: ubuntu-latest + env: + CC: gcc-14 + CXX: g++-14 + steps: + - uses: actions/checkout@v3 + - name: Install g++-14-multilib + run: sudo apt-get install -y g++-14-multilib + - name: Set Execute flags + run: chmod +x GeneratorScripts/GenerateProjectMake.sh && chmod +x libs/premake5/linux/premake5 + - name: Generate Make files + run: cd GeneratorScripts/ && ./GenerateProjectMake.sh --std=C++17 && cd .. + - name: Compile code + run: make config=release_x86 all + build-linux-x86-gcc14-cpp20: + name: Build Linux Source x86 C++20 + runs-on: ubuntu-latest + env: + CC: gcc-14 + CXX: g++-14 + steps: + - uses: actions/checkout@v3 + - name: Install g++-14-multilib + run: sudo apt-get install -y g++-14-multilib + - name: Set Execute flags + run: chmod +x GeneratorScripts/GenerateProjectMake.sh && chmod +x libs/premake5/linux/premake5 + - name: Generate Make files + run: cd GeneratorScripts/ && ./GenerateProjectMake.sh --std=C++17 && cd .. + - name: Compile code + run: make config=release_x86 all + build-windows-x86_64-cpp17: + name: Build Windows Source x86_64 C++17 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + - name: Generate Project files + run: cd GeneratorScripts && .\GenerateProjectVS2022.bat --std=C++17 && cd .. + - name: Compile code + run: '"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" && msbuild /m /p:Configuration=Release /p:Platform=x64 ModernDialogs.sln' + build-windows-x86_64-cpp20: + name: Build Windows Source x86_64 C++20 runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 - name: Generate Project files - run: cd GeneratorScripts && .\GenerateProjectVS2022.bat && cd .. + run: cd GeneratorScripts && .\GenerateProjectVS2022.bat --std=C++20 && cd .. - name: Compile code - run: '"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" && msbuild /m ModernDialogs.sln' + run: '"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" && msbuild /m /p:Configuration=Release /p:Platform=x64 ModernDialogs.sln' + build-windows-x86-cpp17: + name: Build Windows Source x86 C++17 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + - name: Generate Project files + run: cd GeneratorScripts && .\GenerateProjectVS2022.bat --std=C++17 && cd .. + - name: Compile code + run: '"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" && msbuild /m /p:Configuration=Release /p:Platform=Win32 ModernDialogs.sln' + build-windows-x86-cpp20: + name: Build Windows Source x86 C++20 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + - name: Generate Project files + run: cd GeneratorScripts && .\GenerateProjectVS2022.bat --std=C++20 && cd .. + - name: Compile code + run: '"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" && msbuild /m /p:Configuration=Release /p:Platform=Win32 ModernDialogs.sln' + diff --git a/Example/main.cpp b/Example/main.cpp index 9777ea4..54cc91a 100644 --- a/Example/main.cpp +++ b/Example/main.cpp @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2020 - 2022 Jan "GamesTrap" Sch�rkamp +Copyright (c) 2020 - 2025 Jan "GamesTrap" Schürkamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/GeneratorScripts/GenerateProjectCodelite.bat b/GeneratorScripts/GenerateProjectCodelite.bat index e0eec0e..532e489 100644 --- a/GeneratorScripts/GenerateProjectCodelite.bat +++ b/GeneratorScripts/GenerateProjectCodelite.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0 -call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua codelite +call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua codelite %* IF %ERRORLEVEL% NEQ 0 ( PAUSE ) diff --git a/GeneratorScripts/GenerateProjectCodelite.sh b/GeneratorScripts/GenerateProjectCodelite.sh index 82f8d3e..606662a 100644 --- a/GeneratorScripts/GenerateProjectCodelite.sh +++ b/GeneratorScripts/GenerateProjectCodelite.sh @@ -1,5 +1,5 @@ #!/bin/bash -exec="../libs/premake5/linux/./premake5 --file=../premake5.lua codelite" +exec="../libs/premake5/linux/./premake5 --file=../premake5.lua codelite $@" $exec if [[ "$?" -ne 0 ]]; then read -s -N 1 -p "Press any key to continue..."; diff --git a/GeneratorScripts/GenerateProjectMake.bat b/GeneratorScripts/GenerateProjectMake.bat index a98c597..eaadfb7 100644 --- a/GeneratorScripts/GenerateProjectMake.bat +++ b/GeneratorScripts/GenerateProjectMake.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0 -call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua gmake2 +call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua gmake2 %* IF %ERRORLEVEL% NEQ 0 ( PAUSE ) diff --git a/GeneratorScripts/GenerateProjectMake.sh b/GeneratorScripts/GenerateProjectMake.sh index 4d6d3b6..9756237 100644 --- a/GeneratorScripts/GenerateProjectMake.sh +++ b/GeneratorScripts/GenerateProjectMake.sh @@ -1,5 +1,5 @@ #!/bin/bash -exec="../libs/premake5/linux/./premake5 --file=../premake5.lua gmake2" +exec="../libs/premake5/linux/./premake5 --file=../premake5.lua gmake2 $@" $exec if [[ "$?" -ne 0 ]]; then read -s -N 1 -p "Press any key to continue..."; diff --git a/GeneratorScripts/GenerateProjectVS2017.bat b/GeneratorScripts/GenerateProjectVS2017.bat index 76cc862..234ffec 100644 --- a/GeneratorScripts/GenerateProjectVS2017.bat +++ b/GeneratorScripts/GenerateProjectVS2017.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0 -call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2017 +call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2017 %* IF %ERRORLEVEL% NEQ 0 ( PAUSE ) diff --git a/GeneratorScripts/GenerateProjectVS2017.sh b/GeneratorScripts/GenerateProjectVS2017.sh index 0a8156d..a70cdb9 100644 --- a/GeneratorScripts/GenerateProjectVS2017.sh +++ b/GeneratorScripts/GenerateProjectVS2017.sh @@ -1,5 +1,5 @@ #!/bin/bash -exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2017" +exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2017 $@" $exec if [[ "$?" -ne 0 ]]; then read -s -N 1 -p "Press any key to continue..."; diff --git a/GeneratorScripts/GenerateProjectVS2019.bat b/GeneratorScripts/GenerateProjectVS2019.bat index 7c0ca18..bfaed3c 100644 --- a/GeneratorScripts/GenerateProjectVS2019.bat +++ b/GeneratorScripts/GenerateProjectVS2019.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0 -call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2019 +call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2019 %* IF %ERRORLEVEL% NEQ 0 ( PAUSE ) diff --git a/GeneratorScripts/GenerateProjectVS2019.sh b/GeneratorScripts/GenerateProjectVS2019.sh index f66d7fa..9b348aa 100644 --- a/GeneratorScripts/GenerateProjectVS2019.sh +++ b/GeneratorScripts/GenerateProjectVS2019.sh @@ -1,5 +1,5 @@ #!/bin/bash -exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2019" +exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2019 $@" $exec if [[ "$?" -ne 0 ]]; then read -s -N 1 -p "Press any key to continue..."; diff --git a/GeneratorScripts/GenerateProjectVS2022.bat b/GeneratorScripts/GenerateProjectVS2022.bat index 3474d5e..d3cbf98 100644 --- a/GeneratorScripts/GenerateProjectVS2022.bat +++ b/GeneratorScripts/GenerateProjectVS2022.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0 -call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2022 +call ..\libs\premake5\windows\premake5.exe --file=../premake5.lua vs2022 %* IF %ERRORLEVEL% NEQ 0 ( PAUSE ) diff --git a/GeneratorScripts/GenerateProjectVS2022.sh b/GeneratorScripts/GenerateProjectVS2022.sh index 5849e15..6e354c2 100644 --- a/GeneratorScripts/GenerateProjectVS2022.sh +++ b/GeneratorScripts/GenerateProjectVS2022.sh @@ -1,5 +1,5 @@ #!/bin/bash -exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2022" +exec="../libs/premake5/linux/./premake5 --file=../premake5.lua vs2022 $@" $exec if [[ "$?" -ne 0 ]]; then read -s -N 1 -p "Press any key to continue..."; diff --git a/LICENSE b/LICENSE index c4b4b55..f4ba85e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2023 Jan "GamesTrap" Schürkamp +Copyright (c) 2020-2025 Jan "GamesTrap" Schürkamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/ModernDialogs/ModernDialogs.cpp b/ModernDialogs/ModernDialogs.cpp index 787733d..efe9d06 100644 --- a/ModernDialogs/ModernDialogs.cpp +++ b/ModernDialogs/ModernDialogs.cpp @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2020 - 2023 Jan "GamesTrap" Schürkamp +Copyright (c) 2020 - 2025 Jan "GamesTrap" Schürkamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ SOFTWARE. #include #include +#include +#include #ifdef _WIN32 #ifndef _WIN32_WINNT @@ -36,7 +38,7 @@ SOFTWARE. #include #include #include -#else +#elif defined(__linux__) #include #include #include @@ -45,1027 +47,872 @@ SOFTWARE. #include #endif -//-------------------------------------------------------------------------------------------------------------------// - -constexpr static int32_t MaxPathOrCMD = 1024; -constexpr static int32_t MaxMultipleFiles = 1024; +#if _MSVC_LANG >= 202002L || __cplusplus >= 202002L +#define CPP20Constexpr constexpr +#else +#define CPP20Constexpr +#endif -//-------------------------------------------------------------------------------------------------------------------// +namespace +{ + //-------------------------------------------------------------------------------------------------------------------// -//Windows land -#ifdef _WIN32 + constexpr static int32_t MaxPathOrCMD = 1024; + constexpr static int32_t MaxMultipleFiles = 1024; -std::wstring GetPathWithoutFinalSlashW(const std::wstring& source) -{ - std::wstring result; + //-------------------------------------------------------------------------------------------------------------------// - if (!source.empty()) + [[nodiscard]] CPP20Constexpr std::string GetPathWithoutFinalSlash(const std::string& source) { - std::size_t index = source.find_last_of(L'/'); - if (index == std::wstring_view::npos) - index = source.find_last_of(L'\\'); - if (index != std::wstring_view::npos) - result = source.substr(0, index); - else - result = {}; - } - else - result = {}; + if(source.empty()) + return ""; - return result; -} + std::size_t index = source.find_last_of('/'); + if (index == std::string_view::npos) + index = source.find_last_of('\\'); -//-------------------------------------------------------------------------------------------------------------------// + if (index == std::string_view::npos) + return ""; -std::wstring GetLastNameW(const std::wstring& source) -{ - std::wstring result; + return source.substr(0, index); + } - if (!source.empty()) + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] CPP20Constexpr std::string GetLastName(const std::string& source) { - std::size_t index = source.find_last_of(L'/'); - if (index == std::wstring_view::npos) - index = source.find_last_of(L'\\'); - if (index != std::wstring_view::npos) - result = source.substr(index + 1); - else - result = source; - } - else - result = {}; + if (source.empty()) + return ""; - return result; -} + std::size_t index = source.find_last_of('/'); + if (index == std::string_view::npos) + index = source.find_last_of('\\'); -//-------------------------------------------------------------------------------------------------------------------// + if (index == std::string_view::npos) + return source; -std::wstring UTF8To16(const std::string& UTF8String) -{ - std::wstring result; + return source.substr(index + 1); + } - const int32_t count = MultiByteToWideChar(CP_UTF8, 0, UTF8String.data(), -1, nullptr, 0); - if (!count) - return {}; + //-------------------------------------------------------------------------------------------------------------------// - result.resize(count); +//Windows land +#ifdef _WIN32 - if (!MultiByteToWideChar(CP_UTF8, 0, UTF8String.data(), -1, result.data(), static_cast(result.size()))) - return {}; + [[nodiscard]] CPP20Constexpr std::wstring GetPathWithoutFinalSlashW(const std::wstring& source) + { + if(source.empty()) + return L""; - result.pop_back(); + std::size_t index = source.find_last_of(L'/'); + if (index == std::wstring_view::npos) + index = source.find_last_of(L'\\'); - return result; -} + if (index == std::wstring_view::npos) + return L""; -//-------------------------------------------------------------------------------------------------------------------// + return source.substr(0, index); + } -std::string UTF16To8(const std::wstring& UTF16String) -{ - std::string result; + //-------------------------------------------------------------------------------------------------------------------// - const int32_t count = WideCharToMultiByte(CP_UTF8, 0, UTF16String.data(), -1, nullptr, 0, nullptr, nullptr); - if (!count) - return {}; + [[nodiscard]] CPP20Constexpr std::wstring GetLastNameW(const std::wstring& source) + { + if (source.empty()) + return L""; - result.resize(count); + std::size_t index = source.find_last_of(L'/'); + if (index == std::wstring_view::npos) + index = source.find_last_of(L'\\'); - if (!WideCharToMultiByte(CP_UTF8, 0, UTF16String.data(), -1, result.data(), static_cast(result.size()), nullptr, nullptr)) - return {}; + if (index == std::wstring_view::npos) + return source; - return result; -} + return source.substr(index + 1); + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -bool DirExists(const std::string& dirPath) -{ - struct _stat info {}; + [[nodiscard]] std::wstring UTF8To16(const std::string& UTF8String) + { + const int32_t count = MultiByteToWideChar(CP_UTF8, 0, UTF8String.data(), -1, nullptr, 0); + if (!count) + return L""; - if (dirPath.empty()) - return false; - const std::size_t dirLen = dirPath.length(); - if (!dirLen) - return true; - if ((dirLen == 2) && (dirPath[1] == ':')) - return true; - - const std::wstring wStr = UTF8To16(dirPath); - const int32_t statRet = _wstat(wStr.data(), &info); - if (statRet != 0) - return false; - if (info.st_mode & S_IFDIR) - return true; + std::wstring result(count, L'\0'); - return false; -} + if (!MultiByteToWideChar(CP_UTF8, 0, UTF8String.data(), -1, result.data(), static_cast(result.size()))) + return L""; -//-------------------------------------------------------------------------------------------------------------------// + result.pop_back(); //Remove the extra null terminator -bool FileExists(const std::string& filePathAndName) -{ - struct _stat info{}; + return result; + } - if (filePathAndName.empty()) - return false; + //-------------------------------------------------------------------------------------------------------------------// - std::wstring temp = UTF8To16(filePathAndName); - const int32_t statRet = _wstat(temp.data(), &info); + [[nodiscard]] std::string UTF16To8(const std::wstring& UTF16String) + { + const int32_t count = WideCharToMultiByte(CP_UTF8, 0, UTF16String.data(), -1, nullptr, 0, nullptr, nullptr); + if (!count) + return ""; - if (statRet != 0) - return false; + std::string result(count, '\0'); - if (info.st_mode & _S_IFREG) - return true; + if (!WideCharToMultiByte(CP_UTF8, 0, UTF16String.data(), -1, result.data(), static_cast(result.size()), nullptr, nullptr)) + return ""; - return false; -} + result.pop_back(); //Remove the extra null terminator -//-------------------------------------------------------------------------------------------------------------------// + return result; + } -BOOL CALLBACK BrowseCallbackProcWEnum(HWND hwndChild, LPARAM) -{ - std::wstring buffer; - buffer.resize(255); - GetClassNameW(hwndChild, buffer.data(), static_cast(buffer.size())); - if(buffer == L"SysTreeView32") + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] BOOL CALLBACK BrowseCallbackProcWEnum(HWND hwndChild, LPARAM /*lp*/) { - HTREEITEM const hNode = TreeView_GetSelection(hwndChild); - TreeView_EnsureVisible(hwndChild, hNode); - return FALSE; - } + std::wstring buffer(255, L'\0'); + GetClassNameW(hwndChild, buffer.data(), static_cast(buffer.size())); + if(buffer == L"SysTreeView32") + { + HTREEITEM const hNode = TreeView_GetSelection(hwndChild); + TreeView_EnsureVisible(hwndChild, hNode); + return FALSE; + } - return TRUE; -} + return TRUE; + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -int32_t CALLBACK BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM /*lp*/, LPARAM pData) -{ - switch(uMsg) + int32_t CALLBACK BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM /*lp*/, LPARAM pData) { - case BFFM_INITIALIZED: - SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, pData); - break; + switch(uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, pData); + break; - case BFFM_SELCHANGED: - EnumChildWindows(hwnd, BrowseCallbackProcWEnum, 0); - break; + case BFFM_SELCHANGED: + EnumChildWindows(hwnd, BrowseCallbackProcWEnum, 0); + break; - default: - break; - } + default: + break; + } - return 0; -} + return 0; + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -std::wstring SaveFileW(const std::wstring& title, - const std::wstring& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allFiles) -{ - static std::wstring buffer{}; - std::wstring defaultExtension{}; - std::wstring filterPatternsStr; - std::wstring dirName; - HRESULT hResult = CoInitializeEx(nullptr, 0); + [[nodiscard]] std::wstring SaveFileW(const std::wstring& title, + const std::wstring& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + std::wstring defaultExtension = L"*.*"; + std::wstring filterPatternsStr = L"All Files\n*.*\n"; + HRESULT hResult = CoInitializeEx(nullptr, 0); - dirName = GetPathWithoutFinalSlashW(defaultPathAndFile); - buffer = GetLastNameW(defaultPathAndFile); + const std::wstring dirName = GetPathWithoutFinalSlashW(defaultPathAndFile); + std::wstring buffer = GetLastNameW(defaultPathAndFile); - if (!filterPatterns.empty()) - { - defaultExtension = filterPatterns[0].second; - if (filterPatterns[0].first.empty()) - { - filterPatternsStr += filterPatterns[0].second + L'\n'; - filterPatternsStr += filterPatterns[0].second; - } - else - filterPatternsStr += filterPatterns[0].first + L"(" + filterPatterns[0].second + L")\n" + filterPatterns[0].second; - for (uint32_t i = 1; i < filterPatterns.size(); i++) + if (!filterPatterns.empty()) { - if (filterPatterns[i].first.empty()) + const auto& [frontName, frontExtensions] = filterPatterns.front(); + + defaultExtension = frontExtensions; + filterPatternsStr.clear(); + + if (frontName.empty()) { - filterPatternsStr += L'\n' + filterPatterns[i].second; - filterPatternsStr += L'\n' + filterPatterns[i].second; + filterPatternsStr += frontExtensions + L'\n'; + filterPatternsStr += frontExtensions; } else - filterPatternsStr += L'\n' + filterPatterns[i].first + L"(" + filterPatterns[i].second + L")\n" + filterPatterns[i].second; - } - filterPatternsStr += L'\n'; - if (allFiles) - filterPatternsStr += L"All Files\n*.*\n"; - else + filterPatternsStr += frontName + L"(" + frontExtensions + L")\n" + frontExtensions; + + for (uint32_t i = 1u; i < filterPatterns.size(); ++i) + { + const auto& [name, extensions] = filterPatterns[i]; + + if (name.empty()) + { + filterPatternsStr += L'\n' + extensions; + filterPatternsStr += L'\n' + extensions; + } + else + filterPatternsStr += L'\n' + name + L"(" + extensions + L")\n" + extensions; + } filterPatternsStr += L'\n'; - } - else - { - defaultExtension = L"*.*"; - - filterPatternsStr = L"All Files\n*.*\n"; - } - - std::replace(filterPatternsStr.begin(), filterPatternsStr.end(), L'\n', L'\0'); - - OPENFILENAMEW ofn; - std::memset(&ofn, 0, sizeof(OPENFILENAMEW)); - - buffer.resize(MaxPathOrCMD); - ofn.lStructSize = sizeof(OPENFILENAMEW); - ofn.hwndOwner = GetForegroundWindow(); - ofn.hInstance = nullptr; - ofn.lpstrFilter = filterPatternsStr.empty() ? nullptr : filterPatternsStr.data(); - ofn.lpstrCustomFilter = nullptr; - ofn.nMaxCustFilter = 0; - ofn.nFilterIndex = 1; - ofn.lpstrFile = buffer.data(); - - ofn.nMaxFile = MaxPathOrCMD; - ofn.lpstrFileTitle = nullptr; - ofn.nMaxFileTitle = MaxPathOrCMD / 2; - ofn.lpstrInitialDir = dirName.empty() ? nullptr : dirName.data(); - ofn.lpstrTitle = title.empty() ? nullptr : title.data(); - ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; - ofn.nFileOffset = 0; - ofn.nFileExtension = 0; - ofn.lpstrDefExt = defaultExtension.data(); - ofn.lCustData = 0L; - ofn.lpfnHook = nullptr; - ofn.lpTemplateName = nullptr; - - std::wstring retVal; - if (GetSaveFileNameW(&ofn) == 0) - retVal = {}; - else - retVal = buffer; - if (hResult == S_OK || hResult == S_FALSE) - CoUninitialize(); + if (allFiles) + filterPatternsStr += L"All Files\n*.*\n"; + else + filterPatternsStr += L'\n'; + } - return retVal; -} + std::replace(filterPatternsStr.begin(), filterPatternsStr.end(), L'\n', L'\0'); -//-------------------------------------------------------------------------------------------------------------------// + buffer.resize(MaxPathOrCMD); -std::string SaveFileWinGUI(const std::string& title, - const std::string& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allFiles) -{ - std::vector> wFilterPatterns(filterPatterns.size()); - for (uint32_t i = 0; i < wFilterPatterns.size(); i++) - wFilterPatterns[i] = { UTF8To16(filterPatterns[i].first), UTF8To16(filterPatterns[i].second) }; - const std::wstring wTitle = UTF8To16(title); - const std::wstring wDefaultPathAndFile = UTF8To16(defaultPathAndFile); + OPENFILENAMEW ofn{}; + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetForegroundWindow(); + ofn.hInstance = nullptr; + ofn.lpstrFilter = filterPatternsStr.empty() ? nullptr : filterPatternsStr.c_str(); + ofn.nFilterIndex = 1; + ofn.lpstrFile = buffer.data(); + ofn.nMaxFile = static_cast(buffer.size()); + ofn.lpstrInitialDir = dirName.empty() ? nullptr : dirName.c_str(); + ofn.lpstrTitle = title.empty() ? nullptr : title.c_str(); + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; + ofn.lpstrDefExt = defaultExtension.c_str(); - const std::wstring wPath = SaveFileW(wTitle, wDefaultPathAndFile, wFilterPatterns, allFiles); + const BOOL dlgRes = GetSaveFileNameW(&ofn); - if (wPath.empty()) - return {}; + if (hResult == S_OK || hResult == S_FALSE) + CoUninitialize(); - std::string path = UTF16To8(wPath); - UTF16To8({}); + return dlgRes ? std::wstring(buffer.c_str()) : L""; + } - return path; -} + //-------------------------------------------------------------------------------------------------------------------// -//-------------------------------------------------------------------------------------------------------------------// + [[nodiscard]] std::string SaveFileWinGUI(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + std::vector> wFilterPatterns(filterPatterns.size()); + for (uint32_t i = 0; i < wFilterPatterns.size(); i++) + { + const auto& [name, extensions] = filterPatterns[i]; + wFilterPatterns[i] = { UTF8To16(name), UTF8To16(extensions) }; + } -std::vector OpenFileW(const std::wstring& title, - const std::wstring& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allowMultipleSelects, - const bool allFiles) -{ - HRESULT hResult = CoInitializeEx(nullptr, 0); + const std::wstring wTitle = UTF8To16(title); + const std::wstring wDefaultPathAndFile = UTF8To16(defaultPathAndFile); - static std::wstring buffer; + const std::wstring wPath = SaveFileW(wTitle, wDefaultPathAndFile, wFilterPatterns, allFiles); - std::wstring dirName = GetPathWithoutFinalSlashW(defaultPathAndFile); - buffer = GetLastNameW(defaultPathAndFile); + if (wPath.empty()) + return ""; - if (allowMultipleSelects) - buffer.resize(MaxMultipleFiles * MaxPathOrCMD + 1); - else - buffer.resize(MaxPathOrCMD + 1); + return UTF16To8(wPath); + } - std::wstring filterPatternsStr; - if (!filterPatterns.empty()) + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::vector OpenFileW(const std::wstring& title, + const std::wstring& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) { - if (filterPatterns[0].first.empty()) - { - filterPatternsStr += filterPatterns[0].second + L'\n'; - filterPatternsStr += filterPatterns[0].second; - } - else - filterPatternsStr += filterPatterns[0].first + L"(" + filterPatterns[0].second + L")\n" + filterPatterns[0].second; - for (uint32_t i = 1; i < filterPatterns.size(); i++) + HRESULT hResult = CoInitializeEx(nullptr, 0); + + const std::wstring dirName = GetPathWithoutFinalSlashW(defaultPathAndFile); + std::wstring buffer = GetLastNameW(defaultPathAndFile); + + std::wstring filterPatternsStr = L"All Files\n*.*\n"; + if (!filterPatterns.empty()) { - if (filterPatterns[i].first.empty()) + filterPatternsStr.clear(); + + const auto& [firstName, firstExtensions] = filterPatterns[0]; + + if (firstName.empty()) { - filterPatternsStr += L'\n' + filterPatterns[i].second; - filterPatternsStr += L'\n' + filterPatterns[i].second; + filterPatternsStr += firstExtensions + L'\n'; + filterPatternsStr += firstExtensions; } else - filterPatternsStr += L'\n' + filterPatterns[i].first + L"(" + filterPatterns[i].second + L")\n" + filterPatterns[i].second; + filterPatternsStr += firstName + L"(" + firstExtensions + L")\n" + firstExtensions; + + for (uint32_t i = 1; i < filterPatterns.size(); ++i) + { + const auto& [name, extensions] = filterPatterns[i]; + + if (name.empty()) + { + filterPatternsStr += L'\n' + extensions; + filterPatternsStr += L'\n' + extensions; + } + else + filterPatternsStr += L'\n' + name + L"(" + extensions + L")\n" + extensions; + } + filterPatternsStr += L'\n'; + if (allFiles) + filterPatternsStr += L"All Files\n*.*\n"; + else + filterPatternsStr += L'\n'; } - filterPatternsStr += L'\n'; - if (allFiles) - filterPatternsStr += L"All Files\n*.*\n"; + + std::replace(filterPatternsStr.begin(), filterPatternsStr.end(), L'\n', L'\0'); + + if (allowMultipleSelects) + buffer.resize(MaxMultipleFiles * MaxPathOrCMD + 1); else - filterPatternsStr += L'\n'; - } - else - filterPatternsStr = L"All Files\n*.*\n"; - - std::replace(filterPatternsStr.begin(), filterPatternsStr.end(), L'\n', L'\0'); - - OPENFILENAMEW ofn; - std::memset(&ofn, 0, sizeof(OPENFILENAMEW)); - ofn.lStructSize = sizeof(OPENFILENAMEW); - ofn.hwndOwner = GetForegroundWindow(); - ofn.hInstance = nullptr; - ofn.lpstrFilter = filterPatternsStr.empty() ? nullptr : filterPatternsStr.data(); - ofn.lpstrCustomFilter = nullptr; - ofn.nMaxCustFilter = 0; - ofn.nFilterIndex = 1; - ofn.lpstrFile = buffer.data(); - - ofn.nMaxFile = static_cast(buffer.size()); - ofn.lpstrFileTitle = nullptr; - ofn.nMaxFileTitle = MaxPathOrCMD / 2; - ofn.lpstrInitialDir = dirName.empty() ? nullptr : dirName.data(); - ofn.lpstrTitle = title.empty() ? nullptr : title.data(); - ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - ofn.nFileOffset = 0; - ofn.nFileExtension = 0; - ofn.lpstrDefExt = nullptr; - ofn.lCustData = 0L; - ofn.lpfnHook = nullptr; - ofn.lpTemplateName = nullptr; - if (allowMultipleSelects) - ofn.Flags |= OFN_ALLOWMULTISELECT; - - std::vector paths{}; - if (!GetOpenFileNameW(&ofn)) - buffer = {}; - else - { + buffer.resize(MaxPathOrCMD + 1); + + OPENFILENAMEW ofn{}; + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetForegroundWindow(); + ofn.lpstrFilter = filterPatternsStr.empty() ? nullptr : filterPatternsStr.c_str(); + ofn.nFilterIndex = 1; + ofn.lpstrFile = buffer.data(); + ofn.nMaxFile = static_cast(buffer.size()); + ofn.lpstrInitialDir = dirName.empty() ? nullptr : dirName.c_str(); + ofn.lpstrTitle = title.empty() ? nullptr : title.c_str(); + ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; if (allowMultipleSelects) + ofn.Flags |= OFN_ALLOWMULTISELECT; + + const BOOL dlgRes = GetOpenFileNameW(&ofn); + + std::vector paths{}; + if (dlgRes && !buffer.empty()) { - std::wstring folder; - do + if (allowMultipleSelects) { - auto x = buffer.find_first_of(L'\0'); - std::wstring s = buffer.substr(0, x); + std::wstring folder{}; + do + { + const auto x = buffer.find_first_of(L'\0'); + const std::wstring s = buffer.substr(0, x); - if (folder.empty()) - folder = s + L"\\"; - else - paths.push_back(folder + s); + if (folder.empty()) + folder = s + L"\\"; + else + paths.push_back(folder + s); - buffer = buffer.substr(x + 1, buffer.size() - (x + 1)); - } while (buffer[0] != L'\0'); + buffer = buffer.substr(x + 1, buffer.size() - (x + 1)); + } while (buffer[0] != L'\0'); + } + else + paths.push_back(buffer); } - else - paths.push_back(buffer); - } - if (hResult == S_OK || hResult == S_FALSE) - CoUninitialize(); + if (hResult == S_OK || hResult == S_FALSE) + CoUninitialize(); - return paths; -} + return paths; + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -std::vector OpenFileWinGUI(const std::string& title, - const std::string& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allowMultipleSelects, - const bool allFiles) -{ - std::wstring wTitle; - std::wstring wDefaultPathAndFile; + [[nodiscard]] std::vector OpenFileWinGUI(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::vector> wFilterPatterns(filterPatterns.size()); + for(uint32_t i = 0; i < wFilterPatterns.size(); ++i) + { + const auto& [name, extensions] = filterPatterns[i]; + wFilterPatterns[i] = { UTF8To16(name), UTF8To16(extensions) }; + } - std::vector> wFilterPatterns(filterPatterns.size()); - for(uint32_t i = 0; i < wFilterPatterns.size(); i++) - wFilterPatterns[i] = { UTF8To16(filterPatterns[i].first), UTF8To16(filterPatterns[i].second) }; + std::wstring wTitle{}; + if (!title.empty()) + wTitle = UTF8To16(title); - if (!title.empty()) - wTitle = UTF8To16(title); - if (!defaultPathAndFile.empty()) - wDefaultPathAndFile = UTF8To16(defaultPathAndFile); + std::wstring wDefaultPathAndFile{}; + if (!defaultPathAndFile.empty()) + wDefaultPathAndFile = UTF8To16(defaultPathAndFile); - std::vector wPaths = OpenFileW(wTitle, wDefaultPathAndFile, wFilterPatterns, allowMultipleSelects, allFiles); + const std::vector wPaths = OpenFileW(wTitle, wDefaultPathAndFile, wFilterPatterns, allowMultipleSelects, allFiles); + if (wPaths.empty()) + return {}; - if (wPaths.empty()) - return {}; + std::vector paths(wPaths.size()); + std::transform(wPaths.begin(), wPaths.end(), paths.begin(), UTF16To8); - std::vector paths(wPaths.size()); - for (uint32_t i = 0; i < paths.size(); i++) - paths[i] = UTF16To8(wPaths[i]); + return paths; + } - return paths; -} + //-------------------------------------------------------------------------------------------------------------------// -//-------------------------------------------------------------------------------------------------------------------// + [[nodiscard]] std::wstring SelectFolderW(const std::wstring& title, const std::wstring& defaultPath) + { + std::wstring buffer(MaxPathOrCMD, L'\0'); -std::wstring SelectFolderW(const std::wstring& title, const std::wstring& defaultPath) -{ - static std::wstring buffer; - buffer.resize(MaxPathOrCMD); + HRESULT hResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - HRESULT hResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + BROWSEINFOW info{}; + info.hwndOwner = GetForegroundWindow(); + info.pszDisplayName = buffer.data(); + info.lpszTitle = title.empty() ? nullptr : title.c_str(); + if (hResult == S_OK || hResult == S_FALSE) + info.ulFlags = BIF_USENEWUI; + info.lpfn = BrowseCallbackProcW; + info.lParam = reinterpret_cast(defaultPath.c_str()); + info.iImage = -1; - BROWSEINFOW info; - std::memset(&info, 0, sizeof(BROWSEINFOW)); + LPITEMIDLIST lpItem = SHBrowseForFolderW(&info); + if(lpItem) + SHGetPathFromIDListW(lpItem, buffer.data()); - info.hwndOwner = GetForegroundWindow(); - info.pidlRoot = nullptr; - info.pszDisplayName = buffer.data(); - info.lpszTitle = title.empty() ? nullptr : title.data(); - if (hResult == S_OK || hResult == S_FALSE) - info.ulFlags = BIF_USENEWUI; - info.lpfn = BrowseCallbackProcW; - info.lParam = reinterpret_cast(defaultPath.data()); - info.iImage = -1; + if (hResult == S_OK || hResult == S_FALSE) + CoUninitialize(); - std::wstring retVal; - LPITEMIDLIST lpItem = SHBrowseForFolderW(&info); - if(lpItem) - { - SHGetPathFromIDListW(lpItem, buffer.data()); - retVal = buffer; + return std::wstring(buffer.c_str()); } - if (hResult == S_OK || hResult == S_FALSE) - CoUninitialize(); - - return retVal; -} - -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -std::string SelectFolderWinGUI(const std::string& title, const std::string& defaultPath) -{ - std::wstring wTitle; - std::wstring wDefaultPath; + [[nodiscard]] std::string SelectFolderWinGUI(const std::string& title, const std::string& defaultPath) + { + std::wstring wTitle{}; + if (!title.empty()) + wTitle = UTF8To16(title); - if (!title.empty()) - wTitle = UTF8To16(title); - if (!defaultPath.empty()) - wDefaultPath = UTF8To16(defaultPath); + std::wstring wDefaultPath{}; + if (!defaultPath.empty()) + wDefaultPath = UTF8To16(defaultPath); - const std::wstring wPath = SelectFolderW(wTitle, wDefaultPath); + const std::wstring wPath = SelectFolderW(wTitle, wDefaultPath); - if (wPath.empty()) - return {}; + if (wPath.empty()) + return {}; - return UTF16To8(wPath); -} + return UTF16To8(wPath); + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -MD::Selection GetSelection(const int32_t response, const MD::Buttons buttons) -{ - switch(response) + [[nodiscard]] constexpr MD::Selection GetSelection(const int32_t response, const MD::Buttons buttons) { - case IDOK: - return buttons == MD::Buttons::Quit ? MD::Selection::Quit : MD::Selection::OK; + switch(response) + { + case IDOK: + return buttons == MD::Buttons::Quit ? MD::Selection::Quit : MD::Selection::OK; - case IDCANCEL: - return MD::Selection::Cancel; + case IDCANCEL: + return MD::Selection::Cancel; - case IDYES: - return MD::Selection::Yes; + case IDYES: + return MD::Selection::Yes; - case IDNO: - return MD::Selection::No; + case IDNO: + return MD::Selection::No; - default: - return MD::Selection::None; + default: + return MD::Selection::None; + } } -} -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -uint32_t GetIcon(const MD::Style style) -{ - switch(style) + [[nodiscard]] constexpr uint32_t GetIcon(const MD::Style style) { - case MD::Style::Info: - return MB_ICONINFORMATION; + switch(style) + { + case MD::Style::Info: + return MB_ICONINFORMATION; - case MD::Style::Warning: - return MB_ICONWARNING; + case MD::Style::Warning: + return MB_ICONWARNING; - case MD::Style::Error: - return MB_ICONERROR; + case MD::Style::Error: + return MB_ICONERROR; - case MD::Style::Question: - return MB_ICONQUESTION; + case MD::Style::Question: + return MB_ICONQUESTION; - default: - return MB_ICONINFORMATION; + default: + return MB_ICONINFORMATION; + } } -} -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -uint32_t GetButtons(const MD::Buttons buttons) -{ - switch(buttons) + [[nodiscard]] constexpr uint32_t GetButtons(const MD::Buttons buttons) { - case MD::Buttons::OK: - case MD::Buttons::Quit: //There's no 'Quit' button on Windows so use OK - return MB_OK; + switch(buttons) + { + case MD::Buttons::OK: + case MD::Buttons::Quit: //There's no 'Quit' button on Windows so use OK + return MB_OK; - case MD::Buttons::OKCancel: - return MB_OKCANCEL; + case MD::Buttons::OKCancel: + return MB_OKCANCEL; - case MD::Buttons::YesNo: - return MB_YESNO; + case MD::Buttons::YesNo: + return MB_YESNO; - default: - return MB_OK; + default: + return MB_OK; + } } -} -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -MD::Selection ShowMsgBoxWinGUI(const std::string& title, - const std::string& message, - const MD::Style style, - const MD::Buttons buttons) -{ - std::wstring wTitle; - std::wstring wMessage; + [[nodiscard]] MD::Selection ShowMsgBoxWinGUI(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + std::wstring wTitle{}; + if (!title.empty()) + wTitle = UTF8To16(title); - if (!title.empty()) - wTitle = UTF8To16(title); - if (!message.empty()) - wMessage = UTF8To16(message); + std::wstring wMessage{}; + if (!message.empty()) + wMessage = UTF8To16(message); - uint32_t flags = MB_TASKMODAL; + uint32_t flags = MB_TASKMODAL; - flags |= GetIcon(style); - flags |= GetButtons(buttons); + flags |= GetIcon(style); + flags |= GetButtons(buttons); - return GetSelection(MessageBoxW(nullptr, wMessage.c_str(), wTitle.c_str(), flags), buttons); -} + return GetSelection(MessageBoxW(nullptr, wMessage.c_str(), wTitle.c_str(), flags), buttons); + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// //Linux land #else -std::string Python3Name; - -bool DetectPresence(const std::string& executable) -{ - std::string buffer; - buffer.resize(MaxPathOrCMD); - FILE* in; + std::string Python3Name{}; - std::string testedString = "which " + executable + " 2>/dev/null "; - in = popen(testedString.data(), "r"); - if((fgets(buffer.data(), buffer.size(), in) != nullptr) - && (buffer.find_first_of(':') == std::string::npos) && (buffer.find("no ") == std::string::npos)) + [[nodiscard]] bool DetectPresence(const std::string& executable) { - pclose(in); - return true; - } - else - { - pclose(in); - return false; - } -} + std::array buffer{}; + std::string output{}; -//-------------------------------------------------------------------------------------------------------------------// - -bool DirExists(const std::string& dirPath) -{ - DIR* dir; + const std::string cmd = "command -v " + executable + " 2>/dev/null"; - if (dirPath.empty()) - return false; + FILE* const in = popen(cmd.c_str(), "r"); - dir = opendir(dirPath.data()); + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + output += buffer.data(); - if (!dir) - return false; + pclose(in); - closedir(dir); - return true; -} + return !output.empty() && output.find(':') == std::string::npos && output.find("no ") == std::string::npos; + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -bool GetEnvDISPLAY() -{ - return std::getenv("DISPLAY"); -} + [[nodiscard]] int32_t GetEnvDISPLAY() + { + static int32_t returnValue = -1; -//-------------------------------------------------------------------------------------------------------------------// + if(returnValue < 0) + { + returnValue = 0; + if(std::getenv("DISPLAY")) + returnValue += 1; + if(std::getenv("WAYLAND_DISPLAY")) + returnValue += 2; + } -bool IsDarwin() -{ - static int32_t isDarwin = -1; - struct utsname lUtsname; + return returnValue; + } - if (isDarwin < 0) - isDarwin = !uname(&lUtsname) && std::string(lUtsname.sysname) == "Darwin"; + //-------------------------------------------------------------------------------------------------------------------// - return isDarwin; -} + [[nodiscard]] bool IsDarwin() + { + static int32_t isDarwin = -1; + struct utsname lUtsname; -//-------------------------------------------------------------------------------------------------------------------// + if (isDarwin < 0) + isDarwin = !uname(&lUtsname) && std::string_view(lUtsname.sysname) == "Darwin"; -bool GraphicMode() -{ - return (GetEnvDISPLAY() || (IsDarwin() && GetEnvDISPLAY())); -} + return isDarwin; + } -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -bool XPropPresent() -{ - static int32_t xpropPresent = -1; + [[nodiscard]] bool GraphicMode() + { + return (GetEnvDISPLAY() || (IsDarwin() && GetEnvDISPLAY())); + } - if (xpropPresent < 0) - xpropPresent = DetectPresence("xprop"); + //-------------------------------------------------------------------------------------------------------------------// - return xpropPresent && GraphicMode(); -} + [[nodiscard]] bool XPropPresent() + { + static bool xpropReady = false; + static int32_t xpropPresent = -1; -//-------------------------------------------------------------------------------------------------------------------// + if (xpropPresent < 0) + { + if(GetEnvDISPLAY() & 1) + xpropPresent = DetectPresence("xprop"); + else + xpropPresent = 0; + } -bool ZenityPresent() -{ - static int32_t zenityPresent = -1; + if(!xpropPresent) + return 0; - if (zenityPresent < 0) - zenityPresent = DetectPresence("zenity"); + if(!xpropReady) + { + FILE* const in = popen("xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW", "r"); - return zenityPresent && GraphicMode(); -} + std::array buffer{}; + std::string output{}; -//-------------------------------------------------------------------------------------------------------------------// + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + output += buffer.data(); -int32_t Zenity3Present() -{ - static int32_t zenity3Present = -1; - FILE* in; - std::string buffer; - buffer.resize(MaxPathOrCMD); + pclose(in); + + if(output.find("not found") == std::string::npos) + xpropReady = true; + } + + return xpropReady && GraphicMode(); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] bool ZenityPresent() + { + static int32_t zenityPresent = -1; + + if (zenityPresent < 0) + zenityPresent = DetectPresence("zenity"); + + return zenityPresent && GraphicMode(); + } + + //-------------------------------------------------------------------------------------------------------------------// - if(zenity3Present < 0) + [[nodiscard]] int32_t Zenity3Present() { - zenity3Present = 0; - if(ZenityPresent()) + static int32_t zenity3Present = -1; + + if(zenity3Present < 0) { - in = popen("zenity --version", "r"); - if(fgets(buffer.data(), buffer.size(), in) != nullptr) + zenity3Present = 0; + if(ZenityPresent()) { - if(std::stoi(buffer) >= 3) + std::array buffer{}; + std::string output{}; + FILE* const in = popen("zenity --version", "r"); + + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + output += buffer.data(); + + if(!output.empty() && std::stoi(output) >= 3) { zenity3Present = 3; - int32_t temp = std::stoi(buffer.substr(buffer.find_first_not_of('.') + 2)); + const int32_t temp = std::stoi(output.substr(output.find_first_not_of('.') + 2)); if(temp >= 18) zenity3Present = 5; else if(temp >= 10) zenity3Present = 4; } - else if((std::stoi(buffer) == 2) && (std::stoi(buffer.substr(buffer.find_first_not_of('.') + 2)) >= 32)) + else if(!output.empty() && (std::stoi(output) == 2) && (std::stoi(output.substr(output.find_first_not_of('.') + 2)) >= 32)) zenity3Present = 2; + + pclose(in); } - pclose(in); } - } - return GraphicMode() ? zenity3Present : 0; -} - -//-------------------------------------------------------------------------------------------------------------------// + return GraphicMode() ? zenity3Present : 0; + } -bool MateDialogPresent() -{ - static int32_t matedialogPresent = -1; + //-------------------------------------------------------------------------------------------------------------------// - if(matedialogPresent < 0) - matedialogPresent = DetectPresence("matedialog"); + [[nodiscard]] bool MateDialogPresent() + { + static int32_t matedialogPresent = -1; - return matedialogPresent && GraphicMode(); -} + if(matedialogPresent < 0) + matedialogPresent = DetectPresence("matedialog"); -//-------------------------------------------------------------------------------------------------------------------// + return matedialogPresent && GraphicMode(); + } -bool ShellementaryPresent() -{ - static int32_t shellementaryPresent = -1; + //-------------------------------------------------------------------------------------------------------------------// - if(shellementaryPresent < 0) - shellementaryPresent = DetectPresence("shellementary"); + [[nodiscard]] bool ShellementaryPresent() + { + static int32_t shellementaryPresent = -1; - return shellementaryPresent && GraphicMode(); -} + if(shellementaryPresent < 0) + shellementaryPresent = DetectPresence("shellementary"); -//-------------------------------------------------------------------------------------------------------------------// + return shellementaryPresent && GraphicMode(); + } -bool QarmaPresent() -{ - static int32_t qarmaPresent = -1; + //-------------------------------------------------------------------------------------------------------------------// - if(qarmaPresent < 0) - qarmaPresent = DetectPresence("qarma"); + [[nodiscard]] bool QarmaPresent() + { + static int32_t qarmaPresent = -1; - return qarmaPresent && GraphicMode(); -} + if(qarmaPresent < 0) + qarmaPresent = DetectPresence("qarma"); -//-------------------------------------------------------------------------------------------------------------------// + return qarmaPresent && GraphicMode(); + } -bool YadPresent() -{ - static int32_t yadPresent = -1; + //-------------------------------------------------------------------------------------------------------------------// - if(yadPresent < 0) - yadPresent = DetectPresence("yad"); + [[nodiscard]] bool YadPresent() + { + static int32_t yadPresent = -1; - return yadPresent && GraphicMode(); -} + if(yadPresent < 0) + yadPresent = DetectPresence("yad"); -//-------------------------------------------------------------------------------------------------------------------// + return yadPresent && GraphicMode(); + } -bool Python3Present() -{ - static int32_t python3Present = -1; + //-------------------------------------------------------------------------------------------------------------------// - if(python3Present < 0) + [[nodiscard]] bool Python3Present() { - python3Present = 0; - Python3Name = "python3"; - if(DetectPresence(Python3Name)) - python3Present = 1; - else + static int32_t python3Present = -1; + + if(python3Present < 0) { - for(int32_t i = 10; i >= 0; i--) + python3Present = 0; + Python3Name = "python3"; + if(DetectPresence(Python3Name)) + python3Present = 1; + else { - Python3Name = "python3." + std::to_string(i); - if(DetectPresence(Python3Name)) + for(int32_t i = 10; i >= 0; --i) { - python3Present = 1; - break; + Python3Name = "python3." + std::to_string(i); + if(DetectPresence(Python3Name)) + { + python3Present = 1; + break; + } } } } + + return python3Present; } - return python3Present; -} + //-------------------------------------------------------------------------------------------------------------------// -//-------------------------------------------------------------------------------------------------------------------// + [[nodiscard]] bool TryCommand(const std::string& command) + { + std::array buffer{}; + std::string output{}; -bool TryCommand(const std::string& command) -{ - std::string buffer; - buffer.resize(MaxPathOrCMD); - FILE* in; + FILE* const in = popen(command.data(), "r"); + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + output += buffer.data(); - in = popen(command.data(), "r"); - if(fgets(buffer.data(), buffer.size(), in) == nullptr) - { pclose(in); - return true; - } - - pclose(in); - return false; -} -//-------------------------------------------------------------------------------------------------------------------// + return !output.empty(); + } -bool TKinter3Present() -{ - static int32_t tkinter3Present = -1; - std::string pythonCommand; - std::string pythonParams = "-S -c \"try:\n\timport tkinter;\nexcept:\n\tprint(0);\""; + //-------------------------------------------------------------------------------------------------------------------// - if(tkinter3Present < 0) + [[nodiscard]] bool TKinter3Present() { - tkinter3Present = 0; - if(Python3Present()) + static int32_t tkinter3Present = -1; + + if(tkinter3Present < 0) { - pythonCommand = Python3Name + " " + pythonParams; - tkinter3Present = TryCommand(pythonCommand); + tkinter3Present = 0; + if(Python3Present()) + { + static const std::string pythonParams = "-S -c \"try:\n\timport tkinter;\n\tprint(1);\nexcept:\n\tpass\""; + const std::string pythonCommand = Python3Name + " " + pythonParams; + tkinter3Present = TryCommand(pythonCommand); + } } - } - - return tkinter3Present && GraphicMode() && !IsDarwin(); -} -//-------------------------------------------------------------------------------------------------------------------// + return tkinter3Present && GraphicMode() && !IsDarwin(); + } -int32_t KDialogPresent() -{ - static int32_t kdialogPresent = -1; - std::string buffer; - buffer.resize(MaxPathOrCMD); - FILE* in; - std::string desktop; + //-------------------------------------------------------------------------------------------------------------------// - if(kdialogPresent < 0) + [[nodiscard]] int32_t KDialogPresent() { - if(ZenityPresent()) + static int32_t kdialogPresent = -1; + + if(kdialogPresent < 0) { - auto desktopEnv = std::getenv("XDG_SESSION_DESKTOP"); - if(!desktopEnv) + if(ZenityPresent()) { - desktopEnv = std::getenv("XDG_CURRENT_DESKTOP"); + auto desktopEnv = std::getenv("XDG_SESSION_DESKTOP"); if(!desktopEnv) { - desktopEnv = std::getenv("DESKTOP_SESSION"); - + desktopEnv = std::getenv("XDG_CURRENT_DESKTOP"); if(!desktopEnv) { - kdialogPresent = 0; - return kdialogPresent; + desktopEnv = std::getenv("DESKTOP_SESSION"); + + if(!desktopEnv) + { + kdialogPresent = 0; + return kdialogPresent; + } } } + const std::string desktop(desktopEnv); + if(desktop.empty() || (desktop != "KDE" && desktop != "kde" && desktop != "lxqt" && desktop != "LXQT")) + { + kdialogPresent = 0; + return kdialogPresent; + } } - desktop = std::string(desktopEnv); - if(desktop.empty() || ((desktop != "KDE" || desktop != "kde") && (desktop != "lxqt" || desktop != "LXQT"))) - { - kdialogPresent = 0; - return kdialogPresent; - } - } - kdialogPresent = DetectPresence("kdialog"); - if(kdialogPresent) - { - in = popen("kdialog --attach 2>&1", "r"); - if(fgets(buffer.data(), buffer.size(), in) != nullptr) + kdialogPresent = DetectPresence("kdialog"); + if(kdialogPresent) { - if (buffer.find("Unknown") == std::string::npos) - kdialogPresent = 2; - } - pclose(in); - } - } - - return GraphicMode() ? kdialogPresent : 0; -} - -//-------------------------------------------------------------------------------------------------------------------// - -bool FileExists(const std::string& filePathAndName) -{ - FILE* in; - - if(filePathAndName.empty()) - return false; - - in = fopen(filePathAndName.data(), "r"); - if(!in) - return false; - - fclose(in); - return true; -} - -#endif + std::array buffer{}; + std::string output{}; -//-------------------------------------------------------------------------------------------------------------------// - -std::string GetPathWithoutFinalSlash(const std::string& source) -{ - std::string result; - - if (!source.empty()) - { - std::size_t index = source.find_last_of('/'); - if (index == std::string_view::npos) - index = source.find_last_of('\\'); - if (index != std::string_view::npos) - result = source.substr(0, index); - else - return {}; - } - else - return {}; - - return result; -} + FILE* const in = popen("kdialog --attach 2>&1", "r"); + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + output += buffer.data(); -//-------------------------------------------------------------------------------------------------------------------// + pclose(in); -std::string GetLastName(const std::string& source) -{ - std::string result; + if (output.find("Unknown") == std::string::npos) + kdialogPresent = 2; + } + } - if (!source.empty()) - { - std::size_t index = source.find_last_of('/'); - if (index == std::string_view::npos) - index = source.find_last_of('\\'); - if (index != std::string_view::npos) - result = source.substr(index + 1); - else - return source; + return GraphicMode() ? kdialogPresent : 0; } - else - return {}; - - return result; -} - -//-------------------------------------------------------------------------------------------------------------------// -bool FilenameValid(const std::string_view filenameWithoutPath) -{ - if (filenameWithoutPath.empty()) - return false; - - return std::all_of(filenameWithoutPath.cbegin(), filenameWithoutPath.cend(), [](const char c) - { - return c != '\\' && c != '/' && c != ':' && c != '*' && c != '?' && - c != '\"' && c != '<' && c != '>' && c != '|'; - }); -} - -//-------------------------------------------------------------------------------------------------------------------// - -bool QuoteDetected(const std::string_view str) -{ - if (str.empty()) - return false; - - if (str.find_first_of('\'') != std::string_view::npos || str.find_first_of('\"') != std::string_view::npos) - return true; - - return false; -} - -//-------------------------------------------------------------------------------------------------------------------// + //-------------------------------------------------------------------------------------------------------------------// -std::string MD::SaveFile(const std::string& title, - const std::string& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allFiles) -{ - static std::string buffer{}; + constexpr std::string_view XPropCmd = " --attach=$(sleep .01;printf \"%d\" $(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2))"; - if (QuoteDetected(title)) - return SaveFile("INVALID TITLE WITH QUOTES", defaultPathAndFile, filterPatterns, allFiles); - if (QuoteDetected(defaultPathAndFile)) - return SaveFile(title, "INVALID DEFAULT_PATH WITH QUOTES", filterPatterns, allFiles); - for(const auto& filterPattern : filterPatterns) - { - if (QuoteDetected(filterPattern.first) || QuoteDetected(filterPattern.second)) - return SaveFile("INVALID FILTER_PATTERN WITH QUOTES", defaultPathAndFile, {}, allFiles); - } + //-------------------------------------------------------------------------------------------------------------------// - std::string path{}; -#ifdef _WIN32 - path = SaveFileWinGUI(title, defaultPathAndFile, filterPatterns, allFiles); - buffer = path; -#else - std::string dialogString; - if(KDialogPresent()) + [[nodiscard]] CPP20Constexpr std::string GetKDialogFileCommandFilterPart(const std::vector>& filterPatterns, + const bool allFiles) { - dialogString = "kdialog"; - if (KDialogPresent() == 2 && XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - dialogString += " --getsavefilename "; - - if (!defaultPathAndFile.empty()) - { - if (defaultPathAndFile[0] != '/') - dialogString += "$PWD/"; - dialogString += "\"" + defaultPathAndFile + "\""; - } - else - dialogString += "$PWD/"; + std::string dialogString{}; if(!filterPatterns.empty()) { dialogString += " \""; - for(uint32_t i = 0; i < filterPatterns.size(); i++) + for(const auto& [name, extensions] : filterPatterns) { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += filterPatterns[i].first + " (" + filterPatterns[i].second + ")\n"; + if(extensions.find(';') == std::string::npos) + dialogString += name + " (" + extensions + ")\n"; else { - std::string extensions = filterPatterns[i].second; - std::replace(extensions.begin(), extensions.end(), ';', ' '); - dialogString += filterPatterns[i].first + " (" + extensions + ")\n"; + std::string exts = extensions; + std::replace(exts.begin(), exts.end(), ';', ' '); + dialogString += name + " (" + exts + ")\n"; } } } + if(allFiles && filterPatterns.empty()) dialogString += "\"All Files (*.*)\""; else if(allFiles && !filterPatterns.empty()) @@ -1075,414 +922,913 @@ std::string MD::SaveFile(const std::string& title, dialogString.pop_back(); dialogString += "\""; } - if(!title.empty()) - dialogString += " --title \"" + title + "\""; + + return dialogString; } - else if(ZenityPresent() || MateDialogPresent() || ShellementaryPresent() || QarmaPresent()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetKDialogBaseFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles, + const std::string& commandAction) { - if(ZenityPresent()) + std::string dialogString = "kdialog"; + + if (KDialogPresent() == 2 && XPropPresent()) + dialogString += XPropCmd; + + dialogString += " " + commandAction + " "; + + if (!defaultPathAndFile.empty()) { - dialogString = "zenity "; - if(Zenity3Present() >= 4 && XPropPresent()) - dialogString += " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; + if (defaultPathAndFile[0] != '/') + dialogString += "$PWD/"; + dialogString += "\"" + defaultPathAndFile + "\""; } - else if(MateDialogPresent()) - dialogString = "matedialog"; - else if(ShellementaryPresent()) - dialogString = "shellementary"; else - { - dialogString = "qarma"; - if(XPropPresent()) - dialogString += " --attach$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - dialogString += " --file-selection --save --confirm-overwrite"; + dialogString += "$PWD/"; + + dialogString += GetKDialogFileCommandFilterPart(filterPatterns, allFiles); if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!defaultPathAndFile.empty()) - dialogString += " --filename=\"" + defaultPathAndFile + "\""; + dialogString += " --title \"" + title + "\""; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetKDialogSaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + return GetKDialogBaseFileCommand(title, defaultPathAndFile, filterPatterns, allFiles, "--getsavefilename"); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetKDialogOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::string dialogAction = "--getopenfilename"; + if(allowMultipleSelects) + dialogAction += " --multiple --separate-output"; + + return GetKDialogBaseFileCommand(title, defaultPathAndFile, filterPatterns, allFiles, dialogAction); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] CPP20Constexpr std::string GetGenericFileCommandFilterPart(const std::vector>& filterPatterns, + const bool allFiles) + { + std::string dialogString{}; + if(!filterPatterns.empty()) { - for(uint32_t i = 0; i < filterPatterns.size(); i++) + for(const auto& [name, extensions] : filterPatterns) { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + filterPatterns[i].second + "'"; + if(extensions.find(';') == std::string::npos) + dialogString += " --file-filter='" + name + " | " + extensions + "'"; else { - std::string extensions = filterPatterns[i].second; + std::string exts = extensions; std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, " | "); - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + extensions + "'"; + while((index = exts.find(';')) != std::string::npos) + exts.replace(index, 1, " | "); + dialogString += " --file-filter='" + name + " | " + exts + "'"; } } } + if(allFiles) dialogString += " --file-filter='All Files | *'"; - dialogString += " 2>/dev/null "; + + return dialogString; } - else if(YadPresent()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetYadBaseFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles, + const std::string& commandAction) { - dialogString = "yad --file-selection --save --confirm-overwrite"; + std::string dialogString = "yad " + commandAction; + if(!title.empty()) dialogString += " --title=\"" + title + "\""; + if(!defaultPathAndFile.empty()) dialogString += " --filename=\"" + defaultPathAndFile + "\""; - if(!filterPatterns.empty()) - { - for(uint32_t i = 0; i < filterPatterns.size(); i++) - { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + filterPatterns[i].second + "'"; - else - { - std::string extensions = filterPatterns[i].second; - std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, " | "); - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + extensions + "'"; - } - } - } - if(allFiles) - dialogString += " --file-filter='All Files | *'"; + + dialogString += GetGenericFileCommandFilterPart(filterPatterns, allFiles); + dialogString += " 2>/dev/null "; + + return dialogString; } - else if(TKinter3Present()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetYadSaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) { - dialogString = Python3Name + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; - dialogString += "res=filedialog.asksaveasfilename("; - if(!title.empty()) - dialogString += "title='" + title + "',"; - if(!defaultPathAndFile.empty()) - { - std::string str = GetPathWithoutFinalSlash(defaultPathAndFile); - if(!str.empty()) - dialogString += "initialdir='" + str + "',"; - str = GetLastName(defaultPathAndFile); - if(!str.empty()) - dialogString += "initialfile='" + str + "',"; - } + return GetYadBaseFileCommand(title, defaultPathAndFile, filterPatterns, allFiles, "--file --save --confirm-overwrite"); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetYadOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::string dialogAction = "--file"; + if(allowMultipleSelects) + dialogAction += " --multiple"; + + return GetYadBaseFileCommand(title, defaultPathAndFile, filterPatterns, allFiles, dialogAction); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] CPP20Constexpr std::string GetTKinter3FileCommandFilterPart(const std::vector>& filterPatterns, + const bool allFiles) + { + std::string dialogString{}; + if(!filterPatterns.empty() && filterPatterns[0].second[filterPatterns[0].second.size() - 1] != '*') { dialogString += "filetypes=("; - for(uint32_t i = 0; i < filterPatterns.size(); i++) + for(const auto& [name, extensions] : filterPatterns) { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += "('" + filterPatterns[i].first + "',('" + filterPatterns[i].second + "',)),"; + if(extensions.find(';') == std::string::npos) + dialogString += "('" + name + "',('" + extensions + "',)),"; else { - std::string extensions = filterPatterns[i].second; + std::string exts = extensions; std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, "','"); - dialogString += "('" + filterPatterns[i].first + "',('" + extensions + "',)),"; + while((index = exts.find(';')) != std::string::npos) + exts.replace(index, 1, "','"); + dialogString += "('" + name + "',('" + exts + "',)),"; } } } + if(allFiles && !filterPatterns.empty()) dialogString += "('All Files','*'))"; else if(!allFiles && !filterPatterns.empty()) dialogString += ")"; - dialogString += ");\nif not isinstance(res, tuple):\n\tprint(res)\n\""; + + return dialogString; } - FILE* in; - if(!(in = popen(dialogString.data(), "r"))) - return {}; - buffer.resize(MaxPathOrCMD); - while (fgets(buffer.data(), buffer.size(), in) != nullptr){} - pclose(in); - uint32_t off = 0; - for(const auto& c : buffer) + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetTKinter3SaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) { - if(c == '\0') - break; - off++; - } - buffer.resize(off); - if (buffer[buffer.size() - 1] == '\n') - buffer.pop_back(); - if (buffer.empty()) - return {}; - path = buffer; -#endif + std::string dialogString = Python3Name + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; - if (path.empty()) - return {}; - std::string str = GetPathWithoutFinalSlash(path); - if (str.empty() && !DirExists(str)) - return {}; - str = GetLastName(path); - if (!FilenameValid(str)) - return {}; + dialogString += "res=filedialog.asksaveasfilename("; - return path; -} + if(!title.empty()) + dialogString += "title='" + title + "',"; -//-------------------------------------------------------------------------------------------------------------------// -#include -std::vector MD::OpenFile(const std::string& title, - const std::string& defaultPathAndFile, - const std::vector>& filterPatterns, - const bool allowMultipleSelects, - const bool allFiles) -{ - if (QuoteDetected(title)) - return OpenFile("INVALID TITLE WITH QUOTES", defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); - if (QuoteDetected(defaultPathAndFile)) - return OpenFile(title, "INVALID DEFAULT_PATH WITH QUOTES", filterPatterns, allowMultipleSelects, allFiles); - for(const auto& [fst, snd] : filterPatterns) - { - if (QuoteDetected(fst) || QuoteDetected(snd)) - return OpenFile("INVALID FILTER_PATTERN WITH QUOTES", defaultPathAndFile, {}, allowMultipleSelects, allFiles); + if(!defaultPathAndFile.empty()) + { + std::string str = GetPathWithoutFinalSlash(defaultPathAndFile); + if(!str.empty()) + dialogString += "initialdir='" + str + "',"; + str = GetLastName(defaultPathAndFile); + if(!str.empty()) + dialogString += "initialfile='" + str + "',"; + } + + dialogString += GetTKinter3FileCommandFilterPart(filterPatterns, allFiles); + + dialogString += ");\nif not isinstance(res, tuple):\n\tprint(res)\n\""; + + return dialogString; } - std::vector paths{}; -#ifdef _WIN32 - paths = OpenFileWinGUI(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); -#else - std::string dialogString; - bool wasKDialog = false; - if(KDialogPresent()) + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetTKinter3OpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) { - wasKDialog = true; - dialogString = "kdialog "; - if(KDialogPresent() == 2 && XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - dialogString += " --getopenfilename "; + std::string dialogString = Python3Name + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; + + dialogString += "lFiles=filedialog.askopenfilename("; + if(allowMultipleSelects) - dialogString += "--multiple --separate-output "; + dialogString += "multiple=1,"; + + if(!title.empty()) + dialogString += "title='" + title + "',"; + if(!defaultPathAndFile.empty()) { - if(defaultPathAndFile[0] != '/') - dialogString += "$PWD/"; - dialogString += "\"" + defaultPathAndFile + "\""; + std::string tmp = GetPathWithoutFinalSlash(defaultPathAndFile); + if(!tmp.empty()) + dialogString += "initialdir='" + tmp + "',"; + tmp = GetLastName(defaultPathAndFile); + if(!tmp.empty()) + dialogString += "initialfile='" + tmp + "',"; } - else - dialogString += "$PWD/"; - if(!filterPatterns.empty()) - { - dialogString += " \""; - for(uint32_t i = 0; i < filterPatterns.size(); i++) - { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += filterPatterns[i].first + " (" + filterPatterns[i].second + ")\n"; - else - { - std::string extensions = filterPatterns[i].second; - std::replace(extensions.begin(), extensions.end(), ';', ' '); - dialogString += filterPatterns[i].first + " (" + extensions + ")\n"; - } - } - } - if(allFiles && filterPatterns.empty()) - dialogString += "\"All Files (*.*)\""; - else if(allFiles && !filterPatterns.empty()) - dialogString += "All Files (*.*)\""; - else if(!allFiles && !filterPatterns.empty()) - { - dialogString.pop_back(); - dialogString += "\""; - } - if(!title.empty()) - dialogString += " --title \"" + title + "\""; + dialogString += GetTKinter3FileCommandFilterPart(filterPatterns, allFiles); + + dialogString += ");\nif not isinstance(lFiles, tuple):\n\tprint(lFiles)\nelse:\n\tlFilesString=''\n\t"; + dialogString += "for lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\n\tprint(lFilesString[:-1])\n\""; + + return dialogString; } - else if(ZenityPresent() || MateDialogPresent() || ShellementaryPresent() || QarmaPresent()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericBaseFileCommandPart(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) { - if(ZenityPresent()) - { - dialogString = "zenity"; - if(Zenity3Present() >= 4 && XPropPresent()) - dialogString += " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - else if(MateDialogPresent()) - dialogString = "matedialog"; - else if(ShellementaryPresent()) - dialogString = "shellementary"; - else - { - dialogString = "qarma"; - if(XPropPresent()) - dialogString += " --attach=$(xprop --root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - dialogString += " --file-selection"; + std::string dialogString{}; - if(allowMultipleSelects) - dialogString += " --multiple"; if(!title.empty()) dialogString += " --title=\"" + title + "\""; if(!defaultPathAndFile.empty()) dialogString += " --filename=\"" + defaultPathAndFile + "\""; - if(!filterPatterns.empty()) - { - for(uint32_t i = 0; i < filterPatterns.size(); i++) - { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + filterPatterns[i].second + "'"; - else - { - std::string extensions = filterPatterns[i].second; - std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, " | "); - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + extensions + "'"; - } - } - } - if(allFiles) - dialogString += " --file-filter='All Files | *'"; + dialogString += GetGenericFileCommandFilterPart(filterPatterns, allFiles); dialogString += " 2>/dev/null "; + + return dialogString; } - else if(YadPresent()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericSaveFileCommandPart(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) { - dialogString = "yad --file-selection"; + return " --file-selection --save --confirm-overwrite" + GetGenericBaseFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericOpenFileCommandPart(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::string dialogAction = " --file-selection"; if(allowMultipleSelects) - dialogString += " --multiple"; + dialogAction += " --multiple"; + + return dialogAction + GetGenericBaseFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetZenitySaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + std::string dialogString = "zenity "; + if(Zenity3Present() >= 4 && XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericSaveFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetZenityOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::string dialogString = "zenity"; + if(Zenity3Present() >= 4 && XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericOpenFileCommandPart(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetMateDialogSaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + return std::string("matedialog") + GetGenericSaveFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetMateDialogOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + return std::string("matedialog") + GetGenericOpenFileCommandPart(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetShellementarySaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + return std::string("shellementary") + GetGenericSaveFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetShellementaryOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + return std::string("shellementary") + GetGenericOpenFileCommandPart(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetQarmaSaveFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) + { + std::string dialogString = "qarma"; + if(XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericSaveFileCommandPart(title, defaultPathAndFile, filterPatterns, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetQarmaOpenFileCommand(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) + { + std::string dialogString = "qarma"; + if(XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericOpenFileCommandPart(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericSelectFolderCommandPart(const std::string& title, const std::string& defaultPath) + { + return " --file-selection --directory" + GetGenericBaseFileCommandPart(title, defaultPath, {}, false); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetZenitySelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + std::string dialogString = "zenity"; + if(Zenity3Present() >= 4 && XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericSelectFolderCommandPart(title, defaultPath); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetMateDialogSelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + return "matedialog" + GetGenericSelectFolderCommandPart(title, defaultPath); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetShellementarySelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + return "shellementary" + GetGenericSelectFolderCommandPart(title, defaultPath); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetQarmaSelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + std::string dialogString = "qarma"; + if(XPropPresent()) + dialogString += XPropCmd; + + return dialogString + GetGenericSelectFolderCommandPart(title, defaultPath); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetKDialogSelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + return GetKDialogBaseFileCommand(title, defaultPath, {}, false, "--getexistingdirectory"); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetYadSelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + return GetYadBaseFileCommand(title, defaultPath, {}, false, "--file --directory"); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetTKinter3SelectFolderCommand(const std::string& title, const std::string& defaultPath) + { + std::string dialogString = Python3Name; + dialogString += " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; + dialogString += "res=filedialog.askdirectory("; if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!defaultPathAndFile.empty()) - dialogString += " --filename=\"" + defaultPathAndFile + "\""; - if(!filterPatterns.empty()) + dialogString += "title='" + title + "',"; + if(!defaultPath.empty()) + dialogString += "initialdir='" + defaultPath + "'"; + dialogString += ");\nif not isinstance(res, tuple):\n\tprint(res)\n\""; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetKDialogMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + std::string dialogString = "kdialog"; + if (KDialogPresent() == 2 && XPropPresent()) + dialogString += XPropCmd; + + dialogString += " --"; + if (buttons == MD::Buttons::OKCancel || buttons == MD::Buttons::YesNo) { - for(uint32_t i = 0; i < filterPatterns.size(); i++) - { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + filterPatterns[i].second + "'"; - else - { - std::string extensions = filterPatterns[i].second; - std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, " | "); - dialogString += " --file-filter='" + filterPatterns[i].first + " | " + extensions + "'"; - } - } + if (style == MD::Style::Warning || style == MD::Style::Error) + dialogString += "warning"; + dialogString += "yesno"; } - if(allFiles) - dialogString += " --file-filter='All Files | *'"; - dialogString += " 2>/dev/null "; + else if (style == MD::Style::Error) + dialogString += "error"; + else if (style == MD::Style::Warning) + dialogString += "sorry"; + else + dialogString += "msgbox"; + dialogString += " \""; + if (!message.empty()) + dialogString += message; + dialogString += "\""; + if (buttons == MD::Buttons::OKCancel) + dialogString += " --yes-label OK --no-label Cancel"; + if (buttons == MD::Buttons::Quit) + dialogString += " --ok-label Quit"; + if (!title.empty()) + dialogString += " --title \"" + title + "\""; + + dialogString += ";if [ $? = 0 ];then echo 1;else echo 0;fi"; + + return dialogString; } - else if(TKinter3Present()) + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetYadMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) { - dialogString = Python3Name + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; - dialogString += "lFiles=filedialog.askopenfilename("; - if(allowMultipleSelects) - dialogString += "multiple=1,"; + std::string dialogString = "szAnswer=$(yad --"; + + if(buttons == MD::Buttons::OK) + dialogString += "button=OK:1"; + else if(buttons == MD::Buttons::OKCancel) + dialogString += "button=OK:1 --button=Cancel:0"; + else if(buttons == MD::Buttons::YesNo) + dialogString += "button=Yes:1 --button=No:0"; + else if(style == MD::Style::Error) + dialogString += "error"; + else if(style == MD::Style::Warning) + dialogString += "warning"; + else if(style == MD::Style::Question) + dialogString += "question"; + else + dialogString += "info"; + + if(!title.empty()) + dialogString += " --title=\"" + title + "\""; + if(!message.empty()) + dialogString += " --text=\"" + message + "\""; + + dialogString += " --image=dialog-"; + if(style == MD::Style::Error) + dialogString += "error"; + else if(style == MD::Style::Warning) + dialogString += "warning"; + else if(style == MD::Style::Question) + dialogString += "question"; + else + dialogString += "information"; + + dialogString += " 2>/dev/null "; + dialogString += ");echo $?"; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetTKinter3MsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + std::string dialogString = Python3Name; + + dialogString += " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();"; + dialogString += "res=messagebox."; + + if(buttons == MD::Buttons::OKCancel) + dialogString += "askokcancel("; + else if(buttons == MD::Buttons::YesNo) + dialogString += "askyesno("; + else + dialogString += "showinfo("; + + dialogString += "icon='"; + + if(style == MD::Style::Error) + dialogString += "error"; + else if(style == MD::Style::Question) + dialogString += "question"; + else if(style == MD::Style::Warning) + dialogString += "warning"; + else + dialogString += "info"; + + dialogString += "',"; + if(!title.empty()) dialogString += "title='" + title + "',"; - if(!defaultPathAndFile.empty()) + if(!message.empty()) { - std::string tmp; - tmp = GetPathWithoutFinalSlash(defaultPathAndFile); - if(!tmp.empty()) - dialogString += "initialdir='" + tmp + "',"; - tmp = GetLastName(defaultPathAndFile); - if(!tmp.empty()) - dialogString += "initialfile='" + tmp + "',"; + std::string msg = message; + std::size_t p = std::string::npos; + while((p = msg.find('\n')) != std::string::npos) + msg.replace(p, 1, "\\n"); + + dialogString += "message='" + msg + "'"; } - if(!filterPatterns.empty() && filterPatterns[0].second[filterPatterns[0].second.size() - 1] != '*') + + dialogString += ");\nif res is False :\n\tprint (0)\nelse :\n\tprint (1)\n\""; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericMsgBoxCommandPart(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons, + const std::string& commandAction, + const std::string& iconCommand = "") + { + std::string dialogString = "szAnswer=$(" + commandAction + " --"; + + if(buttons == MD::Buttons::OKCancel) + dialogString += "question --ok-label=OK --cancel-label=Cancel"; + else if(buttons == MD::Buttons::YesNo) + dialogString += "question"; + else if(style == MD::Style::Error) + dialogString += "error"; + else if(style == MD::Style::Warning) + dialogString += "warning"; + else + dialogString += "info"; + + if(buttons == MD::Buttons::Quit) + dialogString += " --ok-label=Quit"; + + if(!title.empty()) + dialogString += " --title=\"" + title + "\""; + if(!message.empty()) + dialogString += " --text=\"" + message + "\""; + + dialogString += iconCommand; + + dialogString += " 2>/dev/null "; + dialogString += ");if [ $? = 0 ];then echo 1;else echo 0;fi"; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetGenericMsgBoxIconCommandPart(const MD::Style style) + { + std::string dialogString = " --icon-name=dialog-"; + if(style == MD::Style::Question) + dialogString += "question"; + else if(style == MD::Style::Error) + dialogString += "error"; + else if(style == MD::Style::Warning) + dialogString += "warning"; + else + dialogString += "information"; + + return dialogString; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetZenityMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + std::string commandAction = "zenity"; + if(Zenity3Present() >= 4 && XPropPresent()) + commandAction += XPropCmd; + + return GetGenericMsgBoxCommandPart(title, message, style, buttons, commandAction, GetGenericMsgBoxIconCommandPart(style)); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetMateDialogMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + return GetGenericMsgBoxCommandPart(title, message, style, buttons, "matedialog"); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetShellementaryMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + return GetGenericMsgBoxCommandPart(title, message, style, buttons, "shellementary", GetGenericMsgBoxIconCommandPart(style)); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] std::string GetQarmaMsgBoxCommand(const std::string& title, + const std::string& message, + const MD::Style style, + const MD::Buttons buttons) + { + + std::string dialogString = "qarma"; + if(XPropPresent()) + dialogString += XPropCmd; + + return GetGenericMsgBoxCommandPart(title, message, style, buttons, dialogString, GetGenericMsgBoxIconCommandPart(style)); + } + + #endif + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] CPP20Constexpr bool FilenameValid(const std::string_view filenameWithoutPath) + { + if (filenameWithoutPath.empty()) + return false; + + return std::all_of(filenameWithoutPath.cbegin(), filenameWithoutPath.cend(), [](const char c) { - dialogString += "filetypes=("; - for(uint32_t i = 0; i < filterPatterns.size(); i++) - { - if(filterPatterns[i].second.find(';') == std::string::npos) - dialogString += "('" + filterPatterns[i].first + "',('" + filterPatterns[i].second + "',)),"; - else - { - std::string extensions = filterPatterns[i].second; - std::size_t index = 0; - while((index = extensions.find(';')) != std::string::npos) - extensions.replace(index, 1, "','"); - dialogString += "('" + filterPatterns[i].first + "',('" + extensions + "',)),"; - } - } - } - if(allFiles && !filterPatterns.empty()) - dialogString += "('All Files','*'))"; - else if(!allFiles && !filterPatterns.empty()) - dialogString += ")"; - dialogString += ");\nif not isinstance(lFiles, tuple):\n\tprint(lFiles)\nelse:\n\tlFilesString=''\n\t"; - dialogString += "for lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\n\tprint(lFilesString[:-1])\n\""; + return c != '\\' && c != '/' && c != ':' && c != '*' && c != '?' && + c != '\"' && c != '<' && c != '>' && c != '|'; + }); + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] CPP20Constexpr bool QuoteDetected(const std::string_view str) + { + if (str.empty()) + return false; + + if (str.find_first_of('\'') != std::string_view::npos || str.find_first_of('\"') != std::string_view::npos) + return true; + + return false; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] bool DirExists(const std::string& dirPath) + { + std::error_code ec{}; + + const bool isDir = std::filesystem::is_directory(dirPath, ec); + if(ec != std::error_code{}) + return false; + + const bool dirExists = std::filesystem::exists(dirPath, ec); + if(ec != std::error_code{}) + return false; + + return isDir && dirExists; + } + + //-------------------------------------------------------------------------------------------------------------------// + + [[nodiscard]] bool FileExists(const std::string& filePathAndName) + { + std::error_code ec{}; + + const bool isFile = std::filesystem::is_regular_file(filePathAndName, ec); + if(ec != std::error_code{}) + return false; + + const bool fileExists = std::filesystem::exists(filePathAndName, ec); + if(ec != std::error_code{}) + return false; + + return isFile && fileExists; + } +} + +//-------------------------------------------------------------------------------------------------------------------// + +std::string MD::SaveFile(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allFiles) +{ + if (QuoteDetected(title)) + return SaveFile("INVALID TITLE WITH QUOTES", defaultPathAndFile, filterPatterns, allFiles); + if (QuoteDetected(defaultPathAndFile)) + return SaveFile(title, "INVALID DEFAULT_PATH WITH QUOTES", filterPatterns, allFiles); + for(const auto& filterPattern : filterPatterns) + { + if (QuoteDetected(filterPattern.first) || QuoteDetected(filterPattern.second)) + return SaveFile("INVALID FILTER_PATTERN WITH QUOTES", defaultPathAndFile, {}, allFiles); } - std::string buffer; - if (allowMultipleSelects) - buffer.resize(MaxMultipleFiles * MaxPathOrCMD + 1); - else - buffer.resize(MaxPathOrCMD + 1); + std::string path{}; +#ifdef _WIN32 + path = SaveFileWinGUI(title, defaultPathAndFile, filterPatterns, allFiles); +#else + std::string dialogString{}; + if(KDialogPresent()) + dialogString = GetKDialogSaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(ZenityPresent()) + dialogString = GetZenitySaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(MateDialogPresent()) + dialogString = GetMateDialogSaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(ShellementaryPresent()) + dialogString = GetShellementarySaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(QarmaPresent()) + dialogString = GetQarmaSaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(YadPresent()) + dialogString = GetYadSaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + else if(TKinter3Present()) + dialogString = GetTKinter3SaveFileCommand(title, defaultPathAndFile, filterPatterns, allFiles); + + FILE* in = popen(dialogString.data(), "r"); + if(in == nullptr) + return ""; + + std::array buffer{}; + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + path += buffer.data(); + pclose(in); + + if (!path.empty() && path.back() == '\n') + path.pop_back(); +#endif + + if (path.empty()) + return ""; + std::string str = GetPathWithoutFinalSlash(path); + if (str.empty() || !DirExists(str)) + return ""; + str = GetLastName(path); + if (!FilenameValid(str)) + return ""; + + return path; +} + +//-------------------------------------------------------------------------------------------------------------------// - FILE* in; - if(!(in = popen(dialogString.data(), "r"))) +std::vector MD::OpenFile(const std::string& title, + const std::string& defaultPathAndFile, + const std::vector>& filterPatterns, + const bool allowMultipleSelects, + const bool allFiles) +{ + if (QuoteDetected(title)) + return OpenFile("INVALID TITLE WITH QUOTES", defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + if (QuoteDetected(defaultPathAndFile)) + return OpenFile(title, "INVALID DEFAULT_PATH WITH QUOTES", filterPatterns, allowMultipleSelects, allFiles); + for(const auto& [fst, snd] : filterPatterns) { - buffer = {}; - return {}; + if (QuoteDetected(fst) || QuoteDetected(snd)) + return OpenFile("INVALID FILTER_PATTERN WITH QUOTES", defaultPathAndFile, {}, allowMultipleSelects, allFiles); } - char* data = buffer.data(); - while(fgets(data, &buffer[buffer.size() - 1] - data, in) != nullptr) + + std::vector paths{}; +#ifdef _WIN32 + paths = OpenFileWinGUI(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); +#else + std::string dialogString{}; + bool wasKDialog = false; + if(KDialogPresent()) { - uint64_t off = 0; - for(const char& c : buffer) - { - if(c == '\0') - break; - off++; - } - data = buffer.data() + off; + wasKDialog = true; + dialogString = GetKDialogOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); } + else if(ZenityPresent()) + dialogString = GetZenityOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + else if(MateDialogPresent()) + dialogString = GetMateDialogOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + else if(ShellementaryPresent()) + dialogString = GetShellementaryOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + else if(QarmaPresent()) + dialogString = GetQarmaOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + else if(YadPresent()) + dialogString = GetYadOpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + else if(TKinter3Present()) + dialogString = GetTKinter3OpenFileCommand(title, defaultPathAndFile, filterPatterns, allowMultipleSelects, allFiles); + + FILE* in = popen(dialogString.data(), "r"); + if(in == nullptr) + return {}; + + std::array buffer{}; + std::string tmp{}; + while(fgets(buffer.data(), buffer.size(), in) != nullptr) + tmp += buffer.data(); + pclose(in); - uint32_t off = 0; - for(const auto& c : buffer) - { - if(c == '\0') - break; - off++; - } - buffer.resize(off); - if(buffer[buffer.size() - 1] == '\n') - buffer.pop_back(); + if(!tmp.empty() && tmp.back() == '\n') + tmp.pop_back(); - if(wasKDialog && allowMultipleSelects) - { - buffer += '\n'; - std::size_t pos = 0; - while((pos = buffer.find('\n')) != std::string::npos) - { - std::string token = buffer.substr(0, pos); - paths.push_back(token); - buffer.erase(0, pos + 1); - } - } - else if(allowMultipleSelects) + char separator = '|'; + if(wasKDialog) + separator = '\n'; + + if(!tmp.empty()) { - buffer += '|'; - std::size_t pos = 0; - while((pos = buffer.find('|')) != std::string::npos) + if(allowMultipleSelects) { - std::string token = buffer.substr(0, pos); - paths.push_back(token); - buffer.erase(0, pos + 1); + tmp += separator; + std::size_t pos = 0; + while((pos = tmp.find(separator)) != std::string::npos) + { + std::string token = tmp.substr(0, pos); + paths.push_back(std::move(token)); + tmp.erase(0, pos + 1); + } } + else + paths.push_back(tmp); } - else if(!allowMultipleSelects) - paths.push_back(buffer); #endif if (paths.empty()) return {}; - if (allowMultipleSelects && paths.size() > 1) + for(auto it = paths.begin(); it != paths.end();) { - for(auto it = paths.begin(); it != paths.end(); ++it) - { - if(!FileExists(*it)) - it = paths.erase(it); - } + if(!FileExists(*it)) + it = paths.erase(it); + else + ++it; } - else if (!FileExists(paths[0])) - return {}; return paths; } @@ -1494,7 +1840,7 @@ std::string MD::OpenSingleFile(const std::string& title, const std::vector>& filterPatterns, const bool allFiles) { - std::vector path = MD::OpenFile(title, defaultPathAndFile, filterPatterns, false, allFiles); + const std::vector path = MD::OpenFile(title, defaultPathAndFile, filterPatterns, false, allFiles); return path.empty() ? std::string() : path[0]; } @@ -1513,8 +1859,6 @@ std::vector MD::OpenMultipleFiles(const std::string& title, std::string MD::SelectFolder(const std::string& title, const std::string& defaultPath) { - static std::string buffer; - if (QuoteDetected(title)) return MD::SelectFolder("INVALID TITLE WITH QUOTES", defaultPath); if (QuoteDetected(defaultPath)) @@ -1523,100 +1867,40 @@ std::string MD::SelectFolder(const std::string& title, const std::string& defaul std::string path{}; #ifdef _WIN32 path = SelectFolderWinGUI(title, defaultPath); - buffer = path; #else - buffer.resize(MaxPathOrCMD); std::string dialogString; if(KDialogPresent()) - { - dialogString = "kdialog"; - if(KDialogPresent() == 2 && XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - dialogString += " --getexistingdirectory "; - - if(!defaultPath.empty()) - { - if(defaultPath[0] != '/') - dialogString += "$PWD/"; - dialogString += "\"" + defaultPath + "\""; - } - else - dialogString += "$PWD/"; - - if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - } - else if(ZenityPresent() || MateDialogPresent() || ShellementaryPresent() || QarmaPresent()) - { - if(ZenityPresent()) - { - dialogString = "zenity"; - if(Zenity3Present() >= 4 && XPropPresent()) - dialogString += " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - else if(MateDialogPresent()) - dialogString = "matedialog"; - else if(ShellementaryPresent()) - dialogString = "shellementary"; - else - { - dialogString = "qarma"; - if(XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - dialogString += " --file-selection --directory"; - - if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!defaultPath.empty()) - dialogString += " --filename=\"" + defaultPath + "\""; - dialogString += " 2>/dev/null "; - } + dialogString = GetKDialogSelectFolderCommand(title, defaultPath); + else if(ZenityPresent()) + dialogString = GetZenitySelectFolderCommand(title, defaultPath); + else if(MateDialogPresent()) + dialogString = GetMateDialogSelectFolderCommand(title, defaultPath); + else if(ShellementaryPresent()) + dialogString = GetShellementarySelectFolderCommand(title, defaultPath); + else if(QarmaPresent()) + dialogString = GetQarmaSelectFolderCommand(title, defaultPath); else if(YadPresent()) - { - dialogString = "yad --file-selection --directory"; - if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!defaultPath.empty()) - dialogString += " --filename=\"" + defaultPath + "\""; - dialogString += " 2>/dev/null "; - } + dialogString = GetYadSelectFolderCommand(title, defaultPath); else if(TKinter3Present()) - { - dialogString = Python3Name; - dialogString += " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"; - dialogString += "res=filedialog.askdirectory("; - if(!title.empty()) - dialogString += "title='" + title + "',"; - if(!defaultPath.empty()) - dialogString += "initialdir='" + defaultPath + "'"; - dialogString += ");\nif not isinstance(res, tuple):\n\tprint(res)\n\""; - } + dialogString = GetTKinter3SelectFolderCommand(title, defaultPath); - FILE* in; - if(!(in = popen(dialogString.data(), "r"))) - return {}; + FILE* in = popen(dialogString.data(), "r"); + if(in == nullptr) + return ""; + + std::array buffer{}; while(fgets(buffer.data(), buffer.size(), in) != nullptr) - {} - uint32_t off = 0; - for(const auto& c : buffer) - { - if(c == '\0') - break; - off++; - } - buffer.resize(off); + path += buffer.data(); - if(buffer[buffer.size() - 1] == '\n') - buffer.pop_back(); + if(!path.empty() && path.back() == '\n') + path.pop_back(); - if(!DirExists(buffer)) - return {}; - path = buffer; + if(!DirExists(path)) + return ""; #endif if (path.empty()) - return {}; + return ""; return path; } @@ -1628,8 +1912,6 @@ MD::Selection MD::ShowMsgBox(const std::string& title, const MD::Style style, const MD::Buttons buttons) { - std::string buffer; - buffer.resize(1024); MD::Selection selection = MD::Selection::Error; if (QuoteDetected(title)) @@ -1642,181 +1924,36 @@ MD::Selection MD::ShowMsgBox(const std::string& title, #else std::string dialogString; if(KDialogPresent()) - { - dialogString = "kdialog"; - if (KDialogPresent() == 2 && XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - - dialogString += " --"; - if (buttons == MD::Buttons::OKCancel || buttons == MD::Buttons::YesNo) - { - if (style == MD::Style::Warning || style == MD::Style::Error) - dialogString += "warning"; - dialogString += "yesno"; - } - else if (style == MD::Style::Error) - dialogString += "error"; - else if (style == MD::Style::Warning) - dialogString += "sorry"; - else - dialogString += "msgbox"; - dialogString += " \""; - if (!message.empty()) - dialogString += message; - dialogString += "\""; - if (buttons == MD::Buttons::OKCancel) - dialogString += " --yes-label OK --no-label Cancel"; - if (buttons == MD::Buttons::Quit) - dialogString += " --ok-label Quit"; - if (!title.empty()) - dialogString += " --title \"" + title + "\""; - - dialogString += ";if [ $? = 0 ];then echo 1;else echo 0;fi"; - } - else if(ZenityPresent() || MateDialogPresent() || ShellementaryPresent() || QarmaPresent()) - { - if(ZenityPresent()) - { - dialogString = "szAnswer=$(zenity"; - if(Zenity3Present() >= 4 && XPropPresent()) - dialogString += " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - else if(MateDialogPresent()) - dialogString = "szAnswer=$(matedialog"; - else if(ShellementaryPresent()) - dialogString = "szAnswer=$(shellementary"; - else - { - dialogString = "szAnswer=$(qarma"; - if(XPropPresent()) - dialogString += " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"; - } - dialogString += " --"; - - if(buttons == MD::Buttons::OKCancel) - dialogString += "question --ok-label=OK --cancel-label=Cancel"; - else if(buttons == MD::Buttons::YesNo) - dialogString += "question"; - else if(style == MD::Style::Error) - dialogString += "error"; - else if(style == MD::Style::Warning) - dialogString += "warning"; - else - dialogString += "info"; - - if(buttons == MD::Buttons::Quit) - dialogString += " --ok-label=Quit"; - - if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!message.empty()) - dialogString += " --text=\"" + message + "\""; - - if (Zenity3Present() >= 3 || - (!ZenityPresent() && - (ShellementaryPresent() || QarmaPresent()))) - { - dialogString += " --icon-name=dialog-"; - if(style == MD::Style::Question) - dialogString += "question"; - else if(style == MD::Style::Error) - dialogString += "error"; - else if(style == MD::Style::Warning) - dialogString += "warning"; - else - dialogString += "information"; - } - - dialogString += " 2>/dev/null "; - dialogString += ");if [ $? = 0 ];then echo 1;else echo 0;fi"; - } + dialogString = GetKDialogMsgBoxCommand(title, message, style, buttons); + else if(ZenityPresent()) + dialogString = GetZenityMsgBoxCommand(title, message, style, buttons); + else if(MateDialogPresent()) + dialogString = GetMateDialogMsgBoxCommand(title, message, style, buttons); + else if(ShellementaryPresent()) + dialogString = GetShellementaryMsgBoxCommand(title, message, style, buttons); + else if(QarmaPresent()) + dialogString = GetQarmaMsgBoxCommand(title, message, style, buttons); else if(YadPresent()) - { - dialogString += "szAnswer=$(yad --"; - - if(buttons == MD::Buttons::OK) - dialogString += "button=OK:1"; - else if(buttons == MD::Buttons::OKCancel) - dialogString += "button=OK:1 --button=Cancel:0"; - else if(buttons == MD::Buttons::YesNo) - dialogString += "button=Yes:1 --button=No:0"; - else if(style == MD::Style::Error) - dialogString += "error"; - else if(style == MD::Style::Warning) - dialogString += "warning"; - else if(style == MD::Style::Question) - dialogString += "question"; - else - dialogString += "info"; - - if(!title.empty()) - dialogString += " --title=\"" + title + "\""; - if(!message.empty()) - dialogString += " --text=\"" + message + "\""; - - dialogString += " 2>/dev/null "; - dialogString += ");echo $?"; - } + dialogString = GetYadMsgBoxCommand(title, message, style, buttons); else if(TKinter3Present()) - { - dialogString = Python3Name; - - dialogString += " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();"; - dialogString += "res=messagebox."; - - if(buttons == MD::Buttons::OKCancel) - dialogString += "askokcancel("; - else if(buttons == MD::Buttons::YesNo) - dialogString += "askyesno("; - else - dialogString += "showinfo("; - - dialogString += "icon='"; - - if(style == MD::Style::Error) - dialogString += "error"; - else if(style == MD::Style::Question) - dialogString += "question"; - else if(style == MD::Style::Warning) - dialogString += "warning"; - else - dialogString += "info"; - - dialogString += "',"; - - if(!title.empty()) - dialogString += "title='" + title + "',"; - if(!message.empty()) - { - std::string msg = message; - std::size_t p = std::string::npos; - while((p = msg.find('\n')) != std::string::npos) - msg.replace(p, 1, "\\n"); - - dialogString += "message='" + msg + "'"; - } + dialogString = GetTKinter3MsgBoxCommand(title, message, style, buttons); + else + selection = Selection::None; - dialogString += ");\nif res is False :\n\tprint (0)\nelse :\n\tprint (1)\n\""; - } + std::array buffer{}; + std::string tmp{}; - FILE* in; - if(!(in = popen(dialogString.data(), "r"))) + FILE* in = popen(dialogString.data(), "r"); + if(in == nullptr) return {}; + while(fgets(buffer.data(), buffer.size(), in) != nullptr) - {} - uint32_t off = 0; - for(const auto& c : buffer) - { - if(c == '\0') - break; - off++; - } - buffer.resize(off); + tmp += buffer.data(); - if(buffer[buffer.size() - 1] == '\n') - buffer.pop_back(); + if(!tmp.empty() && tmp.back() == '\n') + tmp.pop_back(); - if (buffer == "1") + if (tmp == "1") { if (buttons == MD::Buttons::YesNo) selection = MD::Selection::Yes; @@ -1827,7 +1964,7 @@ MD::Selection MD::ShowMsgBox(const std::string& title, else if (buttons == MD::Buttons::Quit) selection = MD::Selection::Quit; } - else if (buffer == "0") + else if (tmp == "0") { if (buttons == MD::Buttons::YesNo) selection = MD::Selection::No; @@ -1838,8 +1975,6 @@ MD::Selection MD::ShowMsgBox(const std::string& title, else if (buttons == MD::Buttons::Quit) selection = MD::Selection::Quit; } - else - selection = MD::Selection::Quit; #endif return selection; diff --git a/ModernDialogs/ModernDialogs.h b/ModernDialogs/ModernDialogs.h index fe156bf..418c3f5 100644 --- a/ModernDialogs/ModernDialogs.h +++ b/ModernDialogs/ModernDialogs.h @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2020 - 2023 Jan "GamesTrap" Schürkamp +Copyright (c) 2020 - 2025 Jan "GamesTrap" Schürkamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2b70f02..8a4a781 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Then, execute one of the generator scripts in the GeneratorScripts folder. MIT License -Copyright (c) 2020-2023 Jan "GamesTrap" Schürkamp +Copyright (c) 2020-2025 Jan "GamesTrap" Schürkamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/libs/premake5/linux/example.so b/libs/premake5/linux/example.so deleted file mode 100644 index a50b104..0000000 Binary files a/libs/premake5/linux/example.so and /dev/null differ diff --git a/libs/premake5/linux/libluasocket.so b/libs/premake5/linux/libluasocket.so deleted file mode 100644 index 92ced02..0000000 Binary files a/libs/premake5/linux/libluasocket.so and /dev/null differ diff --git a/libs/premake5/linux/premake5 b/libs/premake5/linux/premake5 index bcb51ce..f8ec5cb 100644 Binary files a/libs/premake5/linux/premake5 and b/libs/premake5/linux/premake5 differ diff --git a/libs/premake5/windows/example.dll b/libs/premake5/windows/example.dll deleted file mode 100644 index 38d869a..0000000 Binary files a/libs/premake5/windows/example.dll and /dev/null differ diff --git a/libs/premake5/windows/example.exp b/libs/premake5/windows/example.exp deleted file mode 100644 index 8a28c9f..0000000 Binary files a/libs/premake5/windows/example.exp and /dev/null differ diff --git a/libs/premake5/windows/luasocket.dll b/libs/premake5/windows/luasocket.dll deleted file mode 100644 index 5768400..0000000 Binary files a/libs/premake5/windows/luasocket.dll and /dev/null differ diff --git a/libs/premake5/windows/luasocket.exp b/libs/premake5/windows/luasocket.exp deleted file mode 100644 index a7f2ae7..0000000 Binary files a/libs/premake5/windows/luasocket.exp and /dev/null differ diff --git a/libs/premake5/windows/premake5.exe b/libs/premake5/windows/premake5.exe index 1a637aa..4f4b2ed 100644 Binary files a/libs/premake5/windows/premake5.exe and b/libs/premake5/windows/premake5.exe differ diff --git a/premake5.lua b/premake5.lua index 4d5bd37..a99648c 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,5 +1,8 @@ workspace "ModernDialogs" - configurations { "Debug32", "Release32", "Debug64", "Release64" } + configurations { "Debug", "Release" } + platforms { "x86", "x86_64"} + + defaultplatform "x86_64" startproject "Example" @@ -8,23 +11,39 @@ workspace "ModernDialogs" "MultiProcessorCompile" } - filter "configurations:*32" + filter "platforms:x86" architecture "x86" - filter "configurations:*64" + filter "platforms:x86_64" architecture "x86_64" outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}" +newoption { + trigger = "std", + value = "C++XX", + description = "Select the C++ standard to use for compilation, defaults to C++-17", + allowed = { + { "C++latest", "Latest C++ standard availble for the toolset"}, + { "C++17", "ISO C++17 standard"}, + { "C++2a", "ISO C++20 draft"}, + { "C++20", "ISO C++20 standard"}, + { "C++2b", "ISO C++23 draft"}, + { "C++23", "ISO C++23 standard"}, + }, + default = "C++17" +} + project "ModernDialogs" location "ModernDialogs" kind "StaticLib" language "C++" staticruntime "off" - cppdialect "C++17" systemversion "latest" warnings "Extra" + cppdialect (_OPTIONS["std"] or "C++17") + targetdir ("bin/" .. outputdir .. "/%{prj.group}/%{prj.name}") objdir ("bin-int/" .. outputdir .. "/%{prj.group}/%{prj.name}")