Merge commit '004534c8e7e16c6414b6f7203be81829b0650411' as 'deps/nfde'

This commit is contained in:
Gary Talent 2022-05-25 20:36:50 -05:00
commit f62d29a273
31 changed files with 4827 additions and 0 deletions

11
deps/nfde/.clang-format vendored Normal file
View File

@ -0,0 +1,11 @@
---
BasedOnStyle: Chromium
IndentWidth: 4
BinPackArguments: false
ColumnLimit: 100
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
---
Language: Cpp
---
Language: ObjC

142
deps/nfde/.github/workflows/cmake.yml vendored Normal file
View File

@ -0,0 +1,142 @@
name: build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
clang-format:
name: ClangFormat check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Format code
run: find src/ test/ -iname '*.c' -or -iname '*.cpp' -or -iname '*.m' -or -iname '*.mm' -or -iname '*.h' -or -iname '*.hpp' | xargs clang-format -i -style=file
- name: Check diff
run: git diff --exit-code
build-ubuntu:
name: Ubuntu (${{ matrix.os }}, ${{ matrix.portal.name }}, ${{ matrix.compiler.c }}, C++${{ matrix.cppstd }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, ubuntu-18.04]
portal: [ {flag: OFF, dep: libgtk-3-dev, name: GTK}, {flag: ON, dep: libdbus-1-dev, name: Portal} ] # The NFD_PORTAL setting defaults to OFF (i.e. uses GTK)
compiler: [ {c: gcc, cpp: g++}, {c: clang, cpp: clang++} ] # The default compiler is gcc/g++
cppstd: [23, 11]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Installing Dependencies
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }}
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: $${{ matrix.os }} - $${{ matrix.compiler.c }}
path: |
build/src/libnfd.a
build/test/test_*
build-macos-clang:
name: MacOS latest - Clang
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: MacOS latest - Clang
path: |
build/src/libnfd.a
build/test/test_*
build-windows-msvc:
name: Windows latest - MSVC
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install --config Release
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: Windows latest - MSVC
path: |
build/src/Release/nfd.lib
build/test/Release/test_*
build-windows-clang:
name: Windows latest - Clang
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -T ClangCL -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install --config Release
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: Windows latest - Clang
path: |
build/src/Release/nfd.lib
build/test/Release/test_*
build-windows-mingw:
name: Windows latest - MinGW
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up MinGW-w64
uses: msys2/setup-msys2@v2
with:
path-type: minimal
install: >-
base-devel
mingw-w64-x86_64-gcc
mingw-w64-x86_64-cmake
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: Windows latest - MinGW
path: |
build/src/libnfd.a
build/test/test_*

7
deps/nfde/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# VS CMake default output
/.vs/
/out/
/CMakeSettings.json
# Mac OS X rubbish
.DS_Store

37
deps/nfde/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.2)
project(nativefiledialog-extended)
set(nfd_PLATFORM Undefined)
if(WIN32)
set(nfd_PLATFORM PLATFORM_WIN32)
elseif(APPLE)
set(nfd_PLATFORM PLATFORM_MACOS)
elseif(UNIX AND NOT APPLE)
set(nfd_PLATFORM PLATFORM_LINUX)
endif()
message("nfd Platform: ${nfd_PLATFORM}")
set(nfd_COMPILER Undefined)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
# This is clang-cl, which has different compiler options
set(nfd_COMPILER COMPILER_CLANGCL)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(nfd_COMPILER COMPILER_MSVC)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(nfd_COMPILER COMPILER_GNU)
endif()
message("nfd Compiler: ${nfd_COMPILER}")
# Use latest C++ by default (should be the best one), but let user override it
if(NOT DEFINED CMAKE_CXX_STANDARD)
set (CMAKE_CXX_STANDARD 23)
endif()
add_subdirectory(src)
option(NFD_BUILD_TESTS "Build tests for nfd" OFF)
if(${NFD_BUILD_TESTS})
add_subdirectory(test)
endif()

16
deps/nfde/LICENSE vendored Normal file
View File

@ -0,0 +1,16 @@
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.

286
deps/nfde/README.md vendored Normal file
View File

@ -0,0 +1,286 @@
# Native File Dialog Extended
![GitHub Actions](https://github.com/btzy/nativefiledialog-extended/workflows/build/badge.svg)
A small C library with that portably invokes native file open, folder select and file save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and Qt.
This library is based on Michael Labbe's Native File Dialog ([mlabbe/nativefiledialog](https://github.com/mlabbe/nativefiledialog)).
Features:
- Lean C API, static library — no C++/ObjC runtime needed
- Supports Windows (MSVC, MinGW, Clang), MacOS (Clang), and Linux (GTK, portal) (GCC, Clang)
- Zlib licensed
- Friendly names for filters (e.g. `C/C++ Source files (*.c;*.cpp)` instead of `(*.c;*.cpp)`) on platforms that support it
- Automatically append file extension on platforms where users expect it
- Support for setting a default folder path
- Support for setting a default file name (e.g. `Untitled.c`)
- Consistent UTF-8 support on all platforms
- Native character set (UTF-16 `wchar_t`) support on Windows
- Initialization and de-initialization of platform library (e.g. COM (Windows) / GTK (Linux GTK) / D-Bus (Linux portal)) decoupled from dialog functions, so applications can choose when to initialize/de-initialize
- Multiple file selection support (for file open dialog)
- Support for Vista's modern `IFileDialog` on Windows
- No third party dependencies
- Modern CMake build system
- Works alongside [SDL2](http://www.libsdl.org) on all platforms
- Optional C++ wrapper with `unique_ptr` auto-freeing semantics and optional parameters, for those using this library from C++
**Comparison with original Native File Dialog:**
The friendly names feature is the primary reason for breaking API compatibility with Michael Labbe's library (and hence this library probably will never be merged with it). There are also a number of tweaks that cause observable differences in this library.
Features added in Native File Dialog Extended:
- Friendly names for filters
- Automatically appending file extensions
- Support for setting a default file name
- Native character set (UTF-16 `wchar_t`) support on Windows
- xdg-desktop-portal support on Linux that opens the "native" file chooser (see "Usage" section below)
- Initialization and de-initialization of platform library decoupled from file dialog functions
- Modern CMake build system
- Optional C++ wrapper with `unique_ptr` auto-freeing semantics and optional parameters
There is also significant code refractoring, especially for the Windows implementation.
# Basic Usage
```C
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
NFD_Init();
nfdchar_t *outPath;
nfdfilteritem_t filterItem[2] = { { "Source code", "c,cpp,cc" }, { "Headers", "h,hpp" } };
nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 2, NULL);
if (result == NFD_OKAY)
{
puts("Success!");
puts(outPath);
NFD_FreePath(outPath);
}
else if (result == NFD_CANCEL)
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError());
}
NFD_Quit();
return 0;
}
```
See [NFD.h](src/include/nfd.h) for more options.
If you are using a platform abstraction framework such as SDL or GLFW, also see the "Usage" section below.
# Screenshots #
![Windows 10](screens/open_win10.png?raw=true)
![MacOS 10.13](screens/open_macos_11.0.png?raw=true)
![GTK3 on Ubuntu 20.04](screens/open_gtk3.png?raw=true)
# Building
## CMake Projects
If your project uses CMake,
simply add the following lines to your CMakeLists.txt:
```
add_subdirectory(path/to/nativefiledialog-extended)
target_link_libraries(MyProgram PRIVATE nfd)
```
Make sure that you also have the needed [dependencies](#dependencies).
## Standalone Library
If you want to build the standalone static library,
execute the following commands (starting from the project root directory):
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
```
The above commands will make a `build` directory,
and build the project (in release mode) there.
If you are developing NFDe, you may want to do `-DCMAKE_BUILD_TYPE=Debug`
to build a debug version of the library instead.
If you want to build the sample programs,
add `-DNFD_BUILD_TESTS=ON` (sample programs are not built by default).
On Linux, if you want to use the Flatpak desktop portal instead of GTK, add `-DNFD_PORTAL=ON`. (Otherwise, GTK will be used.) See the "Usage" section below for more information.
See the [CI build file](.github/workflows/cmake.yml) for some example build commands.
### Visual Studio on Windows
Recent versions of Visual Studio have CMake support built into the IDE.
You should be able to "Open Folder" in the project root directory,
and Visual Studio will recognize and configure the project appropriately.
From there, you will be able to set configurations for Debug vs Release,
and for x86 vs x64.
For more information, see [the Microsoft Docs page]([https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=vs-2019](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=vs-2019)).
This has been tested to work on Visual Studio 2019,
and it probably works on Visual Studio 2017 too.
### Compiling Your Programs
1. Add `src/include` to your include search path.
2. Add `nfd.lib` or `nfd_d.lib` to the list of static libraries to link against (for release or debug, respectively).
3. Add `build/<debug|release>/<arch>` to the library search path.
## Dependencies
### Linux
#### GTK (default)
Make sure `libgtk-3-dev` is installed on your system.
#### Portal
Make sure `libdbus-1-dev` is installed on your system.
### MacOS
On MacOS, add `AppKit` to the list of frameworks.
### Windows
On Windows (both MSVC and MinGW), ensure you are building against `ole32.lib` and `uuid.lib`.
# Usage
See `NFD.h` for API calls. See the `test` directory for example code (both C and C++).
If you turned on the option to build the `test` directory (`-DNFD_BUILD_TESTS=ON`), then `build/bin` will contain the compiled test programs.
## File Filter Syntax
Files can be filtered by file extension groups:
```C
nfdfilteritem_t filterItem[2] = { { "Source code", "c,cpp,cc" }, { "Headers", "h,hpp" } };
```
A file filter is a pair of strings comprising the friendly name and the specification (multiple file extensions are comma-separated).
A list of file filters can be passed as an argument when invoking the library.
A wildcard filter is always added to every dialog.
*Note: On MacOS, the file dialogs do not have friendly names and there is no way to switch between filters, so the filter specifications are combined (e.g. "c,cpp,cc,h,hpp"). The filter specification is also never explicitly shown to the user. This is usual MacOS behaviour and users expect it.*
*Note 2: You must ensure that the specification string is non-empty and that every file extension has at least one character. Otherwise, bad things might ensue (i.e. undefined behaviour).*
*Note 3: On Linux, the file extension is appended (if missing) when the user presses down the "Save" button. The appended file extension will remain visible to the user, even if an overwrite prompt is shown and the user then presses "Cancel".*
*Note 4: On Windows, the default folder parameter is only used if there is no recently used folder available. Otherwise, the default folder will be the folder that was last used. Internally, the Windows implementation calls [IFileDialog::SetDefaultFolder(IShellItem)](https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setdefaultfolder). This is usual Windows behaviour and users expect it.*
## Iterating Over PathSets
A file open dialog that supports multiple selection produces a PathSet, which is a thin abstraction over the platform-specific collection. There are two ways to iterate over a PathSet:
### Accessing by index
This method does array-like access on the PathSet, and is the easiest to use.
However, on certain platforms (Linux, and possibly Windows),
it takes O(N<sup>2</sup>) time in total to iterate the entire PathSet,
because the underlying platform-specific implementation uses a linked list.
See [test_opendialogmultiple.c](test/test_opendialogmultiple.c).
### Using an enumerator (experimental)
This method uses an enumerator object to iterate the paths in the PathSet.
It is guaranteed to take O(N) time in total to iterate the entire PathSet.
See [test_opendialogmultiple_enum.c](test/test_opendialogmultiple_enum.c).
This API is experimental, and subject to change.
## Customization Macros
You can define the following macros *before* including `nfd.h`/`nfd.hpp`:
- `NFD_NATIVE`: Define this before including `nfd.h` to make non-suffixed function names and typedefs (e.g. `NFD_OpenDialog`) aliases for the native functions (e.g. `NFD_OpenDialogN`) instead of aliases for the UTF-8 functions (e.g. `NFD_OpenDialogU8`). This macro does not affect the C++ wrapper `nfd.hpp`.
- `NFD_THROWS_EXCEPTIONS`: (C++ only) Define this before including `nfd.hpp` to make `NFD::Guard` construction throw `std::runtime_error` if `NFD_Init` fails. Otherwise, there is no way to detect failure in `NFD::Guard` construction.
Macros that might be defined by `nfd.h`:
- `NFD_DIFFERENT_NATIVE_FUNCTIONS`: Defined if the native and UTF-8 versions of functions are different (i.e. compiling for Windows); not defined otherwise. If `NFD_DIFFERENT_NATIVE_FUNCTIONS` is not defined, then the UTF-8 versions of functions are aliases for the native versions. This might be useful if you are writing a function that wants to provide overloads depending on whether the native functions and UTF-8 functions are the same. (Native is UTF-16 (`wchar_t`) for Windows and UTF-8 (`char`) for Mac/Linux.)
## Usage with a Platform Abstraction Framework
NFDe is known to work with SDL2 and GLFW, and should also work with other platform abstraction framworks. However, you should initialize NFDe _after_ initializing the framework, and probably should deinitialize NFDe _before_ deinitializing the framework. This is because some frameworks expect to be initialized on a "clean slate", and they may configure the system in a different way from NFDe. `NFD_Init` is generally very careful not to disrupt the existing configuration unless necessary, and `NFD_Quit` restores the configuration back exactly to what it was before initialization.
An example with SDL2:
```
// Initialize SDL2 first
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
// display some error here
}
// Then initialize NFDe
if (NFD_Init() != NFD_OKAY) {
// display some error here
}
/*
Your main program goes here
*/
NFD_Quit(); // deinitialize NFDe first
SDL_Quit(); // Then deinitialize SDL2
```
## Using xdg-desktop-portal on Linux
On Linux, you can use the portal implementation instead of GTK, which will open the "native" file chooser selected by the OS or customized by the user. The user must have `xdg-desktop-portal` and a suitable backend installed (this comes pre-installed with most common desktop distros), otherwise `NFD_ERROR` will be returned.
The portal implementation is much less battle-tested than the GTK implementation. There may be bugs &mdash; please report them on the issue tracker.
To use the portal implementation, add `-DNFD_PORTAL=ON` to the build command.
*Note: Setting a default path is not supported by the portal implementation, and any default path passed to NFDe will be ignored. This is a limitation of the portal API, so there is no way NFDe can work around it.*
### What is a portal?
Unlike Windows and MacOS, Linux does not have a file chooser baked into the operating system. Linux applications that want a file chooser usually link with a library that provides one (such as GTK, as in the Linux screenshot above). This is a mostly acceptable solution that many applications use, but may make the file chooser look foreign on non-GTK distros.
Flatpak was introduced in 2015, and with it came a standardized interface to open a file chooser. Applications using this interface did not need to come with a file chooser, and could use the one provided by Flatpak. This interface became known as the desktop portal, and its use expanded to non-Flatpak applications. Now, most major desktop Linux distros come with the desktop portal installed, with file choosers that fit the theme of the distro. Users can also install a different portal backend if desired. There are currently two known backends: GTK and KDE. (XFCE does not currently seem to have a portal backend.)
# Known Limitations #
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`. (There are no plans to support this; you shouldn't be still using Windows XP anyway.)
- No Emscripten (WebAssembly) bindings. (This might get implemented if I decide to port Circuit Sandbox for the web, but I don't think there is any way to implement a web-based folder picker.)
- GTK dialogs don't set the existing window as parent, so if users click the existing window while the dialog is open then the dialog will go behind it. GTK writes a warning to stdout or stderr about this.
- Portal dialogs (the alternative to GTK on Linux) don't support a default path. Any default path you supply will be ignored.
- This library is not compatible with the original Native File Dialog library. Things might break if you use both in the same project. (There are no plans to support this; you have to use one or the other.)
- This library does not explicitly dispatch calls to the UI thread. This may lead to crashes if you call functions from other threads when the platform does not support it (e.g. MacOS). Users are generally expected to call NFDe from an appropriate UI thread (i.e. the thread performing the UI event loop).
# Reporting Bugs #
Please use the GitHub issue tracker to report bugs or to contribute to this repository. Feel free to submit bug reports of any kind.
# Credit #
Bernard Teo (me) and other contributors for everything that wasn't from Michael Labbe's [Native File Dialog](https://github.com/mlabbe/nativefiledialog).
[Michael Labbe](https://github.com/mlabbe) for his awesome Native File Dialog library, and the other contributors to that library.
Much of this README has also been copied from the README of original Native File Dialog repository.
## License ##
Everything in this repository is distributed under the ZLib license, as is the original Native File Dialog library.
## Support ##
I don't provide any paid support. [Michael Labbe](https://github.com/mlabbe) appears to provide paid support for his [library](https://github.com/mlabbe/nativefiledialog) at the time of writing.

BIN
deps/nfde/screens/open_gtk3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
deps/nfde/screens/open_gtk3_dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
deps/nfde/screens/open_macos_11.0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
deps/nfde/screens/open_win10.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
deps/nfde/screens/open_win10_dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

79
deps/nfde/src/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,79 @@
set(TARGET_NAME nfd)
set(PUBLIC_HEADER_FILES
include/nfd.h
include/nfd.hpp)
set(SOURCE_FILES ${PUBLIC_HEADER_FILES})
if(nfd_PLATFORM STREQUAL PLATFORM_WIN32)
list(APPEND SOURCE_FILES nfd_win.cpp)
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
find_package(PkgConfig REQUIRED)
# for Linux, we support GTK3 and xdg-desktop-portal
option(NFD_PORTAL "Use xdg-desktop-portal instead of GTK" OFF)
if(NOT NFD_PORTAL)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
message("Using GTK version: ${GTK3_VERSION}")
list(APPEND SOURCE_FILES nfd_gtk.cpp)
else()
pkg_check_modules(DBUS REQUIRED dbus-1)
message("Using DBUS version: ${DBUS_VERSION}")
list(APPEND SOURCE_FILES nfd_portal.cpp)
endif()
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
find_library(APPKIT_LIBRARY AppKit)
list(APPEND SOURCE_FILES nfd_cocoa.m)
endif()
# Define the library
add_library(${TARGET_NAME} STATIC
${SOURCE_FILES})
# Allow includes from include/
target_include_directories(${TARGET_NAME}
PUBLIC include/)
if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
if(NOT NFD_PORTAL)
target_include_directories(${TARGET_NAME}
PRIVATE ${GTK3_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME}
PRIVATE ${GTK3_LIBRARIES})
else()
target_include_directories(${TARGET_NAME}
PRIVATE ${DBUS_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME}
PRIVATE ${DBUS_LIBRARIES})
target_compile_definitions(${TARGET_NAME}
PUBLIC NFD_PORTAL)
endif()
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
target_link_libraries(${TARGET_NAME}
PRIVATE ${APPKIT_LIBRARY})
endif()
if(nfd_COMPILER STREQUAL COMPILER_MSVC)
string(REPLACE "/EHsc" "/EHs-c-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY STATIC_LIBRARY_OPTIONS /NODEFAULTLIB)
endif()
if(nfd_COMPILER STREQUAL COMPILER_CLANGCL)
string(REPLACE "/EHsc" "/EHs-c-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()
if(nfd_COMPILER STREQUAL COMPILER_GNU)
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
endif()
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADER_FILES}")
install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib PUBLIC_HEADER DESTINATION include)

281
deps/nfde/src/include/nfd.h vendored Normal file
View File

@ -0,0 +1,281 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo, Michael Labbe
This header contains the functions that can be called by user code.
*/
#ifndef _NFD_H
#define _NFD_H
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include <stddef.h>
#ifdef _WIN32
/* denotes UTF-16 char */
typedef wchar_t nfdnchar_t;
#else
/* denotes UTF-8 char */
typedef char nfdnchar_t;
#endif // _WIN32
/* opaque data structure -- see NFD_PathSet_* */
typedef void nfdpathset_t;
#ifndef NFD_PORTAL
typedef struct {
void* ptr;
} nfdpathsetenum_t;
#else
typedef struct {
void* d1;
void* d2;
unsigned int d3;
int d4;
int d5;
int d6;
int d7;
int d8;
int d9;
int d10;
int d11;
int p1;
void* p2;
void* p3;
} nfdpathsetenum_t;
#endif
typedef unsigned int nfdfiltersize_t;
typedef enum {
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
} nfdresult_t;
typedef struct {
const nfdnchar_t* name;
const nfdnchar_t* spec;
} nfdnfilteritem_t;
/* free a file path that was returned by the dialogs */
/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */
void NFD_FreePathN(nfdnchar_t* filePath);
/* initialize NFD - call this for every thread that might use NFD, before calling any other NFD
* functions on that thread */
nfdresult_t NFD_Init(void);
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
void NFD_Quit(void);
/* single file open dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* NFD_OKAY */
/* If filterCount is zero, filterList is ignored (you can use NULL) */
/* If defaultPath is NULL, the operating system will decide */
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath);
/* multiple file open dialog */
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
* returns NFD_OKAY */
/* If filterCount is zero, filterList is ignored (you can use NULL) */
/* If defaultPath is NULL, the operating system will decide */
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath);
/* save dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* NFD_OKAY */
/* If filterCount is zero, filterList is ignored (you can use NULL) */
/* If defaultPath is NULL, the operating system will decide */
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdnchar_t* defaultName);
/* select folder dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* NFD_OKAY */
/* If defaultPath is NULL, the operating system will decide */
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath);
/* Get last error -- set when nfdresult_t returns NFD_ERROR */
/* Returns the last error that was set, or NULL if there is no error. */
/* The memory is owned by NFD and should not be freed by user code. */
/* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any
* conversion. */
const char* NFD_GetError(void);
/* clear the error */
void NFD_ClearError(void);
/* path set operations */
#ifdef _WIN32
typedef unsigned long nfdpathsetsize_t;
#elif __APPLE__
typedef unsigned long nfdpathsetsize_t;
#else
typedef unsigned int nfdpathsetsize_t;
#endif // _WIN32, __APPLE__
/* Gets the number of entries stored in pathSet */
/* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we
* might not actually have this number of usable paths */
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count);
/* Gets the UTF-8 path at offset index */
/* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function
* returns NFD_OKAY */
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t** outPath);
/* Free the path gotten by NFD_PathSet_GetPathN */
#ifdef _WIN32
#define NFD_PathSet_FreePathN NFD_FreePathN
#elif __APPLE__
#define NFD_PathSet_FreePathN NFD_FreePathN
#else
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath);
#endif // _WIN32, __APPLE__
/* Gets an enumerator of the path set. */
/* It is the caller's responsibility to free `enumerator` via NFD_PathSet_FreeEnum() if this
* function returns NFD_OKAY, and it should be freed before freeing the pathset. */
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator);
/* Frees an enumerator of the path set. */
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator);
/* Gets the next item from the path set enumerator.
* If there are no more items, then *outPaths will be set to NULL. */
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePath() if this
* function returns NFD_OKAY and `*outPath` is not null */
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath);
/* Free the pathSet */
void NFD_PathSet_Free(const nfdpathset_t* pathSet);
#ifdef _WIN32
/* say that the U8 versions of functions are not just #defined to be the native versions */
#define NFD_DIFFERENT_NATIVE_FUNCTIONS
typedef char nfdu8char_t;
typedef struct {
const nfdu8char_t* name;
const nfdu8char_t* spec;
} nfdu8filteritem_t;
/* UTF-8 compatibility functions */
/* free a file path that was returned */
void NFD_FreePathU8(nfdu8char_t* outPath);
/* single file open dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath);
/* multiple file open dialog */
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
* returns NFD_OKAY */
nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath);
/* save dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath,
const nfdu8char_t* defaultName);
/* select folder dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath);
/* Get the UTF-8 path at offset index */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdu8char_t** outPath);
/* Gets the next item from the path set enumerator.
* If there are no more items, then *outPaths will be set to NULL. */
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePathU8() if this
* function returns NFD_OKAY and `*outPath` is not null */
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath);
#define NFD_PathSet_FreePathU8 NFD_FreePathU8
#ifdef NFD_NATIVE
typedef nfdnchar_t nfdchar_t;
typedef nfdnfilteritem_t nfdfilteritem_t;
#define NFD_FreePath NFD_FreePathN
#define NFD_OpenDialog NFD_OpenDialogN
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
#define NFD_SaveDialog NFD_SaveDialogN
#define NFD_PickFolder NFD_PickFolderN
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
#else
typedef nfdu8char_t nfdchar_t;
typedef nfdu8filteritem_t nfdfilteritem_t;
#define NFD_FreePath NFD_FreePathU8
#define NFD_OpenDialog NFD_OpenDialogU8
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleU8
#define NFD_SaveDialog NFD_SaveDialogU8
#define NFD_PickFolder NFD_PickFolderU8
#define NFD_PathSet_GetPath NFD_PathSet_GetPathU8
#define NFD_PathSet_FreePath NFD_PathSet_FreePathU8
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextU8
#endif // NFD_NATIVE
#else // _WIN32
/* the native charset is already UTF-8 */
typedef nfdnchar_t nfdchar_t;
typedef nfdnfilteritem_t nfdfilteritem_t;
#define NFD_FreePath NFD_FreePathN
#define NFD_OpenDialog NFD_OpenDialogN
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
#define NFD_SaveDialog NFD_SaveDialogN
#define NFD_PickFolder NFD_PickFolderN
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
typedef nfdnchar_t nfdu8char_t;
typedef nfdnfilteritem_t nfdu8filteritem_t;
#define NFD_FreePathU8 NFD_FreePathN
#define NFD_OpenDialogU8 NFD_OpenDialogN
#define NFD_OpenDialogMultipleU8 NFD_OpenDialogMultipleN
#define NFD_SaveDialogU8 NFD_SaveDialogN
#define NFD_PickFolderU8 NFD_PickFolderN
#define NFD_PathSet_GetPathU8 NFD_PathSet_GetPathN
#define NFD_PathSet_FreePathU8 NFD_PathSet_FreePathN
#define NFD_PathSet_EnumNextU8 NFD_PathSet_EnumNextN
#endif // _WIN32
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _NFD_H

311
deps/nfde/src/include/nfd.hpp vendored Normal file
View File

@ -0,0 +1,311 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Author: Bernard Teo
This header is a thin C++ wrapper for nfd.h.
C++ projects can choose to use this header instead of nfd.h directly.
Refer to documentation on nfd.h for instructions on how to use these functions.
*/
#ifndef _NFD_HPP
#define _NFD_HPP
#include <nfd.h>
#include <cstddef> // for std::size_t
#include <memory> // for std::unique_ptr
#ifdef NFD_THROWS_EXCEPTIONS
#include <stdexcept>
#endif
namespace NFD {
inline nfdresult_t Init() noexcept {
return ::NFD_Init();
}
inline void Quit() noexcept {
::NFD_Quit();
}
inline void FreePath(nfdnchar_t* outPath) noexcept {
::NFD_FreePathN(outPath);
}
inline nfdresult_t OpenDialog(nfdnchar_t*& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogN(&outPath, filterList, filterCount, defaultPath);
}
inline nfdresult_t OpenDialogMultiple(const nfdpathset_t*& outPaths,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogMultipleN(&outPaths, filterList, filterCount, defaultPath);
}
inline nfdresult_t SaveDialog(nfdnchar_t*& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr,
const nfdnchar_t* defaultName = nullptr) noexcept {
return ::NFD_SaveDialogN(&outPath, filterList, filterCount, defaultPath, defaultName);
}
inline nfdresult_t PickFolder(nfdnchar_t*& outPath,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_PickFolderN(&outPath, defaultPath);
}
inline const char* GetError() noexcept {
return ::NFD_GetError();
}
inline void ClearError() noexcept {
::NFD_ClearError();
}
namespace PathSet {
inline nfdresult_t Count(const nfdpathset_t* pathSet, nfdpathsetsize_t& count) noexcept {
return ::NFD_PathSet_GetCount(pathSet, &count);
}
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t*& outPath) noexcept {
return ::NFD_PathSet_GetPathN(pathSet, index, &outPath);
}
inline void FreePath(nfdnchar_t* filePath) noexcept {
::NFD_PathSet_FreePathN(filePath);
}
inline void Free(const nfdpathset_t* pathSet) noexcept {
::NFD_PathSet_Free(pathSet);
}
} // namespace PathSet
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
/* we need the C++ bindings for the UTF-8 functions as well, because there are different functions
* for them */
inline void FreePath(nfdu8char_t* outPath) noexcept {
::NFD_FreePathU8(outPath);
}
inline nfdresult_t OpenDialog(nfdu8char_t*& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogU8(&outPath, filterList, count, defaultPath);
}
inline nfdresult_t OpenDialogMultiple(const nfdpathset_t*& outPaths,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogMultipleU8(&outPaths, filterList, count, defaultPath);
}
inline nfdresult_t SaveDialog(nfdu8char_t*& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr,
const nfdu8char_t* defaultName = nullptr) noexcept {
return ::NFD_SaveDialogU8(&outPath, filterList, count, defaultPath, defaultName);
}
inline nfdresult_t PickFolder(nfdu8char_t*& outPath,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_PickFolderU8(&outPath, defaultPath);
}
namespace PathSet {
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdu8char_t*& outPath) noexcept {
return ::NFD_PathSet_GetPathU8(pathSet, index, &outPath);
}
inline void FreePath(nfdu8char_t* filePath) noexcept {
::NFD_PathSet_FreePathU8(filePath);
}
} // namespace PathSet
#endif
// smart objects
class Guard {
public:
#ifndef NFD_THROWS_EXCEPTIONS
inline Guard() noexcept {
Init(); // always assume that initialization succeeds
}
#else
inline Guard() {
if (!Init()) {
throw std::runtime_error(GetError());
}
}
#endif
inline ~Guard() noexcept { Quit(); }
// Not allowed to copy or move this class
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
};
template <typename T>
struct PathDeleter {
inline void operator()(T* ptr) const noexcept { FreePath(ptr); }
};
typedef std::unique_ptr<nfdchar_t, PathDeleter<nfdchar_t>> UniquePath;
typedef std::unique_ptr<nfdnchar_t, PathDeleter<nfdnchar_t>> UniquePathN;
typedef std::unique_ptr<nfdu8char_t, PathDeleter<nfdu8char_t>> UniquePathU8;
struct PathSetDeleter {
inline void operator()(const nfdpathset_t* ptr) const noexcept { PathSet::Free(ptr); }
};
typedef std::unique_ptr<const nfdpathset_t, PathSetDeleter> UniquePathSet;
template <typename T>
struct PathSetPathDeleter {
inline void operator()(T* ptr) const noexcept { PathSet::FreePath(ptr); }
};
typedef std::unique_ptr<nfdchar_t, PathSetPathDeleter<nfdchar_t>> UniquePathSetPath;
typedef std::unique_ptr<nfdnchar_t, PathSetPathDeleter<nfdnchar_t>> UniquePathSetPathN;
typedef std::unique_ptr<nfdu8char_t, PathSetPathDeleter<nfdu8char_t>> UniquePathSetPathU8;
inline nfdresult_t OpenDialog(UniquePathN& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = OpenDialog(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t OpenDialogMultiple(UniquePathSet& outPaths,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = OpenDialogMultiple(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}
inline nfdresult_t SaveDialog(UniquePathN& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr,
const nfdnchar_t* defaultName = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = SaveDialog(out, filterList, filterCount, defaultPath, defaultName);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t PickFolder(UniquePathN& outPath,
const nfdnchar_t* defaultPath = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = PickFolder(out, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
inline nfdresult_t OpenDialog(UniquePathU8& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = OpenDialog(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t OpenDialogMultiple(UniquePathSet& outPaths,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = OpenDialogMultiple(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}
inline nfdresult_t SaveDialog(UniquePathU8& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr,
const nfdu8char_t* defaultName = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = SaveDialog(out, filterList, filterCount, defaultPath, defaultName);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t PickFolder(UniquePathU8& outPath,
const nfdu8char_t* defaultPath = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = PickFolder(out, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#endif
namespace PathSet {
inline nfdresult_t Count(const UniquePathSet& uniquePathSet, nfdpathsetsize_t& count) noexcept {
return Count(uniquePathSet.get(), count);
}
inline nfdresult_t GetPath(const UniquePathSet& uniquePathSet,
nfdpathsetsize_t index,
UniquePathSetPathN& outPath) noexcept {
nfdnchar_t* out;
nfdresult_t res = GetPath(uniquePathSet.get(), index, out);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
inline nfdresult_t GetPath(const UniquePathSet& uniquePathSet,
nfdpathsetsize_t index,
UniquePathSetPathU8& outPath) noexcept {
nfdu8char_t* out;
nfdresult_t res = GetPath(uniquePathSet.get(), index, out);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#endif
} // namespace PathSet
} // namespace NFD
#endif

321
deps/nfde/src/nfd_cocoa.m vendored Normal file
View File

@ -0,0 +1,321 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo, Michael Labbe
*/
#include <AppKit/AppKit.h>
#include "nfd.h"
static const char* g_errorstr = NULL;
static void NFDi_SetError(const char* msg) {
g_errorstr = msg;
}
static void* NFDi_Malloc(size_t bytes) {
void* ptr = malloc(bytes);
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
return ptr;
}
static void NFDi_Free(void* ptr) {
assert(ptr);
free(ptr);
}
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
// Commas and semicolons are the same thing on this platform
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
// this is the spec to parse (we don't use the friendly name on OS X)
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
const nfdnchar_t* p_currentFilterBegin = filterSpec;
for (const nfdnchar_t* p_filterSpec = filterSpec; *p_filterSpec; ++p_filterSpec) {
if (*p_filterSpec == ',') {
// add the extension to the array
NSString* filterStr = [[[NSString alloc]
initWithBytes:(const void*)p_currentFilterBegin
length:(sizeof(nfdnchar_t) * (p_filterSpec - p_currentFilterBegin))
encoding:NSUTF8StringEncoding] autorelease];
[buildFilterList addObject:filterStr];
p_currentFilterBegin = p_filterSpec + 1;
}
}
// add the extension to the array
NSString* filterStr = [NSString stringWithUTF8String:p_currentFilterBegin];
[buildFilterList addObject:filterStr];
}
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
[buildFilterList release];
assert([returnArray count] != 0);
return returnArray;
}
static void AddFilterListToDialog(NSSavePanel* dialog,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
// note: NSOpenPanel inherits from NSSavePanel.
if (!filterCount) return;
assert(filterList);
// make NSArray of file types
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
// set it on the dialog
[dialog setAllowedFileTypes:allowedFileTypes];
}
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
if (!defaultPath || !*defaultPath) return;
NSString* defaultPathString = [NSString stringWithUTF8String:defaultPath];
NSURL* url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
}
static void SetDefaultName(NSSavePanel* dialog, const nfdnchar_t* defaultName) {
if (!defaultName || !*defaultName) return;
NSString* defaultNameString = [NSString stringWithUTF8String:defaultName];
[dialog setNameFieldStringValue:defaultNameString];
}
static nfdresult_t CopyUtf8String(const char* utf8Str, nfdnchar_t** out) {
// byte count, not char count
size_t len = strlen(utf8Str);
// Too bad we have to use additional memory for all the result paths,
// because we cannot reconstitute an NSString from a char* to release it properly.
*out = (nfdnchar_t*)NFDi_Malloc(len + 1);
if (*out) {
strcpy(*out, utf8Str);
return NFD_OKAY;
}
return NFD_ERROR;
}
/* public */
const char* NFD_GetError(void) {
return g_errorstr;
}
void NFD_FreePathN(nfdnchar_t* filePath) {
NFDi_Free((void*)filePath);
}
static NSApplicationActivationPolicy old_app_policy;
nfdresult_t NFD_Init(void) {
NSApplication* app = [NSApplication sharedApplication];
old_app_policy = [app activationPolicy];
if (old_app_policy == NSApplicationActivationPolicyProhibited) {
if (![app setActivationPolicy:NSApplicationActivationPolicyAccessory]) {
NFDi_SetError("Failed to set activation policy.");
return NFD_ERROR;
}
}
return NFD_OKAY;
}
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
void NFD_Quit(void) {
[[NSApplication sharedApplication] setActivationPolicy:old_app_policy];
}
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
// Build the filter list
AddFilterListToDialog(dialog, filterList, filterCount);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
if ([dialog runModal] == NSModalResponseOK) {
const NSURL* url = [dialog URL];
const char* utf8Path = [[url path] UTF8String];
result = CopyUtf8String(utf8Path, outPath);
}
// return focus to the key window (i.e. main window)
[keyWindow makeKeyAndOrderFront:nil];
}
return result;
}
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// Build the filter list
AddFilterListToDialog(dialog, filterList, filterCount);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
if ([dialog runModal] == NSModalResponseOK) {
const NSArray* urls = [dialog URLs];
if ([urls count] > 0) {
// have at least one URL, we return this NSArray
[urls retain];
*outPaths = (const nfdpathset_t*)urls;
result = NFD_OKAY;
}
}
// return focus to the key window (i.e. main window)
[keyWindow makeKeyAndOrderFront:nil];
}
return result;
}
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdnchar_t* defaultName) {
nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSSavePanel* dialog = [NSSavePanel savePanel];
[dialog setExtensionHidden:NO];
// allow other file types, to give the user an escape hatch since you can't select "*.*" on
// Mac
[dialog setAllowsOtherFileTypes:TRUE];
// Build the filter list
AddFilterListToDialog(dialog, filterList, filterCount);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
// Set the default file name
SetDefaultName(dialog, defaultName);
if ([dialog runModal] == NSModalResponseOK) {
const NSURL* url = [dialog URL];
const char* utf8Path = [[url path] UTF8String];
result = CopyUtf8String(utf8Path, outPath);
}
// return focus to the key window (i.e. main window)
[keyWindow makeKeyAndOrderFront:nil];
}
return result;
}
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
[dialog setCanChooseDirectories:YES];
[dialog setCanCreateDirectories:YES];
[dialog setCanChooseFiles:NO];
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
if ([dialog runModal] == NSModalResponseOK) {
const NSURL* url = [dialog URL];
const char* utf8Path = [[url path] UTF8String];
result = CopyUtf8String(utf8Path, outPath);
}
// return focus to the key window (i.e. main window)
[keyWindow makeKeyAndOrderFront:nil];
}
return result;
}
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
const NSArray* urls = (const NSArray*)pathSet;
*count = [urls count];
return NFD_OKAY;
}
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t** outPath) {
const NSArray* urls = (const NSArray*)pathSet;
@autoreleasepool {
// autoreleasepool needed because UTF8String method might use the pool
const NSURL* url = [urls objectAtIndex:index];
const char* utf8Path = [[url path] UTF8String];
return CopyUtf8String(utf8Path, outPath);
}
}
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
const NSArray* urls = (const NSArray*)pathSet;
[urls release];
}
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
const NSArray* urls = (const NSArray*)pathSet;
@autoreleasepool {
// autoreleasepool needed because NSEnumerator uses it
NSEnumerator* enumerator = [urls objectEnumerator];
[enumerator retain];
outEnumerator->ptr = (void*)enumerator;
}
return NFD_OKAY;
}
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
[real_enum release];
}
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
@autoreleasepool {
// autoreleasepool needed because NSURL uses it
const NSURL* url = [real_enum nextObject];
if (url) {
const char* utf8Path = [[url path] UTF8String];
return CopyUtf8String(utf8Path, outPath);
} else {
*outPath = NULL;
return NFD_OKAY;
}
}
}

