diff options
| author | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
|---|---|---|
| committer | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
| commit | 5bc16063c29aa4d3d287ebd163ccdbcbf54c4f9f (patch) | |
| tree | c131f947a37b3af2d14d41e9eda098bdec2d061c /gearboy/platforms/desktop-shared/FileBrowser | |
| parent | 78a5f810b22f0d8cafa05f638b0cb2e889824859 (diff) | |
| download | cscg2022-gearboy-master.tar.gz cscg2022-gearboy-master.zip | |
Diffstat (limited to 'gearboy/platforms/desktop-shared/FileBrowser')
3 files changed, 2481 insertions, 0 deletions
diff --git a/gearboy/platforms/desktop-shared/FileBrowser/Dirent/dirent.h b/gearboy/platforms/desktop-shared/FileBrowser/Dirent/dirent.h new file mode 100644 index 00000000..55132b33 --- /dev/null +++ b/gearboy/platforms/desktop-shared/FileBrowser/Dirent/dirent.h @@ -0,0 +1,1160 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include <stdio.h> +#include <stdarg.h> +#include <wchar.h> +#include <string.h> +#include <stdlib.h> +#include <malloc.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir (const char *dirname); +static _WDIR *_wopendir (const wchar_t *dirname); + +static struct dirent *readdir (DIR *dirp); +static struct _wdirent *_wreaddir (_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir (DIR *dirp); +static int _wclosedir (_WDIR *dirp); + +static void rewinddir (DIR* dirp); +static void _wrewinddir (_WDIR* dirp); + +static int scandir (const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort (const struct dirent **a, const struct dirent **b); + +static int versionsort (const struct dirent **a, const struct dirent **b); + + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); + +static int dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count); + +static int dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, + const wchar_t *wcstr, + size_t count); + +static void dirent_set_errno (int error); + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR* +_wopendir( + const wchar_t *dirname) +{ + _WDIR *dirp; + DWORD n; + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); + if (!dirp) { + return NULL; + } + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, 0, NULL, NULL); +#else + /* WinRT */ + n = wcslen (dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); + if (dirp->patt == NULL) { + goto exit_closedir; + } + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, n, dirp->patt, NULL); + if (n <= 0) { + goto exit_closedir; + } +#else + /* WinRT */ + wcsncpy_s (dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first (dirp)) { + goto exit_closedir; + } + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir (dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent* +_wreaddir( + _WDIR *dirp) +{ + struct _wdirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) _wreaddir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, + struct _wdirent *entry, + struct _wdirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp); + if (datap) { + size_t n; + DWORD attr; + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct _wdirent); + + /* Set result address */ + *result = entry; + + } else { + + /* Return NULL to indicate end of directory */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir( + _WDIR *dirp) +{ + int ok; + if (dirp) { + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Release search pattern */ + free (dirp->patt); + + /* Release directory structure */ + free (dirp); + ok = /*success*/0; + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void +_wrewinddir( + _WDIR* dirp) +{ + if (dirp) { + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Open new search handle */ + dirent_first (dirp); + } +} + +/* Get first directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_first( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + DWORD error; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* a directory entry is now waiting in memory */ + datap = &dirp->data; + dirp->cached = 1; + + } else { + + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + datap = NULL; + + /* Set error code */ + error = GetLastError (); + switch (error) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno (EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno (ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno (ENOENT); + } + + } + return datap; +} + +/* + * Get next directory entry (internal). + * + * Returns + */ +static WIN32_FIND_DATAW* +dirent_next( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *p; + + /* Get next directory entry */ + if (dirp->cached != 0) { + + /* A valid directory entry already in memory */ + p = &dirp->data; + dirp->cached = 0; + + } else if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* Get the next directory entry from stream */ + if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { + /* Got a file */ + p = &dirp->data; + } else { + /* The very last entry has been processed or an error occurred */ + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + p = NULL; + } + + } else { + + /* End of directory stream reached */ + p = NULL; + + } + + return p; +} + +/* + * Open directory stream using plain old C-string. + */ +static DIR* +opendir( + const char *dirname) +{ + struct DIR *dirp; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + dirp = (DIR*) malloc (sizeof (struct DIR)); + if (!dirp) { + return NULL; + } + { + int error; + wchar_t wname[PATH_MAX + 1]; + size_t n; + + /* Convert directory name to wide-character string */ + error = dirent_mbstowcs_s( + &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); + if (error) { + /* + * Cannot convert file name to wide-character string. This + * occurs if the string contains invalid multi-byte sequences or + * the output buffer is too small to contain the resulting + * string. + */ + goto exit_free; + } + + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir (wname); + if (!dirp->wdirp) { + goto exit_free; + } + + } + + /* Success */ + return dirp; + + /* Failure */ +exit_free: + free (dirp); + return NULL; +} + +/* + * Read next directory entry. + */ +static struct dirent* +readdir( + DIR *dirp) +{ + struct dirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) readdir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, + struct dirent *entry, + struct dirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp->wdirp); + if (datap) { + size_t n; + int error; + + /* Attempt to convert file name to multi-byte string */ + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, + * then attempt to use old 8+3 file name. This allows traditional + * Unix-code to access some file names despite of unicode + * characters, although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file + * name unless the file system provides one. At least + * VirtualBox shared folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + DWORD attr; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct dirent); + + } else { + + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + + } + + /* Return pointer to directory entry */ + *result = entry; + + } else { + + /* No more directory entries */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream. + */ +static int +closedir( + DIR *dirp) +{ + int ok; + if (dirp) { + + /* Close wide-character directory stream */ + ok = _wclosedir (dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free (dirp); + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream to beginning. + */ +static void +rewinddir( + DIR* dirp) +{ + /* Rewind wide-character string directory stream */ + _wrewinddir (dirp->wdirp); +} + +/* + * Scan directory for entries. + */ +static int +scandir( + const char *dirname, + struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + const size_t init_size = 1; + DIR *dir = NULL; + struct dirent *entry; + struct dirent *tmp = NULL; + size_t i; + int result = 0; + + /* Open directory stream */ + dir = opendir (dirname); + if (dir) { + + /* Read directory entries to memory */ + while (1) { + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + void *p; + size_t num_entries; + + /* Compute number of entries in the enlarged pointer table */ + if (size < init_size) { + /* Allocate initial pointer table */ + num_entries = init_size; + } else { + /* Double the size */ + num_entries = size * 2; + } + + /* Allocate first pointer table or enlarge existing table */ + p = realloc (files, sizeof (void*) * num_entries); + if (p != NULL) { + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } else { + /* Out of memory */ + result = -1; + break; + } + + } + + /* Allocate room for temporary directory entry */ + if (tmp == NULL) { + tmp = (struct dirent*) malloc (sizeof (struct dirent)); + if (tmp == NULL) { + /* Cannot allocate temporary directory entry */ + result = -1; + break; + } + } + + /* Read directory entry to temporary area */ + if (readdir_r (dir, tmp, &entry) == /*OK*/0) { + + /* Did we get an entry? */ + if (entry != NULL) { + int pass; + + /* Determine whether to include the entry in result */ + if (filter) { + /* Let the filter function decide */ + pass = filter (tmp); + } else { + /* No filter function, include everything */ + pass = 1; + } + + if (pass) { + /* Store the temporary entry to pointer table */ + files[size++] = tmp; + tmp = NULL; + + /* Keep up with the number of files */ + result++; + } + + } else { + + /* + * End of directory stream reached => sort entries and + * exit. + */ + qsort (files, size, sizeof (void*), + (int (*) (const void*, const void*)) compare); + break; + + } + + } else { + /* Error reading directory entry */ + result = /*Error*/ -1; + break; + } + + } + + } else { + /* Cannot open directory */ + result = /*Error*/ -1; + } + + /* Release temporary directory entry */ + free (tmp); + + /* Release allocated memory on error */ + if (result < 0) { + for (i = 0; i < size; i++) { + free (files[i]); + } + free (files); + files = NULL; + } + + /* Close directory stream */ + if (dir) { + closedir (dir); + } + + /* Pass pointer table to caller */ + if (namelist) { + *namelist = files; + } + return result; +} + +/* Alphabetical sorting */ +static int +alphasort( + const struct dirent **a, const struct dirent **b) +{ + return strcoll ((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort( + const struct dirent **a, const struct dirent **b) +{ + /* FIXME: implement strverscmp and use that */ + return alphasort (a, b); +} + +/* Convert multi-byte string to wide character string */ +static int +dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to wide-character string (or count characters) */ + n = mbstowcs (wcstr, mbstr, sizeInWords); + if (!wcstr || n < count) { + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) { + n = sizeInWords - 1; + } + wcstr[n] = 0; + } + + /* Length of resulting multi-byte string WITH zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Could not convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Convert wide-character string to multi-byte string */ +static int +dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, /* max size of mbstr */ + const wchar_t *wcstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to multi-byte string (or count the number of bytes needed) */ + n = wcstombs (mbstr, wcstr, sizeInBytes); + if (!mbstr || n < count) { + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Cannot convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Set errno variable */ +static void +dirent_set_errno( + int error) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 and later */ + _set_errno (error); + +#else + + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + +#endif +} + + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/
\ No newline at end of file diff --git a/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.cpp b/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.cpp new file mode 100644 index 00000000..768fcf5e --- /dev/null +++ b/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.cpp @@ -0,0 +1,1198 @@ +#include "ImGuiFileBrowser.h"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "../imgui/imgui_internal.h"
+
+#include <iostream>
+#include <functional>
+#include <climits>
+#include <string.h>
+#include <sstream>
+#include <cwchar>
+#include <cctype>
+#include <algorithm>
+#include <cmath>
+#if defined (WIN32) || defined (_WIN32) || defined (__WIN32)
+#define OSWIN
+#ifndef NOMINMAX
+ #define NOMINMAX
+#endif
+#include "Dirent/dirent.h"
+#include <windows.h>
+#else
+#include <dirent.h>
+#endif // defined (WIN32) || defined (_WIN32)
+
+namespace imgui_addons
+{
+ ImGuiFileBrowser::ImGuiFileBrowser()
+ {
+ filter_mode = FilterMode_Files | FilterMode_Dirs;
+
+ show_inputbar_combobox = false;
+ validate_file = false;
+ show_hidden = false;
+ is_dir = false;
+ filter_dirty = true;
+ is_appearing = true;
+
+ col_items_limit = 12;
+ selected_idx = -1;
+ selected_ext_idx = 0;
+ ext_box_width = -1.0f;
+ col_width = 280.0f;
+ min_size = ImVec2(500,300);
+
+ invfile_modal_id = "Invalid File!";
+ repfile_modal_id = "Replace File?";
+ selected_fn = "";
+ selected_path = "";
+
+ #ifdef OSWIN
+ current_path = "./";
+ #else
+ initCurrentPath();
+ #endif
+ }
+
+ ImGuiFileBrowser::~ImGuiFileBrowser()
+ {
+
+ }
+
+ void ImGuiFileBrowser::clearFileList()
+ {
+ //Clear pointer references to subdirs and subfiles
+ filtered_dirs.clear();
+ filtered_files.clear();
+ inputcb_filter_files.clear();
+
+ //Now clear subdirs and subfiles
+ subdirs.clear();
+ subfiles.clear();
+ filter_dirty = true;
+ selected_idx = -1;
+ }
+
+ void ImGuiFileBrowser::closeDialog()
+ {
+ if (is_open_)
+ *is_open_ = false;
+ valid_types = "";
+ valid_exts.clear();
+ selected_ext_idx = 0;
+ selected_idx = -1;
+
+ input_fn[0] = '\0'; //Hide any text in Input bar for the next time save dialog is opened.
+ filter.Clear(); //Clear Filter for the next time open dialog is called.
+
+ show_inputbar_combobox = false;
+ validate_file = false;
+ show_hidden = false;
+ is_dir = false;
+ filter_dirty = true;
+ is_appearing = true;
+
+ //Clear pointer references to subdirs and subfiles
+ filtered_dirs.clear();
+ filtered_files.clear();
+ inputcb_filter_files.clear();
+
+ //Now clear subdirs and subfiles
+ subdirs.clear();
+ subfiles.clear();
+
+ ImGui::CloseCurrentPopup();
+ }
+
+ bool ImGuiFileBrowser::showFileDialog(const std::string& label, const DialogMode mode, const ImVec2& sz_xy, const std::string& valid_types, bool* is_open)
+ {
+ is_open_ = is_open;
+ dialog_mode = mode;
+ ImGuiIO& io = ImGui::GetIO();
+ max_size.x = io.DisplaySize.x;
+ max_size.y = io.DisplaySize.y;
+ ImGui::SetNextWindowSizeConstraints(min_size, max_size);
+ ImGui::SetNextWindowPos(io.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f,0.5f));
+ ImGui::SetNextWindowSize(ImVec2(std::max(sz_xy.x, min_size.x), std::max(sz_xy.y, min_size.y)), ImGuiCond_Appearing);
+
+ //Set Proper Filter Mode.
+ if(mode == DialogMode::SELECT)
+ filter_mode = FilterMode_Dirs;
+ else
+ filter_mode = FilterMode_Files | FilterMode_Dirs;
+
+ if (ImGui::BeginPopupModal(label.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
+ {
+ bool show_error = false;
+ if (is_open_)
+ *is_open_ = true;
+
+ // If this is the initial run, read current directory and load data once.
+ if(is_appearing)
+ {
+ selected_fn.clear();
+ selected_path.clear();
+ if(mode != DialogMode::SELECT)
+ {
+ this->valid_types = valid_types;
+ setValidExtTypes(valid_types);
+ }
+
+ /* If current path is empty (can happen on Windows if user closes dialog while inside MyComputer.
+ * Since this is a virtual folder, path would be empty) load the drives on Windows else initialize the current path on Unix.
+ */
+ if(current_path.empty())
+ {
+ #ifdef OSWIN
+ show_error |= !(loadWindowsDrives());
+ #else
+ initCurrentPath();
+ show_error |= !(readDIR(current_path));
+ #endif // OSWIN
+ }
+ else
+ show_error |= !(readDIR(current_path));
+ is_appearing = false;
+ }
+
+ show_error |= renderNavAndSearchBarRegion();
+ show_error |= renderFileListRegion();
+ show_error |= renderButtonsAndCheckboxRegion();
+
+ if (*is_open)
+ show_error |= renderInputTextAndExtRegion();
+
+ if(validate_file)
+ {
+ validate_file = false;
+ bool check = validateFile();
+
+ if(!check && dialog_mode == DialogMode::OPEN)
+ {
+ ImGui::OpenPopup(invfile_modal_id.c_str());
+ selected_fn.clear();
+ selected_path.clear();
+ }
+
+ else if(!check && dialog_mode == DialogMode::SAVE)
+ ImGui::OpenPopup(repfile_modal_id.c_str());
+
+ else if(!check && dialog_mode == DialogMode::SELECT)
+ {
+ selected_fn.clear();
+ selected_path.clear();
+ show_error = true;
+ error_title = "Invalid Directory!";
+ error_msg = "Invalid Directory Selected. Please make sure the directory exists.";
+ }
+
+ //If selected file passes through validation check, set path to the file and close file dialog
+ if(check)
+ {
+ selected_path = current_path + selected_fn;
+
+ //Add a trailing "/" to emphasize its a directory not a file. If you want just the dir name it's accessible through "selected_fn"
+ if(dialog_mode == DialogMode::SELECT)
+ selected_path += "/";
+ closeDialog();
+ }
+ }
+
+ // We don't need to check as the modals will only be shown if OpenPopup is called
+ showInvalidFileModal();
+ if(showReplaceFileModal())
+ closeDialog();
+
+ //Show Error Modal if there was an error opening any directory
+ if(show_error)
+ ImGui::OpenPopup(error_title.c_str());
+ showErrorModal();
+
+ ImGui::EndPopup();
+ return (!selected_fn.empty() && !selected_path.empty());
+ }
+ else
+ return false;
+ }
+
+ bool ImGuiFileBrowser::renderNavAndSearchBarRegion()
+ {
+ ImGuiStyle& style = ImGui::GetStyle();
+ bool show_error = false;
+ float frame_height = ImGui::GetFrameHeight();
+ float list_item_height = GImGui->FontSize + style.ItemSpacing.y;
+
+ ImVec2 pw_content_size = ImGui::GetWindowSize() - style.WindowPadding * 2.0;
+ ImVec2 sw_size = ImVec2(ImGui::CalcTextSize("Random").x + 140, style.WindowPadding.y * 2.0f + frame_height);
+ ImVec2 sw_content_size = sw_size - style.WindowPadding * 2.0;
+ ImVec2 nw_size = ImVec2(pw_content_size.x - style.ItemSpacing.x - sw_size.x, sw_size.y);
+
+
+ ImGui::BeginChild("##NavigationWindow", nw_size, true, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
+ for(std::size_t i = 0; i < current_dirlist.size(); i++)
+ {
+ if( ImGui::Button(current_dirlist[i].c_str()) )
+ {
+ //If last button clicked, nothing happens
+ if(i != current_dirlist.size() - 1)
+ show_error |= !(onNavigationButtonClick((int)i));
+ }
+
+ //Draw Arrow Buttons
+ if(i != current_dirlist.size() - 1)
+ {
+ ImGui::SameLine(0,0);
+ float next_label_width = ImGui::CalcTextSize(current_dirlist[i+1].c_str()).x;
+
+ if(i+1 < current_dirlist.size() - 1)
+ next_label_width += frame_height + ImGui::CalcTextSize(">>").x;
+
+ if(ImGui::GetCursorPosX() + next_label_width >= (nw_size.x - style.WindowPadding.x * 3.0))
+ {
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.01f));
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
+
+ //Render a drop down of navigation items on button press
+ if(ImGui::Button(">>"))
+ ImGui::OpenPopup("##NavBarDropboxPopup");
+ if(ImGui::BeginPopup("##NavBarDropboxPopup"))
+ {
+ ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.125f, 0.125f, 0.125f, 1.0f));
+ if(ImGui::ListBoxHeader("##NavBarDropBox", ImVec2(0, list_item_height* 5)))
+ {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
+ for(std::size_t j = i+1; j < current_dirlist.size(); j++)
+ {
+ if(ImGui::Selectable(current_dirlist[j].c_str(), false) && j != current_dirlist.size() - 1)
+ {
+ show_error |= !(onNavigationButtonClick((int)j));
+ ImGui::CloseCurrentPopup();
+ }
+ }
+ ImGui::PopStyleColor();
+ ImGui::ListBoxFooter();
+ }
+ ImGui::PopStyleColor();
+ ImGui::EndPopup();
+ }
+ ImGui::PopStyleColor(2);
+ break;
+ }
+ else
+ {
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.01f));
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
+ ImGui::ArrowButtonEx("##Right", ImGuiDir_Right, ImVec2(frame_height, frame_height), ImGuiButtonFlags_Disabled);
+ ImGui::SameLine(0,0);
+ ImGui::PopStyleColor(2);
+ }
+ }
+ }
+ ImGui::PopStyleColor();
+ ImGui::EndChild();
+
+ ImGui::SameLine();
+ ImGui::BeginChild("##SearchWindow", sw_size, true, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
+
+ //Render Search/Filter bar
+ float marker_width = ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x;
+ if(filter.Draw("##SearchBar", sw_content_size.x - marker_width) || filter_dirty )
+ filterFiles(filter_mode);
+
+ //If filter bar was focused clear selection
+ if(ImGui::GetFocusID() == ImGui::GetID("##SearchBar"))
+ selected_idx = -1;
+
+ ImGui::SameLine();
+ showHelpMarker("Filter (inc, -exc)");
+
+ ImGui::EndChild();
+ return show_error;
+ }
+
+ bool ImGuiFileBrowser::renderFileListRegion()
+ {
+ ImGuiStyle& style = ImGui::GetStyle();
+ ImVec2 pw_size = ImGui::GetWindowSize();
+ bool show_error = false;
+ float list_item_height = ImGui::CalcTextSize("").y + style.ItemSpacing.y;
+ float input_bar_ypos = pw_size.y - ImGui::GetFrameHeightWithSpacing() * 2.5f - style.WindowPadding.y;
+ float window_height = input_bar_ypos - ImGui::GetCursorPosY() - style.ItemSpacing.y;
+ float window_content_height = window_height - style.WindowPadding.y * 2.0f;
+ float min_content_size = pw_size.x - style.WindowPadding.x * 4.0f;
+
+ if(window_content_height <= 0.0f)
+ return show_error;
+
+ //Reinitialize the limit on number of selectables in one column based on height
+ col_items_limit = (int)std::max(1.0f, window_content_height/list_item_height);
+ int num_cols = (int)std::max(1.0f, std::ceil(static_cast<float>(filtered_dirs.size() + filtered_files.size()) / col_items_limit));
+
+ //Limitation by ImGUI in 1.75. If columns are greater than 64 readjust the limit on items per column and recalculate number of columns
+ if(num_cols > 64)
+ {
+ int exceed_items_amount = (num_cols - 64) * col_items_limit;
+ col_items_limit += (int)std::ceil(exceed_items_amount/64.0);
+ num_cols = (int)std::max(1.0f, std::ceil(static_cast<float>(filtered_dirs.size() + filtered_files.size()) / col_items_limit));
+ }
+
+ float content_width = num_cols * col_width;
+ if(content_width < min_content_size)
+ content_width = 0;
+
+ ImGui::SetNextWindowContentSize(ImVec2(content_width, 0));
+ ImGui::BeginChild("##ScrollingRegion", ImVec2(0, window_height), true, ImGuiWindowFlags_HorizontalScrollbar);
+ ImGui::Columns(num_cols);
+
+ //Output directories in yellow
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
+ int items = 0;
+ for (std::size_t i = 0; i < filtered_dirs.size(); i++)
+ {
+ if(!filtered_dirs[i].is_hidden || show_hidden)
+ {
+ items++;
+ if(ImGui::Selectable(filtered_dirs[i].name.c_str(), selected_idx == (int)i && is_dir, ImGuiSelectableFlags_AllowDoubleClick))
+ {
+ selected_idx = (int)i;
+ is_dir = true;
+
+ // If dialog mode is SELECT then copy the selected dir name to the input text bar
+ if(dialog_mode == DialogMode::SELECT)
+ strcpy(input_fn, filtered_dirs[i].name.c_str());
+
+ if(ImGui::IsMouseDoubleClicked(0))
+ {
+ show_error |= !(onDirClick((int)i));
+ break;
+ }
+ }
+ if( (items) % col_items_limit == 0)
+ ImGui::NextColumn();
+ }
+ }
+ ImGui::PopStyleColor(1);
+
+ //Output files
+ for (std::size_t i = 0; i < filtered_files.size(); i++)
+ {
+ if(!filtered_files[i].is_hidden || show_hidden)
+ {
+ items++;
+ if(ImGui::Selectable(filtered_files[i].name.c_str(), selected_idx == (int)i && !is_dir, ImGuiSelectableFlags_AllowDoubleClick))
+ {
+ // unused: int len = filtered_files[i]->name.length();
+ selected_idx = (int)i;
+ is_dir = false;
+
+ // If dialog mode is OPEN/SAVE then copy the selected file name to the input text bar
+ strcpy(input_fn, filtered_files[i].name.c_str());
+
+ if(ImGui::IsMouseDoubleClicked(0))
+ {
+ selected_fn = filtered_files[i].name;
+ validate_file = true;
+ }
+ }
+ if( (items) % col_items_limit == 0)
+ ImGui::NextColumn();
+ }
+ }
+ ImGui::Columns(1);
+ ImGui::EndChild();
+
+ return show_error;
+ }
+
+ bool ImGuiFileBrowser::renderInputTextAndExtRegion()
+ {
+ std::string label = (dialog_mode == DialogMode::SAVE) ? "Save As:" : "Open:";
+ ImGuiStyle& style = ImGui::GetStyle();
+ // unused: ImGuiIO& io = ImGui::GetIO();
+
+ ImVec2 pw_pos = ImGui::GetWindowPos();
+ ImVec2 pw_content_sz = ImGui::GetWindowSize() - style.WindowPadding * 2.0;
+ ImVec2 cursor_pos = ImGui::GetCursorPos();
+
+ if(ext_box_width < 0.0)
+ ext_box_width = ImGui::CalcTextSize(".abc").x + 100;
+ float label_width = ImGui::CalcTextSize(label.c_str()).x + style.ItemSpacing.x;
+ float frame_height_spacing = ImGui::GetFrameHeightWithSpacing();
+ float input_bar_width = pw_content_sz.x - label_width;
+ if(dialog_mode != DialogMode::SELECT)
+ input_bar_width -= (ext_box_width + style.ItemSpacing.x);
+
+ bool show_error = false;
+ ImGui::SetCursorPosY(pw_content_sz.y - frame_height_spacing * 2.0f);
+
+ //Render Input Text Bar label
+ ImGui::Text("%s", label.c_str());
+ ImGui::SameLine();
+
+ //Render Input Text Bar
+ input_combobox_pos = ImVec2(pw_pos + ImGui::GetCursorPos());
+ input_combobox_sz = ImVec2(input_bar_width, 0);
+ ImGui::PushItemWidth(input_bar_width);
+ if(ImGui::InputTextWithHint("##FileNameInput", "Type a name...", &input_fn[0], 256, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
+ {
+ if(strlen(input_fn) > 0)
+ {
+ selected_fn = std::string(input_fn);
+ validate_file = true;
+ }
+ }
+ ImGui::PopItemWidth();
+
+ //If input bar was focused clear selection
+ if(ImGui::IsItemEdited())
+ selected_idx = -1;
+
+ // If Input Bar is edited show a list of files or dirs matching the input text.
+ if(ImGui::IsItemEdited() || ImGui::IsItemActivated())
+ {
+ //If dialog_mode is OPEN/SAVE then filter from list of files..
+ if(dialog_mode == DialogMode::OPEN || dialog_mode == DialogMode::SAVE)
+ {
+ inputcb_filter_files.clear();
+ for(std::size_t i = 0; i < subfiles.size(); i++)
+ {
+ if(ImStristr(subfiles[i].name.c_str(), nullptr, input_fn, nullptr) != nullptr)
+ inputcb_filter_files.push_back(std::ref(subfiles[i].name));
+ }
+ }
+
+ //If dialog_mode == SELECT then filter from list of directories
+ else if(dialog_mode == DialogMode::SELECT)
+ {
+ inputcb_filter_files.clear();
+ for(std::size_t i = 0; i < subdirs.size(); i++)
+ {
+ if(ImStristr(subdirs[i].name.c_str(), nullptr, input_fn, nullptr) != nullptr)
+ inputcb_filter_files.push_back(std::ref(subdirs[i].name));
+ }
+ }
+
+ //If filtered list has any items show dropdown
+ if(inputcb_filter_files.size() > 0)
+ show_inputbar_combobox = true;
+ else
+ show_inputbar_combobox = false;
+ }
+
+ //Render Extensions and File Types DropDown
+ if(dialog_mode != DialogMode::SELECT)
+ {
+ ImGui::SameLine();
+ renderExtBox();
+ }
+
+ //Render a Drop Down of files/dirs (depending on mode) that have matching characters as the input text only.
+ show_error |= renderInputComboBox();
+
+ ImGui::SetCursorPos(cursor_pos);
+ return show_error;
+ }
+
+ bool ImGuiFileBrowser::renderButtonsAndCheckboxRegion()
+ {
+ ImVec2 pw_size = ImGui::GetWindowSize();
+ ImGuiStyle& style = ImGui::GetStyle();
+ bool show_error = false;
+ float frame_height = ImGui::GetFrameHeight();
+ float frame_height_spacing = ImGui::GetFrameHeightWithSpacing();
+ float opensave_btn_width = getButtonSize("Open").x; // Since both Open/Save are 4 characters long, width gonna be same.
+ float selcan_btn_width = getButtonSize("Cancel").x; // Since both Cacnel/Select have same number of characters, so same width.
+ float buttons_xpos;
+
+ if (dialog_mode == DialogMode::SELECT)
+ buttons_xpos = pw_size.x - opensave_btn_width - (2.0f * selcan_btn_width) - ( 2.0f * style.ItemSpacing.x) - style.WindowPadding.x;
+ else
+ buttons_xpos = pw_size.x - opensave_btn_width - selcan_btn_width - style.ItemSpacing.x - style.WindowPadding.x;
+
+ ImGui::SetCursorPosY(pw_size.y - frame_height_spacing - style.WindowPadding.y);
+
+ //Render Checkbox
+ float label_width = ImGui::CalcTextSize("Show Hidden Files and Folders").x + ImGui::GetCursorPosX() + frame_height;
+ bool show_marker = (label_width >= buttons_xpos);
+ ImGui::Checkbox( (show_marker) ? "##showHiddenFiles" : "Show Hidden Files and Folders", &show_hidden);
+ if(show_marker)
+ {
+ ImGui::SameLine();
+ showHelpMarker("Show Hidden Files and Folders");
+ }
+
+ //Render an Open Button (in OPEN/SELECT dialog_mode) or Open/Save depending on what's selected in SAVE dialog_mode
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(buttons_xpos);
+ if(dialog_mode == DialogMode::SAVE)
+ {
+ // If directory selected and Input Text Bar doesn't have focus, render Open Button
+ if(selected_idx != -1 && is_dir && ImGui::GetFocusID() != ImGui::GetID("##FileNameInput"))
+ {
+ if (ImGui::Button("Open"))
+ show_error |= !(onDirClick(selected_idx));
+ }
+ else if (ImGui::Button("Save") && strlen(input_fn) > 0)
+ {
+ selected_fn = std::string(input_fn);
+ validate_file = true;
+ }
+ }
+ else
+ {
+ if (ImGui::Button("Open"))
+ {
+ //It's possible for both to be true at once (user selected directory but input bar has some text. In this case we chose to open the directory instead of opening the file.
+ //Also note that we don't need to access the selected file through "selected_idx" since the if a file is selected, input bar will get populated with that name.
+ if(selected_idx >= 0 && is_dir)
+ show_error |= !(onDirClick(selected_idx));
+ else if(strlen(input_fn) > 0)
+ {
+ selected_fn = std::string(input_fn);
+ validate_file = true;
+ }
+ }
+
+ //Render Select Button if in SELECT Mode
+ if(dialog_mode == DialogMode::SELECT)
+ {
+ //Render Select Button
+ ImGui::SameLine();
+ if (ImGui::Button("Select"))
+ {
+ if(strlen(input_fn) > 0)
+ {
+ selected_fn = std::string(input_fn);
+ validate_file = true;
+ }
+ }
+ }
+ }
+
+ //Render Cancel Button
+ ImGui::SameLine();
+ if (ImGui::Button("Cancel"))
+ closeDialog();
+
+ return show_error;
+ }
+
+ bool ImGuiFileBrowser::renderInputComboBox()
+ {
+ bool show_error = false;
+ ImGuiStyle& style = ImGui::GetStyle();
+ ImGuiID input_id = ImGui::GetID("##FileNameInput");
+ ImGuiID focus_scope_id = ImGui::GetID("##InputBarComboBoxListScope");
+ float frame_height = ImGui::GetFrameHeight();
+
+ input_combobox_sz.y = std::min((inputcb_filter_files.size() + 1) * frame_height + style.WindowPadding.y * 2.0f,
+ 8 * ImGui::GetFrameHeight() + style.WindowPadding.y * 2.0f);
+
+ if(show_inputbar_combobox && ( ImGui::GetFocusScopeID() == focus_scope_id || ImGui::GetCurrentContext()->ActiveIdIsAlive == input_id ))
+ {
+ ImGuiWindowFlags popupFlags = ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoFocusOnAppearing |
+ ImGuiWindowFlags_NoScrollbar |
+ ImGuiWindowFlags_NoSavedSettings;
+
+
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
+ ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.125f, 0.125f, 0.125f, 1.0f));
+ ImGui::SetNextWindowBgAlpha(1.0);
+ ImGui::SetNextWindowPos(input_combobox_pos + ImVec2(0, ImGui::GetFrameHeightWithSpacing()));
+ ImGui::PushClipRect(ImVec2(0,0), ImGui::GetIO().DisplaySize, false);
+
+ ImGui::BeginChild("##InputBarComboBox", input_combobox_sz, true, popupFlags);
+
+ ImVec2 listbox_size = input_combobox_sz - ImGui::GetStyle().WindowPadding * 2.0f;
+ if(ImGui::ListBoxHeader("##InputBarComboBoxList", listbox_size))
+ {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
+ ImGui::PushFocusScope(focus_scope_id);
+ for(auto& element : inputcb_filter_files)
+ {
+ if(ImGui::Selectable(element.get().c_str(), false, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick))
+ {
+ if(element.get().size() > 256)
+ {
+ error_title = "Error!";
+ error_msg = "Selected File Name is longer than 256 characters.";
+ show_error = true;
+ }
+ else
+ {
+ strcpy(input_fn, element.get().c_str());
+ show_inputbar_combobox = false;
+ }
+ }
+ }
+ ImGui::PopFocusScope();
+ ImGui::PopStyleColor(1);
+ ImGui::ListBoxFooter();
+ }
+ ImGui::EndChild();
+ ImGui::PopStyleColor(2);
+ ImGui::PopClipRect();
+ }
+ return show_error;
+ }
+
+ void ImGuiFileBrowser::renderExtBox()
+ {
+ ImGui::PushItemWidth(ext_box_width);
+ if(ImGui::BeginCombo("##FileTypes", valid_exts[selected_ext_idx].c_str()))
+ {
+ for(std::size_t i = 0; i < valid_exts.size(); i++)
+ {
+ if(ImGui::Selectable(valid_exts[i].c_str(), selected_ext_idx == (int)i))
+ {
+ selected_ext_idx = (int)i;
+ if(dialog_mode == DialogMode::SAVE)
+ {
+ std::string name(input_fn);
+ size_t idx = name.find_last_of(".");
+ if(idx == std::string::npos)
+ idx = strlen(input_fn);
+ for(std::size_t j = 0; j < valid_exts[selected_ext_idx].size(); j++)
+ input_fn[idx++] = valid_exts[selected_ext_idx][j];
+ input_fn[idx++] = '\0';
+ }
+ else
+ filterFiles(FilterMode_Files);
+ }
+ }
+ ImGui::EndCombo();
+ }
+ ext = valid_exts[selected_ext_idx];
+ ImGui::PopItemWidth();
+ }
+
+ bool ImGuiFileBrowser::onNavigationButtonClick(int idx)
+ {
+ std::string new_path(current_path);
+
+ //First Button corresponds to virtual folder Computer which lists all logical drives (hard disks and removables) and "/" on Unix
+ if(idx == 0)
+ {
+ #ifdef OSWIN
+ if(!loadWindowsDrives())
+ return false;
+ current_path.clear();
+ current_dirlist.clear();
+ current_dirlist.push_back("Computer");
+ return true;
+ #else
+ new_path = "/";
+ #endif // OSWIN
+ }
+ else
+ {
+ #ifdef OSWIN
+ //Clicked on a drive letter?
+ if(idx == 1)
+ new_path = current_path.substr(0, 3);
+ else
+ new_path = current_path.substr(0, current_path.find("/" + current_dirlist[idx]) + current_dirlist[idx].length() + 2 );
+ #else
+ //find returns 0 based indices and substr takes length of chars thus + 1. Another +1 to include trailing "/"
+ new_path = current_path.substr(0, current_path.find("/" + current_dirlist[idx]) + current_dirlist[idx].length() + 2 );
+ #endif
+ }
+
+ if(readDIR(new_path))
+ {
+ current_dirlist.erase(current_dirlist.begin()+idx+1, current_dirlist.end());
+ current_path = new_path;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ bool ImGuiFileBrowser::onDirClick(int idx)
+ {
+ std::string name;
+ std::string new_path(current_path);
+ bool drives_shown = false;
+
+ #ifdef OSWIN
+ drives_shown = (current_dirlist.size() == 1 && current_dirlist.back() == "Computer");
+ #endif // OSWIN
+
+ name = filtered_dirs[idx].name;
+
+ if(name == "..")
+ {
+ new_path.pop_back(); // Remove trailing '/'
+ new_path = new_path.substr(0, new_path.find_last_of('/') + 1); // Also include a trailing '/'
+ }
+ else
+ {
+ //Remember we displayed drives on Windows as *Local/Removable Disk: X* hence we need last char only
+ if(drives_shown)
+ name = std::string(1, name.back()) + ":";
+ new_path += name + "/";
+ }
+
+ if(readDIR(new_path))
+ {
+ if(name == "..")
+ current_dirlist.pop_back();
+ else
+ current_dirlist.push_back(name);
+
+ current_path = new_path;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ bool ImGuiFileBrowser::readDIR(std::string pathdir)
+ {
+ DIR* dir;
+ struct dirent *ent;
+
+ /* If the current directory doesn't exist, and we are opening the dialog for the first time, reset to defaults to avoid looping of showing error modal.
+ * An example case is when user closes the dialog in a folder. Then deletes the folder outside. On reopening the dialog the current path (previous) would be invalid.
+ */
+ dir = opendir(pathdir.c_str());
+ if(dir == nullptr && is_appearing)
+ {
+ current_dirlist.clear();
+ #ifdef OSWIN
+ current_path = pathdir = "./";
+ #else
+ initCurrentPath();
+ pathdir = current_path;
+ #endif // OSWIN
+
+ dir = opendir(pathdir.c_str());
+ }
+
+ if (dir != nullptr)
+ {
+ #ifdef OSWIN
+ // If we are on Windows and current path is relative then get absolute path from dirent structure
+ if(current_dirlist.empty() && pathdir == "./")
+ {
+ const wchar_t* absolute_path = dir->wdirp->patt;
+ std::string current_directory = wStringToString(absolute_path);
+ std::replace(current_directory.begin(), current_directory.end(), '\\', '/');
+
+ //Remove trailing "*" returned by ** dir->wdirp->patt **
+ current_directory.pop_back();
+ current_path = current_directory;
+
+ //Create a vector of each directory in the file path for the filepath bar. Not Necessary for linux as starting directory is "/"
+ parsePathTabs(current_path);
+ }
+ #endif // OSWIN
+
+ // store all the files and directories within directory and clear previous entries
+ clearFileList();
+ while ((ent = readdir (dir)) != nullptr)
+ {
+ bool is_hidden = false;
+ std::string name(ent->d_name);
+
+ //Ignore current directory
+ if(name == ".")
+ continue;
+
+ //Somehow there is a '..' present in root directory in linux.
+ #ifndef OSWIN
+ if(name == ".." && pathdir == "/")
+ continue;
+ #endif // OSWIN
+
+ if(name != "..")
+ {
+ #ifdef OSWIN
+ std::string dir = pathdir + std::string(ent->d_name);
+ // IF system file skip it...
+ if (FILE_ATTRIBUTE_SYSTEM & GetFileAttributesA(dir.c_str()))
+ continue;
+ if (FILE_ATTRIBUTE_HIDDEN & GetFileAttributesA(dir.c_str()))
+ is_hidden = true;
+ #else
+ if(name[0] == '.')
+ is_hidden = true;
+ #endif // OSWIN
+ }
+ //Store directories and files in separate vectors
+ if(ent->d_type == DT_DIR)
+ subdirs.push_back(Info(name, is_hidden));
+ else if(ent->d_type == DT_REG && dialog_mode != DialogMode::SELECT)
+ subfiles.push_back(Info(name, is_hidden));
+
+ #ifndef OSWIN
+ else if(ent->d_type == DT_LNK)
+ {
+ subdirs.push_back(Info(name, is_hidden));
+ }
+ #endif // OSWIN
+ }
+ closedir (dir);
+ std::sort(subdirs.begin(), subdirs.end(), alphaSortComparator);
+ std::sort(subfiles.begin(), subfiles.end(), alphaSortComparator);
+
+ //Initialize Filtered dirs and files
+ filterFiles(filter_mode);
+ }
+ else
+ {
+ error_title = "Error!";
+ error_msg = "Error opening directory! Make sure the directory exists and you have the proper rights to access the directory.";
+ return false;
+ }
+ return true;
+ }
+
+ void ImGuiFileBrowser::filterFiles(int filter_mode)
+ {
+ filter_dirty = false;
+ if((filter_mode & FilterMode_Dirs) != 0)
+ {
+ filtered_dirs.clear();
+ for (size_t i = 0; i < subdirs.size(); ++i)
+ {
+ if(filter.PassFilter(subdirs[i].name.c_str()))
+ filtered_dirs.push_back(subdirs[i]);
+ }
+ }
+ if((filter_mode & FilterMode_Files) != 0)
+ {
+ filtered_files.clear();
+ for (size_t i = 0; i < subfiles.size(); ++i)
+ {
+ if(valid_exts[selected_ext_idx] == "*.*")
+ {
+ if(filter.PassFilter(subfiles[i].name.c_str()))
+ filtered_files.push_back(subfiles[i]);
+ }
+ else
+ {
+ //if(filter.PassFilter(subfiles[i].name.c_str()) && (ImStristr(subfiles[i].name.c_str(), nullptr, valid_exts[selected_ext_idx].c_str(), nullptr)) != nullptr)
+
+ int last = (int)subfiles[i].name.find_last_of(".");
+
+ if (last >= 0 )
+ {
+ std::string extension = subfiles[i].name.substr(last);
+ std::transform(extension.begin(), extension.end(), extension.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+
+ if(filter.PassFilter(subfiles[i].name.c_str()) && (extension == valid_exts[selected_ext_idx]))
+ filtered_files.push_back(subfiles[i]);
+ }
+ }
+ }
+ }
+ }
+
+ void ImGuiFileBrowser::showHelpMarker(std::string desc)
+ {
+ ImGui::TextDisabled("(?)");
+ if (ImGui::IsItemHovered())
+ {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
+ ImGui::TextUnformatted(desc.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ }
+
+ void ImGuiFileBrowser::showErrorModal()
+ {
+ ImVec2 window_size(260, 0);
+ ImGui::SetNextWindowSize(window_size);
+
+ if (ImGui::BeginPopupModal(error_title.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
+ {
+ ImGui::TextWrapped("%s", error_msg.c_str());
+
+ ImGui::Separator();
+ ImGui::SetCursorPosX(window_size.x/2.0f - getButtonSize("OK").x/2.0f);
+ if (ImGui::Button("OK", getButtonSize("OK")))
+ ImGui::CloseCurrentPopup();
+ ImGui::EndPopup();
+ }
+ }
+
+ bool ImGuiFileBrowser::showReplaceFileModal()
+ {
+ ImVec2 window_size(250, 0);
+ ImGui::SetNextWindowSize(window_size);
+ bool ret_val = false;
+ if (ImGui::BeginPopupModal(repfile_modal_id.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
+ {
+ // unused: float frame_height = ImGui::GetFrameHeightWithSpacing();
+
+ std::string text = "A file with the following filename already exists. Are you sure you want to replace the existing file?";
+ ImGui::TextWrapped("%s", text.c_str());
+
+ ImGui::Separator();
+
+ float buttons_width = getButtonSize("Yes").x + getButtonSize("No").x + ImGui::GetStyle().ItemSpacing.x;
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetWindowWidth()/2.0f - buttons_width/2.0f - ImGui::GetStyle().WindowPadding.x);
+
+ if (ImGui::Button("Yes", getButtonSize("Yes")))
+ {
+ selected_path = current_path + selected_fn;
+ ImGui::CloseCurrentPopup();
+ ret_val = true;
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("No", getButtonSize("No")))
+ {
+ selected_fn.clear();
+ selected_path.clear();
+ ImGui::CloseCurrentPopup();
+ ret_val = false;
+ }
+ ImGui::EndPopup();
+ }
+ return ret_val;
+ }
+
+ void ImGuiFileBrowser::showInvalidFileModal()
+ {
+ // unused: ImGuiStyle& style = ImGui::GetStyle();
+ std::string text = "Selected file either doesn't exist or is not supported. Please select a file with the following extensions...";
+ // unused: ImVec2 text_size = ImGui::CalcTextSize(text.c_str(), nullptr, true, 350 - style.WindowPadding.x * 2.0);
+ ImVec2 button_size = getButtonSize("OK");
+
+ float frame_height = ImGui::GetFrameHeightWithSpacing();
+ float cw_content_height = valid_exts.size() * frame_height;
+ float cw_height = std::min(4.0f * frame_height, cw_content_height);
+ ImVec2 window_size(350, 0);
+ ImGui::SetNextWindowSize(window_size);
+
+ if (ImGui::BeginPopupModal(invfile_modal_id.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
+ {
+
+ ImGui::TextWrapped("%s", text.c_str());
+ ImGui::BeginChild("##SupportedExts", ImVec2(0, cw_height), true);
+ for(std::size_t i = 0; i < valid_exts.size(); i++)
+ ImGui::BulletText("%s", valid_exts[i].c_str());
+ ImGui::EndChild();
+
+ ImGui::SetCursorPosX(window_size.x/2.0f - button_size.x/2.0f);
+ if (ImGui::Button("OK", button_size))
+ ImGui::CloseCurrentPopup();
+ ImGui::EndPopup();
+ }
+ }
+
+ void ImGuiFileBrowser::setValidExtTypes(const std::string& valid_types_string)
+ {
+ /* Initialize a list of files extensions that are valid.
+ * If the user chooses a file that doesn't match the extensions in the
+ * list, we will show an error modal...
+ */
+ std::string max_str = "";
+ valid_exts.clear();
+ std::string extension = "";
+ std::istringstream iss(valid_types_string);
+ while(std::getline(iss, extension, ','))
+ {
+ if(!extension.empty())
+ {
+ if(max_str.size() < extension.size())
+ max_str = extension;
+ std::transform(extension.begin(), extension.end(), extension.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+ valid_exts.push_back(extension);
+ }
+ }
+ float min_width = ImGui::CalcTextSize(".abc").x + 100;
+ ext_box_width = std::max(min_width, ImGui::CalcTextSize(max_str.c_str()).x);
+ }
+
+ bool ImGuiFileBrowser::validateFile()
+ {
+ bool match = false;
+
+ //If there is an item selected, check if the selected file name (the input filename, in other words) matches the selection.
+ if(selected_idx >= 0)
+ {
+ if(dialog_mode == DialogMode::SELECT)
+ match = (filtered_dirs[selected_idx].name == selected_fn);
+ else
+ match = (filtered_files[selected_idx].name == selected_fn);
+ }
+
+ //If the input filename doesn't match we need to explicitly find the input filename..
+ if(!match)
+ {
+ if(dialog_mode == DialogMode::SELECT)
+ {
+ for(std::size_t i = 0; i < subdirs.size(); i++)
+ {
+ if(subdirs[i].name == selected_fn)
+ {
+ match = true;
+ break;
+ }
+ }
+
+ }
+ else
+ {
+ for(std::size_t i = 0; i < subfiles.size(); i++)
+ {
+ if(subfiles[i].name == selected_fn)
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // If file doesn't match, return true on SAVE mode (since file doesn't exist, hence can be saved directly) and return false on other modes (since file doesn't exist so cant open/select)
+ if(!match)
+ return (dialog_mode == DialogMode::SAVE);
+
+ // If file matches, return false on SAVE, we need to show a replace file modal
+ if(dialog_mode == DialogMode::SAVE)
+ return false;
+ // Return true on SELECT, no need to validate extensions
+ else if(dialog_mode == DialogMode::SELECT)
+ return true;
+ else
+ {
+ // If list of extensions has all types, no need to validate.
+ for(auto ext : valid_exts)
+ {
+ if(ext == "*.*")
+ return true;
+ }
+ int idx = (int)selected_fn.find_last_of('.');
+ std::string file_ext = idx == (int)std::string::npos ? "" : selected_fn.substr(idx, selected_fn.length() - idx);
+ std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+ return (std::find(valid_exts.begin(), valid_exts.end(), file_ext) != valid_exts.end());
+ }
+ }
+
+ ImVec2 ImGuiFileBrowser::getButtonSize(std::string button_text)
+ {
+ return (ImGui::CalcTextSize(button_text.c_str()) + ImGui::GetStyle().FramePadding * 2.0);
+ }
+
+ void ImGuiFileBrowser::parsePathTabs(std::string path)
+ {
+ std::string path_element = "";
+ std::string root = "";
+
+ #ifdef OSWIN
+ current_dirlist.push_back("Computer");
+ #else
+ if(path[0] == '/')
+ current_dirlist.push_back("/");
+ #endif //OSWIN
+
+ std::istringstream iss(path);
+ while(std::getline(iss, path_element, '/'))
+ {
+ if(!path_element.empty())
+ current_dirlist.push_back(path_element);
+ }
+ }
+
+ std::string ImGuiFileBrowser::wStringToString(const wchar_t* wchar_arr)
+ {
+ std::mbstate_t state = std::mbstate_t();
+
+ //MinGW bug (patched in mingw-w64), wcsrtombs doesn't ignore length parameter when dest = nullptr. Hence the large number.
+ size_t len = 1 + std::wcsrtombs(nullptr, &(wchar_arr), 600000, &state);
+
+ char* char_arr = new char[len];
+ std::wcsrtombs(char_arr, &wchar_arr, len, &state);
+
+ std::string ret_val(char_arr);
+
+ delete[] char_arr;
+ return ret_val;
+ }
+
+ bool ImGuiFileBrowser::alphaSortComparator(const Info& a, const Info& b)
+ {
+ return a.name < b.name;
+ }
+
+ //Windows Exclusive function
+ #ifdef OSWIN
+ bool ImGuiFileBrowser::loadWindowsDrives()
+ {
+ DWORD len = GetLogicalDriveStringsA(0,nullptr);
+ char* drives = new char[len];
+ if(!GetLogicalDriveStringsA(len,drives))
+ {
+ delete[] drives;
+ return false;
+ }
+
+ clearFileList();
+ char* temp = drives;
+ for(char *drv = nullptr; *temp != '\0'; temp++)
+ {
+ drv = temp;
+ if(DRIVE_REMOVABLE == GetDriveTypeA(drv))
+ subdirs.push_back({"Removable Disk: " + std::string(1,drv[0]), false});
+ else if(DRIVE_FIXED == GetDriveTypeA(drv))
+ subdirs.push_back({"Local Disk: " + std::string(1,drv[0]), false});
+ //Go to nullptr character
+ while(*(++temp));
+ }
+ delete[] drives;
+ return true;
+ }
+ #endif
+
+ //Unix only
+ #ifndef OSWIN
+ void ImGuiFileBrowser::initCurrentPath()
+ {
+ bool path_max_def = false;
+
+ #ifdef PATH_MAX
+ path_max_def = true;
+ #endif // PATH_MAX
+
+ char* buffer = nullptr;
+
+ //If PATH_MAX is defined deal with memory using new/delete. Else fallback to malloc'ed memory from `realpath()`
+ if(path_max_def)
+ buffer = new char[PATH_MAX];
+
+ char* real_path = realpath("./", buffer);
+ if (real_path == nullptr)
+ {
+ current_path = "/";
+ current_dirlist.push_back("/");
+ }
+ else
+ {
+ current_path = std::string(real_path);
+ current_path += "/";
+ parsePathTabs(current_path);
+ }
+
+ if(path_max_def)
+ delete[] buffer;
+ else
+ free(real_path);
+ }
+ #endif // OSWIN
+}
diff --git a/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.h b/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.h new file mode 100644 index 00000000..fadd4011 --- /dev/null +++ b/gearboy/platforms/desktop-shared/FileBrowser/ImGuiFileBrowser.h @@ -0,0 +1,123 @@ +#ifndef IMGUIFILEBROWSER_H
+#define IMGUIFILEBROWSER_H
+
+#include <string>
+#include <vector>
+#include "../imgui/imgui.h"
+
+namespace imgui_addons
+{
+ class ImGuiFileBrowser
+ {
+ public:
+ ImGuiFileBrowser();
+ ~ImGuiFileBrowser();
+
+ enum class DialogMode
+ {
+ SELECT, //Select Directory Mode
+ OPEN, //Open File mode
+ SAVE //Save File mode.
+ };
+
+ /* Use this to show an open file dialog. The function takes label for the window,
+ * the size, a DialogMode enum value defining in which mode the dialog should operate and optionally the extensions that are valid for opening.
+ * Note that the select directory mode doesn't need any extensions.
+ */
+ bool showFileDialog(const std::string& label, const DialogMode mode, const ImVec2& sz_xy = ImVec2(0,0), const std::string& valid_types = "*.*", bool* is_open = 0);
+
+ /* Store the opened/saved file name or dir name (incase of selectDirectoryDialog) and the absolute path to the selection
+ * Should only be accessed when above functions return true else may contain garbage.
+ */
+ std::string selected_fn;
+ std::string selected_path;
+ std::string ext; // Store the saved file extension
+
+
+ private:
+ struct Info
+ {
+ Info(std::string name, bool is_hidden) : name(name), is_hidden(is_hidden)
+ {
+ }
+ std::string name;
+ bool is_hidden;
+ };
+
+ //Enum used as bit flags.
+ enum FilterMode
+ {
+ FilterMode_Files = 0x01,
+ FilterMode_Dirs = 0x02
+ };
+
+ //Helper Functions
+ static std::string wStringToString(const wchar_t* wchar_arr);
+ static bool alphaSortComparator(const Info& a, const Info& b);
+ ImVec2 getButtonSize(std::string button_text);
+
+ /* Helper Functions that render secondary modals
+ * and help in validating file extensions and for filtering, parsing top navigation bar.
+ */
+ void setValidExtTypes(const std::string& valid_types_string);
+ bool validateFile();
+ void showErrorModal();
+ void showInvalidFileModal();
+ bool showReplaceFileModal();
+ void showHelpMarker(std::string desc);
+ void parsePathTabs(std::string str);
+ void filterFiles(int filter_mode);
+
+ /* Core Functions that render the 4 different regions making up
+ * a simple file dialog
+ */
+ bool renderNavAndSearchBarRegion();
+ bool renderFileListRegion();
+ bool renderInputTextAndExtRegion();
+ bool renderButtonsAndCheckboxRegion();
+ bool renderInputComboBox();
+ void renderExtBox();
+
+ /* Core Functions that handle navigation and
+ * reading directories/files
+ */
+ bool readDIR(std::string path);
+ bool onNavigationButtonClick(int idx);
+ bool onDirClick(int idx);
+
+ // Functions that reset state and/or clear file list when reading new directory
+ void clearFileList();
+ void closeDialog();
+
+ #if defined (WIN32) || defined (_WIN32) || defined (__WIN32)
+ bool loadWindowsDrives(); // Helper Function for Windows to load Drive Letters.
+ #endif
+
+ #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
+ void initCurrentPath(); // Helper function for UNIX based system to load Absolute path using realpath
+ #endif
+
+ ImVec2 min_size, max_size, input_combobox_pos, input_combobox_sz;
+ DialogMode dialog_mode;
+ int filter_mode, col_items_limit, selected_idx, selected_ext_idx;
+ float col_width, ext_box_width;
+ bool show_hidden, show_inputbar_combobox, is_dir, is_appearing, filter_dirty, validate_file;
+ char input_fn[256];
+ bool* is_open_;
+
+ std::vector<std::string> valid_exts;
+ std::vector<std::string> current_dirlist;
+ std::vector<Info> subdirs;
+ std::vector<Info> subfiles;
+ std::string current_path, error_msg, error_title, invfile_modal_id, repfile_modal_id;
+
+ ImGuiTextFilter filter;
+ std::string valid_types;
+ std::vector<Info> filtered_dirs; // Note: We don't need to call delete. It's just for storing filtered items from subdirs and subfiles so we don't use PassFilter every frame.
+ std::vector<Info> filtered_files;
+ std::vector< std::reference_wrapper<std::string> > inputcb_filter_files;
+ };
+}
+
+
+#endif // IMGUIFILEBROWSER_H
|
