openMSX
ImGuiFileDialog.cc
Go to the documentation of this file.
1//#pragma region PVS STUDIO
2
3// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
4// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
5
6//#pragma endregion
7
8//#pragma region IGFD LICENSE
9
10/*
11MIT License
12
13Copyright (c) 2019-2020 Stephane Cuillerdier (aka aiekick)
14
15Permission is hereby granted, free of charge, to any person obtaining a copy
16of this software and associated documentation files (the "Software"), to deal
17in the Software without restriction, including without limitation the rights
18to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19copies of the Software, and to permit persons to whom the Software is
20furnished to do so, subject to the following conditions:
21
22The above copyright notice and this permission notice shall be included in all
23copies or substantial portions of the Software.
24
25THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31SOFTWARE.
32*/
33
34//#pragma endregion
35
36#include "ImGuiFileDialog.h"
37
38#ifdef __cplusplus
39
40//#pragma region Includes
41
42#include <cfloat>
43#include <cstring> // stricmp / strcasecmp
44#include <cstdarg> // variadic
45#include <sstream>
46#include <iomanip>
47#include <ctime>
48#include <memory>
49#include <sys/stat.h>
50#include <cstdio>
51#include <cerrno>
52
53// this option need c++17
54#ifdef USE_STD_FILESYSTEM
55#include <filesystem>
56#include <exception>
57#endif // USE_STD_FILESYSTEM
58
59#ifdef __EMSCRIPTEN__
60#include <emscripten.h>
61#endif // __EMSCRIPTEN__
62
63#ifdef _MSC_VER
64
65#define IGFD_DEBUG_BREAK \
66 if (IsDebuggerPresent()) __debugbreak()
67#else
68#define IGFD_DEBUG_BREAK
69#endif
70
71#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) || defined(__WIN64__) || defined(WIN64) || defined(_WIN64) || defined(_MSC_VER)
72#define _IGFD_WIN_
73#define stat _stat
74#define stricmp _stricmp
75#include <cctype>
76// this option need c++17
77#ifdef USE_STD_FILESYSTEM
78#include <windows.h>
79#else
80#include "dirent/dirent.h" // directly open the dirent file attached to this lib
81#endif // USE_STD_FILESYSTEM
82#define PATH_SEP '\\'
83#ifndef PATH_MAX
84#define PATH_MAX 260
85#endif // PATH_MAX
86#elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__EMSCRIPTEN__)
87#define _IGFD_UNIX_
88#define stricmp strcasecmp
89#include <sys/types.h>
90// this option need c++17
91#ifndef USE_STD_FILESYSTEM
92#include <dirent.h>
93#endif // USE_STD_FILESYSTEM
94#define PATH_SEP '/'
95#endif // _IGFD_UNIX_
96
97#include "imgui.h"
98#include "imgui_internal.h"
99
100// legacy compatibility 1.89
101#ifndef IM_TRUNC
102#define IM_TRUNC IM_FLOOR
103#endif
104
105#include <cstdlib>
106#include <algorithm>
107#include <iostream>
108
109//#pragma endregion
110
111//#pragma region Common defines
112
113#ifdef USE_THUMBNAILS
114#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION
115#ifndef STB_IMAGE_IMPLEMENTATION
116#define STB_IMAGE_IMPLEMENTATION
117#endif // STB_IMAGE_IMPLEMENTATION
118#endif // DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION
119#include "stb/stb_image.h"
120#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION
121#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION
122#define STB_IMAGE_RESIZE_IMPLEMENTATION
123#endif // STB_IMAGE_RESIZE_IMPLEMENTATION
124#endif // DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION
125#include "stb/stb_image_resize.h"
126#endif // USE_THUMBNAILS
127
128// float comparisons
129#ifndef IS_FLOAT_DIFFERENT
130#define IS_FLOAT_DIFFERENT(a, b) (fabs((a) - (b)) > FLT_EPSILON)
131#endif // IS_FLOAT_DIFFERENT
132#ifndef IS_FLOAT_EQUAL
133#define IS_FLOAT_EQUAL(a, b) (fabs((a) - (b)) < FLT_EPSILON)
134#endif // IS_FLOAT_EQUAL
135
136//#pragma endregion
137
138//#pragma region IGFD NAMESPACE
139
140//#pragma region CUSTOMIZATION DEFINES
141
143// COMBOBOX
145#ifndef FILTER_COMBO_AUTO_SIZE
146#define FILTER_COMBO_AUTO_SIZE 1
147#endif // FILTER_COMBO_AUTO_SIZE
148#ifndef FILTER_COMBO_MIN_WIDTH
149#define FILTER_COMBO_MIN_WIDTH 150.0f
150#endif // FILTER_COMBO_MIN_WIDTH
151#ifndef IMGUI_BEGIN_COMBO
152#define IMGUI_BEGIN_COMBO ImGui::BeginCombo
153#endif // IMGUI_BEGIN_COMBO
155// BUTTON
157// for lets you define your button widget
158// if you have like me a special bi-color button
159#ifndef IMGUI_PATH_BUTTON
160#define IMGUI_PATH_BUTTON ImGui::Button
161#endif // IMGUI_PATH_BUTTON
162#ifndef IMGUI_BUTTON
163#define IMGUI_BUTTON ImGui::Button
164#endif // IMGUI_BUTTON
166// locales
168#ifndef createDirButtonString
169#define createDirButtonString "+"
170#endif // createDirButtonString
171#ifndef okButtonString
172#define okButtonString "OK"
173#endif // okButtonString
174#ifndef okButtonWidth
175#define okButtonWidth 0.0f
176#endif // okButtonWidth
177#ifndef cancelButtonString
178#define cancelButtonString "Cancel"
179#endif // cancelButtonString
180#ifndef cancelButtonWidth
181#define cancelButtonWidth 0.0f
182#endif // cancelButtonWidth
183#ifndef okCancelButtonAlignement
184#define okCancelButtonAlignement 0.0f
185#endif // okCancelButtonAlignement
186#ifndef invertOkAndCancelButtons
187// 0 => disabled, 1 => enabled
188#define invertOkAndCancelButtons 0
189#endif // invertOkAndCancelButtons
190#ifndef resetButtonString
191#define resetButtonString "R"
192#endif // resetButtonString
193#ifndef drivesButtonString
194#define drivesButtonString "Drives"
195#endif // drivesButtonString
196#ifndef editPathButtonString
197#define editPathButtonString "E"
198#endif // editPathButtonString
199#ifndef searchString
200#define searchString "Search :"
201#endif // searchString
202#ifndef dirEntryString
203#define dirEntryString "[Dir]"
204#endif // dirEntryString
205#ifndef linkEntryString
206#define linkEntryString "[Link]"
207#endif // linkEntryString
208#ifndef fileEntryString
209#define fileEntryString "[File]"
210#endif // fileEntryString
211#ifndef fileNameString
212#define fileNameString "File Name:"
213#endif // fileNameString
214#ifndef dirNameString
215#define dirNameString "Directory Path:"
216#endif // dirNameString
217#ifndef buttonResetSearchString
218#define buttonResetSearchString "Reset search"
219#endif // buttonResetSearchString
220#ifndef buttonDriveString
221#define buttonDriveString "Drives"
222#endif // buttonDriveString
223#ifndef buttonEditPathString
224#define buttonEditPathString "Edit path\nYou can also right click on path buttons"
225#endif // buttonEditPathString
226#ifndef buttonResetPathString
227#define buttonResetPathString "Reset to current directory"
228#endif // buttonResetPathString
229#ifndef buttonCreateDirString
230#define buttonCreateDirString "Create Directory"
231#endif // buttonCreateDirString
232#ifndef tableHeaderAscendingIcon
233#define tableHeaderAscendingIcon "A|"
234#endif // tableHeaderAscendingIcon
235#ifndef tableHeaderDescendingIcon
236#define tableHeaderDescendingIcon "D|"
237#endif // tableHeaderDescendingIcon
238#ifndef tableHeaderFileNameString
239#define tableHeaderFileNameString "File name"
240#endif // tableHeaderFileNameString
241#ifndef tableHeaderFileTypeString
242#define tableHeaderFileTypeString "Type"
243#endif // tableHeaderFileTypeString
244#ifndef tableHeaderFileSizeString
245#define tableHeaderFileSizeString "Size"
246#endif // tableHeaderFileSizeString
247#ifndef tableHeaderFileDateString
248#define tableHeaderFileDateString "Date"
249#endif // tableHeaderFileDateString
250#ifndef fileSizeBytes
251#define fileSizeBytes "o"
252#endif // fileSizeBytes
253#ifndef fileSizeKiloBytes
254#define fileSizeKiloBytes "Ko"
255#endif // fileSizeKiloBytes
256#ifndef fileSizeMegaBytes
257#define fileSizeMegaBytes "Mo"
258#endif // fileSizeMegaBytes
259#ifndef fileSizeGigaBytes
260#define fileSizeGigaBytes "Go"
261#endif // fileSizeGigaBytes
262#ifndef OverWriteDialogTitleString
263#define OverWriteDialogTitleString "The selected file already exists!"
264#endif // OverWriteDialogTitleString
265#ifndef OverWriteDialogMessageString
266#define OverWriteDialogMessageString "Are you sure you want to overwrite it?"
267#endif // OverWriteDialogMessageString
268#ifndef OverWriteDialogConfirmButtonString
269#define OverWriteDialogConfirmButtonString "Confirm"
270#endif // OverWriteDialogConfirmButtonString
271#ifndef OverWriteDialogCancelButtonString
272#define OverWriteDialogCancelButtonString "Cancel"
273#endif // OverWriteDialogCancelButtonString
274#ifndef DateTimeFormat
275// see strftime functionin <ctime> for customize
276#define DateTimeFormat "%Y/%m/%d %H:%M"
277#endif // DateTimeFormat
279// THUMBNAILS
281#ifdef USE_THUMBNAILS
282#ifndef tableHeaderFileThumbnailsString
283#define tableHeaderFileThumbnailsString "Thumbnails"
284#endif // tableHeaderFileThumbnailsString
285#ifndef DisplayMode_FilesList_ButtonString
286#define DisplayMode_FilesList_ButtonString "FL"
287#endif // DisplayMode_FilesList_ButtonString
288#ifndef DisplayMode_FilesList_ButtonHelp
289#define DisplayMode_FilesList_ButtonHelp "File List"
290#endif // DisplayMode_FilesList_ButtonHelp
291#ifndef DisplayMode_ThumbailsList_ButtonString
292#define DisplayMode_ThumbailsList_ButtonString "TL"
293#endif // DisplayMode_ThumbailsList_ButtonString
294#ifndef DisplayMode_ThumbailsList_ButtonHelp
295#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
296#endif // DisplayMode_ThumbailsList_ButtonHelp
297#ifndef DisplayMode_ThumbailsGrid_ButtonString
298#define DisplayMode_ThumbailsGrid_ButtonString "TG"
299#endif // DisplayMode_ThumbailsGrid_ButtonString
300#ifndef DisplayMode_ThumbailsGrid_ButtonHelp
301#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid"
302#endif // DisplayMode_ThumbailsGrid_ButtonHelp
303#ifndef DisplayMode_ThumbailsList_ImageHeight
304#define DisplayMode_ThumbailsList_ImageHeight 32.0f
305#endif // DisplayMode_ThumbailsList_ImageHeight
306#ifndef IMGUI_RADIO_BUTTON
307inline bool inRadioButton(const char* vLabel, bool vToggled) {
308 bool pressed = false;
309 if (vToggled) {
310 ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
311 ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text);
312 ImGui::PushStyleColor(ImGuiCol_Button, te);
313 ImGui::PushStyleColor(ImGuiCol_ButtonActive, te);
314 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te);
315 ImGui::PushStyleColor(ImGuiCol_Text, bua);
316 }
317 pressed = IMGUI_BUTTON(vLabel);
318 if (vToggled) {
319 ImGui::PopStyleColor(4); //-V112
320 }
321 return pressed;
322}
323#define IMGUI_RADIO_BUTTON inRadioButton
324#endif // IMGUI_RADIO_BUTTON
325#endif // USE_THUMBNAILS
327// BOOKMARKS
329#ifdef USE_BOOKMARK
330#ifndef defaultBookmarkPaneWith
331#define defaultBookmarkPaneWith 150.0f
332#endif // defaultBookmarkPaneWith
333#ifndef bookmarksButtonString
334#define bookmarksButtonString "Bookmark"
335#endif // bookmarksButtonString
336#ifndef bookmarksButtonHelpString
337#define bookmarksButtonHelpString "Bookmark"
338#endif // bookmarksButtonHelpString
339#ifndef addBookmarkButtonString
340#define addBookmarkButtonString "+"
341#endif // addBookmarkButtonString
342#ifndef removeBookmarkButtonString
343#define removeBookmarkButtonString "-"
344#endif // removeBookmarkButtonString
345#ifndef IMGUI_TOGGLE_BUTTON
346inline bool inToggleButton(const char* vLabel, bool* vToggled) {
347 bool pressed = false;
348
349 if (vToggled && *vToggled) {
350 ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
351 // ImVec4 buh = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);
352 // ImVec4 bu = ImGui::GetStyleColorVec4(ImGuiCol_Button);
353 ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text);
354 ImGui::PushStyleColor(ImGuiCol_Button, te);
355 ImGui::PushStyleColor(ImGuiCol_ButtonActive, te);
356 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te);
357 ImGui::PushStyleColor(ImGuiCol_Text, bua);
358 }
359
360 pressed = IMGUI_BUTTON(vLabel);
361
362 if (vToggled && *vToggled) {
363 ImGui::PopStyleColor(4); //-V112
364 }
365
366 if (vToggled && pressed) *vToggled = !*vToggled;
367
368 return pressed;
369}
370#define IMGUI_TOGGLE_BUTTON inToggleButton
371#endif // IMGUI_TOGGLE_BUTTON
372#endif // USE_BOOKMARK
373
374//#pragma endregion
375
376//#pragma region INTERNAL
377
378//#pragma region EXCEPTION
379
380class IGFDException : public std::exception {
381private:
382 std::string m_Message;
383
384public:
385 IGFDException(const std::string& vMessage) : m_Message(vMessage) {
386 }
387 const char* what() {
388 return m_Message.c_str();
389 }
390};
391
392//#pragma endregion
393
394//#pragma region FILE SYSTEM INTERFACE
395
396#ifndef CUSTOM_FILESYSTEM_INCLUDE
397#ifdef USE_STD_FILESYSTEM
398
399static std::filesystem::path stringToPath(const std::string& str)
400{
401#ifdef _IGFD_WIN_
402 return std::filesystem::path(IGFD::Utils::UTF8Decode(str));
403#else
404 return std::filesystem::path(str);
405#endif
406}
407
408static std::string pathToString(const std::filesystem::path& path)
409{
410#ifdef _IGFD_WIN_
411 return IGFD::Utils::UTF8Encode(path.wstring());
412#else
413 return path.string();
414#endif
415}
416
417class FileSystemStd : public IGFD::IFileSystem {
418public:
419 bool IsDirectoryCanBeOpened(const std::string& vName) override {
420 bool bExists = false;
421 if (!vName.empty()) {
422 namespace fs = std::filesystem;
423 auto pathName = stringToPath(vName);
424 try {
425 // interesting, in the case of a protected dir or for any reason the dir cant be opened
426 // this func will work but will say nothing more . not like the dirent version
427 bExists = fs::is_directory(pathName);
428 // test if can be opened, this function can thrown an exception if there is an issue with this dir
429 // here, the dir_iter is need else not exception is thrown..
430 const auto dir_iter = fs::directory_iterator(pathName);
431 (void)dir_iter; // for avoid unused warnings
432 } catch (const std::exception& /*ex*/) {
433 // fail so this dir cant be opened
434 bExists = false;
435 }
436 }
437 return bExists; // this is not a directory!
438 }
439 bool IsDirectoryExist(const std::string& vName) override {
440 if (!vName.empty()) {
441 namespace fs = std::filesystem;
442 return fs::is_directory(stringToPath(vName));
443 }
444 return false; // this is not a directory!
445 }
446 bool IsFileExist(const std::string& vName) override {
447 namespace fs = std::filesystem;
448 return fs::is_regular_file(stringToPath(vName));
449 }
450 bool CreateDirectoryIfNotExist(const std::string& vName) override {
451 if (vName.empty()) return false;
452 if (IsDirectoryExist(vName)) return false; // TODO is this really an error? Should this be checked at all? (creating an existing directory is not an error)
453
454#if defined(__EMSCRIPTEN__)
455 std::string str = std::string("FS.mkdir('") + vName + "');";
456 emscripten_run_script(str.c_str());
457 bool res = true;
458#else
459 namespace fs = std::filesystem;
460 bool res = fs::create_directories(stringToPath(vName));
461#endif // _IGFD_WIN_
462 if (!res) {
463 std::cout << "Error creating directory " << vName << std::endl;
464 }
465 return res;
466 }
467 std::vector<std::string> GetDrivesList() override {
468 std::vector<std::string> res;
469#ifdef _IGFD_WIN_
470 const DWORD mydrives = 2048;
471 char lpBuffer[2048];
472#define mini(a, b) (((a) < (b)) ? (a) : (b))
473 const DWORD countChars = mini(GetLogicalDriveStringsA(mydrives, lpBuffer), 2047);
474#undef mini
475 if (countChars > 0U && countChars < 2049U) {
476 std::string var = std::string(lpBuffer, (size_t)countChars);
477 IGFD::Utils::ReplaceString(var, "\\", "");
478 res = IGFD::Utils::SplitStringToVector(var, '\0', false);
479 }
480#endif // _IGFD_WIN_
481 return res;
482 }
483
484 IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) override {
485 // https://github.com/aiekick/ImGuiFileDialog/issues/54
486 namespace fs = std::filesystem;
487 IGFD::Utils::PathStruct res;
488 if (vPathFileName.empty()) return res;
489 auto fsPath = stringToPath(vPathFileName);
490 if (fs::is_directory(fsPath)) {
491 res.name = "";
492 res.path = pathToString(fsPath);
493 res.isOk = true;
494 } else if (fs::is_regular_file(fsPath)) {
495 res.name = pathToString(fsPath.filename());
496 res.path = pathToString(fsPath.parent_path());
497 res.isOk = true;
498 }
499 return res;
500 }
501
502 std::vector<IGFD::FileInfos> ScanDirectory(const std::string& vPath) override {
503 std::vector<IGFD::FileInfos> res;
504 try {
505 namespace fs = std::filesystem;
506 auto fspath = stringToPath(vPath);
507 const auto dir_iter = fs::directory_iterator(fspath);
508 IGFD::FileType fstype = IGFD::FileType(IGFD::FileType::ContentType::Directory, fs::is_symlink(fs::status(fspath)));
509 {
510 IGFD::FileInfos file_two_dot;
511 file_two_dot.filePath = vPath;
512 file_two_dot.fileNameExt = "..";
513 file_two_dot.fileType = fstype;
514 res.push_back(file_two_dot);
515 }
516 for (const auto& file : dir_iter) {
517 IGFD::FileType fileType;
518 if (file.is_symlink()) {
519 fileType.SetSymLink(file.is_symlink());
520 fileType.SetContent(IGFD::FileType::ContentType::LinkToUnknown);
521 }
522 if (file.is_directory()) {
523 fileType.SetContent(IGFD::FileType::ContentType::Directory);
524 } // directory or symlink to directory
525 else if (file.is_regular_file()) {
526 fileType.SetContent(IGFD::FileType::ContentType::File);
527 }
528 if (fileType.isValid()) {
529 auto fileNameExt = pathToString(file.path().filename());
530 {
531 IGFD::FileInfos _file;
532 _file.filePath = vPath;
533 _file.fileNameExt = fileNameExt;
534 _file.fileType = fileType;
535 res.push_back(_file);
536 }
537 }
538 }
539 } catch (const std::exception& ex) {
540 printf("%s", ex.what());
541 }
542 return res;
543 }
544 bool IsDirectory(const std::string& vFilePathName) override {
545 namespace fs = std::filesystem;
546 return fs::is_directory(stringToPath(vFilePathName));
547 }
548};
549#define FILE_SYSTEM_OVERRIDE FileSystemStd
550#else
551class FileSystemDirent : public IGFD::IFileSystem {
552public:
553 bool IsDirectoryCanBeOpened(const std::string& vName) override {
554 if (!vName.empty()) {
555 DIR* pDir = nullptr;
556 // interesting, in the case of a protected dir or for any reason the dir cant be opened
557 // this func will fail
558 pDir = opendir(vName.c_str());
559 if (pDir != nullptr) {
560 (void)closedir(pDir);
561 return true;
562 }
563 }
564 return false;
565 }
566 bool IsDirectoryExist(const std::string& vName) override {
567 bool bExists = false;
568 if (!vName.empty()) {
569 DIR* pDir = nullptr;
570 pDir = opendir(vName.c_str());
571 if (pDir) {
572 bExists = true;
573 closedir(pDir);
574 } else if (ENOENT == errno) {
575 /* Directory does not exist. */
576 // bExists = false;
577 } else {
578 /* opendir() failed for some other reason.
579 like if a dir is protected, or not accessable with user right
580 */
581 bExists = true;
582 }
583 }
584 return bExists;
585 }
586 bool IsFileExist(const std::string& vName) override {
587 std::ifstream docFile(vName, std::ios::in);
588 if (docFile.is_open()) {
589 docFile.close();
590 return true;
591 }
592 return false;
593 }
594 bool CreateDirectoryIfNotExist(const std::string& vName) override {
595 bool res = false;
596 if (!vName.empty()) {
597 if (!IsDirectoryExist(vName)) {
598#ifdef _IGFD_WIN_
599 std::wstring wname = IGFD::Utils::UTF8Decode(vName);
600 if (CreateDirectoryW(wname.c_str(), nullptr)) {
601 res = true;
602 }
603#elif defined(__EMSCRIPTEN__) // _IGFD_WIN_
604 std::string str = std::string("FS.mkdir('") + vName + "');";
605 emscripten_run_script(str.c_str());
606 res = true;
607#elif defined(_IGFD_UNIX_)
608 char buffer[PATH_MAX] = {};
609 snprintf(buffer, PATH_MAX, "mkdir -p \"%s\"", vName.c_str());
610 const int dir_err = std::system(buffer);
611 if (dir_err != -1) {
612 res = true;
613 }
614#endif // _IGFD_WIN_
615 if (!res) {
616 std::cout << "Error creating directory " << vName << std::endl;
617 }
618 }
619 }
620
621 return res;
622 }
623
624 std::vector<std::string> GetDrivesList() override {
625 std::vector<std::string> res;
626#ifdef _IGFD_WIN_
627 const DWORD mydrives = 2048;
628 char lpBuffer[2048];
629#define mini(a, b) (((a) < (b)) ? (a) : (b))
630 const DWORD countChars = mini(GetLogicalDriveStringsA(mydrives, lpBuffer), 2047);
631#undef mini
632 if (countChars > 0U && countChars < 2049U) {
633 std::string var = std::string(lpBuffer, (size_t)countChars);
634 IGFD::Utils::ReplaceString(var, "\\", "");
635 res = IGFD::Utils::SplitStringToVector(var, '\0', false);
636 }
637#endif // _IGFD_WIN_
638 return res;
639 }
640
641 IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) override {
642 IGFD::Utils::PathStruct res;
643 if (!vPathFileName.empty()) {
644 std::string pfn = vPathFileName;
645 std::string separator(1u, PATH_SEP);
646 IGFD::Utils::ReplaceString(pfn, "\\", separator);
647 IGFD::Utils::ReplaceString(pfn, "/", separator);
648 size_t lastSlash = pfn.find_last_of(separator);
649 if (lastSlash != std::string::npos) {
650 res.name = pfn.substr(lastSlash + 1);
651 res.path = pfn.substr(0, lastSlash);
652 res.isOk = true;
653 }
654 size_t lastPoint = pfn.find_last_of('.');
655 if (lastPoint != std::string::npos) {
656 if (!res.isOk) {
657 res.name = pfn;
658 res.isOk = true;
659 }
660 res.ext = pfn.substr(lastPoint + 1);
661 IGFD::Utils::ReplaceString(res.name, "." + res.ext, "");
662 }
663 if (!res.isOk) {
664 res.name = std::move(pfn);
665 res.isOk = true;
666 }
667 }
668 return res;
669 }
670
671 std::vector<IGFD::FileInfos> ScanDirectory(const std::string& vPath) override {
672 std::vector<IGFD::FileInfos> res;
673 struct dirent** files = nullptr;
674 size_t n = scandir(vPath.c_str(), &files, nullptr, //
675 [](const struct dirent** a, const struct dirent** b) { //
676 return strcoll((*a)->d_name, (*b)->d_name);
677 });
678 if (n && files) {
679 for (size_t i = 0; i < n; ++i) {
680 struct dirent* ent = files[i];
681 IGFD::FileType fileType;
682 switch (ent->d_type) {
683 case DT_DIR: fileType.SetContent(IGFD::FileType::ContentType::Directory); break;
684 case DT_REG: fileType.SetContent(IGFD::FileType::ContentType::File); break;
685#if defined(_IGFD_UNIX_) || (DT_LNK != DT_UNKNOWN)
686 case DT_LNK:
687#endif
688 case DT_UNKNOWN: {
689 struct stat sb = {};
690#ifdef _IGFD_WIN_
691 auto filePath = vPath + ent->d_name;
692#else
693 auto filePath = vPath + IGFD::Utils::GetPathSeparator() + ent->d_name;
694#endif
695 if (!stat(filePath.c_str(), &sb)) {
696 if (sb.st_mode & S_IFLNK) {
697 fileType.SetSymLink(true);
698 // by default if we can't figure out the target type.
699 fileType.SetContent(IGFD::FileType::ContentType::LinkToUnknown);
700 }
701 if (sb.st_mode & S_IFREG) {
702 fileType.SetContent(IGFD::FileType::ContentType::File);
703 break;
704 } else if (sb.st_mode & S_IFDIR) {
705 fileType.SetContent(IGFD::FileType::ContentType::Directory);
706 break;
707 }
708 }
709 break;
710 }
711 default: break; // leave it invalid (devices, etc.)
712 }
713 if (fileType.isValid()) {
714 IGFD::FileInfos _file;
715 _file.filePath = vPath;
716 _file.fileNameExt = ent->d_name;
717 _file.fileType = fileType;
718 res.push_back(_file);
719 }
720 }
721 for (size_t i = 0; i < n; ++i) {
722 free(files[i]);
723 }
724 free(files);
725 }
726 return res;
727 }
728 bool IsDirectory(const std::string& vFilePathName) override {
729 DIR *pDir = opendir(vFilePathName.c_str());
730 if (pDir) {
731 (void)closedir(pDir);
732 return true;
733 }
734 return false;
735 }
736};
737#define FILE_SYSTEM_OVERRIDE FileSystemDirent
738#endif // USE_STD_FILESYSTEM
739#else
740#include CUSTOM_FILESYSTEM_INCLUDE
741#endif // USE_CUSTOM_FILESYSTEM
742
743//#pragma endregion
744
745//#pragma region Utils
746
747// https://github.com/ocornut/imgui/issues/1720
748bool IGFD::Utils::ImSplitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size) {
749 using namespace ImGui;
750 ImGuiContext& g = *GImGui;
751 ImGuiWindow* window = g.CurrentWindow;
752 ImGuiID id = window->GetID("##Splitter");
753 ImRect bb;
754 bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
755 bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f);
756 return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 1.0f);
757}
758
759// Convert a wide Unicode string to an UTF8 string
760std::string IGFD::Utils::UTF8Encode(const std::wstring& wstr) {
761 std::string res;
762#ifdef _IGFD_WIN_
763 if (!wstr.empty()) {
764 int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
765 if (size_needed) {
766 res = std::string(size_needed, 0);
767 WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &res[0], size_needed, NULL, NULL);
768 }
769 }
770#else
771 // Suppress warnings from the compiler.
772 (void)wstr;
773#endif // _IGFD_WIN_
774 return res;
775}
776
777// Convert an UTF8 string to a wide Unicode String
778std::wstring IGFD::Utils::UTF8Decode(const std::string& str) {
779 std::wstring res;
780#ifdef _IGFD_WIN_
781 if (!str.empty()) {
782 int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
783 if (size_needed) {
784 res = std::wstring(size_needed, 0);
785 MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &res[0], size_needed);
786 }
787 }
788#else
789 // Suppress warnings from the compiler.
790 (void)str;
791#endif // _IGFD_WIN_
792 return res;
793}
794
795bool IGFD::Utils::ReplaceString(std::string& str, const ::std::string& oldStr, const ::std::string& newStr, const size_t& vMaxRecursion) {
796 if (!str.empty() && oldStr != newStr) {
797 bool res = false;
798 size_t pos = 0;
799 bool found = false;
800 size_t max_recursion = vMaxRecursion;
801 do {
802 pos = str.find(oldStr, pos);
803 if (pos != std::string::npos) {
804 found = res = true;
805 str.replace(pos, oldStr.length(), newStr);
806 pos += newStr.length();
807 } else if (found && max_recursion > 0) { // recursion loop
808 found = false;
809 pos = 0;
810 --max_recursion;
811 }
812 } while (pos != std::string::npos);
813 return res;
814 }
815 return false;
816}
817
818std::vector<std::string> IGFD::Utils::SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty) {
819 std::vector<std::string> arr;
820 if (!vText.empty()) {
821 size_t start = 0;
822 size_t end = vText.find(vDelimiter, start);
823 while (end != std::string::npos) {
824 auto token = vText.substr(start, end - start);
825 if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728
826 arr.push_back(token);
827 }
828 start = end + 1;
829 end = vText.find(vDelimiter, start);
830 }
831 auto token = vText.substr(start);
832 if (!token.empty() || (token.empty() && vPushEmpty)) { //-V728
833 arr.push_back(token);
834 }
835 }
836 return arr;
837}
838
839void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) {
840 std::string st = vStr;
841 size_t len = vBufferLen - 1u;
842 size_t slen = strlen(vBuffer);
843
844 if (!st.empty() && st != "\n") {
845 IGFD::Utils::ReplaceString(st, "\n", "");
846 IGFD::Utils::ReplaceString(st, "\r", "");
847 }
848 vBuffer[slen] = '\0';
849 std::string str = std::string(vBuffer);
850 // if (!str.empty()) str += "\n";
851 str += vStr;
852 if (len > str.size()) {
853 len = str.size();
854 }
855#ifdef _MSC_VER
856 strncpy_s(vBuffer, vBufferLen, str.c_str(), len);
857#else // _MSC_VER
858 strncpy(vBuffer, str.c_str(), len);
859#endif // _MSC_VER
860 vBuffer[len] = '\0';
861}
862
863void IGFD::Utils::ResetBuffer(char* vBuffer) {
864 vBuffer[0] = '\0';
865}
866
867void IGFD::Utils::SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) {
868 ResetBuffer(vBuffer);
869 AppendToBuffer(vBuffer, vBufferLen, vStr);
870}
871
872std::string IGFD::Utils::LowerCaseString(const std::string& vString) {
873 auto str = vString;
874
875 // convert to lower case
876 for (char& c : str) {
877 c = (char)std::tolower(c);
878 }
879
880 return str;
881}
882
883size_t IGFD::Utils::GetCharCountInString(const std::string& vString, const char& vChar) {
884 size_t res = 0U;
885 for (const auto& c : vString) {
886 if (c == vChar) {
887 ++res;
888 }
889 }
890 return res;
891}
892
893size_t IGFD::Utils::GetLastCharPosWithMinCharCount(const std::string& vString, const char& vChar, const size_t& vMinCharCount) {
894 if (vMinCharCount) {
895 size_t last_dot_pos = vString.size() + 1U;
896 size_t count_dots = vMinCharCount;
897 while (count_dots > 0U && last_dot_pos > 0U && last_dot_pos != std::string::npos) {
898 auto new_dot = vString.rfind(vChar, last_dot_pos - 1U);
899 if (new_dot != std::string::npos) {
900 last_dot_pos = new_dot;
901 --count_dots;
902 } else {
903 break;
904 }
905 }
906 return last_dot_pos;
907 }
908 return std::string::npos;
909}
910
911std::string IGFD::Utils::GetPathSeparator() {
912 return std::string(1U, PATH_SEP);
913}
914
915std::string IGFD::Utils::RoundNumber(double vvalue, int n) {
916 std::stringstream tmp;
917 tmp << std::setprecision(n) << std::fixed << vvalue;
918 return tmp.str();
919}
920
921std::string IGFD::Utils::FormatFileSize(size_t vByteSize) {
922 if (vByteSize != 0) {
923 static double lo = 1024.0;
924 static double ko = 1024.0 * 1024.0;
925 static double mo = 1024.0 * 1024.0 * 1024.0;
926
927 auto v = (double)vByteSize;
928
929 if (v < lo)
930 return RoundNumber(v, 0) + " " + fileSizeBytes; // octet
931 else if (v < ko)
932 return RoundNumber(v / lo, 2) + " " + fileSizeKiloBytes; // ko
933 else if (v < mo)
934 return RoundNumber(v / ko, 2) + " " + fileSizeMegaBytes; // Mo
935 else
936 return RoundNumber(v / mo, 2) + " " + fileSizeGigaBytes; // Go
937 }
938
939 return "0 " fileSizeBytes;
940}
941
942//#pragma endregion
943
944//#pragma region FileStyle
945
946IGFD::FileStyle::FileStyle() : color(0, 0, 0, 0) {
947}
948
949IGFD::FileStyle::FileStyle(const FileStyle& vStyle) {
950 color = vStyle.color;
951 icon = vStyle.icon;
952 font = vStyle.font;
953 flags = vStyle.flags;
954}
955
956IGFD::FileStyle::FileStyle(const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) : color(vColor), icon(vIcon), font(vFont) {
957}
958
959//#pragma endregion
960
961//#pragma region SearchManager
962
963void IGFD::SearchManager::Clear() {
964 searchTag.clear();
965 IGFD::Utils::ResetBuffer(searchBuffer);
966}
967
968void IGFD::SearchManager::DrawSearchBar(FileDialogInternal& vFileDialogInternal) {
969 // search field
970 if (IMGUI_BUTTON(resetButtonString "##BtnImGuiFileDialogSearchField")) {
971 Clear();
972 vFileDialogInternal.fileManager.ApplyFilteringOnFileList(vFileDialogInternal);
973 }
974 if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonResetSearchString);
975 ImGui::SameLine();
976 ImGui::Text(searchString);
977 ImGui::SameLine();
978 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
979 bool edited = ImGui::InputText("##InputImGuiFileDialogSearchField", searchBuffer, MAX_FILE_DIALOG_NAME_BUFFER);
980 if (ImGui::GetItemID() == ImGui::GetActiveID()) searchInputIsActive = true;
981 ImGui::PopItemWidth();
982 if (edited) {
983 searchTag = searchBuffer;
984 vFileDialogInternal.fileManager.ApplyFilteringOnFileList(vFileDialogInternal);
985 }
986}
987
988//#pragma endregion
989
990//#pragma region FilterInfos
991
992void IGFD::FilterInfos::setCollectionTitle(const std::string& vTitle) {
993 title = vTitle;
994}
995
996void IGFD::FilterInfos::addFilter(const std::string& vFilter, const bool& vIsRegex) {
997 setCollectionTitle(vFilter);
998 addCollectionFilter(vFilter, vIsRegex);
999}
1000
1001void IGFD::FilterInfos::addCollectionFilter(const std::string& vFilter, const bool& vIsRegex) {
1002 if (!vIsRegex) {
1003 if (vFilter.find('*') != std::string::npos) {
1004 const auto& regex_string = transformAsteriskBasedFilterToRegex(vFilter);
1005 addCollectionFilter(regex_string, true);
1006 return;
1007 }
1008 filters.try_add(vFilter);
1009 filters_optimized.try_add(Utils::LowerCaseString(vFilter));
1010 auto _count_dots = Utils::GetCharCountInString(vFilter, '.');
1011 if (_count_dots > count_dots) {
1012 count_dots = _count_dots;
1013 }
1014 } else {
1015 try {
1016 auto rx = std::regex(vFilter);
1017 filters.try_add(vFilter);
1018 filters_regex.emplace_back(rx);
1019 } catch (std::exception&) {
1020 assert(0); // YOUR REGEX FILTER IS INVALID
1021 }
1022 }
1023}
1024
1025void IGFD::FilterInfos::clear() {
1026 title.clear();
1027 filters.clear();
1028 filters_optimized.clear();
1029 filters_regex.clear();
1030}
1031
1032bool IGFD::FilterInfos::empty() const {
1033 return filters.empty() || filters.begin()->empty();
1034}
1035
1036const std::string& IGFD::FilterInfos::getFirstFilter() const {
1037 if (!filters.empty()) {
1038 return *filters.begin();
1039 }
1040 return empty_string;
1041}
1042
1043bool IGFD::FilterInfos::exist(const FileInfos& vFileInfos, bool vIsCaseInsensitive) const {
1044 for (const auto& filter : filters) {
1045 if (vFileInfos.SearchForExt(filter, vIsCaseInsensitive, count_dots)) {
1046 return true;
1047 }
1048 }
1049 return false;
1050}
1051
1052bool IGFD::FilterInfos::regexExist(const std::string& vFilter) const {
1053 for (auto regex : filters_regex) {
1054 if (std::regex_search(vFilter, regex)) {
1055 return true;
1056 }
1057 }
1058 return false;
1059}
1060
1061std::string IGFD::FilterInfos::transformAsteriskBasedFilterToRegex(const std::string& vFilter) {
1062 std::string res;
1063 if (!vFilter.empty() && vFilter.find('*') != std::string::npos) {
1064 res = "((";
1065 for (const auto& c : vFilter) {
1066 if (c == '.') {
1067 res += "[.]"; // [.] => a dot
1068 } else if (c == '*') {
1069 res += ".*"; // .* => any char zero or many
1070 } else {
1071 res += c; // other chars
1072 }
1073 }
1074 res += "$))"; // $ => end fo the string
1075 }
1076 return res;
1077}
1078
1079//#pragma endregion
1080
1081//#pragma region FilterManager
1082
1083const IGFD::FilterInfos& IGFD::FilterManager::GetSelectedFilter() const {
1084 return m_SelectedFilter;
1085}
1086
1087void IGFD::FilterManager::ParseFilters(const char* vFilters) {
1088 m_ParsedFilters.clear();
1089
1090 if (vFilters) {
1091 dLGFilters = vFilters; // file mode
1092 } else {
1093 dLGFilters.clear(); // directory mode
1094 }
1095
1096 if (!dLGFilters.empty()) {
1097 /* Rules
1098 0) a filter must have 2 chars mini and the first must be a .
1099 1) a regex must be in (( and ))
1100 2) a , will separate filters except if between a ( and )
1101 3) name{filter1, filter2} is a spetial form for collection filters
1102 3.1) the name can be composed of what you want except { and }
1103 3.2) the filter can be a regex
1104 4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1)
1105 5) the filters cannot integrate a ','
1106 */
1107
1108 bool current_filter_found = false;
1109 bool started = false;
1110 bool regex_started = false;
1111 bool parenthesis_started = false;
1112
1113 std::string word;
1114 std::string filter_name;
1115
1116 char last_split_char = 0;
1117 for (char c : dLGFilters) {
1118 if (c == '{') {
1119 if (regex_started) {
1120 word += c;
1121 } else {
1122 started = true;
1123 m_ParsedFilters.emplace_back();
1124 m_ParsedFilters.back().setCollectionTitle(filter_name);
1125 filter_name.clear();
1126 word.clear();
1127 }
1128 last_split_char = c;
1129 } else if (c == '}') {
1130 if (regex_started) {
1131 word += c;
1132 } else {
1133 if (started) {
1134 if (word.size() > 1U && word[0] == '.') {
1135 if (m_ParsedFilters.empty()) {
1136 m_ParsedFilters.emplace_back();
1137 }
1138 m_ParsedFilters.back().addCollectionFilter(word, false);
1139 }
1140 word.clear();
1141 filter_name.clear();
1142 started = false;
1143 }
1144 }
1145 last_split_char = c;
1146 } else if (c == '(') {
1147 word += c;
1148 if (last_split_char == '(') {
1149 regex_started = true;
1150 }
1151 parenthesis_started = true;
1152 if (!started) {
1153 filter_name += c;
1154 }
1155 last_split_char = c;
1156 } else if (c == ')') {
1157 word += c;
1158 if (last_split_char == ')') {
1159 if (regex_started) {
1160 if (started) {
1161 m_ParsedFilters.back().addCollectionFilter(word, true);
1162 } else {
1163 m_ParsedFilters.emplace_back();
1164 m_ParsedFilters.back().addFilter(word, true);
1165 }
1166 word.clear();
1167 filter_name.clear();
1168 regex_started = false;
1169 } else {
1170 if (!started) {
1171 if (!m_ParsedFilters.empty()) {
1172 m_ParsedFilters.erase(m_ParsedFilters.begin() + m_ParsedFilters.size() - 1U);
1173 } else {
1174 m_ParsedFilters.clear();
1175 }
1176 }
1177 word.clear();
1178 filter_name.clear();
1179 }
1180 }
1181 parenthesis_started = false;
1182 if (!started) {
1183 filter_name += c;
1184 }
1185 last_split_char = c;
1186 } else if (c == '.') {
1187 word += c;
1188 if (!started) {
1189 filter_name += c;
1190 }
1191 last_split_char = c;
1192 } else if (c == ',') {
1193 if (regex_started) {
1194 regex_started = false;
1195 word.clear();
1196 filter_name.clear();
1197 } else {
1198 if (started) {
1199 if (word.size() > 1U && word[0] == '.') {
1200 m_ParsedFilters.back().addCollectionFilter(word, false);
1201 word.clear();
1202 filter_name.clear();
1203 }
1204 } else {
1205 if (word.size() > 1U && word[0] == '.') {
1206 m_ParsedFilters.emplace_back();
1207 m_ParsedFilters.back().addFilter(word, false);
1208 word.clear();
1209 filter_name.clear();
1210 }
1211 if (parenthesis_started) {
1212 filter_name += c;
1213 }
1214 }
1215 }
1216 } else {
1217 if (c != ' ') {
1218 word += c;
1219 }
1220 if (!started) {
1221 filter_name += c;
1222 }
1223 }
1224 }
1225
1226 if (started) {
1227 if (!m_ParsedFilters.empty()) {
1228 m_ParsedFilters.erase(m_ParsedFilters.begin() + m_ParsedFilters.size() - 1U);
1229 } else {
1230 m_ParsedFilters.clear();
1231 }
1232 } else if (word.size() > 1U && word[0] == '.') {
1233 m_ParsedFilters.emplace_back();
1234 m_ParsedFilters.back().addFilter(word, false);
1235 word.clear();
1236 }
1237
1238 for (const auto& it : m_ParsedFilters) {
1239 if (it.title == m_SelectedFilter.title) {
1240 m_SelectedFilter = it;
1241 current_filter_found = true;
1242 break;
1243 }
1244 }
1245
1246 if (!current_filter_found) {
1247 if (!m_ParsedFilters.empty()) {
1248 m_SelectedFilter = *m_ParsedFilters.begin();
1249 }
1250 }
1251 }
1252}
1253
1254void IGFD::FilterManager::SetSelectedFilterWithExt(const std::string& vFilter) {
1255 if (!m_ParsedFilters.empty()) {
1256 if (!vFilter.empty()) {
1257 for (const auto& infos : m_ParsedFilters) {
1258 for (const auto& filter : infos.filters) {
1259 if (vFilter == filter) {
1260 m_SelectedFilter = infos;
1261 }
1262 }
1263 }
1264 }
1265
1266 if (m_SelectedFilter.empty()) {
1267 m_SelectedFilter = *m_ParsedFilters.begin();
1268 }
1269 }
1270}
1271
1272void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) {
1273 std::string _criteria = (vCriteria != nullptr) ? std::string(vCriteria) : "";
1274 m_FilesStyle[vFlags][_criteria] = std::make_shared<FileStyle>(vInfos);
1275 m_FilesStyle[vFlags][_criteria]->flags = vFlags;
1276}
1277
1278// will be called internally
1279// will not been exposed to IGFD API
1280bool IGFD::FilterManager::m_FillFileStyle(std::shared_ptr<FileInfos> vFileInfos) const {
1281 // todo : better system to found regarding what style to priorize regarding other
1282 // maybe with a lambda fucntion for let the user use his style
1283 // according to his use case
1284 if (vFileInfos.use_count() && !m_FilesStyle.empty()) {
1285 for (const auto& _flag : m_FilesStyle) {
1286 for (const auto& _file : _flag.second) {
1287 if ((_flag.first & IGFD_FileStyleByTypeDir && _flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isDir() && vFileInfos->fileType.isSymLink()) ||
1288 (_flag.first & IGFD_FileStyleByTypeFile && _flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isFile() && vFileInfos->fileType.isSymLink()) ||
1289 (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType.isSymLink()) || (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType.isDir()) ||
1290 (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType.isFile())) {
1291 if (_file.first.empty()) { // for all links
1292 vFileInfos->fileStyle = _file.second;
1293 } else if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt,
1294 std::regex(_file.first))) { // for links who are equal to style criteria
1295 vFileInfos->fileStyle = _file.second;
1296 } else if (_file.first == vFileInfos->fileNameExt) { // for links who are equal to style criteria
1297 vFileInfos->fileStyle = _file.second;
1298 }
1299 }
1300
1301 if (_flag.first & IGFD_FileStyleByExtention) {
1302 if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileExtLevels[0], std::regex(_file.first))) {
1303 vFileInfos->fileStyle = _file.second;
1304 } else if (vFileInfos->SearchForExt(_file.first, false)) {
1305 vFileInfos->fileStyle = _file.second;
1306 }
1307 }
1308
1309 if (_flag.first & IGFD_FileStyleByFullName) {
1310 if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt, std::regex(_file.first))) {
1311 vFileInfos->fileStyle = _file.second;
1312 } else if (_file.first == vFileInfos->fileNameExt) {
1313 vFileInfos->fileStyle = _file.second;
1314 }
1315 }
1316
1317 if (_flag.first & IGFD_FileStyleByContainedInFullName) {
1318 if (_file.first.find("((") != std::string::npos && std::regex_search(vFileInfos->fileNameExt, std::regex(_file.first))) {
1319 vFileInfos->fileStyle = _file.second;
1320 } else if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) {
1321 vFileInfos->fileStyle = _file.second;
1322 }
1323 }
1324
1325 for (auto& functor : m_FilesStyleFunctors) {
1326 if (functor) {
1327 FileStyle result;
1328 if (functor(*(vFileInfos.get()), result)) {
1329 vFileInfos->fileStyle = std::make_shared<FileStyle>(std::move(result));
1330 }
1331 }
1332 }
1333
1334 if (vFileInfos->fileStyle.use_count()) {
1335 return true;
1336 }
1337 }
1338 }
1339 }
1340
1341 return false;
1342}
1343
1344void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) {
1345 std::string _criteria;
1346 if (vCriteria) _criteria = std::string(vCriteria);
1347 m_FilesStyle[vFlags][_criteria] = std::make_shared<FileStyle>(vColor, vIcon, vFont);
1348 m_FilesStyle[vFlags][_criteria]->flags = vFlags;
1349}
1350
1351void IGFD::FilterManager::SetFileStyle(FileStyle::FileStyleFunctor vFunctor) {
1352 if (vFunctor) {
1353 m_FilesStyleFunctors.push_back(vFunctor);
1354 }
1355}
1356
1357// todo : refactor this fucking function
1358bool IGFD::FilterManager::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont** vOutFont) {
1359 if (vOutColor) {
1360 if (!m_FilesStyle.empty()) {
1361 if (m_FilesStyle.find(vFlags) != m_FilesStyle.end()) { // found
1362 if (vFlags & IGFD_FileStyleByContainedInFullName) {
1363 // search for vCriteria who are containing the criteria
1364 for (const auto& _file : m_FilesStyle.at(vFlags)) {
1365 if (vCriteria.find(_file.first) != std::string::npos) {
1366 if (_file.second.use_count()) {
1367 *vOutColor = _file.second->color;
1368 if (vOutIcon) *vOutIcon = _file.second->icon;
1369 if (vOutFont) *vOutFont = _file.second->font;
1370 return true;
1371 }
1372 }
1373 }
1374 } else {
1375 if (m_FilesStyle.at(vFlags).find(vCriteria) != m_FilesStyle.at(vFlags).end()) { // found
1376 *vOutColor = m_FilesStyle[vFlags][vCriteria]->color;
1377 if (vOutIcon) *vOutIcon = m_FilesStyle[vFlags][vCriteria]->icon;
1378 if (vOutFont) *vOutFont = m_FilesStyle[vFlags][vCriteria]->font;
1379 return true;
1380 }
1381 }
1382 } else {
1383 // search for flag composition
1384 for (const auto& _flag : m_FilesStyle) {
1385 if (_flag.first & vFlags) {
1386 if (_flag.first & IGFD_FileStyleByContainedInFullName) {
1387 // search for vCriteria who are containing the criteria
1388 for (const auto& _file : m_FilesStyle.at(_flag.first)) {
1389 if (vCriteria.find(_file.first) != std::string::npos) {
1390 if (_file.second.use_count()) {
1391 *vOutColor = _file.second->color;
1392 if (vOutIcon) *vOutIcon = _file.second->icon;
1393 if (vOutFont) *vOutFont = _file.second->font;
1394 return true;
1395 }
1396 }
1397 }
1398 } else {
1399 if (m_FilesStyle.at(_flag.first).find(vCriteria) != m_FilesStyle.at(_flag.first).end()) { // found
1400 *vOutColor = m_FilesStyle[_flag.first][vCriteria]->color;
1401 if (vOutIcon) *vOutIcon = m_FilesStyle[_flag.first][vCriteria]->icon;
1402 if (vOutFont) *vOutFont = m_FilesStyle[_flag.first][vCriteria]->font;
1403 return true;
1404 }
1405 }
1406 }
1407 }
1408 }
1409 }
1410 }
1411 return false;
1412}
1413
1414void IGFD::FilterManager::ClearFilesStyle() {
1415 m_FilesStyle.clear();
1416}
1417
1418bool IGFD::FilterManager::IsCoveredByFilters(const FileInfos& vFileInfos, bool vIsCaseInsensitive) const {
1419 if (!dLGFilters.empty() && !m_SelectedFilter.empty()) {
1420 return (m_SelectedFilter.exist(vFileInfos, vIsCaseInsensitive) || m_SelectedFilter.regexExist(vFileInfos.fileNameExt));
1421 }
1422
1423 return false;
1424}
1425
1426float IGFD::FilterManager::GetFilterComboBoxWidth() const {
1427#if FILTER_COMBO_AUTO_SIZE
1428 const auto& combo_width = ImGui::CalcTextSize(m_SelectedFilter.title.c_str()).x + ImGui::GetFrameHeight() + ImGui::GetStyle().ItemInnerSpacing.x;
1429 return ImMax(combo_width, FILTER_COMBO_MIN_WIDTH);
1430#else
1431 return FILTER_COMBO_MIN_WIDTH;
1432#endif
1433}
1434
1435bool IGFD::FilterManager::DrawFilterComboBox(FileDialogInternal& vFileDialogInternal) {
1436 if (!dLGFilters.empty()) {
1437 ImGui::SameLine();
1438 bool needToApllyNewFilter = false;
1439 ImGui::PushItemWidth(GetFilterComboBoxWidth());
1440 if (IMGUI_BEGIN_COMBO("##Filters", m_SelectedFilter.title.c_str(), ImGuiComboFlags_None)) {
1441 intptr_t i = 0;
1442 for (const auto& filter : m_ParsedFilters) {
1443 const bool item_selected = (filter.title == m_SelectedFilter.title);
1444 ImGui::PushID((void*)(intptr_t)i++);
1445 if (ImGui::Selectable(filter.title.c_str(), item_selected)) {
1446 m_SelectedFilter = filter;
1447 needToApllyNewFilter = true;
1448 }
1449 ImGui::PopID();
1450 }
1451 ImGui::EndCombo();
1452 }
1453 ImGui::PopItemWidth();
1454 if (needToApllyNewFilter) {
1455 vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal);
1456 }
1457 return needToApllyNewFilter;
1458 }
1459 return false;
1460}
1461
1462std::string IGFD::FilterManager::ReplaceExtentionWithCurrentFilterIfNeeded(const std::string& vFileName, IGFD_ResultMode vFlag) const {
1463 auto result = vFileName;
1464 if (!result.empty()) {
1465 const auto& current_filter = m_SelectedFilter.getFirstFilter();
1466 if (!current_filter.empty()) {
1467 Utils::ReplaceString(result, "..", ".");
1468
1469 // is a regex => no change
1470 if (current_filter.find("((") != std::string::npos) {
1471 return result;
1472 }
1473
1474 // contain .* => no change
1475 if (current_filter.find(".*") != std::string::npos) {
1476 return result;
1477 }
1478
1479 switch (vFlag) {
1480 case IGFD_ResultMode_KeepInputFile: {
1481 return vFileName;
1482 }
1483 case IGFD_ResultMode_OverwriteFileExt: {
1484 const auto& count_dots = Utils::GetCharCountInString(vFileName, '.');
1485 const auto& min_dots = ImMin<size_t>(count_dots, m_SelectedFilter.count_dots);
1486 const auto& lp = Utils::GetLastCharPosWithMinCharCount(vFileName, '.', min_dots);
1487 if (lp != std::string::npos) { // there is a user extention
1488 const auto& file_name_without_user_ext = vFileName.substr(0, lp);
1489 result = file_name_without_user_ext + current_filter;
1490 } else { // add extention
1491 result = vFileName + current_filter;
1492 }
1493 break;
1494 }
1495 case IGFD_ResultMode_AddIfNoFileExt: {
1496 const auto& count_dots = Utils::GetCharCountInString(vFileName, '.');
1497 const auto& min_dots = ImMin<size_t>(count_dots, m_SelectedFilter.count_dots);
1498 const auto& lp = Utils::GetLastCharPosWithMinCharCount(vFileName, '.', min_dots);
1499 if (lp == std::string::npos || // there is no user extention
1500 lp == (vFileName.size() - 1U)) { // or this pos is also the last char => considered like no user extention
1501 const auto& file_name_without_user_ext = vFileName.substr(0, lp);
1502 result = file_name_without_user_ext + current_filter;
1503 }
1504 break;
1505 }
1506 }
1507
1508 Utils::ReplaceString(result, "..", ".");
1509 }
1510 }
1511 return result;
1512}
1513
1514void IGFD::FilterManager::SetDefaultFilterIfNotDefined() {
1515 if (m_SelectedFilter.empty() && // no filter selected
1516 !m_ParsedFilters.empty()) { // filter exist
1517 m_SelectedFilter = *m_ParsedFilters.begin(); // we take the first filter
1518 }
1519}
1520
1521//#pragma endregion
1522
1523//#pragma region FileType
1524
1525IGFD::FileType::FileType() = default;
1526IGFD::FileType::FileType(const ContentType& vContentType, const bool& vIsSymlink) : m_Content(vContentType), m_Symlink(vIsSymlink) {
1527}
1528void IGFD::FileType::SetContent(const ContentType& vContentType) {
1529 m_Content = vContentType;
1530}
1531void IGFD::FileType::SetSymLink(const bool& vIsSymlink) {
1532 m_Symlink = vIsSymlink;
1533}
1534bool IGFD::FileType::isValid() const {
1535 return m_Content != ContentType::Invalid;
1536}
1537bool IGFD::FileType::isDir() const {
1538 return m_Content == ContentType::Directory;
1539}
1540bool IGFD::FileType::isFile() const {
1541 return m_Content == ContentType::File;
1542}
1543bool IGFD::FileType::isLinkToUnknown() const {
1544 return m_Content == ContentType::LinkToUnknown;
1545}
1546bool IGFD::FileType::isSymLink() const {
1547 return m_Symlink;
1548}
1549// Comparisons only care about the content type, ignoring whether it's a symlink or not.
1550bool IGFD::FileType::operator==(const FileType& rhs) const {
1551 return m_Content == rhs.m_Content;
1552}
1553bool IGFD::FileType::operator!=(const FileType& rhs) const {
1554 return m_Content != rhs.m_Content;
1555}
1556bool IGFD::FileType::operator<(const FileType& rhs) const {
1557 return m_Content < rhs.m_Content;
1558}
1559bool IGFD::FileType::operator>(const FileType& rhs) const {
1560 return m_Content > rhs.m_Content;
1561}
1562
1563//#pragma endregion
1564
1565//#pragma region FileInfos
1566
1567bool IGFD::FileInfos::SearchForTag(const std::string& vTag) const {
1568 if (!vTag.empty()) {
1569 if (fileNameExt_optimized == "..") return true;
1570 return fileNameExt_optimized.find(vTag) != std::string::npos || // first try without case and accents
1571 fileNameExt.find(vTag) != std::string::npos; // second if searched with case and accents
1572 }
1573
1574 // if tag is empty => its a special case but all is found
1575 return true;
1576}
1577
1578bool IGFD::FileInfos::SearchForExt(const std::string& vExt, const bool& vIsCaseInsensitive, const size_t& vMaxLevel) const {
1579 if (!vExt.empty()) {
1580 const auto& ext_to_check = vIsCaseInsensitive ? Utils::LowerCaseString(vExt) : vExt;
1581 const auto& ext_levels = vIsCaseInsensitive ? fileExtLevels_optimized : fileExtLevels;
1582 if (vMaxLevel >= 1 && countExtDot >= vMaxLevel) {
1583 for (const auto& ext : ext_levels) {
1584 if (!ext.empty() && ext == ext_to_check) {
1585 return true;
1586 }
1587 }
1588 } else {
1589 return (fileExtLevels[0] == vExt);
1590 }
1591 }
1592 return false;
1593}
1594
1595bool IGFD::FileInfos::SearchForExts(const std::string& vComaSepExts, const bool& vIsCaseInsensitive, const size_t& vMaxLevel) const {
1596 if (!vComaSepExts.empty()) {
1597 const auto& arr = Utils::SplitStringToVector(vComaSepExts, ',', false);
1598 for (const auto& a : arr) {
1599 if (SearchForExt(a, vIsCaseInsensitive, vMaxLevel)) {
1600 return true;
1601 }
1602 }
1603 }
1604 return false;
1605}
1606
1607bool IGFD::FileInfos::FinalizeFileTypeParsing(const size_t& vMaxDotToExtract) {
1608 if (fileType.isFile() || fileType.isLinkToUnknown()) { // link can have the same extention of a file
1609 countExtDot = Utils::GetCharCountInString(fileNameExt, '.');
1610 size_t lpt = 0U;
1611 if (countExtDot > 1U) { // multi layer ext
1612 size_t max_dot_to_extract = vMaxDotToExtract;
1613 if (max_dot_to_extract > countExtDot) {
1614 max_dot_to_extract = countExtDot;
1615 }
1616 lpt = Utils::GetLastCharPosWithMinCharCount(fileNameExt, '.', max_dot_to_extract);
1617 } else {
1618 lpt = fileNameExt.find_first_of('.');
1619 }
1620 if (lpt != std::string::npos) {
1621 size_t lvl = 0U;
1622 fileNameLevels[lvl] = fileNameExt.substr(0, lpt);
1623 fileNameLevels[lvl] = Utils::LowerCaseString(fileNameLevels[lvl]);
1624 fileExtLevels[lvl] = fileNameExt.substr(lpt);
1625 fileExtLevels_optimized[lvl] = Utils::LowerCaseString(fileExtLevels[lvl]);
1626 if (countExtDot > 1U) { // multi layer ext
1627 auto count = countExtDot;
1628 while (count > 0 && lpt != std::string::npos && lvl < fileExtLevels.size()) {
1629 ++lpt, ++lvl;
1630 if (fileNameExt.size() > lpt) {
1631 lpt = fileNameExt.find_first_of('.', lpt);
1632 if (lpt != std::string::npos) {
1633 fileNameLevels[lvl] = fileNameExt.substr(0, lpt);
1634 fileNameLevels[lvl] = Utils::LowerCaseString(fileNameLevels[lvl]);
1635 fileExtLevels[lvl] = fileNameExt.substr(lpt);
1636 fileExtLevels_optimized[lvl] = Utils::LowerCaseString(fileExtLevels[lvl]);
1637 }
1638 }
1639 }
1640 }
1641 }
1642 return true;
1643 }
1644 return false;
1645}
1646
1647//#pragma endregion
1648
1649//#pragma region FileManager
1650
1651IGFD::FileManager::FileManager() {
1652 fsRoot = IGFD::Utils::GetPathSeparator();
1653 m_FileSystemName = typeid(FILE_SYSTEM_OVERRIDE).name();
1654 // std::make_unique is not available un cpp11
1655 m_FileSystemPtr = std::unique_ptr<FILE_SYSTEM_OVERRIDE>(new FILE_SYSTEM_OVERRIDE());
1656 // m_FileSystemPtr = std::make_unique<FILE_SYSTEM_OVERRIDE>();
1657}
1658
1659void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) {
1660 showDrives = false;
1661 ClearComposer();
1662 ClearFileLists();
1663 if (dLGDirectoryMode) { // directory mode
1664 SetDefaultFileName(".");
1665 } else {
1666 SetDefaultFileName(dLGDefaultFileName);
1667 }
1668 ScanDir(vFileDialogInternal, GetCurrentPath());
1669}
1670
1671void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal) {
1672 m_SortFields(vFileDialogInternal, m_FileList, m_FilteredFileList);
1673}
1674
1675void IGFD::FileManager::m_SortFields(const FileDialogInternal& vFileDialogInternal, std::vector<std::shared_ptr<FileInfos> >& vFileInfosList, std::vector<std::shared_ptr<FileInfos> >& vFileInfosFilteredList) {
1676 static constexpr const char* defaultHeaderString[] = {
1677 tableHeaderFileNameString,
1678 tableHeaderFileTypeString,
1679 tableHeaderFileSizeString,
1680 tableHeaderFileDateString,
1681#ifdef USE_THUMBNAILS
1682 tableHeaderFileThumbnailsString,
1683#endif // #ifdef USE_THUMBNAILS
1684 };
1685 for (int i = 0; i < SortingFieldEnum::NUM_FIELDS; ++i) {
1686 header[i] = defaultHeaderString[i];
1687 }
1688 bool ascending = sortingDirection[sortingField];
1689#ifdef USE_CUSTOM_SORTING_ICON
1690 header[sortingField] = (ascending ? tableHeaderAscendingIcon : tableHeaderDescendingIcon)
1691 + header[sortingField];
1692#endif
1693
1694 std::stable_sort(vFileInfosList.begin(), vFileInfosList.end(), [&](const std::shared_ptr<FileInfos>& a, const std::shared_ptr<FileInfos>& b) -> bool {
1695 bool a_used = a.use_count() != 0;
1696 bool b_used = b.use_count() != 0;
1697 if (a_used != b_used) return a_used < b_used;
1698 if (!a_used) return true; // neither is used
1699
1700 // We want ".." ALWAYS at the top
1701 bool a_not_dotdot = a->fileNameExt != "..";
1702 bool b_not_dotdot = b->fileNameExt != "..";
1703 if (a_not_dotdot != b_not_dotdot) return a_not_dotdot < b_not_dotdot;
1704
1705 auto compare = [&](auto extractor) {
1706 // directories before (or after) files
1707 auto a_type = a->fileType;
1708 auto b_type = a->fileType;
1709 if (a_type != b_type) return ascending ? a_type < b_type : b_type < a_type;
1710
1711 // then sort on column-specific properties
1712 auto a_properties = extractor(a);
1713 auto b_properties = extractor(b);
1714 return ascending ? a_properties < b_properties : b_properties < a_properties;
1715 };
1716 switch (sortingField) {
1717 case SortingFieldEnum::FIELD_FILENAME:
1718 return compare([](const auto& x) { return x->fileNameExt_optimized; });
1719 case SortingFieldEnum::FIELD_TYPE:
1720 return compare([](const auto& x) { return x->fileExtLevels_optimized[0]; });
1721 case SortingFieldEnum::FIELD_SIZE:
1722 return compare([](const auto& x) { return x->fileSize; });
1723 case SortingFieldEnum::FIELD_DATE:
1724 return compare([](const auto& x) { return x->fileModifDate; });
1725#ifdef USE_THUMBNAILS
1726 case SortingFieldEnum::FIELD_THUMBNAILS:
1727 return compare([](const auto& x) { return std::make_tuple(x->thumbnailInfo.textureWidth, x->thumbnailInfo.textureHeight); });
1728#endif
1729 default:
1730 return false; // we shouldn't get here
1731 }
1732 });
1733
1734 m_ApplyFilteringOnFileList(vFileDialogInternal, vFileInfosList, vFileInfosFilteredList);
1735}
1736
1737bool IGFD::FileManager::m_CompleteFileInfosWithUserFileAttirbutes(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr<FileInfos>& vInfos) {
1738 if (vFileDialogInternal.getDialogConfig().userFileAttributes != nullptr) {
1739 if (!vFileDialogInternal.getDialogConfig().userFileAttributes(vInfos.get(), vFileDialogInternal.getDialogConfig().userDatas)) {
1740 return false; // the file will be ignored, so not added to the file list, so not displayed
1741 } else {
1742 if (!vInfos->fileType.isDir()) {
1743 vInfos->formatedFileSize = IGFD::Utils::FormatFileSize(vInfos->fileSize);
1744 }
1745 }
1746 }
1747 return true; // file will be added to file list, so displayed
1748}
1749
1750void IGFD::FileManager::ClearFileLists() {
1751 m_FilteredFileList.clear();
1752 m_FileList.clear();
1753}
1754
1755void IGFD::FileManager::ClearPathLists() {
1756 m_FilteredPathList.clear();
1757 m_PathList.clear();
1758}
1759
1760void IGFD::FileManager::m_AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const FileType& vFileType) {
1761 auto infos = std::make_shared<FileInfos>();
1762
1763 infos->filePath = vPath;
1764 infos->fileNameExt = vFileName;
1765 infos->fileNameExt_optimized = Utils::LowerCaseString(infos->fileNameExt);
1766 infos->fileType = vFileType;
1767
1768 if (infos->fileNameExt.empty() || (infos->fileNameExt == "." && !vFileDialogInternal.filterManager.dLGFilters.empty())) { // filename empty or filename is the current dir '.' //-V807
1769 return;
1770 }
1771
1772 if (infos->fileNameExt != ".." && (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') { // dont show hidden files
1773 if (!vFileDialogInternal.filterManager.dLGFilters.empty() || (vFileDialogInternal.filterManager.dLGFilters.empty() && infos->fileNameExt != ".")) { // except "." if in directory mode //-V728
1774 return;
1775 }
1776 }
1777
1778 if (infos->FinalizeFileTypeParsing(vFileDialogInternal.filterManager.GetSelectedFilter().count_dots)) {
1779 if (!vFileDialogInternal.filterManager.IsCoveredByFilters(*infos.get(), (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_CaseInsensitiveExtention) != 0)) {
1780 return;
1781 }
1782 }
1783
1784 vFileDialogInternal.filterManager.m_FillFileStyle(infos);
1785
1786 m_CompleteFileInfos(infos);
1787
1788 if (m_CompleteFileInfosWithUserFileAttirbutes(vFileDialogInternal, infos)) {
1789 m_FileList.push_back(infos);
1790 }
1791}
1792
1793void IGFD::FileManager::m_AddPath(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const FileType& vFileType) {
1794 if (!vFileType.isDir()) return;
1795
1796 auto infos = std::make_shared<FileInfos>();
1797
1798 infos->filePath = vPath;
1799 infos->fileNameExt = vFileName;
1800 infos->fileNameExt_optimized = Utils::LowerCaseString(infos->fileNameExt);
1801 infos->fileType = vFileType;
1802
1803 if (infos->fileNameExt.empty() || (infos->fileNameExt == "." && !vFileDialogInternal.filterManager.dLGFilters.empty())) { // filename empty or filename is the current dir '.' //-V807
1804 return;
1805 }
1806
1807 if (infos->fileNameExt != ".." && (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') { // dont show hidden files
1808 if (!vFileDialogInternal.filterManager.dLGFilters.empty() || (vFileDialogInternal.filterManager.dLGFilters.empty() && infos->fileNameExt != ".")) { // except "." if in directory mode //-V728
1809 return;
1810 }
1811 }
1812
1813 vFileDialogInternal.filterManager.m_FillFileStyle(infos);
1814
1815 m_CompleteFileInfos(infos);
1816
1817 if (m_CompleteFileInfosWithUserFileAttirbutes(vFileDialogInternal, infos)) {
1818 m_PathList.push_back(infos);
1819 }
1820}
1821
1822void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) {
1823 std::string path = vPath;
1824
1825 if (m_CurrentPathDecomposition.empty()) {
1826 SetCurrentDir(path);
1827 }
1828
1829 if (!m_CurrentPathDecomposition.empty()) {
1830#ifdef _IGFD_WIN_
1831 if (path == fsRoot) path += IGFD::Utils::GetPathSeparator();
1832#endif // _IGFD_WIN_
1833
1834 ClearFileLists();
1835
1836 const auto& files = m_FileSystemPtr->ScanDirectory(vPath);
1837 for (const auto& file : files) {
1838 m_AddFile(vFileDialogInternal, path, file.fileNameExt, file.fileType);
1839 }
1840
1841 m_SortFields(vFileDialogInternal, m_FileList, m_FilteredFileList);
1842 }
1843}
1844
1845void IGFD::FileManager::m_ScanDirForPathSelection(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) {
1846 std::string path = vPath;
1847
1848 if (!path.empty()) {
1849#ifdef _IGFD_WIN_
1850 if (path == fsRoot) path += IGFD::Utils::GetPathSeparator();
1851#endif // _IGFD_WIN_
1852
1853 ClearPathLists();
1854
1855 const auto& files = m_FileSystemPtr->ScanDirectory(path);
1856 for (const auto& file : files) {
1857 if (file.fileType.isDir()) {
1858 m_AddPath(vFileDialogInternal, path, file.fileNameExt, file.fileType);
1859 }
1860 }
1861
1862 m_SortFields(vFileDialogInternal, m_PathList, m_FilteredPathList);
1863 }
1864}
1865
1866void IGFD::FileManager::m_OpenPathPopup(const FileDialogInternal& vFileDialogInternal, std::vector<std::string>::iterator vPathIter) {
1867 const auto path = ComposeNewPath(vPathIter);
1868 m_ScanDirForPathSelection(vFileDialogInternal, path);
1869 m_PopupComposedPath = vPathIter;
1870 ImGui::OpenPopup("IGFD_Path_Popup");
1871}
1872
1873bool IGFD::FileManager::GetDrives() {
1874 auto drives = m_FileSystemPtr->GetDrivesList();
1875 if (!drives.empty()) {
1876 m_CurrentPath.clear();
1877 m_CurrentPathDecomposition.clear();
1878 ClearFileLists();
1879 for (auto& drive : drives) {
1880 auto info = std::make_shared<FileInfos>();
1881 info->fileNameExt = drive;
1882 info->fileNameExt_optimized = Utils::LowerCaseString(drive);
1883 info->fileType.SetContent(FileType::ContentType::Directory);
1884
1885 if (!info->fileNameExt.empty()) {
1886 m_FileList.push_back(info);
1887 }
1888 }
1889 showDrives = true;
1890 return true;
1891 }
1892 return false;
1893}
1894
1895bool IGFD::FileManager::IsComposerEmpty() {
1896 return m_CurrentPathDecomposition.empty();
1897}
1898
1899size_t IGFD::FileManager::GetComposerSize() {
1900 return m_CurrentPathDecomposition.size();
1901}
1902
1903bool IGFD::FileManager::IsFileListEmpty() {
1904 return m_FileList.empty();
1905}
1906
1907bool IGFD::FileManager::IsPathListEmpty() {
1908 return m_PathList.empty();
1909}
1910
1911size_t IGFD::FileManager::GetFullFileListSize() {
1912 return m_FileList.size();
1913}
1914
1915std::shared_ptr<IGFD::FileInfos> IGFD::FileManager::GetFullFileAt(size_t vIdx) {
1916 if (vIdx < m_FileList.size()) return m_FileList[vIdx];
1917 return nullptr;
1918}
1919
1920bool IGFD::FileManager::IsFilteredListEmpty() {
1921 return m_FilteredFileList.empty();
1922}
1923
1924bool IGFD::FileManager::IsPathFilteredListEmpty() {
1925 return m_FilteredPathList.empty();
1926}
1927
1928size_t IGFD::FileManager::GetFilteredListSize() {
1929 return m_FilteredFileList.size();
1930}
1931
1932size_t IGFD::FileManager::GetPathFilteredListSize() {
1933 return m_FilteredPathList.size();
1934}
1935
1936std::shared_ptr<IGFD::FileInfos> IGFD::FileManager::GetFilteredFileAt(size_t vIdx) {
1937 if (vIdx < m_FilteredFileList.size()) return m_FilteredFileList[vIdx];
1938 return nullptr;
1939}
1940
1941std::shared_ptr<IGFD::FileInfos> IGFD::FileManager::GetFilteredPathAt(size_t vIdx) {
1942 if (vIdx < m_FilteredPathList.size()) return m_FilteredPathList[vIdx];
1943 return nullptr;
1944}
1945
1946std::vector<std::string>::iterator IGFD::FileManager::GetCurrentPopupComposedPath() {
1947 return m_PopupComposedPath;
1948}
1949
1950bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) {
1951 return m_SelectedFileNames.find(vFileName) != m_SelectedFileNames.end();
1952}
1953
1954std::string IGFD::FileManager::GetBack() {
1955 return m_CurrentPathDecomposition.back();
1956}
1957
1958void IGFD::FileManager::ClearComposer() {
1959 m_CurrentPathDecomposition.clear();
1960}
1961
1962void IGFD::FileManager::ClearAll() {
1963 ClearComposer();
1964 ClearFileLists();
1965 ClearPathLists();
1966}
1967void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) {
1968 m_ApplyFilteringOnFileList(vFileDialogInternal, m_FileList, m_FilteredFileList);
1969}
1970
1971void IGFD::FileManager::m_ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal, std::vector<std::shared_ptr<FileInfos> >& vFileInfosList, std::vector<std::shared_ptr<FileInfos> >& vFileInfosFilteredList) {
1972 vFileInfosFilteredList.clear();
1973 for (const auto& file : vFileInfosList) {
1974 if (!file.use_count()) continue;
1975 bool show = true;
1976 if (!file->SearchForTag(vFileDialogInternal.searchManager.searchTag)) // if search tag
1977 show = false;
1978 if (dLGDirectoryMode && !file->fileType.isDir()) show = false;
1979 if (show) vFileInfosFilteredList.push_back(file);
1980 }
1981}
1982
1983void IGFD::FileManager::m_CompleteFileInfos(const std::shared_ptr<FileInfos>& vInfos) {
1984 if (!vInfos.use_count()) return;
1985
1986 if (vInfos->fileNameExt != "." && vInfos->fileNameExt != "..") {
1987 // _stat struct :
1988 // dev_t st_dev; /* ID of device containing file */
1989 // ino_t st_ino; /* inode number */
1990 // mode_t st_mode; /* protection */
1991 // nlink_t st_nlink; /* number of hard links */
1992 // uid_t st_uid; /* user ID of owner */
1993 // gid_t st_gid; /* group ID of owner */
1994 // dev_t st_rdev; /* device ID (if special file) */
1995 // off_t st_size; /* total size, in bytes */
1996 // blksize_t st_blksize; /* blocksize for file system I/O */
1997 // blkcnt_t st_blocks; /* number of 512B blocks allocated */
1998 // time_t st_atime; /* time of last access - not sure out of ntfs */
1999 // time_t st_mtime; /* time of last modification - not sure out of ntfs */
2000 // time_t st_ctime; /* time of last status change - not sure out of ntfs */
2001
2002 std::string fpn;
2003
2004 // FIXME: so the condition is always true?
2005 if (vInfos->fileType.isFile() || vInfos->fileType.isLinkToUnknown() || vInfos->fileType.isDir()) {
2006 fpn = vInfos->filePath + IGFD::Utils::GetPathSeparator() + vInfos->fileNameExt;
2007 }
2008
2009 struct stat statInfos = {};
2010 char timebuf[100];
2011 int result = stat(fpn.c_str(), &statInfos);
2012 if (!result) {
2013 if (!vInfos->fileType.isDir()) {
2014 vInfos->fileSize = (size_t)statInfos.st_size;
2015 vInfos->formatedFileSize = IGFD::Utils::FormatFileSize(vInfos->fileSize);
2016 }
2017
2018 size_t len = 0;
2019#ifdef _MSC_VER
2020 struct tm _tm;
2021 errno_t err = localtime_s(&_tm, &statInfos.st_mtime);
2022 if (!err) len = strftime(timebuf, 99, DateTimeFormat, &_tm);
2023#else // _MSC_VER
2024 struct tm* _tm = localtime(&statInfos.st_mtime);
2025 if (_tm) len = strftime(timebuf, 99, DateTimeFormat, _tm);
2026#endif // _MSC_VER
2027 if (len) {
2028 vInfos->fileModifDate = std::string(timebuf, len);
2029 }
2030 }
2031 }
2032}
2033
2034void IGFD::FileManager::m_RemoveFileNameInSelection(const std::string& vFileName) {
2035 m_SelectedFileNames.erase(vFileName);
2036
2037 if (m_SelectedFileNames.size() == 1) {
2038 snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str());
2039 } else {
2040 snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", m_SelectedFileNames.size());
2041 }
2042}
2043
2044void IGFD::FileManager::m_m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName) {
2045 m_SelectedFileNames.emplace(vFileName);
2046
2047 if (m_SelectedFileNames.size() == 1) {
2048 snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str());
2049 } else {
2050 snprintf(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", m_SelectedFileNames.size());
2051 }
2052
2053 if (vSetLastSelectionFileName) m_LastSelectedFileName = vFileName;
2054}
2055
2056void IGFD::FileManager::SetCurrentDir(const std::string& vPath) {
2057 std::string path = vPath;
2058#ifdef _IGFD_WIN_
2059 if (fsRoot == path) path += IGFD::Utils::GetPathSeparator();
2060#endif // _IGFD_WIN_
2061
2062 bool dir_opened = m_FileSystemPtr->IsDirectory(path);
2063 if (!dir_opened) {
2064 path = ".";
2065 dir_opened = m_FileSystemPtr->IsDirectory(path);
2066 }
2067 if (dir_opened) {
2068#ifdef _IGFD_WIN_
2069 DWORD numchar = 0;
2070 std::wstring wpath = IGFD::Utils::UTF8Decode(path);
2071 numchar = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr);
2072 std::wstring fpath(numchar, 0);
2073 GetFullPathNameW(wpath.c_str(), numchar, (wchar_t*)fpath.data(), nullptr);
2074 std::string real_path = IGFD::Utils::UTF8Encode(fpath);
2075 while (real_path.back() == '\0') // for fix issue we can have with std::string concatenation.. if there is a \0 at end
2076 real_path = real_path.substr(0, real_path.size() - 1U);
2077 if (!real_path.empty())
2078#elif defined(_IGFD_UNIX_) // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE
2079 char real_path[PATH_MAX];
2080 char* numchar = realpath(path.c_str(), real_path);
2081 if (numchar != nullptr)
2082#endif // _IGFD_WIN_
2083 {
2084 m_CurrentPath = std::move(real_path);
2085 if (m_CurrentPath[m_CurrentPath.size() - 1] == PATH_SEP) {
2086 m_CurrentPath = m_CurrentPath.substr(0, m_CurrentPath.size() - 1);
2087 }
2088 IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath);
2089 m_CurrentPathDecomposition = IGFD::Utils::SplitStringToVector(m_CurrentPath, PATH_SEP, false);
2090#ifdef _IGFD_UNIX_ // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE
2091 m_CurrentPathDecomposition.insert(m_CurrentPathDecomposition.begin(), IGFD::Utils::GetPathSeparator());
2092#endif // _IGFD_UNIX_
2093 if (!m_CurrentPathDecomposition.empty()) {
2094#ifdef _IGFD_WIN_
2095 fsRoot = m_CurrentPathDecomposition[0];
2096#endif // _IGFD_WIN_
2097 }
2098 }
2099 }
2100}
2101
2102bool IGFD::FileManager::CreateDir(const std::string& vPath) {
2103 if (!vPath.empty()) {
2104 std::string path = m_CurrentPath + IGFD::Utils::GetPathSeparator() + vPath;
2105 return m_FileSystemPtr->CreateDirectoryIfNotExist(path);
2106 }
2107 return false;
2108}
2109
2110std::string IGFD::FileManager::ComposeNewPath(std::vector<std::string>::iterator vIter) {
2111 std::string res;
2112
2113 while (true) {
2114 if (!res.empty()) {
2115#ifdef _IGFD_WIN_
2116 res = *vIter + IGFD::Utils::GetPathSeparator() + res;
2117#elif defined(_IGFD_UNIX_) // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE
2118 if (*vIter == fsRoot)
2119 res = *vIter + res;
2120 else
2121 res = *vIter + PATH_SEP + res;
2122#endif // _IGFD_WIN_
2123 } else
2124 res = *vIter;
2125
2126 if (vIter == m_CurrentPathDecomposition.begin()) {
2127#ifdef _IGFD_UNIX_ // _IGFD_UNIX_ is _IGFD_WIN_ or APPLE
2128 if (res[0] != PATH_SEP) res = PATH_SEP + res;
2129#else
2130 if (res.back() != PATH_SEP) res.push_back(PATH_SEP);
2131#endif // defined(_IGFD_UNIX_)
2132 break;
2133 }
2134
2135 --vIter;
2136 }
2137
2138 return res;
2139}
2140
2141bool IGFD::FileManager::SetPathOnParentDirectoryIfAny() {
2142 if (m_CurrentPathDecomposition.size() > 1) {
2143 m_CurrentPath = ComposeNewPath(m_CurrentPathDecomposition.end() - 2);
2144 return true;
2145 }
2146 return false;
2147}
2148
2149std::string IGFD::FileManager::GetCurrentPath() {
2150 if (m_CurrentPath.empty()) m_CurrentPath = ".";
2151 return m_CurrentPath;
2152}
2153
2154void IGFD::FileManager::SetCurrentPath(const std::string& vCurrentPath) {
2155 if (vCurrentPath.empty())
2156 m_CurrentPath = ".";
2157 else
2158 m_CurrentPath = vCurrentPath;
2159}
2160
2161void IGFD::FileManager::SetDefaultFileName(const std::string& vFileName) {
2162 dLGDefaultFileName = vFileName;
2163 IGFD::Utils::SetBuffer(fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName);
2164}
2165
2166bool IGFD::FileManager::SelectDirectory(const std::shared_ptr<FileInfos>& vInfos) {
2167 if (!vInfos.use_count()) return false;
2168
2169 bool pathClick = false;
2170
2171 if (vInfos->fileNameExt == "..") {
2172 pathClick = SetPathOnParentDirectoryIfAny();
2173 } else {
2174 std::string newPath;
2175
2176 if (showDrives) {
2177 newPath = vInfos->fileNameExt + IGFD::Utils::GetPathSeparator();
2178 } else {
2179#ifdef __linux__
2180 if (fsRoot == m_CurrentPath)
2181 newPath = m_CurrentPath + vInfos->fileNameExt;
2182 else
2183#endif // __linux__
2184 newPath = m_CurrentPath + IGFD::Utils::GetPathSeparator() + vInfos->fileNameExt;
2185 }
2186
2187 if (m_FileSystemPtr->IsDirectoryCanBeOpened(newPath)) {
2188 if (showDrives) {
2189 m_CurrentPath = vInfos->fileNameExt;
2190 fsRoot = m_CurrentPath;
2191 } else {
2192 m_CurrentPath = newPath; //-V820
2193 }
2194 pathClick = true;
2195 }
2196 }
2197
2198 return pathClick;
2199}
2200
2201void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr<FileInfos>& vInfos) {
2202 if (!vInfos.use_count()) return;
2203
2204 if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) {
2205 if (dLGcountSelectionMax == 0) // infinite selection
2206 {
2207 if (m_SelectedFileNames.find(vInfos->fileNameExt) == m_SelectedFileNames.end()) // not found +> add
2208 {
2209 m_m_AddFileNameInSelection(vInfos->fileNameExt, true);
2210 } else { // found +> remove
2211 m_RemoveFileNameInSelection(vInfos->fileNameExt);
2212 }
2213 } else // selection limited by size
2214 {
2215 if (m_SelectedFileNames.size() < dLGcountSelectionMax) {
2216 if (m_SelectedFileNames.find(vInfos->fileNameExt) == m_SelectedFileNames.end()) // not found +> add
2217 {
2218 m_m_AddFileNameInSelection(vInfos->fileNameExt, true);
2219 } else { // found +> remove
2220 m_RemoveFileNameInSelection(vInfos->fileNameExt);
2221 }
2222 }
2223 }
2224 } else if (ImGui::IsKeyDown(ImGuiMod_Shift)) {
2225 if (dLGcountSelectionMax != 1) {
2226 m_SelectedFileNames.clear();
2227 // we will iterate filelist and get the last selection after the start selection
2228 bool startMultiSelection = false;
2229 std::string fileNameToSelect = vInfos->fileNameExt;
2230 std::string savedLastSelectedFileName; // for invert selection mode
2231 for (const auto& file : m_FileList) {
2232 if (!file.use_count()) continue;
2233
2234 bool canTake = true;
2235 if (!file->SearchForTag(vFileDialogInternal.searchManager.searchTag)) canTake = false;
2236 if (canTake) // if not filtered, we will take files who are filtered by the dialog
2237 {
2238 if (file->fileNameExt == m_LastSelectedFileName) {
2239 startMultiSelection = true;
2240 m_m_AddFileNameInSelection(m_LastSelectedFileName, false);
2241 } else if (startMultiSelection) {
2242 if (dLGcountSelectionMax == 0) // infinite selection
2243 {
2244 m_m_AddFileNameInSelection(file->fileNameExt, false);
2245 } else { // selection limited by size
2246 if (m_SelectedFileNames.size() < dLGcountSelectionMax) {
2247 m_m_AddFileNameInSelection(file->fileNameExt, false);
2248 } else {
2249 startMultiSelection = false;
2250 if (!savedLastSelectedFileName.empty()) m_LastSelectedFileName = savedLastSelectedFileName;
2251 break;
2252 }
2253 }
2254 }
2255
2256 if (file->fileNameExt == fileNameToSelect) {
2257 if (!startMultiSelection) // we are before the last Selected FileName, so we must inverse
2258 {
2259 savedLastSelectedFileName = m_LastSelectedFileName;
2260 m_LastSelectedFileName = fileNameToSelect;
2261 fileNameToSelect = savedLastSelectedFileName;
2262 startMultiSelection = true;
2263 m_m_AddFileNameInSelection(m_LastSelectedFileName, false);
2264 } else {
2265 startMultiSelection = false;
2266 if (!savedLastSelectedFileName.empty()) m_LastSelectedFileName = savedLastSelectedFileName;
2267 break;
2268 }
2269 }
2270 }
2271 }
2272 }
2273 } else {
2274 m_SelectedFileNames.clear();
2275 IGFD::Utils::ResetBuffer(fileNameBuffer);
2276 m_m_AddFileNameInSelection(vInfos->fileNameExt, true);
2277 }
2278}
2279
2280void IGFD::FileManager::DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal) {
2281 if (vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableCreateDirectoryButton) return;
2282
2283 if (IMGUI_BUTTON(createDirButtonString)) {
2284 if (!m_CreateDirectoryMode) {
2285 m_CreateDirectoryMode = true;
2286 IGFD::Utils::ResetBuffer(directoryNameBuffer);
2287 }
2288 }
2289 if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonCreateDirString);
2290
2291 if (m_CreateDirectoryMode) {
2292 ImGui::SameLine();
2293
2294 ImGui::PushItemWidth(100.0f);
2295 ImGui::InputText("##DirectoryFileName", directoryNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER);
2296 ImGui::PopItemWidth();
2297
2298 ImGui::SameLine();
2299
2300 if (IMGUI_BUTTON(okButtonString)) {
2301 std::string newDir = std::string(directoryNameBuffer);
2302 if (CreateDir(newDir)) {
2303 SetCurrentPath(m_CurrentPath + IGFD::Utils::GetPathSeparator() + newDir);
2304 OpenCurrentPath(vFileDialogInternal);
2305 }
2306
2307 m_CreateDirectoryMode = false;
2308 }
2309
2310 ImGui::SameLine();
2311
2312 if (IMGUI_BUTTON(cancelButtonString)) {
2313 m_CreateDirectoryMode = false;
2314 }
2315 }
2316
2317 ImGui::SameLine();
2318}
2319
2320void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) {
2321 if (IMGUI_BUTTON(resetButtonString)) {
2322 SetCurrentPath(".");
2323 OpenCurrentPath(vFileDialogInternal);
2324 }
2325 if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonResetPathString);
2326
2327#ifdef _IGFD_WIN_
2328 ImGui::SameLine();
2329
2330 if (IMGUI_BUTTON(drivesButtonString)) {
2331 drivesClicked = true;
2332 }
2333 if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonDriveString);
2334#endif // _IGFD_WIN_
2335
2336 ImGui::SameLine();
2337
2338 if (IMGUI_BUTTON(editPathButtonString)) {
2339 inputPathActivated = !inputPathActivated;
2340 if (inputPathActivated) {
2341 if (!m_CurrentPathDecomposition.empty()) {
2342 auto endIt = m_CurrentPathDecomposition.end();
2343 m_CurrentPath = ComposeNewPath(--endIt);
2344 IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath);
2345 }
2346 }
2347 }
2348 if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonEditPathString);
2349
2350 ImGui::SameLine();
2351
2352 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
2353
2354 // show current path
2355 if (!m_CurrentPathDecomposition.empty()) {
2356 ImGui::SameLine();
2357
2358 if (inputPathActivated) {
2359 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
2360 ImGui::InputText("##pathedition", inputPathBuffer, MAX_PATH_BUFFER_SIZE);
2361 ImGui::PopItemWidth();
2362 } else {
2363 int _id = 0;
2364 for (auto itPathDecomp = m_CurrentPathDecomposition.begin(); itPathDecomp != m_CurrentPathDecomposition.end(); ++itPathDecomp) {
2365 if (itPathDecomp != m_CurrentPathDecomposition.begin()) {
2366#if defined(CUSTOM_PATH_SPACING)
2367 ImGui::SameLine(0, CUSTOM_PATH_SPACING);
2368#else
2369 ImGui::SameLine();
2370#endif // USE_CUSTOM_PATH_SPACING
2371 if (!(vFileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableQuickPathSelection)) {
2372#if defined(_IGFD_WIN_)
2373 const char* sep = "\\";
2374#elif defined(_IGFD_UNIX_)
2375 const char* sep = "/";
2376 if (itPathDecomp != m_CurrentPathDecomposition.begin() + 1)
2377#endif
2378 {
2379 ImGui::PushID(_id++);
2380 bool click = IMGUI_PATH_BUTTON(sep);
2381 ImGui::PopID();
2382
2383#if defined(CUSTOM_PATH_SPACING)
2384 ImGui::SameLine(0, CUSTOM_PATH_SPACING);
2385#else
2386 ImGui::SameLine();
2387#endif // USE_CUSTOM_PATH_SPACING
2388
2389 if (click) {
2390 m_OpenPathPopup(vFileDialogInternal, itPathDecomp - 1);
2391 } else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
2392 m_SetCurrentPath(itPathDecomp - 1);
2393 break;
2394 }
2395 }
2396 }
2397 }
2398
2399 ImGui::PushID(_id++);
2400 bool click = IMGUI_PATH_BUTTON((*itPathDecomp).c_str());
2401 ImGui::PopID();
2402 if (click) {
2403 m_CurrentPath = ComposeNewPath(itPathDecomp);
2404 puPathClicked = true;
2405 break;
2406 } else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { // activate input for path
2407 m_SetCurrentPath(itPathDecomp);
2408 break;
2409 }
2410 }
2411 }
2412 }
2413}
2414
2415void IGFD::FileManager::m_SetCurrentPath(std::vector<std::string>::iterator vPathIter) {
2416 m_CurrentPath = ComposeNewPath(vPathIter);
2417 IGFD::Utils::SetBuffer(inputPathBuffer, MAX_PATH_BUFFER_SIZE, m_CurrentPath);
2418 inputPathActivated = true;
2419}
2420
2421std::string IGFD::FileManager::GetResultingPath() {
2422 if (dLGDirectoryMode && m_SelectedFileNames.size() == 1) { // if directory mode with selection 1
2423 std::string selectedDirectory = fileNameBuffer;
2424 std::string path = m_CurrentPath;
2425 if (!selectedDirectory.empty() && selectedDirectory != ".") {
2426 path += IGFD::Utils::GetPathSeparator() + selectedDirectory;
2427 }
2428 return path;
2429 }
2430 return m_CurrentPath; // if file mode
2431}
2432
2433std::string IGFD::FileManager::GetResultingFileName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) {
2434 if (!dLGDirectoryMode) { // if not directory mode
2435 const auto& filename = std::string(fileNameBuffer);
2436 return vFileDialogInternal.filterManager.ReplaceExtentionWithCurrentFilterIfNeeded(filename, vFlag);
2437 }
2438 return ""; // directory mode
2439}
2440
2441std::string IGFD::FileManager::GetResultingFilePathName(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) {
2442 if (!dLGDirectoryMode) { // if not directory mode
2443 auto result = GetResultingPath();
2444 const auto& filename = GetResultingFileName(vFileDialogInternal, vFlag);
2445 if (!filename.empty()) {
2446 if (m_FileSystemPtr != nullptr && m_FileSystemPtr->IsFileExist(filename)) {
2447 result = filename; // #144, exist file, so absolute, so return it (maybe set by user in inputText)
2448 } else { // #144, else concate path with current filename
2449#ifdef _IGFD_UNIX_
2450 if (fsRoot != result)
2451#endif // _IGFD_UNIX_
2452 {
2453 result += IGFD::Utils::GetPathSeparator();
2454 }
2455 result += filename;
2456 }
2457 }
2458
2459 return result;
2460 }
2461 return ""; // file mode
2462}
2463
2464std::map<std::string, std::string> IGFD::FileManager::GetResultingSelection(FileDialogInternal& vFileDialogInternal, IGFD_ResultMode vFlag) {
2465 std::map<std::string, std::string> res;
2466 for (const auto& selectedFileName : m_SelectedFileNames) {
2467 auto result = GetResultingPath();
2468#ifdef _IGFD_UNIX_
2469 if (fsRoot != result)
2470#endif // _IGFD_UNIX_
2471 {
2472 result += IGFD::Utils::GetPathSeparator();
2473 }
2474 result += vFileDialogInternal.filterManager.ReplaceExtentionWithCurrentFilterIfNeeded(selectedFileName, vFlag);
2475 res[selectedFileName] = result;
2476 }
2477 return res;
2478}
2479
2480//#pragma endregion
2481
2482//#pragma region FileDialogInternal
2483
2484void IGFD::FileDialogInternal::NewFrame() {
2485 canWeContinue = true; // reset flag for possibily validate the dialog
2486 isOk = false; // reset dialog result
2487 fileManager.drivesClicked = false;
2488 fileManager.puPathClicked = false;
2489
2490 needToExitDialog = false;
2491
2492#ifdef USE_DIALOG_EXIT_WITH_KEY
2493 if (ImGui::IsKeyPressed(IGFD_EXIT_KEY)) {
2494 // we do that here with the data's defined at the last frame
2495 // because escape key can quit input activation and at the end of the frame all flag will be false
2496 // so we will detect nothing
2497 if (!(fileManager.inputPathActivated || searchManager.searchInputIsActive || fileInputIsActive || fileListViewIsActive)) {
2498 needToExitDialog = true; // need to quit dialog
2499 }
2500 } else
2501#endif
2502 {
2503 searchManager.searchInputIsActive = false;
2504 fileInputIsActive = false;
2505 fileListViewIsActive = false;
2506 }
2507}
2508
2509void IGFD::FileDialogInternal::EndFrame() {
2510 // directory change
2511 if (fileManager.puPathClicked) {
2512 fileManager.OpenCurrentPath(*this);
2513 }
2514
2515 if (fileManager.drivesClicked) {
2516 if (fileManager.GetDrives()) {
2517 fileManager.ApplyFilteringOnFileList(*this);
2518 }
2519 }
2520
2521 if (fileManager.inputPathActivated) {
2522 auto gio = ImGui::GetIO();
2523 if (ImGui::IsKeyReleased(ImGuiKey_Enter)) {
2524 fileManager.SetCurrentPath(std::string(fileManager.inputPathBuffer));
2525 fileManager.OpenCurrentPath(*this);
2526 fileManager.inputPathActivated = false;
2527 }
2528 if (ImGui::IsKeyReleased(ImGuiKey_Escape)) {
2529 fileManager.inputPathActivated = false;
2530 }
2531 }
2532}
2533
2534void IGFD::FileDialogInternal::ResetForNewDialog() {
2535}
2536
2537void IGFD::FileDialogInternal::configureDialog(const std::string& vKey, const std::string& vTitle, const char* vFilters, const FileDialogConfig& vConfig) {
2538 m_DialogConfig = vConfig;
2539 ResetForNewDialog();
2540 dLGkey = vKey;
2541 dLGtitle = vTitle;
2542
2543 // treatment
2544 if (m_DialogConfig.sidePane == nullptr) {
2545 m_DialogConfig.sidePaneWidth = 0.0f;
2546 }
2547
2548 if (m_DialogConfig.filePathName.empty()) {
2549 if (m_DialogConfig.path.empty()) {
2550 fileManager.dLGpath = fileManager.GetCurrentPath();
2551 } else {
2552 fileManager.dLGpath = m_DialogConfig.path;
2553 }
2554 fileManager.SetCurrentPath(m_DialogConfig.path);
2555 fileManager.dLGcountSelectionMax = (size_t)m_DialogConfig.countSelectionMax;
2556 fileManager.SetDefaultFileName(m_DialogConfig.fileName);
2557 } else {
2558 auto ps = fileManager.GetFileSystemInstance()->ParsePathFileName(m_DialogConfig.filePathName);
2559 if (ps.isOk) {
2560 fileManager.dLGpath = ps.path;
2561 fileManager.SetDefaultFileName(ps.name);
2562 filterManager.dLGdefaultExt = "." + ps.ext;
2563 } else {
2564 fileManager.dLGpath = fileManager.GetCurrentPath();
2565 fileManager.SetDefaultFileName("");
2566 filterManager.dLGdefaultExt.clear();
2567 }
2568 }
2569
2570 filterManager.dLGdefaultExt.clear();
2571 filterManager.ParseFilters(vFilters);
2572 filterManager.SetSelectedFilterWithExt(filterManager.dLGdefaultExt);
2573 fileManager.SetCurrentPath(fileManager.dLGpath);
2574 fileManager.dLGDirectoryMode = (vFilters == nullptr);
2575 fileManager.dLGcountSelectionMax = m_DialogConfig.countSelectionMax; //-V101
2576 fileManager.ClearAll();
2577 showDialog = true;
2578}
2579
2580const IGFD::FileDialogConfig& IGFD::FileDialogInternal::getDialogConfig() const {
2581 return m_DialogConfig;
2582}
2583
2584IGFD::FileDialogConfig& IGFD::FileDialogInternal::getDialogConfigRef() {
2585 return m_DialogConfig;
2586}
2587
2588//#pragma endregion
2589
2590//#pragma endregion
2591
2592//#pragma region Optional Features
2593
2594//#pragma region ThumbnailFeature
2595
2596IGFD::ThumbnailFeature::ThumbnailFeature() {
2597#ifdef USE_THUMBNAILS
2598 m_DisplayMode = DisplayModeEnum::FILE_LIST;
2599#endif
2600}
2601
2602IGFD::ThumbnailFeature::~ThumbnailFeature() = default;
2603
2604void IGFD::ThumbnailFeature::m_NewThumbnailFrame(FileDialogInternal& /*vFileDialogInternal*/) {
2605#ifdef USE_THUMBNAILS
2606 m_StartThumbnailFileDatasExtraction();
2607#endif
2608}
2609
2610void IGFD::ThumbnailFeature::m_EndThumbnailFrame(FileDialogInternal& vFileDialogInternal) {
2611#ifdef USE_THUMBNAILS
2612 m_ClearThumbnails(vFileDialogInternal);
2613#else
2614 (void)vFileDialogInternal;
2615#endif
2616}
2617
2618void IGFD::ThumbnailFeature::m_QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal) {
2619#ifdef USE_THUMBNAILS
2620 m_StopThumbnailFileDatasExtraction();
2621 m_ClearThumbnails(vFileDialogInternal);
2622#else
2623 (void)vFileDialogInternal;
2624#endif
2625}
2626
2627#ifdef USE_THUMBNAILS
2628void IGFD::ThumbnailFeature::m_StartThumbnailFileDatasExtraction() {
2629 const bool res = m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable();
2630 if (!res) {
2631 m_IsWorking = true;
2632 m_CountFiles = 0U;
2633 m_ThumbnailGenerationThread = std::shared_ptr<std::thread>(new std::thread(&IGFD::ThumbnailFeature::m_ThreadThumbnailFileDatasExtractionFunc, this), [this](std::thread* obj) {
2634 m_IsWorking = false;
2635 if (obj) {
2636 m_ThumbnailFileDatasToGetCv.notify_all();
2637 obj->join();
2638 }
2639 });
2640 }
2641}
2642
2643bool IGFD::ThumbnailFeature::m_StopThumbnailFileDatasExtraction() {
2644 const bool res = m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable();
2645 if (res) {
2646 m_ThumbnailGenerationThread.reset();
2647 }
2648
2649 return res;
2650}
2651
2652void IGFD::ThumbnailFeature::m_ThreadThumbnailFileDatasExtractionFunc() {
2653 m_CountFiles = 0U;
2654 m_IsWorking = true;
2655
2656 // infinite loop while is thread working
2657 while (m_IsWorking) {
2658 std::unique_lock<std::mutex> thumbnailFileDatasToGetLock(m_ThumbnailFileDatasToGetMutex);
2659 m_ThumbnailFileDatasToGetCv.wait(thumbnailFileDatasToGetLock);
2660 if (!m_ThumbnailFileDatasToGet.empty()) {
2661 std::shared_ptr<FileInfos> file = nullptr;
2662 // get the first file in the list
2663 file = (*m_ThumbnailFileDatasToGet.begin());
2664 m_ThumbnailFileDatasToGet.pop_front();
2665 thumbnailFileDatasToGetLock.unlock();
2666
2667 // retrieve datas of the texture file if its an image file
2668 if (file.use_count()) {
2669 if (file->fileType.isFile()) //-V522
2670 {
2671 //|| file->fileExtLevels == ".hdr" => format float so in few times
2672 if (file->SearchForExts(".png,.bmp,.tga,.jpg,.jpeg,.gif,.psd,.pic,.ppm,.pgm", true)) {
2673 auto fpn = file->filePath + IGFD::Utils::GetPathSeparator() + file->fileNameExt;
2674
2675 int w = 0;
2676 int h = 0;
2677 int chans = 0;
2678 uint8_t* datas = stbi_load(fpn.c_str(), &w, &h, &chans, STBI_rgb_alpha);
2679 if (datas) {
2680 if (w && h) {
2681 // resize with respect to glyph ratio
2682 const float ratioX = (float)w / (float)h;
2683 const float newX = DisplayMode_ThumbailsList_ImageHeight * ratioX;
2684 float newY = w / ratioX;
2685 if (newX < w) newY = DisplayMode_ThumbailsList_ImageHeight;
2686
2687 const auto newWidth = (int)newX;
2688 const auto newHeight = (int)newY;
2689 const auto newBufSize = (size_t)(newWidth * newHeight * 4U); //-V112 //-V1028
2690 auto resizedData = new uint8_t[newBufSize];
2691
2692 const int resizeSucceeded = stbir_resize_uint8(datas, w, h, 0, resizedData, newWidth, newHeight, 0,
2693 4); //-V112
2694
2695 if (resizeSucceeded) {
2696 auto th = &file->thumbnailInfo;
2697
2698 th->textureFileDatas = resizedData;
2699 th->textureWidth = newWidth;
2700 th->textureHeight = newHeight;
2701 th->textureChannels = 4; //-V112
2702
2703 // we set that at least, because will launch the gpu creation of the texture in the
2704 // main thread
2705 th->isReadyToUpload = true;
2706
2707 // need gpu loading
2708 m_AddThumbnailToCreate(file);
2709 }
2710 } else {
2711 printf("image loading fail : w:%i h:%i c:%i\n", w, h, 4); //-V112
2712 }
2713
2714 stbi_image_free(datas);
2715 }
2716 }
2717 }
2718 }
2719 } else {
2720 thumbnailFileDatasToGetLock.unlock();
2721 }
2722 }
2723}
2724
2725void IGFD::ThumbnailFeature::m_VariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...) {
2726 va_list args;
2727 va_start(args, fmt);
2728 char TempBuffer[512];
2729 const int w = vsnprintf(TempBuffer, 511, fmt, args);
2730 va_end(args);
2731 if (w) {
2732 ImGui::ProgressBar(fraction, size_arg, TempBuffer);
2733 }
2734}
2735
2736void IGFD::ThumbnailFeature::m_DrawThumbnailGenerationProgress() {
2737 if (m_ThumbnailGenerationThread.use_count() && m_ThumbnailGenerationThread->joinable()) {
2738 if (!m_ThumbnailFileDatasToGet.empty()) {
2739 const auto p = (float)((double)m_CountFiles / (double)m_ThumbnailFileDatasToGet.size()); // read => no thread concurency issues
2740 m_VariadicProgressBar(p, ImVec2(50, 0), "%u/%u", m_CountFiles,
2741 (uint32_t)m_ThumbnailFileDatasToGet.size()); // read => no thread concurency issues
2742 ImGui::SameLine();
2743 }
2744 }
2745}
2746
2747void IGFD::ThumbnailFeature::m_AddThumbnailToLoad(const std::shared_ptr<FileInfos>& vFileInfos) {
2748 if (vFileInfos.use_count()) {
2749 if (vFileInfos->fileType.isFile()) {
2750 //|| file->fileExtLevels == ".hdr" => format float so in few times
2751 if (vFileInfos->SearchForExts(".png,.bmp,.tga,.jpg,.jpeg,.gif,.psd,.pic,.ppm,.pgm", true)) {
2752 // write => thread concurency issues
2753 m_ThumbnailFileDatasToGetMutex.lock();
2754 m_ThumbnailFileDatasToGet.push_back(vFileInfos);
2755 vFileInfos->thumbnailInfo.isLoadingOrLoaded = true;
2756 m_ThumbnailFileDatasToGetMutex.unlock();
2757 m_ThumbnailFileDatasToGetCv.notify_all();
2758 }
2759 }
2760 }
2761}
2762
2763void IGFD::ThumbnailFeature::m_AddThumbnailToCreate(const std::shared_ptr<FileInfos>& vFileInfos) {
2764 if (vFileInfos.use_count()) {
2765 // write => thread concurency issues
2766 m_ThumbnailToCreateMutex.lock();
2767 m_ThumbnailToCreate.push_back(vFileInfos);
2768 m_ThumbnailToCreateMutex.unlock();
2769 }
2770}
2771
2772void IGFD::ThumbnailFeature::m_AddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info) {
2773 // write => thread concurency issues
2774 m_ThumbnailToDestroyMutex.lock();
2775 m_ThumbnailToDestroy.push_back(vIGFD_Thumbnail_Info);
2776 m_ThumbnailToDestroyMutex.unlock();
2777}
2778
2779void IGFD::ThumbnailFeature::m_DrawDisplayModeToolBar() {
2780 if (IMGUI_RADIO_BUTTON(DisplayMode_FilesList_ButtonString, m_DisplayMode == DisplayModeEnum::FILE_LIST)) m_DisplayMode = DisplayModeEnum::FILE_LIST;
2781 if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_FilesList_ButtonHelp);
2782 ImGui::SameLine();
2783 if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsList_ButtonString, m_DisplayMode == DisplayModeEnum::THUMBNAILS_LIST)) m_DisplayMode = DisplayModeEnum::THUMBNAILS_LIST;
2784 if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsList_ButtonHelp);
2785 ImGui::SameLine();
2786 /* todo
2787 if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsGrid_ButtonString,
2788 m_DisplayMode == DisplayModeEnum::THUMBNAILS_GRID))
2789 m_DisplayMode = DisplayModeEnum::THUMBNAILS_GRID;
2790 if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsGrid_ButtonHelp);
2791 ImGui::SameLine();
2792 */
2793 m_DrawThumbnailGenerationProgress();
2794}
2795
2796void IGFD::ThumbnailFeature::m_ClearThumbnails(FileDialogInternal& vFileDialogInternal) {
2797 // directory wil be changed so the file list will be erased
2798 if (vFileDialogInternal.fileManager.puPathClicked) {
2799 size_t count = vFileDialogInternal.fileManager.GetFullFileListSize();
2800 for (size_t idx = 0U; idx < count; idx++) {
2801 auto file = vFileDialogInternal.fileManager.GetFullFileAt(idx);
2802 if (file.use_count()) {
2803 if (file->thumbnailInfo.isReadyToDisplay) //-V522
2804 {
2805 m_AddThumbnailToDestroy(file->thumbnailInfo);
2806 }
2807 }
2808 }
2809 }
2810}
2811
2812void IGFD::ThumbnailFeature::SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun) {
2813 m_CreateThumbnailFun = vCreateThumbnailFun;
2814}
2815
2816void IGFD::ThumbnailFeature::SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun) {
2817 m_DestroyThumbnailFun = vCreateThumbnailFun;
2818}
2819
2820void IGFD::ThumbnailFeature::ManageGPUThumbnails() {
2821 if (m_CreateThumbnailFun) {
2822 m_ThumbnailToCreateMutex.lock();
2823 if (!m_ThumbnailToCreate.empty()) {
2824 for (const auto& file : m_ThumbnailToCreate) {
2825 if (file.use_count()) {
2826 m_CreateThumbnailFun(&file->thumbnailInfo);
2827 }
2828 }
2829 m_ThumbnailToCreate.clear();
2830 }
2831 m_ThumbnailToCreateMutex.unlock();
2832 } else {
2833 printf(
2834 "No Callback found for create texture\nYou need to define the callback with a call to "
2835 "SetCreateThumbnailCallback\n");
2836 }
2837
2838 if (m_DestroyThumbnailFun) {
2839 m_ThumbnailToDestroyMutex.lock();
2840 if (!m_ThumbnailToDestroy.empty()) {
2841 for (auto thumbnail : m_ThumbnailToDestroy) {
2842 m_DestroyThumbnailFun(&thumbnail);
2843 }
2844 m_ThumbnailToDestroy.clear();
2845 }
2846 m_ThumbnailToDestroyMutex.unlock();
2847 } else {
2848 printf(
2849 "No Callback found for destroy texture\nYou need to define the callback with a call to "
2850 "SetCreateThumbnailCallback\n");
2851 }
2852}
2853
2854#endif // USE_THUMBNAILS
2855
2856//#pragma endregion
2857
2858//#pragma region BookMarkFeature
2859
2860IGFD::BookMarkFeature::BookMarkFeature() {
2861#ifdef USE_BOOKMARK
2862 m_BookmarkWidth = defaultBookmarkPaneWith;
2863#endif // USE_BOOKMARK
2864}
2865
2866#ifdef USE_BOOKMARK
2867void IGFD::BookMarkFeature::m_DrawBookmarkButton() {
2868 IMGUI_TOGGLE_BUTTON(bookmarksButtonString, &m_BookmarkPaneShown);
2869
2870 if (ImGui::IsItemHovered()) ImGui::SetTooltip(bookmarksButtonHelpString);
2871}
2872
2873bool IGFD::BookMarkFeature::m_DrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize) {
2874 bool res = false;
2875
2876 ImGui::BeginChild("##bookmarkpane", vSize);
2877
2878 static int selectedBookmarkForEdition = -1;
2879
2880 if (IMGUI_BUTTON(addBookmarkButtonString "##ImGuiFileDialogAddBookmark")) {
2881 if (!vFileDialogInternal.fileManager.IsComposerEmpty()) {
2882 BookmarkStruct bookmark;
2883 bookmark.name = vFileDialogInternal.fileManager.GetBack();
2884 bookmark.path = vFileDialogInternal.fileManager.GetCurrentPath();
2885 m_Bookmarks.push_back(bookmark);
2886 }
2887 }
2888 if (selectedBookmarkForEdition >= 0 && selectedBookmarkForEdition < (int)m_Bookmarks.size()) {
2889 ImGui::SameLine();
2890 if (IMGUI_BUTTON(removeBookmarkButtonString "##ImGuiFileDialogAddBookmark")) {
2891 m_Bookmarks.erase(m_Bookmarks.begin() + selectedBookmarkForEdition);
2892 if (selectedBookmarkForEdition == (int)m_Bookmarks.size()) selectedBookmarkForEdition--;
2893 }
2894
2895 if (selectedBookmarkForEdition >= 0 && selectedBookmarkForEdition < (int)m_Bookmarks.size()) {
2896 ImGui::SameLine();
2897
2898 ImGui::PushItemWidth(vSize.x - ImGui::GetCursorPosX());
2899 if (ImGui::InputText("##ImGuiFileDialogBookmarkEdit", m_BookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER)) {
2900 m_Bookmarks[(size_t)selectedBookmarkForEdition].name = std::string(m_BookmarkEditBuffer);
2901 }
2902 ImGui::PopItemWidth();
2903 }
2904 }
2905
2906 ImGui::Separator();
2907
2908 if (!m_Bookmarks.empty()) {
2909 m_BookmarkClipper.Begin((int)m_Bookmarks.size(), ImGui::GetTextLineHeightWithSpacing());
2910 while (m_BookmarkClipper.Step()) {
2911 for (int i = m_BookmarkClipper.DisplayStart; i < m_BookmarkClipper.DisplayEnd; i++) {
2912 if (i < 0) continue;
2913 const BookmarkStruct& bookmark = m_Bookmarks[(size_t)i];
2914 ImGui::PushID(i);
2915 if (ImGui::Selectable(bookmark.name.c_str(), selectedBookmarkForEdition == i,
2916 ImGuiSelectableFlags_AllowDoubleClick) ||
2917 (selectedBookmarkForEdition == -1 && bookmark.path == vFileDialogInternal.fileManager.GetCurrentPath())) // select if path is current
2918 {
2919 selectedBookmarkForEdition = i;
2920 IGFD::Utils::ResetBuffer(m_BookmarkEditBuffer);
2921 IGFD::Utils::AppendToBuffer(m_BookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER, bookmark.name);
2922
2923 if (ImGui::IsMouseDoubleClicked(0)) // apply path
2924 {
2925 vFileDialogInternal.fileManager.SetCurrentPath(bookmark.path);
2926 vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal);
2927 res = true;
2928 }
2929 }
2930 ImGui::PopID();
2931 if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", bookmark.path.c_str()); //-V111
2932 }
2933 }
2934 m_BookmarkClipper.End();
2935 }
2936
2937 ImGui::EndChild();
2938
2939 return res;
2940}
2941
2942std::string IGFD::BookMarkFeature::SerializeBookmarks(const bool& vDontSerializeCodeBasedBookmarks) {
2943 std::string res;
2944
2945 size_t idx = 0;
2946 for (auto& it : m_Bookmarks) {
2947 if (vDontSerializeCodeBasedBookmarks && it.defined_by_code) continue;
2948
2949 if (idx++ != 0) res += "##"; // ## because reserved by imgui, so an input text cant have ##
2950
2951 res += it.name + "##" + it.path;
2952 }
2953
2954 return res;
2955}
2956
2957void IGFD::BookMarkFeature::DeserializeBookmarks(const std::string& vBookmarks) {
2958 if (!vBookmarks.empty()) {
2959 m_Bookmarks.clear();
2960 auto arr = IGFD::Utils::SplitStringToVector(vBookmarks, '#', false);
2961 for (size_t i = 0; i < arr.size(); i += 2) {
2962 if (i + 1 < arr.size()) // for avoid crash if arr size is impair due to user mistake after edition
2963 {
2964 BookmarkStruct bookmark;
2965 bookmark.name = arr[i];
2966 // if bad format we jump this bookmark
2967 bookmark.path = arr[i + 1];
2968 m_Bookmarks.push_back(bookmark);
2969 }
2970 }
2971 }
2972}
2973
2974void IGFD::BookMarkFeature::AddBookmark(const std::string& vBookMarkName, const std::string& vBookMarkPath) {
2975 if (vBookMarkName.empty() || vBookMarkPath.empty()) return;
2976
2977 BookmarkStruct bookmark;
2978 bookmark.name = vBookMarkName;
2979 bookmark.path = vBookMarkPath;
2980 bookmark.defined_by_code = true;
2981 m_Bookmarks.push_back(bookmark);
2982}
2983
2984bool IGFD::BookMarkFeature::RemoveBookmark(const std::string& vBookMarkName) {
2985 if (vBookMarkName.empty()) return false;
2986
2987 for (auto bookmark_it = m_Bookmarks.begin(); bookmark_it != m_Bookmarks.end(); ++bookmark_it) {
2988 if ((*bookmark_it).name == vBookMarkName) {
2989 m_Bookmarks.erase(bookmark_it);
2990 return true;
2991 }
2992 }
2993
2994 return false;
2995}
2996#endif // USE_BOOKMARK
2997
2998//#pragma endregion
2999
3000//#pragma region KeyExplorerFeature
3001
3002IGFD::KeyExplorerFeature::KeyExplorerFeature() = default;
3003
3004#ifdef USE_EXPLORATION_BY_KEYS
3005bool IGFD::KeyExplorerFeature::m_LocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC) {
3006 bool found = false;
3007
3008 auto& fdi = vFileDialogInternal.fileManager;
3009 if (!fdi.IsFilteredListEmpty()) {
3010 auto countFiles = fdi.GetFilteredListSize();
3011 for (size_t i = m_LocateFileByInputChar_lastFileIdx; i < countFiles; i++) {
3012 auto nfo = fdi.GetFilteredFileAt(i);
3013 if (nfo.use_count()) {
3014 if (nfo->fileNameExt_optimized[0] == vC || // lower case search //-V522
3015 nfo->fileNameExt[0] == vC) // maybe upper case search
3016 {
3017 // float p = ((float)i) * ImGui::GetTextLineHeightWithSpacing();
3018 float p = (float)((double)i / (double)countFiles) * ImGui::GetScrollMaxY();
3019 ImGui::SetScrollY(p);
3020 m_LocateFileByInputChar_lastFound = true;
3021 m_LocateFileByInputChar_lastFileIdx = i;
3022 m_StartFlashItem(m_LocateFileByInputChar_lastFileIdx);
3023
3024 auto infos = fdi.GetFilteredFileAt(m_LocateFileByInputChar_lastFileIdx);
3025 if (infos.use_count()) {
3026 if (infos->fileType.isDir()) //-V522
3027 {
3028 if (fdi.dLGDirectoryMode) // directory chooser
3029 {
3030 fdi.SelectFileName(vFileDialogInternal, infos);
3031 }
3032 } else {
3033 fdi.SelectFileName(vFileDialogInternal, infos);
3034 }
3035
3036 found = true;
3037 break;
3038 }
3039 }
3040 }
3041 }
3042 }
3043
3044 return found;
3045}
3046
3047void IGFD::KeyExplorerFeature::m_LocateByInputKey(FileDialogInternal& vFileDialogInternal) {
3048 ImGuiContext& g = *GImGui;
3049 auto& fdi = vFileDialogInternal.fileManager;
3050 if (!g.ActiveId && !fdi.IsFilteredListEmpty()) {
3051 auto& queueChar = ImGui::GetIO().InputQueueCharacters;
3052 auto countFiles = fdi.GetFilteredListSize();
3053
3054 // point by char
3055 if (!queueChar.empty()) {
3056 ImWchar c = queueChar.back();
3057 if (m_LocateFileByInputChar_InputQueueCharactersSize != queueChar.size()) {
3058 if (c == m_LocateFileByInputChar_lastChar) // next file starting with same char until
3059 {
3060 if (m_LocateFileByInputChar_lastFileIdx < countFiles - 1U)
3061 m_LocateFileByInputChar_lastFileIdx++;
3062 else
3063 m_LocateFileByInputChar_lastFileIdx = 0;
3064 }
3065
3066 if (!m_LocateItem_Loop(vFileDialogInternal, c)) {
3067 // not found, loop again from 0 this time
3068 m_LocateFileByInputChar_lastFileIdx = 0;
3069 m_LocateItem_Loop(vFileDialogInternal, c);
3070 }
3071
3072 m_LocateFileByInputChar_lastChar = c;
3073 }
3074 }
3075
3076 m_LocateFileByInputChar_InputQueueCharactersSize = queueChar.size();
3077 }
3078}
3079
3080void IGFD::KeyExplorerFeature::m_ExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID) {
3081 auto& fdi = vFileDialogInternal.fileManager;
3082 if (!fdi.IsFilteredListEmpty()) {
3083 bool canWeExplore = false;
3084 bool hasNav = (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
3085
3086 ImGuiContext& g = *GImGui;
3087 if (!hasNav && !g.ActiveId) // no nav and no activated inputs
3088 canWeExplore = true;
3089
3090 if (g.NavId && g.NavId == vListViewID) {
3091 if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter) || ImGui::IsKeyPressed(ImGuiKey_Space)) {
3092 ImGui::ActivateItemByID(vListViewID);
3093 ImGui::SetActiveID(vListViewID, g.CurrentWindow);
3094 }
3095 }
3096
3097 if (vListViewID == g.LastActiveId - 1) // if listview id is the last acticated nav id (ImGui::ActivateItemByID(vListViewID);)
3098 canWeExplore = true;
3099
3100 if (canWeExplore && ImGui::IsWindowFocused()) {
3101 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
3102 ImGui::ClearActiveID();
3103 g.LastActiveId = 0;
3104 }
3105
3106 auto countFiles = fdi.GetFilteredListSize();
3107
3108 // explore
3109 bool exploreByKey = false;
3110 bool enterInDirectory = false;
3111 bool exitDirectory = false;
3112
3113 if ((hasNav && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) || (!hasNav && ImGui::IsKeyPressed(ImGuiKey_UpArrow))) {
3114 exploreByKey = true;
3115 if (m_LocateFileByInputChar_lastFileIdx > 0)
3116 m_LocateFileByInputChar_lastFileIdx--;
3117 else
3118 m_LocateFileByInputChar_lastFileIdx = countFiles - 1U;
3119 } else if ((hasNav && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) || (!hasNav && ImGui::IsKeyPressed(ImGuiKey_DownArrow))) {
3120 exploreByKey = true;
3121 if (m_LocateFileByInputChar_lastFileIdx < countFiles - 1U)
3122 m_LocateFileByInputChar_lastFileIdx++;
3123 else
3124 m_LocateFileByInputChar_lastFileIdx = 0U;
3125 } else if (ImGui::IsKeyReleased(ImGuiKey_Enter)) {
3126 exploreByKey = true;
3127 enterInDirectory = true;
3128 } else if (ImGui::IsKeyReleased(ImGuiKey_Backspace)) {
3129 exploreByKey = true;
3130 exitDirectory = true;
3131 }
3132
3133 if (exploreByKey) {
3134 // float totalHeight = m_FilteredFileList.size() * ImGui::GetTextLineHeightWithSpacing();
3135 float p = (float)((double)m_LocateFileByInputChar_lastFileIdx / (double)(countFiles - 1U)) * ImGui::GetScrollMaxY(); // seems not udpated in tables version outside tables
3136 // float p = ((float)locateFileByInputChar_lastFileIdx) * ImGui::GetTextLineHeightWithSpacing();
3137 ImGui::SetScrollY(p);
3138 m_StartFlashItem(m_LocateFileByInputChar_lastFileIdx);
3139
3140 auto infos = fdi.GetFilteredFileAt(m_LocateFileByInputChar_lastFileIdx);
3141 if (infos.use_count()) {
3142 if (infos->fileType.isDir()) //-V522
3143 {
3144 if (!fdi.dLGDirectoryMode || enterInDirectory) {
3145 if (enterInDirectory) {
3146 if (fdi.SelectDirectory(infos)) {
3147 // changement de repertoire
3148 vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal);
3149 if (m_LocateFileByInputChar_lastFileIdx > countFiles - 1U) {
3150 m_LocateFileByInputChar_lastFileIdx = 0;
3151 }
3152 }
3153 }
3154 } else // directory chooser
3155 {
3156 fdi.SelectFileName(vFileDialogInternal, infos);
3157 }
3158 } else {
3159 fdi.SelectFileName(vFileDialogInternal, infos);
3160
3161 if (enterInDirectory) {
3162 vFileDialogInternal.isOk = true;
3163 }
3164 }
3165
3166 if (exitDirectory) {
3167 auto nfo = std::make_shared<FileInfos>();
3168 nfo->fileNameExt = "..";
3169
3170 if (fdi.SelectDirectory(nfo)) {
3171 // changement de repertoire
3172 vFileDialogInternal.fileManager.OpenCurrentPath(vFileDialogInternal);
3173 if (m_LocateFileByInputChar_lastFileIdx > countFiles - 1U) {
3174 m_LocateFileByInputChar_lastFileIdx = 0;
3175 }
3176 }
3177#ifdef _IGFD_WIN_
3178 else {
3179 if (fdi.GetComposerSize() == 1U) {
3180 if (fdi.GetDrives()) {
3181 fdi.ApplyFilteringOnFileList(vFileDialogInternal);
3182 }
3183 }
3184 }
3185#endif // _IGFD_WIN_
3186 }
3187 }
3188 }
3189 }
3190 }
3191}
3192
3193bool IGFD::KeyExplorerFeature::m_FlashableSelectable(const char* label, bool selected, ImGuiSelectableFlags flags, bool vFlashing, const ImVec2& size_arg) {
3194 using namespace ImGui;
3195
3196 ImGuiWindow* window = GetCurrentWindow();
3197 if (window->SkipItems) return false;
3198
3199 ImGuiContext& g = *GImGui;
3200 const ImGuiStyle& style = g.Style;
3201
3202 // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
3203 ImGuiID id = window->GetID(label);
3204 ImVec2 label_size = CalcTextSize(label, NULL, true);
3205 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
3206 ImVec2 pos = window->DC.CursorPos;
3207 pos.y += window->DC.CurrLineTextBaseOffset;
3208 ItemSize(size, 0.0f);
3209
3210 // Fill horizontal space
3211 // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match
3212 // other widgets.
3213 const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
3214 const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
3215 const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
3216 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x);
3217
3218 // Text stays at the submission position, but bounding box may be extended on both sides
3219 const ImVec2 text_min = pos;
3220 const ImVec2 text_max(min_x + size.x, pos.y + size.y);
3221
3222 // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
3223 ImRect bb(min_x, pos.y, text_max.x, text_max.y);
3224 if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) {
3225 const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
3226 const float spacing_y = style.ItemSpacing.y;
3227 const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
3228 const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
3229 bb.Min.x -= spacing_L;
3230 bb.Min.y -= spacing_U;
3231 bb.Max.x += (spacing_x - spacing_L);
3232 bb.Max.y += (spacing_y - spacing_U);
3233 }
3234 // if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
3235
3236 // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
3237 const float backup_clip_rect_min_x = window->ClipRect.Min.x;
3238 const float backup_clip_rect_max_x = window->ClipRect.Max.x;
3239 if (span_all_columns) {
3240 window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
3241 window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
3242 }
3243
3244 const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
3245 const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? ImGuiItemFlags_Disabled : ImGuiItemFlags_None);
3246 if (span_all_columns) {
3247 window->ClipRect.Min.x = backup_clip_rect_min_x;
3248 window->ClipRect.Max.x = backup_clip_rect_max_x;
3249 }
3250
3251 if (!item_add) return false;
3252
3253 const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
3254 if (disabled_item && !disabled_global) // Only testing this as an optimization
3255 BeginDisabled();
3256
3257 // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
3258 // which would be advantageous since most selectable are not selected.
3259 if (span_all_columns && window->DC.CurrentColumns)
3260 PushColumnsBackground();
3261 else if (span_all_columns && g.CurrentTable)
3262 TablePushBackgroundChannel();
3263
3264 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
3265 ImGuiButtonFlags button_flags = 0;
3266 if (flags & ImGuiSelectableFlags_NoHoldingActiveID) {
3267 button_flags |= ImGuiButtonFlags_NoHoldingActiveId;
3268 }
3269 if (flags & ImGuiSelectableFlags_NoSetKeyOwner) {
3270 button_flags |= ImGuiButtonFlags_NoSetKeyOwner;
3271 }
3272 if (flags & ImGuiSelectableFlags_SelectOnClick) {
3273 button_flags |= ImGuiButtonFlags_PressedOnClick;
3274 }
3275 if (flags & ImGuiSelectableFlags_SelectOnRelease) {
3276 button_flags |= ImGuiButtonFlags_PressedOnRelease;
3277 }
3278 if (flags & ImGuiSelectableFlags_AllowDoubleClick) {
3279 button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
3280 }
3281 if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) {
3282 button_flags |= ImGuiButtonFlags_AllowOverlap;
3283 }
3284
3285 const bool was_selected = selected;
3286 bool hovered, held;
3287 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
3288
3289 // Auto-select when moved into
3290 // - This will be more fully fleshed in the range-select branch
3291 // - This is not exposed as it won't nicely work with some user side handling of shift/control
3292 // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
3293 // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
3294 // - (2) usage will fail with clipped items
3295 // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
3296 if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
3297 if (g.NavJustMovedToId == id) selected = pressed = true;
3298
3299 // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
3300 if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) {
3301 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) {
3302 SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
3303 g.NavDisableHighlight = true;
3304 }
3305 }
3306 if (pressed) MarkItemEdited(id);
3307
3308 // In this branch, Selectable() cannot toggle the selection so this will never trigger.
3309 if (selected != was_selected) //-V547
3310 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
3311
3313 // this function copy ImGui::Selectable just for this line....
3314 hovered |= vFlashing;
3316
3317 // Render
3318 if (hovered || selected) {
3319 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
3320 RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
3321 }
3322 if (g.NavId == id) RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding);
3323
3324 if (span_all_columns && window->DC.CurrentColumns)
3325 PopColumnsBackground();
3326 else if (span_all_columns && g.CurrentTable)
3327 TablePopBackgroundChannel();
3328
3329 RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
3330
3331 // Automatically close popups
3332 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) CloseCurrentPopup();
3333
3334 if (disabled_item && !disabled_global) EndDisabled();
3335
3336 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
3337 return pressed; //-V1020
3338}
3339
3340void IGFD::KeyExplorerFeature::m_StartFlashItem(size_t vIdx) {
3341 m_FlashAlpha = 1.0f;
3342 m_FlashedItem = vIdx;
3343}
3344
3345bool IGFD::KeyExplorerFeature::m_BeginFlashItem(size_t vIdx) {
3346 bool res = false;
3347
3348 if (m_FlashedItem == vIdx && std::abs(m_FlashAlpha - 0.0f) > 0.00001f) {
3349 m_FlashAlpha -= m_FlashAlphaAttenInSecs * ImGui::GetIO().DeltaTime;
3350 if (m_FlashAlpha < 0.0f) m_FlashAlpha = 0.0f;
3351
3352 ImVec4 hov = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered);
3353 hov.w = m_FlashAlpha;
3354 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hov);
3355 res = true;
3356 }
3357
3358 return res;
3359}
3360
3361void IGFD::KeyExplorerFeature::m_EndFlashItem() {
3362 ImGui::PopStyleColor();
3363}
3364
3365void IGFD::KeyExplorerFeature::SetFlashingAttenuationInSeconds(float vAttenValue) {
3366 m_FlashAlphaAttenInSecs = 1.0f / ImMax(vAttenValue, 0.01f);
3367}
3368#endif // USE_EXPLORATION_BY_KEYS
3369
3370//#pragma endregion
3371
3372//#pragma endregion
3373
3374//#pragma region FileDialog
3375
3376IGFD::FileDialog::FileDialog() : BookMarkFeature(), KeyExplorerFeature(), ThumbnailFeature() {
3377}
3378IGFD::FileDialog::~FileDialog() = default;
3379
3383
3384// path and fileNameExt can be specified
3385void IGFD::FileDialog::OpenDialog(const std::string& vKey, const std::string& vTitle, const char* vFilters, const FileDialogConfig& vConfig) {
3386 if (m_FileDialogInternal.showDialog) // if already opened, quit
3387 return;
3388 m_FileDialogInternal.configureDialog(vKey, vTitle, vFilters, vConfig);
3389}
3390
3394
3395bool IGFD::FileDialog::Display(const std::string& vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) {
3396 bool res = false;
3397
3398 if (m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey) {
3399 if (m_FileDialogInternal.puUseCustomLocale) setlocale(m_FileDialogInternal.localeCategory, m_FileDialogInternal.localeBegin.c_str());
3400
3401 auto& fdFile = m_FileDialogInternal.fileManager;
3402 auto& fdFilter = m_FileDialogInternal.filterManager;
3403
3404 static ImGuiWindowFlags flags; // todo: not a good solution for multi instance, to fix
3405
3406 // to be sure than only one dialog is displayed per frame
3407 ImGuiContext& g = *GImGui;
3408 if (g.FrameCount == m_FileDialogInternal.lastImGuiFrameCount) // one instance was displayed this frame before
3409 // for this key +> quit
3410 return res;
3411 m_FileDialogInternal.lastImGuiFrameCount = g.FrameCount; // mark this instance as used this frame
3412
3413 std::string name = m_FileDialogInternal.dLGtitle + "##" + m_FileDialogInternal.dLGkey;
3414 if (m_FileDialogInternal.name != name) {
3415 fdFile.ClearComposer();
3416 fdFile.ClearFileLists();
3417 flags = vFlags;
3418 }
3419
3420 m_NewFrame();
3421
3422#ifdef IMGUI_HAS_VIEWPORT
3423 if (!ImGui::GetIO().ConfigViewportsNoDecoration) {
3424 // https://github.com/ocornut/imgui/issues/4534
3425 ImGuiWindowClass window_class;
3426 window_class.ViewportFlagsOverrideClear = ImGuiViewportFlags_NoDecoration;
3427 ImGui::SetNextWindowClass(&window_class);
3428 }
3429#endif // IMGUI_HAS_VIEWPORT
3430
3431 bool beg = false;
3432 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NoDialog) // disable our own dialog system (standard or modal)
3433 {
3434 beg = true;
3435 } else {
3436 ImGui::SetNextWindowSizeConstraints(vMinSize, vMaxSize);
3437
3438 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal && !m_FileDialogInternal.okResultToConfirm) // disable modal because the confirm dialog for overwrite is
3439 // a new modal
3440 {
3441 ImGui::OpenPopup(name.c_str());
3442 beg = ImGui::BeginPopupModal(name.c_str(), (bool*)nullptr, flags | ImGuiWindowFlags_NoScrollbar);
3443 } else {
3444 beg = ImGui::Begin(name.c_str(), (bool*)nullptr, flags | ImGuiWindowFlags_NoScrollbar);
3445 }
3446 }
3447 if (beg) {
3448#ifdef IMGUI_HAS_VIEWPORT
3449 // if decoration is enabled we disable the resizing feature of imgui for avoid crash with SDL2 and GLFW3
3450 if (ImGui::GetIO().ConfigViewportsNoDecoration) {
3451 flags = vFlags;
3452 } else {
3453 auto win = ImGui::GetCurrentWindowRead();
3454 if (win->Viewport->Idx != 0)
3455 flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar;
3456 else
3457 flags = vFlags;
3458 }
3459#endif // IMGUI_HAS_VIEWPORT
3460
3461 ImGuiID _frameId = ImGui::GetID(name.c_str());
3462 ImVec2 frameSize = ImVec2(0, 0);
3463 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NoDialog) frameSize = vMaxSize;
3464 if (ImGui::BeginChild(_frameId, frameSize, false, flags | ImGuiWindowFlags_NoScrollbar)) {
3465 m_FileDialogInternal.name = name; //-V820
3466
3467 if (fdFile.dLGpath.empty()) fdFile.dLGpath = "."; // defaut path is '.'
3468
3469 fdFilter.SetDefaultFilterIfNotDefined();
3470
3471 // init list of files
3472 if (fdFile.IsFileListEmpty() && !fdFile.showDrives) {
3473 if (fdFile.dLGpath != ".") // Removes extension seperator in filename if we don't check
3474 IGFD::Utils::ReplaceString(fdFile.dLGDefaultFileName, fdFile.dLGpath, ""); // local path
3475
3476 if (!fdFile.dLGDefaultFileName.empty()) {
3477 fdFile.SetDefaultFileName(fdFile.dLGDefaultFileName);
3478 fdFilter.SetSelectedFilterWithExt(fdFilter.dLGdefaultExt);
3479 } else if (fdFile.dLGDirectoryMode) // directory mode
3480 fdFile.SetDefaultFileName(".");
3481 fdFile.ScanDir(m_FileDialogInternal, fdFile.dLGpath);
3482 }
3483
3484 // draw dialog parts
3485 m_DrawHeader(); // bookmark, directory, path
3486 m_DrawContent(); // bookmark, files view, side pane
3487 res = m_DrawFooter(); // file field, filter combobox, ok/cancel buttons
3488
3489 m_EndFrame();
3490 }
3491 ImGui::EndChild();
3492
3493 // for display in dialog center, the confirm to overwrite dlg
3494 m_FileDialogInternal.dialogCenterPos = ImGui::GetCurrentWindowRead()->ContentRegionRect.GetCenter();
3495
3496 // when the confirm to overwrite dialog will appear we need to
3497 // disable the modal mode of the main file dialog
3498 // see prOkResultToConfirm under
3499 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal && !m_FileDialogInternal.okResultToConfirm) {
3500 ImGui::EndPopup();
3501 }
3502 }
3503
3504 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_NoDialog) { // disable our own dialog system (standard or modal)
3505 } else {
3506 // same things here regarding prOkResultToConfirm
3507 if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_Modal) || m_FileDialogInternal.okResultToConfirm) {
3508 ImGui::End();
3509 }
3510 }
3511 // confirm the result and show the confirm to overwrite dialog if needed
3512 res = m_Confirm_Or_OpenOverWriteFileDialog_IfNeeded(res, vFlags);
3513
3514 if (m_FileDialogInternal.puUseCustomLocale) setlocale(m_FileDialogInternal.localeCategory, m_FileDialogInternal.localeEnd.c_str());
3515 }
3516
3517 return res;
3518}
3519
3520void IGFD::FileDialog::m_NewFrame() {
3521 m_FileDialogInternal.NewFrame();
3522 m_NewThumbnailFrame(m_FileDialogInternal);
3523}
3524
3525void IGFD::FileDialog::m_EndFrame() {
3526 m_EndThumbnailFrame(m_FileDialogInternal);
3527 m_FileDialogInternal.EndFrame();
3528}
3529void IGFD::FileDialog::m_QuitFrame() {
3530 m_QuitThumbnailFrame(m_FileDialogInternal);
3531}
3532
3533void IGFD::FileDialog::m_DrawHeader() {
3534#ifdef USE_BOOKMARK
3535 if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableBookmarkMode)) {
3536 m_DrawBookmarkButton();
3537 ImGui::SameLine();
3538 }
3539
3540#endif // USE_BOOKMARK
3541
3542 m_FileDialogInternal.fileManager.DrawDirectoryCreation(m_FileDialogInternal);
3543
3544 if (
3545#ifdef USE_BOOKMARK
3546 !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableBookmarkMode) ||
3547#endif // USE_BOOKMARK
3548 !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableCreateDirectoryButton)) {
3549 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
3550 ImGui::SameLine();
3551 }
3552 m_FileDialogInternal.fileManager.DrawPathComposer(m_FileDialogInternal);
3553
3554#ifdef USE_THUMBNAILS
3555 if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode)) {
3556 m_DrawDisplayModeToolBar();
3557 ImGui::SameLine();
3558 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
3559 ImGui::SameLine();
3560 }
3561#endif // USE_THUMBNAILS
3562
3563 m_FileDialogInternal.searchManager.DrawSearchBar(m_FileDialogInternal);
3564}
3565
3566void IGFD::FileDialog::m_DrawContent() {
3567 ImVec2 size = ImGui::GetContentRegionAvail() - ImVec2(0.0f, m_FileDialogInternal.footerHeight);
3568
3569#ifdef USE_BOOKMARK
3570 if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableBookmarkMode)) {
3571 if (m_BookmarkPaneShown) {
3572 // size.x -= m_BookmarkWidth;
3573 float otherWidth = size.x - m_BookmarkWidth;
3574 ImGui::PushID("##splitterbookmark");
3575 IGFD::Utils::ImSplitter(true, 4.0f, &m_BookmarkWidth, &otherWidth, 10.0f, 10.0f + m_FileDialogInternal.getDialogConfig().sidePaneWidth, size.y);
3576 ImGui::PopID();
3577 size.x -= otherWidth;
3578 m_DrawBookmarkPane(m_FileDialogInternal, size);
3579 ImGui::SameLine();
3580 }
3581 }
3582#endif // USE_BOOKMARK
3583
3584 size.x = ImGui::GetContentRegionAvail().x - m_FileDialogInternal.getDialogConfig().sidePaneWidth;
3585
3586 if (m_FileDialogInternal.getDialogConfig().sidePane) {
3587 ImGui::PushID("##splittersidepane");
3588 IGFD::Utils::ImSplitter(true, 4.0f, &size.x, &m_FileDialogInternal.getDialogConfigRef().sidePaneWidth, 10.0f, 10.0f, size.y);
3589 ImGui::PopID();
3590 }
3591
3592#ifdef USE_THUMBNAILS
3593 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode) {
3594 m_DrawFileListView(size);
3595 } else {
3596 switch (m_DisplayMode) {
3597 case DisplayModeEnum::FILE_LIST: m_DrawFileListView(size); break;
3598 case DisplayModeEnum::THUMBNAILS_LIST: m_DrawThumbnailsListView(size); break;
3599 case DisplayModeEnum::THUMBNAILS_GRID: m_DrawThumbnailsGridView(size);
3600 }
3601 }
3602#else // USE_THUMBNAILS
3603 m_DrawFileListView(size);
3604#endif // USE_THUMBNAILS
3605
3606 if (m_FileDialogInternal.getDialogConfig().sidePane) {
3607 m_DrawSidePane(size.y);
3608 }
3609
3610 if (!(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableQuickPathSelection)) {
3611 m_DisplayPathPopup(size);
3612 }
3613}
3614
3615void IGFD::FileDialog::m_DisplayPathPopup(ImVec2 vSize) {
3616 ImVec2 size = ImVec2(vSize.x * 0.5f, vSize.y * 0.5f);
3617 if (ImGui::BeginPopup("IGFD_Path_Popup")) {
3618 auto& fdi = m_FileDialogInternal.fileManager;
3619
3620 ImGui::PushID(this);
3621
3622 static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY;
3623 auto listViewID = ImGui::GetID("##FileDialog_pathTable");
3624 if (ImGui::BeginTableEx("##FileDialog_pathTable", listViewID, 1, flags, size, 0.0f)) //-V112
3625 {
3626 ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible
3627 ImGui::TableSetupColumn(tableHeaderFileNameString, ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0);
3628
3629 ImGui::TableHeadersRow();
3630
3631 if (!fdi.IsPathFilteredListEmpty()) {
3632 std::string _str;
3633 ImFont* _font = nullptr;
3634 bool _showColor = false;
3635
3636 m_PathListClipper.Begin((int)fdi.GetPathFilteredListSize(), ImGui::GetTextLineHeightWithSpacing());
3637 while (m_PathListClipper.Step()) {
3638 for (int i = m_PathListClipper.DisplayStart; i < m_PathListClipper.DisplayEnd; i++) {
3639 if (i < 0) continue;
3640
3641 auto infos = fdi.GetFilteredPathAt((size_t)i);
3642 if (!infos.use_count()) continue;
3643
3644 m_BeginFileColorIconStyle(infos, _showColor, _str, &_font);
3645
3646 bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found
3647
3648 ImGui::TableNextRow();
3649
3650 if (ImGui::TableNextColumn()) // file name
3651 {
3652 if (ImGui::Selectable(infos->fileNameExt.c_str(), &selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth)) {
3653 fdi.SetCurrentPath(fdi.ComposeNewPath(fdi.GetCurrentPopupComposedPath()));
3654 fdi.puPathClicked = fdi.SelectDirectory(infos);
3655 ImGui::CloseCurrentPopup();
3656 }
3657 }
3658
3659 m_EndFileColorIconStyle(_showColor, _font);
3660 }
3661 }
3662 m_PathListClipper.End();
3663 }
3664
3665 ImGui::EndTable();
3666 }
3667
3668 ImGui::PopID();
3669
3670 ImGui::EndPopup();
3671 }
3672}
3673
3674bool IGFD::FileDialog::m_DrawOkButton() {
3675 auto& fdFile = m_FileDialogInternal.fileManager;
3676 if (m_FileDialogInternal.canWeContinue && strlen(fdFile.fileNameBuffer)) {
3677 if (IMGUI_BUTTON(okButtonString "##validationdialog", ImVec2(okButtonWidth, 0.0f)) || m_FileDialogInternal.isOk) {
3678 m_FileDialogInternal.isOk = true;
3679 return true;
3680 }
3681
3682#if !invertOkAndCancelButtons
3683 ImGui::SameLine();
3684#endif
3685 }
3686
3687 return false;
3688}
3689
3690bool IGFD::FileDialog::m_DrawCancelButton() {
3691 if (IMGUI_BUTTON(cancelButtonString "##validationdialog", ImVec2(cancelButtonWidth, 0.0f)) || m_FileDialogInternal.needToExitDialog) // dialog exit asked
3692 {
3693 m_FileDialogInternal.isOk = false;
3694 return true;
3695 }
3696
3697#if invertOkAndCancelButtons
3698 ImGui::SameLine();
3699#endif
3700
3701 return false;
3702}
3703
3704bool IGFD::FileDialog::m_DrawValidationButtons() {
3705 bool res = false;
3706
3707 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetContentRegionAvail().x - prOkCancelButtonWidth) * okCancelButtonAlignement);
3708
3709 ImGui::BeginGroup();
3710
3711 if (invertOkAndCancelButtons) {
3712 res |= m_DrawCancelButton();
3713 res |= m_DrawOkButton();
3714 } else {
3715 res |= m_DrawOkButton();
3716 res |= m_DrawCancelButton();
3717 }
3718
3719 ImGui::EndGroup();
3720
3721 prOkCancelButtonWidth = ImGui::GetItemRectSize().x;
3722
3723 return res;
3724}
3725
3726bool IGFD::FileDialog::m_DrawFooter() {
3727 auto& fdFile = m_FileDialogInternal.fileManager;
3728
3729 float posY = ImGui::GetCursorPos().y; // height of last bar calc
3730 ImGui::AlignTextToFramePadding();
3731 if (!fdFile.dLGDirectoryMode)
3732 ImGui::Text(fileNameString);
3733 else // directory chooser
3734 ImGui::Text(dirNameString);
3735 ImGui::SameLine();
3736
3737 // Input file fields
3738 float width = ImGui::GetContentRegionAvail().x;
3739 if (!fdFile.dLGDirectoryMode) {
3740 ImGuiContext& g = *GImGui;
3741 width -= m_FileDialogInternal.filterManager.GetFilterComboBoxWidth() + g.Style.ItemSpacing.x;
3742 }
3743
3744 ImGui::PushItemWidth(width);
3745 ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue;
3746 if (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ReadOnlyFileNameField) {
3747 flags |= ImGuiInputTextFlags_ReadOnly;
3748 }
3749 if (ImGui::InputText("##FileName", fdFile.fileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, flags)) {
3750 m_FileDialogInternal.isOk = true;
3751 }
3752 if (ImGui::GetItemID() == ImGui::GetActiveID()) m_FileDialogInternal.fileInputIsActive = true;
3753 ImGui::PopItemWidth();
3754
3755 // combobox of filters
3756 m_FileDialogInternal.filterManager.DrawFilterComboBox(m_FileDialogInternal);
3757
3758 bool res = m_DrawValidationButtons();
3759 m_FileDialogInternal.footerHeight = ImGui::GetCursorPosY() - posY;
3760 return res;
3761}
3762
3763void IGFD::FileDialog::m_SelectableItem(int vidx, std::shared_ptr<FileInfos> vInfos, bool vSelected, const char* vFmt, ...) {
3764 if (!vInfos.use_count()) return;
3765
3766 auto& fdi = m_FileDialogInternal.fileManager;
3767
3768 static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth;
3769
3770 va_list args;
3771 va_start(args, vFmt);
3772 vsnprintf(fdi.variadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args);
3773 va_end(args);
3774
3775 float h = 0.0f;
3776#ifdef USE_THUMBNAILS
3777 if (m_DisplayMode == DisplayModeEnum::THUMBNAILS_LIST && !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_DisableThumbnailMode)) {
3778 h = DisplayMode_ThumbailsList_ImageHeight;
3779 }
3780#endif // USE_THUMBNAILS
3781#ifdef USE_EXPLORATION_BY_KEYS
3782 bool flashed = m_BeginFlashItem((size_t)vidx);
3783 bool res = m_FlashableSelectable(fdi.variadicBuffer, vSelected, selectableFlags, flashed, ImVec2(-1.0f, h));
3784 if (flashed) m_EndFlashItem();
3785#else // USE_EXPLORATION_BY_KEYS
3786 (void)vidx; // remove a warnings ofr unused var
3787
3788 bool res = ImGui::Selectable(fdi.variadicBuffer, vSelected, selectableFlags, ImVec2(-1.0f, h));
3789#endif // USE_EXPLORATION_BY_KEYS
3790 if (res) {
3791 if (vInfos->fileType.isDir()) {
3792 // nav system, selectable cause open directory or select directory
3793 if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) {
3794 // little fix for get back the mouse behavior in nav system
3795 if (ImGui::IsMouseDoubleClicked(0)) // 0 -> left mouse button double click
3796 {
3797 fdi.puPathClicked = fdi.SelectDirectory(vInfos);
3798 } else if (fdi.dLGDirectoryMode) // directory chooser
3799 {
3800 fdi.SelectFileName(m_FileDialogInternal, vInfos);
3801 } else {
3802 fdi.puPathClicked = fdi.SelectDirectory(vInfos);
3803 }
3804 } else // no nav system => classic behavior
3805 {
3806 if (ImGui::IsMouseDoubleClicked(0)) // 0 -> left mouse button double click
3807 {
3808 fdi.puPathClicked = fdi.SelectDirectory(vInfos);
3809 } else if (fdi.dLGDirectoryMode) // directory chooser
3810 {
3811 fdi.SelectFileName(m_FileDialogInternal, vInfos);
3812 }
3813 }
3814 } else {
3815 fdi.SelectFileName(m_FileDialogInternal, vInfos);
3816
3817 if (ImGui::IsMouseDoubleClicked(0)) {
3818 m_FileDialogInternal.isOk = true;
3819 }
3820 }
3821 }
3822}
3823
3824void IGFD::FileDialog::m_DisplayFileInfosTooltip(const int32_t& vRowIdx, const int32_t& vColumnIdx, std::shared_ptr<FileInfos> vFileInfos) {
3825 if (ImGui::IsItemHovered()) {
3826 if (vFileInfos != nullptr && vFileInfos->tooltipColumn == vColumnIdx) {
3827 if (!vFileInfos->tooltipMessage.empty()) {
3828 ImGui::SetTooltip("%s", vFileInfos->tooltipMessage.c_str());
3829 }
3830 }
3831 }
3832}
3833
3834void IGFD::FileDialog::m_BeginFileColorIconStyle(std::shared_ptr<FileInfos> vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) {
3835 vOutStr.clear();
3836 vOutShowColor = false;
3837
3838 if (vFileInfos->fileStyle.use_count()) //-V807 //-V522
3839 {
3840 vOutShowColor = true;
3841
3842 *vOutFont = vFileInfos->fileStyle->font;
3843 }
3844
3845 if (vOutShowColor && !vFileInfos->fileStyle->icon.empty())
3846 vOutStr = vFileInfos->fileStyle->icon;
3847 else if (vFileInfos->fileType.isDir())
3848 vOutStr = dirEntryString;
3849 else if (vFileInfos->fileType.isLinkToUnknown())
3850 vOutStr = linkEntryString;
3851 else if (vFileInfos->fileType.isFile())
3852 vOutStr = fileEntryString;
3853
3854 vOutStr += " " + vFileInfos->fileNameExt;
3855
3856 if (vOutShowColor) ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color);
3857 if (*vOutFont) ImGui::PushFont(*vOutFont);
3858}
3859
3860void IGFD::FileDialog::m_EndFileColorIconStyle(const bool& vShowColor, ImFont* vFont) {
3861 if (vFont) ImGui::PopFont();
3862 if (vShowColor) ImGui::PopStyleColor();
3863}
3864
3865void IGFD::FileDialog::m_DrawFileListView(ImVec2 vSize) {
3866 auto& fdi = m_FileDialogInternal.fileManager;
3867
3868 ImGui::PushID(this);
3869
3870 static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY
3871#ifndef USE_CUSTOM_SORTING_ICON
3872 | ImGuiTableFlags_Sortable
3873#endif // USE_CUSTOM_SORTING_ICON
3874 ;
3875 auto listViewID = ImGui::GetID("##FileDialog_fileTable");
3876 if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 4, flags, vSize, 0.0f)) //-V112
3877 {
3878 ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible
3879 ImGui::TableSetupColumn(fdi.header[FileManager::SortingFieldEnum::FIELD_FILENAME].c_str(), ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0);
3880 ImGui::TableSetupColumn(fdi.header[FileManager::SortingFieldEnum::FIELD_TYPE].c_str(),
3881 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderType ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
3882 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0),
3883 -1, 1);
3884 ImGui::TableSetupColumn(fdi.header[FileManager::SortingFieldEnum::FIELD_SIZE].c_str(),
3885 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderSize ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
3886 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0),
3887 -1, 2);
3888 ImGui::TableSetupColumn(fdi.header[FileManager::SortingFieldEnum::FIELD_DATE].c_str(),
3889 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderDate ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
3890 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0),
3891 -1, 3);
3892
3893#ifndef USE_CUSTOM_SORTING_ICON
3894 // Sort our data if sort specs have been changed!
3895 if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) {
3896 if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) {
3897 bool direction = sorts_specs->Specs->SortDirection == ImGuiSortDirection_Ascending;
3898
3899 if (sorts_specs->Specs->ColumnUserID == 0) {
3900 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME;
3901 fdi.sortingDirection[0] = direction;
3902 fdi.SortFields(m_FileDialogInternal);
3903 } else if (sorts_specs->Specs->ColumnUserID == 1) {
3904 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE;
3905 fdi.sortingDirection[1] = direction;
3906 fdi.SortFields(m_FileDialogInternal);
3907 } else if (sorts_specs->Specs->ColumnUserID == 2) {
3908 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE;
3909 fdi.sortingDirection[2] = direction;
3910 fdi.SortFields(m_FileDialogInternal);
3911 } else // if (sorts_specs->Specs->ColumnUserID == 3) => alwayd true for the moment, to uncomment if we
3912 // add a fourth column
3913 {
3914 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE;
3915 fdi.sortingDirection[3] = direction;
3916 fdi.SortFields(m_FileDialogInternal);
3917 }
3918
3919 sorts_specs->SpecsDirty = false;
3920 }
3921 }
3922
3923 ImGui::TableHeadersRow();
3924#else // USE_CUSTOM_SORTING_ICON
3925 ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
3926 for (int column = 0; column < 4; column++) //-V112
3927 {
3928 ImGui::TableSetColumnIndex(column);
3929 const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn()
3930 ImGui::PushID(column);
3931 ImGui::TableHeader(column_name);
3932 ImGui::PopID();
3933 if (ImGui::IsItemClicked()) {
3934 if (column == 0) {
3935 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME)
3936 fdi.sortingDirection[0] = !fdi.sortingDirection[0];
3937 else
3938 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME;
3939
3940 fdi.SortFields(m_FileDialogInternal);
3941 } else if (column == 1) {
3942 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_TYPE)
3943 fdi.sortingDirection[1] = !fdi.sortingDirection[1];
3944 else
3945 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE;
3946
3947 fdi.SortFields(m_FileDialogInternal);
3948 } else if (column == 2) {
3949 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_SIZE)
3950 fdi.sortingDirection[2] = !fdi.sortingDirection[2];
3951 else
3952 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE;
3953
3954 fdi.SortFields(m_FileDialogInternal);
3955 } else // if (column == 3) => alwayd true for the moment, to uncomment if we add a fourth column
3956 {
3957 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_DATE)
3958 fdi.sortingDirection[3] = !fdi.sortingDirection[3];
3959 else
3960 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE;
3961
3962 fdi.SortFields(m_FileDialogInternal);
3963 }
3964 }
3965 }
3966#endif // USE_CUSTOM_SORTING_ICON
3967 if (!fdi.IsFilteredListEmpty()) {
3968 std::string _str;
3969 ImFont* _font = nullptr;
3970 bool _showColor = false;
3971
3972 int column_id = 0;
3973 m_FileListClipper.Begin((int)fdi.GetFilteredListSize(), ImGui::GetTextLineHeightWithSpacing());
3974 while (m_FileListClipper.Step()) {
3975 for (int i = m_FileListClipper.DisplayStart; i < m_FileListClipper.DisplayEnd; i++) {
3976 if (i < 0) continue;
3977
3978 auto infos = fdi.GetFilteredFileAt((size_t)i);
3979 if (!infos.use_count()) continue;
3980
3981 m_BeginFileColorIconStyle(infos, _showColor, _str, &_font);
3982
3983 bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found
3984
3985 ImGui::TableNextRow();
3986
3987 column_id = 0;
3988 if (ImGui::TableNextColumn()) // file name
3989 {
3990 m_SelectableItem(i, infos, selected, _str.c_str());
3991 m_DisplayFileInfosTooltip(i, column_id++, infos);
3992 }
3993 if (ImGui::TableNextColumn()) // file type
3994 {
3995 ImGui::Text("%s", infos->fileExtLevels[0].c_str());
3996 m_DisplayFileInfosTooltip(i, column_id++, infos);
3997 }
3998 if (ImGui::TableNextColumn()) // file size
3999 {
4000 if (!infos->fileType.isDir()) {
4001 ImGui::Text("%s ", infos->formatedFileSize.c_str());
4002 } else {
4004 }
4005 m_DisplayFileInfosTooltip(i, column_id++, infos);
4006 }
4007 if (ImGui::TableNextColumn()) // file date + time
4008 {
4009 ImGui::Text("%s", infos->fileModifDate.c_str());
4010 m_DisplayFileInfosTooltip(i, column_id++, infos);
4011 }
4012
4013 m_EndFileColorIconStyle(_showColor, _font);
4014 }
4015 }
4016 m_FileListClipper.End();
4017 }
4018
4019#ifdef USE_EXPLORATION_BY_KEYS
4020 if (!fdi.inputPathActivated) {
4021 m_LocateByInputKey(m_FileDialogInternal);
4022 m_ExploreWithkeys(m_FileDialogInternal, listViewID);
4023 }
4024#endif // USE_EXPLORATION_BY_KEYS
4025
4026 ImGuiContext& g = *GImGui;
4027 if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) {
4028 m_FileDialogInternal.fileListViewIsActive = true;
4029 }
4030
4031 ImGui::EndTable();
4032 }
4033
4034 ImGui::PopID();
4035}
4036
4037#ifdef USE_THUMBNAILS
4038void IGFD::FileDialog::m_DrawThumbnailsListView(ImVec2 vSize) {
4039 auto& fdi = m_FileDialogInternal.fileManager;
4040
4041 ImGui::PushID(this);
4042
4043 static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_NoHostExtendY
4044#ifndef USE_CUSTOM_SORTING_ICON
4045 | ImGuiTableFlags_Sortable
4046#endif // USE_CUSTOM_SORTING_ICON
4047 ;
4048 auto listViewID = ImGui::GetID("##FileDialog_fileTable");
4049 if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 5, flags, vSize, 0.0f)) {
4050 ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible
4051 ImGui::TableSetupColumn(fdi.headerFileName.c_str(), ImGuiTableColumnFlags_WidthStretch | (defaultSortOrderFilename ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 0);
4052 ImGui::TableSetupColumn(fdi.headerFileType.c_str(),
4053 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderType ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
4054 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0),
4055 -1, 1);
4056 ImGui::TableSetupColumn(fdi.headerFileSize.c_str(),
4057 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderSize ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
4058 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0),
4059 -1, 2);
4060 ImGui::TableSetupColumn(fdi.headerFileDate.c_str(),
4061 ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderDate ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending) |
4062 ((m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0),
4063 -1, 3);
4064 // not needed to have an option for hide the thumbnails since this is why this view is used
4065 ImGui::TableSetupColumn(fdi.header[FileManager::SortingFieldEnum::FIELD_THUMBNAILS].c_str(), ImGuiTableColumnFlags_WidthFixed | (defaultSortOrderThumbnails ? ImGuiTableColumnFlags_PreferSortAscending : ImGuiTableColumnFlags_PreferSortDescending), -1, 4); //-V112
4066
4067#ifndef USE_CUSTOM_SORTING_ICON
4068 // Sort our data if sort specs have been changed!
4069 if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) {
4070 if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) {
4071 bool direction = sorts_specs->Specs->SortDirection == ImGuiSortDirection_Ascending;
4072
4073 if (sorts_specs->Specs->ColumnUserID == 0) {
4074 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME;
4075 fdi.sortingDirection[0] = direction;
4076 fdi.SortFields(m_FileDialogInternal);
4077 } else if (sorts_specs->Specs->ColumnUserID == 1) {
4078 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE;
4079 fdi.sortingDirection[1] = direction;
4080 fdi.SortFields(m_FileDialogInternal);
4081 } else if (sorts_specs->Specs->ColumnUserID == 2) {
4082 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE;
4083 fdi.sortingDirection[2] = direction;
4084 fdi.SortFields(m_FileDialogInternal);
4085 } else if (sorts_specs->Specs->ColumnUserID == 3) {
4086 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE;
4087 fdi.sortingDirection[3] = direction;
4088 fdi.SortFields(m_FileDialogInternal);
4089 } else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we
4090 // add another column
4091 {
4092 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS;
4093 fdi.sortingDirection[4] = direction;
4094 fdi.SortFields(m_FileDialogInternal);
4095 }
4096
4097 sorts_specs->SpecsDirty = false;
4098 }
4099 }
4100
4101 ImGui::TableHeadersRow();
4102#else // USE_CUSTOM_SORTING_ICON
4103 ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
4104 for (int column = 0; column < 5; column++) {
4105 ImGui::TableSetColumnIndex(column);
4106 const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn()
4107 ImGui::PushID(column);
4108 ImGui::TableHeader(column_name);
4109 ImGui::PopID();
4110 if (ImGui::IsItemClicked()) {
4111 if (column == 0) {
4112 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME)
4113 fdi.sortingDirection[0] = !fdi.sortingDirection[0];
4114 else
4115 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME;
4116
4117 fdi.SortFields(m_FileDialogInternal);
4118 } else if (column == 1) {
4119 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_TYPE)
4120 fdi.sortingDirection[1] = !fdi.sortingDirection[1];
4121 else
4122 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_TYPE;
4123
4124 fdi.SortFields(m_FileDialogInternal);
4125 } else if (column == 2) {
4126 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_SIZE)
4127 fdi.sortingDirection[2] = !fdi.sortingDirection[2];
4128 else
4129 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_SIZE;
4130
4131 fdi.SortFields(m_FileDialogInternal);
4132 } else if (column == 3) {
4133 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_DATE)
4134 fdi.sortingDirection[3] = !fdi.sortingDirection[3];
4135 else
4136 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_DATE;
4137
4138 fdi.SortFields(m_FileDialogInternal);
4139 } else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we
4140 // add another column
4141 {
4142 if (fdi.sortingField == IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS)
4143 fdi.sortingDirection[4] = !fdi.sortingDirection[4];
4144 else
4145 fdi.sortingField = IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS;
4146
4147 fdi.SortFields(m_FileDialogInternal);
4148 }
4149 }
4150 }
4151#endif // USE_CUSTOM_SORTING_ICON
4152 if (!fdi.IsFilteredListEmpty()) {
4153 std::string _str;
4154 ImFont* _font = nullptr;
4155 bool _showColor = false;
4156
4157 ImGuiContext& g = *GImGui;
4158 const float itemHeight = ImMax(g.FontSize, DisplayMode_ThumbailsList_ImageHeight) + g.Style.ItemSpacing.y;
4159
4160 int column_id = 0;
4161 m_FileListClipper.Begin((int)fdi.GetFilteredListSize(), itemHeight);
4162 while (m_FileListClipper.Step()) {
4163 for (int i = m_FileListClipper.DisplayStart; i < m_FileListClipper.DisplayEnd; i++) {
4164 if (i < 0) continue;
4165
4166 auto infos = fdi.GetFilteredFileAt((size_t)i);
4167 if (!infos.use_count()) continue;
4168
4169 m_BeginFileColorIconStyle(infos, _showColor, _str, &_font);
4170
4171 bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found
4172
4173 ImGui::TableNextRow();
4174
4175 column_id = 0;
4176 if (ImGui::TableNextColumn()) // file name
4177 {
4178 m_SelectableItem(i, infos, selected, _str.c_str());
4179 m_DisplayFileInfosTooltip(i, column_id++, infos);
4180 }
4181 if (ImGui::TableNextColumn()) // file type
4182 {
4183 ImGui::Text("%s", infos->fileExtLevels[0].c_str());
4184 m_DisplayFileInfosTooltip(i, column_id++, infos);
4185 }
4186 if (ImGui::TableNextColumn()) // file size
4187 {
4188 if (!infos->fileType.isDir()) {
4189 ImGui::Text("%s ", infos->formatedFileSize.c_str());
4190 } else {
4192 }
4193 m_DisplayFileInfosTooltip(i, column_id++, infos);
4194 }
4195 if (ImGui::TableNextColumn()) // file date + time
4196 {
4197 ImGui::Text("%s", infos->fileModifDate.c_str());
4198 m_DisplayFileInfosTooltip(i, column_id++, infos);
4199 }
4200 if (ImGui::TableNextColumn()) // file thumbnails
4201 {
4202 auto th = &infos->thumbnailInfo;
4203
4204 if (!th->isLoadingOrLoaded) {
4205 m_AddThumbnailToLoad(infos);
4206 }
4207 if (th->isReadyToDisplay && th->textureID) {
4208 ImGui::Image((ImTextureID)th->textureID, ImVec2((float)th->textureWidth, (float)th->textureHeight));
4209 }
4210 m_DisplayFileInfosTooltip(i, column_id++, infos);
4211 }
4212
4213 m_EndFileColorIconStyle(_showColor, _font);
4214 }
4215 }
4216 m_FileListClipper.End();
4217 }
4218
4219#ifdef USE_EXPLORATION_BY_KEYS
4220 if (!fdi.inputPathActivated) {
4221 m_LocateByInputKey(m_FileDialogInternal);
4222 m_ExploreWithkeys(m_FileDialogInternal, listViewID);
4223 }
4224#endif // USE_EXPLORATION_BY_KEYS
4225
4226 ImGuiContext& g = *GImGui;
4227 if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) {
4228 m_FileDialogInternal.fileListViewIsActive = true;
4229 }
4230
4231 ImGui::EndTable();
4232 }
4233
4234 ImGui::PopID();
4235}
4236
4237void IGFD::FileDialog::m_DrawThumbnailsGridView(ImVec2 vSize) {
4238 if (ImGui::BeginChild("##thumbnailsGridsFiles", vSize)) {
4239 // todo
4240 }
4241
4242 ImGui::EndChild();
4243}
4244
4245#endif
4246
4247void IGFD::FileDialog::m_DrawSidePane(float vHeight) {
4248 ImGui::SameLine();
4249
4250 ImGui::BeginChild("##FileTypes", ImVec2(0, vHeight));
4251
4252 m_FileDialogInternal.getDialogConfig().sidePane(
4253 m_FileDialogInternal.filterManager.GetSelectedFilter().getFirstFilter().c_str(),
4254 m_FileDialogInternal.getDialogConfigRef().userDatas,
4255 &m_FileDialogInternal.canWeContinue);
4256 ImGui::EndChild();
4257}
4258
4259void IGFD::FileDialog::Close() {
4260 m_FileDialogInternal.dLGkey.clear();
4261 m_FileDialogInternal.showDialog = false;
4262}
4263
4264bool IGFD::FileDialog::WasOpenedThisFrame(const std::string& vKey) const {
4265 bool res = m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey;
4266 if (res) {
4267 res &= m_FileDialogInternal.lastImGuiFrameCount == GImGui->FrameCount; // return true if a dialog was displayed in this frame
4268 }
4269 return res;
4270}
4271
4272bool IGFD::FileDialog::WasOpenedThisFrame() const {
4273 bool res = m_FileDialogInternal.showDialog;
4274 if (res) {
4275 res &= m_FileDialogInternal.lastImGuiFrameCount == GImGui->FrameCount; // return true if a dialog was displayed in this frame
4276 }
4277 return res;
4278}
4279
4280bool IGFD::FileDialog::IsOpened(const std::string& vKey) const {
4281 return (m_FileDialogInternal.showDialog && m_FileDialogInternal.dLGkey == vKey);
4282}
4283
4284bool IGFD::FileDialog::IsOpened() const {
4285 return m_FileDialogInternal.showDialog;
4286}
4287
4288std::string IGFD::FileDialog::GetOpenedKey() const {
4289 if (m_FileDialogInternal.showDialog) {
4290 return m_FileDialogInternal.dLGkey;
4291 }
4292 return "";
4293}
4294
4295std::string IGFD::FileDialog::GetFilePathName(IGFD_ResultMode vFlag) {
4296 return m_FileDialogInternal.fileManager.GetResultingFilePathName(m_FileDialogInternal, vFlag);
4297}
4298
4299std::string IGFD::FileDialog::GetCurrentPath() {
4300 return m_FileDialogInternal.fileManager.GetResultingPath();
4301}
4302
4303std::string IGFD::FileDialog::GetCurrentFileName(IGFD_ResultMode vFlag) {
4304 return m_FileDialogInternal.fileManager.GetResultingFileName(m_FileDialogInternal, vFlag);
4305}
4306
4307std::string IGFD::FileDialog::GetCurrentFilter() {
4308 return m_FileDialogInternal.filterManager.GetSelectedFilter().title;
4309}
4310
4311std::map<std::string, std::string> IGFD::FileDialog::GetSelection(IGFD_ResultMode vFlag) {
4312 return m_FileDialogInternal.fileManager.GetResultingSelection(m_FileDialogInternal, vFlag);
4313}
4314
4315IGFD::UserDatas IGFD::FileDialog::GetUserDatas() const {
4316 return m_FileDialogInternal.getDialogConfig().userDatas;
4317}
4318
4319bool IGFD::FileDialog::IsOk() const {
4320 return m_FileDialogInternal.isOk;
4321}
4322
4323void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) {
4324 m_FileDialogInternal.filterManager.SetFileStyle(vFlags, vCriteria, vInfos);
4325}
4326
4327void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) {
4328 m_FileDialogInternal.filterManager.SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont);
4329}
4330
4331void IGFD::FileDialog::SetFileStyle(FileStyle::FileStyleFunctor vFunctor) {
4332 m_FileDialogInternal.filterManager.SetFileStyle(vFunctor);
4333}
4334
4335bool IGFD::FileDialog::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont** vOutFont) {
4336 return m_FileDialogInternal.filterManager.GetFileStyle(vFlags, vCriteria, vOutColor, vOutIcon, vOutFont);
4337}
4338
4339void IGFD::FileDialog::ClearFilesStyle() {
4340 m_FileDialogInternal.filterManager.ClearFilesStyle();
4341}
4342
4343void IGFD::FileDialog::SetLocales(const int& /*vLocaleCategory*/, const std::string& vLocaleBegin, const std::string& vLocaleEnd) {
4344 m_FileDialogInternal.puUseCustomLocale = true;
4345 m_FileDialogInternal.localeBegin = vLocaleBegin;
4346 m_FileDialogInternal.localeEnd = vLocaleEnd;
4347}
4348
4352
4353bool IGFD::FileDialog::m_Confirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags) {
4354 // if confirmation => return true for confirm the overwrite et quit the dialog
4355 // if cancel => return false && set IsOk to false for keep inside the dialog
4356
4357 // if IsOk == false => return false for quit the dialog
4358 if (!m_FileDialogInternal.isOk && vLastAction) {
4359 m_QuitFrame();
4360 return true;
4361 }
4362
4363 // if IsOk == true && no check of overwrite => return true for confirm the dialog
4364 if (m_FileDialogInternal.isOk && vLastAction && !(m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ConfirmOverwrite)) {
4365 m_QuitFrame();
4366 return true;
4367 }
4368
4369 // if IsOk == true && check of overwrite => return false and show confirm to overwrite dialog
4370 if ((m_FileDialogInternal.okResultToConfirm || (m_FileDialogInternal.isOk && vLastAction)) && (m_FileDialogInternal.getDialogConfig().flags & ImGuiFileDialogFlags_ConfirmOverwrite)) {
4371 if (m_FileDialogInternal.isOk) // catched only one time
4372 {
4373 if (!m_FileDialogInternal.fileManager.GetFileSystemInstance()->IsFileExist(GetFilePathName())) // not existing => quit dialog
4374 {
4375 m_QuitFrame();
4376 return true;
4377 } else // existing => confirm dialog to open
4378 {
4379 m_FileDialogInternal.isOk = false;
4380 m_FileDialogInternal.okResultToConfirm = true;
4381 }
4382 }
4383
4384 std::string name = OverWriteDialogTitleString "##" + m_FileDialogInternal.dLGtitle + m_FileDialogInternal.dLGkey + "OverWriteDialog";
4385
4386 bool res = false;
4387
4388 ImGui::OpenPopup(name.c_str());
4389 if (ImGui::BeginPopupModal(name.c_str(), (bool*)0, vFlags | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
4390 ImGui::SetWindowPos(m_FileDialogInternal.dialogCenterPos - ImGui::GetWindowSize() * 0.5f); // next frame needed for GetWindowSize to work
4391
4392 ImGui::Text("%s", OverWriteDialogMessageString);
4393
4394 if (IMGUI_BUTTON(OverWriteDialogConfirmButtonString)) {
4395 m_FileDialogInternal.okResultToConfirm = false;
4396 m_FileDialogInternal.isOk = true;
4397 res = true;
4398 ImGui::CloseCurrentPopup();
4399 }
4400
4401 ImGui::SameLine();
4402
4403 if (IMGUI_BUTTON(OverWriteDialogCancelButtonString)) {
4404 m_FileDialogInternal.okResultToConfirm = false;
4405 m_FileDialogInternal.isOk = false;
4406 res = false;
4407 ImGui::CloseCurrentPopup();
4408 }
4409
4410 ImGui::EndPopup();
4411 }
4412
4413 if (res) {
4414 m_QuitFrame();
4415 }
4416 return res;
4417 }
4418
4419 return false;
4420}
4421
4422//#pragma endregion
4423
4424//#pragma endregion
4425
4426#endif // __cplusplus
4427
4428//#pragma region IGFD_C_API
4429
4430// return an initialized IGFD_FileDialog_Config
4431IGFD_C_API IGFD_FileDialog_Config IGFD_FileDialog_Config_Get() {
4432 IGFD_FileDialog_Config res = {};
4433 res.path = "";
4434 res.fileName = "";
4435 res.filePathName = "";
4436 res.countSelectionMax = 1;
4437 res.userDatas = nullptr;
4438 res.sidePane = nullptr;
4439 res.sidePaneWidth = 250.0f;
4440 res.flags = ImGuiFileDialogFlags_Default;
4441 return res;
4442}
4443
4444// Return an initialized IGFD_Selection_Pair
4445IGFD_C_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(void) {
4446 IGFD_Selection_Pair res = {};
4447 res.fileName = nullptr;
4448 res.filePathName = nullptr;
4449 return res;
4450}
4451
4452// destroy only the content of vSelection_Pair
4453IGFD_C_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair) {
4454 if (vSelection_Pair) {
4455 delete[] vSelection_Pair->fileName;
4456 delete[] vSelection_Pair->filePathName;
4457 }
4458}
4459
4460// Return an initialized IGFD_Selection
4461IGFD_C_API IGFD_Selection IGFD_Selection_Get(void) {
4462 return {nullptr, 0U};
4463}
4464
4465// destroy only the content of vSelection
4466IGFD_C_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection) {
4467 if (vSelection) {
4468 if (vSelection->table) {
4469 for (size_t i = 0U; i < vSelection->count; i++) {
4470 IGFD_Selection_Pair_DestroyContent(&vSelection->table[i]);
4471 }
4472 delete[] vSelection->table;
4473 }
4474 vSelection->count = 0U;
4475 }
4476}
4477
4478// create an instance of ImGuiFileDialog
4479IGFD_C_API ImGuiFileDialog* IGFD_Create(void) {
4480 return new ImGuiFileDialog();
4481}
4482
4483// destroy the instance of ImGuiFileDialog
4484IGFD_C_API void IGFD_Destroy(ImGuiFileDialog* vContextPtr) {
4485 if (vContextPtr != nullptr) {
4486 delete vContextPtr;
4487 vContextPtr = nullptr;
4488 }
4489}
4490
4491IGFD_C_API void IGFD_OpenDialog( // open a standard dialog
4492 ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
4493 const char* vKey, // key dialog
4494 const char* vTitle, // title
4495 const char* vFilters, // filters/filter collections. set it to null for directory mode
4496 const IGFD_FileDialog_Config vConfig) { // path
4497 if (vContextPtr != nullptr) {
4498 IGFD::FileDialogConfig config;
4499 config.path = vConfig.path;
4500 config.fileName = vConfig.fileName;
4501 config.filePathName = vConfig.filePathName;
4502 config.countSelectionMax = vConfig.countSelectionMax;
4503 config.userDatas = vConfig.userDatas;
4504 config.flags = vConfig.flags;
4505 config.sidePane = vConfig.sidePane;
4506 config.sidePaneWidth = vConfig.sidePaneWidth;
4507 vContextPtr->OpenDialog(vKey, vTitle, vFilters, config);
4508 }
4509}
4510
4511IGFD_C_API bool IGFD_DisplayDialog(ImGuiFileDialog* vContextPtr, const char* vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) {
4512 if (vContextPtr != nullptr) {
4513 return vContextPtr->Display(vKey, vFlags, vMinSize, vMaxSize);
4514 }
4515 return false;
4516}
4517
4518IGFD_C_API void IGFD_CloseDialog(ImGuiFileDialog* vContextPtr) {
4519 if (vContextPtr != nullptr) {
4520 vContextPtr->Close();
4521 }
4522}
4523
4524IGFD_C_API bool IGFD_IsOk(ImGuiFileDialog* vContextPtr) {
4525 if (vContextPtr != nullptr) {
4526 return vContextPtr->IsOk();
4527 }
4528 return false;
4529}
4530
4531IGFD_C_API bool IGFD_WasKeyOpenedThisFrame(ImGuiFileDialog* vContextPtr, const char* vKey) {
4532 if (vContextPtr != nullptr) {
4533 return vContextPtr->WasOpenedThisFrame(vKey);
4534 }
4535 return false;
4536}
4537
4538IGFD_C_API bool IGFD_WasOpenedThisFrame(ImGuiFileDialog* vContextPtr) {
4539 if (vContextPtr != nullptr) {
4540 return vContextPtr->WasOpenedThisFrame();
4541 }
4542
4543 return false;
4544}
4545
4546IGFD_C_API bool IGFD_IsKeyOpened(ImGuiFileDialog* vContextPtr, const char* vCurrentOpenedKey) {
4547 if (vContextPtr != nullptr) {
4548 return vContextPtr->IsOpened(vCurrentOpenedKey);
4549 }
4550
4551 return false;
4552}
4553
4554IGFD_C_API bool IGFD_IsOpened(ImGuiFileDialog* vContextPtr) {
4555 if (vContextPtr != nullptr) {
4556 return vContextPtr->IsOpened();
4557 }
4558
4559 return false;
4560}
4561
4562IGFD_C_API IGFD_Selection IGFD_GetSelection(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) {
4563 IGFD_Selection res = IGFD_Selection_Get();
4564 if (vContextPtr != nullptr) {
4565 auto sel = vContextPtr->GetSelection(vMode);
4566 if (!sel.empty()) {
4567 res.count = sel.size();
4568 res.table = new IGFD_Selection_Pair[res.count];
4569
4570 size_t idx = 0U;
4571 for (const auto& s : sel) {
4572 IGFD_Selection_Pair* pair = res.table + idx++;
4573
4574 // fileNameExt
4575 if (!s.first.empty()) {
4576 size_t siz = s.first.size() + 1U;
4577 pair->fileName = new char[siz];
4578#ifndef _MSC_VER
4579 strncpy(pair->fileName, s.first.c_str(), siz);
4580#else // _MSC_VER
4581 strncpy_s(pair->fileName, siz, s.first.c_str(), siz);
4582#endif // _MSC_VER
4583 pair->fileName[siz - 1U] = '\0';
4584 }
4585
4586 // filePathName
4587 if (!s.second.empty()) {
4588 size_t siz = s.second.size() + 1U;
4589 pair->filePathName = new char[siz];
4590#ifndef _MSC_VER
4591 strncpy(pair->filePathName, s.second.c_str(), siz);
4592#else // _MSC_VER
4593 strncpy_s(pair->filePathName, siz, s.second.c_str(), siz);
4594#endif // _MSC_VER
4595 pair->filePathName[siz - 1U] = '\0';
4596 }
4597 }
4598
4599 return res;
4600 }
4601 }
4602
4603 return res;
4604}
4605
4606IGFD_C_API char* IGFD_GetFilePathName(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) {
4607 char* res = nullptr;
4608
4609 if (vContextPtr != nullptr) {
4610 auto s = vContextPtr->GetFilePathName(vMode);
4611 if (!s.empty()) {
4612 size_t siz = s.size() + 1U;
4613 res = (char*)malloc(siz);
4614 if (res) {
4615#ifndef _MSC_VER
4616 strncpy(res, s.c_str(), siz);
4617#else // _MSC_VER
4618 strncpy_s(res, siz, s.c_str(), siz);
4619#endif // _MSC_VER
4620 res[siz - 1U] = '\0';
4621 }
4622 }
4623 }
4624
4625 return res;
4626}
4627
4628IGFD_C_API char* IGFD_GetCurrentFileName(ImGuiFileDialog* vContextPtr, IGFD_ResultMode vMode) {
4629 char* res = nullptr;
4630
4631 if (vContextPtr != nullptr) {
4632 auto s = vContextPtr->GetCurrentFileName(vMode);
4633 if (!s.empty()) {
4634 size_t siz = s.size() + 1U;
4635 res = (char*)malloc(siz);
4636 if (res) {
4637#ifndef _MSC_VER
4638 strncpy(res, s.c_str(), siz);
4639#else // _MSC_VER
4640 strncpy_s(res, siz, s.c_str(), siz);
4641#endif // _MSC_VER
4642 res[siz - 1U] = '\0';
4643 }
4644 }
4645 }
4646
4647 return res;
4648}
4649
4650IGFD_C_API char* IGFD_GetCurrentPath(ImGuiFileDialog* vContextPtr) {
4651 char* res = nullptr;
4652
4653 if (vContextPtr != nullptr) {
4654 auto s = vContextPtr->GetCurrentPath();
4655 if (!s.empty()) {
4656 size_t siz = s.size() + 1U;
4657 res = (char*)malloc(siz);
4658 if (res) {
4659#ifndef _MSC_VER
4660 strncpy(res, s.c_str(), siz);
4661#else // _MSC_VER
4662 strncpy_s(res, siz, s.c_str(), siz);
4663#endif // _MSC_VER
4664 res[siz - 1U] = '\0';
4665 }
4666 }
4667 }
4668
4669 return res;
4670}
4671
4672IGFD_C_API char* IGFD_GetCurrentFilter(ImGuiFileDialog* vContextPtr) {
4673 char* res = nullptr;
4674
4675 if (vContextPtr != nullptr) {
4676 auto s = vContextPtr->GetCurrentFilter();
4677 if (!s.empty()) {
4678 size_t siz = s.size() + 1U;
4679 res = (char*)malloc(siz);
4680 if (res) {
4681#ifndef _MSC_VER
4682 strncpy(res, s.c_str(), siz);
4683#else // _MSC_VER
4684 strncpy_s(res, siz, s.c_str(), siz);
4685#endif // _MSC_VER
4686 res[siz - 1U] = '\0';
4687 }
4688 }
4689 }
4690
4691 return res;
4692}
4693
4694IGFD_C_API void* IGFD_GetUserDatas(ImGuiFileDialog* vContextPtr) {
4695 if (vContextPtr != nullptr) {
4696 return vContextPtr->GetUserDatas();
4697 }
4698
4699 return nullptr;
4700}
4701
4702IGFD_C_API void IGFD_SetFileStyle(ImGuiFileDialog* vContextPtr, IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4 vColor, const char* vIcon,
4703 ImFont* vFont) //-V813
4704{
4705 if (vContextPtr != nullptr) {
4706 vContextPtr->SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont);
4707 }
4708}
4709
4710IGFD_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) {
4711 if (vContextPtr != nullptr) {
4712 vContextPtr->SetFileStyle(vFlags, vCriteria, ImVec4(vR, vG, vB, vA), vIcon, vFont);
4713 }
4714}
4715
4716IGFD_C_API bool IGFD_GetFileStyle(ImGuiFileDialog* vContextPtr, IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4* vOutColor, char** vOutIconText, ImFont** vOutFont) {
4717 if (vContextPtr != nullptr) {
4718 std::string icon;
4719 bool res = vContextPtr->GetFileStyle(vFlags, vCriteria, vOutColor, &icon, vOutFont);
4720 if (!icon.empty() && vOutIconText) {
4721 size_t siz = icon.size() + 1U;
4722 *vOutIconText = (char*)malloc(siz);
4723 if (*vOutIconText) {
4724#ifndef _MSC_VER
4725 strncpy(*vOutIconText, icon.c_str(), siz);
4726#else // _MSC_VER
4727 strncpy_s(*vOutIconText, siz, icon.c_str(), siz);
4728#endif // _MSC_VER
4729 (*vOutIconText)[siz - 1U] = '\0';
4730 }
4731 }
4732 return res;
4733 }
4734
4735 return false;
4736}
4737
4738IGFD_C_API void IGFD_ClearFilesStyle(ImGuiFileDialog* vContextPtr) {
4739 if (vContextPtr != nullptr) {
4740 vContextPtr->ClearFilesStyle();
4741 }
4742}
4743
4744IGFD_C_API void SetLocales(ImGuiFileDialog* vContextPtr, const int vCategory, const char* vBeginLocale, const char* vEndLocale) {
4745 if (vContextPtr != nullptr) {
4746 vContextPtr->SetLocales(vCategory, (vBeginLocale ? vBeginLocale : ""), (vEndLocale ? vEndLocale : ""));
4747 }
4748}
4749
4750#ifdef USE_EXPLORATION_BY_KEYS
4751IGFD_C_API void IGFD_SetFlashingAttenuationInSeconds(ImGuiFileDialog* vContextPtr, float vAttenValue) {
4752 if (vContextPtr != nullptr) {
4753 vContextPtr->SetFlashingAttenuationInSeconds(vAttenValue);
4754 }
4755}
4756#endif
4757
4758#ifdef USE_BOOKMARK
4759IGFD_C_API char* IGFD_SerializeBookmarks(ImGuiFileDialog* vContextPtr, bool vDontSerializeCodeBasedBookmarks) {
4760 char* res = nullptr;
4761
4762 if (vContextPtr != nullptr) {
4763 auto s = vContextPtr->SerializeBookmarks(vDontSerializeCodeBasedBookmarks);
4764 if (!s.empty()) {
4765 size_t siz = s.size() + 1U;
4766 res = (char*)malloc(siz);
4767 if (res) {
4768#ifndef _MSC_VER
4769 strncpy(res, s.c_str(), siz);
4770#else // _MSC_VER
4771 strncpy_s(res, siz, s.c_str(), siz);
4772#endif // _MSC_VER
4773 res[siz - 1U] = '\0';
4774 }
4775 }
4776 }
4777
4778 return res;
4779}
4780
4781IGFD_C_API void IGFD_DeserializeBookmarks(ImGuiFileDialog* vContextPtr, const char* vBookmarks) {
4782 if (vContextPtr != nullptr) {
4783 vContextPtr->DeserializeBookmarks(vBookmarks);
4784 }
4785}
4786
4787IGFD_C_API void IGFD_AddBookmark(ImGuiFileDialog* vContextPtr, const char* vBookMarkName, const char* vBookMarkPath) {
4788 if (vContextPtr != nullptr) {
4789 vContextPtr->AddBookmark(vBookMarkName, vBookMarkPath);
4790 }
4791}
4792
4793IGFD_C_API void IGFD_RemoveBookmark(ImGuiFileDialog* vContextPtr, const char* vBookMarkName) {
4794 if (vContextPtr != nullptr) {
4795 vContextPtr->RemoveBookmark(vBookMarkName);
4796 }
4797}
4798
4799#endif
4800
4801#ifdef USE_THUMBNAILS
4802IGFD_C_API void SetCreateThumbnailCallback(ImGuiFileDialog* vContextPtr, const IGFD_CreateThumbnailFun vCreateThumbnailFun) {
4803 if (vContextPtr != nullptr) {
4804 vContextPtr->SetCreateThumbnailCallback(vCreateThumbnailFun);
4805 }
4806}
4807
4808IGFD_C_API void SetDestroyThumbnailCallback(ImGuiFileDialog* vContextPtr, const IGFD_DestroyThumbnailFun vDestroyThumbnailFun) {
4809 if (vContextPtr != nullptr) {
4810 vContextPtr->SetDestroyThumbnailCallback(vDestroyThumbnailFun);
4811 }
4812}
4813
4814IGFD_C_API void ManageGPUThumbnails(ImGuiFileDialog* vContextPtr) {
4815 if (vContextPtr != nullptr) {
4816 vContextPtr->ManageGPUThumbnails();
4817 }
4818}
4819#endif // USE_THUMBNAILS
4820
4821//#pragma endregion
IGFD_C_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(void)
IGFD_C_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair *vSelection_Pair)
IGFD_C_API void IGFD_RemoveBookmark(ImGuiFileDialog *vContextPtr, const char *vBookMarkName)
IGFD_C_API IGFD_Selection IGFD_GetSelection(ImGuiFileDialog *vContextPtr, IGFD_ResultMode vMode)
IGFD_C_API void * IGFD_GetUserDatas(ImGuiFileDialog *vContextPtr)
IGFD_C_API char * IGFD_GetFilePathName(ImGuiFileDialog *vContextPtr, IGFD_ResultMode vMode)
IGFD_C_API char * IGFD_SerializeBookmarks(ImGuiFileDialog *vContextPtr, bool vDontSerializeCodeBasedBookmarks)
IGFD_C_API void IGFD_DeserializeBookmarks(ImGuiFileDialog *vContextPtr, const char *vBookmarks)
IGFD_C_API bool IGFD_IsOk(ImGuiFileDialog *vContextPtr)
IGFD_C_API ImGuiFileDialog * IGFD_Create(void)
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)
IGFD_C_API IGFD_FileDialog_Config IGFD_FileDialog_Config_Get()
IGFD_C_API void IGFD_ClearFilesStyle(ImGuiFileDialog *vContextPtr)
IGFD_C_API bool IGFD_IsKeyOpened(ImGuiFileDialog *vContextPtr, const char *vCurrentOpenedKey)
IGFD_C_API void IGFD_Destroy(ImGuiFileDialog *vContextPtr)
IGFD_C_API char * IGFD_GetCurrentPath(ImGuiFileDialog *vContextPtr)
IGFD_C_API void SetLocales(ImGuiFileDialog *vContextPtr, const int vCategory, const char *vBeginLocale, const char *vEndLocale)
IGFD_C_API IGFD_Selection IGFD_Selection_Get(void)
IGFD_C_API void IGFD_AddBookmark(ImGuiFileDialog *vContextPtr, const char *vBookMarkName, const char *vBookMarkPath)
IGFD_C_API char * IGFD_GetCurrentFileName(ImGuiFileDialog *vContextPtr, IGFD_ResultMode vMode)
IGFD_C_API void IGFD_CloseDialog(ImGuiFileDialog *vContextPtr)
IGFD_C_API void IGFD_SetFileStyle(ImGuiFileDialog *vContextPtr, IGFD_FileStyleFlags vFlags, const char *vCriteria, ImVec4 vColor, const char *vIcon, ImFont *vFont)
IGFD_C_API bool IGFD_IsOpened(ImGuiFileDialog *vContextPtr)
IGFD_C_API char * IGFD_GetCurrentFilter(ImGuiFileDialog *vContextPtr)
IGFD_C_API void IGFD_SetFlashingAttenuationInSeconds(ImGuiFileDialog *vContextPtr, float vAttenValue)
IGFD_C_API void IGFD_OpenDialog(ImGuiFileDialog *vContextPtr, const char *vKey, const char *vTitle, const char *vFilters, const IGFD_FileDialog_Config vConfig)
IGFD_C_API bool IGFD_WasOpenedThisFrame(ImGuiFileDialog *vContextPtr)
IGFD_C_API bool IGFD_DisplayDialog(ImGuiFileDialog *vContextPtr, const char *vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize)
IGFD_C_API void IGFD_Selection_DestroyContent(IGFD_Selection *vSelection)
IGFD_C_API bool IGFD_WasKeyOpenedThisFrame(ImGuiFileDialog *vContextPtr, const char *vKey)
IGFD_C_API bool IGFD_GetFileStyle(ImGuiFileDialog *vContextPtr, IGFD_FileStyleFlags vFlags, const char *vCriteria, ImVec4 *vOutColor, char **vOutIconText, ImFont **vOutFont)
int g
ImGuiContext * GImGui
Definition imgui.cc:1185
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition lz4.cc:146
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
size_t size(std::string_view utf8)
auto filter(ForwardRange &&range, Predicate pred)
Definition view.hh:538
constexpr auto end(const zstring_view &x)