5158 lines
208 KiB
C++
5158 lines
208 KiB
C++
|
|
|||
|
// 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 <cstring> // stricmp / strcasecmp
|
|||
|
#include <cstdarg> // variadic
|
|||
|
#include <sstream>
|
|||
|
#include <iomanip>
|
|||
|
#include <ctime>
|
|||
|
#include <memory>
|
|||
|
#include <sys/stat.h>
|
|||
|
#include <cstdio>
|
|||
|
#include <cerrno>
|
|||
|
|
|||
|
// this option need c++17
|
|||
|
#ifdef USE_STD_FILESYSTEM
|
|||
|
#include <filesystem>
|
|||
|
#include <exception>
|
|||
|
#endif // USE_STD_FILESYSTEM
|
|||
|
|
|||
|
#ifdef __EMSCRIPTEN__
|
|||
|
#include <emscripten.h>
|
|||
|
#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 <cctype>
|
|||
|
// this option need c++17
|
|||
|
#ifdef USE_STD_FILESYSTEM
|
|||
|
#include <windows.h>
|
|||
|
#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 <sys/types.h>
|
|||
|
// this option need c++17
|
|||
|
#ifndef USE_STD_FILESYSTEM
|
|||
|
#include <dirent.h>
|
|||
|
#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 <cstdlib>
|
|||
|
#include <algorithm>
|
|||
|
#include <iostream>
|
|||
|
|
|||
|
///////////////////////////////
|
|||
|
// 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 <ctime> 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<IGFD::PathDisplayedName> GetDevicesList() override {
|
|||
|
std::vector<IGFD::PathDisplayedName> 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<IGFD::FileInfos> ScanDirectory(const std::string& vPath) override {
|
|||
|
std::vector<IGFD::FileInfos> 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<IGFD::PathDisplayedName> GetDevicesList() override {
|
|||
|
std::vector<IGFD::PathDisplayedName> 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<IGFD::FileInfos> ScanDirectory(const std::string& vPath) override {
|
|||
|
std::vector<IGFD::FileInfos> 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<std::string> IGFD::Utils::SplitStringToVector(const std::string& vText, const std::string& vDelimiterPattern, const bool& vPushEmpty) {
|
|||
|
std::vector<std::string> 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<std::string> IGFD::Utils::SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty) {
|
|||
|
std::vector<std::string> 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<double>(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<68>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<FileStyle>(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<FileInfos> 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<FileStyle>(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<FileStyle>(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<size_t>(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<size_t>(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> IGFD::FileInfos::create() {
|
|||
|
return std::make_shared<IGFD::FileInfos>();
|
|||
|
}
|
|||
|
|
|||
|
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<FILE_SYSTEM_OVERRIDE>(new FILE_SYSTEM_OVERRIDE());
|
|||
|
// m_FileSystemPtr = std::make_unique<FILE_SYSTEM_OVERRIDE>();
|
|||
|
}
|
|||
|
|
|||
|
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<std::shared_ptr<FileInfos> >& vFileInfosList, std::vector<std::shared_ptr<FileInfos> >& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& a, const std::shared_ptr<FileInfos>& 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<FileInfos>& 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<std::string>::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::FileInfos> 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::FileInfos> IGFD::FileManager::GetFilteredFileAt(size_t vIdx) {
|
|||
|
if (vIdx < m_FilteredFileList.size()) return m_FilteredFileList[vIdx];
|
|||
|
return nullptr;
|
|||
|
}
|
|||
|
|
|||
|
std::shared_ptr<IGFD::FileInfos> IGFD::FileManager::GetFilteredPathAt(size_t vIdx) {
|
|||
|
if (vIdx < m_FilteredPathList.size()) return m_FilteredPathList[vIdx];
|
|||
|
return nullptr;
|
|||
|
}
|
|||
|
|
|||
|
std::vector<std::string>::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<std::shared_ptr<FileInfos> >& vFileInfosList, std::vector<std::shared_ptr<FileInfos> >& 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<FileInfos>& 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<std::string>::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<FileInfos>& 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<FileInfos>& vInfos) {
|
|||
|
if (!vInfos.use_count()) {
|
|||
|
return;
|
|||
|
}
|
|||
|
m_AddFileNameInSelection(vInfos->fileNameExt, true);
|
|||
|
}
|
|||
|
|
|||
|
void IGFD::FileManager::SelectOrDeselectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr<FileInfos>& 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<std::string>::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<std::string, std::string> IGFD::FileManager::GetResultingSelection(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) {
|
|||
|
std::map<std::string, std::string> 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<std::thread>(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<std::mutex> thumbnailFileDatasToGetLock(m_ThumbnailFileDatasToGetMutex);
|
|||
|
m_ThumbnailFileDatasToGetCv.wait(thumbnailFileDatasToGetLock);
|
|||
|
if (!m_ThumbnailFileDatasToGet.empty()) {
|
|||
|
std::shared_ptr<FileInfos> 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<FileInfos>& 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<FileInfos>& 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<GroupStruct>();
|
|||
|
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<FileInfos> 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<FileInfos> 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<FileInfos> 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<std::string, std::string> 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
|