631
deps/nfde/src/nfd_gtk.cpp vendored Normal file
View File

@ -0,0 +1,631 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo, Michael Labbe
Note: We do not check for malloc failure on Linux - Linux overcommits memory!
*/
#include <assert.h>
#include <gtk/gtk.h>
#if defined(GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#endif
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nfd.h"
namespace {
template <typename T>
struct Free_Guard {
T* data;
Free_Guard(T* freeable) noexcept : data(freeable) {}
~Free_Guard() { NFDi_Free(data); }
};
template <typename T>
struct FreeCheck_Guard {
T* data;
FreeCheck_Guard(T* freeable = nullptr) noexcept : data(freeable) {}
~FreeCheck_Guard() {
if (data) NFDi_Free(data);
}
};
/* current error */
const char* g_errorstr = nullptr;
void NFDi_SetError(const char* msg) {
g_errorstr = msg;
}
template <typename T = void>
T* NFDi_Malloc(size_t bytes) {
void* ptr = malloc(bytes);
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
return static_cast<T*>(ptr);
}
template <typename T>
void NFDi_Free(T* ptr) {
assert(ptr);
free(static_cast<void*>(ptr));
}
template <typename T>
T* copy(const T* begin, const T* end, T* out) {
for (; begin != end; ++begin) {
*out++ = *begin;
}
return out;
}
// Does not own the filter and extension.
struct Pair_GtkFileFilter_FileExtension {
GtkFileFilter* filter;
const nfdnchar_t* extensionBegin;
const nfdnchar_t* extensionEnd;
};
struct ButtonClickedArgs {
Pair_GtkFileFilter_FileExtension* map;
GtkFileChooser* chooser;
};
void AddFiltersToDialog(GtkFileChooser* chooser,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
if (filterCount) {
assert(filterList);
// we have filters to add ... format and add them
for (nfdfiltersize_t index = 0; index != filterCount; ++index) {
GtkFileFilter* filter = gtk_file_filter_new();
// count number of file extensions
size_t sep = 1;
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
if (*p_spec == L',') {
++sep;
}
}
// friendly name conversions: "png,jpg" -> "Image files
// (png, jpg)"
// calculate space needed (including the trailing '\0')
size_t nameSize =
sep + strlen(filterList[index].spec) + 3 + strlen(filterList[index].name);
// malloc the required memory
nfdnchar_t* nameBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * nameSize);
nfdnchar_t* p_nameBuf = nameBuf;
for (const nfdnchar_t* p_filterName = filterList[index].name; *p_filterName;
++p_filterName) {
*p_nameBuf++ = *p_filterName;
}
*p_nameBuf++ = ' ';
*p_nameBuf++ = '(';
const nfdnchar_t* p_extensionStart = filterList[index].spec;
for (const nfdnchar_t* p_spec = filterList[index].spec; true; ++p_spec) {
if (*p_spec == ',' || !*p_spec) {
if (*p_spec == ',') {
*p_nameBuf++ = ',';
*p_nameBuf++ = ' ';
}
// +1 for the trailing '\0'
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) *
(p_spec - p_extensionStart + 3));
nfdnchar_t* p_extnBufEnd = extnBuf;
*p_extnBufEnd++ = '*';
*p_extnBufEnd++ = '.';
p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd);
*p_extnBufEnd++ = '\0';
assert((size_t)(p_extnBufEnd - extnBuf) ==
sizeof(nfdnchar_t) * (p_spec - p_extensionStart + 3));
gtk_file_filter_add_pattern(filter, extnBuf);
NFDi_Free(extnBuf);
if (*p_spec) {
// update the extension start point
p_extensionStart = p_spec + 1;
} else {
// reached the '\0' character
break;
}
} else {
*p_nameBuf++ = *p_spec;
}
}
*p_nameBuf++ = ')';
*p_nameBuf++ = '\0';
assert((size_t)(p_nameBuf - nameBuf) == sizeof(nfdnchar_t) * nameSize);
// add to the filter
gtk_file_filter_set_name(filter, nameBuf);
// free the memory
NFDi_Free(nameBuf);
// add filter to chooser
gtk_file_chooser_add_filter(chooser, filter);
}
}
/* always append a wildcard option to the end*/
GtkFileFilter* filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "All files");
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(chooser, filter);
}
// returns null-terminated map (trailing .filter is null)
Pair_GtkFileFilter_FileExtension* AddFiltersToDialogWithMap(GtkFileChooser* chooser,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
Pair_GtkFileFilter_FileExtension* map = NFDi_Malloc<Pair_GtkFileFilter_FileExtension>(
sizeof(Pair_GtkFileFilter_FileExtension) * (filterCount + 1));
if (filterCount) {
assert(filterList);
// we have filters to add ... format and add them
for (nfdfiltersize_t index = 0; index != filterCount; ++index) {
GtkFileFilter* filter = gtk_file_filter_new();
// store filter in map
map[index].filter = filter;
map[index].extensionBegin = filterList[index].spec;
map[index].extensionEnd = nullptr;
// count number of file extensions
size_t sep = 1;
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
if (*p_spec == L',') {
++sep;
}
}
// friendly name conversions: "png,jpg" -> "Image files
// (png, jpg)"
// calculate space needed (including the trailing '\0')
size_t nameSize =
sep + strlen(filterList[index].spec) + 3 + strlen(filterList[index].name);
// malloc the required memory
nfdnchar_t* nameBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * nameSize);
nfdnchar_t* p_nameBuf = nameBuf;
for (const nfdnchar_t* p_filterName = filterList[index].name; *p_filterName;
++p_filterName) {
*p_nameBuf++ = *p_filterName;
}
*p_nameBuf++ = ' ';
*p_nameBuf++ = '(';
const nfdnchar_t* p_extensionStart = filterList[index].spec;
for (const nfdnchar_t* p_spec = filterList[index].spec; true; ++p_spec) {
if (*p_spec == ',' || !*p_spec) {
if (*p_spec == ',') {
*p_nameBuf++ = ',';
*p_nameBuf++ = ' ';
}
// +1 for the trailing '\0'
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) *
(p_spec - p_extensionStart + 3));
nfdnchar_t* p_extnBufEnd = extnBuf;
*p_extnBufEnd++ = '*';
*p_extnBufEnd++ = '.';
p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd);
*p_extnBufEnd++ = '\0';
assert((size_t)(p_extnBufEnd - extnBuf) ==
sizeof(nfdnchar_t) * (p_spec - p_extensionStart + 3));
gtk_file_filter_add_pattern(filter, extnBuf);
NFDi_Free(extnBuf);
// store current pointer in map (if it's
// the first one)
if (map[index].extensionEnd == nullptr) {
map[index].extensionEnd = p_spec;
}
if (*p_spec) {
// update the extension start point
p_extensionStart = p_spec + 1;
} else {
// reached the '\0' character
break;
}
} else {
*p_nameBuf++ = *p_spec;
}
}
*p_nameBuf++ = ')';
*p_nameBuf++ = '\0';
assert((size_t)(p_nameBuf - nameBuf) == sizeof(nfdnchar_t) * nameSize);
// add to the filter
gtk_file_filter_set_name(filter, nameBuf);
// free the memory
NFDi_Free(nameBuf);
// add filter to chooser
gtk_file_chooser_add_filter(chooser, filter);
}
}
// set trailing map index to null
map[filterCount].filter = nullptr;
/* always append a wildcard option to the end*/
GtkFileFilter* filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "All files");
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(chooser, filter);
return map;
}
void SetDefaultPath(GtkFileChooser* chooser, const char* defaultPath) {
if (!defaultPath || !*defaultPath) return;
/* GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
If consistency with the native OS is preferred, this is the line
to comment out. -ml */
gtk_file_chooser_set_current_folder(chooser, defaultPath);
}
void SetDefaultName(GtkFileChooser* chooser, const char* defaultName) {
if (!defaultName || !*defaultName) return;
gtk_file_chooser_set_current_name(chooser, defaultName);
}
void WaitForCleanup() {
while (gtk_events_pending()) gtk_main_iteration();
}
struct Widget_Guard {
GtkWidget* data;
Widget_Guard(GtkWidget* widget) : data(widget) {}
~Widget_Guard() {
WaitForCleanup();
gtk_widget_destroy(data);
WaitForCleanup();
}
};
void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) {
(void)saveButton; // silence the unused arg warning
ButtonClickedArgs* args = static_cast<ButtonClickedArgs*>(userdata);
GtkFileChooser* chooser = args->chooser;
char* currentFileName = gtk_file_chooser_get_current_name(chooser);
if (*currentFileName) { // string is not empty
// find a '.' in the file name
const char* p_period = currentFileName;
for (; *p_period; ++p_period) {
if (*p_period == '.') {
break;
}
}
if (!*p_period) { // there is no '.', so append the default extension
Pair_GtkFileFilter_FileExtension* filterMap =
static_cast<Pair_GtkFileFilter_FileExtension*>(args->map);
GtkFileFilter* currentFilter = gtk_file_chooser_get_filter(chooser);
if (currentFilter) {
for (; filterMap->filter; ++filterMap) {
if (filterMap->filter == currentFilter) break;
}
}
if (filterMap->filter) {
// memory for appended string (including '.' and
// trailing '\0')
char* appendedFileName = NFDi_Malloc<char>(
sizeof(char) * ((p_period - currentFileName) +
(filterMap->extensionEnd - filterMap->extensionBegin) + 2));
char* p_fileName = copy(currentFileName, p_period, appendedFileName);
*p_fileName++ = '.';
p_fileName = copy(filterMap->extensionBegin, filterMap->extensionEnd, p_fileName);
*p_fileName++ = '\0';
assert(p_fileName - appendedFileName ==
(p_period - currentFileName) +
(filterMap->extensionEnd - filterMap->extensionBegin) + 2);
// set the appended file name
gtk_file_chooser_set_current_name(chooser, appendedFileName);
// free the memory
NFDi_Free(appendedFileName);
}
}
}
// free the memory
g_free(currentFileName);
}
// wrapper for gtk_dialog_run() that brings the dialog to the front
// see issues at:
// https://github.com/btzy/nativefiledialog-extended/issues/31
// https://github.com/mlabbe/nativefiledialog/pull/92
// https://github.com/guillaumechereau/noc/pull/11
gint RunDialogWithFocus(GtkDialog* dialog) {
#if defined(GDK_WINDOWING_X11)
gtk_widget_show_all(GTK_WIDGET(dialog)); // show the dialog so that it gets a display
if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(dialog)))) {
GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(dialog));
gdk_window_set_events(
window,
static_cast<GdkEventMask>(gdk_window_get_events(window) | GDK_PROPERTY_CHANGE_MASK));
gtk_window_present_with_time(GTK_WINDOW(dialog), gdk_x11_get_server_time(window));
}
#endif
return gtk_dialog_run(dialog);
}
} // namespace
const char* NFD_GetError(void) {
return g_errorstr;
}
void NFD_ClearError(void) {
NFDi_SetError(nullptr);
}
/* public */
nfdresult_t NFD_Init(void) {
// Init GTK
if (!gtk_init_check(NULL, NULL)) {
NFDi_SetError("Failed to initialize GTK+ with gtk_init_check.");
return NFD_ERROR;
}
return NFD_OKAY;
}
void NFD_Quit(void) {
// do nothing, GTK cannot be de-initialized
}
void NFD_FreePathN(nfdnchar_t* filePath) {
assert(filePath);
g_free(filePath);
}
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
GtkWidget* widget = gtk_file_chooser_dialog_new("Open File",
nullptr,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel",
GTK_RESPONSE_CANCEL,
"_Open",
GTK_RESPONSE_ACCEPT,
nullptr);
// guard to destroy the widget when returning from this function
Widget_Guard widgetGuard(widget);
/* Build the filter list */
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);
/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
return NFD_OKAY;
} else {
return NFD_CANCEL;
}
}
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
GtkWidget* widget = gtk_file_chooser_dialog_new("Open Files",
nullptr,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel",
GTK_RESPONSE_CANCEL,
"_Open",
GTK_RESPONSE_ACCEPT,
nullptr);
// guard to destroy the widget when returning from this function
Widget_Guard widgetGuard(widget);
// set select multiple
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(widget), TRUE);
/* Build the filter list */
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);
/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
GSList* fileList = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
*outPaths = static_cast<void*>(fileList);
return NFD_OKAY;
} else {
return NFD_CANCEL;
}
}
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdnchar_t* defaultName) {
GtkWidget* widget = gtk_file_chooser_dialog_new("Save File",
nullptr,
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel",
GTK_RESPONSE_CANCEL,
nullptr);
// guard to destroy the widget when returning from this function
Widget_Guard widgetGuard(widget);
GtkWidget* saveButton = gtk_dialog_add_button(GTK_DIALOG(widget), "_Save", GTK_RESPONSE_ACCEPT);
// Prompt on overwrite
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(widget), TRUE);
/* Build the filter list */
ButtonClickedArgs buttonClickedArgs;
buttonClickedArgs.chooser = GTK_FILE_CHOOSER(widget);
buttonClickedArgs.map =
AddFiltersToDialogWithMap(GTK_FILE_CHOOSER(widget), filterList, filterCount);
/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
/* Set the default file name */
SetDefaultName(GTK_FILE_CHOOSER(widget), defaultName);
/* set the handler to add file extension */
gulong handlerID = g_signal_connect(G_OBJECT(saveButton),
"pressed",
G_CALLBACK(FileActivatedSignalHandler),
static_cast<void*>(&buttonClickedArgs));
/* invoke the dialog (blocks until dialog is closed) */
gint result = RunDialogWithFocus(GTK_DIALOG(widget));
/* unset the handler */
g_signal_handler_disconnect(G_OBJECT(saveButton), handlerID);
/* free the filter map */
NFDi_Free(buttonClickedArgs.map);
if (result == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
return NFD_OKAY;
} else {
return NFD_CANCEL;
}
}
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder",
nullptr,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel",
GTK_RESPONSE_CANCEL,
"_Select",
GTK_RESPONSE_ACCEPT,
nullptr);
// guard to destroy the widget when returning from this function
Widget_Guard widgetGuard(widget);
/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
return NFD_OKAY;
} else {
return NFD_CANCEL;
}
}
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
assert(pathSet);
// const_cast because methods on GSList aren't const, but it should act
// like const to the caller
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
*count = g_slist_length(fileList);
return NFD_OKAY;
}
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t** outPath) {
assert(pathSet);
// const_cast because methods on GSList aren't const, but it should act
// like const to the caller
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
// Note: this takes linear time... but should be good enough
*outPath = static_cast<nfdnchar_t*>(g_slist_nth_data(fileList, index));
return NFD_OKAY;
}
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) {
assert(filePath);
(void)filePath; // prevent warning in release build
// no-op, because NFD_PathSet_Free does the freeing for us
}
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
assert(pathSet);
// const_cast because methods on GSList aren't const, but it should act
// like const to the caller
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
// free all the nodes
for (GSList* node = fileList; node; node = node->next) {
assert(node->data);
g_free(node->data);
}
// free the path set memory
g_slist_free(fileList);
}
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
// The pathset (GSList) is already a linked list, so the enumeration is itself
outEnumerator->ptr = const_cast<void*>(pathSet);
return NFD_OKAY;
}
void NFD_PathSet_FreeEnum(nfdpathsetenum_t*) {
// Do nothing, because the enumeration is the pathset itself
}
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
const GSList* fileList = static_cast<const GSList*>(enumerator->ptr);
if (fileList) {
*outPath = static_cast<nfdnchar_t*>(fileList->data);
enumerator->ptr = static_cast<void*>(fileList->next);
} else {
*outPath = nullptr;
}
return NFD_OKAY;
}

