Skip to content

Instantly share code, notes, and snippets.

@Flix01
Last active August 19, 2023 08:36
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Flix01/f34b5efa91e50a241c1b to your computer and use it in GitHub Desktop.
imguifilesystem dialogs for imgui v.1.17 wip (https://github.com/ocornut/imgui/issues/88)
imguifilesystem dialogs for imgui v.1.17 wip (https://github.com/ocornut/imgui).
See also: https://github.com/ocornut/imgui/issues/88
It's composed by three files:
- imguifilesystem.h (usage instructions are here)
- imguifilesystem.cpp
- dirent_portable.h
It needs testing and feedback (expecially for Windows/VisualC++ and MacOS).
It does not use any C++ string class: so it should be straightforward to merge its code
into imgui.h/imgui.cpp if we want to (and if the code is robust enough).
/*
* This file was originally: "dirent for Visual C++" from: http://softagalleria.net/dirent.php (version 1.20.1)
* However I've modified it to <dirent_portable.h> by adding:
*
* a fallback to <dirent.h> if _WIN32 is not defined
* two missing methods: scandir(...) and alphasort(...)
* and some other minor modifications (see below)
*
*
* Original license from http://softagalleria.net/dirent.php
*
*========================================================================
*
* dirent.h - dirent API for Microsoft Visual Studio
*
* Copyright (C) 2006-2012 Toni Ronkko
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* ``Software''), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* $Id: dirent.h,v 1.20 2014/03/19 17:52:23 tronkko Exp $
*
* =========================================================================
* Added:
* -> some undefs to prevent possible compiler warnings
* -> the scandir(...) and alphasort(...) methods
* -> the optional DIRENT_USES_UTF8_CHARS definition (needed for browsing with long UTF8 paths, instead of short ASCII paths).
* WARNING: in my tests the usage of the DIRENT_USES_UTF8_CHARS is not fully functional (patches are welcome)
* All these additions have been made to made <dirent_portable.h> usage for Windows consistent
* with what I get using <direct.h> under my Ubuntu Linux OS.
* =========================================================================
*
* The code of the scandir(...) method come from the musl library (http://www.musl-libc.org/)
* (MIT licensed, Copyright © 2005-2014 Rich Felker, et al.).
*
* The code of the alphasort(...) method and of all the other minor modifications is in the public domain.
*
*/
#if (!defined(_WIN32) && !defined(_WIN64))
# include <dirent.h>
#else // #if (!defined(_WIN32) && !defined(_WIN64))
#ifndef DIRENT_H
#define DIRENT_H
/*
* Define architecture flags so we don't need to include windows.h.
* Avoiding windows.h makes it simpler to use windows sockets in conjunction
* with dirent.h.
*/
#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86)
# define _X86_
#endif
#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64)
#define _AMD64_
#endif
#include <stdio.h>
#include <stdarg.h>
#include <windef.h>
#include <winbase.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//#define DIRENT_USES_UTF8_CHARS // Test only
/* 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() */
#if !defined(S_IFMT)
# define S_IFMT _S_IFMT /* File type mask */
#endif
#if !defined(S_IFDIR)
# define S_IFDIR _S_IFDIR /* Directory */
#endif
#if !defined(S_IFCHR)
# define S_IFCHR _S_IFCHR /* Character device */
#endif
#if !defined(S_IFFIFO)
# define S_IFFIFO _S_IFFIFO /* Pipe */
#endif
#if !defined(S_IFREG)
# define S_IFREG _S_IFREG /* Regular file */
#endif
#if !defined(S_IREAD)
# define S_IREAD _S_IREAD /* Read permission */
#endif
#if !defined(S_IWRITE)
# define S_IWRITE _S_IWRITE /* Write permission */
#endif
#if !defined(S_IEXEC)
# define S_IEXEC _S_IEXEC /* Execute permission */
#endif
#if !defined(S_IFIFO)
# define S_IFIFO _S_IFIFO /* Pipe */
#endif
#if !defined(S_IFBLK)
# define S_IFBLK 0 /* Block device */
#endif
#if !defined(S_IFLNK)
# define S_IFLNK 0 /* Link */
#endif
#if !defined(S_IFSOCK)
# define S_IFSOCK 0 /* Socket */
#endif
#if defined(_MSC_VER)
# define S_IRUSR S_IREAD /* Read user */
# define S_IWUSR S_IWRITE /* Write user */
# define S_IXUSR 0 /* Execute user */
# define S_IRGRP 0 /* Read group */
# define S_IWGRP 0 /* Write group */
# define S_IXGRP 0 /* Execute group */
# define S_IROTH 0 /* Read others */
# define S_IWOTH 0 /* Write others */
# define S_IXOTH 0 /* Execute others */
#endif
/* Maximum length of file name */
#ifdef DIRENT_USES_UTF8_CHARS
// utf8 strings can have up to 4 bytes per char
# undef PATH_MAX
# define PATH_MAX (MAX_PATH*4)
# undef FILENAME_MAX
# define FILENAME_MAX MAX_PATH
# undef NAME_MAX
# define NAME_MAX FILENAME_MAX
#else //DIRENT_USES_UTF8_CHARS
# 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
#endif //DIRENT_USES_UTF8_CHARS
/* 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.
*/
// Added some undefs to prevent possible compiler warnings
#undef S_ISFIFO
#undef S_ISDIR
#undef S_ISREG
#undef S_ISLNK
#undef S_ISSOCK
#undef S_ISSOCK
#undef S_ISCHR
#undef S_ISBLK
#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
#define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR)
#define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK)
/* Return the exact length of d_namlen without zero terminator */
#define _D_EXACT_NAMLEN(p) ((p)->d_namlen)
/* Return number of bytes needed to store d_namlen */
#define _D_ALLOC_NAMLEN(p) (PATH_MAX)
#ifdef __cplusplus
extern "C" {
#endif
/* Wide-character version */
struct _wdirent {
long d_ino; /* Always zero */
unsigned short d_reclen; /* Structure size */
size_t d_namlen; /* Length of name without \0 */
int d_type; /* File type */
wchar_t d_name[PATH_MAX]; /* File name */
};
typedef struct _wdirent _wdirent;
struct _WDIR {
struct _wdirent ent; /* Current directory entry */
WIN32_FIND_DATAW data; /* Private file data */
int cached; /* True if data is valid */
HANDLE handle; /* Win32 search handle */
wchar_t *patt; /* Initial directory name */
};
typedef struct _WDIR _WDIR;
static _WDIR *_wopendir (const wchar_t *dirname);
static struct _wdirent *_wreaddir (_WDIR *dirp);
static int _wclosedir (_WDIR *dirp);
static void _wrewinddir (_WDIR* dirp);
/* For compatibility with Symbian */
#define wdirent _wdirent
#define WDIR _WDIR
#define wopendir _wopendir
#define wreaddir _wreaddir
#define wclosedir _wclosedir
#define wrewinddir _wrewinddir
/* Multi-byte character versions */
struct dirent {
long d_ino; /* Always zero */
unsigned short d_reclen; /* Structure size */
size_t d_namlen; /* Length of name without \0 */
int d_type; /* File type */
char d_name[PATH_MAX]; /* File name */
};
typedef struct dirent dirent;
struct DIR {
struct dirent ent;
struct _WDIR *wdirp;
};
typedef struct DIR DIR;
static DIR *opendir (const char *dirname);
static struct dirent *readdir (DIR *dirp);
static int closedir (DIR *dirp);
static void rewinddir (DIR* dirp);
/* 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 = NULL;
int error;
/* 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 != NULL) {
DWORD n;
/* Reset _WDIR structure */
dirp->handle = INVALID_HANDLE_VALUE;
dirp->patt = NULL;
dirp->cached = 0;
/* Compute the length of full path plus zero terminator */
n = GetFullPathNameW (dirname, 0, NULL, NULL);
/* Allocate room for absolute directory name and search pattern */
dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16);
if (dirp->patt) {
/*
* 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().
*/
n = GetFullPathNameW (dirname, n, dirp->patt, NULL);
if (n > 0) {
wchar_t *p;
/* Append search pattern \* to the directory name */
p = dirp->patt + n;
if (dirp->patt < p) {
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)) {
/* Directory stream opened successfully */
error = 0;
} else {
/* Cannot retrieve first entry */
error = 1;
dirent_set_errno (ENOENT);
}
} else {
/* Cannot retrieve full path name */
dirent_set_errno (ENOENT);
error = 1;
}
} else {
/* Cannot allocate memory for search pattern */
error = 1;
}
} else {
/* Cannot allocate _WDIR structure */
error = 1;
}
/* Clean up in case of error */
if (error && dirp) {
_wclosedir (dirp);
dirp = NULL;
}
return dirp;
}
/*
* Read next directory entry. The directory entry is returned in dirent
* structure in the d_name field. Individual directory entries returned by
* this function include regular files, sub-directories, pseudo-directories
* "." and ".." as well as volume labels, hidden files and system files.
*/
static struct _wdirent*
_wreaddir(
_WDIR *dirp)
{
WIN32_FIND_DATAW *datap;
struct _wdirent *entp;
/* Read next directory entry */
datap = dirent_next (dirp);
if (datap) {
size_t n;
DWORD attr;
/* Pointer to directory entry to return */
entp = &dirp->ent;
/*
* 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 + 1 < PATH_MAX && datap->cFileName[n] != 0) {
entp->d_name[n] = datap->cFileName[n];
n++;
}
dirp->ent.d_name[n] = 0;
/* Length of file name excluding zero terminator */
entp->d_namlen = n;
/* File type */
attr = datap->dwFileAttributes;
if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
entp->d_type = DT_CHR;
} else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
entp->d_type = DT_DIR;
} else {
entp->d_type = DT_REG;
}
/* Reset dummy fields */
entp->d_ino = 0;
entp->d_reclen = sizeof (struct _wdirent);
} else {
/* Last directory entry read */
entp = NULL;
}
return entp;
}
/*
* 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);
dirp->handle = INVALID_HANDLE_VALUE;
}
/* Release search pattern */
if (dirp->patt) {
free (dirp->patt);
dirp->patt = NULL;
}
/* 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;
/* Open directory and retrieve the first entry */
dirp->handle = FindFirstFileW (dirp->patt, &dirp->data);
if (dirp->handle != INVALID_HANDLE_VALUE) {
/* a directory entry is now waiting in memory */
datap = &dirp->data;
dirp->cached = 1;
} else {
/* Failed to re-open directory: no directory entry in memory */
dirp->cached = 0;
datap = NULL;
}
return datap;
}
/* Get next directory entry (internal) */
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 occured */
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;
int error;
/* 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) {
wchar_t wname[PATH_MAX];
size_t n;
/* Convert directory name to wide-character string */
error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX);
if (!error) {
/* Open directory stream using wide-character name */
dirp->wdirp = _wopendir (wname);
if (dirp->wdirp) {
/* Directory stream opened */
error = 0;
} else {
/* Failed to open directory stream */
error = 1;
}
} else {
/*
* 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.
*/
error = 1;
}
} else {
/* Cannot allocate DIR structure */
error = 1;
}
/* Clean up in case of error */
if (error && dirp) {
free (dirp);
dirp = NULL;
}
return dirp;
}
/*
* Read next directory entry.
*
* When working with text consoles, please note that file names returned by
* readdir() are represented in the default ANSI code page while any output to
* console is typically formatted on another code page. Thus, non-ASCII
* characters in file names will not usually display correctly on console. The
* problem can be fixed in two ways: (1) change the character set of console
* to 1252 using chcp utility and use Lucida Console font, or (2) use
* _cprintf function when writing to console. The _cprinf() will re-encode
* ANSI strings to the console code page so many non-ASCII characters will
* display correcly.
*/
static struct dirent*
readdir(
DIR *dirp)
{
WIN32_FIND_DATAW *datap;
struct dirent *entp;
/* 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, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX);
/*
* 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, dirp->ent.d_name, PATH_MAX,
datap->cAlternateFileName, PATH_MAX);
}
if (!error) {
DWORD attr;
/* Initialize directory entry for return */
entp = &dirp->ent;
/* Length of file name excluding zero terminator */
entp->d_namlen = n - 1;
/* File attributes */
attr = datap->dwFileAttributes;
if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
entp->d_type = DT_CHR;
} else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
entp->d_type = DT_DIR;
} else {
entp->d_type = DT_REG;
}
/* Reset dummy fields */
entp->d_ino = 0;
entp->d_reclen = sizeof (struct dirent);
} else {
/*
* Cannot convert file name to multi-byte string so construct
* an errornous directory entry and return that. Note that
* we cannot return NULL as that would stop the processing
* of directory entries completely.
*/
entp = &dirp->ent;
entp->d_name[0] = '?';
entp->d_name[1] = '\0';
entp->d_namlen = 1;
entp->d_type = DT_UNKNOWN;
entp->d_ino = 0;
entp->d_reclen = 0;
}
} else {
/* No more directory entries */
entp = NULL;
}
return entp;
}
/*
* 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);
}
/* 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;
#ifdef DIRENT_USES_UTF8_CHARS
// we don't use "count" at all: we assume mstr is zero terminated:
size_t n = (size_t) MultiByteToWideChar (CP_UTF8, 0, mbstr, -1, wcstr, 0);//sizeInWords);
if (n==0) {
error = 1;
if (sizeInWords>0) wcstr[0]=L'\0';
if (pReturnValue) *pReturnValue = 0;
}
else if (n<=sizeInWords) {
error = MultiByteToWideChar (CP_UTF8, 0, mbstr, -1, wcstr, n) == 0 ? 1 : 0;
if (pReturnValue) *pReturnValue = n;
}
else {
// Buffer too low:
if (sizeInWords>0) {
if (sizeInWords>1) MultiByteToWideChar (CP_UTF8, 0, mbstr, -1, wcstr, sizeInWords-1);
wcstr[sizeInWords-1] = L'\0';
}
if (pReturnValue) *pReturnValue = sizeInWords;
error = 1;
}
/*
if (!wcstr || n < count) {
// Zero-terminate output buffer
if (wcstr && sizeInWords) {
if (n >= sizeInWords) {
n = sizeInWords - 1;
}
wcstr[n] = 0;
}
// Length of resuting multi-byte string WITH zero terminator
if (pReturnValue) {
*pReturnValue = n + 1;
}
// Success
error = 0;
} else {
// Could not convert string
error = 1;
}
*/
#else //DIRENT_USES_UTF8_CHARS
#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 resuting multi-byte string WITH zero terminator */
if (pReturnValue) {
*pReturnValue = n + 1;
}
/* Success */
error = 0;
} else {
/* Could not convert string */
error = 1;
}
#endif
#endif //DIRENT_USES_UTF8_CHARS
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;
#ifdef DIRENT_USES_UTF8_CHARS
// we don't use "count" at all: we assume wcstr is zero terminated:
size_t n = (size_t) WideCharToMultiByte (CP_UTF8, 0, wcstr, -1, mbstr, 0,NULL,NULL);//sizeInBytes, NULL, NULL);
if (n==0) {
error = 1;
if (sizeInBytes>0) mbstr[0]='\0';
if (pReturnValue) *pReturnValue = 0;
}
else if (n<=sizeInBytes) {
error = WideCharToMultiByte (CP_UTF8, 0, wcstr, -1, mbstr, n, NULL, NULL) == 0 ? 1 : 0;
if (pReturnValue) *pReturnValue = n;
}
else {
// Buffer too low:
if (sizeInBytes>0) {
if (sizeInBytes>1) WideCharToMultiByte (CP_UTF8, 0, wcstr, -1, mbstr, sizeInBytes-1, NULL, NULL);
mbstr[sizeInBytes-1] = '\0';
}
if (pReturnValue) *pReturnValue = sizeInBytes;
error = 1;
}
/*
if (!mbstr || n < count) {
// Zero-terminate output buffer
if (mbstr && sizeInBytes) {
if (n >= sizeInBytes) {
n = sizeInBytes - 1;
}
mbstr[n] = '\0';
}
// Lenght of resulting multi-bytes string WITH zero-terminator
if (pReturnValue) {
*pReturnValue = n + 1;
}
// Success
error = 0;
} else {
// Cannot convert string
error = 1;
}
*/
#else //DIRENT_USES_UTF8_CHARS
#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';
}
/* Lenght of resulting multi-bytes string WITH zero-terminator */
if (pReturnValue) {
*pReturnValue = n + 1;
}
/* Success */
error = 0;
} else {
/* Cannot convert string */
error = 1;
}
#endif
#endif //DIRENT_USES_UTF8_CHARS
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
}
// The code of this single method comes from the musl library
// (MIT licensed, Copyright © 2005-2014 Rich Felker, et al.)
inline static int scandir(const char *path, struct dirent ***res,
int (*sel)(const struct dirent *),
int (*cmp)(const struct dirent **, const struct dirent **))
{
DIR *d = opendir(path);
struct dirent *de, **names=0, **tmp;
size_t cnt=0, len=0;
int old_errno = errno;
if (!d) return -1;
while ((errno=0), (de = readdir(d))) {
if (sel && !sel(de)) continue;
if (cnt >= len) {
len = 2*len+1;
if (len > SIZE_MAX/sizeof *names) break;
tmp = (dirent**)realloc(names, len * sizeof *names);
if (!tmp) break;
names = tmp;
}
names[cnt] = (dirent*)malloc(de->d_reclen);
if (!names[cnt]) break;
memcpy(names[cnt++], de, de->d_reclen);
}
closedir(d);
if (errno) {
if (names) while (cnt-->0) free(names[cnt]);
free(names);
return -1;
}
errno = old_errno;
if (cmp) qsort(names, cnt, sizeof *names, (int (*)(const void *, const void *))cmp);
*res = names;
return cnt;
}
// alphasort: Function to compare two `struct dirent's alphabetically.
inline static int alphasort (const struct dirent **e1,const struct dirent **e2) {
return strcmp((*e1)->d_name,(*e2)->d_name);
}
#ifdef __cplusplus
}
#endif
#endif /*DIRENT_H*/
#endif //#if (!defined(_WIN32) && !defined(_WIN64))
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#include "imguifilesystem.h"
#ifdef _WIN32
#include <shlobj.h> // Known Directory locations
# ifndef CSIDL_MYPICTURES
# define CSIDL_MYPICTURES 0x0027
# endif //CSIDL_MYPICTURES
# ifndef CSIDL_MYMUSIC
# define CSIDL_MYMUSIC 0x000d
# endif //CSIDL_MYMUSIC
# ifndef CSIDL_MYVIDEO
# define CSIDL_MYVIDEO 0x000e
# endif //CSIDL_MYVIDEO
#else // _WIN32
# include <unistd.h> // getpwuid
# include <pwd.h> // getenv ?
#endif //#ifdef _WIN32
#include "dirent_portable.h"
#include <sys/stat.h>
#include <ctype.h> // tolower,...
#include <string.h> // strcmp
#include <stdio.h> // FILENAME_MAX
#include <new> // operator new
namespace ImGuiFs {
const int MAX_FILENAME_BYTES = FILENAME_MAX;
const int MAX_PATH_BYTES = PATH_MAX;
enum Sorting {
SORT_ORDER_ALPHABETIC=0,
SORT_ORDER_ALPHABETIC_INVERSE=1,
SORT_ORDER_LAST_MODIFICATION=2,
SORT_ORDER_LAST_MODIFICATION_INVERSE=3,
SORT_ORDER_SIZE=4,
SORT_ORDER_SIZE_INVERSE=5,
SORT_ORDER_TYPE=6,
SORT_ORDER_TYPE_INVERSE=7,
SORT_ORDER_COUNT
};
// Definitions of some helper classes (String,Path,SortingHelper,Directory). Better not expose them in the header file----------
/*
// MAIN ISSUE: All these string handling methods work with ASCII strings,
// but the ones returned by dirent are multibyte OS dependant strings.
// That means that there are some limitations:
// LIMITATIONS:
// -> paths with '/','\\','.' bytes (and possibly a few others) inside multibyte codepoints are not supported (*)
// -> file extensions composed by characters with more than one byte are not supported (**)
//(*) That's because when I call something like: mystring.find_last_of('/') or Path::Split('/')
// the result might not be correct if there's some multibyte codepoint that includes that byte(s) (bytes include '/','\\','.').
// (**) String::ToLower() deeply breaks any multibyte char.
// They're currently used only in:
// Path::GetExtension(...)
// Directory::GetFiles(const string& path,const string& wantedExtensions,const string& unwantedExtensions)
// That's because file extensions must be returned lowercase, so that e.g. ".PNG" and ".png" can be string matched (even on case sensitive file systems).
*/
class String {
protected:
String() {}
public:
inline static void PushBack(ImVector<char[MAX_FILENAME_BYTES]>& rv,const char* s) {
const size_t sz = rv.size();
rv.resize(sz+1);
strcpy(&rv[sz][0], s ? s : "\0");
}
# if (FILENAME_MAX!=PATH_MAX) // Will this work ? (I don't want to use templates)
inline static void PushBack(ImVector<char[MAX_PATH_BYTES]>& rv,const char* s) {
const size_t sz = rv.size();
rv.resize(sz+1);
strcpy(&rv[sz][0], s ? s : "\0");
}
# endif //#if (FILENAME_MAX!=PATH_MAX)
inline static void Substr(const char* text,char* rv,int start,int count=-1) {
if (!text) count=0;
if (count<0) count = (int) strlen(text) - start;
if (count>0) strncpy(rv,&text[start],count);
rv[count]='\0';
}
inline static int Find(const char* text,const char toFind,int beg=0) {
if (!text) return -1;
for (size_t i=beg,len=strlen(text);i<len;i++) {
if (text[i]==toFind) return i;
}
return -1;
}
inline static int FindLastOf(const char* text,const char toFind) {
if (!text) return -1;
for (int i=(int)strlen(text)-1;i>=0;i--) {
if (text[i]==toFind) return i;
}
return -1;
}
inline static void ToLower(const char* text,char* rv) {
if (!text) {
rv[0]='\0';
return;
}
const size_t len = strlen(text);
for (size_t i=0;i<len;i++) {
rv[i]=tolower(text[i]);
}
rv[len]='\0';
}
inline static void ToLowerInPlace(char* text) {
if (!text) return;
for (size_t i=0,len = strlen(text);i<len;i++) {
char& c = text[i];
c=tolower(c);
}
}
inline static void Split(const char* text,ImVector<char[MAX_FILENAME_BYTES]>& rv,const char c=' ') {
rv.clear();
if (!text) return;
const int len = (int)strlen(text);
if (len==0) return;
int beg = 0;char tmp[MAX_FILENAME_BYTES];
for (int i=0;i<len;i++) {
const char ch = text[i];
if (ch==c) {
Substr(text,tmp,beg,i-beg);
PushBack(rv,tmp);
beg = i+1;
}
}
if (beg<len) {
Substr(text,tmp,beg,len-beg);
PushBack(rv,tmp);
}
}
inline static void Replace(const char* baseText,const char textToReplace,const char replacement,char* rv) {
strcpy(rv,baseText);
ReplaceInPlace(rv,textToReplace,replacement);
}
inline static void ReplaceInPlace(char* text,const char toReplace,const char replacement) {
if (!text) return;
for (size_t i=0,len = strlen(text);i<len;i++) {
char& c = text[i];
if (c==toReplace) c=replacement;
}
}
# ifdef _WIN32
// Convert a wide Unicode string to an UTF8 string
inline static void wide_to_utf8(const wchar_t* wstr,char* rv) {
rv[0]='\0';
if (!wstr) return;
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
WideCharToMultiByte (CP_UTF8, 0, wstr, -1, &rv[0], size_needed, NULL, NULL);
//rv[size_needed]='\0'; // If the parameter after wstr is -1, the function processes the entire input string, including the terminating null character. Therefore, the resulting character string has a terminating null character, and the length returned by the function includes this character.
return ;
}
// Convert an UTF8 string to a wide Unicode String
inline static void utf8_to_wide(const char* str,wchar_t* rv) {
rv[0]=L'\0';
if (!str) return;
int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
MultiByteToWideChar (CP_UTF8, 0, str, -1, &rv[0], size_needed);
//rv[size_needed]=L'\0'; // // If the parameter after str is -1, the function processes the entire input string, including the terminating null character. Therefore, the resulting character string has a terminating null character, and the length returned by the function includes this character.
return;
}
# endif // _WIN32
};
class Path {
protected:
Path() {}
public:
static void GetAbsolutePath(const char *path, char *rv) {
rv[0]='\0';
# ifndef _WIN32
if (!path || strlen(path)==0) realpath("./", rv);
else realpath(path, rv);
# else //_WIN32
static const int bufferSize = PATH_MAX+1; // 4097 is good (PATH_MAX should be in <limits.h>, or something like that)
static wchar_t buffer[bufferSize];
static wchar_t wpath[bufferSize];
String::utf8_to_wide(path ? path : "",wpath);
::GetFullPathNameW(&wpath[0],bufferSize,&buffer[0],NULL);
String::wide_to_utf8(&buffer[0],rv);
String::ReplaceInPlace(rv,'\\','/');
size_t len;
while ( (len=strlen(rv))>0 && rv[len-1]=='/') rv[len-1]='\0';
//fprintf(stderr,"AbsolutePath = \"%s\" (len:%d)\n",rv,(int) strlen(rv)); // TO remove!
# endif // _WIN32
}
static void GetDirectoryName(const char *filePath, char *rv) {
rv[0]='\0';if (!filePath) return;
const int sz = strlen(filePath);
if (sz==0 || strcmp(filePath,"/")==0 || strcmp(filePath,"\\")==0) {
strcpy(rv,filePath);
return;
}
const char c = filePath[sz-1];
if (c == '/' || c=='\\') {
char tmp[MAX_PATH_BYTES];
String::Substr(filePath,tmp,0,sz-1);
GetDirectoryName(tmp,rv);
return;
}
if (c==':') {
strcpy(rv,filePath);
return;
}
int beg=String::FindLastOf(filePath,'\\');
int beg2=String::FindLastOf(filePath,'/');
beg=(beg>beg2?beg:beg2);
if (beg==0) {
String::Substr(filePath,rv,0,1);
return;
}
if (beg!=-1) {
String::Substr(filePath,rv,0,beg);
return;
}
rv[0]='\0';
return;
}
static void GetFileName(const char *filePath, char *rv) {
int beg=String::FindLastOf(filePath,'\\');
int beg2=String::FindLastOf(filePath,'/');
beg=(beg>beg2?beg:beg2);
if (beg!=-1) {
String::Substr(filePath,rv,beg+1);
return;
}
strcpy(rv,filePath);
return;
}
static void GetExtension(const char* filePath,char *rv) {
int beg=String::FindLastOf(filePath,'.');
int beg2=String::FindLastOf(filePath,'/');
int beg3=String::FindLastOf(filePath,'\\');
if (beg2!=-1) {
if (beg3!=-1) beg2 = beg3;
else beg2 = beg2 > beg3 ? beg2 : beg3;
}
else if (beg3!=-1) beg2 = beg3;
else {
if (beg!=-1) {
String::Substr(filePath,rv,beg);
String::ToLowerInPlace(rv);
return;
}
}
if (beg>beg2) {
if (beg!=-1) {
String::Substr(filePath,rv,beg);
String::ToLowerInPlace(rv);
return;
}
}
rv[0]='\0';
return;
}
static void Combine(const char* directory,const char* fileName,char* rv,bool appendMode=true) {
if (!appendMode) rv[0]='\0';
const size_t size= directory ? strlen(directory) : 0;
if (size==0) {
strcat(rv,fileName);
return;
}
strcat(rv,directory);
if (directory[size-1]!='\\' && directory[size-1]!='/') {
strcat(rv,"/");
strcat(rv,fileName);
}
else strcat(rv,fileName);
return;
}
static void Append(const char* directory,char* rv) {
if (!directory || strlen(directory)==0) return;
size_t size = strlen(rv);
if (size>0 && (rv[size-1]!='\\' && rv[size-1]!='/')) {strcat(rv,"/");++size;}
strcat(rv,directory);
size = strlen(rv);
while (size>0 && (rv[size-1]=='\\' || rv[size-1]=='/')) {rv[size-1]='\0';--size;}
if (size==0 || rv[size-1]==':') strcat(rv,"/");
}
static void Split(const char* path,ImVector<char[MAX_FILENAME_BYTES]>& rv,bool leaveIntermediateTrailingSlashes=true) {
rv.clear();
static char tex[MAX_PATH_BYTES];
String::Replace(path,'\\','/',tex);
size_t len = strlen(tex);
static char tex2[MAX_PATH_BYTES];
# ifdef _WIN32
while ((len = strlen(tex))>0 && tex[len-1]=='/') {
strncpy(tex2,tex,len+1);
String::Substr(tex2,tex,0,len-1);
}
# endif //_WIN32
if (len==0) return;
int beg=-1;
while ( (beg = String::Find(tex,'/'))!=-1) {
static char tmp[MAX_FILENAME_BYTES];
String::Substr(tex,tmp,0,leaveIntermediateTrailingSlashes ? beg+1 : beg);
String::PushBack(rv,tmp);
strcpy(tex2,tex);
String::Substr(tex2,tex,beg+1);
}
String::PushBack(rv,tex);
if (rv.size()>0 && strlen(rv[0])==0) strcpy((char*)&rv[0][0],"/\0");
# ifdef _WIN32
if (rv.size()==1 && strlen(rv[0])>0 && rv[0][strlen(rv[0])-1]==':') strcat((char*)&rv[0][0],"/");
# endif //_WIN32
return;
}
/*
inline static bool Exists(const char* path) {
struct stat statbuf;
return (stat(path, &statbuf) != -1);
}
*/
};
/*
class File {
public:
inline static bool Exists(const char* filePath) {
struct stat statbuf;
return (stat(path, &statbuf) != -1 && (S_ISREG(statbuf.st_mode)));// || (acceptLinks ? S_ISLNK(statbuf.st_mode) : 1));
}
protected:
File() {}
};
*/
class SortingHelper {
public:
typedef int (*SorterSignature)(const struct dirent **e1,const struct dirent **e2);
inline static const SorterSignature& SetSorter(Sorting sorting) {
const int isort =(int) sorting;
if (isort>=0 && isort<(int)SORT_ORDER_COUNT) return (sorter = Sorters[isort]);
return (sorter = Sorters[0]);
}
protected:
SortingHelper() {}
const static SorterSignature Sorters[];
static struct stat stat1;
static struct stat stat2;
static SorterSignature sorter;
# ifdef MSC_VER
// Never tested (I've just been told that cl.exe does not have strcasecmp: please search the web for other possible alternative implementations)
inline static int strcasecmp( const char *s1, const char *s2 ) {
return _stricmp(s1,s2);
//return lstrcmpiA(s1,s2); // Not sure this is better
}
# endif //MSC_VER
// Possible problem: sorting is in ASCII with these methods
static int Alphasort(const struct dirent **e1,const struct dirent **e2) {
return strcasecmp((*e1)->d_name,(*e2)->d_name);
}
static int Alphasortinverse (const struct dirent **e1,const struct dirent **e2) {
return -strcasecmp((*e1)->d_name,(*e2)->d_name);
}
// Please note that calling stat(...) every time inside sorters is a suicide! And that I'm doing right that! (but I guess and hope that on many systems the calls get cached somewhere: otherwise it would take ages to sort)
static int Lastmodsort (const struct dirent **e1,const struct dirent **e2) {
if (stat((*e1)->d_name,&stat1)==-1) return -1;
if (stat((*e2)->d_name,&stat2)==-1) return 1;
return (stat1.st_mtime < stat2.st_mtime ? -1 : stat1.st_mtime > stat2.st_mtime ? 1 : 0);
}
static int Lastmodsortinverse(const struct dirent **e1,const struct dirent **e2) {
if (stat((*e1)->d_name,&stat1)==-1) return 1;
if (stat((*e2)->d_name,&stat2)==-1) return -1;
return (stat2.st_mtime < stat1.st_mtime ? -1 : stat2.st_mtime > stat1.st_mtime ? 1 : 0);
}
static int Sizesort (const struct dirent **e1,const struct dirent **e2) {
if (stat((*e1)->d_name,&stat1)==-1) return -1;
if (stat((*e2)->d_name,&stat2)==-1) return 1;
return (stat1.st_size < stat2.st_size ? -1 : stat1.st_size > stat2.st_size ? 1 : 0);
}
static int Sizesortinverse(const struct dirent **e1,const struct dirent **e2) {
if (stat((*e1)->d_name,&stat1)==-1) return 1;
if (stat((*e2)->d_name,&stat2)==-1) return -1;
return (stat2.st_size < stat1.st_size ? -1 : stat2.st_size > stat1.st_size ? 1 : 0);
}
// Please note that calculating the file extension every time inside sorters is a suicide (well, much less than before...)!
static int Typesort(const struct dirent **e1,const struct dirent **e2) {
static const int dot = (int) '.';
const char * p1 = strrchr((const char*) (*e1)->d_name, dot );
const char * p2 = strrchr((const char*) (*e2)->d_name, dot );
if (!p1) return (!p2?0:-1);
else if (!p2) return 1;
return strcasecmp(p1,p2);
}
static int Typesortinverse (const struct dirent **e1,const struct dirent **e2) {
static const int dot = (int) '.';
const char * p1 = strrchr((const char*) (*e1)->d_name, dot );
const char * p2 = strrchr((const char*) (*e2)->d_name, dot );
if (!p1) return (!p2?0:1);
else if (!p2) return -1;
return -strcasecmp(p1,p2);
}
};
const SortingHelper::SorterSignature SortingHelper::Sorters[] = {&SortingHelper::Alphasort,&SortingHelper::Alphasortinverse,&SortingHelper::Lastmodsort,&SortingHelper::Lastmodsortinverse,&SortingHelper::Sizesort,&SortingHelper::Sizesortinverse,&Typesort,&SortingHelper::Typesortinverse};
SortingHelper::SorterSignature SortingHelper::sorter;
struct stat SortingHelper::stat1;
struct stat SortingHelper::stat2;
class Directory {
public:
static void GetDirectories(const char* directoryName,ImVector<char[MAX_PATH_BYTES]>& result,ImVector<char[MAX_FILENAME_BYTES]>* pOptionalNamesOut=NULL,Sorting sorting= SORT_ORDER_ALPHABETIC) {
result.clear();if (pOptionalNamesOut) pOptionalNamesOut->clear();
static char tempString[MAX_PATH_BYTES];size_t sz;
struct dirent **eps = NULL;
sz = strlen(directoryName);
static char directoryName2[MAX_PATH_BYTES];
strcpy(directoryName2,directoryName);
# ifdef _WIN32
if (sz>0 && directoryName[sz-1]==':') {directoryName2[sz]='\\';directoryName2[sz+1]='\0';}
# endif //_WIN32
const int n = scandir (directoryName2, &eps, DirentGetDirectories, SortingHelper::SetSorter(sorting));
static char directoryNameWithoutSlash[MAX_PATH_BYTES];
if (sz>0 && directoryName[sz-1] == '/') String::Substr(directoryName,directoryNameWithoutSlash,0,sz-1);
else strcpy(directoryNameWithoutSlash,directoryName);
if (n >= 0) {
result.reserve((size_t)n);if (pOptionalNamesOut) pOptionalNamesOut->reserve((size_t)n);
for (int cnt = 0; cnt < n; ++cnt) {
const char* pName = &eps[cnt]->d_name[0];
sz = strlen(pName);
if (sz==0) continue;
if (strcmp(pName,".")!=0 && strcmp(pName,"..")!=0 && pName[0]!='.' && pName[sz-1]!='~') {
strcpy(tempString,directoryNameWithoutSlash);
strcat(tempString,"/");
strcat(tempString,pName);
String::PushBack(result,tempString);
if (pOptionalNamesOut) String::PushBack(*pOptionalNamesOut,pName);
}
}
}
if (eps) {free(eps);eps=NULL;}
}
static void GetFiles(const char* directoryName,ImVector<char[MAX_PATH_BYTES]>& result,ImVector<char[MAX_FILENAME_BYTES]>* pOptionalNamesOut=NULL, Sorting sorting= SORT_ORDER_ALPHABETIC) {
result.clear();if (pOptionalNamesOut) pOptionalNamesOut->clear();
static char tempString[MAX_PATH_BYTES];size_t sz;
struct dirent **eps = NULL;
sz = strlen(directoryName);
static char directoryName2[MAX_PATH_BYTES];
strcpy(directoryName2,directoryName);
# ifdef _WIN32
if (sz>0 && directoryName[sz-1]==':') {directoryName2[sz]='\\';directoryName2[sz+1]='\0';}
# endif //_WIN32
const int n = scandir (directoryName2, &eps, DirentGetFiles, SortingHelper::SetSorter(sorting));
static char directoryNameWithoutSlash[MAX_PATH_BYTES];
if (sz>0 && directoryName[sz-1] == '/') String::Substr(directoryName,directoryNameWithoutSlash,0,sz-1);
else strcpy(directoryNameWithoutSlash,directoryName);
if (n >= 0) {
result.reserve((size_t)n);if (pOptionalNamesOut) pOptionalNamesOut->reserve((size_t)n);
for (int cnt = 0; cnt < n; ++cnt) {
const char* pName = &eps[cnt]->d_name[0];
sz = strlen(pName);
if (sz==0) continue;
if (pName[0]!='.' && pName[sz-1]!='~') {
strcpy(tempString,directoryNameWithoutSlash);
strcat(tempString,"/");
strcat(tempString,pName);
String::PushBack(result,tempString);
if (pOptionalNamesOut) String::PushBack(*pOptionalNamesOut,pName);
}
}
}
if (eps) {free(eps);eps=NULL;}
}
// e.g. ".txt;.jpg;.png". To use unwantedExtensions, set wantedExtensions="".
static void GetFiles(const char* path,ImVector<char[MAX_PATH_BYTES]>& files,const char* wantedExtensions,const char* unwantedExtensions=NULL,ImVector<char[MAX_FILENAME_BYTES]>* pOptionalNamesOut=NULL,Sorting sorting= SORT_ORDER_ALPHABETIC) {
ImVector<char[MAX_PATH_BYTES]> filesIn;
ImVector<char[MAX_FILENAME_BYTES]> namesIn;
GetFiles(path,filesIn,&namesIn,sorting);
if ((wantedExtensions==0 || strlen(wantedExtensions)==0) && (unwantedExtensions==0 || strlen(unwantedExtensions)==0)) {files = filesIn;return;}
files.clear();if (pOptionalNamesOut) pOptionalNamesOut->clear();
char wext[MAX_PATH_BYTES];String::ToLower(wantedExtensions,wext);
char woext[MAX_PATH_BYTES];String::ToLower(unwantedExtensions,woext);
char ext[MAX_PATH_BYTES];
if (wantedExtensions && strlen(wantedExtensions)>0) {
files.reserve(filesIn.size());if (pOptionalNamesOut) pOptionalNamesOut->reserve(namesIn.size());
ImVector<char[MAX_FILENAME_BYTES]> wExts;String::Split(wext,wExts,';');
const size_t wExtsSize = wExts.size();
if (wExtsSize>0) {
for (size_t i = 0,sz = filesIn.size();i<sz;i++) {
Path::GetExtension(filesIn[i],ext);
for (size_t e=0;e<wExtsSize;e++) {
if (strcmp(ext,wExts[e])==0) {
String::PushBack(files,filesIn[i]);
if (pOptionalNamesOut) String::PushBack(*pOptionalNamesOut,namesIn[i]);
}
}
}
}
else return;
}
else if (unwantedExtensions && strlen(unwantedExtensions)>0) {
files.reserve(filesIn.size());if (pOptionalNamesOut) pOptionalNamesOut->reserve(namesIn.size());
ImVector<char[MAX_FILENAME_BYTES]> woExts;String::Split(woext,woExts,';');
const size_t woExtsSize = woExts.size();
if (woExts.size()==0) {files = filesIn;return;}
bool match;
for (size_t i = 0,sz = filesIn.size();i<sz;i++) {
Path::GetExtension(filesIn[i],ext);
match = false;
for (size_t e=0;e<woExtsSize;e++) {
if (strcmp(ext,woExts[e])==0) {
match = true;
break;
}
}
if (!match) {
String::PushBack(files,filesIn[i]);
if (pOptionalNamesOut) String::PushBack(*pOptionalNamesOut,namesIn[i]);
}
}
}
else {
files = filesIn;
if (pOptionalNamesOut) *pOptionalNamesOut = namesIn;
}
}
inline static void Create(const char* directoryName) {
# ifndef _WIN32
const mode_t mode = S_IFDIR | S_IREAD | S_IWRITE | S_IRWXU | S_IRWXG | S_IRWXO;
mkdir(directoryName,mode);
# else //_WIN32
static wchar_t name[PATH_MAX+1];
String::utf8_to_wide(directoryName,name);
::CreateDirectoryW(name,NULL);
# endif //_WIN32
}
inline static bool Exists(const char* path) {
struct stat statbuf;
return (stat(path, &statbuf) != -1 && S_ISDIR(statbuf.st_mode));
}
inline static const ImVector<char[MAX_PATH_BYTES]> &GetUserKnownDirectories(const ImVector<char[MAX_FILENAME_BYTES]> **pOptionalUserKnownDirectoryDisplayNamesOut,const int** pOptionalNumberKnownUserDirectoriesExceptDrives=NULL,bool forceUpdate=false) {
static bool init = false;
static ImVector<char[MAX_PATH_BYTES]> rv;
static ImVector<char[MAX_FILENAME_BYTES]> dn;
static ImVector<char[MAX_PATH_BYTES]> mediaFolders;
static int numberKnownUserDirectoriesExceptDrives = 0;
if (pOptionalUserKnownDirectoryDisplayNamesOut) *pOptionalUserKnownDirectoryDisplayNamesOut = &dn;
if (pOptionalNumberKnownUserDirectoriesExceptDrives) *pOptionalNumberKnownUserDirectoriesExceptDrives = &numberKnownUserDirectoriesExceptDrives;
if (init && !forceUpdate) return rv;
init = true;
rv.clear();dn.clear();
# ifdef _WIN32
static const int csid[] = {
CSIDL_DESKTOP,
CSIDL_PERSONAL, //(Documents)
CSIDL_FAVORITES,
CSIDL_MYMUSIC,
CSIDL_MYPICTURES,
CSIDL_RECENT,
CSIDL_MYVIDEO
};
static const char* name[] = {
"Desktop",
"Documents",
"Favorites",
"Music",
"Pictures",
"Recent",
"Video"
};
static const int csidSize = sizeof(csid)/sizeof(csid[0]);
static const int nameSize = sizeof(name)/sizeof(name[0]);
IM_ASSERT(csidSize==nameSize);
if (csidSize!=nameSize) fprintf(stderr,"ERROR in file: imguifilesystem.cpp. Directory::GetUserKnownDirectories(...) csidSize!=nameSize.\n");
char tmp[MAX_PATH_BYTES] = "C:/";while (tmp[0]<='Z') {if (Directory::Exists(tmp)) String::PushBack(mediaFolders,tmp);tmp[0]=(char)((int)tmp[0]+1);}
rv.reserve(csidSize+mediaFolders.size());
dn.reserve(csidSize+mediaFolders.size());
WCHAR path[MAX_PATH+1];
for (int i=0;i<csidSize;i++) {
if (!GetSpecialFolderPathW(csid[i],&path[0],NULL)) continue;
static char tmp2[MAX_PATH_BYTES];
String::wide_to_utf8(&path[0],tmp2);
String::PushBack(rv,tmp2);
String::PushBack(dn,name[i]);
}
numberKnownUserDirectoriesExceptDrives = (int) rv.size();
static char mediaFolderName[MAX_PATH_BYTES];
for (int i=0,msz=mediaFolders.size();i<msz;i++) {
const char* mediaFolder = mediaFolders[i];
String::PushBack(rv,mediaFolder);
String::Substr(mediaFolder,mediaFolderName,0,strlen(mediaFolder)-1);
String::PushBack(dn,mediaFolderName);
}
# else //_WIN32
const char* homedir = NULL;
if ((homedir = getenv("HOME")) == NULL) {
homedir = getpwuid(getuid())->pw_dir;
}
if (homedir==NULL) return rv;
char homeString[MAX_PATH_BYTES];strcpy(homeString,homedir);
char userString[MAX_PATH_BYTES];Path::GetFileName(homeString,userString);
// Known folders ---------------------------------------------
static const char folder[][MAX_FILENAME_BYTES] = {
"Desktop",
"Documents",
"Downloads",
"Music",
"Pictures",
"Videos"
};
static const int folderSize = sizeof(folder)/sizeof(folder[0]);
rv.reserve(folderSize+1);
dn.reserve(rv.size());
String::PushBack(rv,homeString);
char temp[MAX_PATH_BYTES];
strcpy(temp,"Home");
String::PushBack(dn,temp);
for (int i=0;i<folderSize;i++) {
Path::Combine(homeString,folder[i],temp,false);
if (Directory::Exists(temp)) {
String::PushBack(rv,temp);
String::PushBack(dn,folder[i]);
}
}
numberKnownUserDirectoriesExceptDrives = (int) rv.size();
// Additional Drives --------------------------------------------
static const char* mountLocations[] = {"/media","/mnt","/Volumes","/vol","/data"};
static const int mountLocationSize = sizeof(mountLocations)/sizeof(mountLocations[0]);
static const bool ifHomeSubfolerIsFoundInMountLocationsForgetThatRootMountLocation = true; // That means: if "/media/myusername" exists, don't add any other "/media/..." entries.
char userMediaString[MAX_PATH_BYTES];bool lastGood = false;
for (int mntLocIndex=0,sz = 2*mountLocationSize;mntLocIndex<sz;mntLocIndex++) {
const int index = mntLocIndex/2;
const char* mntLocString = mountLocations[index];
const bool useUserSuffix = (mntLocIndex%2)==0;
if (useUserSuffix) {
Path::Combine(mntLocString,userString,userMediaString,false);
strcpy(temp,userMediaString);
}
else if (lastGood && ifHomeSubfolerIsFoundInMountLocationsForgetThatRootMountLocation) {lastGood = false;continue;} // see "ifHomeSubfolerIsFoundInMountLocationsForgetThatRootMountLocation" above
else strcpy(userMediaString,mntLocString);
lastGood = Directory::Exists(userMediaString);
if (!lastGood) continue;
Directory::GetDirectories(userMediaString,mediaFolders);
if (mediaFolders.size()==0) continue;
rv.reserve(rv.size()+mediaFolders.size());
dn.reserve(rv.size());
for (int i=0,msz=mediaFolders.size();i<msz;i++) {
if (strcmp(mediaFolders[i],temp)==0) continue; // I we have processed "/media/myusername" once cycle before, exclude it from processing "/media" subfolders
String::PushBack(rv,mediaFolders[i]);
static char tmp[MAX_FILENAME_BYTES];
Path::GetFileName(mediaFolders[i],tmp);
String::PushBack(dn,tmp);
}
}
# endif //_WIN32
return rv;
}
protected:
Directory() {}
static int DirentGetDirectories(const struct dirent *de) {
if (de->d_type==DT_DIR) return 1;
return 0;
}
static int DirentGetFiles(const struct dirent *de) {
if (de->d_type==DT_REG) return 1;
return 0;
}
# ifdef _WIN32
static bool GetSpecialFolderPathW(int specialFolderCSIDL,WCHAR* pathOutWithSizeMaxPathPlusOne,HWND parent) {
// CSIDL_DESKTOP,CSIDL_BITBUCKET,CSIDL_CONTROLS,CSIDL_DESKTOP,CSIDL_DESKTOPDIRECTORY,
// CSIDL_DRIVES,CSIDL_FONTS,CSIDL_NETHOOD,CSIDL_NETWORK,CSIDL_PERSONAL (Documents)
// CSIDL_PRINTERS,CSIDL_PROGRAMS,CSIDL_RECENT,CSIDL_SENDTO,CSIDL_STARTMENU,
// CSIDL_STARTUP,CSIDL_TEMPLATES
// CSIDL_INTERNET_CACHE,CSIDL_COOKIES,CSIDL_HISTORY,CSIDL_COMMON_APPDATA,
// CSIDL_WINDOWS,CSIDL_SYSTEM,CSIDL_PROGRAM_FILES,CSIDL_MYPICTURES,...
WCHAR* temp_path = pathOutWithSizeMaxPathPlusOne;//[MAX_PATH+1];
temp_path[0]=L'\0';
LPITEMIDLIST pidl=NULL;
if (!SUCCEEDED(::SHGetSpecialFolderLocation(parent,specialFolderCSIDL, &pidl)))
{
temp_path[0]=L'\0';return false;
}
bool ok=SUCCEEDED(::SHGetPathFromIDListW(pidl,&temp_path[0]));
LPMALLOC mal = NULL;
if ( ::SHGetMalloc( & mal ) == E_FAIL || !mal ) ::free( pidl );
else
{
mal->Free( pidl );
mal->Release();
}
if (!ok)
{
temp_path[0]=L'\0';return false;
}
return true;
}
# endif //_WIN32
};
// End definitions of some helper classes----------------------------------------------------------------------------------------
// Internal usage----------------------------------------------------------------------------------------
struct FolderInfo {
char fullFolder[MAX_PATH_BYTES];
char currentFolder[MAX_PATH_BYTES];
int splitPathIndex;
static ImVector<char[MAX_FILENAME_BYTES]> SplitPath; // tmp field used internally
void display() const {
fprintf(stderr,"fullFolder=\"%s\" currentFolder=\"%s\" splitPathIndex=%d\n",fullFolder,currentFolder,splitPathIndex);
}
void getSplitPath(ImVector<char[MAX_FILENAME_BYTES]>& splitPath) const {
Path::Split(fullFolder,splitPath);
}
const FolderInfo& operator=(const FolderInfo& o) {
strcpy(currentFolder,o.currentFolder);
strcpy(fullFolder,o.fullFolder);
splitPathIndex = o.splitPathIndex;
return *this;
}
inline void reset() {
currentFolder[0]='\0';fullFolder[0]='\0';splitPathIndex=-1;
}
FolderInfo() {reset();}
FolderInfo(const FolderInfo& o) {*this=o;}
void fromCurrentFolder(const char* path) {
if (!path || strlen(path)==0) reset();
else {
strcpy(currentFolder,path);
strcpy(fullFolder,path);
Path::Split(fullFolder,SplitPath);
splitPathIndex = (int) SplitPath.size()-1;
}
}
bool isEqual(const FolderInfo& fi) const {
return strcmp(fullFolder,fi.fullFolder)==0 && strcmp(currentFolder,fi.currentFolder)==0;
}
bool isEqual(const char* path) const {
return strcmp(fullFolder,path)==0 && strcmp(currentFolder,path)==0;
}
int getSplitPathIndexFor(const char* path) const {
if (!path || strncmp(path,fullFolder,strlen(path))!=0) return -1;
Path::Split(fullFolder,SplitPath);
char tmp[MAX_PATH_BYTES];tmp[0]='\0';
for (int i=0,sz=(int)SplitPath.size();i<sz;i++) {
Path::Append(SplitPath[i],tmp);
//fprintf(stderr,"%d) \"%s\" <-> \"%s\"\n",i,tmp,path);
if (strcmp(tmp,path)==0) return i;
}
return -1;
}
bool getFolderInfoForSplitPathIndex(int _splitPathIndex,FolderInfo& rv) const {
Path::Split(fullFolder,SplitPath);
const int splitPathSize = (int)SplitPath.size();
if (_splitPathIndex<0 || _splitPathIndex>=splitPathSize) return false;
rv = *this;
rv.splitPathIndex = _splitPathIndex;
rv.currentFolder[0]='\0';
if (_splitPathIndex>=0 && _splitPathIndex<splitPathSize) {
for (int i=0;i<=_splitPathIndex;i++) {
Path::Append(SplitPath[i],rv.currentFolder);
//fprintf(stderr,"%d) \"%s\" (\"%s\")\n",i,rv.currentFolder,SplitPath[i]);
}
}
/*fprintf(stderr,"getFolderInfoForSplitPathIndex(%d):\nSource: ",_splitPathIndex);
this->display();
fprintf(stderr,"Result: ");
rv.display();*/
return true;
}
};
ImVector<char[MAX_FILENAME_BYTES]> FolderInfo::SplitPath; // tmp field used internally
struct History {
protected:
ImVector<FolderInfo> info;
int currentInfoIndex; // into info
public:
inline bool canGoBack() {
return currentInfoIndex>0;
}
inline bool canGoForward() {
return currentInfoIndex>=0 && currentInfoIndex<(int)info.size()-1;
}
void reset() {info.clear();currentInfoIndex=-1;}
History() {reset();}
// -------------------------------------------------------------------------------------------------
void goBack() {
if (canGoBack()) --currentInfoIndex;
}
void goForward() {
if (canGoForward()) ++currentInfoIndex;
}
bool switchTo(const char* currentFolder) {
if (!currentFolder || strlen(currentFolder)==0) return false;
if (currentInfoIndex<0) {
++currentInfoIndex;
info.resize(currentInfoIndex+1);
FolderInfo& fi = info[currentInfoIndex];
fi.fromCurrentFolder(currentFolder);
return true;
}
else {
const FolderInfo& lastInfo = info[currentInfoIndex];
if (lastInfo.isEqual(currentFolder)) return false;
const int splitPathIndexInsideLastInfo = lastInfo.getSplitPathIndexFor(currentFolder);
++currentInfoIndex;
info.resize(currentInfoIndex+1);
FolderInfo& fi = info[currentInfoIndex];
if (splitPathIndexInsideLastInfo==-1) fi.fromCurrentFolder(currentFolder);
else {
fi = lastInfo;
fi.splitPathIndex = splitPathIndexInsideLastInfo;
strcpy(fi.currentFolder,currentFolder);
}
return true;
}
}
bool switchTo(const FolderInfo& fi) {
if (!fi.currentFolder || strlen(fi.currentFolder)==0) return false;
if (currentInfoIndex>=0) {
const FolderInfo& lastInfo = info[currentInfoIndex];
if (lastInfo.isEqual(fi)) return false;
}
++currentInfoIndex;
info.resize(currentInfoIndex+1);
info[currentInfoIndex] = fi;
return true;
}
//-----------------------------------------------------------------------------------------------------
inline bool isValid() const {return (currentInfoIndex>=0 && currentInfoIndex<(int)info.size());}
const FolderInfo* getCurrentFolderInfo() const {return isValid() ? &info[currentInfoIndex] : NULL;}
const char* getCurrentFolder() const {return isValid() ? &info[currentInfoIndex].currentFolder[0] : NULL;}
bool getCurrentSplitPath(ImVector<char[MAX_FILENAME_BYTES]>& rv) const {
if (isValid()) {
info[currentInfoIndex].getSplitPath(rv);
return true;
}
else return false;
}
const int* getCurrentSplitPathIndex() const {return isValid() ? &info[currentInfoIndex].splitPathIndex : NULL;}
size_t getInfoSize() const {return info.size();}
};
struct Internal {
ImVector<char[MAX_PATH_BYTES]> dirs,files;
ImVector<char[MAX_FILENAME_BYTES]> dirNames,fileNames,currentSplitPath;
char currentFolder[MAX_PATH_BYTES];
bool forceRescan;
bool open;
ImVec2 wndPos;
ImVec2 wndSize;
char wndTitle[MAX_PATH_BYTES];
int sortingMode;
History history;
//-----------------------------------------------------
bool isSelectFolderDialog;
bool isSaveFileDialog;
bool allowDirectoryCreation,forbidDirectoryCreation;
bool allowKnownDirectoriesSection;
char newDirectoryName[MAX_FILENAME_BYTES];
char saveFileName[MAX_FILENAME_BYTES];
//----------------------------------------------------
char chosenPath[MAX_PATH_BYTES];
bool rescan;
int uniqueNumber;
ImGuiTextFilter filter;
bool allowFiltering;
int totalNumBrowsingEntries;
int numBrowsingColumns;
int numBrowsingEntriesPerColumn;
static bool BrowsingPerRow;
bool allowDisplayByOption;
bool detectKnownDirectoriesAtEveryOpening;
bool mustFilterSaveFilePathWithFileFilterExtensionString;
bool editLocationCheckButtonPressed;
char editLocationInputText[MAX_PATH_BYTES];
void resetVariables() {
strcpy(currentFolder,"./");
forceRescan = false;
open = true;
wndTitle[0] = '\0';
sortingMode = 0;
history.reset();
isSelectFolderDialog = false;
isSaveFileDialog = false;
allowDirectoryCreation = true;
forbidDirectoryCreation = false;
strcpy(newDirectoryName,"New Folder");
saveFileName[0] = '\0';
uniqueNumber = 0;
rescan = true;
chosenPath[0] = '\0';
filter.Clear();
allowFiltering = false;
totalNumBrowsingEntries = 0;
numBrowsingColumns = 1;
numBrowsingEntriesPerColumn = 1000;
detectKnownDirectoriesAtEveryOpening = false;
allowDisplayByOption = false;
allowKnownDirectoriesSection = true;
mustFilterSaveFilePathWithFileFilterExtensionString = true;
editLocationCheckButtonPressed = false;
strcpy(editLocationInputText,"\0");
}
// Just a convenience enum used internally
enum Color {
ImGuiCol_Dialog_Directory_Background,
ImGuiCol_Dialog_Directory_Hover,
ImGuiCol_Dialog_Directory_Pressed,
ImGuiCol_Dialog_Directory_Text,
ImGuiCol_Dialog_File_Background,
ImGuiCol_Dialog_File_Hover,
ImGuiCol_Dialog_File_Pressed,
ImGuiCol_Dialog_File_Text,
ImGuiCol_Dialog_SelectedFolder_Text,
ImGuiCol_Dialog_Size
};
inline static void ColorCombine(ImVec4& c,const ImVec4& r,const ImVec4& factor) {
const float rr = (r.x+r.y+r.z)*0.3334f;
c.x = rr * factor.x;c.y = rr * factor.y;c.z = rr * factor.z;c.w = r.w;
}
};
bool Internal::BrowsingPerRow = false;
// End Internal Usage-------------------------------------------------------------------------------------
Dialog::Dialog(bool noKnownDirectoriesSection,bool noCreateDirectorySection,bool noFilteringSection,bool detectKnownDirectoriesAtEachOpening,bool addDisplayByOption,bool dontFilterSaveFilePathsEnteredByTheUser) {
internal = (Internal*) ImGui::MemAlloc(sizeof(Internal));
new(internal) Internal();
internal->resetVariables();
static int un = 0;
internal->uniqueNumber = un++;
internal->detectKnownDirectoriesAtEveryOpening = detectKnownDirectoriesAtEachOpening;
internal->allowDisplayByOption = addDisplayByOption;
internal->forbidDirectoryCreation = noCreateDirectorySection;
internal->allowKnownDirectoriesSection = !noKnownDirectoriesSection;
internal->allowFiltering = !noFilteringSection;
internal->mustFilterSaveFilePathWithFileFilterExtensionString = !dontFilterSaveFilePathsEnteredByTheUser;
}
Dialog::~Dialog() {
if (internal) {
ImGui::MemFree(internal);
//delete internal;
internal = NULL;
}
}
const char* Dialog::getChosenPath() const {return internal->chosenPath;}
const char* Dialog::getLastDirectory() const {return internal->currentFolder;}
// -- from imgui.cpp --
static size_t ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int w = vsnprintf(buf, buf_size, fmt, args);
va_end(args);
buf[buf_size-1] = 0;
return (w == -1) ? buf_size : (size_t)w;
}
// ---------------------
// 90% of the functionality of the whole imguifilesystem.cpp is inside this single method
const char* ChooseFileMainMethod(Dialog& ist,const char* directory,const bool _isFolderChooserDialog,const bool _isSaveFileDialog,const char* _saveFileName,const char* fileFilterExtensionString,const char* windowTitle,const ImVec2& windowSize,const ImVec2& windowPos,const float windowAlpha) {
//-----------------------------------------------------------------------------
Internal& I = *ist.internal;
char* rv = I.chosenPath;rv[0] = '\0';
//-----------------------------------------------------
bool& isSelectFolderDialog = I.isSelectFolderDialog = _isFolderChooserDialog;
bool& isSaveFileDialog = I.isSaveFileDialog = _isSaveFileDialog;
bool& allowDirectoryCreation = I.allowDirectoryCreation = I.forbidDirectoryCreation ? false : (isSelectFolderDialog || isSaveFileDialog);
//----------------------------------------------------------
static const int* pNumberKnownUserDirectoriesExceptDrives=NULL;
static const ImVector<char[MAX_FILENAME_BYTES]>* pUserKnownDirectoryDisplayNames=NULL;
static const ImVector<char[MAX_PATH_BYTES]>* pUserKnownDirectories = &Directory::GetUserKnownDirectories(&pUserKnownDirectoryDisplayNames,&pNumberKnownUserDirectoriesExceptDrives);
//----------------------------------------------------------
const ImGuiStyle& style = ImGui::GetStyle();
ImVec4 dummyButtonColor(0.0f,0.0f,0.0f,0.5f); // Only the alpha is twickable from here
static ImVec4 ColorSet[Internal::ImGuiCol_Dialog_Size];
// Fill ColorSet above and fix dummyButtonColor here
{
static const ImVec4 df(0.9,0.9,0.3,1); // directory color factor
static const ImVec4 ff(0.7,0.7,0.7,1); // file color factor
for (int i=0,sz=(int)Internal::ImGuiCol_Dialog_Directory_Text;i<=sz;i++) {
ImVec4& c = ColorSet[i];
const ImVec4& r = style.Colors[i<sz ? ((int)ImGuiCol_Button + i) : ImGuiCol_Text];
Internal::ColorCombine(c,r,df);
}
for (int i=(int)Internal::ImGuiCol_Dialog_File_Background,sz=(int)Internal::ImGuiCol_Dialog_File_Text;i<=sz;i++) {
ImVec4& c = ColorSet[i];
const ImVec4& r = style.Colors[i<sz ? ((int)ImGuiCol_Button-(int)Internal::ImGuiCol_Dialog_File_Background + i) : ImGuiCol_Text];
Internal::ColorCombine(c,r,ff);
}
if (dummyButtonColor.w>0) {
const ImVec4& bbc = style.Colors[ImGuiCol_Button];
dummyButtonColor.x = bbc.x;dummyButtonColor.y = bbc.y;dummyButtonColor.z = bbc.z;dummyButtonColor.w *= bbc.w;
}
}
if (I.rescan) {
char validDirectory[MAX_PATH_BYTES];validDirectory[0]='\0'; // for robustness
if (directory && strlen(directory)>0) {
if (Directory::Exists(directory)) strcpy(validDirectory,directory);
else {
Path::GetDirectoryName(directory,validDirectory);
if (!Directory::Exists(validDirectory)) validDirectory[0]='\0';
}
}
Path::GetAbsolutePath(validDirectory,I.currentFolder);
I.editLocationCheckButtonPressed = false;
I.history.reset(); // reset history
I.history.switchTo(I.currentFolder); // init history
I.dirs.clear();I.files.clear();I.dirNames.clear();I.fileNames.clear();I.currentSplitPath.clear();
strcpy(&I.newDirectoryName[0],"New Folder");
if (_saveFileName) {
//strcpy(&I.saveFileName[0],_saveFileName);
Path::GetFileName(_saveFileName,I.saveFileName); // Better!
}
else I.saveFileName[0]='\0';
isSelectFolderDialog = _isFolderChooserDialog;
isSaveFileDialog = _isSaveFileDialog;
allowDirectoryCreation = I.forbidDirectoryCreation ? false : (isSelectFolderDialog || isSaveFileDialog);
if (isSelectFolderDialog && I.sortingMode>SORT_ORDER_LAST_MODIFICATION_INVERSE) I.sortingMode = 0;
I.forceRescan = true;
I.open = true;
I.filter.Clear();
if (!windowTitle || strlen(windowTitle)==0) {
if (isSelectFolderDialog) strcpy(I.wndTitle,"Please select a folder");
else if (isSaveFileDialog) strcpy(I.wndTitle,"Please choose/create a file for saving");
else strcpy(I.wndTitle,"Please choose a file");
}
else strcpy(I.wndTitle,windowTitle);
strcat(I.wndTitle,"##");
char tmpWndTitleNumber[12];
ImFormatString(tmpWndTitleNumber,11,"%d",I.uniqueNumber);
strcat(I.wndTitle,tmpWndTitleNumber);
I.wndPos = windowPos;
I.wndSize = windowSize;
if (I.wndSize.x<=0) I.wndSize.x = 400;
if (I.wndSize.y<=0) I.wndSize.y = 400;
const ImVec2 mousePos = ImGui::GetMousePos();//
ImGui::GetCursorPos();
if (I.wndPos.x<=0) I.wndPos.x = mousePos.x - I.wndSize.x*0.5f;
if (I.wndPos.y<=0) I.wndPos.y = mousePos.y - I.wndSize.y*0.5f;
const ImVec2 screenSize = ImGui::GetIO().DisplaySize;
if (I.wndPos.x>screenSize.x-I.wndSize.x) I.wndPos.x = screenSize.x-I.wndSize.x;
if (I.wndPos.y>screenSize.y-I.wndSize.y) I.wndPos.y = screenSize.y-I.wndSize.y;
if (I.wndPos.x < 0) I.wndPos.x = 0;
if (I.wndPos.y < 0) I.wndPos.y = 0;
//fprintf(stderr,"screenSize = %f,%f mousePos = %f,%f wndPos = %f,%f wndSize = %f,%f\n",screenSize.x,screenSize.y,mousePos.x,mousePos.y,wndPos.x,wndPos.y,wndSize.x,wndSize.y);
if (I.detectKnownDirectoriesAtEveryOpening) pUserKnownDirectories = &Directory::GetUserKnownDirectories(&pUserKnownDirectoryDisplayNames,&pNumberKnownUserDirectoriesExceptDrives,true);
}
if (!I.open) return rv;
if (I.forceRescan) {
I.forceRescan = false;
const int sortingModeForDirectories = (I.sortingMode <= (int)SORT_ORDER_LAST_MODIFICATION_INVERSE) ? I.sortingMode : (I.sortingMode%2);
Directory::GetDirectories(I.currentFolder,I.dirs,&I.dirNames,(Sorting)sortingModeForDirectories); // this is because directories don't return their size or their file extensions (so if needed we sort them alphabetically)
//I.dirNames.resize(I.dirs.size());for (int i=0,sz=I.dirs.size();i<sz;i++) Path::GetFileName(I.dirs[i],(char*)I.dirNames[i]);
if (!isSelectFolderDialog) {
if (!fileFilterExtensionString || strlen(fileFilterExtensionString)==0) Directory::GetFiles(I.currentFolder,I.files,&I.fileNames,(Sorting)I.sortingMode);
else Directory::GetFiles(I.currentFolder,I.files,fileFilterExtensionString,NULL,&I.fileNames,(Sorting)I.sortingMode);
//I.fileNames.resize(I.files.size());for (int i=0,sz=I.files.size();i<sz;i++) Path::GetFileName(I.files[i],(char*)I.fileNames[i]);
}
else {
I.files.clear();I.fileNames.clear();
I.saveFileName[0]='\0';
char currentFolderName[MAX_FILENAME_BYTES];
Path::GetFileName(I.currentFolder,currentFolderName);
const size_t currentFolderNameSize = strlen(currentFolderName);
if (currentFolderNameSize==0 || currentFolderName[currentFolderNameSize-1]==':') strcat(currentFolderName,"/");
strcat(I.saveFileName,currentFolderName);
}
I.history.getCurrentSplitPath(I.currentSplitPath);
const static int approxNumEntriesPerColumn = 20;//(int) (20.f / browseSectionFontScale);// tweakable
I.totalNumBrowsingEntries = (int)(I.dirs.size()+I.files.size());
I.numBrowsingColumns = I.totalNumBrowsingEntries/approxNumEntriesPerColumn;
if (I.numBrowsingColumns<=0) I.numBrowsingColumns = 1;
if (I.totalNumBrowsingEntries%approxNumEntriesPerColumn>(approxNumEntriesPerColumn/2)) ++I.numBrowsingColumns;
if (I.numBrowsingColumns>6) I.numBrowsingColumns = 6;
I.numBrowsingEntriesPerColumn = I.totalNumBrowsingEntries/I.numBrowsingColumns;
if (I.totalNumBrowsingEntries%I.numBrowsingColumns!=0) ++I.numBrowsingEntriesPerColumn;
//# define DEBUG_HISTORY
# ifdef DEBUG_HISTORY
if (I.history.getInfoSize()>0) fprintf(stderr,"\nHISTORY: currentFolder:\"%s\" history.canGoBack=%s history.canGoForward=%s currentHistory:\n",I.currentFolder,I.history.canGoBack()?"true":"false",I.history.canGoForward()?"true":"false");
if (I.history.getCurrentFolderInfo()) I.history.getCurrentFolderInfo()->display();
# endif //DEBUG_HISTORY
}
if (I.rescan) {
I.rescan = false; // Mandatory
ImGui::Begin(I.wndTitle, &I.open, I.wndSize,windowAlpha);
ImGui::SetWindowPos(I.wndPos);
ImGui::SetWindowSize(I.wndSize);
//fprintf(stderr,"\"%s\" wndPos={%1.2f,%1.2f}\n",wndTitle.c_str(),wndPos.x,wndPos.y);
}
else ImGui::Begin(I.wndTitle, &I.open,ImVec2(0,0),windowAlpha);
ImGui::Separator();
//------------------------------------------------------------------------------------
// History (=buttons: < and >)
{
bool historyBackClicked = false;
bool historyForwardClicked = false;
// history -----------------------------------------------
ImGui::PushID("historyDirectoriesID");
const bool historyCanGoBack = I.history.canGoBack();
const bool historyCanGoForward = I.history.canGoForward();
if (!historyCanGoBack) {
ImGui::PushStyleColor(ImGuiCol_Button,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,dummyButtonColor);
}
historyBackClicked = ImGui::Button("<")&historyCanGoBack;
ImGui::SameLine();
if (!historyCanGoBack) {
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
if (!historyCanGoForward) {
ImGui::PushStyleColor(ImGuiCol_Button,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,dummyButtonColor);
}
historyForwardClicked = ImGui::Button(">")&historyCanGoForward;
ImGui::SameLine();
if (!historyCanGoForward) {
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
ImGui::PopID();
// -------------------------------------------------------
if (historyBackClicked || historyForwardClicked) {
ImGui::End();
if (historyBackClicked) I.history.goBack();
else if (historyForwardClicked) I.history.goForward();
I.forceRescan = true;
strcpy(I.currentFolder,I.history.getCurrentFolder());
strcpy(I.editLocationInputText,I.currentFolder);
# ifdef DEBUG_HISTORY
if (historyBackClicked) fprintf(stderr,"\nPressed BACK to\t");
else fprintf(stderr,"\nPressed FORWARD to\t");
fprintf(stderr,"\"%s\" (%d)\n",I.currentFolder,(int)*I.history.getCurrentSplitPathIndex());
# undef DEBUG_HISTOTY
# endif //DEBUG_HISTORY
return rv;
}
}
//------------------------------------------------------------------------------------
// Edit Location CheckButton
bool editLocationInputTextReturnPressed = false;
{
bool mustValidateInputPath = false;
ImGui::PushStyleColor(ImGuiCol_Button,I.editLocationCheckButtonPressed ? dummyButtonColor : style.Colors[ImGuiCol_Button]);
if (ImGui::Button("L##EditLocationCheckButton")) {
I.editLocationCheckButtonPressed = !I.editLocationCheckButtonPressed;
if (I.editLocationCheckButtonPressed) {
strcpy(I.editLocationInputText,I.currentFolder);
ImGui::SetKeyboardFocusHere();
}
//if (!I.editLocationCheckButtonPressed) mustValidateInputPath = true; // or not ? I mean: the user wants to quit or to validate in this case ?
}
ImGui::PopStyleColor();
if (I.editLocationCheckButtonPressed) {
ImGui::SameLine();
editLocationInputTextReturnPressed = ImGui::InputText("##EditLocationInputText",I.editLocationInputText,MAX_PATH_BYTES,ImGuiInputTextFlags_AutoSelectAll|ImGuiInputTextFlags_EnterReturnsTrue);
if (editLocationInputTextReturnPressed) mustValidateInputPath = true;
else ImGui::Separator();
}
if (mustValidateInputPath) {
// it's better to clean the input path here from trailing slashes:
char cleanEnteredPath[MAX_PATH_BYTES];
strcpy(cleanEnteredPath,I.editLocationInputText);
size_t len = strlen(cleanEnteredPath);
while (len>0 && (cleanEnteredPath[len-1]=='/' || cleanEnteredPath[len-1]=='\\')) {cleanEnteredPath[len-1]='\0';len = strlen(cleanEnteredPath);}
if (len==0 || strcmp(I.currentFolder,cleanEnteredPath)==0) I.editLocationCheckButtonPressed = false;
else if (Directory::Exists(cleanEnteredPath)) {
I.editLocationCheckButtonPressed = false; // Optional (return to split-path buttons)
//----------------------------------------------------------------------------------
I.history.switchTo(cleanEnteredPath);
strcpy(I.currentFolder,cleanEnteredPath);
I.forceRescan = true;
}
//else fprintf(stderr,"mustValidateInputPath NOOP: \"%s\" \"%s\"\n",I.currentFolder,cleanEnteredPath);
}
else ImGui::SameLine();
}
//------------------------------------------------------------------------------------
// Split Path control
if (!I.editLocationCheckButtonPressed && !editLocationInputTextReturnPressed) {
bool mustSwitchSplitPath = false;
const FolderInfo& fi = *I.history.getCurrentFolderInfo();
ImVec2& framePadding = ImGui::GetStyle().FramePadding;
const float originalFramePaddingX = framePadding.x;
framePadding.x = 0;
// Split Path
// Tab:
{
//-----------------------------------------------------
// TAB LABELS
//-----------------------------------------------------
{
const int numTabs=(int) I.currentSplitPath.size();
int newSelectedTab = fi.splitPathIndex;
for (int t=0;t<numTabs;t++) {
if (t>0) ImGui::SameLine(0,0);
if (t==fi.splitPathIndex) {
ImGui::PushStyleColor(ImGuiCol_Button,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,dummyButtonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,dummyButtonColor);
}
ImGui::PushID(&I.currentSplitPath[t]);
const bool pressed = ImGui::Button(I.currentSplitPath[t]);
ImGui::PopID();
if (pressed) {
if (fi.splitPathIndex!=t && !mustSwitchSplitPath) mustSwitchSplitPath = true;
newSelectedTab = t;
}
if (t==fi.splitPathIndex) {
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
}
if (mustSwitchSplitPath) {
FolderInfo mfi;
fi.getFolderInfoForSplitPathIndex(newSelectedTab,mfi);
I.history.switchTo(mfi);
I.forceRescan = true;
strcpy(I.currentFolder,I.history.getCurrentFolder());
strcpy(I.editLocationInputText,I.currentFolder);
//fprintf(stderr,"%s\n",I.currentFolder);
}
}
}
framePadding.x = originalFramePaddingX;
}
//------------------------------------------------------------------------------------
// Start collapsable regions----------------------------------------------------------
// User Known directories-------------------------------------------------------------
if (I.allowKnownDirectoriesSection && pUserKnownDirectories->size()>0) {
ImGui::Separator();
if (ImGui::CollapsingHeader("Known Directories##UserKnownDirectories")) {
static int id;
ImGui::PushID(&id);
ImGui::PushStyleColor(ImGuiCol_Text,ColorSet[Internal::ImGuiCol_Dialog_Directory_Text]);
ImGui::PushStyleColor(ImGuiCol_Button,ColorSet[Internal::ImGuiCol_Dialog_Directory_Background]);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ColorSet[Internal::ImGuiCol_Dialog_Directory_Hover]);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,ColorSet[Internal::ImGuiCol_Dialog_Directory_Pressed]);
for (int i=0,sz=(int)pUserKnownDirectories->size();i<sz;i++) {
const char* userKnownFolder = (*pUserKnownDirectories)[i];
const char* userKnownFolderDisplayName = (*pUserKnownDirectoryDisplayNames)[i];
if (ImGui::SmallButton(userKnownFolderDisplayName) && strcmp(userKnownFolder,I.currentFolder)!=0) {
strcpy(I.currentFolder,userKnownFolder);
strcpy(I.editLocationInputText,I.currentFolder);
I.history.switchTo(I.currentFolder);
I.forceRescan = true;
//------------------------------------------------------------------------------------------------------------------------------
}
if (i!=sz-1 && (i>=*pNumberKnownUserDirectoriesExceptDrives || i%7!=6)) ImGui::SameLine();
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopID();
}
}
// End User Known directories---------------------------------------------------------
// Allow directory creation ----------------------------------------------------------
if (allowDirectoryCreation) {
ImGui::Separator();
bool mustCreate = false;
if (ImGui::CollapsingHeader("New Directory##allowDirectoryCreation")) {
static int id;
ImGui::PushID(&id);
ImGui::InputText("##createNewFolderName",&I.newDirectoryName[0],MAX_FILENAME_BYTES);
ImGui::SameLine();
mustCreate = ImGui::Button("CREATE");
ImGui::PopID();
}
if (mustCreate && strlen(I.newDirectoryName)>0) {
char newDirPath[MAX_PATH_BYTES];
Path::Combine(I.currentFolder,I.newDirectoryName,newDirPath,false);
if (!Directory::Exists(newDirPath)) {
//# define SIMULATING_ONLY
# ifdef SIMULATING_ONLY
fprintf(stderr,"creating: \"%s\"\n",newDirPath);
# undef SIMULATING_ONLY
# else //SIMULATING_ONLY
Directory::Create(newDirPath);
if (!Directory::Exists(newDirPath)) fprintf(stderr,"Error creating new folder: \"%s\"\n",newDirPath);
else I.forceRescan = true; // Just update
# endif //SIMULATING_ONLY
}
}
}
// End allow directory creation ------------------------------------------------------
// Filtering entries -----------------------------------------------------------------
if (I.allowFiltering) {
ImGui::Separator();
if (ImGui::CollapsingHeader("Filtering##fileNameFiltering")) {
static int id;
ImGui::PushID(&id);
I.filter.Draw();
ImGui::PopID();
}
}
// End filtering entries -------------------------------------------------------------
// End collapsable regions------------------------------------------------------------
// Selection field -------------------------------------------------------------------
if (isSaveFileDialog || isSelectFolderDialog) {
ImGui::Separator();
bool selectionButtonPressed = false;
static int id;
ImGui::PushID(&id);
if (isSaveFileDialog) {
ImGui::AlignFirstTextHeightToWidgets();
ImGui::Text("File:");ImGui::SameLine();
ImGui::InputText("##saveFileName",&I.saveFileName[0],MAX_FILENAME_BYTES);
ImGui::SameLine();
}
else {
ImGui::AlignFirstTextHeightToWidgets();
ImGui::Text("Folder:");ImGui::SameLine();
static const ImVec4 sf(1.0,0.8,0.5,1); // delected folder color factor
ImVec4& c = ColorSet[Internal::ImGuiCol_Dialog_SelectedFolder_Text];
const ImVec4& r = style.Colors[ImGuiCol_Text];
Internal::ColorCombine(c,r,sf);
ImGui::TextColored(ColorSet[Internal::ImGuiCol_Dialog_SelectedFolder_Text],"%s",&I.saveFileName[0],MAX_FILENAME_BYTES);
ImGui::SameLine();
}
if (isSelectFolderDialog) selectionButtonPressed = ImGui::Button("Select");
else selectionButtonPressed = ImGui::Button("Save");
ImGui::PopID();
if (selectionButtonPressed) {
if (isSelectFolderDialog) {
strcpy(rv,I.currentFolder);
I.open = true;
}
else if (isSaveFileDialog) {
if (strlen(I.saveFileName)>0) {
bool pathOk = true;
if (I.mustFilterSaveFilePathWithFileFilterExtensionString && fileFilterExtensionString && strlen(fileFilterExtensionString)>0) {
pathOk = false;
char saveFileNameExtension[MAX_FILENAME_BYTES];Path::GetExtension(I.saveFileName,saveFileNameExtension);
const bool saveFileNameHasExtension = strlen(saveFileNameExtension)>0;
//-------------------------------------------------------------------
ImVector<char[MAX_FILENAME_BYTES]> wExts;String::Split(fileFilterExtensionString,wExts,';');
const size_t wExtsSize = wExts.size();
if (!saveFileNameHasExtension) {
if (wExtsSize==0) pathOk = true; // Bad situation, better allow this case
else strcat(I.saveFileName,wExts[0]);
}
else {
// saveFileNameHasExtension
for (size_t i = 0;i<wExtsSize;i++) {
const char* ext = wExts[i];
if (strcmp(ext,saveFileNameExtension)==0) {
pathOk = true;
break;
}
}
if (!pathOk && wExtsSize>0) strcat(I.saveFileName,wExts[0]);
}
}
if (pathOk) {
char savePath[MAX_PATH_BYTES];
Path::Combine(I.currentFolder,I.saveFileName,savePath,false);
strcpy(rv,savePath);
I.open = true;
}
}
}
}
//ImGui::Spacing();
}
// End selection field----------------------------------------------------------------
ImGui::Separator();
// sorting --------------------------------------------------------------------
ImGui::Text("Sorting by: ");ImGui::SameLine();
{
const int oldSortingMode = I.sortingMode;
const int oldSelectedTab = I.sortingMode/2;
//-----------------------------------------------------
// TAB LABELS
//-----------------------------------------------------
{
static const int numTabs=(int)SORT_ORDER_COUNT/2;
int newSortingMode = oldSortingMode;
static const char* names[numTabs] = {"Name","Modified","Size","Type"};
const int numUsedTabs = isSelectFolderDialog ? 2 : numTabs;
for (int t=0;t<numUsedTabs;t++) {
if (t>0) ImGui::SameLine();
if (t==oldSelectedTab) {
ImGui::PushStyleColor(ImGuiCol_Button,dummyButtonColor);
}
ImGui::PushID(&names[t]);
const bool pressed = ImGui::SmallButton(names[t]);
ImGui::PopID();
if (pressed) {
if (oldSelectedTab==t) {
newSortingMode = oldSortingMode;
if (newSortingMode%2==0) ++newSortingMode;// 0,2,4
else --newSortingMode;
}
else newSortingMode = t*2;
}
if (t==oldSelectedTab) {
ImGui::PopStyleColor();
}
}
if (newSortingMode!=oldSortingMode) {
I.sortingMode = newSortingMode;
//printf("sortingMode = %d\n",sortingMode);
I.forceRescan = true;
}
//-- Browsing per row -----------------------------------
if (I.allowDisplayByOption && I.numBrowsingColumns>1) {
ImGui::SameLine();
ImGui::Text(" Display by:");
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button,dummyButtonColor);
if (ImGui::SmallButton(!Internal::BrowsingPerRow ? "Column##browsingPerRow" : "Row##browsingPerRow")) {
Internal::BrowsingPerRow = !Internal::BrowsingPerRow;
}
ImGui::PopStyleColor();
}
//-- End browsing per row -------------------------------
}
}
//-----------------------------------------------------------------------------
ImGui::Separator();
//-----------------------------------------------------------------------------
// MAIN BROWSING FRAME:
//-----------------------------------------------------------------------------
{
ImGui::BeginChild("BrowsingFrame");
// ImGui::SetScrollPosHere(); // possible future ref: while drawing to place the scroll bar
ImGui::Columns(I.numBrowsingColumns);
static int id;
ImGui::PushID(&id);
int cntEntries = 0;
// Directories --------------------------------------------------------------
if (I.dirs.size()>0) {
ImGui::PushStyleColor(ImGuiCol_Text,ColorSet[Internal::ImGuiCol_Dialog_Directory_Text]);
ImGui::PushStyleColor(ImGuiCol_Button,ColorSet[Internal::ImGuiCol_Dialog_Directory_Background]);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ColorSet[Internal::ImGuiCol_Dialog_Directory_Hover]);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,ColorSet[Internal::ImGuiCol_Dialog_Directory_Pressed]);
for (int i=0,sz=(int)I.dirs.size();i<sz;i++) {
const char* dirName = &I.dirNames[i][0];
if (I.filter.PassFilter(dirName)) {
if (ImGui::SmallButton(dirName)) {
strcpy(I.currentFolder,I.dirs[i]);
strcpy(I.editLocationInputText,I.currentFolder);
I.history.switchTo(I.currentFolder);
I.forceRescan = true;
//------------------------------------------------------------------------------------------------------------------------------
}
++cntEntries;
if (Internal::BrowsingPerRow) ImGui::NextColumn();
else if (cntEntries==I.numBrowsingEntriesPerColumn) {
cntEntries = 0;
ImGui::NextColumn();
}
}
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
// Files ----------------------------------------------------------------------
if (!isSelectFolderDialog && I.files.size()>0) {
ImGui::PushStyleColor(ImGuiCol_Text,ColorSet[Internal::ImGuiCol_Dialog_File_Text]);
ImGui::PushStyleColor(ImGuiCol_Button,ColorSet[Internal::ImGuiCol_Dialog_File_Background]);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ColorSet[Internal::ImGuiCol_Dialog_File_Hover]);
ImGui::PushStyleColor(ImGuiCol_ButtonActive,ColorSet[Internal::ImGuiCol_Dialog_File_Pressed]);
for (int i=0,sz=(int)I.files.size();i<sz;i++) {
const char* fileName = &I.fileNames[i][0];
if (I.filter.PassFilter(fileName)) {
if (ImGui::SmallButton(fileName)) {
if (!isSaveFileDialog) {
strcpy(rv,I.files[i]);
I.open = true;
}
else {
Path::GetFileName(I.files[i],I.saveFileName);
}
}
++cntEntries;
if (Internal::BrowsingPerRow) ImGui::NextColumn();
else if (cntEntries==I.numBrowsingEntriesPerColumn) {
cntEntries = 0;
ImGui::NextColumn();
}
}
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
//-----------------------------------------------------------------------------
ImGui::PopID();
ImGui::EndChild();
}
//-----------------------------------------------------------------------------
ImGui::End();
return rv;
}
const char* Dialog::chooseFileDialog(bool dialogTriggerButton,const char* directory,const char* fileFilterExtensionString,const char* windowTitle,const ImVec2& windowSize,const ImVec2& windowPos,const float windowAlpha) {
if (dialogTriggerButton) {internal->rescan = true;internal->chosenPath[0]='\0';}
if (dialogTriggerButton || (!internal->rescan && strlen(getChosenPath())==0)) {
ChooseFileMainMethod(*this,directory,false,false,"",fileFilterExtensionString,windowTitle,windowSize,windowPos,windowAlpha);
}
return getChosenPath();
}
const char* Dialog::chooseFolderDialog(bool dialogTriggerButton,const char* directory,const char* windowTitle,const ImVec2& windowSize,const ImVec2& windowPos,const float windowAlpha) {
if (dialogTriggerButton) {internal->rescan = true;internal->chosenPath[0]='\0';}
if (dialogTriggerButton || (!internal->rescan && strlen(getChosenPath())==0)) {
ChooseFileMainMethod(*this,directory,true,false,"","",windowTitle,windowSize,windowPos,windowAlpha);
}
return getChosenPath();
}
const char* Dialog::saveFileDialog(bool dialogTriggerButton,const char* directory,const char* startingFileNameEntry,const char* fileFilterExtensionString,const char* windowTitle,const ImVec2& windowSize,const ImVec2& windowPos,const float windowAlpha) {
if (dialogTriggerButton) {internal->rescan = true;internal->chosenPath[0]='\0';}
if (dialogTriggerButton || (!internal->rescan && strlen(getChosenPath())==0)) {
ChooseFileMainMethod(*this,directory,false,true,startingFileNameEntry,fileFilterExtensionString,windowTitle,windowSize,windowPos,windowAlpha);
}
return getChosenPath();
}
} // namespace ImGuiFs
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#ifndef IMGUI_FILESYSTEM_H_
#define IMGUI_FILESYSTEM_H_
// USAGE EXAMPLE:
/*
#include "imguifilesystem.h" // imguifilesystem.cpp must be compiled
// Inside a ImGui window:
const bool browseButtonPressed = ImGui::Button("..."); // we need a trigger boolean variable
static ImGuiFs::Dialog dlg; // one per dialog (and must be static)
dlg.chooseFileDialog(browseButtonPressed); // see other dialog types and the full list of arguments for advanced usage
if (strlen(dlg.getChosenPath())>0) {
ImGui::Text("Chosen file: \"%s\"",dlg.getChosenPath());
}
// If you want to copy the (valid) returned path somewhere, you can use something like:
static char myPath[ImGuiFs::MAX_PATH_BYTES];
if (strlen(dlg.getChosenPath())>0) {
strcpy(myPath,dlg.getChosenPath());
}
*/
// MISSING FEATURES:
/*
-> [File and Folder] Links are NOT supported (they don't show up).
-> Multiselections in chooseFileDialogs are NOT supported.
-> Hidden and temporary files don't show up on nix systems (by design). Nothing has been done for Windows about it yet.
*/
// COMPILING AND TESTING:
/*
-> Compiled and tested using "ImGui library v1.17 wip"
-> Successfully compiled using gcc, clang and mingw32 compilers.
x> Never compiled on any other compiler (Visual C++'s cl.exe included).
-> Tested on Ubuntu 64bit and Wine 32bit.
x> Never tested on a real Windows OS and on MacOS.
*/
//#define DIRENT_USES_UTF8_CHARS // Optional. Affects Windows users only. Needs recompilation of imguifilesystem.cpp. Enables long UTF8 paths instead of short ASCII paths.
// Unfortunately it's NOT 100% functional (in my tests some folders can't be browsed). Patches are welcome. See "dirent_portable.h" for further info.
// When experiencing problems on Windows, trying commenting this definition out is a good start.
// On a second thought, I think we should leave this definition commented out (Windows users can always define it at the project level, if needed).
#include <imgui.h>
namespace ImGuiFs {
extern const int MAX_FILENAME_BYTES;
extern const int MAX_PATH_BYTES;
struct Dialog {
public:
// default arguments are usually what most users expect (better not touch them in most cases)
Dialog(bool noKnownDirectoriesSection=false,bool noCreateDirectorySection=false,bool noFilteringSection=false,bool detectKnownDirectoriesAtEachOpening=false,bool addDisplayByOption=false,bool dontFilterSaveFilePathsEnteredByTheUser=false);
~Dialog();
// "dialogTriggerButton" is usually a bool variable connected to a ImGui::Button(...).
// returns the chosen path (internally stored). Users can check when the returned path has strlen()>0.
// "fileFilterExtensionString" can be something like ".png;.jpg;.jpeg;.bmp;.tga;.gif;.tiff;.txt". It's case insensitive.
// "directory" and "startingFileNameEntry" (only available in saveFileDialog(...)) are quite flexible and can be set to almost everything: the method will use the most resonable choice.
const char* chooseFileDialog(bool dialogTriggerButton,const char* directory=NULL,const char* fileFilterExtensionString=NULL,const char* windowTitle=NULL,const ImVec2& windowSize=ImVec2(-1,-1),const ImVec2& windowPos=ImVec2(-1,-1),const float windowAlpha=0.875f);
const char* chooseFolderDialog(bool dialogTriggerButton,const char* directory=NULL,const char* windowTitle=NULL,const ImVec2& windowSize=ImVec2(-1,-1),const ImVec2& windowPos=ImVec2(-1,-1),const float windowAlpha=0.875f);
const char* saveFileDialog(bool dialogTriggerButton,const char* directory=NULL,const char* startingFileNameEntry=NULL,const char* fileFilterExtensionString=NULL,const char* windowTitle=NULL,const ImVec2& windowSize=ImVec2(-1,-1),const ImVec2& windowPos=ImVec2(-1,-1),const float windowAlpha=0.875f);
// gets the chosen path (internally stored). It's valid (its strlen()>0) only when the user performs a valid selection.
const char* getChosenPath() const;
// returns the last directory browsed by the user using this class (internally stored). Can be passed as "directory" parameter in the methods above to reuse last used directory.
const char* getLastDirectory() const;
private:
struct Internal* internal;
friend const char* ChooseFileMainMethod(Dialog& ist,const char* directory,const bool _isFolderChooserDialog,const bool _isSaveFileDialog,const char* _saveFileName,const char* fileFilterExtensionString,const char* windowTitle,const ImVec2& windowSize,const ImVec2& windowPos,const float windowAlpha);
};
} // namespace ImGuiFs
#endif //IMGUI_FILESYSTEM_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment