diff --git a/CMakeLists.txt b/CMakeLists.txt index bc7b5f9..e2422f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,17 @@ target_include_directories(imgui PUBLIC ) target_link_libraries(imgui PUBLIC SDL3::SDL3) +# import imguifiledialog + +add_library(imguifiledialog STATIC) +target_sources(imguifiledialog PRIVATE + ${PROJECT_SOURCE_DIR}/imguifiledialog/ImGuiFileDialog.cpp +) +target_include_directories(imguifiledialog PUBLIC + ${PROJECT_SOURCE_DIR}/imguifiledialog +) +target_link_libraries(imguifiledialog PUBLIC imgui) + # editor add_executable(yeti3dpro-editor) @@ -32,4 +43,4 @@ target_sources(yeti3dpro-editor PRIVATE ${PROJECT_SOURCE_DIR}/source/main.cpp ${PROJECT_SOURCE_DIR}/source/editor.cpp ) -target_link_libraries(yeti3dpro-editor PRIVATE imgui y3d game) +target_link_libraries(yeti3dpro-editor PRIVATE imguifiledialog imgui y3d game) diff --git a/imguifiledialog/ImGuiFileDialog.cpp b/imguifiledialog/ImGuiFileDialog.cpp new file mode 100644 index 0000000..318f922 --- /dev/null +++ b/imguifiledialog/ImGuiFileDialog.cpp @@ -0,0 +1,5157 @@ + +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/* +MIT License + +Copyright (c) 2019-2024 Stephane Cuillerdier (aka aiekick) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "ImGuiFileDialog.h" + +#ifdef __cplusplus + +#include // stricmp / strcasecmp +#include // variadic +#include +#include +#include +#include +#include +#include +#include + +// this option need c++17 +#ifdef USE_STD_FILESYSTEM +#include +#include +#endif // USE_STD_FILESYSTEM + +#ifdef __EMSCRIPTEN__ +#include +#endif // __EMSCRIPTEN__ + +#ifdef _MSC_VER + +#define IGFD_DEBUG_BREAK \ + if (IsDebuggerPresent()) __debugbreak() +#else +#define IGFD_DEBUG_BREAK +#endif + +#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) || defined(__WIN64__) || defined(WIN64) || defined(_WIN64) || defined(_MSC_VER) +#define _IGFD_WIN_ +#define stat _stati64 +#define stricmp _stricmp +#include +// this option need c++17 +#ifdef USE_STD_FILESYSTEM +#include +#else +#include "dirent/dirent.h" // directly open the dirent file attached to this lib +#endif // USE_STD_FILESYSTEM +#define PATH_SEP '\\' +#ifndef PATH_MAX +#define PATH_MAX 260 +#endif // PATH_MAX +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__EMSCRIPTEN__) +#define _IGFD_UNIX_ +#define stricmp strcasecmp +#include +// this option need c++17 +#ifndef USE_STD_FILESYSTEM +#include +#endif // USE_STD_FILESYSTEM +#define PATH_SEP '/' +#endif // _IGFD_UNIX_ + +#include "imgui.h" +#include "imgui_internal.h" + +// legacy compatibility 1.89 +#ifndef IM_TRUNC +#define IM_TRUNC IM_FLOOR +#endif + +#include +#include +#include + +/////////////////////////////// +// STB IMAGE LIBS +/////////////////////////////// + +#ifdef USE_THUMBNAILS +#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#endif // STB_IMAGE_IMPLEMENTATION +#endif // DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +#include "stb/stb_image.h" +#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION +#endif // DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb/stb_image_resize2.h" +#endif // USE_THUMBNAILS + +/////////////////////////////// +// FLOAT MACROS +/////////////////////////////// + +// float comparisons +#ifndef IS_FLOAT_DIFFERENT +#define IS_FLOAT_DIFFERENT(a, b) (fabs((a) - (b)) > FLT_EPSILON) +#endif // IS_FLOAT_DIFFERENT +#ifndef IS_FLOAT_EQUAL +#define IS_FLOAT_EQUAL(a, b) (fabs((a) - (b)) < FLT_EPSILON) +#endif // IS_FLOAT_EQUAL + +/////////////////////////////// +// COMBOBOX +/////////////////////////////// + +#ifndef FILTER_COMBO_AUTO_SIZE +#define FILTER_COMBO_AUTO_SIZE 1 +#endif // FILTER_COMBO_AUTO_SIZE +#ifndef FILTER_COMBO_MIN_WIDTH +#define FILTER_COMBO_MIN_WIDTH 150.0f +#endif // FILTER_COMBO_MIN_WIDTH +#ifndef IMGUI_BEGIN_COMBO +#define IMGUI_BEGIN_COMBO ImGui::BeginCombo +#endif // IMGUI_BEGIN_COMBO + +/////////////////////////////// +// BUTTON +/////////////////////////////// + +// for lets you define your button widget +// if you have like me a special bi-color button +#ifndef IMGUI_PATH_BUTTON +#define IMGUI_PATH_BUTTON ImGui::Button +#endif // IMGUI_PATH_BUTTON +#ifndef IMGUI_BUTTON +#define IMGUI_BUTTON ImGui::Button +#endif // IMGUI_BUTTON + +/////////////////////////////// +// locales +/////////////////////////////// + +#ifndef createDirButtonString +#define createDirButtonString "+" +#endif // createDirButtonString +#ifndef okButtonString +#define okButtonString "OK" +#endif // okButtonString +#ifndef okButtonWidth +#define okButtonWidth 0.0f +#endif // okButtonWidth +#ifndef cancelButtonString +#define cancelButtonString "Cancel" +#endif // cancelButtonString +#ifndef cancelButtonWidth +#define cancelButtonWidth 0.0f +#endif // cancelButtonWidth +#ifndef okCancelButtonAlignement +#define okCancelButtonAlignement 0.0f +#endif // okCancelButtonAlignement +#ifndef invertOkAndCancelButtons +// 0 => disabled, 1 => enabled +#define invertOkAndCancelButtons 0 +#endif // invertOkAndCancelButtons +#ifndef resetButtonString +#define resetButtonString "R" +#endif // resetButtonString +#ifndef devicesButtonString +#define devicesButtonString "Devices" +#endif // devicesButtonString +#ifndef editPathButtonString +#define editPathButtonString "E" +#endif // editPathButtonString +#ifndef searchString +#define searchString "Search :" +#endif // searchString +#ifndef dirEntryString +#define dirEntryString "[Dir]" +#endif // dirEntryString +#ifndef linkEntryString +#define linkEntryString "[Link]" +#endif // linkEntryString +#ifndef fileEntryString +#define fileEntryString "[File]" +#endif // fileEntryString +#ifndef fileNameString +#define fileNameString "File Name:" +#endif // fileNameString +#ifndef dirNameString +#define dirNameString "Directory Path:" +#endif // dirNameString +#ifndef buttonResetSearchString +#define buttonResetSearchString "Reset search" +#endif // buttonResetSearchString +#ifndef buttonDriveString +#define buttonDriveString "Devices" +#endif // buttonDriveString +#ifndef buttonEditPathString +#define buttonEditPathString "Edit path\nYou can also right click on path buttons" +#endif // buttonEditPathString +#ifndef buttonResetPathString +#define buttonResetPathString "Reset to current directory" +#endif // buttonResetPathString +#ifndef buttonCreateDirString +#define buttonCreateDirString "Create Directory" +#endif // buttonCreateDirString +#ifndef tableHeaderAscendingIcon +#define tableHeaderAscendingIcon "A|" +#endif // tableHeaderAscendingIcon +#ifndef tableHeaderDescendingIcon +#define tableHeaderDescendingIcon "D|" +#endif // tableHeaderDescendingIcon +#ifndef tableHeaderFileNameString +#define tableHeaderFileNameString "File name" +#endif // tableHeaderFileNameString +#ifndef tableHeaderFileTypeString +#define tableHeaderFileTypeString "Type" +#endif // tableHeaderFileTypeString +#ifndef tableHeaderFileSizeString +#define tableHeaderFileSizeString "Size" +#endif // tableHeaderFileSizeString +#ifndef tableHeaderFileDateString +#define tableHeaderFileDateString "Date" +#endif // tableHeaderFileDateString +#ifndef fileSizeBytes +#define fileSizeBytes "o" +#endif // fileSizeBytes +#ifndef fileSizeKiloBytes +#define fileSizeKiloBytes "Ko" +#endif // fileSizeKiloBytes +#ifndef fileSizeMegaBytes +#define fileSizeMegaBytes "Mo" +#endif // fileSizeMegaBytes +#ifndef fileSizeGigaBytes +#define fileSizeGigaBytes "Go" +#endif // fileSizeGigaBytes +#ifndef OverWriteDialogTitleString +#define OverWriteDialogTitleString "The selected file already exists!" +#endif // OverWriteDialogTitleString +#ifndef OverWriteDialogMessageString +#define OverWriteDialogMessageString "Are you sure you want to overwrite it?" +#endif // OverWriteDialogMessageString +#ifndef OverWriteDialogConfirmButtonString +#define OverWriteDialogConfirmButtonString "Confirm" +#endif // OverWriteDialogConfirmButtonString +#ifndef OverWriteDialogCancelButtonString +#define OverWriteDialogCancelButtonString "Cancel" +#endif // OverWriteDialogCancelButtonString +#ifndef DateTimeFormat +// see strftime functionin for customize +#define DateTimeFormat "%Y/%m/%d %H:%M" +#endif // DateTimeFormat + +/////////////////////////////// +//// SHORTCUTS => ctrl + KEY +/////////////////////////////// + +#ifndef SelectAllFilesKey +#define SelectAllFilesKey ImGuiKey_A +#endif // SelectAllFilesKey + +/////////////////////////////// +// THUMBNAILS +/////////////////////////////// + +#ifdef USE_THUMBNAILS +#ifndef tableHeaderFileThumbnailsString +#define tableHeaderFileThumbnailsString "Thumbnails" +#endif // tableHeaderFileThumbnailsString +#ifndef DisplayMode_FilesList_ButtonString +#define DisplayMode_FilesList_ButtonString "FL" +#endif // DisplayMode_FilesList_ButtonString +#ifndef DisplayMode_FilesList_ButtonHelp +#define DisplayMode_FilesList_ButtonHelp "File List" +#endif // DisplayMode_FilesList_ButtonHelp +#ifndef DisplayMode_ThumbailsList_ButtonString +#define DisplayMode_ThumbailsList_ButtonString "TL" +#endif // DisplayMode_ThumbailsList_ButtonString +#ifndef DisplayMode_ThumbailsList_ButtonHelp +#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" +#endif // DisplayMode_ThumbailsList_ButtonHelp +#ifndef DisplayMode_ThumbailsGrid_ButtonString +#define DisplayMode_ThumbailsGrid_ButtonString "TG" +#endif // DisplayMode_ThumbailsGrid_ButtonString +#ifndef DisplayMode_ThumbailsGrid_ButtonHelp +#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" +#endif // DisplayMode_ThumbailsGrid_ButtonHelp +#ifndef DisplayMode_ThumbailsList_ImageHeight +#define DisplayMode_ThumbailsList_ImageHeight 32.0f +#endif // DisplayMode_ThumbailsList_ImageHeight +#ifndef IMGUI_RADIO_BUTTON +inline bool inRadioButton(const char* vLabel, bool vToggled) { + bool pressed = false; + if (vToggled) { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } + pressed = IMGUI_BUTTON(vLabel); + if (vToggled) { + ImGui::PopStyleColor(4); //-V112 + } + return pressed; +} +#define IMGUI_RADIO_BUTTON inRadioButton +#endif // IMGUI_RADIO_BUTTON +#endif // USE_THUMBNAILS + +/////////////////////////////// +// PLACES +/////////////////////////////// + +#ifdef USE_PLACES_FEATURE +#ifndef defaultPlacePaneWith +#define defaultPlacePaneWith 150.0f +#endif // defaultPlacePaneWith +#ifndef placesButtonString +#define placesButtonString "Places" +#endif // placesButtonString +#ifndef placesButtonHelpString +#define placesButtonHelpString "Places" +#endif // placesButtonHelpString +#ifndef placesBookmarksGroupName +#define placesBookmarksGroupName "Bookmarks" +#endif // placesBookmarksGroupName +#ifndef PLACES_BOOKMARK_DEFAULT_OPEPEND +#define PLACES_BOOKMARK_DEFAULT_OPEPEND true +#endif // PLACES_BOOKMARK_DEFAULT_OPEPEND +#ifndef PLACES_DEVICES_DEFAULT_OPEPEND +#define PLACES_DEVICES_DEFAULT_OPEPEND true +#endif // PLACES_DEVICES_DEFAULT_OPEPEND +#ifndef placesBookmarksDisplayOrder +#define placesBookmarksDisplayOrder 0 +#endif // placesBookmarksDisplayOrder +#ifndef placesDevicesGroupName +#define placesDevicesGroupName "Devices" +#endif // placesDevicesGroupName +#ifndef placesDevicesDisplayOrder +#define placesDevicesDisplayOrder 10 +#endif // placesDevicesDisplayOrder +#ifndef addPlaceButtonString +#define addPlaceButtonString "+" +#endif // addPlaceButtonString +#ifndef removePlaceButtonString +#define removePlaceButtonString "-" +#endif // removePlaceButtonString +#ifndef validatePlaceButtonString +#define validatePlaceButtonString "ok" +#endif // validatePlaceButtonString +#ifndef editPlaceButtonString +#define editPlaceButtonString "E" +#endif // editPlaceButtonString +#ifndef PLACES_PANE_DEFAULT_SHOWN +#define PLACES_PANE_DEFAULT_SHOWN false +#endif // PLACES_PANE_DEFAULT_SHOWN +#ifndef IMGUI_TOGGLE_BUTTON +inline bool inToggleButton(const char* vLabel, bool* vToggled) { + bool pressed = false; + + if (vToggled && *vToggled) { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + // ImVec4 buh = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + // ImVec4 bu = ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } + + pressed = IMGUI_BUTTON(vLabel); + + if (vToggled && *vToggled) { + ImGui::PopStyleColor(4); //-V112 + } + + if (vToggled && pressed) *vToggled = !*vToggled; + + return pressed; +} +#define IMGUI_TOGGLE_BUTTON inToggleButton +#endif // IMGUI_TOGGLE_BUTTON +#endif // USE_PLACES_FEATURE + +class IGFDException : public std::exception { +private: + char const* m_msg{}; + +public: + IGFDException() : std::exception() { + } + explicit IGFDException(char const* const vMsg) + : std::exception(), // std::exception(msg) is not availaiable on linux it seems... but on windos yes + m_msg(vMsg) { + } + char const* what() const noexcept override { + return m_msg; + } +}; + +#ifndef CUSTOM_FILESYSTEM_INCLUDE +#ifdef USE_STD_FILESYSTEM + +static std::filesystem::path stringToPath(const std::string& str) { +#ifdef _IGFD_WIN_ + return std::filesystem::path(IGFD::Utils::UTF8Decode(str)); +#else + return std::filesystem::path(str); +#endif +} + +static std::string pathToString(const std::filesystem::path& path) { +#ifdef _IGFD_WIN_ + return IGFD::Utils::UTF8Encode(path.wstring()); +#else + return path.string(); +#endif +} + +class FileSystemStd : public IGFD::IFileSystem { +public: + bool IsDirectoryCanBeOpened(const std::string& vName) override { + bool bExists = false; + if (!vName.empty()) { + namespace fs = std::filesystem; + auto pathName = stringToPath(vName); + try { + // interesting, in the case of a protected dir or for any reason the dir cant be opened + // this func will work but will say nothing more . not like the dirent version + bExists = fs::is_directory(pathName); + // test if can be opened, this function can thrown an exception if there is an issue with this dir + // here, the dir_iter is need else not exception is thrown.. + const auto dir_iter = fs::directory_iterator(pathName); + (void)dir_iter; // for avoid unused warnings + } catch (const std::exception& /*ex*/) { + // fail so this dir cant be opened + bExists = false; + } + } + return bExists; // this is not a directory! + } + bool IsDirectoryExist(const std::string& vName) override { + if (!vName.empty()) { + namespace fs = std::filesystem; + return fs::is_directory(stringToPath(vName)); + } + return false; // this is not a directory! + } + bool IsFileExist(const std::string& vName) override { + namespace fs = std::filesystem; + return fs::is_regular_file(stringToPath(vName)); + } + bool CreateDirectoryIfNotExist(const std::string& vName) override { + if (vName.empty()) return false; + if (IsDirectoryExist(vName)) return true; + +#if defined(__EMSCRIPTEN__) + std::string str = std::string("FS.mkdir('") + vName + "');"; + emscripten_run_script(str.c_str()); + bool res = true; +#else + namespace fs = std::filesystem; + bool res = fs::create_directory(stringToPath(vName)); +#endif // _IGFD_WIN_ + if (!res) { + std::cout << "Error creating directory " << vName << std::endl; + } + return res; + } + + std::vector GetDevicesList() override { + std::vector res; +#ifdef _IGFD_WIN_ + const DWORD mydevices = 2048; + char lpBuffer[2048]; +#define mini(a, b) (((a) < (b)) ? (a) : (b)) + const DWORD countChars = mini(GetLogicalDriveStringsA(mydevices, lpBuffer), 2047); +#undef mini + if (countChars > 0U && countChars < 2049U) { + std::string var = std::string(lpBuffer, (size_t)countChars); + IGFD::Utils::ReplaceString(var, "\\", ""); + auto arr = IGFD::Utils::SplitStringToVector(var, '\0', false); + wchar_t szVolumeName[2048]; + IGFD::PathDisplayedName path_name; + for (auto& a : arr) { + path_name.first = a; + path_name.second.clear(); + std::wstring wpath = IGFD::Utils::UTF8Decode(a); + if (GetVolumeInformationW(wpath.c_str(), szVolumeName, 2048, nullptr, nullptr, nullptr, nullptr, 0)) { + path_name.second = IGFD::Utils::UTF8Encode(szVolumeName); + } + res.push_back(path_name); + } + } +#endif // _IGFD_WIN_ + return res; + } + + IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) override { + // https://github.com/aiekick/ImGuiFileDialog/issues/54 + namespace fs = std::filesystem; + IGFD::Utils::PathStruct res; + if (vPathFileName.empty()) return res; + auto fsPath = stringToPath(vPathFileName); + if (fs::is_directory(fsPath)) { + res.name = ""; + res.path = pathToString(fsPath); + res.isOk = true; + } else if (fs::is_regular_file(fsPath)) { + res.name = pathToString(fsPath.filename()); + res.path = pathToString(fsPath.parent_path()); + res.isOk = true; + } + return res; + } + + std::vector ScanDirectory(const std::string& vPath) override { + std::vector res; + try { + namespace fs = std::filesystem; + auto fspath = stringToPath(vPath); + const auto dir_iter = fs::directory_iterator(fspath); + IGFD::FileType fstype = IGFD::FileType(IGFD::FileType::ContentType::Directory, fs::is_symlink(fs::status(fspath))); + { + IGFD::FileInfos file_two_dot; + file_two_dot.filePath = vPath; + file_two_dot.fileNameExt = ".."; + file_two_dot.fileType = fstype; + res.push_back(file_two_dot); + } + for (const auto& file : dir_iter) { + try { + IGFD::FileType fileType; + if (file.is_symlink()) { + fileType.SetSymLink(file.is_symlink()); + fileType.SetContent(IGFD::FileType::ContentType::LinkToUnknown); + } + if (file.is_directory()) { + fileType.SetContent(IGFD::FileType::ContentType::Directory); + } // directory or symlink to directory + else if (file.is_regular_file()) { + fileType.SetContent(IGFD::FileType::ContentType::File); + } + if (fileType.isValid()) { + auto fileNameExt = pathToString(file.path().filename()); + { + IGFD::FileInfos _file; + _file.filePath = vPath; + _file.fileNameExt = fileNameExt; + _file.fileType = fileType; + res.push_back(_file); + } + } + } catch (const std::exception& ex) { + std::cout << "IGFD : " << ex.what() << std::endl; + } + } + } catch (const std::exception& ex) { + std::cout << "IGFD : " << ex.what() << std::endl; + } + return res; + } + bool IsDirectory(const std::string& vFilePathName) override { + namespace fs = std::filesystem; + return fs::is_directory(stringToPath(vFilePathName)); + } +}; +#define FILE_SYSTEM_OVERRIDE FileSystemStd +#else +class FileSystemDirent : public IGFD::IFileSystem { +public: + bool IsDirectoryCanBeOpened(const std::string& vName) override { + if (!vName.empty()) { + DIR* pDir = nullptr; + // interesting, in the case of a protected dir or for any reason the dir cant be opened + // this func will fail + pDir = opendir(vName.c_str()); + if (pDir != nullptr) { + (void)closedir(pDir); + return true; + } + } + return false; + } + bool IsDirectoryExist(const std::string& vName) override { + bool bExists = false; + if (!vName.empty()) { + DIR* pDir = nullptr; + pDir = opendir(vName.c_str()); + if (pDir) { + bExists = true; + closedir(pDir); + } else if (ENOENT == errno) { + /* Directory does not exist. */ + // bExists = false; + } else { + /* opendir() failed for some other reason. + like if a dir is protected, or not accessable with user right + */ + bExists = true; + } + } + return bExists; + } + bool IsFileExist(const std::string& vName) override { + std::ifstream docFile(vName, std::ios::in); + if (docFile.is_open()) { + docFile.close(); + return true; + } + return false; + } + bool CreateDirectoryIfNotExist(const std::string& vName) override { + bool res = false; + if (!vName.empty()) { + if (!IsDirectoryExist(vName)) { +#ifdef _IGFD_WIN_ + std::wstring wname = IGFD::Utils::UTF8Decode(vName); + if (CreateDirectoryW(wname.c_str(), nullptr)) { + res = true; + } +#elif defined(__EMSCRIPTEN__) // _IGFD_WIN_ + std::string str = std::string("FS.mkdir('") + vName + "');"; + emscripten_run_script(str.c_str()); + res = true; +#elif defined(_IGFD_UNIX_) + char buffer[PATH_MAX] = {}; + snprintf(buffer, PATH_MAX, "mkdir -p \"%s\"", vName.c_str()); + const int dir_err = std::system(buffer); + if (dir_err != -1) { + res = true; + } +#endif // _IGFD_WIN_ + if (!res) { + std::cout << "Error creating directory " << vName << std::endl; + } + } + } + + return res; + } + + std::vector GetDevicesList() override { + std::vector res; +#ifdef _IGFD_WIN_ + const DWORD mydevices = 2048; + char lpBuffer[2048]; +#define mini(a, b) (((a) < (b)) ? (a) : (b)) + const DWORD countChars = mini(GetLogicalDriveStringsA(mydevices, lpBuffer), 2047); +#undef mini + if (countChars > 0U && countChars < 2049U) { + std::string var = std::string(lpBuffer, (size_t)countChars); + IGFD::Utils::ReplaceString(var, "\\", ""); + auto arr = IGFD::Utils::SplitStringToVector(var, '\0', false); + wchar_t szVolumeName[2048]; + IGFD::PathDisplayedName path_name; + for (auto& a : arr) { + path_name.first = a; + path_name.second.clear(); + std::wstring wpath = IGFD::Utils::UTF8Decode(a); + if (GetVolumeInformationW(wpath.c_str(), szVolumeName, 2048, nullptr, nullptr, nullptr, nullptr, 0)) { + path_name.second = IGFD::Utils::UTF8Encode(szVolumeName); + } + res.push_back(path_name); + } + } +#endif // _IGFD_WIN_ + return res; + } + + IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) override { + IGFD::Utils::PathStruct res; + if (!vPathFileName.empty()) { + std::string pfn = vPathFileName; + std::string separator(1u, PATH_SEP); + IGFD::Utils::ReplaceString(pfn, "\\", separator); + IGFD::Utils::ReplaceString(pfn, "/", separator); + size_t lastSlash = pfn.find_last_of(separator); + if (lastSlash != std::string::npos) { + res.name = pfn.substr(lastSlash + 1); + res.path = pfn.substr(0, lastSlash); + res.isOk = true; + } + size_t lastPoint = pfn.find_last_of('.'); + if (lastPoint != std::string::npos) { + if (!res.isOk) { + res.name = pfn; + res.isOk = true; + } + res.ext = pfn.substr(lastPoint + 1); + IGFD::Utils::ReplaceString(res.name, "." + res.ext, ""); + } + if (!res.isOk) { + res.name = std::move(pfn); + res.isOk = true; + } + } + return res; + } + + std::vector ScanDirectory(const std::string& vPath) override { + std::vector res; + struct dirent** files = nullptr; + size_t n = scandir(vPath.c_str(), &files, nullptr, // + [](const struct dirent** a, const struct dirent** b) { // + return strcoll((*a)->d_name, (*b)->d_name); + }); + if (n && files) { + for (size_t i = 0; i < n; ++i) { + struct dirent* ent = files[i]; + IGFD::FileType fileType; + switch (ent->d_type) { + case DT_DIR: fileType.SetContent(IGFD::FileType::ContentType::Directory); break; + case DT_REG: fileType.SetContent(IGFD::FileType::ContentType::File); break; +#if defined(_IGFD_UNIX_) || (DT_LNK != DT_UNKNOWN) + case DT_LNK: +#endif + case DT_UNKNOWN: { + struct stat sb = {}; +#ifdef _IGFD_WIN_ + auto filePath = vPath + ent->d_name; +#else + auto filePath = vPath + IGFD::Utils::GetPathSeparator() + ent->d_name; +#endif + if (!stat(filePath.c_str(), &sb)) { + if (sb.st_mode & S_IFLNK) { + fileType.SetSymLink(true); + // by default if we can't figure out the target type. + fileType.SetContent(IGFD::FileType::ContentType::LinkToUnknown); + } + if (sb.st_mode & S_IFREG) { + fileType.SetContent(IGFD::FileType::ContentType::File); + break; + } else if (sb.st_mode & S_IFDIR) { + fileType.SetContent(IGFD::FileType::ContentType::Directory); + break; + } + } + break; + } + default: break; // leave it invalid (devices, etc.) + } + if (fileType.isValid()) { + IGFD::FileInfos _file; + _file.filePath = vPath; + _file.fileNameExt = ent->d_name; + _file.fileType = fileType; + res.push_back(_file); + } + } + for (size_t i = 0; i < n; ++i) { + free(files[i]); + } + free(files); + } + return res; + } + bool IsDirectory(const std::string& vFilePathName) override { + DIR* pDir = opendir(vFilePathName.c_str()); + if (pDir) { + (void)closedir(pDir); + return true; + } + return false; + } +}; +#define FILE_SYSTEM_OVERRIDE FileSystemDirent +#endif // USE_STD_FILESYSTEM +#else +#include CUSTOM_FILESYSTEM_INCLUDE +#endif // USE_CUSTOM_FILESYSTEM + +// https://github.com/ocornut/imgui/issues/1720 +bool IGFD::Utils::ImSplitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size) { + auto* window = ImGui::GetCurrentWindow(); + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + ImGui::CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); + return ImGui::SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 1.0f, 0.0, ImGui::GetColorU32(ImGuiCol_FrameBg)); +} + +// Convert a wide Unicode string to an UTF8 string +std::string IGFD::Utils::UTF8Encode(const std::wstring& wstr) { + std::string res; +#ifdef _IGFD_WIN_ + if (!wstr.empty()) { + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), nullptr, 0, nullptr, nullptr); + if (size_needed) { + res = std::string(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &res[0], size_needed, nullptr, nullptr); + } + } +#else + // Suppress warnings from the compiler. + (void)wstr; +#endif // _IGFD_WIN_ + return res; +} + +// Convert an UTF8 string to a wide Unicode String +std::wstring IGFD::Utils::UTF8Decode(const std::string& str) { + std::wstring res; +#ifdef _IGFD_WIN_ + if (!str.empty()) { + int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), nullptr, 0); + if (size_needed) { + res = std::wstring(size_needed, 0); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &res[0], size_needed); + } + } +#else + // Suppress warnings from the compiler. + (void)str; +#endif // _IGFD_WIN_ + return res; +} + +bool IGFD::Utils::ReplaceString(std::string& str, const ::std::string& oldStr, const ::std::string& newStr, const size_t& vMaxRecursion) { + if (!str.empty() && oldStr != newStr) { + bool res = false; + size_t pos = 0; + bool found = false; + size_t max_recursion = vMaxRecursion; + do { + pos = str.find(oldStr, pos); + if (pos != std::string::npos) { + found = res = true; + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } else if (found && max_recursion > 0) { // recursion loop + found = false; + pos = 0; + --max_recursion; + } + } while (pos != std::string::npos); + return res; + } + return false; +} + +std::vector IGFD::Utils::SplitStringToVector(const std::string& vText, const std::string& vDelimiterPattern, const bool& vPushEmpty) { + std::vector arr; + if (!vText.empty()) { + size_t start = 0; + size_t end = vText.find(vDelimiterPattern, start); + while (end != std::string::npos) { + auto token = vText.substr(start, end - start); + if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728 + arr.push_back(token); + } + start = end + vDelimiterPattern.size(); + end = vText.find(vDelimiterPattern, start); + } + auto token = vText.substr(start); + if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728 + arr.push_back(token); + } + } + return arr; +} + +std::vector IGFD::Utils::SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty) { + std::vector arr; + if (!vText.empty()) { + size_t start = 0; + size_t end = vText.find(vDelimiter, start); + while (end != std::string::npos) { + auto token = vText.substr(start, end - start); + if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728 + arr.push_back(token); + } + start = end + 1; + end = vText.find(vDelimiter, start); + } + auto token = vText.substr(start); + if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728 + arr.push_back(token); + } + } + return arr; +} + +void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) { + std::string st = vStr; + size_t len = vBufferLen - 1u; + size_t slen = strlen(vBuffer); + + if (!st.empty() && st != "\n") { + IGFD::Utils::ReplaceString(st, "\n", ""); + IGFD::Utils::ReplaceString(st, "\r", ""); + } + vBuffer[slen] = '\0'; + std::string str = std::string(vBuffer); + // if (!str.empty()) str += "\n"; + str += vStr; + if (len > str.size()) { + len = str.size(); + } +#ifdef _MSC_VER + strncpy_s(vBuffer, vBufferLen, str.c_str(), len); +#else // _MSC_VER + strncpy(vBuffer, str.c_str(), len); +#endif // _MSC_VER + vBuffer[len] = '\0'; +} + +void IGFD::Utils::ResetBuffer(char* vBuffer) { + vBuffer[0] = '\0'; +} + +void IGFD::Utils::SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) { + ResetBuffer(vBuffer); + AppendToBuffer(vBuffer, vBufferLen, vStr); +} + +std::string IGFD::Utils::LowerCaseString(const std::string& vString) { + auto str = vString; + + // convert to lower case + for (char& c : str) { + c = (char)std::tolower(c); + } + + return str; +} + +size_t IGFD::Utils::GetCharCountInString(const std::string& vString, const char& vChar) { + size_t res = 0U; + for (const auto& c : vString) { + if (c == vChar) { + ++res; + } + } + return res; +} + +size_t IGFD::Utils::GetLastCharPosWithMinCharCount(const std::string& vString, const char& vChar, const size_t& vMinCharCount) { + if (vMinCharCount) { + size_t last_dot_pos = vString.size() + 1U; + size_t count_dots = vMinCharCount; + while (count_dots > 0U && last_dot_pos > 0U && last_dot_pos != std::string::npos) { + auto new_dot = vString.rfind(vChar, last_dot_pos - 1U); + if (new_dot != std::string::npos) { + last_dot_pos = new_dot; + --count_dots; + } else { + break; + } + } + return last_dot_pos; + } + return std::string::npos; +} + +std::string IGFD::Utils::GetPathSeparator() { + return std::string(1U, PATH_SEP); +} + +std::string IGFD::Utils::RoundNumber(double vvalue, int n) { + std::stringstream tmp; + tmp << std::setprecision(n) << std::fixed << vvalue; + return tmp.str(); +} + +std::string IGFD::Utils::FormatFileSize(size_t vByteSize) { + if (vByteSize != 0) { + static double lo = 1024.0; + static double ko = 1024.0 * 1024.0; + static double mo = 1024.0 * 1024.0 * 1024.0; + const auto v = static_cast(vByteSize); + if (v < lo) + return RoundNumber(v, 0) + " " + fileSizeBytes; // octet + else if (v < ko) + return RoundNumber(v / lo, 2) + " " + fileSizeKiloBytes; // ko + else if (v < mo) + return RoundNumber(v / ko, 2) + " " + fileSizeMegaBytes; // Mo + else + return RoundNumber(v / mo, 2) + " " + fileSizeGigaBytes; // Go + } + + return "0 " fileSizeBytes; +} + +// https://cplusplus.com/reference/cstdlib/strtod +bool IGFD::Utils::M_IsAValidCharExt(const char& c) { + return c == '.' || // .5 + c == '-' || c == '+'; // -2.5 or +2.5; +} + +// https://cplusplus.com/reference/cstdlib/strtod +bool IGFD::Utils::M_IsAValidCharSuffix(const char& c) { + return c == 'e' || c == 'E' || // 1e5 or 1E5 + c == 'x' || c == 'X' || // 0x14 or 0X14 + c == 'p' || c == 'P'; // 6.2p2 or 3.2P-5 +} + +bool IGFD::Utils::M_ExtractNumFromStringAtPos(const std::string& str, size_t& pos, double& vOutNum) { + if (!str.empty() && pos < str.size()) { + const char fc = str.at(pos); // first char + // if the first char is not possible for a number we quit + if (std::isdigit(fc) || M_IsAValidCharExt(fc)) { + static constexpr size_t COUNT_CHAR = 64; + char buf[COUNT_CHAR + 1]; + size_t buf_p = 0; + bool is_last_digit = false; + bool is_last_suffix = false; + const auto& ss = str.size(); + while (ss > 1 && pos < ss && buf_p < COUNT_CHAR) { + const char& c = str.at(pos); + // a suffix must be after a number + if (is_last_digit && M_IsAValidCharSuffix(c)) { + is_last_suffix = true; + buf[buf_p++] = c; + } else if (std::isdigit(c)) { + is_last_suffix = false; + is_last_digit = true; + buf[buf_p++] = c; + } else if (M_IsAValidCharExt(c)) { + is_last_digit = false; + buf[buf_p++] = c; + } else { + break; + } + ++pos; + } + // if the last char is a suffix so its not a number + if (buf_p != 0 && !is_last_suffix) { + buf[buf_p] = '\0'; + char* endPtr; + vOutNum = strtod(buf, &endPtr); + // the edge cases for numbers will be next filtered by strtod + if (endPtr != buf) { + return true; + } + } + } + } + return false; +} + +// Fonction de comparaison naturelle entre deux cha�nes +bool IGFD::Utils::NaturalCompare(const std::string& vA, const std::string& vB, bool vInsensitiveCase, bool vDescending) { + std::size_t ia = 0, ib = 0; + double nA, nB; + const auto& as = vA.size(); + const auto& bs = vB.size(); + while (ia < as && ib < bs) { + const char& ca = vInsensitiveCase ? std::tolower(vA[ia]) : vA[ia]; + const char& cb = vInsensitiveCase ? std::tolower(vB[ib]) : vB[ib]; + // we cannot start a number extraction from suffixs + const auto rA = M_ExtractNumFromStringAtPos(vA, ia, nA); + const auto rB = M_ExtractNumFromStringAtPos(vB, ib, nB); + if (rA && rB) { + if (nA != nB) { + return vDescending ? nA > nB : nA < nB; + } + } else { + if (ca != cb) { + return vDescending ? ca > cb : ca < cb; + } + ++ia; + ++ib; + } + } + return vDescending ? as > bs : as < bs; // toto1 < toto1+ +} + +IGFD::FileStyle::FileStyle() : color(0, 0, 0, 0) { +} + +IGFD::FileStyle::FileStyle(const FileStyle& vStyle) { + color = vStyle.color; + icon = vStyle.icon; + font = vStyle.font; + flags = vStyle.flags; +} + +IGFD::FileStyle::FileStyle(const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) : color(vColor), icon(vIcon), font(vFont) { +} + +void IGFD::SearchManager::Clear() { + searchTag.clear(); + IGFD::Utils::ResetBuffer(searchBuffer); +} + +void IGFD::SearchManager::DrawSearchBar(FileDialogInternal& vFileDialogInternal) { + // search field + if (IMGUI_BUTTON(resetButtonString "##BtnImGuiFileDialogSearchField")) { + Clear(); + vFileDialogInternal.fileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonResetSearchString); + ImGui::SameLine(); + ImGui::Text(searchString); + ImGui::SameLine(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + bool edited = ImGui::InputText("##InputImGuiFileDialogSearchField", searchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + if (ImGui::GetItemID() == ImGui::GetActiveID()) searchInputIsActive = true; + ImGui::PopItemWidth(); + if (edited) { + searchTag = searchBuffer; + vFileDialogInternal.fileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } +} + +void IGFD::FilterInfos::setCollectionTitle(const std::string& vTitle) { + title = vTitle; +} + +void IGFD::FilterInfos::addFilter(const std::string& vFilter, const bool& vIsRegex) { + setCollectionTitle(vFilter); + addCollectionFilter(vFilter, vIsRegex); +} + +void IGFD::FilterInfos::addCollectionFilter(const std::string& vFilter, const bool& vIsRegex) { + if (!vIsRegex) { + auto _count_dots = Utils::GetCharCountInString(vFilter, '.'); + if (_count_dots > IGFD::FilterInfos::count_dots) { + IGFD::FilterInfos::count_dots = _count_dots; + } + if (vFilter.find('*') != std::string::npos) { + const auto& regex_string = transformAsteriskBasedFilterToRegex(vFilter); + addCollectionFilter(regex_string, true); + return; + } + filters.try_add(vFilter); + filters_optimized.try_add(Utils::LowerCaseString(vFilter)); + } else { + try { + auto rx = std::regex(vFilter); + filters.try_add(vFilter); + filters_regex.emplace_back(rx); + } catch (std::exception& e) { + const std::string msg = "IGFD : The regex \"" + vFilter + "\" parsing was failed with msg : " + e.what(); + throw IGFDException(msg.c_str()); + } + } +} + +void IGFD::FilterInfos::clear() { + title.clear(); + filters.clear(); + filters_optimized.clear(); + filters_regex.clear(); +} + +bool IGFD::FilterInfos::empty() const { + return filters.empty() || filters.begin()->empty(); +} + +const std::string& IGFD::FilterInfos::getFirstFilter() const { + if (!filters.empty()) { + return *filters.begin(); + } + return empty_string; +} + +bool IGFD::FilterInfos::exist(const FileInfos& vFileInfos, bool vIsCaseInsensitive) const { + for (const auto& filter : filters) { + if (vFileInfos.SearchForExt(filter, vIsCaseInsensitive, count_dots)) { + return true; + } + } + return false; +} + +bool IGFD::FilterInfos::regexExist(const std::string& vFilter) const { + for (const auto& regex : filters_regex) { + if (std::regex_search(vFilter, regex)) { + return true; + } + } + return false; +} + +std::string IGFD::FilterInfos::transformAsteriskBasedFilterToRegex(const std::string& vFilter) { + std::string res; + if (!vFilter.empty() && vFilter.find('*') != std::string::npos) { + res = "(("; + for (const auto& c : vFilter) { + if (c == '.') { + res += "[.]"; // [.] => a dot + } else if (c == '*') { + res += ".*"; // .* => any char zero or many + } else { + res += c; // other chars + } + } + res += "$))"; // $ => end fo the string + } + return res; +} + +const IGFD::FilterInfos& IGFD::FilterManager::GetSelectedFilter() const { + return m_SelectedFilter; +} + +void IGFD::FilterManager::ParseFilters(const char* vFilters) { + m_ParsedFilters.clear(); + + if (vFilters) { + dLGFilters = vFilters; // file mode + } else { + dLGFilters.clear(); // directory mode + } + + if (!dLGFilters.empty()) { + /* Rules + 0) a filter must have 2 chars mini and the first must be a . + 1) a regex must be in (( and )) + 2) a , will separate filters except if between a ( and ) + 3) name{filter1, filter2} is a spetial form for collection filters + 3.1) the name can be composed of what you want except { and } + 3.2) the filter can be a regex + 4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1) + 5) the filters cannot integrate a ',' + */ + + bool current_filter_found = false; + bool started = false; + bool regex_started = false; + bool parenthesis_started = false; + + std::string word; + std::string filter_name; + + char last_split_char = 0; + for (char c : dLGFilters) { + if (c == '{') { + if (regex_started) { + word += c; + } else { + started = true; + m_ParsedFilters.emplace_back(); + m_ParsedFilters.back().setCollectionTitle(filter_name); + filter_name.clear(); + word.clear(); + } + last_split_char = c; + } else if (c == '}') { + if (regex_started) { + word += c; + } else { + if (started) { + if (word.size() > 1U && word[0] == '.') { + if (m_ParsedFilters.empty()) { + m_ParsedFilters.emplace_back(); + } + m_ParsedFilters.back().addCollectionFilter(word, false); + } + word.clear(); + filter_name.clear(); + started = false; + } + } + last_split_char = c; + } else if (c == '(') { + word += c; + if (last_split_char == '(') { + regex_started = true; + } + parenthesis_started = true; + if (!started) { + filter_name += c; + } + last_split_char = c; + } else if (c == ')') { + word += c; + if (last_split_char == ')') { + if (regex_started) { + if (started) { + m_ParsedFilters.back().addCollectionFilter(word, true); + } else { + m_ParsedFilters.emplace_back(); + m_ParsedFilters.back().addFilter(word, true); + } + word.clear(); + filter_name.clear(); + regex_started = false; + } else { + if (!started) { + if (!m_ParsedFilters.empty()) { + m_ParsedFilters.erase(m_ParsedFilters.begin() + m_ParsedFilters.size() - 1U); + } else { + m_ParsedFilters.clear(); + } + } + word.clear(); + filter_name.clear(); + } + } + parenthesis_started = false; + if (!started) { + filter_name += c; + } + last_split_char = c; + } else if (c == '.') { + word += c; + if (!started) { + filter_name += c; + } + last_split_char = c; + } else if (c == ',') { + if (regex_started) { + regex_started = false; + word.clear(); + filter_name.clear(); + } else { + if (started) { + if (word.size() > 1U && word[0] == '.') { + m_ParsedFilters.back().addCollectionFilter(word, false); + word.clear(); + filter_name.clear(); + } + } else { + if (word.size() > 1U && word[0] == '.') { + m_ParsedFilters.emplace_back(); + m_ParsedFilters.back().addFilter(word, false); + word.clear(); + filter_name.clear(); + } + if (parenthesis_started) { + filter_name += c; + } + } + } + } else { + if (c != ' ') { + word += c; + } + if (!started) { + filter_name += c; + } + } + } + + if (started) { + if (!m_ParsedFilters.empty()) { + m_ParsedFilters.erase(m_ParsedFilters.begin() + m_ParsedFilters.size() - 1U); + } else { + m_ParsedFilters.clear(); + } + } else if (word.size() > 1U && word[0] == '.') { + m_ParsedFilters.emplace_back(); + m_ParsedFilters.back().addFilter(word, false); + word.clear(); + } + + for (const auto& it : m_ParsedFilters) { + if (it.title == m_SelectedFilter.title) { + m_SelectedFilter = it; + current_filter_found = true; + break; + } + } + + if (!current_filter_found) { + if (!m_ParsedFilters.empty()) { + m_SelectedFilter = *m_ParsedFilters.begin(); + } + } + } +} + +void IGFD::FilterManager::SetSelectedFilterWithExt(const std::string& vFilter) { + if (!m_ParsedFilters.empty()) { + if (!vFilter.empty()) { + for (const auto& infos : m_ParsedFilters) { + for (const auto& filter : infos.filters) { + if (vFilter == filter) { + m_SelectedFilter = infos; + } + } + } + } + + if (m_SelectedFilter.empty()) { + m_SelectedFilter = *m_ParsedFilters.begin(); + } + } +} + +void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) { + std::string _criteria = (vCriteria != nullptr) ? std::string(vCriteria) : ""; + m_FilesStyle[vFlags][_criteria] = std::make_shared(vInfos); + m_FilesStyle[vFlags][_criteria]->flags = vFlags; +} + +// will be called internally +// will not been exposed to IGFD API +bool IGFD::FilterManager::FillFileStyle(std::shared_ptr vFileInfos) const { + // todo : better system to found regarding what style to priorize regarding other + // maybe with a lambda fucntion for let the user use his style + // according to his use case + if (vFileInfos.use_count() && !m_FilesStyle.empty()) { + for (const auto& _flag : m_FilesStyle) { + for (const auto& _file : _flag.second) { + if ((_flag.first & IGFD_FileStyleByTypeDir && _flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isDir() && vFileInfos->fileType.isSymLink()) || + (_flag.first & IGFD_FileStyleByTypeFile && _flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isFile() && vFileInfos->fileType.isSymLink()) || + (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isSymLink()) || (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType.isDir()) || + (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType.isFile())) { + if (_file.first.empty()) { // for all links + vFileInfos->fileStyle = _file.second; + } else if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt, + std::regex(_file.first))) { // for links who are equal to style criteria + vFileInfos->fileStyle = _file.second; + } else if (_file.first == vFileInfos->fileNameExt) { // for links who are equal to style criteria + vFileInfos->fileStyle = _file.second; + } + } + + if (_flag.first & IGFD_FileStyleByExtention) { + if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileExtLevels[0], std::regex(_file.first))) { + vFileInfos->fileStyle = _file.second; + } else if (vFileInfos->SearchForExt(_file.first, false)) { + vFileInfos->fileStyle = _file.second; + } + } + + if (_flag.first & IGFD_FileStyleByFullName) { + if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt, std::regex(_file.first))) { + vFileInfos->fileStyle = _file.second; + } else if (_file.first == vFileInfos->fileNameExt) { + vFileInfos->fileStyle = _file.second; + } + } + + if (_flag.first & IGFD_FileStyleByContainedInFullName) { + if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt, std::regex(_file.first))) { + vFileInfos->fileStyle = _file.second; + } else if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) { + vFileInfos->fileStyle = _file.second; + } + } + + for (auto& functor : m_FilesStyleFunctors) { + if (functor) { + FileStyle result; + if (functor(*(vFileInfos.get()), result)) { + vFileInfos->fileStyle = std::make_shared(std::move(result)); + } + } + } + + if (vFileInfos->fileStyle.use_count()) { + return true; + } + } + } + } + + return false; +} + +void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) { + std::string _criteria; + if (vCriteria) _criteria = std::string(vCriteria); + m_FilesStyle[vFlags][_criteria] = std::make_shared(vColor, vIcon, vFont); + m_FilesStyle[vFlags][_criteria]->flags = vFlags; +} + +void IGFD::FilterManager::SetFileStyle(FileStyle::FileStyleFunctor vFunctor) { + if (vFunctor) { + m_FilesStyleFunctors.push_back(vFunctor); + } +} + +// todo : refactor this fucking function +bool IGFD::FilterManager::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont** vOutFont) { + if (vOutColor) { + if (!m_FilesStyle.empty()) { + if (m_FilesStyle.find(vFlags) != m_FilesStyle.end()) { // found + if (vFlags & IGFD_FileStyleByContainedInFullName) { + // search for vCriteria who are containing the criteria + for (const auto& _file : m_FilesStyle.at(vFlags)) { + if (vCriteria.find(_file.first) != std::string::npos) { + if (_file.second.use_count()) { + *vOutColor = _file.second->color; + if (vOutIcon) *vOutIcon = _file.second->icon; + if (vOutFont) *vOutFont = _file.second->font; + return true; + } + } + } + } else { + if (m_FilesStyle.at(vFlags).find(vCriteria) != m_FilesStyle.at(vFlags).end()) { // found + *vOutColor = m_FilesStyle[vFlags][vCriteria]->color; + if (vOutIcon) *vOutIcon = m_FilesStyle[vFlags][vCriteria]->icon; + if (vOutFont) *vOutFont = m_FilesStyle[vFlags][vCriteria]->font; + return true; + } + } + } else { + // search for flag composition + for (const auto& _flag : m_FilesStyle) { + if (_flag.first & vFlags) { + if (_flag.first & IGFD_FileStyleByContainedInFullName) { + // search for vCriteria who are containing the criteria + for (const auto& _file : m_FilesStyle.at(_flag.first)) { + if (vCriteria.find(_file.first) != std::string::npos) { + if (_file.second.use_count()) { + *vOutColor = _file.second->color; + if (vOutIcon) *vOutIcon = _file.second->icon; + if (vOutFont) *vOutFont = _file.second->font; + return true; + } + } + } + } else { + if (m_FilesStyle.at(_flag.first).find(vCriteria) != m_FilesStyle.at(_flag.first).end()) { // found + *vOutColor = m_FilesStyle[_flag.first][vCriteria]->color; + if (vOutIcon) *vOutIcon = m_FilesStyle[_flag.first][vCriteria]->icon; + if (vOutFont) *vOutFont = m_FilesStyle[_flag.first][vCriteria]->font; + return true; + } + } + } + } + } + } + } + return false; +} + +void IGFD::FilterManager::ClearFilesStyle() { + m_FilesStyle.clear(); +} + +bool IGFD::FilterManager::IsCoveredByFilters(const FileInfos& vFileInfos, bool vIsCaseInsensitive) const { + if (!dLGFilters.empty() && !m_SelectedFilter.empty()) { + return (m_SelectedFilter.exist(vFileInfos, vIsCaseInsensitive) || m_SelectedFilter.regexExist(vFileInfos.fileNameExt)); + } + + return false; +} + +float IGFD::FilterManager::GetFilterComboBoxWidth() const { +#if FILTER_COMBO_AUTO_SIZE + const auto& combo_width = ImGui::CalcTextSize(m_SelectedFilter.title.c_str()).x + ImGui::GetFrameHeight() + ImGui::GetStyle().ItemInnerSpacing.x; + return ImMax(combo_width, FILTER_COMBO_MIN_WIDTH); +#else + return FILTER_COMBO_MIN_WIDTH; +#endif +} + +bool IGFD::FilterManager::DrawFilterComboBox(FileDialogInternal& vFileDialogInternal) { + if (!dLGFilters.empty()) { + ImGui::SameLine(); + bool needToApllyNewFilter = false; + ImGui::PushItemWidth(GetFilterComboBoxWidth()); + if (IMGUI_BEGIN_COMBO("##Filters", m_SelectedFilter.title.c_str(), ImGuiComboFlags_None)) { + intptr_t i = 0; + for (const auto& filter : m_ParsedFilters) { + const bool item_selected = (filter.title == m_SelectedFilter.title); + ImGui::PushID((void*)(intptr_t)i++); + if (ImGui::Selectable(filter.title.c_str(), item_selected)) { + m_SelectedFilter = filter; + needToApllyNewFilter = true; + } + ImGui::PopID(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + if (needToApllyNewFilter) { + vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal); + } + return needToApllyNewFilter; + } + return false; +} + +std::string IGFD::FilterManager::ReplaceExtentionWithCurrentFilterIfNeeded(const std::string& vFileName, IGFD_ResultMode vFlag) const { + auto result = vFileName; + if (!result.empty()) { + const auto& current_filter = m_SelectedFilter.getFirstFilter(); + if (!current_filter.empty()) { + Utils::ReplaceString(result, "..", "."); + + // is a regex => no change + if (current_filter.find("((") != std::string::npos) { + return result; + } + + // contain .* => no change + if (current_filter.find(".*") != std::string::npos) { + return result; + } + + switch (vFlag) { + case IGFD_ResultMode_KeepInputFile: { + return vFileName; + } + case IGFD_ResultMode_OverwriteFileExt: { + const auto& count_dots = Utils::GetCharCountInString(vFileName, '.'); + const auto& min_dots = ImMin(count_dots, m_SelectedFilter.count_dots); + const auto& lp = Utils::GetLastCharPosWithMinCharCount(vFileName, '.', min_dots); + if (lp != std::string::npos) { // there is a user extention + const auto& file_name_without_user_ext = vFileName.substr(0, lp); + result = file_name_without_user_ext + current_filter; + } else { // add extention + result = vFileName + current_filter; + } + break; + } + case IGFD_ResultMode_AddIfNoFileExt: { + const auto& count_dots = Utils::GetCharCountInString(vFileName, '.'); + const auto& min_dots = ImMin(count_dots, m_SelectedFilter.count_dots); + const auto& lp = Utils::GetLastCharPosWithMinCharCount(vFileName, '.', min_dots); + if (lp == std::string::npos || // there is no user extention + lp == (vFileName.size() - 1U)) { // or this pos is also the last char => considered like no user extention + const auto& file_name_without_user_ext = vFileName.substr(0, lp); + result = file_name_without_user_ext + current_filter; + } + break; + } + default: break; + } + + Utils::ReplaceString(result, "..", "."); + } + } + return result; +} + +void IGFD::FilterManager::SetDefaultFilterIfNotDefined() { + if (m_SelectedFilter.empty() && // no filter selected + !m_ParsedFilters.empty()) { // filter exist + m_SelectedFilter = *m_ParsedFilters.begin(); // we take the first filter + } +} + +IGFD::FileType::FileType() = default; +IGFD::FileType::FileType(const ContentType& vContentType, const bool& vIsSymlink) : m_Content(vContentType), m_Symlink(vIsSymlink) { +} +void IGFD::FileType::SetContent(const ContentType& vContentType) { + m_Content = vContentType; +} +void IGFD::FileType::SetSymLink(const bool& vIsSymlink) { + m_Symlink = vIsSymlink; +} +bool IGFD::FileType::isValid() const { + return m_Content != ContentType::Invalid; +} +bool IGFD::FileType::isDir() const { + return m_Content == ContentType::Directory; +} +bool IGFD::FileType::isFile() const { + return m_Content == ContentType::File; +} +bool IGFD::FileType::isLinkToUnknown() const { + return m_Content == ContentType::LinkToUnknown; +} +bool IGFD::FileType::isSymLink() const { + return m_Symlink; +} +// Comparisons only care about the content type, ignoring whether it's a symlink or not. +bool IGFD::FileType::operator==(const FileType& rhs) const { + return m_Content == rhs.m_Content; +} +bool IGFD::FileType::operator!=(const FileType& rhs) const { + return m_Content != rhs.m_Content; +} +bool IGFD::FileType::operator<(const FileType& rhs) const { + return m_Content < rhs.m_Content; +} +bool IGFD::FileType::operator>(const FileType& rhs) const { + return m_Content > rhs.m_Content; +} + +std::shared_ptr IGFD::FileInfos::create() { + return std::make_shared(); +} + +bool IGFD::FileInfos::SearchForTag(const std::string& vTag) const { + if (!vTag.empty()) { + if (fileNameExt_optimized == "..") return true; + return fileNameExt_optimized.find(vTag) != std::string::npos || // first try without case and accents + fileNameExt.find(vTag) != std::string::npos; // second if searched with case and accents + } + + // if tag is empty => its a special case but all is found + return true; +} + +bool IGFD::FileInfos::SearchForExt(const std::string& vExt, const bool& vIsCaseInsensitive, const size_t& vMaxLevel) const { + if (!vExt.empty()) { + const auto& ext_to_check = vIsCaseInsensitive ? Utils::LowerCaseString(vExt) : vExt; + const auto& ext_levels = vIsCaseInsensitive ? fileExtLevels_optimized : fileExtLevels; + if (vMaxLevel >= 1 && countExtDot >= vMaxLevel) { + for (const auto& ext : ext_levels) { + if (!ext.empty() && ext == ext_to_check) { + return true; + } + } + } else { + return (fileExtLevels[0] == vExt); + } + } + return false; +} + +bool IGFD::FileInfos::SearchForExts(const std::string& vComaSepExts, const bool& vIsCaseInsensitive, const size_t& vMaxLevel) const { + if (!vComaSepExts.empty()) { + const auto& arr = Utils::SplitStringToVector(vComaSepExts, ',', false); + for (const auto& a : arr) { + if (SearchForExt(a, vIsCaseInsensitive, vMaxLevel)) { + return true; + } + } + } + return false; +} + +bool IGFD::FileInfos::FinalizeFileTypeParsing(const size_t& vMaxDotToExtract) { + if (fileType.isFile() || fileType.isLinkToUnknown()) { // link can have the same extention of a file + countExtDot = Utils::GetCharCountInString(fileNameExt, '.'); + size_t lpt = 0U; + if (countExtDot > 1U) { // multi layer ext + size_t max_dot_to_extract = vMaxDotToExtract; + if (max_dot_to_extract > countExtDot) { + max_dot_to_extract = countExtDot; + } + lpt = Utils::GetLastCharPosWithMinCharCount(fileNameExt, '.', max_dot_to_extract); + } else { + lpt = fileNameExt.find_first_of('.'); + } + if (lpt != std::string::npos) { + size_t lvl = 0U; + fileNameLevels[lvl] = fileNameExt.substr(0, lpt); + fileNameLevels[lvl] = Utils::LowerCaseString(fileNameLevels[lvl]); + fileExtLevels[lvl] = fileNameExt.substr(lpt); + fileExtLevels_optimized[lvl] = Utils::LowerCaseString(fileExtLevels[lvl]); + if (countExtDot > 1U) { // multi layer ext + auto count = countExtDot; + while (count > 0 && lpt != std::string::npos && lvl < fileExtLevels.size()) { + ++lpt; + ++lvl; + if (fileNameExt.size() > lpt) { + lpt = fileNameExt.find_first_of('.', lpt); + if (lpt != std::string::npos) { + fileNameLevels[lvl] = fileNameExt.substr(0, lpt); + fileNameLevels[lvl] = Utils::LowerCaseString(fileNameLevels[lvl]); + fileExtLevels[lvl] = fileNameExt.substr(lpt); + fileExtLevels_optimized[lvl] = Utils::LowerCaseString(fileExtLevels[lvl]); + } + } + } + } + } + return true; + } + return false; +} + +IGFD::FileManager::FileManager() { + fsRoot = IGFD::Utils::GetPathSeparator(); +#define STR(x) #x +#define STR_AFTER_EXPAND(x) STR(x) + m_FileSystemName = STR_AFTER_EXPAND(FILE_SYSTEM_OVERRIDE); +#undef STR_AFTER_EXPAND +#undef STR + // std::make_unique is not available un cpp11 + m_FileSystemPtr = std::unique_ptr(new FILE_SYSTEM_OVERRIDE()); + // m_FileSystemPtr = std::make_unique(); +} + +void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) { + showDevices = false; + ClearComposer(); + ClearFileLists(); + if (dLGDirectoryMode) { // directory mode + SetDefaultFileName("."); + } else { + SetDefaultFileName(dLGDefaultFileName); + } + ScanDir(vFileDialogInternal, GetCurrentPath()); +} + +void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal) { + m_SortFields(vFileDialogInternal, m_FileList, m_FilteredFileList); +} + +bool IGFD::FileManager::M_SortStrings(const FileDialogInternal& vFileDialogInternal, const bool vInsensitiveCase, const bool vDescendingOrder, const std::string& vA, const std::string& vB) { + if (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NaturalSorting) { + return IGFD::Utils::NaturalCompare(vA, vB, vInsensitiveCase, vDescendingOrder); + } else if (vInsensitiveCase) { + const auto ret = stricmp(vA.c_str(), vB.c_str()); + return vDescendingOrder ? (ret > 0) : (ret < 0); + } else { + const auto ret = strcmp(vA.c_str(), vB.c_str()); + return vDescendingOrder ? (ret > 0) : (ret < 0); + } +} + +void IGFD::FileManager::m_SortFields(const FileDialogInternal& vFileDialogInternal, std::vector >& vFileInfosList, std::vector >& vFileInfosFilteredList) { + if (sortingField != SortingFieldEnum::FIELD_NONE) { + headerFileName = tableHeaderFileNameString; + headerFileType = tableHeaderFileTypeString; + headerFileSize = tableHeaderFileSizeString; + headerFileDate = tableHeaderFileDateString; +#ifdef USE_THUMBNAILS + headerFileThumbnails = tableHeaderFileThumbnailsString; +#endif // #ifdef USE_THUMBNAILS + } + if (sortingField == SortingFieldEnum::FIELD_FILENAME) { + if (sortingDirection[0]) { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileName = tableHeaderAscendingIcon + headerFileName; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), // + [&vFileDialogInternal](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType < b->fileType); // directories first + return M_SortStrings(vFileDialogInternal, true, false, a->fileNameExt, b->fileNameExt); // sort in insensitive case + }); + } else { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileName = tableHeaderDescendingIcon + headerFileName; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), // + [&vFileDialogInternal](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType > b->fileType); // directories last + return M_SortStrings(vFileDialogInternal, true, true, a->fileNameExt, b->fileNameExt); // sort in insensitive case + }); + } + } else if (sortingField == SortingFieldEnum::FIELD_TYPE) { + if (sortingDirection[1]) { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileType = tableHeaderAscendingIcon + headerFileType; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [&vFileDialogInternal](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType < b->fileType); // directory in first + return M_SortStrings(vFileDialogInternal, true, false, a->fileExtLevels[0], b->fileExtLevels[0]); // sort in sensitive case + }); + } else { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileType = tableHeaderDescendingIcon + headerFileType; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [&vFileDialogInternal](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType > b->fileType); // directory in last + return M_SortStrings(vFileDialogInternal, true, true, a->fileExtLevels[0], b->fileExtLevels[0]); // sort in sensitive case + }); + } + } else if (sortingField == SortingFieldEnum::FIELD_SIZE) { + if (sortingDirection[2]) { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileSize = tableHeaderAscendingIcon + headerFileSize; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType < b->fileType); // directory in first + return (a->fileSize < b->fileSize); // else + }); + } else { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileSize = tableHeaderDescendingIcon + headerFileSize; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType > b->fileType); // directory in last + return (a->fileSize > b->fileSize); // else + }); + } + } else if (sortingField == SortingFieldEnum::FIELD_DATE) { + if (sortingDirection[3]) { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileDate = tableHeaderAscendingIcon + headerFileDate; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType < b->fileType); // directory in first + return (a->fileModifDate < b->fileModifDate); // else + }); + } else { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileDate = tableHeaderDescendingIcon + headerFileDate; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType > b->fileType); // directory in last + return (a->fileModifDate > b->fileModifDate); // else + }); + } + } +#ifdef USE_THUMBNAILS + else if (sortingField == SortingFieldEnum::FIELD_THUMBNAILS) { + // we will compare thumbnails by : + // 1) width + // 2) height + + if (sortingDirection[4]) { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileThumbnails = tableHeaderAscendingIcon + headerFileThumbnails; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (a->fileType.isDir()); // directory in first + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) return (a->thumbnailInfo.textureHeight < b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth < b->thumbnailInfo.textureWidth); + }); + } + + else { +#ifdef USE_CUSTOM_SORTING_ICON + headerFileThumbnails = tableHeaderDescendingIcon + headerFileThumbnails; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(vFileInfosList.begin(), vFileInfosList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { + if (!a.use_count() || !b.use_count()) return false; + if (a->fileType != b->fileType) return (!a->fileType.isDir()); // directory in last + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) return (a->thumbnailInfo.textureHeight > b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth > b->thumbnailInfo.textureWidth); + }); + } + } +#endif // USE_THUMBNAILS + + m_ApplyFilteringOnFileList(vFileDialogInternal, vFileInfosList, vFileInfosFilteredList); +} + +bool IGFD::FileManager::m_CompleteFileInfosWithUserFileAttirbutes(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) { + if (vFileDialogInternal.getDialogConfig().userFileAttributes != nullptr) { + if (!vFileDialogInternal.getDialogConfig().userFileAttributes(vInfos.get(), vFileDialogInternal.getDialogConfig().userDatas)) { + return false; // the file will be ignored, so not added to the file list, so not displayed + } else { + if (!vInfos->fileType.isDir()) { + vInfos->formatedFileSize = IGFD::Utils::FormatFileSize(vInfos->fileSize); + } + } + } + return true; // file will be added to file list, so displayed +} + +void IGFD::FileManager::ClearFileLists() { + m_FilteredFileList.clear(); + m_FileList.clear(); +} + +void IGFD::FileManager::ClearPathLists() { + m_FilteredPathList.clear(); + m_PathList.clear(); +} + +void IGFD::FileManager::m_AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const FileType& vFileType) { + auto infos_ptr = FileInfos::create(); + + infos_ptr->filePath = vPath; + infos_ptr->fileNameExt = vFileName; + infos_ptr->fileNameExt_optimized = Utils::LowerCaseString(infos_ptr->fileNameExt); + infos_ptr->fileType = vFileType; + + if (infos_ptr->fileNameExt.empty() || (infos_ptr->fileNameExt == "." && !vFileDialogInternal.filterManager.dLGFilters.empty())) { // filename empty or filename is the current dir '.' //-V807 + return; + } + + if (infos_ptr->fileNameExt != ".." && (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos_ptr->fileNameExt[0] == '.') { // dont show hidden files + if (!vFileDialogInternal.filterManager.dLGFilters.empty() || (vFileDialogInternal.filterManager.dLGFilters.empty() && infos_ptr->fileNameExt != ".")) { // except "." if in directory mode //-V728 + return; + } + } + + if (infos_ptr->FinalizeFileTypeParsing(vFileDialogInternal.filterManager.GetSelectedFilter().count_dots)) { + if (!vFileDialogInternal.filterManager.IsCoveredByFilters(*infos_ptr.get(), // + (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_CaseInsensitiveExtentionFiltering) != 0)) { + return; + } + } + + vFileDialogInternal.filterManager.FillFileStyle(infos_ptr); + + m_CompleteFileInfos(infos_ptr); + + if (m_CompleteFileInfosWithUserFileAttirbutes(vFileDialogInternal, infos_ptr)) { + m_FileList.push_back(infos_ptr); + } +} + +void IGFD::FileManager::m_AddPath(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const FileType& vFileType) { + if (!vFileType.isDir()) return; + + auto infos_ptr = FileInfos::create(); + + infos_ptr->filePath = vPath; + infos_ptr->fileNameExt = vFileName; + infos_ptr->fileNameExt_optimized = Utils::LowerCaseString(infos_ptr->fileNameExt); + infos_ptr->fileType = vFileType; + + if (infos_ptr->fileNameExt.empty() || (infos_ptr->fileNameExt == "." && !vFileDialogInternal.filterManager.dLGFilters.empty())) { // filename empty or filename is the current dir '.' //-V807 + return; + } + + if (infos_ptr->fileNameExt != ".." && (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos_ptr->fileNameExt[0] == '.') { // dont show hidden files + if (!vFileDialogInternal.filterManager.dLGFilters.empty() || (vFileDialogInternal.filterManager.dLGFilters.empty() && infos_ptr->fileNameExt != ".")) { // except "." if in directory mode //-V728 + return; + } + } + + vFileDialogInternal.filterManager.FillFileStyle(infos_ptr); + + m_CompleteFileInfos(infos_ptr); + + if (m_CompleteFileInfosWithUserFileAttirbutes(vFileDialogInternal, infos_ptr)) { + m_PathList.push_back(infos_ptr); + } +} + +void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) { + std::string path = vPath; + + if (m_CurrentPathDecomposition.empty()) { + SetCurrentDir(path); + } + + if (!m_CurrentPathDecomposition.empty()) { +#ifdef _IGFD_WIN_ + if (path == fsRoot) { + path += IGFD::Utils::GetPathSeparator(); + } +#endif // _IGFD_WIN_ + + ClearFileLists(); + + const auto& files = m_FileSystemPtr->ScanDirectory(path); + for (const auto& file : files) { + m_AddFile(vFileDialogInternal, path, file.fileNameExt, file.fileType); + } + + m_SortFields(vFileDialogInternal, m_FileList, m_FilteredFileList); + } +} + +void IGFD::FileManager::m_ScanDirForPathSelection(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) { + std::string path = vPath; + + if (!path.empty()) { +#ifdef _IGFD_WIN_ + if (path == fsRoot) path += IGFD::Utils::GetPathSeparator(); +#endif // _IGFD_WIN_ + + ClearPathLists(); + + const auto& files = m_FileSystemPtr->ScanDirectory(path); + for (const auto& file : files) { + if (file.fileType.isDir()) { + m_AddPath(vFileDialogInternal, path, file.fileNameExt, file.fileType); + } + } + + m_SortFields(vFileDialogInternal, m_PathList, m_FilteredPathList); + } +} + +void IGFD::FileManager::m_OpenPathPopup(const FileDialogInternal& vFileDialogInternal, std::vector::iterator vPathIter) { + const auto path = ComposeNewPath(vPathIter); + m_ScanDirForPathSelection(vFileDialogInternal, path); + m_PopupComposedPath = vPathIter; + ImGui::OpenPopup("IGFD_Path_Popup"); +} + +bool IGFD::FileManager::GetDevices() { + auto devices = m_FileSystemPtr->GetDevicesList(); + if (!devices.empty()) { + m_CurrentPath.clear(); + m_CurrentPathDecomposition.clear(); + ClearFileLists(); + for (auto& drive : devices) { + auto info_ptr = FileInfos::create(); + info_ptr->fileNameExt = drive.first; + info_ptr->fileNameExt_optimized = Utils::LowerCaseString(drive.first); + info_ptr->deviceInfos = drive.second; + info_ptr->fileType.SetContent(FileType::ContentType::Directory); + if (!info_ptr->fileNameExt.empty()) { + m_FileList.push_back(info_ptr); + showDevices = true; + } + } + return true; + } + return false; +} + +bool IGFD::FileManager::IsComposerEmpty() const { + return m_CurrentPathDecomposition.empty(); +} + +size_t IGFD::FileManager::GetComposerSize() const { + return m_CurrentPathDecomposition.size(); +} + +bool IGFD::FileManager::IsFileListEmpty() const { + return m_FileList.empty(); +} + +bool IGFD::FileManager::IsPathListEmpty() const { + return m_PathList.empty(); +} + +size_t IGFD::FileManager::GetFullFileListSize() const { + return m_FileList.size(); +} + +std::shared_ptr IGFD::FileManager::GetFullFileAt(size_t vIdx) { + if (vIdx < m_FileList.size()) return m_FileList[vIdx]; + return nullptr; +} + +bool IGFD::FileManager::IsFilteredListEmpty() const { + return m_FilteredFileList.empty(); +} + +bool IGFD::FileManager::IsPathFilteredListEmpty() const { + return m_FilteredPathList.empty(); +} + +size_t IGFD::FileManager::GetFilteredListSize() const { + return m_FilteredFileList.size(); +} + +size_t IGFD::FileManager::GetPathFilteredListSize() const { + return m_FilteredPathList.size(); +} + +std::shared_ptr IGFD::FileManager::GetFilteredFileAt(size_t vIdx) { + if (vIdx < m_FilteredFileList.size()) return m_FilteredFileList[vIdx]; + return nullptr; +} + +std::shared_ptr IGFD::FileManager::GetFilteredPathAt(size_t vIdx) { + if (vIdx < m_FilteredPathList.size()) return m_FilteredPathList[vIdx]; + return nullptr; +} + +std::vector::iterator IGFD::FileManager::GetCurrentPopupComposedPath() const { + return m_PopupComposedPath; +} + +bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) { + return m_SelectedFileNames.find(vFileName) != m_SelectedFileNames.end(); +} + +std::string IGFD::FileManager::GetBack() { + return m_CurrentPathDecomposition.back(); +} + +void IGFD::FileManager::ClearComposer() { + m_CurrentPathDecomposition.clear(); +} + +void IGFD::FileManager::ClearAll() { + ClearComposer(); + ClearFileLists(); + ClearPathLists(); +} +void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) { + m_ApplyFilteringOnFileList(vFileDialogInternal, m_FileList, m_FilteredFileList); +} + +void IGFD::FileManager::m_ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal, std::vector >& vFileInfosList, std::vector >& vFileInfosFilteredList) { + vFileInfosFilteredList.clear(); + for (const auto& file : vFileInfosList) { + if (!file.use_count()) continue; + bool show = true; + if (!file->SearchForTag(vFileDialogInternal.searchManager.searchTag)) // if search tag + show = false; + if (dLGDirectoryMode && !file->fileType.isDir()) show = false; + if (show) vFileInfosFilteredList.push_back(file); + } +} + +void IGFD::FileManager::m_CompleteFileInfos(const std::shared_ptr& vInfos) { + if (!vInfos.use_count()) return; + + if (vInfos->fileNameExt != "." && vInfos->fileNameExt != "..") { + // _stat struct : + // dev_t st_dev; /* ID of device containing file */ + // ino_t st_ino; /* inode number */ + // mode_t st_mode; /* protection */ + // nlink_t st_nlink; /* number of hard links */ + // uid_t st_uid; /* user ID of owner */ + // gid_t st_gid; /* group ID of owner */ + // dev_t st_rdev; /* device ID (if special file) */ + // off_t st_size; /* total size, in bytes */ + // blksize_t st_blksize; /* blocksize for file system I/O */ + // blkcnt_t st_blocks; /* number of 512B blocks allocated */ + // time_t st_atime; /* time of last access - not sure out of ntfs */ + // time_t st_mtime; /* time of last modification - not sure out of ntfs */ + // time_t st_ctime; /* time of last status change - not sure out of ntfs */ + + std::string fpn; + + // FIXME: so the condition is always true? + if (vInfos->fileType.isFile() || vInfos->fileType.isLinkToUnknown() || vInfos->fileType.isDir()) { + fpn = vInfos->filePath + IGFD::Utils::GetPathSeparator() + vInfos->fileNameExt; + } + + struct stat statInfos = {}; + char timebuf[100]; + int result = stat(fpn.c_str(), &statInfos); + if (!result) { + if (!vInfos->fileType.isDir()) { + vInfos->fileSize = (size_t)statInfos.st_size; + vInfos->formatedFileSize = IGFD::Utils::FormatFileSize(vInfos->fileSize); + } + + size_t len = 0; +#ifdef _MSC_VER + struct tm _tm; + errno_t err = localtime_s(&_tm, &statInfos.st_mtime); + if (!err) len = strftime(timebuf, 99, DateTimeFormat, &_tm); +#else // _MSC_VER + struct tm* _tm = localtime(&statInfos.st_mtime); + if (_tm) len = strftime(timebuf, 99, DateTimeFormat, _tm); +#endif // _MSC_VER + if (len) { + vInfos->fileModifDate = std::string(timebuf, len); + } + } + } +} + +void IGFD::FileManager::m_RemoveFileNameInSelection(const std::string& vFileName) { + m_SelectedFileNames.erase(vFileName); + + if (m_SelectedFileNames.size() == 1) { + snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } else { + snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", m_SelectedFileNames.size()); + } +} + +void IGFD::FileManager::m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName) { + if (vFileName == "." || vFileName == "..") { + return; + } + m_SelectedFileNames.emplace(vFileName); + + if (m_SelectedFileNames.size() == 1) { + snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } else { + snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", m_SelectedFileNames.size()); + } + + if (vSetLastSelectionFileName) { + m_LastSelectedFileName = vFileName; + } +} + +void IGFD::FileManager::SetCurrentDir(const std::string& vPath) { + std::string path = vPath; +#ifdef _IGFD_WIN_ + if (fsRoot == path) path += IGFD::Utils::GetPathSeparator(); +#endif // _IGFD_WIN_ + + bool dir_opened = m_FileSystemPtr->IsDirectory(path); + if (!dir_opened) { + path = "."; + dir_opened = m_FileSystemPtr->IsDirectory(path); + } + if (dir_opened) { +#ifdef _IGFD_WIN_ + DWORD numchar = 0; + std::wstring wpath = IGFD::Utils::UTF8Decode(path); + numchar = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr); + std::wstring fpath(numchar, 0); + GetFullPathNameW(wpath.c_str(), numchar, (wchar_t*)fpath.data(), nullptr); + std::string real_path = IGFD::Utils::UTF8Encode(fpath); + while (real_path.back() == '\0') // for fix issue we can have with std::string concatenation.. if there is a \0 at end + real_path = real_path.substr(0, real_path.size() - 1U); + if (!real_path.empty()) +#elif defined(_IGFD_UNIX_) // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE + char real_path[PATH_MAX]; + char* numchar = realpath(path.c_str(), real_path); + if (numchar != nullptr) +#endif // _IGFD_WIN_ + { + m_CurrentPath = std::move(real_path); + if (m_CurrentPath[m_CurrentPath.size() - 1] == PATH_SEP) { + m_CurrentPath = m_CurrentPath.substr(0, m_CurrentPath.size() - 1); + } + IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath); + m_CurrentPathDecomposition = IGFD::Utils::SplitStringToVector(m_CurrentPath, PATH_SEP, false); +#ifdef _IGFD_UNIX_ // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE + m_CurrentPathDecomposition.insert(m_CurrentPathDecomposition.begin(), IGFD::Utils::GetPathSeparator()); +#endif // _IGFD_UNIX_ + if (!m_CurrentPathDecomposition.empty()) { +#ifdef _IGFD_WIN_ + fsRoot = m_CurrentPathDecomposition[0]; +#endif // _IGFD_WIN_ + } + } + } +} + +bool IGFD::FileManager::CreateDir(const std::string& vPath) { + if (!vPath.empty()) { + std::string path = m_CurrentPath + IGFD::Utils::GetPathSeparator() + vPath; + return m_FileSystemPtr->CreateDirectoryIfNotExist(path); + } + return false; +} + +std::string IGFD::FileManager::ComposeNewPath(std::vector::iterator vIter) { + std::string res; + + while (true) { + if (!res.empty()) { +#ifdef _IGFD_WIN_ + res = *vIter + IGFD::Utils::GetPathSeparator() + res; +#elif defined(_IGFD_UNIX_) // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE + if (*vIter == fsRoot) + res = *vIter + res; + else + res = *vIter + PATH_SEP + res; +#endif // _IGFD_WIN_ + } else + res = *vIter; + + if (vIter == m_CurrentPathDecomposition.begin()) { +#ifdef _IGFD_UNIX_ // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE + if (res[0] != PATH_SEP) res = PATH_SEP + res; +#else + if (res.back() != PATH_SEP) res.push_back(PATH_SEP); +#endif // defined(_IGFD_UNIX_) + break; + } + + --vIter; + } + + return res; +} + +bool IGFD::FileManager::SetPathOnParentDirectoryIfAny() { + if (m_CurrentPathDecomposition.size() > 1) { + m_CurrentPath = ComposeNewPath(m_CurrentPathDecomposition.end() - 2); + return true; + } + return false; +} + +std::string IGFD::FileManager::GetCurrentPath() { + if (m_CurrentPath.empty()) m_CurrentPath = "."; + return m_CurrentPath; +} + +void IGFD::FileManager::SetCurrentPath(const std::string& vCurrentPath) { + if (vCurrentPath.empty()) + m_CurrentPath = "."; + else + m_CurrentPath = vCurrentPath; +} + +void IGFD::FileManager::SetDefaultFileName(const std::string& vFileName) { + dLGDefaultFileName = vFileName; + IGFD::Utils::SetBuffer(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName); +} + +bool IGFD::FileManager::SelectDirectory(const std::shared_ptr& vInfos) { + if (!vInfos.use_count()) return false; + + bool pathClick = false; + + if (vInfos->fileNameExt == "..") { + pathClick = SetPathOnParentDirectoryIfAny(); + } else { + std::string newPath; + + if (showDevices) { + newPath = vInfos->fileNameExt + IGFD::Utils::GetPathSeparator(); + } else { +#ifdef __linux__ + if (fsRoot == m_CurrentPath) + newPath = m_CurrentPath + vInfos->fileNameExt; + else +#endif // __linux__ + newPath = m_CurrentPath + IGFD::Utils::GetPathSeparator() + vInfos->fileNameExt; + } + + if (m_FileSystemPtr->IsDirectoryCanBeOpened(newPath)) { + if (showDevices) { + m_CurrentPath = vInfos->fileNameExt; + fsRoot = m_CurrentPath; + } else { + m_CurrentPath = newPath; //-V820 + } + pathClick = true; + } + } + + return pathClick; +} + +void IGFD::FileManager::SelectAllFileNames() { + m_SelectedFileNames.clear(); + for (const auto& infos_ptr : m_FilteredFileList) { + if (infos_ptr != nullptr) { + m_AddFileNameInSelection(infos_ptr->fileNameExt, true); + } + } +} + +void IGFD::FileManager::SelectFileName(const std::shared_ptr& vInfos) { + if (!vInfos.use_count()) { + return; + } + m_AddFileNameInSelection(vInfos->fileNameExt, true); +} + +void IGFD::FileManager::SelectOrDeselectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) { + if (!vInfos.use_count()) { + return; + } + + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { + if (dLGcountSelectionMax == 0) { // infinite selection + if (m_SelectedFileNames.find(vInfos->fileNameExt) == m_SelectedFileNames.end()) { // not found +> add + m_AddFileNameInSelection(vInfos->fileNameExt, true); + } else { // found +> remove + m_RemoveFileNameInSelection(vInfos->fileNameExt); + } + } else { // selection limited by size + if (m_SelectedFileNames.size() < dLGcountSelectionMax) { + if (m_SelectedFileNames.find(vInfos->fileNameExt) == m_SelectedFileNames.end()) { // not found +> add + m_AddFileNameInSelection(vInfos->fileNameExt, true); + } else { // found +> remove + m_RemoveFileNameInSelection(vInfos->fileNameExt); + } + } + } + } else if (ImGui::IsKeyDown(ImGuiMod_Shift)) { + if (dLGcountSelectionMax != 1) { + m_SelectedFileNames.clear(); + // we will iterate filelist and get the last selection after the start selection + bool startMultiSelection = false; + std::string fileNameToSelect = vInfos->fileNameExt; + std::string savedLastSelectedFileName; // for invert selection mode + for (const auto& file : m_FileList) { + if (!file.use_count()) { + continue; + } + bool canTake = true; + if (!file->SearchForTag(vFileDialogInternal.searchManager.searchTag)) canTake = false; + if (canTake) { // if not filtered, we will take files who are filtered by the dialog + if (file->fileNameExt == m_LastSelectedFileName) { + startMultiSelection = true; + m_AddFileNameInSelection(m_LastSelectedFileName, false); + } else if (startMultiSelection) { + if (dLGcountSelectionMax == 0) { // infinite selection + m_AddFileNameInSelection(file->fileNameExt, false); + } else { // selection limited by size + if (m_SelectedFileNames.size() < dLGcountSelectionMax) { + m_AddFileNameInSelection(file->fileNameExt, false); + } else { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) m_LastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } + + if (file->fileNameExt == fileNameToSelect) { + if (!startMultiSelection) { // we are before the last Selected FileName, so we must inverse + savedLastSelectedFileName = m_LastSelectedFileName; + m_LastSelectedFileName = fileNameToSelect; + fileNameToSelect = savedLastSelectedFileName; + startMultiSelection = true; + m_AddFileNameInSelection(m_LastSelectedFileName, false); + } else { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) m_LastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } + } + } + } else { + m_SelectedFileNames.clear(); + IGFD::Utils::ResetBuffer(fileNameBuffer); + m_AddFileNameInSelection(vInfos->fileNameExt, true); + } +} + +void IGFD::FileManager::DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal) { + if (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableCreateDirectoryButton) return; + + if (IMGUI_BUTTON(createDirButtonString)) { + if (!m_CreateDirectoryMode) { + m_CreateDirectoryMode = true; + IGFD::Utils::ResetBuffer(directoryNameBuffer); + } + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonCreateDirString); + + if (m_CreateDirectoryMode) { + ImGui::SameLine(); + + ImGui::PushItemWidth(100.0f); + ImGui::InputText("##DirectoryFileName", directoryNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + if (IMGUI_BUTTON(okButtonString)) { + std::string newDir = std::string(directoryNameBuffer); + if (CreateDir(newDir)) { + SetCurrentPath(m_CurrentPath + IGFD::Utils::GetPathSeparator() + newDir); + OpenCurrentPath(vFileDialogInternal); + } + + m_CreateDirectoryMode = false; + } + + ImGui::SameLine(); + + if (IMGUI_BUTTON(cancelButtonString)) { + m_CreateDirectoryMode = false; + } + } + + ImGui::SameLine(); +} + +void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) { + if (IMGUI_BUTTON(resetButtonString)) { + SetCurrentPath("."); + OpenCurrentPath(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(buttonResetPathString); + } + if (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ShowDevicesButton) { + ImGui::SameLine(); + if (IMGUI_BUTTON(devicesButtonString)) { + devicesClicked = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(buttonDriveString); + } + } + + ImGui::SameLine(); + + if (IMGUI_BUTTON(editPathButtonString)) { + inputPathActivated = !inputPathActivated; + if (inputPathActivated) { + if (!m_CurrentPathDecomposition.empty()) { + auto endIt = m_CurrentPathDecomposition.end(); + m_CurrentPath = ComposeNewPath(--endIt); + IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath); + } + } + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonEditPathString); + + ImGui::SameLine(); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + + // show current path + if (!m_CurrentPathDecomposition.empty()) { + ImGui::SameLine(); + + if (inputPathActivated) { + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##pathedition", inputPathBuffer, MAX_PATH_BUFFER_SIZE); + ImGui::PopItemWidth(); + } else { + int _id = 0; + for (auto itPathDecomp = m_CurrentPathDecomposition.begin(); itPathDecomp != m_CurrentPathDecomposition.end(); ++itPathDecomp) { + if (itPathDecomp != m_CurrentPathDecomposition.begin()) { +#if defined(CUSTOM_PATH_SPACING) + ImGui::SameLine(0, CUSTOM_PATH_SPACING); +#else + ImGui::SameLine(); +#endif // USE_CUSTOM_PATH_SPACING + if (!(vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableQuickPathSelection)) { +#if defined(_IGFD_WIN_) + const char* sep = "\\"; +#elif defined(_IGFD_UNIX_) + const char* sep = "/"; + if (itPathDecomp != m_CurrentPathDecomposition.begin() + 1) +#endif + { + ImGui::PushID(_id++); + bool click = IMGUI_PATH_BUTTON(sep); + ImGui::PopID(); + +#if defined(CUSTOM_PATH_SPACING) + ImGui::SameLine(0, CUSTOM_PATH_SPACING); +#else + ImGui::SameLine(); +#endif // USE_CUSTOM_PATH_SPACING + + if (click) { + m_OpenPathPopup(vFileDialogInternal, itPathDecomp - 1); + } else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + m_SetCurrentPath(itPathDecomp - 1); + break; + } + } + } + } + + ImGui::PushID(_id++); + bool click = IMGUI_PATH_BUTTON((*itPathDecomp).c_str()); + ImGui::PopID(); + if (click) { + m_CurrentPath = ComposeNewPath(itPathDecomp); + pathClicked = true; + break; + } else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { // activate input for path + m_SetCurrentPath(itPathDecomp); + break; + } + } + } + } +} + +void IGFD::FileManager::m_SetCurrentPath(std::vector::iterator vPathIter) { + m_CurrentPath = ComposeNewPath(vPathIter); + IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath); + inputPathActivated = true; +} + +std::string IGFD::FileManager::GetResultingPath() { + if (dLGDirectoryMode && m_SelectedFileNames.size() == 1) { // if directory mode with selection 1 + std::string selectedDirectory = fileNameBuffer; + std::string path = m_CurrentPath; + if (!selectedDirectory.empty() && selectedDirectory != ".") { + path += IGFD::Utils::GetPathSeparator() + selectedDirectory; + } + return path; + } + return m_CurrentPath; // if file mode +} + +std::string IGFD::FileManager::GetResultingFileName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) { + if (!dLGDirectoryMode) { // if not directory mode + const auto& filename = std::string(fileNameBuffer); + return vFileDialogInternal.filterManager.ReplaceExtentionWithCurrentFilterIfNeeded(filename, vFlag); + } + return ""; // directory mode +} + +std::string IGFD::FileManager::GetResultingFilePathName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) { + if (!dLGDirectoryMode) { // if not directory mode + auto result = GetResultingPath(); + const auto& file_path_name = GetResultingFileName(vFileDialogInternal, vFlag); + if (!file_path_name.empty()) { + if (m_FileSystemPtr != nullptr && file_path_name.find(IGFD::Utils::GetPathSeparator()) != std::string::npos && // check if a path + m_FileSystemPtr->IsFileExist(file_path_name)) { // do that only if filename is a path, not only a file name + result = file_path_name; // #144, exist file, so absolute, so return it (maybe set by user in inputText) + } else { // #144, else concate path with current filename +#ifdef _IGFD_UNIX_ + if (fsRoot != result) +#endif // _IGFD_UNIX_ + { + result += IGFD::Utils::GetPathSeparator(); + } + result += file_path_name; + } + } + + return result; // file mode + } + return ""; // directory mode +} + +std::map IGFD::FileManager::GetResultingSelection(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) { + std::map res; + for (const auto& selectedFileName : m_SelectedFileNames) { + auto result = GetResultingPath(); +#ifdef _IGFD_UNIX_ + if (fsRoot != result) +#endif // _IGFD_UNIX_ + { + result += IGFD::Utils::GetPathSeparator(); + } + result += vFileDialogInternal.filterManager.ReplaceExtentionWithCurrentFilterIfNeeded(selectedFileName, vFlag); + res[selectedFileName] = result; + } + return res; +} + +void IGFD::FileDialogInternal::NewFrame() { + canWeContinue = true; // reset flag for possibily validate the dialog + isOk = false; // reset dialog result + fileManager.devicesClicked = false; + fileManager.pathClicked = false; + + needToExitDialog = false; + +#ifdef USE_DIALOG_EXIT_WITH_KEY + if (ImGui::IsKeyPressed(IGFD_EXIT_KEY)) { + // we do that here with the data's defined at the last frame + // because escape key can quit input activation and at the end of the frame all flag will be false + // so we will detect nothing + if (!(fileManager.inputPathActivated || searchManager.searchInputIsActive || fileInputIsActive || fileListViewIsActive)) { + needToExitDialog = true; // need to quit dialog + } + } else +#endif + { + searchManager.searchInputIsActive = false; + fileInputIsActive = false; + fileListViewIsActive = false; + } +} + +void IGFD::FileDialogInternal::EndFrame() { + // directory change + if (fileManager.pathClicked) { + fileManager.OpenCurrentPath(*this); + } + + if (fileManager.devicesClicked) { + if (fileManager.GetDevices()) { + fileManager.ApplyFilteringOnFileList(*this); + } + } + + if (fileManager.inputPathActivated) { + auto gio = ImGui::GetIO(); + if (ImGui::IsKeyReleased(ImGuiKey_Enter)) { + fileManager.SetCurrentPath(std::string(fileManager.inputPathBuffer)); + fileManager.OpenCurrentPath(*this); + fileManager.inputPathActivated = false; + } + if (ImGui::IsKeyReleased(ImGuiKey_Escape)) { + fileManager.inputPathActivated = false; + } + } + + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { + if (ImGui::IsKeyDown(SelectAllFilesKey)) { + fileManager.SelectAllFileNames(); + } + } +} + +void IGFD::FileDialogInternal::ResetForNewDialog() { +} + +void IGFD::FileDialogInternal::configureDialog(const std::string& vKey, const std::string& vTitle, const char* vFilters, const FileDialogConfig& vConfig) { + m_DialogConfig = vConfig; + ResetForNewDialog(); + dLGkey = vKey; + dLGtitle = vTitle; + + // treatment + if (m_DialogConfig.sidePane == nullptr) { + m_DialogConfig.sidePaneWidth = 0.0f; + } + + if (m_DialogConfig.filePathName.empty()) { + if (m_DialogConfig.path.empty()) { + fileManager.dLGpath = fileManager.GetCurrentPath(); + } else { + fileManager.dLGpath = m_DialogConfig.path; + } + fileManager.SetCurrentPath(m_DialogConfig.path); + fileManager.dLGcountSelectionMax = (size_t)m_DialogConfig.countSelectionMax; + fileManager.SetDefaultFileName(m_DialogConfig.fileName); + } else { + auto ps = fileManager.GetFileSystemInstance()->ParsePathFileName(m_DialogConfig.filePathName); + if (ps.isOk) { + fileManager.dLGpath = ps.path; + fileManager.SetDefaultFileName(ps.name); + filterManager.dLGdefaultExt = "." + ps.ext; + } else { + fileManager.dLGpath = fileManager.GetCurrentPath(); + fileManager.SetDefaultFileName(""); + filterManager.dLGdefaultExt.clear(); + } + } + + filterManager.dLGdefaultExt.clear(); + filterManager.ParseFilters(vFilters); + filterManager.SetSelectedFilterWithExt(filterManager.dLGdefaultExt); + fileManager.SetCurrentPath(fileManager.dLGpath); + fileManager.dLGDirectoryMode = (vFilters == nullptr); + fileManager.dLGcountSelectionMax = m_DialogConfig.countSelectionMax; //-V101 + fileManager.ClearAll(); + showDialog = true; +} + +const IGFD::FileDialogConfig& IGFD::FileDialogInternal::getDialogConfig() const { + return m_DialogConfig; +} + +IGFD::FileDialogConfig& IGFD::FileDialogInternal::getDialogConfigRef() { + return m_DialogConfig; +} + +IGFD::ThumbnailFeature::ThumbnailFeature() { +#ifdef USE_THUMBNAILS + m_DisplayMode = DisplayModeEnum::FILE_LIST; +#endif +} + +IGFD::ThumbnailFeature::~ThumbnailFeature() = default; + +void IGFD::ThumbnailFeature::m_NewThumbnailFrame(FileDialogInternal& /*vFileDialogInternal*/) { +#ifdef USE_THUMBNAILS + m_StartThumbnailFileDatasExtraction(); +#endif +} + +void IGFD::ThumbnailFeature::m_EndThumbnailFrame(FileDialogInternal& vFileDialogInternal) { +#ifdef USE_THUMBNAILS + m_ClearThumbnails(vFileDialogInternal); +#else + (void)vFileDialogInternal; +#endif +} + +void IGFD::ThumbnailFeature::m_QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal) { +#ifdef USE_THUMBNAILS + m_StopThumbnailFileDatasExtraction(); + m_ClearThumbnails(vFileDialogInternal); +#else + (void)vFileDialogInternal; +#endif +} + +#ifdef USE_THUMBNAILS +void IGFD::ThumbnailFeature::m_StartThumbnailFileDatasExtraction() { + const bool res = m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable(); + if (!res) { + m_IsWorking = true; + m_CountFiles = 0U; + m_ThumbnailGenerationThread = std::shared_ptr(new std::thread(&IGFD::ThumbnailFeature::m_ThreadThumbnailFileDatasExtractionFunc, this), [this](std::thread* obj_ptr) { + m_IsWorking = false; + if (obj_ptr != nullptr) { + m_ThumbnailFileDatasToGetCv.notify_all(); + obj_ptr->join(); + } + }); + } +} + +bool IGFD::ThumbnailFeature::m_StopThumbnailFileDatasExtraction() { + const bool res = m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable(); + if (res) { + m_ThumbnailGenerationThread.reset(); + } + return res; +} + +void IGFD::ThumbnailFeature::m_ThreadThumbnailFileDatasExtractionFunc() { + m_CountFiles = 0U; + m_IsWorking = true; + // infinite loop while is thread working + while (m_IsWorking) { + std::unique_lock thumbnailFileDatasToGetLock(m_ThumbnailFileDatasToGetMutex); + m_ThumbnailFileDatasToGetCv.wait(thumbnailFileDatasToGetLock); + if (!m_ThumbnailFileDatasToGet.empty()) { + std::shared_ptr file = nullptr; + // get the first file in the list + file = (*m_ThumbnailFileDatasToGet.begin()); + m_ThumbnailFileDatasToGet.pop_front(); + thumbnailFileDatasToGetLock.unlock(); + // retrieve datas of the texture file if its an image file + if (file.use_count()) { + if (file->fileType.isFile()) { //-V522 + //|| file->fileExtLevels == ".hdr" => format float so in few times + if (file->SearchForExts(".png,.bmp,.tga,.jpg,.jpeg,.gif,.psd,.pic,.ppm,.pgm", true)) { + auto fpn = file->filePath + IGFD::Utils::GetPathSeparator() + file->fileNameExt; + int w = 0; + int h = 0; + int chans = 0; + uint8_t* datas = stbi_load(fpn.c_str(), &w, &h, &chans, STBI_rgb_alpha); + if (datas != nullptr) { + if (w != 0 && h != 0) { + // resize with respect to glyph ratio + const float ratioX = (float)w / (float)h; + const float newX = DisplayMode_ThumbailsList_ImageHeight * ratioX; + float newY = w / ratioX; + if (newX < w) { + newY = DisplayMode_ThumbailsList_ImageHeight; + } + const auto newWidth = (int)newX; + const auto newHeight = (int)newY; + const auto newBufSize = (size_t)(newWidth * newHeight * 4U); //-V112 //-V1028 + auto resizedData = new uint8_t[newBufSize]; + const auto* resizeSucceeded = stbir_resize_uint8_linear(datas, w, h, 0, resizedData, newWidth, newHeight, 0, stbir_pixel_layout::STBIR_RGBA); //-V112 + if (resizeSucceeded != nullptr) { + auto th = &file->thumbnailInfo; + th->textureFileDatas = resizedData; + th->textureWidth = newWidth; + th->textureHeight = newHeight; + th->textureChannels = 4; //-V112 + // we set that at least, because will launch the gpu creation of the texture in the + // main thread + th->isReadyToUpload = true; + // need gpu loading + m_AddThumbnailToCreate(file); + } else { + delete[] resizedData; + } + } else { + printf("image loading fail : w:%i h:%i c:%i\n", w, h, 4); //-V112 + } + stbi_image_free(datas); + } + } + } + } + } else { + thumbnailFileDatasToGetLock.unlock(); + } + } +} + +void IGFD::ThumbnailFeature::m_VariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + char TempBuffer[512]; + const int w = vsnprintf(TempBuffer, 511, fmt, args); + va_end(args); + if (w) { + ImGui::ProgressBar(fraction, size_arg, TempBuffer); + } +} + +void IGFD::ThumbnailFeature::m_DrawThumbnailGenerationProgress() { + if (m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable()) { + m_ThumbnailFileDatasToGetMutex.lock(); + if (!m_ThumbnailFileDatasToGet.empty()) { + const auto p = (float)((double)m_CountFiles / (double)m_ThumbnailFileDatasToGet.size()); // read => no thread concurency issues + m_VariadicProgressBar(p, ImVec2(50, 0), "%u/%u", m_CountFiles, (uint32_t)m_ThumbnailFileDatasToGet.size()); // read => no thread concurency issues + ImGui::SameLine(); + } + m_ThumbnailFileDatasToGetMutex.unlock(); + m_ThumbnailFileDatasToGetCv.notify_all(); + } +} + +void IGFD::ThumbnailFeature::m_AddThumbnailToLoad(const std::shared_ptr& vFileInfos) { + if (vFileInfos.use_count()) { + if (vFileInfos->fileType.isFile()) { + //|| file->fileExtLevels == ".hdr" => format float so in few times + if (vFileInfos->SearchForExts(".png,.bmp,.tga,.jpg,.jpeg,.gif,.psd,.pic,.ppm,.pgm", true)) { + // write => thread concurency issues + m_ThumbnailFileDatasToGetMutex.lock(); + m_ThumbnailFileDatasToGet.push_back(vFileInfos); + vFileInfos->thumbnailInfo.isLoadingOrLoaded = true; + m_ThumbnailFileDatasToGetMutex.unlock(); + } + m_ThumbnailFileDatasToGetCv.notify_all(); + } + } +} + +void IGFD::ThumbnailFeature::m_AddThumbnailToCreate(const std::shared_ptr& vFileInfos) { + if (vFileInfos.use_count()) { + // write => thread concurency issues + m_ThumbnailToCreateMutex.lock(); + m_ThumbnailToCreate.push_back(vFileInfos); + m_ThumbnailToCreateMutex.unlock(); + } +} + +void IGFD::ThumbnailFeature::m_AddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info) { + // write => thread concurency issues + m_ThumbnailToDestroyMutex.lock(); + m_ThumbnailToDestroy.push_back(vIGFD_Thumbnail_Info); + m_ThumbnailToDestroyMutex.unlock(); +} + +void IGFD::ThumbnailFeature::m_DrawDisplayModeToolBar() { + if (IMGUI_RADIO_BUTTON(DisplayMode_FilesList_ButtonString, m_DisplayMode == DisplayModeEnum::FILE_LIST)) m_DisplayMode = DisplayModeEnum::FILE_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_FilesList_ButtonHelp); + ImGui::SameLine(); + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsList_ButtonString, m_DisplayMode == DisplayModeEnum::THUMBNAILS_LIST)) m_DisplayMode = DisplayModeEnum::THUMBNAILS_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsList_ButtonHelp); + ImGui::SameLine(); + /* todo + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsGrid_ButtonString, + m_DisplayMode == DisplayModeEnum::THUMBNAILS_GRID)) + m_DisplayMode = DisplayModeEnum::THUMBNAILS_GRID; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsGrid_ButtonHelp); + ImGui::SameLine(); + */ + m_DrawThumbnailGenerationProgress(); +} + +void IGFD::ThumbnailFeature::m_ClearThumbnails(FileDialogInternal& vFileDialogInternal) { + // directory wil be changed so the file list will be erased + if (vFileDialogInternal.fileManager.pathClicked) { + size_t count = vFileDialogInternal.fileManager.GetFullFileListSize(); + for (size_t idx = 0U; idx < count; idx++) { + auto file = vFileDialogInternal.fileManager.GetFullFileAt(idx); + if (file.use_count()) { + if (file->thumbnailInfo.isReadyToDisplay) //-V522 + { + m_AddThumbnailToDestroy(file->thumbnailInfo); + } + } + } + } +} + +void IGFD::ThumbnailFeature::SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun) { + m_CreateThumbnailFun = vCreateThumbnailFun; +} + +void IGFD::ThumbnailFeature::SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun) { + m_DestroyThumbnailFun = vCreateThumbnailFun; +} + +void IGFD::ThumbnailFeature::ManageGPUThumbnails() { + if (m_CreateThumbnailFun) { + m_ThumbnailToCreateMutex.lock(); + if (!m_ThumbnailToCreate.empty()) { + for (const auto& file : m_ThumbnailToCreate) { + if (file.use_count()) { + m_CreateThumbnailFun(&file->thumbnailInfo); + } + } + m_ThumbnailToCreate.clear(); + } + m_ThumbnailToCreateMutex.unlock(); + } else { + printf( + "No Callback found for create texture\nYou need to define the callback with a call to " + "SetCreateThumbnailCallback\n"); + } + + if (m_DestroyThumbnailFun) { + m_ThumbnailToDestroyMutex.lock(); + if (!m_ThumbnailToDestroy.empty()) { + for (auto thumbnail : m_ThumbnailToDestroy) { + m_DestroyThumbnailFun(&thumbnail); + } + m_ThumbnailToDestroy.clear(); + } + m_ThumbnailToDestroyMutex.unlock(); + } else { + printf( + "No Callback found for destroy texture\nYou need to define the callback with a call to " + "SetCreateThumbnailCallback\n"); + } +} + +#endif // USE_THUMBNAILS + +IGFD::PlacesFeature::PlacesFeature() { +#ifdef USE_PLACES_FEATURE + m_PlacesPaneWidth = defaultPlacePaneWith; + m_PlacesPaneShown = PLACES_PANE_DEFAULT_SHOWN; +#endif // USE_PLACES_FEATURE +} + +#ifdef USE_PLACES_FEATURE +void IGFD::PlacesFeature::m_InitPlaces(FileDialogInternal& vFileDialogInternal) { +#ifdef USE_PLACES_BOOKMARKS + (void)vFileDialogInternal; // for disable compiler warning about unused var + AddPlacesGroup(placesBookmarksGroupName, placesBookmarksDisplayOrder, true, PLACES_BOOKMARK_DEFAULT_OPEPEND); +#endif // USE_PLACES_BOOKMARK +#ifdef USE_PLACES_DEVICES + AddPlacesGroup(placesDevicesGroupName, placesDevicesDisplayOrder, false, PLACES_DEVICES_DEFAULT_OPEPEND); + auto devices_ptr = GetPlacesGroupPtr(placesDevicesGroupName); + if (devices_ptr != nullptr && vFileDialogInternal.fileManager.GetFileSystemInstance() != nullptr) { + const auto& devices = vFileDialogInternal.fileManager.GetFileSystemInstance()->GetDevicesList(); + for (const auto& device : devices) { + devices_ptr->AddPlace(device.first + " " + device.second, device.first + IGFD::Utils::GetPathSeparator(), false); + } + devices_ptr = nullptr; + } +#endif // USE_PLACES_DEVICES +} + +void IGFD::PlacesFeature::m_DrawPlacesButton() { + IMGUI_TOGGLE_BUTTON(placesButtonString, &m_PlacesPaneShown); + if (ImGui::IsItemHovered()) ImGui::SetTooltip(placesButtonHelpString); +} + +bool IGFD::PlacesFeature::m_DrawPlacesPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize) { + bool res = false; + ImGui::BeginChild("##placespane", vSize); + for (const auto& group : m_OrderedGroups) { + auto group_ptr = group.second.lock(); + if (group_ptr != nullptr) { + if (ImGui::CollapsingHeader(group_ptr->name.c_str(), group_ptr->collapsingHeaderFlag)) { + ImGui::BeginChild(group_ptr->name.c_str(), ImVec2(0, 0), ImGuiChildFlags_AutoResizeY); + if (group_ptr->canBeEdited) { + ImGui::PushID(group_ptr.get()); + if (IMGUI_BUTTON(addPlaceButtonString "##ImGuiFileDialogAddPlace")) { + if (!vFileDialogInternal.fileManager.IsComposerEmpty()) { + group_ptr->AddPlace(vFileDialogInternal.fileManager.GetBack(), vFileDialogInternal.fileManager.GetCurrentPath(), true); + } + } + if (group_ptr->selectedPlaceForEdition >= 0 && group_ptr->selectedPlaceForEdition < (int)group_ptr->places.size()) { + ImGui::SameLine(); + if (IMGUI_BUTTON(removePlaceButtonString "##ImGuiFileDialogRemovePlace")) { + group_ptr->places.erase(group_ptr->places.begin() + group_ptr->selectedPlaceForEdition); + if (group_ptr->selectedPlaceForEdition == (int)group_ptr->places.size()) { + --group_ptr->selectedPlaceForEdition; + } + } + if (group_ptr->selectedPlaceForEdition >= 0 && group_ptr->selectedPlaceForEdition < (int)group_ptr->places.size()) { + ImGui::SameLine(); + if (IMGUI_BUTTON(validatePlaceButtonString "##ImGuiFileDialogOkPlace")) { + group_ptr->places[(size_t)group_ptr->selectedPlaceForEdition].name = std::string(group_ptr->editBuffer); + group_ptr->selectedPlaceForEdition = -1; + } + ImGui::SameLine(); + ImGui::PushItemWidth(vSize.x - ImGui::GetCursorPosX()); + if (ImGui::InputText("##ImGuiFileDialogPlaceEdit", group_ptr->editBuffer, MAX_FILE_DIALOG_NAME_BUFFER)) { + group_ptr->places[(size_t)group_ptr->selectedPlaceForEdition].name = std::string(group_ptr->editBuffer); + } + ImGui::PopItemWidth(); + } + } + ImGui::PopID(); + ImGui::Separator(); + } + if (!group_ptr->places.empty()) { + const auto& current_path = vFileDialogInternal.fileManager.GetCurrentPath(); + group_ptr->clipper.Begin((int)group_ptr->places.size(), ImGui::GetTextLineHeightWithSpacing()); + while (group_ptr->clipper.Step()) { + for (int i = group_ptr->clipper.DisplayStart; i < group_ptr->clipper.DisplayEnd; i++) { + if (i < 0) { + continue; + } + const PlaceStruct& place = group_ptr->places[(size_t)i]; + if (place.thickness > 0.0f) { + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, place.thickness); + } else { + ImGui::PushID(i); + std::string place_name = place.name; + if (!place.style.icon.empty()) { + place_name = place.style.icon + " " + place_name; + } + if (group_ptr->canBeEdited) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + if (ImGui::SmallButton(editPlaceButtonString "##ImGuiFileDialogPlaceEditButton")) { + group_ptr->selectedPlaceForEdition = i; + IGFD::Utils::ResetBuffer(group_ptr->editBuffer); + IGFD::Utils::AppendToBuffer(group_ptr->editBuffer, MAX_FILE_DIALOG_NAME_BUFFER, place.name); + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::SameLine(); + } + if (ImGui::Selectable(place_name.c_str(), current_path == place.path || group_ptr->selectedPlaceForEdition == i, ImGuiSelectableFlags_AllowDoubleClick)) { // select if path is current + if (ImGui::IsMouseDoubleClicked(0)) { + group_ptr->selectedPlaceForEdition = -1; // stop edition + // apply path + vFileDialogInternal.fileManager.SetCurrentPath(place.path); + vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal); + res = true; + } + } + ImGui::PopID(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", place.path.c_str()); + } + } + } + } + group_ptr->clipper.End(); + } + ImGui::EndChild(); + } + } + } + ImGui::EndChild(); + return res; +} + +std::string IGFD::PlacesFeature::SerializePlaces(const bool& /*vForceSerialisationForAll*/) { + std::string res; + size_t idx = 0; + for (const auto& group : m_Groups) { + if (group.second->canBeSaved) { + // ## is used because reserved by imgui, so an input text cannot have ## + res += "###" + group.first + "###"; + for (const auto& place : group.second->places) { + if (place.canBeSaved) { + if (idx++ != 0) res += "##"; + res += place.name + "##" + place.path; + } + } + } + } + return res; +} + +void IGFD::PlacesFeature::DeserializePlaces(const std::string& vPlaces) { + if (!vPlaces.empty()) { + const auto& groups = IGFD::Utils::SplitStringToVector(vPlaces, "###", false); + if (groups.size() > 1) { + for (size_t i = 0; i < groups.size(); i += 2) { + auto group_ptr = GetPlacesGroupPtr(groups[i]); + if (group_ptr != nullptr) { + const auto& places = IGFD::Utils::SplitStringToVector(groups[i + 1], "##", false); + if (places.size() > 1) { + for (size_t j = 0; j < places.size(); j += 2) { + group_ptr->AddPlace(places[j], places[j + 1], true); // was saved so we set canBeSaved to true + } + } + } + } + } + } +} + +bool IGFD::PlacesFeature::AddPlacesGroup(const std::string& vGroupName, const size_t& vDisplayOrder, const bool& vCanBeEdited, const bool& vOpenedByDefault) { + if (vGroupName.empty()) { + return false; + } + auto group_ptr = std::make_shared(); + group_ptr->displayOrder = vDisplayOrder; + group_ptr->name = vGroupName; + group_ptr->defaultOpened = vOpenedByDefault; + if (group_ptr->defaultOpened) { + group_ptr->collapsingHeaderFlag = ImGuiTreeNodeFlags_DefaultOpen; + } + group_ptr->canBeSaved = group_ptr->canBeEdited = vCanBeEdited; // can be user edited mean can be saved + m_Groups[vGroupName] = group_ptr; + m_OrderedGroups[group_ptr->displayOrder] = group_ptr; // an exisitng display order will be overwrote for code simplicity + return true; +} + +bool IGFD::PlacesFeature::RemovePlacesGroup(const std::string& vGroupName) { + for (auto it = m_Groups.begin(); it != m_Groups.end(); ++it) { + if ((*it).second->name == vGroupName) { + m_Groups.erase(it); + return true; + } + } + return false; +} + +IGFD::PlacesFeature::GroupStruct* IGFD::PlacesFeature::GetPlacesGroupPtr(const std::string& vGroupName) { + if (m_Groups.find(vGroupName) != m_Groups.end()) { + return m_Groups.at(vGroupName).get(); + } + return nullptr; +} + +bool IGFD::PlacesFeature::GroupStruct::AddPlace(const std::string& vPlaceName, const std::string& vPlacePath, const bool& vCanBeSaved, const FileStyle& vStyle) { + if (vPlaceName.empty() || vPlacePath.empty()) { + return false; + } + canBeSaved |= vCanBeSaved; // if one place must be saved so we mark the group to be saved + PlaceStruct place; + place.name = vPlaceName; + place.path = vPlacePath; + place.canBeSaved = vCanBeSaved; + place.style = vStyle; + places.push_back(place); + return true; +} + +void IGFD::PlacesFeature::GroupStruct::AddPlaceSeparator(const float& vThickness) { + PlaceStruct place; + place.thickness = vThickness; + places.push_back(place); +} + +bool IGFD::PlacesFeature::GroupStruct::RemovePlace(const std::string& vPlaceName) { + if (vPlaceName.empty()) { + return false; + } + for (auto places_it = places.begin(); places_it != places.end(); ++places_it) { + if ((*places_it).name == vPlaceName) { + places.erase(places_it); + return true; + } + } + return false; +} +#endif // USE_PLACES_FEATURE + +IGFD::KeyExplorerFeature::KeyExplorerFeature() = default; + +#ifdef USE_EXPLORATION_BY_KEYS +bool IGFD::KeyExplorerFeature::m_LocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC) { + bool found = false; + + auto& fdi = vFileDialogInternal.fileManager; + if (!fdi.IsFilteredListEmpty()) { + auto countFiles = fdi.GetFilteredListSize(); + for (size_t i = m_LocateFileByInputChar_lastFileIdx; i < countFiles; i++) { + auto nfo = fdi.GetFilteredFileAt(i); + if (nfo.use_count()) { + if (nfo->fileNameExt_optimized[0] == vC || // lower case search //-V522 + nfo->fileNameExt[0] == vC) // maybe upper case search + { + // float p = ((float)i) * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)i / (double)countFiles) * ImGui::GetScrollMaxY(); + ImGui::SetScrollY(p); + m_LocateFileByInputChar_lastFound = true; + m_LocateFileByInputChar_lastFileIdx = i; + m_StartFlashItem(m_LocateFileByInputChar_lastFileIdx); + + auto infos_ptr = fdi.GetFilteredFileAt(m_LocateFileByInputChar_lastFileIdx); + if (infos_ptr.use_count()) { + if (infos_ptr->fileType.isDir()) //-V522 + { + if (fdi.dLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(infos_ptr); + } + } else { + fdi.SelectFileName(infos_ptr); + } + + found = true; + break; + } + } + } + } + } + + return found; +} + +void IGFD::KeyExplorerFeature::m_LocateByInputKey(FileDialogInternal& vFileDialogInternal) { + ImGuiContext& g = *GImGui; + auto& fdi = vFileDialogInternal.fileManager; + if (!g.ActiveId && !fdi.IsFilteredListEmpty()) { + auto& queueChar = ImGui::GetIO().InputQueueCharacters; + auto countFiles = fdi.GetFilteredListSize(); + + // point by char + if (!queueChar.empty()) { + ImWchar c = queueChar.back(); + if (m_LocateFileByInputChar_InputQueueCharactersSize != queueChar.size()) { + if (c == m_LocateFileByInputChar_lastChar) // next file starting with same char until + { + if (m_LocateFileByInputChar_lastFileIdx < countFiles - 1U) + m_LocateFileByInputChar_lastFileIdx++; + else + m_LocateFileByInputChar_lastFileIdx = 0; + } + + if (!m_LocateItem_Loop(vFileDialogInternal, c)) { + // not found, loop again from 0 this time + m_LocateFileByInputChar_lastFileIdx = 0; + m_LocateItem_Loop(vFileDialogInternal, c); + } + + m_LocateFileByInputChar_lastChar = c; + } + } + + m_LocateFileByInputChar_InputQueueCharactersSize = queueChar.size(); + } +} + +void IGFD::KeyExplorerFeature::m_ExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID) { + auto& fdi = vFileDialogInternal.fileManager; + if (!fdi.IsFilteredListEmpty()) { + bool canWeExplore = false; + bool hasNav = (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + + ImGuiContext& g = *GImGui; + if (!hasNav && !g.ActiveId) // no nav and no activated inputs + canWeExplore = true; + + if (g.NavId && g.NavId == vListViewID) { + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter) || ImGui::IsKeyPressed(ImGuiKey_Space)) { + ImGui::ActivateItemByID(vListViewID); + ImGui::SetActiveID(vListViewID, g.CurrentWindow); + } + } + + if (vListViewID == g.LastActiveId - 1) // if listview id is the last acticated nav id (ImGui::ActivateItemByID(vListViewID);) + canWeExplore = true; + + if (canWeExplore && ImGui::IsWindowFocused()) { + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + ImGui::ClearActiveID(); + g.LastActiveId = 0; + } + + auto countFiles = fdi.GetFilteredListSize(); + + // explore + bool exploreByKey = false; + bool enterInDirectory = false; + bool exitDirectory = false; + + if ((hasNav && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) || (!hasNav && ImGui::IsKeyPressed(ImGuiKey_UpArrow))) { + exploreByKey = true; + if (m_LocateFileByInputChar_lastFileIdx > 0) + m_LocateFileByInputChar_lastFileIdx--; + else + m_LocateFileByInputChar_lastFileIdx = countFiles - 1U; + } else if ((hasNav && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) || (!hasNav && ImGui::IsKeyPressed(ImGuiKey_DownArrow))) { + exploreByKey = true; + if (m_LocateFileByInputChar_lastFileIdx < countFiles - 1U) + m_LocateFileByInputChar_lastFileIdx++; + else + m_LocateFileByInputChar_lastFileIdx = 0U; + } else if (ImGui::IsKeyReleased(ImGuiKey_Enter)) { + exploreByKey = true; + enterInDirectory = true; + } else if (ImGui::IsKeyReleased(ImGuiKey_Backspace)) { + exploreByKey = true; + exitDirectory = true; + } + + if (exploreByKey) { + // float totalHeight = m_FilteredFileList.size() * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)m_LocateFileByInputChar_lastFileIdx / (double)(countFiles - 1U)) * ImGui::GetScrollMaxY(); // seems not udpated in tables version outside tables + // float p = ((float)locateFileByInputChar_lastFileIdx) * ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetScrollY(p); + m_StartFlashItem(m_LocateFileByInputChar_lastFileIdx); + + auto infos_ptr = fdi.GetFilteredFileAt(m_LocateFileByInputChar_lastFileIdx); + if (infos_ptr.use_count()) { + if (infos_ptr->fileType.isDir()) //-V522 + { + if (!fdi.dLGDirectoryMode || enterInDirectory) { + if (enterInDirectory) { + if (fdi.SelectDirectory(infos_ptr)) { + // changement de repertoire + vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal); + if (m_LocateFileByInputChar_lastFileIdx > countFiles - 1U) { + m_LocateFileByInputChar_lastFileIdx = 0; + } + } + } + } else // directory chooser + { + fdi.SelectFileName(infos_ptr); + } + } else { + fdi.SelectFileName(infos_ptr); + + if (enterInDirectory) { + vFileDialogInternal.isOk = true; + } + } + + if (exitDirectory) { + auto nfo_ptr = FileInfos::create(); + nfo_ptr->fileNameExt = ".."; + + if (fdi.SelectDirectory(nfo_ptr)) { + // changement de repertoire + vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal); + if (m_LocateFileByInputChar_lastFileIdx > countFiles - 1U) { + m_LocateFileByInputChar_lastFileIdx = 0; + } + } +#ifdef _IGFD_WIN_ + else { + if (fdi.GetComposerSize() == 1U) { + if (fdi.GetDevices()) { + fdi.ApplyFilteringOnFileList(vFileDialogInternal); + } + } + } +#endif // _IGFD_WIN_ + } + } + } + } + } +} + +bool IGFD::KeyExplorerFeature::m_FlashableSelectable(const char* label, bool selected, ImGuiSelectableFlags flags, bool vFlashing, const ImVec2& size_arg) { + using namespace ImGui; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(size, 0.0f); + + // Fill horizontal space + // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x); + + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); + + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currenty doesn't allow offsetting CursorPos. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_TRUNC(spacing_x * 0.50f); + const float spacing_U = IM_TRUNC(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + // if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } + + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const bool is_visible = ItemAdd(bb, id, NULL, disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None); + + if (span_all_columns) { + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + + const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) + return false; + + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (disabled_item && !disabled_global) // Only testing this as an optimization + BeginDisabled(); + + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns) { + if (g.CurrentTable) + TablePushBackgroundChannel(); + else if (window->DC.CurrentColumns) + PushColumnsBackground(); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; + g.LastItemData.ClipRect = window->ClipRect; + } + + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { + button_flags |= ImGuiButtonFlags_NoHoldingActiveId; + } + if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { + button_flags |= ImGuiButtonFlags_NoSetKeyOwner; + } + if (flags & ImGuiSelectableFlags_SelectOnClick) { + button_flags |= ImGuiButtonFlags_PressedOnClick; + } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { + button_flags |= ImGuiButtonFlags_PressedOnRelease; + } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { + button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + } + if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { + button_flags |= ImGuiButtonFlags_AllowOverlap; + } + + // Multi-selection support (header) + const bool was_selected = selected; + if (is_multi_select) { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + } + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + + // Multi-selection support (footer) + if (is_multi_select) { + MultiSelectItemFooter(id, &selected, &pressed); + } else { + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if (g.NavJustMovedToId == id) selected = pressed = true; + } + + ////////////////////////////////////////////////////////////////// + // this function copy ImGui::Selectable just for this line.... + hovered |= vFlashing; + ////////////////////////////////////////////////////////////////// + + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) + g.NavDisableHighlight = true; + } + } + if (pressed) MarkItemEdited(id); + + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + if (is_visible) { + if (hovered || selected) { + // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected + ImU32 col; + if (selected && !hovered) + col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f)); + else + col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + if (g.NavId == id) { + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding; + if (is_multi_select) nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle + RenderNavHighlight(bb, id, nav_highlight_flags); + } + } + + if (span_all_columns) { + if (g.CurrentTable) + TablePopBackgroundChannel(); + else if (window->DC.CurrentColumns) + PopColumnsBackground(); + } + + if (is_visible) RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.InFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); + + if (disabled_item && !disabled_global) EndDisabled(); + + // Selectable() always returns a pressed state! + // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve + // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect(). + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; //-V1020 +} + +void IGFD::KeyExplorerFeature::m_StartFlashItem(size_t vIdx) { + m_FlashAlpha = 1.0f; + m_FlashedItem = vIdx; +} + +bool IGFD::KeyExplorerFeature::m_BeginFlashItem(size_t vIdx) { + bool res = false; + + if (m_FlashedItem == vIdx && std::abs(m_FlashAlpha - 0.0f) > 0.00001f) { + m_FlashAlpha -= m_FlashAlphaAttenInSecs * ImGui::GetIO().DeltaTime; + if (m_FlashAlpha < 0.0f) m_FlashAlpha = 0.0f; + + ImVec4 hov = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered); + hov.w = m_FlashAlpha; + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hov); + res = true; + } + + return res; +} + +void IGFD::KeyExplorerFeature::m_EndFlashItem() { + ImGui::PopStyleColor(); +} + +void IGFD::KeyExplorerFeature::SetFlashingAttenuationInSeconds(float vAttenValue) { + m_FlashAlphaAttenInSecs = 1.0f / ImMax(vAttenValue, 0.01f); +} +#endif // USE_EXPLORATION_BY_KEYS + +IGFD::FileDialog::FileDialog() : PlacesFeature(), KeyExplorerFeature(), ThumbnailFeature() { +#ifdef USE_PLACES_FEATURE + m_InitPlaces(m_FileDialogInternal); +#endif +} +IGFD::FileDialog::~FileDialog() = default; + +////////////////////////////////////////////////////////////////////////////////////////////////// +///// FILE DIALOG STANDARD DIALOG //////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////// + +// path and fileNameExt can be specified +void IGFD::FileDialog::OpenDialog(const std::string& vKey, const std::string& vTitle, const char* vFilters, const FileDialogConfig& vConfig) { + if (m_FileDialogInternal.showDialog) // if already opened, quit + return; + m_FileDialogInternal.configureDialog(vKey, vTitle, vFilters, vConfig); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +///// FILE DIALOG DISPLAY FUNCTION /////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////// + +bool IGFD::FileDialog::Display(const std::string& vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) { + bool res = false; + + if (m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey) { + if (m_FileDialogInternal.puUseCustomLocale) setlocale(m_FileDialogInternal.localeCategory, m_FileDialogInternal.localeBegin.c_str()); + + auto& fdFile = m_FileDialogInternal.fileManager; + auto& fdFilter = m_FileDialogInternal.filterManager; + + // to be sure than only one dialog is displayed per frame + ImGuiContext& g = *GImGui; + if (g.FrameCount == m_FileDialogInternal.lastImGuiFrameCount) { // one instance was displayed this frame before + return res; // for this key +> quit + } + m_FileDialogInternal.lastImGuiFrameCount = g.FrameCount; // mark this instance as used this frame + + m_CurrentDisplayedFlags = ImGuiWindowFlags_None; + std::string name = m_FileDialogInternal.dLGtitle + "##" + m_FileDialogInternal.dLGkey; + if (m_FileDialogInternal.name != name) { + fdFile.ClearComposer(); + fdFile.ClearFileLists(); + m_CurrentDisplayedFlags = vFlags; + } + + m_NewFrame(); + +#ifdef IMGUI_HAS_VIEWPORT + if (!ImGui::GetIO().ConfigViewportsNoDecoration) { + // https://github.com/ocornut/imgui/issues/4534 + ImGuiWindowClass window_class; + window_class.ViewportFlagsOverrideClear = ImGuiViewportFlags_NoDecoration; + ImGui::SetNextWindowClass(&window_class); + } +#endif // IMGUI_HAS_VIEWPORT + + bool beg = false; + ImVec2 frameSize = ImVec2(0, 0); + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NoDialog) { // disable our own dialog system (standard or modal) + frameSize = vMinSize; + beg = true; + } else { + ImGui::SetNextWindowSizeConstraints(vMinSize, vMaxSize); + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal && // disable modal because the confirm dialog for overwrite is + !m_FileDialogInternal.okResultToConfirm) { // a new modal + ImGui::OpenPopup(name.c_str()); + beg = ImGui::BeginPopupModal(name.c_str(), (bool*)nullptr, m_CurrentDisplayedFlags | ImGuiWindowFlags_NoScrollbar); + } else { + beg = ImGui::Begin(name.c_str(), (bool*)nullptr, m_CurrentDisplayedFlags | ImGuiWindowFlags_NoScrollbar); + } + } + if (beg) { +#ifdef IMGUI_HAS_VIEWPORT + // if decoration is enabled we disable the resizing feature of imgui for avoid crash with SDL2 and GLFW3 + if (ImGui::GetIO().ConfigViewportsNoDecoration) { + m_CurrentDisplayedFlags = vFlags; + } else { + auto win = ImGui::GetCurrentWindowRead(); + if (win->Viewport->Idx != 0) + m_CurrentDisplayedFlags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar; + else + m_CurrentDisplayedFlags = vFlags; + } +#endif // IMGUI_HAS_VIEWPORT + + ImGuiID _frameId = ImGui::GetID(name.c_str()); + if (ImGui::BeginChild(_frameId, frameSize, false, m_CurrentDisplayedFlags | ImGuiWindowFlags_NoScrollbar)) { + m_FileDialogInternal.name = name; //-V820 + if (fdFile.dLGpath.empty()) { + fdFile.dLGpath = "."; // defaut path is '.' + } + fdFilter.SetDefaultFilterIfNotDefined(); + + // init list of files + if (fdFile.IsFileListEmpty() && !fdFile.showDevices) { + if (fdFile.dLGpath != ".") // Removes extension seperator in filename if we don't check + IGFD::Utils::ReplaceString(fdFile.dLGDefaultFileName, fdFile.dLGpath, ""); // local path + + if (!fdFile.dLGDefaultFileName.empty()) { + fdFile.SetDefaultFileName(fdFile.dLGDefaultFileName); + fdFilter.SetSelectedFilterWithExt(fdFilter.dLGdefaultExt); + } else if (fdFile.dLGDirectoryMode) // directory mode + fdFile.SetDefaultFileName("."); + fdFile.ScanDir(m_FileDialogInternal, fdFile.dLGpath); + } + + // draw dialog parts + m_DrawHeader(); // place, directory, path + m_DrawContent(); // place, files view, side pane + res = m_DrawFooter(); // file field, filter combobox, ok/cancel buttons + + m_EndFrame(); + } + ImGui::EndChild(); + + // for display in dialog center, the confirm to overwrite dlg + m_FileDialogInternal.dialogCenterPos = ImGui::GetCurrentWindowRead()->ContentRegionRect.GetCenter(); + + // when the confirm to overwrite dialog will appear we need to + // disable the modal mode of the main file dialog + // see prOkResultToConfirm under + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal && !m_FileDialogInternal.okResultToConfirm) { + ImGui::EndPopup(); + } + } + + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NoDialog) { // disable our own dialog system (standard or modal) + } else { + // same things here regarding prOkResultToConfirm + if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal) || m_FileDialogInternal.okResultToConfirm) { + ImGui::End(); + } + } + // confirm the result and show the confirm to overwrite dialog if needed + res = m_Confirm_Or_OpenOverWriteFileDialog_IfNeeded(res, vFlags); + + if (m_FileDialogInternal.puUseCustomLocale) setlocale(m_FileDialogInternal.localeCategory, m_FileDialogInternal.localeEnd.c_str()); + } + + return res; +} + +void IGFD::FileDialog::m_NewFrame() { + m_FileDialogInternal.NewFrame(); + m_NewThumbnailFrame(m_FileDialogInternal); +} + +void IGFD::FileDialog::m_EndFrame() { + m_EndThumbnailFrame(m_FileDialogInternal); + m_FileDialogInternal.EndFrame(); +} +void IGFD::FileDialog::m_QuitFrame() { + m_QuitThumbnailFrame(m_FileDialogInternal); +} + +void IGFD::FileDialog::m_DrawHeader() { +#ifdef USE_PLACES_FEATURE + if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisablePlaceMode)) { + m_DrawPlacesButton(); + ImGui::SameLine(); + } + +#endif // USE_PLACES_FEATURE + + m_FileDialogInternal.fileManager.DrawDirectoryCreation(m_FileDialogInternal); + + if ( +#ifdef USE_PLACES_FEATURE + !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisablePlaceMode) || +#endif // USE_PLACES_FEATURE + !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableCreateDirectoryButton)) { + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + } + m_FileDialogInternal.fileManager.DrawPathComposer(m_FileDialogInternal); + +#ifdef USE_THUMBNAILS + if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode)) { + m_DrawDisplayModeToolBar(); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + } +#endif // USE_THUMBNAILS + + m_FileDialogInternal.searchManager.DrawSearchBar(m_FileDialogInternal); +} + +void IGFD::FileDialog::m_DrawContent() { + ImVec2 size = ImGui::GetContentRegionAvail() - ImVec2(0.0f, m_FileDialogInternal.footerHeight); + +#ifdef USE_PLACES_FEATURE + if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisablePlaceMode)) { + if (m_PlacesPaneShown) { + float otherWidth = size.x - m_PlacesPaneWidth; + ImGui::PushID("##splitterplaces"); + IGFD::Utils::ImSplitter(true, 4.0f, &m_PlacesPaneWidth, &otherWidth, 10.0f, 10.0f + m_FileDialogInternal.getDialogConfig().sidePaneWidth, size.y); + ImGui::PopID(); + size.x -= otherWidth; + m_DrawPlacesPane(m_FileDialogInternal, size); + ImGui::SameLine(); + } + } +#endif // USE_PLACES_FEATURE + + size.x = ImGui::GetContentRegionAvail().x - m_FileDialogInternal.getDialogConfig().sidePaneWidth; + + if (m_FileDialogInternal.getDialogConfig().sidePane) { + ImGui::PushID("##splittersidepane"); + IGFD::Utils::ImSplitter(true, 4.0f, &size.x, &m_FileDialogInternal.getDialogConfigRef().sidePaneWidth, 10.0f, 10.0f, size.y); + ImGui::PopID(); + } + +#ifdef USE_THUMBNAILS + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode) { + m_DrawFileListView(size); + } else { + switch (m_DisplayMode) { + case DisplayModeEnum::FILE_LIST: m_DrawFileListView(size); break; + case DisplayModeEnum::THUMBNAILS_LIST: m_DrawThumbnailsListView(size); break; + case DisplayModeEnum::THUMBNAILS_GRID: m_DrawThumbnailsGridView(size); + } + } +#else // USE_THUMBNAILS + m_DrawFileListView(size); +#endif // USE_THUMBNAILS + + if (m_FileDialogInternal.getDialogConfig().sidePane) { + m_DrawSidePane(size.y); + } + + if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableQuickPathSelection)) { + m_DisplayPathPopup(size); + } +} + +void IGFD::FileDialog::m_DisplayPathPopup(ImVec2 vSize) { + ImVec2 size = ImVec2(vSize.x * 0.5f, vSize.y * 0.5f); + if (ImGui::BeginPopup("IGFD_Path_Popup")) { + auto& fdi = m_FileDialogInternal.fileManager; + + ImGui::PushID(this); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY; + auto listViewID = ImGui::GetID("##FileDialog_pathTable"); + if (ImGui::BeginTableEx("##FileDialog_pathTable", listViewID, 1, flags, size, 0.0f)) //-V112 + { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(tableHeaderFileNameString, ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0); + + ImGui::TableHeadersRow(); + + if (!fdi.IsPathFilteredListEmpty()) { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + m_PathListClipper.Begin((int)fdi.GetPathFilteredListSize(), ImGui::GetTextLineHeightWithSpacing()); + while (m_PathListClipper.Step()) { + for (int i = m_PathListClipper.DisplayStart; i < m_PathListClipper.DisplayEnd; i++) { + if (i < 0) continue; + + auto infos_ptr = fdi.GetFilteredPathAt((size_t)i); + if (!infos_ptr.use_count()) continue; + + m_BeginFileColorIconStyle(infos_ptr, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos_ptr->fileNameExt); // found + + ImGui::TableNextRow(); + + if (ImGui::TableNextColumn()) // file name + { + if (ImGui::Selectable(infos_ptr->fileNameExt.c_str(), &selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth)) { + fdi.SetCurrentPath(fdi.ComposeNewPath(fdi.GetCurrentPopupComposedPath())); + fdi.pathClicked = fdi.SelectDirectory(infos_ptr); + ImGui::CloseCurrentPopup(); + } + } + + m_EndFileColorIconStyle(_showColor, _font); + } + } + m_PathListClipper.End(); + } + + ImGui::EndTable(); + } + + ImGui::PopID(); + + ImGui::EndPopup(); + } +} + +bool IGFD::FileDialog::m_DrawOkButton() { + auto& fdFile = m_FileDialogInternal.fileManager; + if (m_FileDialogInternal.canWeContinue && strlen(fdFile.fileNameBuffer)) { + if (IMGUI_BUTTON(okButtonString "##validationdialog", ImVec2(okButtonWidth, 0.0f)) || m_FileDialogInternal.isOk) { + m_FileDialogInternal.isOk = true; + return true; + } + +#if !invertOkAndCancelButtons + ImGui::SameLine(); +#endif + } + + return false; +} + +bool IGFD::FileDialog::m_DrawCancelButton() { + if (IMGUI_BUTTON(cancelButtonString "##validationdialog", ImVec2(cancelButtonWidth, 0.0f)) || m_FileDialogInternal.needToExitDialog) // dialog exit asked + { + m_FileDialogInternal.isOk = false; + return true; + } + +#if invertOkAndCancelButtons + ImGui::SameLine(); +#endif + + return false; +} + +bool IGFD::FileDialog::m_DrawValidationButtons() { + bool res = false; + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetContentRegionAvail().x - prOkCancelButtonWidth) * okCancelButtonAlignement); + + ImGui::BeginGroup(); + + if (invertOkAndCancelButtons) { + res |= m_DrawCancelButton(); + res |= m_DrawOkButton(); + } else { + res |= m_DrawOkButton(); + res |= m_DrawCancelButton(); + } + + ImGui::EndGroup(); + + prOkCancelButtonWidth = ImGui::GetItemRectSize().x; + + return res; +} + +bool IGFD::FileDialog::m_DrawFooter() { + auto& fdFile = m_FileDialogInternal.fileManager; + + float posY = ImGui::GetCursorPos().y; // height of last bar calc + ImGui::AlignTextToFramePadding(); + if (!fdFile.dLGDirectoryMode) + ImGui::Text(fileNameString); + else // directory chooser + ImGui::Text(dirNameString); + ImGui::SameLine(); + + // Input file fields + float width = ImGui::GetContentRegionAvail().x; + if (!fdFile.dLGDirectoryMode) { + ImGuiContext& g = *GImGui; + width -= m_FileDialogInternal.filterManager.GetFilterComboBoxWidth() + g.Style.ItemSpacing.x; + } + + ImGui::PushItemWidth(width); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue; + if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ReadOnlyFileNameField) { + flags |= ImGuiInputTextFlags_ReadOnly; + } + if (ImGui::InputText("##FileName", fdFile.fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, flags)) { + m_FileDialogInternal.isOk = true; + } + if (ImGui::GetItemID() == ImGui::GetActiveID()) m_FileDialogInternal.fileInputIsActive = true; + ImGui::PopItemWidth(); + + // combobox of filters + m_FileDialogInternal.filterManager.DrawFilterComboBox(m_FileDialogInternal); + + bool res = m_DrawValidationButtons(); + m_FileDialogInternal.footerHeight = ImGui::GetCursorPosY() - posY; + return res; +} + +void IGFD::FileDialog::m_SelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, ...) { + if (!vInfos.use_count()) return; + + auto& fdi = m_FileDialogInternal.fileManager; + + static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; + + va_list args; + va_start(args, vFmt); + vsnprintf(fdi.variadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); + va_end(args); + + float h = 0.0f; +#ifdef USE_THUMBNAILS + if (m_DisplayMode == DisplayModeEnum::THUMBNAILS_LIST && !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode)) { + h = DisplayMode_ThumbailsList_ImageHeight; + } +#endif // USE_THUMBNAILS +#ifdef USE_EXPLORATION_BY_KEYS + bool flashed = m_BeginFlashItem((size_t)vidx); + bool res = m_FlashableSelectable(fdi.variadicBuffer, vSelected, selectableFlags, flashed, ImVec2(-1.0f, h)); + if (flashed) { + m_EndFlashItem(); + } +#else // USE_EXPLORATION_BY_KEYS + (void)vidx; // remove a warnings for unused var + + bool res = ImGui::Selectable(fdi.variadicBuffer, vSelected, selectableFlags, ImVec2(-1.0f, h)); +#endif // USE_EXPLORATION_BY_KEYS + if (res) { + if (vInfos->fileType.isDir()) { + // nav system, selectable cause open directory or select directory + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) { + // little fix for get back the mouse behavior in nav system + if (ImGui::IsMouseDoubleClicked(0)) { // 0 -> left mouse button double click + fdi.pathClicked = fdi.SelectDirectory(vInfos); + } else if (fdi.dLGDirectoryMode) { // directory chooser + fdi.SelectOrDeselectFileName(m_FileDialogInternal, vInfos); + } else { + fdi.pathClicked = fdi.SelectDirectory(vInfos); + } + } else { // no nav system => classic behavior + if (ImGui::IsMouseDoubleClicked(0)) { // 0 -> left mouse button double click + fdi.pathClicked = fdi.SelectDirectory(vInfos); + } else if (fdi.dLGDirectoryMode) { // directory chooser + fdi.SelectOrDeselectFileName(m_FileDialogInternal, vInfos); + } + } + } else { + fdi.SelectOrDeselectFileName(m_FileDialogInternal, vInfos); + if (ImGui::IsMouseDoubleClicked(0)) { + m_FileDialogInternal.isOk = true; + } + } + } +} + +void IGFD::FileDialog::m_DisplayFileInfosTooltip(const int32_t& /*vRowIdx*/, const int32_t& vColumnIdx, std::shared_ptr vFileInfos) { + if (ImGui::IsItemHovered()) { + if (vFileInfos != nullptr && vFileInfos->tooltipColumn == vColumnIdx) { + if (!vFileInfos->tooltipMessage.empty()) { + ImGui::SetTooltip("%s", vFileInfos->tooltipMessage.c_str()); + } + } + } +} + +void IGFD::FileDialog::m_BeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) { + vOutStr.clear(); + vOutShowColor = false; + + if (vFileInfos->fileStyle.use_count()) //-V807 //-V522 + { + vOutShowColor = true; + + *vOutFont = vFileInfos->fileStyle->font; + } + + if (vOutShowColor && !vFileInfos->fileStyle->icon.empty()) + vOutStr = vFileInfos->fileStyle->icon; + else if (vFileInfos->fileType.isDir()) + vOutStr = dirEntryString; + else if (vFileInfos->fileType.isLinkToUnknown()) + vOutStr = linkEntryString; + else if (vFileInfos->fileType.isFile()) + vOutStr = fileEntryString; + + vOutStr += " " + vFileInfos->fileNameExt; + + if (vOutShowColor) ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color); + if (*vOutFont) ImGui::PushFont(*vOutFont); +} + +void IGFD::FileDialog::m_EndFileColorIconStyle(const bool& vShowColor, ImFont* vFont) { + if (vFont) ImGui::PopFont(); + if (vShowColor) ImGui::PopStyleColor(); +} + +void IGFD::FileDialog::m_DrawFileListView(ImVec2 vSize) { + auto& fdi = m_FileDialogInternal.fileManager; + + ImGui::PushID(this); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY +#ifndef USE_CUSTOM_SORTING_ICON + | ImGuiTableFlags_Sortable +#endif // USE_CUSTOM_SORTING_ICON + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 4, flags, vSize, 0.0f)) { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.headerFileName.c_str(), ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0); + ImGui::TableSetupColumn(fdi.headerFileType.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderType ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 1); + ImGui::TableSetupColumn(fdi.headerFileSize.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderSize ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 2); + ImGui::TableSetupColumn(fdi.headerFileDate.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderDate ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 3); + +#ifndef USE_CUSTOM_SORTING_ICON + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) { + bool direction = sorts_specs->Specs->SortDirection == ImGuiSortDirection_Ascending; + + if (sorts_specs->Specs->ColumnUserID == 0) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME; + fdi.sortingDirection[0] = direction; + fdi.SortFields(m_FileDialogInternal); + } else if (sorts_specs->Specs->ColumnUserID == 1) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE; + fdi.sortingDirection[1] = direction; + fdi.SortFields(m_FileDialogInternal); + } else if (sorts_specs->Specs->ColumnUserID == 2) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE; + fdi.sortingDirection[2] = direction; + fdi.SortFields(m_FileDialogInternal); + } else // if (sorts_specs->Specs->ColumnUserID == 3) => alwayd true for the moment, to uncomment if we + // add a fourth column + { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE; + fdi.sortingDirection[3] = direction; + fdi.SortFields(m_FileDialogInternal); + } + + sorts_specs->SpecsDirty = false; + } + } + + ImGui::TableHeadersRow(); +#else // USE_CUSTOM_SORTING_ICON + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 4; column++) //-V112 + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) { + if (column == 0) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME) + fdi.sortingDirection[0] = !fdi.sortingDirection[0]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME; + + fdi.SortFields(m_FileDialogInternal); + } else if (column == 1) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_TYPE) + fdi.sortingDirection[1] = !fdi.sortingDirection[1]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE; + + fdi.SortFields(m_FileDialogInternal); + } else if (column == 2) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_SIZE) + fdi.sortingDirection[2] = !fdi.sortingDirection[2]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE; + + fdi.SortFields(m_FileDialogInternal); + } else // if (column == 3) => alwayd true for the moment, to uncomment if we add a fourth column + { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_DATE) + fdi.sortingDirection[3] = !fdi.sortingDirection[3]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE; + + fdi.SortFields(m_FileDialogInternal); + } + } + } +#endif // USE_CUSTOM_SORTING_ICON + if (!fdi.IsFilteredListEmpty()) { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + int column_id = 0; + m_FileListClipper.Begin((int)fdi.GetFilteredListSize(), ImGui::GetTextLineHeightWithSpacing()); + while (m_FileListClipper.Step()) { + for (int i = m_FileListClipper.DisplayStart; i < m_FileListClipper.DisplayEnd; i++) { + if (i < 0) continue; + + auto infos_ptr = fdi.GetFilteredFileAt((size_t)i); + if (!infos_ptr.use_count()) continue; + + m_BeginFileColorIconStyle(infos_ptr, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos_ptr->fileNameExt); // found + + ImGui::TableNextRow(); + + column_id = 0; + if (ImGui::TableNextColumn()) { // file name + if (!infos_ptr->deviceInfos.empty()) { + _str += " " + infos_ptr->deviceInfos; + } + m_SelectableItem(i, infos_ptr, selected, _str.c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file type + ImGui::Text("%s", infos_ptr->fileExtLevels[0].c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file size + if (!infos_ptr->fileType.isDir()) { + ImGui::Text("%s ", infos_ptr->formatedFileSize.c_str()); + } else { + ImGui::TextUnformatted(""); + } + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file date + time + ImGui::Text("%s", infos_ptr->fileModifDate.c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + + m_EndFileColorIconStyle(_showColor, _font); + } + } + m_FileListClipper.End(); + } + +#ifdef USE_EXPLORATION_BY_KEYS + if (!fdi.inputPathActivated) { + m_LocateByInputKey(m_FileDialogInternal); + m_ExploreWithkeys(m_FileDialogInternal, listViewID); + } +#endif // USE_EXPLORATION_BY_KEYS + + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) { + m_FileDialogInternal.fileListViewIsActive = true; + } + + ImGui::EndTable(); + } + + ImGui::PopID(); +} + +#ifdef USE_THUMBNAILS +void IGFD::FileDialog::m_DrawThumbnailsListView(ImVec2 vSize) { + auto& fdi = m_FileDialogInternal.fileManager; + + ImGui::PushID(this); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY +#ifndef USE_CUSTOM_SORTING_ICON + | ImGuiTableFlags_Sortable +#endif // USE_CUSTOM_SORTING_ICON + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 5, flags, vSize, 0.0f)) { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.headerFileName.c_str(), ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0); + ImGui::TableSetupColumn(fdi.headerFileType.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderType ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 1); + ImGui::TableSetupColumn(fdi.headerFileSize.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderSize ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 2); + ImGui::TableSetupColumn(fdi.headerFileDate.c_str(), + ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderDate ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) | + ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), + -1, 3); + // not needed to have an option for hide the thumbnails since this is why this view is used + ImGui::TableSetupColumn(fdi.headerFileThumbnails.c_str(), ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderThumbnails ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 4); //-V112 + +#ifndef USE_CUSTOM_SORTING_ICON + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) { + bool direction = sorts_specs->Specs->SortDirection == ImGuiSortDirection_Ascending; + + if (sorts_specs->Specs->ColumnUserID == 0) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME; + fdi.sortingDirection[0] = direction; + fdi.SortFields(m_FileDialogInternal); + } else if (sorts_specs->Specs->ColumnUserID == 1) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE; + fdi.sortingDirection[1] = direction; + fdi.SortFields(m_FileDialogInternal); + } else if (sorts_specs->Specs->ColumnUserID == 2) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE; + fdi.sortingDirection[2] = direction; + fdi.SortFields(m_FileDialogInternal); + } else if (sorts_specs->Specs->ColumnUserID == 3) { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE; + fdi.sortingDirection[3] = direction; + fdi.SortFields(m_FileDialogInternal); + } else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we + // add another column + { + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS; + fdi.sortingDirection[4] = direction; + fdi.SortFields(m_FileDialogInternal); + } + + sorts_specs->SpecsDirty = false; + } + } + + ImGui::TableHeadersRow(); +#else // USE_CUSTOM_SORTING_ICON + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 5; column++) { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) { + if (column == 0) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME) + fdi.sortingDirection[0] = !fdi.sortingDirection[0]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME; + + fdi.SortFields(m_FileDialogInternal); + } else if (column == 1) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_TYPE) + fdi.sortingDirection[1] = !fdi.sortingDirection[1]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE; + + fdi.SortFields(m_FileDialogInternal); + } else if (column == 2) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_SIZE) + fdi.sortingDirection[2] = !fdi.sortingDirection[2]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE; + + fdi.SortFields(m_FileDialogInternal); + } else if (column == 3) { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_DATE) + fdi.sortingDirection[3] = !fdi.sortingDirection[3]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE; + + fdi.SortFields(m_FileDialogInternal); + } else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we + // add another column + { + if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS) + fdi.sortingDirection[4] = !fdi.sortingDirection[4]; + else + fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS; + + fdi.SortFields(m_FileDialogInternal); + } + } + } +#endif // USE_CUSTOM_SORTING_ICON + if (!fdi.IsFilteredListEmpty()) { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + ImGuiContext& g = *GImGui; + const float itemHeight = ImMax(g.FontSize, DisplayMode_ThumbailsList_ImageHeight) + g.Style.ItemSpacing.y; + + int column_id = 0; + m_FileListClipper.Begin((int)fdi.GetFilteredListSize(), itemHeight); + while (m_FileListClipper.Step()) { + for (int i = m_FileListClipper.DisplayStart; i < m_FileListClipper.DisplayEnd; i++) { + if (i < 0) continue; + + auto infos_ptr = fdi.GetFilteredFileAt((size_t)i); + if (!infos_ptr.use_count()) continue; + + m_BeginFileColorIconStyle(infos_ptr, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos_ptr->fileNameExt); // found + + ImGui::TableNextRow(); + + column_id = 0; + if (ImGui::TableNextColumn()) { // file name + if (!infos_ptr->deviceInfos.empty()) { + _str += " " + infos_ptr->deviceInfos; + } + m_SelectableItem(i, infos_ptr, selected, _str.c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file type + ImGui::Text("%s", infos_ptr->fileExtLevels[0].c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file size + if (!infos_ptr->fileType.isDir()) { + ImGui::Text("%s ", infos_ptr->formatedFileSize.c_str()); + } else { + ImGui::TextUnformatted(""); + } + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file date + time + ImGui::Text("%s", infos_ptr->fileModifDate.c_str()); + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + if (ImGui::TableNextColumn()) { // file thumbnails + auto th = &infos_ptr->thumbnailInfo; + + if (!th->isLoadingOrLoaded) { + m_AddThumbnailToLoad(infos_ptr); + } + if (th->isReadyToDisplay && th->textureID) { + ImGui::Image((ImTextureID)th->textureID, ImVec2((float)th->textureWidth, (float)th->textureHeight)); + } + m_DisplayFileInfosTooltip(i, column_id++, infos_ptr); + } + + m_EndFileColorIconStyle(_showColor, _font); + } + } + m_FileListClipper.End(); + } + +#ifdef USE_EXPLORATION_BY_KEYS + if (!fdi.inputPathActivated) { + m_LocateByInputKey(m_FileDialogInternal); + m_ExploreWithkeys(m_FileDialogInternal, listViewID); + } +#endif // USE_EXPLORATION_BY_KEYS + + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) { + m_FileDialogInternal.fileListViewIsActive = true; + } + + ImGui::EndTable(); + } + + ImGui::PopID(); +} + +void IGFD::FileDialog::m_DrawThumbnailsGridView(ImVec2 vSize) { + if (ImGui::BeginChild("##thumbnailsGridsFiles", vSize)) { + // todo + } + + ImGui::EndChild(); +} + +#endif + +void IGFD::FileDialog::m_DrawSidePane(float vHeight) { + ImGui::SameLine(); + + ImGui::BeginChild("##FileTypes", ImVec2(0, vHeight)); + + m_FileDialogInternal.getDialogConfig().sidePane(m_FileDialogInternal.filterManager.GetSelectedFilter().getFirstFilter().c_str(), m_FileDialogInternal.getDialogConfigRef().userDatas, &m_FileDialogInternal.canWeContinue); + ImGui::EndChild(); +} + +void IGFD::FileDialog::Close() { + m_FileDialogInternal.dLGkey.clear(); + m_FileDialogInternal.showDialog = false; +} + +bool IGFD::FileDialog::WasOpenedThisFrame(const std::string& vKey) const { + bool res = m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey; + if (res) { + res &= m_FileDialogInternal.lastImGuiFrameCount == GImGui->FrameCount; // return true if a dialog was displayed in this frame + } + return res; +} + +bool IGFD::FileDialog::WasOpenedThisFrame() const { + bool res = m_FileDialogInternal.showDialog; + if (res) { + res &= m_FileDialogInternal.lastImGuiFrameCount == GImGui->FrameCount; // return true if a dialog was displayed in this frame + } + return res; +} + +bool IGFD::FileDialog::IsOpened(const std::string& vKey) const { + return (m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey); +} + +bool IGFD::FileDialog::IsOpened() const { + return m_FileDialogInternal.showDialog; +} + +std::string IGFD::FileDialog::GetOpenedKey() const { + if (m_FileDialogInternal.showDialog) { + return m_FileDialogInternal.dLGkey; + } + return ""; +} + +std::string IGFD::FileDialog::GetFilePathName(IGFD_ResultMode vFlag) { + return m_FileDialogInternal.fileManager.GetResultingFilePathName(m_FileDialogInternal, vFlag); +} + +std::string IGFD::FileDialog::GetCurrentPath() { + return m_FileDialogInternal.fileManager.GetResultingPath(); +} + +std::string IGFD::FileDialog::GetCurrentFileName(IGFD_ResultMode vFlag) { + return m_FileDialogInternal.fileManager.GetResultingFileName(m_FileDialogInternal, vFlag); +} + +std::string IGFD::FileDialog::GetCurrentFilter() { + return m_FileDialogInternal.filterManager.GetSelectedFilter().title; +} + +std::map IGFD::FileDialog::GetSelection(IGFD_ResultMode vFlag) { + return m_FileDialogInternal.fileManager.GetResultingSelection(m_FileDialogInternal, vFlag); +} + +IGFD::UserDatas IGFD::FileDialog::GetUserDatas() const { + return m_FileDialogInternal.getDialogConfig().userDatas; +} + +bool IGFD::FileDialog::IsOk() const { + return m_FileDialogInternal.isOk; +} + +void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) { + m_FileDialogInternal.filterManager.SetFileStyle(vFlags, vCriteria, vInfos); +} + +void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) { + m_FileDialogInternal.filterManager.SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); +} + +void IGFD::FileDialog::SetFileStyle(FileStyle::FileStyleFunctor vFunctor) { + m_FileDialogInternal.filterManager.SetFileStyle(vFunctor); +} + +bool IGFD::FileDialog::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont** vOutFont) { + return m_FileDialogInternal.filterManager.GetFileStyle(vFlags, vCriteria, vOutColor, vOutIcon, vOutFont); +} + +void IGFD::FileDialog::ClearFilesStyle() { + m_FileDialogInternal.filterManager.ClearFilesStyle(); +} + +void IGFD::FileDialog::SetLocales(const int& /*vLocaleCategory*/, const std::string& vLocaleBegin, const std::string& vLocaleEnd) { + m_FileDialogInternal.puUseCustomLocale = true; + m_FileDialogInternal.localeBegin = vLocaleBegin; + m_FileDialogInternal.localeEnd = vLocaleEnd; +} + +////////////////////////////////////////////////////////////////////////////// +//// OVERWRITE DIALOG //////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +bool IGFD::FileDialog::m_Confirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags) { + // if confirmation => return true for confirm the overwrite et quit the dialog + // if cancel => return false && set IsOk to false for keep inside the dialog + + // if IsOk == false => return false for quit the dialog + if (!m_FileDialogInternal.isOk && vLastAction) { + m_QuitFrame(); + return true; + } + + // if IsOk == true && no check of overwrite => return true for confirm the dialog + if (m_FileDialogInternal.isOk && vLastAction && !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ConfirmOverwrite)) { + m_QuitFrame(); + return true; + } + + // if IsOk == true && check of overwrite => return false and show confirm to overwrite dialog + if ((m_FileDialogInternal.okResultToConfirm || (m_FileDialogInternal.isOk && vLastAction)) && (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ConfirmOverwrite)) { + if (m_FileDialogInternal.isOk) // catched only one time + { + if (!m_FileDialogInternal.fileManager.GetFileSystemInstance()->IsFileExist(GetFilePathName())) // not existing => quit dialog + { + m_QuitFrame(); + return true; + } else // existing => confirm dialog to open + { + m_FileDialogInternal.isOk = false; + m_FileDialogInternal.okResultToConfirm = true; + } + } + + std::string name = OverWriteDialogTitleString "##" + m_FileDialogInternal.dLGtitle + m_FileDialogInternal.dLGkey + "OverWriteDialog"; + + bool res = false; + + ImGui::OpenPopup(name.c_str()); + if (ImGui::BeginPopupModal(name.c_str(), (bool*)0, vFlags | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { + ImGui::SetWindowPos(m_FileDialogInternal.dialogCenterPos - ImGui::GetWindowSize() * 0.5f); // next frame needed for GetWindowSize to work + + ImGui::Text("%s", OverWriteDialogMessageString); + + if (IMGUI_BUTTON(OverWriteDialogConfirmButtonString)) { + m_FileDialogInternal.okResultToConfirm = false; + m_FileDialogInternal.isOk = true; + res = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + + if (IMGUI_BUTTON(OverWriteDialogCancelButtonString)) { + m_FileDialogInternal.okResultToConfirm = false; + m_FileDialogInternal.isOk = false; + res = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + if (res) { + m_QuitFrame(); + } + return res; + } + + return false; +} + +#endif // __cplusplus + +// return an initialized IGFD_FileDialog_Config +IGFD_C_API IGFD_FileDialog_Config IGFD_FileDialog_Config_Get() { + IGFD_FileDialog_Config res = {}; + res.path = ""; + res.fileName = ""; + res.filePathName = ""; + res.countSelectionMax = 1; + res.userDatas = nullptr; + res.sidePane = nullptr; + res.sidePaneWidth = 250.0f; + res.flags = ImGuiFileDialogFlags_Default; + return res; +} + +// Return an initialized IGFD_Selection_Pair +IGFD_C_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(void) { + IGFD_Selection_Pair res = {}; + res.fileName = nullptr; + res.filePathName = nullptr; + return res; +} + +// destroy only the content of vSelection_Pair +IGFD_C_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair) { + if (vSelection_Pair) { + delete[] vSelection_Pair->fileName; + delete[] vSelection_Pair->filePathName; + } +} + +// Return an initialized IGFD_Selection +IGFD_C_API IGFD_Selection IGFD_Selection_Get(void) { + return {nullptr, 0U}; +} + +// destroy only the content of vSelection +IGFD_C_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection) { + if (vSelection) { + if (vSelection->table) { + for (size_t i = 0U; i < vSelection->count; i++) { + IGFD_Selection_Pair_DestroyContent(&vSelection->table[i]); + } + delete[] vSelection->table; + } + vSelection->count = 0U; + } +} + +// create an instance of ImGuiFileDialog +IGFD_C_API ImGuiFileDialog* IGFD_Create(void) { + return new ImGuiFileDialog(); +} + +// destroy the instance of ImGuiFileDialog +IGFD_C_API void IGFD_Destroy(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + delete vContextPtr; + vContextPtr = nullptr; + } +} + +IGFD_C_API void IGFD_OpenDialog( // open a standard dialog + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const IGFD_FileDialog_Config vConfig) { // path + if (vContextPtr != nullptr) { + IGFD::FileDialogConfig config; + config.path = vConfig.path; + config.fileName = vConfig.fileName; + config.filePathName = vConfig.filePathName; + config.countSelectionMax = vConfig.countSelectionMax; + config.userDatas = vConfig.userDatas; + config.flags = vConfig.flags; + config.sidePane = vConfig.sidePane; + config.sidePaneWidth = vConfig.sidePaneWidth; + vContextPtr->OpenDialog(vKey, vTitle, vFilters, config); + } +} + +IGFD_C_API bool IGFD_DisplayDialog(ImGuiFileDialog* vContextPtr, const char* vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) { + if (vContextPtr != nullptr) { + return vContextPtr->Display(vKey, vFlags, vMinSize, vMaxSize); + } + return false; +} + +IGFD_C_API void IGFD_CloseDialog(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + vContextPtr->Close(); + } +} + +IGFD_C_API bool IGFD_IsOk(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + return vContextPtr->IsOk(); + } + return false; +} + +IGFD_C_API bool IGFD_WasKeyOpenedThisFrame(ImGuiFileDialog* vContextPtr, const char* vKey) { + if (vContextPtr != nullptr) { + return vContextPtr->WasOpenedThisFrame(vKey); + } + return false; +} + +IGFD_C_API bool IGFD_WasOpenedThisFrame(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + return vContextPtr->WasOpenedThisFrame(); + } + + return false; +} + +IGFD_C_API bool IGFD_IsKeyOpened(ImGuiFileDialog* vContextPtr, const char* vCurrentOpenedKey) { + if (vContextPtr != nullptr) { + return vContextPtr->IsOpened(vCurrentOpenedKey); + } + + return false; +} + +IGFD_C_API bool IGFD_IsOpened(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + return vContextPtr->IsOpened(); + } + + return false; +} + +IGFD_C_API IGFD_Selection IGFD_GetSelection(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) { + IGFD_Selection res = IGFD_Selection_Get(); + if (vContextPtr != nullptr) { + auto sel = vContextPtr->GetSelection(vMode); + if (!sel.empty()) { + res.count = sel.size(); + res.table = new IGFD_Selection_Pair[res.count]; + + size_t idx = 0U; + for (const auto& s : sel) { + IGFD_Selection_Pair* pair = res.table + idx++; + + // fileNameExt + if (!s.first.empty()) { + size_t siz = s.first.size() + 1U; + pair->fileName = new char[siz]; +#ifndef _MSC_VER + strncpy(pair->fileName, s.first.c_str(), siz); +#else // _MSC_VER + strncpy_s(pair->fileName, siz, s.first.c_str(), siz); +#endif // _MSC_VER + pair->fileName[siz - 1U] = '\0'; + } + + // filePathName + if (!s.second.empty()) { + size_t siz = s.second.size() + 1U; + pair->filePathName = new char[siz]; +#ifndef _MSC_VER + strncpy(pair->filePathName, s.second.c_str(), siz); +#else // _MSC_VER + strncpy_s(pair->filePathName, siz, s.second.c_str(), siz); +#endif // _MSC_VER + pair->filePathName[siz - 1U] = '\0'; + } + } + + return res; + } + } + + return res; +} + +IGFD_C_API char* IGFD_GetFilePathName(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) { + char* res = nullptr; + + if (vContextPtr != nullptr) { + auto s = vContextPtr->GetFilePathName(vMode); + if (!s.empty()) { + size_t siz = s.size() + 1U; + res = (char*)malloc(siz); + if (res) { +#ifndef _MSC_VER + strncpy(res, s.c_str(), siz); +#else // _MSC_VER + strncpy_s(res, siz, s.c_str(), siz); +#endif // _MSC_VER + res[siz - 1U] = '\0'; + } + } + } + + return res; +} + +IGFD_C_API char* IGFD_GetCurrentFileName(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) { + char* res = nullptr; + + if (vContextPtr != nullptr) { + auto s = vContextPtr->GetCurrentFileName(vMode); + if (!s.empty()) { + size_t siz = s.size() + 1U; + res = (char*)malloc(siz); + if (res) { +#ifndef _MSC_VER + strncpy(res, s.c_str(), siz); +#else // _MSC_VER + strncpy_s(res, siz, s.c_str(), siz); +#endif // _MSC_VER + res[siz - 1U] = '\0'; + } + } + } + + return res; +} + +IGFD_C_API char* IGFD_GetCurrentPath(ImGuiFileDialog* vContextPtr) { + char* res = nullptr; + + if (vContextPtr != nullptr) { + auto s = vContextPtr->GetCurrentPath(); + if (!s.empty()) { + size_t siz = s.size() + 1U; + res = (char*)malloc(siz); + if (res) { +#ifndef _MSC_VER + strncpy(res, s.c_str(), siz); +#else // _MSC_VER + strncpy_s(res, siz, s.c_str(), siz); +#endif // _MSC_VER + res[siz - 1U] = '\0'; + } + } + } + + return res; +} + +IGFD_C_API char* IGFD_GetCurrentFilter(ImGuiFileDialog* vContextPtr) { + char* res = nullptr; + + if (vContextPtr != nullptr) { + auto s = vContextPtr->GetCurrentFilter(); + if (!s.empty()) { + size_t siz = s.size() + 1U; + res = (char*)malloc(siz); + if (res) { +#ifndef _MSC_VER + strncpy(res, s.c_str(), siz); +#else // _MSC_VER + strncpy_s(res, siz, s.c_str(), siz); +#endif // _MSC_VER + res[siz - 1U] = '\0'; + } + } + } + + return res; +} + +IGFD_C_API void* IGFD_GetUserDatas(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + return vContextPtr->GetUserDatas(); + } + + return nullptr; +} + +IGFD_C_API void IGFD_SetFileStyle(ImGuiFileDialog* vContextPtr, IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4 vColor, const char* vIcon, + ImFont* vFont) //-V813 +{ + if (vContextPtr != nullptr) { + vContextPtr->SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); + } +} + +IGFD_C_API void IGFD_SetFileStyle2(ImGuiFileDialog* vContextPtr, IGFD_FileStyleFlags vFlags, const char* vCriteria, float vR, float vG, float vB, float vA, const char* vIcon, ImFont* vFont) { + if (vContextPtr != nullptr) { + vContextPtr->SetFileStyle(vFlags, vCriteria, ImVec4(vR, vG, vB, vA), vIcon, vFont); + } +} + +IGFD_C_API bool IGFD_GetFileStyle(ImGuiFileDialog* vContextPtr, IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4* vOutColor, char** vOutIconText, ImFont** vOutFont) { + if (vContextPtr != nullptr) { + std::string icon; + bool res = vContextPtr->GetFileStyle(vFlags, vCriteria, vOutColor, &icon, vOutFont); + if (!icon.empty() && vOutIconText) { + size_t siz = icon.size() + 1U; + *vOutIconText = (char*)malloc(siz); + if (*vOutIconText) { +#ifndef _MSC_VER + strncpy(*vOutIconText, icon.c_str(), siz); +#else // _MSC_VER + strncpy_s(*vOutIconText, siz, icon.c_str(), siz); +#endif // _MSC_VER + (*vOutIconText)[siz - 1U] = '\0'; + } + } + return res; + } + + return false; +} + +IGFD_C_API void IGFD_ClearFilesStyle(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + vContextPtr->ClearFilesStyle(); + } +} + +IGFD_C_API void SetLocales(ImGuiFileDialog* vContextPtr, const int vCategory, const char* vBeginLocale, const char* vEndLocale) { + if (vContextPtr != nullptr) { + vContextPtr->SetLocales(vCategory, (vBeginLocale ? vBeginLocale : ""), (vEndLocale ? vEndLocale : "")); + } +} + +#ifdef USE_EXPLORATION_BY_KEYS +IGFD_C_API void IGFD_SetFlashingAttenuationInSeconds(ImGuiFileDialog* vContextPtr, float vAttenValue) { + if (vContextPtr != nullptr) { + vContextPtr->SetFlashingAttenuationInSeconds(vAttenValue); + } +} +#endif + +#ifdef USE_PLACES_FEATURE +IGFD_C_API char* IGFD_SerializePlaces(ImGuiFileDialog* vContextPtr, bool vDontSerializeCodeBasedPlaces) { + char* res = nullptr; + + if (vContextPtr != nullptr) { + auto s = vContextPtr->SerializePlaces(vDontSerializeCodeBasedPlaces); + if (!s.empty()) { + size_t siz = s.size() + 1U; + res = (char*)malloc(siz); + if (res) { +#ifndef _MSC_VER + strncpy(res, s.c_str(), siz); +#else // _MSC_VER + strncpy_s(res, siz, s.c_str(), siz); +#endif // _MSC_VER + res[siz - 1U] = '\0'; + } + } + } + + return res; +} + +IGFD_C_API void IGFD_DeserializePlaces(ImGuiFileDialog* vContextPtr, const char* vPlaces) { + if (vContextPtr != nullptr) { + vContextPtr->DeserializePlaces(vPlaces); + } +} + +IGFD_C_API bool IGFD_AddPlacesGroup(ImGuiFileDialog* vContextPtr, const char* vGroupName, size_t vDisplayOrder, bool vCanBeEdited) { + if (vContextPtr != nullptr) { + return vContextPtr->AddPlacesGroup(vGroupName, vDisplayOrder, vCanBeEdited); + } + return false; +} + +IGFD_C_API bool IGFD_RemovePlacesGroup(ImGuiFileDialog* vContextPtr, const char* vGroupName) { + if (vContextPtr != nullptr) { + return vContextPtr->RemovePlacesGroup(vGroupName); + } + return false; +} + +IGFD_C_API bool IGFD_AddPlace(ImGuiFileDialog* vContextPtr, const char* vGroupName, const char* vPlaceName, const char* vPlacePath, bool vCanBeSaved, const char* vIconText) { + if (vContextPtr != nullptr) { + auto group_ptr = vContextPtr->GetPlacesGroupPtr(vGroupName); + if (group_ptr != nullptr) { + IGFD::FileStyle style; + style.icon = vIconText; + return group_ptr->AddPlace(vPlaceName, vPlacePath, vCanBeSaved, style); + } + } + return false; +} + +IGFD_C_API bool IGFD_RemovePlace(ImGuiFileDialog* vContextPtr, const char* vGroupName, const char* vPlaceName) { + if (vContextPtr != nullptr) { + auto group_ptr = vContextPtr->GetPlacesGroupPtr(vGroupName); + if (group_ptr != nullptr) { + return group_ptr->RemovePlace(vPlaceName); + } + } + return false; +} + +#endif + +#ifdef USE_THUMBNAILS +IGFD_C_API void SetCreateThumbnailCallback(ImGuiFileDialog* vContextPtr, const IGFD_CreateThumbnailFun vCreateThumbnailFun) { + if (vContextPtr != nullptr) { + vContextPtr->SetCreateThumbnailCallback(vCreateThumbnailFun); + } +} + +IGFD_C_API void SetDestroyThumbnailCallback(ImGuiFileDialog* vContextPtr, const IGFD_DestroyThumbnailFun vDestroyThumbnailFun) { + if (vContextPtr != nullptr) { + vContextPtr->SetDestroyThumbnailCallback(vDestroyThumbnailFun); + } +} + +IGFD_C_API void ManageGPUThumbnails(ImGuiFileDialog* vContextPtr) { + if (vContextPtr != nullptr) { + vContextPtr->ManageGPUThumbnails(); + } +} +#endif // USE_THUMBNAILS + +#pragma endregion diff --git a/imguifiledialog/ImGuiFileDialog.h b/imguifiledialog/ImGuiFileDialog.h new file mode 100644 index 0000000..1b8a0c0 --- /dev/null +++ b/imguifiledialog/ImGuiFileDialog.h @@ -0,0 +1,1220 @@ +/* + _____ _____ _ ______ _ _ _____ _ _ + |_ _| / ____| (_)| ____|(_)| | | __ \ (_) | | + | | _ __ ___ | | __ _ _ _ | |__ _ | | ___ | | | | _ __ _ | | ___ __ _ + | | | '_ ` _ \ | | |_ || | | || || __| | || | / _ \| | | || | / _` || | / _ \ / _` | + _| |_ | | | | | || |__| || |_| || || | | || || __/| |__| || || (_| || || (_) || (_| | + |_____||_| |_| |_| \_____| \__,_||_||_| |_||_| \___||_____/ |_| \__,_||_| \___/ \__, | + __/ | + |___/ + ___ __ ___ + / _ \ / / / _ \ + __ __| | | | / /_ | (_) | + \ \ / /| | | | | '_ \ > _ < + \ V / | |_| |_| (_) |_| (_) | + \_/ \___/(_)\___/(_)\___/ + +GITHUB REPOT : https://github.com/aiekick/ImGuiFileDialog +DOCUMENTATION : see the attached Documentation.md + +generated with "Text to ASCII Art Generator (TAAG)" +https://patorjk.com/software/taag/#p=display&h=1&v=0&f=Big&t=ImGuiFileDialog%0Av0.6.8 + +MIT License + +Copyright (c) 2018-2024 Stephane Cuillerdier (aka aiekick) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#define IGFD_VERSION "v0.6.8" +#define IGFD_IMGUI_SUPPORTED_VERSION "1.90.5 WIP" + +// Config file +#ifndef CUSTOM_IMGUIFILEDIALOG_CONFIG +#include "ImGuiFileDialogConfig.h" +#else // CUSTOM_IMGUIFILEDIALOG_CONFIG +#include CUSTOM_IMGUIFILEDIALOG_CONFIG +#endif // CUSTOM_IMGUIFILEDIALOG_CONFIG + +// Define attributes of all API symbols declarations (e.g. for DLL under Windows) +// Using ImGuiFileDialog via a shared library is not recommended, because we don't guarantee +// backward nor forward ABI compatibility and also function call overhead. If you +// do use ImGuiFileDialog as a DLL, be sure to call ImGui::SetImGuiContext (see ImGui doc Miscellanous section). + +#ifndef IGFD_API +#define IGFD_API +#endif // IGFD_API + +/////////////////////////////////////////////////////////// +/////////////// FLAGS ///////////////////////////////////// +/////////////////////////////////////////////////////////// + +// file style enum for file display (color, icon, font) +typedef int IGFD_FileStyleFlags; // -> enum IGFD_FileStyleFlags_ +enum IGFD_FileStyleFlags_ // by evaluation / priority order +{ + IGFD_FileStyle_None = 0, // define none style + IGFD_FileStyleByTypeFile = (1 << 0), // define style for all files + IGFD_FileStyleByTypeDir = (1 << 1), // define style for all dir + IGFD_FileStyleByTypeLink = (1 << 2), // define style for all link + IGFD_FileStyleByExtention = (1 << 3), // define style by extention, for files or links + IGFD_FileStyleByFullName = (1 << 4), // define style for particular file/dir/link full name (filename + extention) + IGFD_FileStyleByContainedInFullName = (1 << 5), // define style for file/dir/link when criteria is contained in full name +}; + +typedef int ImGuiFileDialogFlags; // -> enum ImGuiFileDialogFlags_ +enum ImGuiFileDialogFlags_ { + ImGuiFileDialogFlags_None = 0, // define none default flag + ImGuiFileDialogFlags_ConfirmOverwrite = (1 << 0), // show confirm to overwrite dialog + ImGuiFileDialogFlags_DontShowHiddenFiles = (1 << 1), // dont show hidden file (file starting with a .) + ImGuiFileDialogFlags_DisableCreateDirectoryButton = (1 << 2), // disable the create directory button + ImGuiFileDialogFlags_HideColumnType = (1 << 3), // hide column file type + ImGuiFileDialogFlags_HideColumnSize = (1 << 4), // hide column file size + ImGuiFileDialogFlags_HideColumnDate = (1 << 5), // hide column file date + ImGuiFileDialogFlags_NoDialog = (1 << 6), // let the dialog embedded in your own imgui begin / end scope + ImGuiFileDialogFlags_ReadOnlyFileNameField = (1 << 7), // don't let user type in filename field for file open style dialogs + ImGuiFileDialogFlags_CaseInsensitiveExtentionFiltering = (1 << 8), // the file extentions filtering will not take into account the case + ImGuiFileDialogFlags_Modal = (1 << 9), // modal + ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 10), // disable the thumbnail mode + ImGuiFileDialogFlags_DisablePlaceMode = (1 << 11), // disable the place mode + ImGuiFileDialogFlags_DisableQuickPathSelection = (1 << 12), // disable the quick path selection + ImGuiFileDialogFlags_ShowDevicesButton = (1 << 13), // show the devices selection button + ImGuiFileDialogFlags_NaturalSorting = (1 << 14), // enable the antural sorting for filenames and extentions, slower than standard sorting + + // default behavior when no flags is defined. seems to be the more common cases + ImGuiFileDialogFlags_Default = ImGuiFileDialogFlags_ConfirmOverwrite | // + ImGuiFileDialogFlags_Modal | // + ImGuiFileDialogFlags_HideColumnType +}; + +// flags used for GetFilePathName(flag) or GetSelection(flag) +typedef int IGFD_ResultMode; // -> enum IGFD_ResultMode_ +enum IGFD_ResultMode_ { + // IGFD_ResultMode_AddIfNoFileExt + // add the file ext only if there is no file ext + // filter {.cpp,.h} with file : + // toto.h => toto.h + // toto.a.h => toto.a.h + // toto.a. => toto.a.cpp + // toto. => toto.cpp + // toto => toto.cpp + // filter {.z,.a.b} with file : + // toto.a.h => toto.a.h + // toto. => toto.z + // toto => toto.z + // filter {.g.z,.a} with file : + // toto.a.h => toto.a.h + // toto. => toto.g.z + // toto => toto.g.z + IGFD_ResultMode_AddIfNoFileExt = 0, // default + + // IGFD_ResultMode_OverwriteFileExt + // Overwrite the file extention by the current filter : + // filter {.cpp,.h} with file : + // toto.h => toto.cpp + // toto.a.h => toto.a.cpp + // toto.a. => toto.a.cpp + // toto.a.h.t => toto.a.h.cpp + // toto. => toto.cpp + // toto => toto.cpp + // filter {.z,.a.b} with file : + // toto.a.h => toto.z + // toto.a.h.t => toto.a.z + // toto. => toto.z + // toto => toto.z + // filter {.g.z,.a} with file : + // toto.a.h => toto.g.z + // toto.a.h.y => toto.a.g.z + // toto.a. => toto.g.z + // toto. => toto.g.z + // toto => toto.g.z + IGFD_ResultMode_OverwriteFileExt = 1, // behavior pre IGFD v0.6.6 + + // IGFD_ResultMode_KeepInputFile + // keep the input file => no modification : + // filter {.cpp,.h} with file : + // toto.h => toto.h + // toto. => toto. + // toto => toto + // filter {.z,.a.b} with file : + // toto.a.h => toto.a.h + // toto. => toto. + // toto => toto + // filter {.g.z,.a} with file : + // toto.a.h => toto.a.h + // toto. => toto. + // toto => toto + IGFD_ResultMode_KeepInputFile = 2 +}; + +/////////////////////////////////////////////////////////// +/////////////// STRUCTS /////////////////////////////////// +/////////////////////////////////////////////////////////// + +#ifdef USE_THUMBNAILS +struct IGFD_Thumbnail_Info { + int isReadyToDisplay = 0; // ready to be rendered, so texture created + int isReadyToUpload = 0; // ready to upload to gpu + int isLoadingOrLoaded = 0; // was sent to laoding or loaded + int textureWidth = 0; // width of the texture to upload + int textureHeight = 0; // height of the texture to upload + int textureChannels = 0; // count channels of the texture to upload + unsigned char* textureFileDatas = 0; // file texture datas, will be rested to null after gpu upload + void* textureID = 0; // 2d texture id (void* is like ImtextureID type) (GL, DX, VK, Etc..) + void* userDatas = 0; // user datas +}; +#endif // USE_THUMBNAILS + +// stdint is used for cpp and c apî (cstdint is only for cpp) +#include + +#ifdef __cplusplus + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif // IMGUI_DEFINE_MATH_OPERATORS + +#ifdef IMGUI_INCLUDE +#include IMGUI_INCLUDE +#else // IMGUI_INCLUDE +#include +#endif // IMGUI_INCLUDE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef defaultSortField +#define defaultSortField FIELD_FILENAME +#endif // defaultSortField + +#ifndef defaultSortOrderFilename +#define defaultSortOrderFilename true +#endif // defaultSortOrderFilename +#ifndef defaultSortOrderType +#define defaultSortOrderType true +#endif // defaultSortOrderType +#ifndef defaultSortOrderSize +#define defaultSortOrderSize true +#endif // defaultSortOrderSize +#ifndef defaultSortOrderDate +#define defaultSortOrderDate true +#endif // defaultSortOrderDate +#ifndef defaultSortOrderThumbnails +#define defaultSortOrderThumbnails true +#endif // defaultSortOrderThumbnails + +#ifndef MAX_FILE_DIALOG_NAME_BUFFER +#define MAX_FILE_DIALOG_NAME_BUFFER 1024 +#endif // MAX_FILE_DIALOG_NAME_BUFFER + +#ifndef MAX_PATH_BUFFER_SIZE +#define MAX_PATH_BUFFER_SIZE 1024 +#endif // MAX_PATH_BUFFER_SIZE + +#ifndef EXT_MAX_LEVEL +#define EXT_MAX_LEVEL 10U +#endif // EXT_MAX_LEVEL + +namespace IGFD { + +template +class SearchableVector { +private: + std::unordered_map m_Dico; + std::vector m_Array; + +public: + void clear() { + m_Dico.clear(); + m_Array.clear(); + } + bool empty() const { + return m_Array.empty(); + } + size_t size() const { + return m_Array.size(); + } + T& operator[](const size_t& vIdx) { + return m_Array[vIdx]; + } + T& at(const size_t& vIdx) { + return m_Array.at(vIdx); + } + typename std::vector::iterator begin() { + return m_Array.begin(); + } + typename std::vector::const_iterator begin() const { + return m_Array.begin(); + } + typename std::vector::iterator end() { + return m_Array.end(); + } + typename std::vector::const_iterator end() const { + return m_Array.end(); + } + + bool try_add(T vKey) { + if (!exist(vKey)) { + m_Dico[vKey] = m_Array.size(); + m_Array.push_back(vKey); + return true; + } + return false; + } + + bool try_set_existing(T vKey) { + if (exist(vKey)) { + auto row = m_Dico.at(vKey); + m_Array[row] = vKey; + return true; + } + return false; + } + + bool exist(const std::string& vKey) const { + return (m_Dico.find(vKey) != m_Dico.end()); + } +}; + +class IGFD_API FileInfos; +class IGFD_API FileDialogInternal; + +class IGFD_API Utils { +public: + struct PathStruct { + std::string path; + std::string name; + std::string ext; + bool isOk = false; + }; + +public: + static bool ImSplitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f); + static bool ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr, const size_t& vMaxRecursion = 10U); + static void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); + static void ResetBuffer(char* vBuffer); + static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); + static std::string UTF8Encode(const std::wstring& wstr); + static std::wstring UTF8Decode(const std::string& str); + static std::vector SplitStringToVector(const std::string& vText, const std::string& vDelimiterPattern, const bool& vPushEmpty); + static std::vector SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty); + static std::string LowerCaseString(const std::string& vString); // turn all text in lower case for search facilitie + static size_t GetCharCountInString(const std::string& vString, const char& vChar); + static size_t GetLastCharPosWithMinCharCount(const std::string& vString, const char& vChar, const size_t& vMinCharCount); + static std::string GetPathSeparator(); // return the slash for any OS ( \\ win, / unix) + static std::string RoundNumber(double vvalue, int n); // custom rounding number + static std::string FormatFileSize(size_t vByteSize); // format file size field + static bool NaturalCompare(const std::string& vA, const std::string& vB, bool vInsensitiveCase, bool vDescending); // natural sorting + +#ifdef NEED_TO_BE_PUBLIC_FOR_TESTS +public: +#else +private: +#endif + static bool M_IsAValidCharExt(const char& c); + static bool M_IsAValidCharSuffix(const char& c); + static bool M_ExtractNumFromStringAtPos(const std::string& str, size_t& pos, double& vOutNum); +}; + +class IGFD_API FileStyle { +public: + typedef std::function FileStyleFunctor; + +public: + ImVec4 color = ImVec4(0, 0, 0, 0); + std::string icon; + ImFont* font = nullptr; + IGFD_FileStyleFlags flags = 0; + +public: + FileStyle(); + FileStyle(const FileStyle& vStyle); + FileStyle(const ImVec4& vColor, const std::string& vIcon = "", ImFont* vFont = nullptr); +}; + +class IGFD_API SearchManager { +public: + std::string searchTag; + char searchBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; + bool searchInputIsActive = false; + +public: + void Clear(); // clear datas + void DrawSearchBar(FileDialogInternal& vFileDialogInternal); // draw the search bar +}; + +class IGFD_API FilterInfos { +private: + // just for return a default const std::string& in getFirstFilter. + // cannot be const, because FilterInfos must be affected to an another FilterInfos + // must stay empty all time + std::string empty_string; + +public: + std::string title; // displayed filter.can be different than rela filter + SearchableVector filters; // filters + SearchableVector filters_optimized; // optimized filters for case insensitive search + std::vector filters_regex; // collection of regex filter type + size_t count_dots = 0U; // the max count dot the max per filter of all filters + +public: + void clear(); // clear the datas + bool empty() const; // is filter empty + const std::string& getFirstFilter() const; // get the first filter + bool regexExist(const std::string& vFilter) const; // is regex filter exist + bool exist(const FileInfos& vFileInfos, bool vIsCaseInsensitive) const; // is filter exist + void setCollectionTitle(const std::string& vTitle); // set the collection title + void addFilter(const std::string& vFilter, const bool& vIsRegex); // add a filter + void addCollectionFilter(const std::string& vFilter, const bool& vIsRegex); // add a filter in collection + static std::string transformAsteriskBasedFilterToRegex(const std::string& vFilter); // will transform a filter who contain * to a regex +}; + +class IGFD_API FilterManager { +#ifdef NEED_TO_BE_PUBLIC_FOR_TESTS +public: +#else +private: +#endif + std::vector m_ParsedFilters; + std::unordered_map > > m_FilesStyle; // file infos for file + // extention only + std::vector m_FilesStyleFunctors; // file style via lambda function + FilterInfos m_SelectedFilter; + +public: + std::string dLGFilters; + std::string dLGdefaultExt; + +public: + const FilterInfos& GetSelectedFilter() const; + void ParseFilters(const char* vFilters); // Parse filter syntax, detect and parse filter collection + void SetSelectedFilterWithExt(const std::string& vFilter); // Select filter + bool FillFileStyle(std::shared_ptr vFileInfos) const; // fill with the good style + void SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos); // Set FileStyle + void SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, + ImFont* vFont); // link file style to Color and Icon and Font + void SetFileStyle(FileStyle::FileStyleFunctor vFunctor); // lambda functor for set file style. + bool GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, + ImFont** vOutFont); // Get Color and Icon for Filter + void ClearFilesStyle(); // clear m_FileStyle + bool IsCoveredByFilters(const FileInfos& vFileInfos, + bool vIsCaseInsensitive) const; // check if current file extention (vExt) is covered by current filter, or by regex (vNameExt) + float GetFilterComboBoxWidth() const; // will return the current combo box widget width + bool DrawFilterComboBox(FileDialogInternal& vFileDialogInternal); // draw the filter combobox // get the current selected filter + std::string ReplaceExtentionWithCurrentFilterIfNeeded(const std::string& vFileName, + IGFD_ResultMode vFlag) const; // replace the extention of the current file by the selected filter + void SetDefaultFilterIfNotDefined(); // define the first filter if no filter is selected +}; + +class IGFD_API FileType { +public: + enum class ContentType { + // The ordering will be used during sort. + Invalid = -1, + Directory = 0, + File = 1, + LinkToUnknown = 2, // link to something that is not a regular file or directory. + }; + +private: + ContentType m_Content = ContentType::Invalid; + bool m_Symlink = false; + +public: + FileType(); + FileType(const ContentType& vContentType, const bool& vIsSymlink); + + void SetContent(const ContentType& vContentType); + void SetSymLink(const bool& vIsSymlink); + + bool isValid() const; + bool isDir() const; + bool isFile() const; + bool isLinkToUnknown() const; + bool isSymLink() const; + + // Comparisons only care about the content type, ignoring whether it's a symlink or not. + bool operator==(const FileType& rhs) const; + bool operator!=(const FileType& rhs) const; + bool operator<(const FileType& rhs) const; + bool operator>(const FileType& rhs) const; +}; + +class IGFD_API FileInfos { +public: + static std::shared_ptr create(); + +public: + // extention of the file, the array is the levels of ext, by ex : .a.b.c, will be save in {.a.b.c, .b.c, .c} + // 10 level max are sufficient i guess. the others levels will be checked if countExtDot > 1 + std::array fileExtLevels; + std::array fileExtLevels_optimized; // optimized for search => insensitivecase + // same for file name, can be sued in userFileAttributesFun + std::array fileNameLevels; + std::array fileNameLevels_optimized; // optimized for search => insensitivecase + size_t countExtDot = 0U; // count dots in file extention. this count will give the levels in fileExtLevels + FileType fileType; // fileType + std::string filePath; // path of the file + std::string fileName; // file name only + std::string fileNameExt; // filename of the file (file name + extention) (but no path) + std::string fileNameExt_optimized; // optimized for search => insensitivecase + std::string deviceInfos; // quick infos to display after name for devices + std::string tooltipMessage; // message to display on the tooltip, is not empty + int32_t tooltipColumn = -1; // the tooltip will appears only when the mouse is over the tooltipColumn if > -1 + size_t fileSize = 0U; // for sorting operations + std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go) + std::string fileModifDate; // file user defined format of the date (data + time by default) + std::shared_ptr fileStyle = nullptr; // style of the file +#ifdef USE_THUMBNAILS + IGFD_Thumbnail_Info thumbnailInfo; // structre for the display for image file tetxure +#endif // USE_THUMBNAILS + +public: + bool SearchForTag(const std::string& vTag) const; // will search a tag in fileNameExt and fileNameExt_optimized + bool SearchForExt(const std::string& vExt, const bool& vIsCaseInsensitive, + const size_t& vMaxLevel = EXT_MAX_LEVEL) const; // will check the fileExtLevels levels for vExt, until vMaxLevel + bool SearchForExts(const std::string& vComaSepExts, const bool& vIsCaseInsensitive, + const size_t& vMaxLevel = EXT_MAX_LEVEL) const; // will check the fileExtLevels levels for vExts (ext are coma separated), until vMaxLevel + bool FinalizeFileTypeParsing(const size_t& vMaxDotToExtract); // finalize the parsing the file (only a file or link to file. no dir) +}; + +typedef std::pair PathDisplayedName; + +class IFileSystem { +public: + virtual ~IFileSystem() = default; + // say if a directory can be openened or for any reason locked + virtual bool IsDirectoryCanBeOpened(const std::string& vName) = 0; + // say if a directory exist + virtual bool IsDirectoryExist(const std::string& vName) = 0; + // say if a file exist + virtual bool IsFileExist(const std::string& vName) = 0; + // say if a directory was created, return false if vName is invalid or alreayd exist + virtual bool CreateDirectoryIfNotExist(const std::string& vName) = 0; + // extract the component of a file path name, like path, name, ext + virtual IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) = 0; + // will return a list of files inside a path + virtual std::vector ScanDirectory(const std::string& vPath) = 0; + // say if the path is well a directory + virtual bool IsDirectory(const std::string& vFilePathName) = 0; + // return a device list () on windows, but can be used on other platforms for give to the user a list of devices paths. + virtual std::vector GetDevicesList() = 0; +}; + +class IGFD_API FileManager { +public: // types + enum class SortingFieldEnum { // sorting for filetering of the file lsit + FIELD_NONE = 0, // no sorting reference, result indetermined haha.. + FIELD_FILENAME, // sorted by filename + FIELD_TYPE, // sorted by filetype + FIELD_SIZE, // sorted by filesize (formated file size) + FIELD_DATE, // sorted by filedate + FIELD_THUMBNAILS, // sorted by thumbnails (comparaison by width then by height) + }; + +#ifdef NEED_TO_BE_PUBLIC_FOR_TESTS +public: +#else +private: +#endif + std::string m_CurrentPath; // current path (to be decomposed in m_CurrentPathDecomposition + std::vector m_CurrentPathDecomposition; // part words + std::vector > m_FileList; // base container + std::vector > m_FilteredFileList; // filtered container (search, sorting, etc..) + std::vector > m_PathList; // base container for path selection + std::vector > m_FilteredPathList; // filtered container for path selection (search, sorting, etc..) + std::vector::iterator m_PopupComposedPath; // iterator on m_CurrentPathDecomposition for Current Path popup + std::string m_LastSelectedFileName; // for shift multi selection + std::set m_SelectedFileNames; // the user selection of FilePathNames + bool m_CreateDirectoryMode = false; // for create directory widget + std::string m_FileSystemName; + std::unique_ptr m_FileSystemPtr = nullptr; + +public: + bool inputPathActivated = false; // show input for path edition + bool devicesClicked = false; // event when a drive button is clicked + bool pathClicked = false; // event when a path button was clicked + char inputPathBuffer[MAX_PATH_BUFFER_SIZE] = ""; // input path buffer for imgui widget input text (displayed in palce of composer) + char variadicBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // called by m_SelectableItem + char fileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // file name buffer in footer for imgui widget input text + char directoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // directory name buffer (when in directory mode) + std::string headerFileName; // detail view name of column file + std::string headerFileType; // detail view name of column type + std::string headerFileSize; // detail view name of column size + std::string headerFileDate; // detail view name of column date + time +#ifdef USE_THUMBNAILS + std::string headerFileThumbnails; // detail view name of column thumbnails + bool sortingDirection[5] = { // true => Ascending, false => Descending + defaultSortOrderFilename, defaultSortOrderType, defaultSortOrderSize, defaultSortOrderDate, defaultSortOrderThumbnails}; +#else + bool sortingDirection[4] = { // true => Ascending, false => Descending + defaultSortOrderFilename, defaultSortOrderType, defaultSortOrderSize, defaultSortOrderDate}; +#endif + SortingFieldEnum sortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column + bool showDevices = false; // devices are shown (only on os windows) + + std::string dLGpath; // base path set by user when OpenDialog was called + std::string dLGDefaultFileName; // base default file path name set by user when OpenDialog was called + size_t dLGcountSelectionMax = 1U; // 0 for infinite // base max selection count set by user when OpenDialog was called + bool dLGDirectoryMode = false; // is directory mode (defiend like : dLGDirectoryMode = (filters.empty())) + + std::string fsRoot; + +#ifdef NEED_TO_BE_PUBLIC_FOR_TESTS +public: +#else +private: +#endif + static void m_CompleteFileInfos(const std::shared_ptr& vInfos); // set time and date infos of a file (detail view mode) + void m_RemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name + void m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name + void m_AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, + const FileType& vFileType); // add file called by scandir + void m_AddPath(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, + const FileType& vFileType); // add file called by scandir + void m_ScanDirForPathSelection(const FileDialogInternal& vFileDialogInternal, + const std::string& vPath); // scan the directory for retrieve the path list + void m_OpenPathPopup(const FileDialogInternal& vFileDialogInternal, + std::vector::iterator vPathIter); // open the popup list of paths + void m_SetCurrentPath(std::vector::iterator vPathIter); // set the current path, update the path bar + void m_ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal, std::vector >& vFileInfosList, std::vector >& vFileInfosFilteredList); + static bool M_SortStrings(const FileDialogInternal& vFileDialogInternal, // + const bool vInsensitiveCase, const bool vDescendingOrder, // + const std::string& vA, const std::string& vB); + void m_SortFields(const FileDialogInternal& vFileDialogInternal, std::vector >& vFileInfosList, + std::vector >& vFileInfosFilteredList); // will sort a column + bool m_CompleteFileInfosWithUserFileAttirbutes(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos); + +public: + FileManager(); + bool IsComposerEmpty() const; + size_t GetComposerSize() const; + bool IsFileListEmpty() const; + bool IsPathListEmpty() const; + bool IsFilteredListEmpty() const; + bool IsPathFilteredListEmpty() const; + size_t GetFullFileListSize() const; + std::shared_ptr GetFullFileAt(size_t vIdx); + size_t GetFilteredListSize() const; + size_t GetPathFilteredListSize() const; + std::shared_ptr GetFilteredFileAt(size_t vIdx); + std::shared_ptr GetFilteredPathAt(size_t vIdx); + std::vector::iterator GetCurrentPopupComposedPath() const; + bool IsFileNameSelected(const std::string& vFileName); + std::string GetBack(); + void ClearComposer(); + void ClearFileLists(); // clear file list, will destroy thumbnail textures + void ClearPathLists(); // clear path list, will destroy thumbnail textures + void ClearAll(); + void ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal); + void SortFields(const FileDialogInternal& vFileDialogInternal); // will sort a column + void OpenCurrentPath(const FileDialogInternal& vFileDialogInternal); // set the path of the dialog, will launch the scandir for populate the file listview + bool GetDevices(); // list devices + bool CreateDir(const std::string& vPath); // create a directory on the file system + std::string ComposeNewPath(std::vector::iterator vIter); // compose a path from the compose path widget + bool SetPathOnParentDirectoryIfAny(); // compose paht on parent directory + std::string GetCurrentPath(); // get the current path + void SetCurrentPath(const std::string& vCurrentPath); // set the current path + void SetDefaultFileName(const std::string& vFileName); + bool SelectDirectory(const std::shared_ptr& vInfos); // enter directory + void SelectAllFileNames(); + void SelectFileName(const std::shared_ptr& vInfos); // add a filename in selection + void SelectOrDeselectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos); // add/remove a filename in selection + void SetCurrentDir(const std::string& vPath); // define current directory for scan + void ScanDir(const FileDialogInternal& vFileDialogInternal, + const std::string& vPath); // scan the directory for retrieve the file list + std::string GetResultingPath(); + std::string GetResultingFileName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag); + std::string GetResultingFilePathName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag); + std::map GetResultingSelection(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag); + + void DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal); // draw directory creation widget + void DrawPathComposer(const FileDialogInternal& vFileDialogInternal); + + IFileSystem* GetFileSystemInstance() const { + return m_FileSystemPtr.get(); + } + const std::string& GetFileSystemName() const { + return m_FileSystemName; + } +}; + +typedef void* UserDatas; +typedef std::function PaneFun; // side pane function binding +typedef std::function UserFileAttributesFun; // custom file Attributes call back, reject file if false + +struct IGFD_API FileDialogConfig { + std::string path; // path + std::string fileName; // defaut file name + std::string filePathName; // if not empty, the filename and the path will be obtained from filePathName + int32_t countSelectionMax = 1; // count selection max, 0 for infinite + UserDatas userDatas = nullptr; // user datas (can be retrieved in pane) + ImGuiFileDialogFlags flags = ImGuiFileDialogFlags_None; // ImGuiFileDialogFlags + PaneFun sidePane; // side pane callback + float sidePaneWidth = 250.0f; // side pane width + UserFileAttributesFun userFileAttributes; // user file Attibutes callback +}; + +class IGFD_API FileDialogInternal { +public: + FileManager fileManager; // the file manager + FilterManager filterManager; // the filter manager + SearchManager searchManager; // the search manager + +public: + std::string name; // the internal dialog name (title + ##word) + bool showDialog = false; // the dialog is shown + ImVec2 dialogCenterPos = ImVec2(0, 0); // center pos for display the confirm overwrite dialog + int lastImGuiFrameCount = 0; // to be sure than only one dialog displayed per frame + float footerHeight = 0.0f; // footer height + bool canWeContinue = true; // events + bool okResultToConfirm = false; // to confim if ok for OverWrite + bool isOk = false; // is dialog ok button click + bool fileInputIsActive = false; // when input text for file or directory is active + bool fileListViewIsActive = false; // when list view is active + std::string dLGkey; // the dialog key + std::string dLGtitle; // the dialog title + bool needToExitDialog = false; // we need to exit the dialog + bool puUseCustomLocale = false; // custom user locale + int localeCategory = LC_ALL; // locale category to use + std::string localeBegin; // the locale who will be applied at start of the display dialog + std::string localeEnd; // the locale who will be applaied at end of the display dialog + +private: + FileDialogConfig m_DialogConfig; + +public: + void NewFrame(); // new frame, so maybe neded to do somethings, like reset events + void EndFrame(); // end frame, so maybe neded to do somethings fater all + void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog + + void configureDialog( // open simple dialog + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters, if null, will display only directories + const FileDialogConfig& vConfig); // FileDialogConfig + const FileDialogConfig& getDialogConfig() const; + FileDialogConfig& getDialogConfigRef(); +}; + +class IGFD_API ThumbnailFeature { +protected: + ThumbnailFeature(); + ~ThumbnailFeature(); + + void m_NewThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void m_EndThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void m_QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal); + +#ifdef USE_THUMBNAILS +public: + typedef std::function CreateThumbnailFun; // texture 2d creation function binding + typedef std::function DestroyThumbnailFun; // texture 2d destroy function binding + +protected: + enum class DisplayModeEnum { FILE_LIST = 0, THUMBNAILS_LIST, THUMBNAILS_GRID }; + +private: + uint32_t m_CountFiles = 0U; + bool m_IsWorking = false; + std::shared_ptr m_ThumbnailGenerationThread = nullptr; + std::list > m_ThumbnailFileDatasToGet; // base container + std::mutex m_ThumbnailFileDatasToGetMutex; + std::condition_variable m_ThumbnailFileDatasToGetCv; + std::list > m_ThumbnailToCreate; // base container + std::mutex m_ThumbnailToCreateMutex; + std::list m_ThumbnailToDestroy; // base container + std::mutex m_ThumbnailToDestroyMutex; + + CreateThumbnailFun m_CreateThumbnailFun = nullptr; + DestroyThumbnailFun m_DestroyThumbnailFun = nullptr; + +protected: + DisplayModeEnum m_DisplayMode = DisplayModeEnum::FILE_LIST; + +private: + void m_VariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...); + +protected: + // will be call in cpu zone (imgui computations, will call a texture file retrieval thread) + void m_StartThumbnailFileDatasExtraction(); // start the thread who will get byte buffer from image files + bool m_StopThumbnailFileDatasExtraction(); // stop the thread who will get byte buffer from image files + void m_ThreadThumbnailFileDatasExtractionFunc(); // the thread who will get byte buffer from image files + void m_DrawThumbnailGenerationProgress(); // a little progressbar who will display the texture gen status + void m_AddThumbnailToLoad(const std::shared_ptr& vFileInfos); // add texture to load in the thread + void m_AddThumbnailToCreate(const std::shared_ptr& vFileInfos); + void m_AddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info); + void m_DrawDisplayModeToolBar(); // draw display mode toolbar (file list, thumbnails list, small thumbnails grid, big thumbnails grid) + void m_ClearThumbnails(FileDialogInternal& vFileDialogInternal); + +public: + void SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun); + void SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun); + + // must be call in gpu zone (rendering, possibly one rendering thread) + void ManageGPUThumbnails(); // in gpu rendering zone, whill create or destroy texture +#endif +}; + +class IGFD_API PlacesFeature { +protected: + PlacesFeature(); + +#ifdef USE_PLACES_FEATURE +private: + struct PlaceStruct { + std::string name; // name of the place + // todo: the path could be relative, better if the app is moved but place path can be outside of the app + std::string path; // absolute path of the place + bool canBeSaved = true; // defined by code, can be used for prevent serialization / deserialization + FileStyle style; + float thickness = 0.0f; // when more than 0.0f, is a separator + }; + + struct GroupStruct { + bool canBeSaved = false; // defined by code, can be used for prevent serialization / deserialization + size_t displayOrder = 0U; // the display order will be usedf first, then alphanumeric + bool defaultOpened = false; // the group is opened by default + bool canBeEdited = false; // will show +/- button for add/remove place in the group + char editBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // temp buffer for name edition + int32_t selectedPlaceForEdition = -1; + ImGuiTreeNodeFlags collapsingHeaderFlag = ImGuiTreeNodeFlags_None; + ImGuiListClipper clipper; // the list clipper of the grou + std::string name; // the group name, will be displayed + std::vector places; // the places (name + path) + bool AddPlace( // add a place by code + const std::string& vPlaceName, // place name + const std::string& vPlacePath, // place path + const bool& vCanBeSaved, // prevent serialization + const FileStyle& vStyle = {}); // style + void AddPlaceSeparator(const float& vThickness = 1.0f); + bool RemovePlace( // remove a place by code, return true if succeed + const std::string& vPlaceName); // place name to remove + }; + +private: + std::unordered_map > m_Groups; + std::map > m_OrderedGroups; + +protected: + float m_PlacesPaneWidth = 200.0f; + bool m_PlacesPaneShown = false; + +protected: + void m_InitPlaces(FileDialogInternal& vFileDialogInternal); + void m_DrawPlacesButton(); // draw place button + bool m_DrawPlacesPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw place Pane + +public: + std::string SerializePlaces( // serialize place : return place buffer to save in a file + const bool& vForceSerialisationForAll = true); // for avoid serialization of places with flag 'canBeSaved to false' + void DeserializePlaces( // deserialize place : load place buffer to load in the dialog (saved from + const std::string& vPlaces); // previous use with SerializePlaces()) place buffer to load + bool AddPlacesGroup( // add a group + const std::string& vGroupName, // the group name + const size_t& vDisplayOrder, // the display roder of the group + const bool& vCanBeEdited = false, // let the user add/remove place in the group + const bool& vOpenedByDefault = true); // hte group is opened by default + bool RemovePlacesGroup(const std::string& vGroupName); // remove the group + GroupStruct* GetPlacesGroupPtr(const std::string& vGroupName); // get the group, if not existed, will be created +#endif // USE_PLACES_FEATURE +}; + +// file localization by input chat // widget flashing +class IGFD_API KeyExplorerFeature { +protected: + KeyExplorerFeature(); + +#ifdef USE_EXPLORATION_BY_KEYS +private: + bool m_LocateFileByInputChar_lastFound = false; + ImWchar m_LocateFileByInputChar_lastChar = 0; + float m_FlashAlpha = 0.0f; // flash when select by char + float m_FlashAlphaAttenInSecs = 1.0f; // fps display dependant + int m_LocateFileByInputChar_InputQueueCharactersSize = 0; + size_t m_FlashedItem = 0; // flash when select by char + size_t m_LocateFileByInputChar_lastFileIdx = 0; + +protected: + void m_LocateByInputKey(FileDialogInternal& vFileDialogInternal); // select a file line in listview according to char key + bool m_LocateItem_Loop(FileDialogInternal& vFileDialogInternal, + ImWchar vC); // restrat for start of list view if not found a corresponding file + void m_ExploreWithkeys(FileDialogInternal& vFileDialogInternal, + ImGuiID vListViewID); // select file/directory line in listview accroding to up/down enter/backspace keys + void m_StartFlashItem(size_t vIdx); // define than an item must be flashed + bool m_BeginFlashItem(size_t vIdx); // start the flashing of a line in lsit view + static void m_EndFlashItem(); // end the fleshing accrdoin to var m_FlashAlphaAttenInSecs + static bool m_FlashableSelectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, bool vFlashing = false, + const ImVec2& size = ImVec2(0, 0)); // custom flashing selectable widgets, for flash the selected line in a short time + +public: + void SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds +#endif // USE_EXPLORATION_BY_KEYS +}; + +class IGFD_API FileDialog : public PlacesFeature, public KeyExplorerFeature, public ThumbnailFeature { +protected: + FileDialogInternal m_FileDialogInternal; + ImGuiListClipper m_FileListClipper; + ImGuiListClipper m_PathListClipper; + float prOkCancelButtonWidth = 0.0f; + ImGuiWindowFlags m_CurrentDisplayedFlags; + +public: + // Singleton for easier accces form anywhere but only one dialog at a time + // vCopy or vForce can be used for share a memory pointer in a new memory space like a dll module + static FileDialog* Instance(FileDialog* vCopy = nullptr, bool vForce = false) { + static FileDialog _instance; + static FileDialog* _instance_copy = nullptr; + if (vCopy || vForce) { + _instance_copy = vCopy; + } + if (_instance_copy) { + return _instance_copy; + } + return &_instance; + } + +public: + FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same time (not possible with singleton) + virtual ~FileDialog(); // ImGuiFileDialog Destructor + + // standard dialog + virtual void OpenDialog( // open simple dialog + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters, if null, will display only directories + const FileDialogConfig& vConfig = {}); // FileDialogConfig + + // Display / Close dialog form + bool Display( // Display the dialog. return true if a result was obtained (Ok or not) + const std::string& vKey, // key dialog to display (if not the same key as defined by OpenDialog => no opening) + ImGuiWindowFlags vFlags = ImGuiWindowFlags_NoCollapse, // ImGuiWindowFlags + ImVec2 vMinSize = ImVec2(0, 0), // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize = ImVec2(FLT_MAX, FLT_MAX)); // maximal size contraint for the ImGuiWindow + + void Close(); // close dialog + + // queries + bool WasOpenedThisFrame(const std::string& vKey) const; // say if the dialog key was already opened this frame + bool WasOpenedThisFrame() const; // say if the dialog was already opened this frame + bool IsOpened(const std::string& vKey) const; // say if the key is opened + bool IsOpened() const; // say if the dialog is opened somewhere + std::string GetOpenedKey() const; // return the dialog key who is opened, return nothing if not opened + + // get result + bool IsOk() const; // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + std::map GetSelection(IGFD_ResultMode vFlag = IGFD_ResultMode_KeepInputFile); // Open File behavior : will return selection via a + // map + std::string GetFilePathName(IGFD_ResultMode vFlag = IGFD_ResultMode_AddIfNoFileExt); // Save File behavior : will return the current file path name + std::string GetCurrentFileName(IGFD_ResultMode vFlag = IGFD_ResultMode_AddIfNoFileExt); // Save File behavior : will return the content file name + std::string GetCurrentPath(); // will return current file path + std::string GetCurrentFilter(); // will return current filter + UserDatas GetUserDatas() const; // will return user datas send with Open Dialog + + // file style by extentions + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const FileStyle& vInfos); // Filter Extention Struct who contain Color and Icon/Text for the display of the + // file with extention filter + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const ImVec4& vColor, // wanted color for the display of the file with extention filter + const std::string& vIcon = "", // wanted text or icon of the file with extention filter + ImFont* vFont = nullptr); // wanted font + void SetFileStyle(FileStyle::FileStyleFunctor vFunctor); // set file style via lambda function + bool GetFileStyle( // GetExtention datas. return true is extention exist + const IGFD_FileStyleFlags& vFlags, // file style + const std::string& vCriteria, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + std::string* vOutIcon = nullptr, // icon or text to retrieve + ImFont** vOutFont = nullptr); // font to retreive + void ClearFilesStyle(); // clear extentions setttings + + void SetLocales( // set locales to use before and after the dialog display + const int& vLocaleCategory, // set local category + const std::string& vLocaleBegin, // locale to use at begining of the dialog display + const std::string& vLocaleEnd); // locale to use at the end of the dialog display + +protected: + void m_NewFrame(); // new frame just at begining of display + void m_EndFrame(); // end frame just at end of display + void m_QuitFrame(); // quit frame when qui quit the dialog + + // others + bool m_Confirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags); // treatment of the result, start the confirm to overwrite dialog + // if needed (if defined with flag) + + // dialog parts + virtual void m_DrawHeader(); // draw header part of the dialog (place btn, dir creation, path composer, search + // bar) + virtual void m_DrawContent(); // draw content part of the dialog (place pane, file list, side pane) + virtual bool m_DrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's) + + // widgets components + virtual void m_DisplayPathPopup(ImVec2 vSize); // draw path popup when click on a \ or / + virtual bool m_DrawValidationButtons(); // draw validations btns, ok, cancel buttons + virtual bool m_DrawOkButton(); // draw ok button + virtual bool m_DrawCancelButton(); // draw cancel button + virtual void m_DrawSidePane(float vHeight); // draw side pane + virtual void m_SelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, + ...); // draw a custom selectable behavior item + virtual void m_DrawFileListView(ImVec2 vSize); // draw file list view (default mode) + +#ifdef USE_THUMBNAILS + virtual void m_DrawThumbnailsListView(ImVec2 vSize); // draw file list view with small thumbnails on the same line + virtual void m_DrawThumbnailsGridView(ImVec2 vSize); // draw a grid of small thumbnails +#endif + + // to be called only by these function and theirs overrides + // - m_DrawFileListView + // - m_DrawThumbnailsListView + // - m_DrawThumbnailsGridView + void m_BeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, + ImFont** vOutFont); // begin style apply of filter with color an icon if any + void m_EndFileColorIconStyle(const bool& vShowColor, ImFont* vFont); // end style apply of filter + + void m_DisplayFileInfosTooltip(const int32_t& vRowIdx, const int32_t& vColumnIdx, std::shared_ptr vFileInfos); +}; + +} // namespace IGFD + +#endif // __cplusplus + +///////////////////////////////////////////////// +////// C LANG API /////////////////////////////// +///////////////////////////////////////////////// + +#ifdef __cplusplus +#define IGFD_C_API extern "C" IGFD_API +typedef IGFD::UserDatas IGFDUserDatas; +typedef IGFD::PaneFun IGFDPaneFun; +typedef IGFD::FileDialog ImGuiFileDialog; +#else // __cplusplus +#define IGFD_C_API +typedef struct ImGuiFileDialog ImGuiFileDialog; +typedef struct IGFD_Selection_Pair IGFD_Selection_Pair; +typedef struct IGFD_Selection IGFD_Selection; +#endif // __cplusplus + +typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane + +struct IGFD_FileDialog_Config { + const char* path; // path + const char* fileName; // defaut file name + const char* filePathName; // if not empty, the filename and the path will be obtained from filePathName + int32_t countSelectionMax; // count selection max + void* userDatas; // user datas (can be retrieved in pane) + IGFD_PaneFun sidePane; // side pane callback + float sidePaneWidth; // side pane width}; + ImGuiFileDialogFlags flags; // ImGuiFileDialogFlags +}; +IGFD_C_API struct IGFD_FileDialog_Config IGFD_FileDialog_Config_Get(); // return an initialized IGFD_FileDialog_Config + +struct IGFD_Selection_Pair { + char* fileName; + char* filePathName; +}; + +IGFD_C_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(); // return an initialized IGFD_Selection_Pair +IGFD_C_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair); // destroy the content of a IGFD_Selection_Pair + +struct IGFD_Selection { + IGFD_Selection_Pair* table; // 0 + size_t count; // 0U +}; + +IGFD_C_API IGFD_Selection IGFD_Selection_Get(); // return an initialized IGFD_Selection +IGFD_C_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection); // destroy the content of a IGFD_Selection + +// constructor / destructor +IGFD_C_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context +IGFD_C_API void IGFD_Destroy(ImGuiFileDialog* vContextPtr); // destroy the filedialog context + +#ifdef USE_THUMBNAILS +typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture +typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture +#endif // USE_THUMBNAILS + +IGFD_C_API void IGFD_OpenDialog( // open a standard dialog + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const struct IGFD_FileDialog_Config vConfig); // config + +IGFD_C_API bool IGFD_DisplayDialog( // Display the dialog + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vKey, // key dialog to display (if not the same key as defined by OpenDialog => no opening) + ImGuiWindowFlags vFlags, // ImGuiWindowFlags + ImVec2 vMinSize, // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize); // maximal size contraint for the ImGuiWindow + +IGFD_C_API void IGFD_CloseDialog( // Close the dialog + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API bool IGFD_IsOk( // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API bool IGFD_WasKeyOpenedThisFrame( // say if the dialog key was already opened this frame + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vKey); + +IGFD_C_API bool IGFD_WasOpenedThisFrame( // say if the dialog was already opened this frame + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API bool IGFD_IsKeyOpened( // say if the dialog key is opened + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vCurrentOpenedKey); // the dialog key + +IGFD_C_API bool IGFD_IsOpened( // say if the dialog is opened somewhere + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API IGFD_Selection IGFD_GetSelection( // Open File behavior : will return selection via a map + ImGuiFileDialog* vContextPtr, // user datas (can be retrieved in pane) + IGFD_ResultMode vMode); // Result Mode + +IGFD_C_API char* IGFD_GetFilePathName( // Save File behavior : will always return the content of the field with current + // filter extention and current path, WARNINGS you are responsible to free it + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_ResultMode vMode); // Result Mode + +IGFD_C_API char* IGFD_GetCurrentFileName( // Save File behavior : will always return the content of the field with + // current filter extention, WARNINGS you are responsible to free it + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_ResultMode vMode); // Result Mode + +IGFD_C_API char* IGFD_GetCurrentPath( // will return current path, WARNINGS you are responsible to free it + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API char* IGFD_GetCurrentFilter( // will return selected filter, WARNINGS you are responsible to free it + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API void* IGFD_GetUserDatas( // will return user datas send with Open Dialog + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API void IGFD_SetFileStyle( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + ImVec4 vColor, // wanted color for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer + +IGFD_C_API void IGFD_SetFileStyle2( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + float vR, float vG, float vB, + float vA, // wanted color channels RGBA for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer + +IGFD_C_API bool IGFD_GetFileStyle(ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + char** vOutIconText, // icon or text to retrieve, WARNINGS you are responsible to free it + ImFont** vOutFont); // font pointer to retrived + +IGFD_C_API void IGFD_ClearFilesStyle( // clear extentions setttings + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context + +IGFD_C_API void SetLocales( // set locales to use before and after display + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const int vCategory, // set local category + const char* vBeginLocale, // locale to use at begining of the dialog display + const char* vEndLocale); // locale to set at end of the dialog display + +#ifdef USE_EXPLORATION_BY_KEYS +IGFD_C_API void IGFD_SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use + // exploration keys + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds +#endif + +#ifdef USE_PLACES_FEATURE +IGFD_C_API char* IGFD_SerializePlaces( // serialize place : return place buffer to save in a file, WARNINGS + // you are responsible to free it + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + bool vDontSerializeCodeBasedPlaces); // for avoid serialization of place added by code + +IGFD_C_API void IGFD_DeserializePlaces( // deserialize place : load bookmar buffer to load in the dialog (saved + // from previous use with SerializePlaces()) + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vPlaces); // place buffer to load + +IGFD_C_API bool IGFD_AddPlacesGroup( // add a places group by code + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vGroupName, // the group name + size_t vDisplayOrder, // the display roder of the group + bool vCanBeEdited); // let the user add/remove place in the group + +IGFD_C_API bool IGFD_RemovePlacesGroup( // remove a place group by code, return true if succeed + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vGroupName); // place name to remove + +IGFD_C_API bool IGFD_AddPlace( // add a place by code + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vGroupName, // the group name + const char* vPlaceName, // place name + const char* vPlacePath, // place path + bool vCanBeSaved, // place can be saved + const char* vIconText); // wanted text or icon of the file with extention filter (can be used with font icon) + +IGFD_C_API bool IGFD_RemovePlace( // remove a place by code, return true if succeed + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + const char* vGroupName, // the group name + const char* vPlaceName); // place name to remove + +#endif + +#ifdef USE_THUMBNAILS +IGFD_C_API void SetCreateThumbnailCallback( // define the callback for create the thumbnails texture + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_CreateThumbnailFun vCreateThumbnailFun); // the callback for create the thumbnails texture + +IGFD_C_API void SetDestroyThumbnailCallback( // define the callback for destroy the thumbnails texture + ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context + IGFD_DestroyThumbnailFun vDestroyThumbnailFun); // the callback for destroy the thumbnails texture + +IGFD_C_API void ManageGPUThumbnails( // must be call in gpu zone, possibly a thread, will call the callback for create + // / destroy the textures + ImGuiFileDialog* vContextPtr); // ImGuiFileDialog context +#endif // USE_THUMBNAILS diff --git a/imguifiledialog/ImGuiFileDialogConfig.h b/imguifiledialog/ImGuiFileDialogConfig.h new file mode 100644 index 0000000..12495ed --- /dev/null +++ b/imguifiledialog/ImGuiFileDialogConfig.h @@ -0,0 +1,204 @@ +#pragma once + +// uncomment and modify defines under for customize ImGuiFileDialog + +///////////////////////////////// +//// STL FILE SYSTEM //////////// +///////////////////////////////// + +// uncomment if you need to use your FileSystem Interface +// if commented, you have two defualt interface, std::filesystem or dirent +// #define USE_CUSTOM_FILESYSTEM +// this options need c++17 +// #define USE_STD_FILESYSTEM + +///////////////////////////////// +//// MISC /////////////////////// +///////////////////////////////// + +// the spacing between button path's can be customized. +// if disabled the spacing is defined by the imgui theme +// define the space between path buttons +// #define CUSTOM_PATH_SPACING 2 + +// #define MAX_FILE_DIALOG_NAME_BUFFER 1024 +// #define MAX_PATH_BUFFER_SIZE 1024 + +///////////////////////////////// +//// QUICK PATH ///////////////// +///////////////////////////////// + +// the slash's buttons in path cna be used for quick select parallles directories +// #define USE_QUICK_PATH_SELECT + +///////////////////////////////// +//// THUMBNAILS ///////////////// +///////////////////////////////// + +// #define USE_THUMBNAILS +// the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation +// btw if you already use them in your app, you can have compiler error due to "implemntation found in double" +// so uncomment these line for prevent the creation of implementation of these libs again +// #define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +// #define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +// #define IMGUI_RADIO_BUTTON RadioButton +// #define DisplayMode_ThumbailsList_ImageHeight 32.0f +// #define tableHeaderFileThumbnailsString "Thumbnails" +// #define DisplayMode_FilesList_ButtonString "FL" +// #define DisplayMode_FilesList_ButtonHelp "File List" +// #define DisplayMode_ThumbailsList_ButtonString "TL" +// #define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" +// todo +// #define DisplayMode_ThumbailsGrid_ButtonString "TG" +// #define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" + +///////////////////////////////// +//// EXPLORATION BY KEYS //////// +///////////////////////////////// + +// #define USE_EXPLORATION_BY_KEYS +// this mapping by default is for GLFW but you can use another +// #include +// Up key for explore to the top +// #define IGFD_KEY_UP ImGuiKey_UpArrow +// Down key for explore to the bottom +// #define IGFD_KEY_DOWN ImGuiKey_DownArrow +// Enter key for open directory +// #define IGFD_KEY_ENTER ImGuiKey_Enter +// BackSpace for comming back to the last directory +// #define IGFD_KEY_BACKSPACE ImGuiKey_Backspace + +///////////////////////////////// +//// SHORTCUTS => ctrl + KEY //// +///////////////////////////////// + +// #define SelectAllFilesKey ImGuiKey_A + +///////////////////////////////// +//// DIALOG EXIT //////////////// +///////////////////////////////// + +// by ex you can quit the dialog by pressing the key excape +// #define USE_DIALOG_EXIT_WITH_KEY +// #define IGFD_EXIT_KEY ImGuiKey_Escape + +///////////////////////////////// +//// WIDGETS //////////////////// +///////////////////////////////// + +// widget +// begin combo widget +// #define IMGUI_BEGIN_COMBO ImGui::BeginCombo +// when auto resized, FILTER_COMBO_MIN_WIDTH will be considered has minimum width +// FILTER_COMBO_AUTO_SIZE is enabled by default now to 1 +// uncomment if you want disable +// #define FILTER_COMBO_AUTO_SIZE 0 +// filter combobox width +// #define FILTER_COMBO_MIN_WIDTH 120.0f +// button widget use for compose path +// #define IMGUI_PATH_BUTTON ImGui::Button +// standard button +// #define IMGUI_BUTTON ImGui::Button + +///////////////////////////////// +//// STRING'S /////////////////// +///////////////////////////////// + +// locales string +// #define createDirButtonString "+" +// #define resetButtonString "R" +// #define devicesButtonString "Devices" +// #define editPathButtonString "E" +// #define searchString "Search" +// #define dirEntryString "[DIR] " +// #define linkEntryString "[LINK] " +// #define fileEntryString "[FILE] " +// #define fileNameString "File Name : " +// #define dirNameString "Directory Path :" +// #define buttonResetSearchString "Reset search" +// #define buttonDriveString "Devices" +// #define buttonEditPathString "Edit path\nYou can also right click on path buttons" +// #define buttonResetPathString "Reset to current directory" +// #define buttonCreateDirString "Create Directory" +// #define OverWriteDialogTitleString "The file Already Exist !" +// #define OverWriteDialogMessageString "Would you like to OverWrite it ?" +// #define OverWriteDialogConfirmButtonString "Confirm" +// #define OverWriteDialogCancelButtonString "Cancel" + +// Validation buttons +// #define okButtonString " OK" +// #define okButtonWidth 0.0f +// #define cancelButtonString " Cancel" +// #define cancelButtonWidth 0.0f +// alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios +// #define okCancelButtonAlignement 0.0f +// #define invertOkAndCancelButtons 0 + +// DateTimeFormat +// see strftime functionin for customize +// "%Y/%m/%d %H:%M" give 2021:01:22 11:47 +// "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM +// #define DateTimeFormat "%Y/%m/%d %i:%M%p" + +///////////////////////////////// +//// SORTING ICONS ////////////// +///////////////////////////////// + +// theses icons will appear in table headers +// #define USE_CUSTOM_SORTING_ICON +// #define tableHeaderAscendingIcon "A|" +// #define tableHeaderDescendingIcon "D|" +// #define tableHeaderFileNameString " File name" +// #define tableHeaderFileTypeString " Type" +// #define tableHeaderFileSizeString " Size" +// #define tableHeaderFileDateTimeString " Date" +// #define fileSizeBytes "o" +// #define fileSizeKiloBytes "Ko" +// #define fileSizeMegaBytes "Mo" +// #define fileSizeGigaBytes "Go" + +// default table sort field (must be FIELD_FILENAME, FIELD_TYPE, FIELD_SIZE, FIELD_DATE or FIELD_THUMBNAILS) +// #define defaultSortField FIELD_FILENAME + +// default table sort order for each field (true => Descending, false => Ascending) +// #define defaultSortOrderFilename true +// #define defaultSortOrderType true +// #define defaultSortOrderSize true +// #define defaultSortOrderDate true +// #define defaultSortOrderThumbnails true + +///////////////////////////////// +//// PLACES FEATURES //////////// +///////////////////////////////// + +// #define USE_PLACES_FEATURE +// #define PLACES_PANE_DEFAULT_SHOWN false +// #define placesPaneWith 150.0f +// #define IMGUI_TOGGLE_BUTTON ToggleButton +// #define placesButtonString "Place" +// #define placesButtonHelpString "Places" +// #define addPlaceButtonString "+" +// #define removePlaceButtonString "-" +// #define validatePlaceButtonString "ok" +// #define editPlaceButtonString "E" + +////////////////////////////////////// +//// PLACES FEATURES : BOOKMARKS ///// +////////////////////////////////////// + +// a group for bookmarks will be added by default, but you can also create it yourself and many more +// #define USE_PLACES_BOOKMARKS +// #define PLACES_BOOKMARK_DEFAULT_OPEPEND true +// #define placesBookmarksGroupName "Bookmarks" +// #define placesBookmarksDisplayOrder 0 // to the first + +////////////////////////////////////// +//// PLACES FEATURES : DEVICES /////// +////////////////////////////////////// + +// a group for system devices (returned by IFileSystem), but you can also add yours +// by ex if you would like to display a specific icon for some devices +// #define USE_PLACES_DEVICES +// #define PLACES_DEVICES_DEFAULT_OPEPEND true +// #define placesDevicesGroupName "Devices" +// #define placesDevicesDisplayOrder 10 // to the end