1414
deps/nfde/src/nfd_portal.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

969
deps/nfde/src/nfd_win.cpp vendored Normal file
View File

@ -0,0 +1,969 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Author: Bernard Teo
*/
/* only locally define UNICODE in this compilation unit */
#ifndef UNICODE
#define UNICODE
#endif
#ifdef __MINGW32__
// Explicitly setting NTDDI version, this is necessary for the MinGW compiler
#define NTDDI_VERSION NTDDI_VISTA
#define _WIN32_WINNT _WIN32_WINNT_VISTA
#endif
#if _MSC_VER
// see
// https://developercommunity.visualstudio.com/content/problem/185399/error-c2760-in-combaseapih-with-windows-sdk-81-and.html
struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax error: 'identifier' was
// unexpected here" when using /permissive-
#endif
#include <assert.h>
#include <shobjidl.h>
#include <stdio.h>
#include <wchar.h>
#include <windows.h>
#include "nfd.h"
namespace {
/* current error */
const char* g_errorstr = nullptr;
void NFDi_SetError(const char* msg) {
g_errorstr = msg;
}
template <typename T = void>
T* NFDi_Malloc(size_t bytes) {
void* ptr = malloc(bytes);
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
return static_cast<T*>(ptr);
}
template <typename T>
void NFDi_Free(T* ptr) {
assert(ptr);
free(static_cast<void*>(ptr));
}
/* guard objects */
template <typename T>
struct Release_Guard {
T* data;
Release_Guard(T* releasable) noexcept : data(releasable) {}
~Release_Guard() { data->Release(); }
};
template <typename T>
struct Free_Guard {
T* data;
Free_Guard(T* freeable) noexcept : data(freeable) {}
~Free_Guard() { NFDi_Free(data); }
};
template <typename T>
struct FreeCheck_Guard {
T* data;
FreeCheck_Guard(T* freeable = nullptr) noexcept : data(freeable) {}
~FreeCheck_Guard() {
if (data) NFDi_Free(data);
}
};
/* helper functions */
nfdresult_t AddFiltersToDialog(::IFileDialog* fileOpenDialog,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
/* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
COMDLG_FILTERSPEC* specList =
NFDi_Malloc<COMDLG_FILTERSPEC>(sizeof(COMDLG_FILTERSPEC) * (filterCount + 1));
if (!specList) {
return NFD_ERROR;
}
/* ad-hoc RAII object to free memory when destructing */
struct COMDLG_FILTERSPEC_Guard {
COMDLG_FILTERSPEC* _specList;
nfdfiltersize_t index;
COMDLG_FILTERSPEC_Guard(COMDLG_FILTERSPEC* specList) noexcept
: _specList(specList), index(0) {}
~COMDLG_FILTERSPEC_Guard() {
for (--index; index != static_cast<nfdfiltersize_t>(-1); --index) {
NFDi_Free(const_cast<nfdnchar_t*>(_specList[index].pszSpec));
}
NFDi_Free(_specList);
}
};
COMDLG_FILTERSPEC_Guard specListGuard(specList);
if (filterCount) {
assert(filterList);
// we have filters to add ... format and add them
// use the index that comes from the RAII object (instead of making a copy), so the RAII
// object will know which memory to free
nfdfiltersize_t& index = specListGuard.index;
for (; index != filterCount; ++index) {
// set the friendly name of this filter
specList[index].pszName = filterList[index].name;
// set the specification of this filter...
// count number of file extensions
size_t sep = 1;
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
if (*p_spec == L',') {
++sep;
}
}
// calculate space needed (including the trailing '\0')
size_t specSize = sep * 2 + wcslen(filterList[index].spec) + 1;
// malloc the required memory and populate it
nfdnchar_t* specBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * specSize);
if (!specBuf) {
// automatic freeing of memory via COMDLG_FILTERSPEC_Guard
return NFD_ERROR;
}
// convert "png,jpg" to "*.png;*.jpg" as required by Windows ...
nfdnchar_t* p_specBuf = specBuf;
*p_specBuf++ = L'*';
*p_specBuf++ = L'.';
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
if (*p_spec == L',') {
*p_specBuf++ = L';';
*p_specBuf++ = L'*';
*p_specBuf++ = L'.';
} else {
*p_specBuf++ = *p_spec;
}
}
*p_specBuf++ = L'\0';
// assert that we had allocated exactly the correct amount of memory that we used
assert(static_cast<size_t>(p_specBuf - specBuf) == specSize);
// save the buffer to the guard object
specList[index].pszSpec = specBuf;
}
}
/* Add wildcard */
specList[filterCount].pszName = L"All files";
specList[filterCount].pszSpec = L"*.*";
// add the filter to the dialog
if (!SUCCEEDED(fileOpenDialog->SetFileTypes(filterCount + 1, specList))) {
NFDi_SetError("Failed to set the allowable file types for the drop-down menu.");
return NFD_ERROR;
}
// automatic freeing of memory via COMDLG_FILTERSPEC_Guard
return NFD_OKAY;
}
/* call after AddFiltersToDialog */
nfdresult_t SetDefaultExtension(::IFileDialog* fileOpenDialog,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
// if there are no filters, then don't set default extensions
if (!filterCount) {
return NFD_OKAY;
}
assert(filterList);
// set the first item as the default index, and set the default extension
if (!SUCCEEDED(fileOpenDialog->SetFileTypeIndex(1))) {
NFDi_SetError("Failed to set the selected file type index.");
return NFD_ERROR;
}
// set the first item as the default file extension
const nfdnchar_t* p_spec = filterList[0].spec;
for (; *p_spec; ++p_spec) {
if (*p_spec == ',') {
break;
}
}
if (*p_spec) {
// multiple file extensions for this type (need to allocate memory)
size_t numChars = p_spec - filterList[0].spec;
// allocate one more char space for the '\0'
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * (numChars + 1));
if (!extnBuf) {
return NFD_ERROR;
}
Free_Guard<nfdnchar_t> extnBufGuard(extnBuf);
// copy the extension
for (size_t i = 0; i != numChars; ++i) {
extnBuf[i] = filterList[0].spec[i];
}
// pad with trailing '\0'
extnBuf[numChars] = L'\0';
if (!SUCCEEDED(fileOpenDialog->SetDefaultExtension(extnBuf))) {
NFDi_SetError("Failed to set default extension.");
return NFD_ERROR;
}
} else {
// single file extension for this type (no need to allocate memory)
if (!SUCCEEDED(fileOpenDialog->SetDefaultExtension(filterList[0].spec))) {
NFDi_SetError("Failed to set default extension.");
return NFD_ERROR;
}
}
return NFD_OKAY;
}
nfdresult_t SetDefaultPath(IFileDialog* dialog, const nfdnchar_t* defaultPath) {
if (!defaultPath || !*defaultPath) return NFD_OKAY;
IShellItem* folder;
HRESULT result = SHCreateItemFromParsingName(defaultPath, nullptr, IID_PPV_ARGS(&folder));
// Valid non results.
if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE)) {
return NFD_OKAY;
}
if (!SUCCEEDED(result)) {
NFDi_SetError("Failed to create ShellItem for setting the default path.");
return NFD_ERROR;
}
Release_Guard<IShellItem> folderGuard(folder);
// SetDefaultFolder() might use another recently used folder if available, so the user doesn't
// need to keep navigating back to the default folder (recommended by Windows). change to
// SetFolder() if you always want to use the default folder
if (!SUCCEEDED(dialog->SetDefaultFolder(folder))) {
NFDi_SetError("Failed to set default path.");
return NFD_ERROR;
}
return NFD_OKAY;
}
nfdresult_t SetDefaultName(IFileDialog* dialog, const nfdnchar_t* defaultName) {
if (!defaultName || !*defaultName) return NFD_OKAY;
if (!SUCCEEDED(dialog->SetFileName(defaultName))) {
NFDi_SetError("Failed to set default file name.");
return NFD_ERROR;
}
return NFD_OKAY;
}
nfdresult_t AddOptions(IFileDialog* dialog, FILEOPENDIALOGOPTIONS options) {
FILEOPENDIALOGOPTIONS existingOptions;
if (!SUCCEEDED(dialog->GetOptions(&existingOptions))) {
NFDi_SetError("Failed to get options.");
return NFD_ERROR;
}
if (!SUCCEEDED(dialog->SetOptions(existingOptions | options))) {
NFDi_SetError("Failed to set options.");
return NFD_ERROR;
}
return NFD_OKAY;
}
} // namespace
const char* NFD_GetError(void) {
return g_errorstr;
}
void NFD_ClearError(void) {
NFDi_SetError(nullptr);
}
/* public */
namespace {
// The user might have initialized with COINIT_MULTITHREADED before,
// in which case we will fail to do CoInitializeEx(), but file dialogs will still work.
// See https://github.com/mlabbe/nativefiledialog/issues/72 for more information.
bool needs_uninitialize;
} // namespace
nfdresult_t NFD_Init(void) {
// Init COM library.
HRESULT result =
::CoInitializeEx(nullptr, ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(result)) {
needs_uninitialize = true;
return NFD_OKAY;
} else if (result == RPC_E_CHANGED_MODE) {
// If this happens, the user already initialized COM using COINIT_MULTITHREADED,
// so COM will still work, but we shouldn't uninitialize it later.
needs_uninitialize = false;
return NFD_OKAY;
} else {
NFDi_SetError("Failed to initialize COM.");
return NFD_ERROR;
}
}
void NFD_Quit(void) {
if (needs_uninitialize) ::CoUninitialize();
}
void NFD_FreePathN(nfdnchar_t* filePath) {
assert(filePath);
::CoTaskMemFree(filePath);
}
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
::IFileOpenDialog* fileOpenDialog;
// Create dialog
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog,
nullptr,
CLSCTX_ALL,
::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog));
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not create dialog.");
return NFD_ERROR;
}
// make sure we remember to free the dialog
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
// Build the filter list
if (!AddFiltersToDialog(fileOpenDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set auto-completed default extension
if (!SetDefaultExtension(fileOpenDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set the default path
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
return NFD_ERROR;
}
// Only show file system items
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM)) {
return NFD_ERROR;
}
// Show the dialog.
result = fileOpenDialog->Show(nullptr);
if (SUCCEEDED(result)) {
// Get the file name
::IShellItem* psiResult;
result = fileOpenDialog->GetResult(&psiResult);
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not get shell item from dialog.");
return NFD_ERROR;
}
Release_Guard<::IShellItem> psiResultGuard(psiResult);
nfdnchar_t* filePath;
result = psiResult->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not get file path from shell item returned by dialog.");
return NFD_ERROR;
}
*outPath = filePath;
return NFD_OKAY;
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
return NFD_CANCEL;
} else {
NFDi_SetError("File dialog box show failed.");
return NFD_ERROR;
}
}
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath) {
::IFileOpenDialog* fileOpenDialog(nullptr);
// Create dialog
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog,
nullptr,
CLSCTX_ALL,
::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog));
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not create dialog.");
return NFD_ERROR;
}
// make sure we remember to free the dialog
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
// Build the filter list
if (!AddFiltersToDialog(fileOpenDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set auto-completed default extension
if (!SetDefaultExtension(fileOpenDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set the default path
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
return NFD_ERROR;
}
// Set a flag for multiple options and file system items only
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM | ::FOS_ALLOWMULTISELECT)) {
return NFD_ERROR;
}
// Show the dialog.
result = fileOpenDialog->Show(nullptr);
if (SUCCEEDED(result)) {
::IShellItemArray* shellItems;
result = fileOpenDialog->GetResults(&shellItems);
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not get shell items.");
return NFD_ERROR;
}
// save the path set to the output
*outPaths = static_cast<void*>(shellItems);
return NFD_OKAY;
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
return NFD_CANCEL;
} else {
NFDi_SetError("File dialog box show failed.");
return NFD_ERROR;
}
}
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdnchar_t* defaultName) {
::IFileSaveDialog* fileSaveDialog;
// Create dialog
HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog,
nullptr,
CLSCTX_ALL,
::IID_IFileSaveDialog,
reinterpret_cast<void**>(&fileSaveDialog));
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not create dialog.");
return NFD_ERROR;
}
// make sure we remember to free the dialog
Release_Guard<::IFileSaveDialog> fileSaveDialogGuard(fileSaveDialog);
// Build the filter list
if (!AddFiltersToDialog(fileSaveDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set default extension
if (!SetDefaultExtension(fileSaveDialog, filterList, filterCount)) {
return NFD_ERROR;
}
// Set the default path
if (!SetDefaultPath(fileSaveDialog, defaultPath)) {
return NFD_ERROR;
}
// Set the default name
if (!SetDefaultName(fileSaveDialog, defaultName)) {
return NFD_ERROR;
}
// Only show file system items
if (!AddOptions(fileSaveDialog, ::FOS_FORCEFILESYSTEM)) {
return NFD_ERROR;
}
// Show the dialog.
result = fileSaveDialog->Show(nullptr);
if (SUCCEEDED(result)) {
// Get the file name
::IShellItem* psiResult;
result = fileSaveDialog->GetResult(&psiResult);
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not get shell item from dialog.");
return NFD_ERROR;
}
Release_Guard<::IShellItem> psiResultGuard(psiResult);
nfdnchar_t* filePath;
result = psiResult->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if (!SUCCEEDED(result)) {
NFDi_SetError("Could not get file path from shell item returned by dialog.");
return NFD_ERROR;
}
*outPath = filePath;
return NFD_OKAY;
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
return NFD_CANCEL;
} else {
NFDi_SetError("File dialog box show failed.");
return NFD_ERROR;
}
}
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
::IFileOpenDialog* fileOpenDialog;
// Create dialog
if (!SUCCEEDED(::CoCreateInstance(::CLSID_FileOpenDialog,
nullptr,
CLSCTX_ALL,
::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog)))) {
NFDi_SetError("Could not create dialog.");
return NFD_ERROR;
}
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
// Set the default path
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
return NFD_ERROR;
}
// Only show items that are folders and on the file system
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM | ::FOS_PICKFOLDERS)) {
return NFD_ERROR;
}
// Show the dialog to the user
const HRESULT result = fileOpenDialog->Show(nullptr);
if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
return NFD_CANCEL;
} else if (!SUCCEEDED(result)) {
NFDi_SetError("File dialog box show failed.");
return NFD_ERROR;
}
// Get the shell item result
::IShellItem* psiResult;
if (!SUCCEEDED(fileOpenDialog->GetResult(&psiResult))) {
return NFD_ERROR;
}
Release_Guard<::IShellItem> psiResultGuard(psiResult);
// Finally get the path
nfdnchar_t* filePath;
// Why are we not using SIGDN_FILESYSPATH?
if (!SUCCEEDED(psiResult->GetDisplayName(::SIGDN_DESKTOPABSOLUTEPARSING, &filePath))) {
NFDi_SetError("Could not get file path from shell item returned by dialog.");
return NFD_ERROR;
}
*outPath = filePath;
return NFD_OKAY;
}
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
assert(pathSet);
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
// the caller
::IShellItemArray* psiaPathSet =
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
DWORD numPaths;
if (!SUCCEEDED(psiaPathSet->GetCount(&numPaths))) {
NFDi_SetError("Could not get path count.");
return NFD_ERROR;
}
*count = numPaths;
return NFD_OKAY;
}
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t** outPath) {
assert(pathSet);
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
// the caller
::IShellItemArray* psiaPathSet =
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
::IShellItem* psiPath;
if (!SUCCEEDED(psiaPathSet->GetItemAt(index, &psiPath))) {
NFDi_SetError("Could not get shell item.");
return NFD_ERROR;
}
Release_Guard<::IShellItem> psiPathGuard(psiPath);
nfdnchar_t* name;
if (!SUCCEEDED(psiPath->GetDisplayName(::SIGDN_FILESYSPATH, &name))) {
NFDi_SetError("Could not get file path from shell item.");
return NFD_ERROR;
}
*outPath = name;
return NFD_OKAY;
}
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
assert(pathSet);
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
// the caller
::IShellItemArray* psiaPathSet =
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
::IEnumShellItems* pesiPaths;
if (!SUCCEEDED(psiaPathSet->EnumItems(&pesiPaths))) {
NFDi_SetError("Could not get enumerator.");
return NFD_ERROR;
}
outEnumerator->ptr = static_cast<void*>(pesiPaths);
return NFD_OKAY;
}
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
assert(enumerator->ptr);
::IEnumShellItems* pesiPaths = static_cast<::IEnumShellItems*>(enumerator->ptr);
// free the enumerator memory
pesiPaths->Release();
}
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
assert(enumerator->ptr);
::IEnumShellItems* pesiPaths = static_cast<::IEnumShellItems*>(enumerator->ptr);
::IShellItem* psiPath;
HRESULT res = pesiPaths->Next(1, &psiPath, NULL);
if (!SUCCEEDED(res)) {
NFDi_SetError("Could not get next item of enumerator.");
return NFD_ERROR;
}
if (res != S_OK) {
*outPath = nullptr;
return NFD_OKAY;
}
Release_Guard<::IShellItem> psiPathGuard(psiPath);
nfdnchar_t* name;
if (!SUCCEEDED(psiPath->GetDisplayName(::SIGDN_FILESYSPATH, &name))) {
NFDi_SetError("Could not get file path from shell item.");
return NFD_ERROR;
}
*outPath = name;
return NFD_OKAY;
}
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
assert(pathSet);
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
// the caller
::IShellItemArray* psiaPathSet =
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
// free the path set memory
psiaPathSet->Release();
}
namespace {
// allocs the space in outStr -- call NFDi_Free()
nfdresult_t CopyCharToWChar(const nfdu8char_t* inStr, nfdnchar_t*& outStr) {
int charsNeeded = MultiByteToWideChar(CP_UTF8, 0, inStr, -1, nullptr, 0);
assert(charsNeeded);
nfdnchar_t* tmp_outStr = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * charsNeeded);
if (!tmp_outStr) {
return NFD_ERROR;
}
int ret = MultiByteToWideChar(CP_UTF8, 0, inStr, -1, tmp_outStr, charsNeeded);
assert(ret && ret == charsNeeded);
(void)ret; // prevent warning in release build
outStr = tmp_outStr;
return NFD_OKAY;
}
// allocs the space in outPath -- call NFDi_Free()
nfdresult_t CopyWCharToNFDChar(const nfdnchar_t* inStr, nfdu8char_t*& outStr) {
int bytesNeeded = WideCharToMultiByte(CP_UTF8, 0, inStr, -1, nullptr, 0, nullptr, nullptr);
assert(bytesNeeded);
nfdu8char_t* tmp_outStr = NFDi_Malloc<nfdu8char_t>(sizeof(nfdu8char_t) * bytesNeeded);
if (!tmp_outStr) {
return NFD_ERROR;
}
int ret = WideCharToMultiByte(CP_UTF8, 0, inStr, -1, tmp_outStr, bytesNeeded, nullptr, nullptr);
assert(ret && ret == bytesNeeded);
(void)ret; // prevent warning in release build
outStr = tmp_outStr;
return NFD_OKAY;
}
struct FilterItem_Guard {
nfdnfilteritem_t* data;
nfdfiltersize_t index;
FilterItem_Guard() noexcept : data(nullptr), index(0) {}
~FilterItem_Guard() {
assert(data || index == 0);
for (--index; index != static_cast<nfdfiltersize_t>(-1); --index) {
NFDi_Free(const_cast<nfdnchar_t*>(data[index].spec));
NFDi_Free(const_cast<nfdnchar_t*>(data[index].name));
}
if (data) NFDi_Free(data);
}
};
nfdresult_t CopyFilterItem(const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
FilterItem_Guard& filterItemsNGuard) {
if (count) {
nfdnfilteritem_t*& filterItemsN = filterItemsNGuard.data;
filterItemsN = NFDi_Malloc<nfdnfilteritem_t>(sizeof(nfdnfilteritem_t) * count);
if (!filterItemsN) {
return NFD_ERROR;
}
nfdfiltersize_t& index = filterItemsNGuard.index;
for (; index != count; ++index) {
nfdresult_t res = CopyCharToWChar(filterList[index].name,
const_cast<nfdnchar_t*&>(filterItemsN[index].name));
if (!res) {
return NFD_ERROR;
}
res = CopyCharToWChar(filterList[index].spec,
const_cast<nfdnchar_t*&>(filterItemsN[index].spec));
if (!res) {
// remember to free the name, because we also created it (and it won't be protected
// by the guard, because we have not incremented the index)
NFDi_Free(const_cast<nfdnchar_t*>(filterItemsN[index].name));
return NFD_ERROR;
}
}
}
return NFD_OKAY;
}
nfdresult_t ConvertU8ToNative(const nfdu8char_t* u8Text, FreeCheck_Guard<nfdnchar_t>& nativeText) {
if (u8Text) {
nfdresult_t res = CopyCharToWChar(u8Text, nativeText.data);
if (!res) {
return NFD_ERROR;
}
}
return NFD_OKAY;
}
void NormalizePathSeparator(nfdnchar_t* path) {
if (path) {
for (; *path; ++path) {
if (*path == L'/') *path = L'\\';
}
}
}
} // namespace
void NFD_FreePathU8(nfdu8char_t* outPath) {
NFDi_Free(outPath);
}
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath) {
// populate the real nfdnfilteritem_t
FilterItem_Guard filterItemsNGuard;
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
return NFD_ERROR;
}
// convert and normalize the default path, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
ConvertU8ToNative(defaultPath, defaultPathNGuard);
NormalizePathSeparator(defaultPathNGuard.data);
// call the native function
nfdnchar_t* outPathN;
nfdresult_t res =
NFD_OpenDialogN(&outPathN, filterItemsNGuard.data, count, defaultPathNGuard.data);
if (res != NFD_OKAY) {
return res;
}
// convert the outPath to UTF-8
res = CopyWCharToNFDChar(outPathN, *outPath);
// free the native out path, and return the result
NFD_FreePathN(outPathN);
return res;
}
/* multiple file open dialog */
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
* returns NFD_OKAY */
nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath) {
// populate the real nfdnfilteritem_t
FilterItem_Guard filterItemsNGuard;
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
return NFD_ERROR;
}
// convert and normalize the default path, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
ConvertU8ToNative(defaultPath, defaultPathNGuard);
NormalizePathSeparator(defaultPathNGuard.data);
// call the native function
return NFD_OpenDialogMultipleN(outPaths, filterItemsNGuard.data, count, defaultPathNGuard.data);
}
/* save dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath,
const nfdu8filteritem_t* filterList,
nfdfiltersize_t count,
const nfdu8char_t* defaultPath,
const nfdu8char_t* defaultName) {
// populate the real nfdnfilteritem_t
FilterItem_Guard filterItemsNGuard;
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
return NFD_ERROR;
}
// convert and normalize the default path, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
ConvertU8ToNative(defaultPath, defaultPathNGuard);
NormalizePathSeparator(defaultPathNGuard.data);
// convert the default name, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultNameNGuard;
ConvertU8ToNative(defaultName, defaultNameNGuard);
// call the native function
nfdnchar_t* outPathN;
nfdresult_t res = NFD_SaveDialogN(
&outPathN, filterItemsNGuard.data, count, defaultPathNGuard.data, defaultNameNGuard.data);
if (res != NFD_OKAY) {
return res;
}
// convert the outPath to UTF-8
res = CopyWCharToNFDChar(outPathN, *outPath);
// free the native out path, and return the result
NFD_FreePathN(outPathN);
return res;
}
/* select folder dialog */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath) {
// convert and normalize the default path, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
ConvertU8ToNative(defaultPath, defaultPathNGuard);
NormalizePathSeparator(defaultPathNGuard.data);
// call the native function
nfdnchar_t* outPathN;
nfdresult_t res = NFD_PickFolderN(&outPathN, defaultPathNGuard.data);
if (res != NFD_OKAY) {
return res;
}
// convert the outPath to UTF-8
res = CopyWCharToNFDChar(outPathN, *outPath);
// free the native out path, and return the result
NFD_FreePathN(outPathN);
return res;
}
/* Get the UTF-8 path at offset index */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
* NFD_OKAY */
nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdu8char_t** outPath) {
// call the native function
nfdnchar_t* outPathN;
nfdresult_t res = NFD_PathSet_GetPathN(pathSet, index, &outPathN);
if (res != NFD_OKAY) {
return res;
}
// convert the outPath to UTF-8
res = CopyWCharToNFDChar(outPathN, *outPath);
// free the native out path, and return the result
NFD_FreePathN(outPathN);
return res;
}
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) {
// call the native function
nfdnchar_t* outPathN;
nfdresult_t res = NFD_PathSet_EnumNextN(enumerator, &outPathN);
if (res != NFD_OKAY) {
return res;
}
if (outPathN) {
// convert the outPath to UTF-8
res = CopyWCharToNFDChar(outPathN, *outPath);
// free the native out path, and return the result
NFD_FreePathN(outPathN);
} else {
*outPath = nullptr;
res = NFD_OKAY;
}
return res;
}

