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