18
deps/nfde/test/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,18 @@
set(TEST_LIST
test_opendialog.c
test_opendialog_cpp.cpp
test_opendialogmultiple.c
test_opendialogmultiple_cpp.cpp
test_opendialogmultiple_enum.c
test_pickfolder.c
test_pickfolder_cpp.cpp
test_savedialog.c)
foreach (TEST ${TEST_LIST})
string(REPLACE "." "_" CLEAN_TEST_NAME ${TEST})
add_executable(${CLEAN_TEST_NAME}
${TEST})
target_link_libraries(${CLEAN_TEST_NAME}
PUBLIC nfd)
endforeach()

36
deps/nfde/test/test_opendialog.c vendored Normal file
View File

@ -0,0 +1,36 @@
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main(void) {
// initialize NFD
// either call NFD_Init at the start of your program and NFD_Quit at the end of your program,
// or before/after every time you want to show a file dialog.
NFD_Init();
nfdchar_t* outPath;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Headers", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 2, NULL);
if (result == NFD_OKAY) {
puts("Success!");
puts(outPath);
// remember to free the memory (since NFD_OKAY is returned)
NFD_FreePath(outPath);
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
} else {
printf("Error: %s\n", NFD_GetError());
}
// Quit NFD
NFD_Quit();
return 0;
}

29
deps/nfde/test/test_opendialog_cpp.cpp vendored Normal file
View File

@ -0,0 +1,29 @@
#include <iostream>
#include "nfd.hpp"
/* this test should compile on all supported platforms */
/* this demonstrates the thin C++ wrapper */
int main() {
// initialize NFD
NFD::Guard nfdGuard;
// auto-freeing memory
NFD::UniquePath outPath;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Headers", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD::OpenDialog(outPath, filterItem, 2);
if (result == NFD_OKAY) {
std::cout << "Success!" << std::endl << outPath.get() << std::endl;
} else if (result == NFD_CANCEL) {
std::cout << "User pressed cancel." << std::endl;
} else {
std::cout << "Error: " << NFD::GetError() << std::endl;
}
// NFD::Guard will automatically quit NFD.
return 0;
}

View File

@ -0,0 +1,50 @@
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main(void) {
// initialize NFD
// either call NFD_Init at the start of your program and NFD_Quit at the end of your program,
// or before/after every time you want to show a file dialog.
NFD_Init();
const nfdpathset_t* outPaths;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Headers", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD_OpenDialogMultiple(&outPaths, filterItem, 2, NULL);
if (result == NFD_OKAY) {
puts("Success!");
nfdpathsetsize_t numPaths;
NFD_PathSet_GetCount(outPaths, &numPaths);
nfdpathsetsize_t i;
for (i = 0; i < numPaths; ++i) {
nfdchar_t* path;
NFD_PathSet_GetPath(outPaths, i, &path);
printf("Path %i: %s\n", (int)i, path);
// remember to free the pathset path with NFD_PathSet_FreePath (not NFD_FreePath!)
NFD_PathSet_FreePath(path);
}
// remember to free the pathset memory (since NFD_OKAY is returned)
NFD_PathSet_Free(outPaths);
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
} else {
printf("Error: %s\n", NFD_GetError());
}
// Quit NFD
NFD_Quit();
return 0;
}

View File

@ -0,0 +1,40 @@
#include "nfd.hpp"
#include <iostream>
/* this test should compile on all supported platforms */
/* this demonstrates the thin C++ wrapper */
int main() {
// initialize NFD
NFD::Guard nfdGuard;
// auto-freeing memory
NFD::UniquePathSet outPaths;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Headers", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD::OpenDialogMultiple(outPaths, filterItem, 2);
if (result == NFD_OKAY) {
std::cout << "Success!" << std::endl;
nfdpathsetsize_t numPaths;
NFD::PathSet::Count(outPaths, numPaths);
nfdpathsetsize_t i;
for (i = 0; i < numPaths; ++i) {
NFD::UniquePathSetPath path;
NFD::PathSet::GetPath(outPaths, i, path);
std::cout << "Path " << i << ": " << path.get() << std::endl;
}
} else if (result == NFD_CANCEL) {
std::cout << "User pressed cancel." << std::endl;
} else {
std::cout << "Error: " << NFD::GetError() << std::endl;
}
// NFD::Guard will automatically quit NFD.
return 0;
}

View File

@ -0,0 +1,53 @@
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main(void) {
// initialize NFD
// either call NFD_Init at the start of your program and NFD_Quit at the end of your program,
// or before/after every time you want to show a file dialog.
NFD_Init();
const nfdpathset_t* outPaths;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Headers", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD_OpenDialogMultiple(&outPaths, filterItem, 2, NULL);
if (result == NFD_OKAY) {
puts("Success!");
// declare enumerator (not a pointer)
nfdpathsetenum_t enumerator;
NFD_PathSet_GetEnum(outPaths, &enumerator);
nfdchar_t* path;
unsigned i = 0;
while (NFD_PathSet_EnumNext(&enumerator, &path) && path) {
printf("Path %u: %s\n", i++, path);
// remember to free the pathset path with NFD_PathSet_FreePath (not NFD_FreePath!)
NFD_PathSet_FreePath(path);
}
// remember to free the pathset enumerator memory (before freeing the pathset)
NFD_PathSet_FreeEnum(&enumerator);
// remember to free the pathset memory (since NFD_OKAY is returned)
NFD_PathSet_Free(outPaths);
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
} else {
printf("Error: %s\n", NFD_GetError());
}
// Quit NFD
NFD_Quit();
return 0;
}

33
deps/nfde/test/test_pickfolder.c vendored Normal file
View File

@ -0,0 +1,33 @@
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main(void) {
// initialize NFD
// either call NFD_Init at the start of your program and NFD_Quit at the end of your program,
// or before/after every time you want to show a file dialog.
NFD_Init();
nfdchar_t* outPath;
// show the dialog
nfdresult_t result = NFD_PickFolder(&outPath, NULL);
if (result == NFD_OKAY) {
puts("Success!");
puts(outPath);
// remember to free the memory (since NFD_OKAY is returned)
NFD_FreePath(outPath);
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
} else {
printf("Error: %s\n", NFD_GetError());
}
// Quit NFD
NFD_Quit();
return 0;
}

27
deps/nfde/test/test_pickfolder_cpp.cpp vendored Normal file
View File

@ -0,0 +1,27 @@
#include "nfd.hpp"
#include <iostream>
/* this test should compile on all supported platforms */
/* this demonstrates the thin C++ wrapper */
int main() {
// initialize NFD
NFD::Guard nfdGuard;
// auto-freeing memory
NFD::UniquePath outPath;
// show the dialog
nfdresult_t result = NFD::PickFolder(outPath);
if (result == NFD_OKAY) {
std::cout << "Success!" << std::endl << outPath.get() << std::endl;
} else if (result == NFD_CANCEL) {
std::cout << "User pressed cancel." << std::endl;
} else {
std::cout << "Error: " << NFD::GetError() << std::endl;
}
// NFD::Guard will automatically quit NFD.
return 0;
}

36
deps/nfde/test/test_savedialog.c vendored Normal file
View File

@ -0,0 +1,36 @@
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main(void) {
// initialize NFD
// either call NFD_Init at the start of your program and NFD_Quit at the end of your program,
// or before/after every time you want to show a file dialog.
NFD_Init();
nfdchar_t* savePath;
// prepare filters for the dialog
nfdfilteritem_t filterItem[2] = {{"Source code", "c,cpp,cc"}, {"Header", "h,hpp"}};
// show the dialog
nfdresult_t result = NFD_SaveDialog(&savePath, filterItem, 2, NULL, "Untitled.c");
if (result == NFD_OKAY) {
puts("Success!");
puts(savePath);
// remember to free the memory (since NFD_OKAY is returned)
NFD_FreePath(savePath);
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
} else {
printf("Error: %s\n", NFD_GetError());
}
// Quit NFD
NFD_Quit();
return 0;
}