From 25edefbf3f58ed6996a1a984a63a30c38482acb9 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:31:40 +0530 Subject: [PATCH 01/10] Add the option to set title on pickFolder dialog --- src/include/nfd.h | 4 +- src/include/nfd.hpp | 17 +- src/nfd_cocoa.m | 7 +- src/nfd_gtk.cpp | 17 +- src/nfd_portal.cpp | 12 +- src/nfd_win.cpp | 1949 ++++++++++++++++++++++--------------------- 6 files changed, 1021 insertions(+), 985 deletions(-) diff --git a/src/include/nfd.h b/src/include/nfd.h index eb9ba6d..25530e3 100644 --- a/src/include/nfd.h +++ b/src/include/nfd.h @@ -108,7 +108,7 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, /* 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); +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title); /* Get last error -- set when nfdresult_t returns NFD_ERROR */ /* Returns the last error that was set, or NULL if there is no error. */ @@ -207,7 +207,7 @@ nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, /* 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); +nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath, const nfdu8char_t* title); /* Get the UTF-8 path at offset index */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns diff --git a/src/include/nfd.hpp b/src/include/nfd.hpp index bbce108..05359f0 100644 --- a/src/include/nfd.hpp +++ b/src/include/nfd.hpp @@ -57,8 +57,8 @@ inline nfdresult_t SaveDialog(nfdnchar_t*& outPath, } inline nfdresult_t PickFolder(nfdnchar_t*& outPath, - const nfdnchar_t* defaultPath = nullptr) noexcept { - return ::NFD_PickFolderN(&outPath, defaultPath); + const nfdnchar_t* defaultPath = nullptr, const nfdnchar_t* title = nullptr) noexcept { + return ::NFD_PickFolderN(&outPath, defaultPath, title); } inline const char* GetError() noexcept { @@ -120,8 +120,9 @@ inline nfdresult_t SaveDialog(nfdu8char_t*& outPath, } inline nfdresult_t PickFolder(nfdu8char_t*& outPath, - const nfdu8char_t* defaultPath = nullptr) noexcept { - return ::NFD_PickFolderU8(&outPath, defaultPath); + const nfdu8char_t* defaultPath = nullptr, + const nfdu8char_t* title = nullptr) noexcept { + return ::NFD_PickFolderU8(&outPath, defaultPath, title); } namespace PathSet { @@ -220,9 +221,9 @@ inline nfdresult_t SaveDialog(UniquePathN& outPath, } inline nfdresult_t PickFolder(UniquePathN& outPath, - const nfdnchar_t* defaultPath = nullptr) noexcept { + const nfdnchar_t* defaultPath = nullptr, const nfdnchar_t* title = nullptr) noexcept { nfdnchar_t* out; - nfdresult_t res = PickFolder(out, defaultPath); + nfdresult_t res = PickFolder(out, defaultPath, title); if (res == NFD_OKAY) { outPath.reset(out); } @@ -268,9 +269,9 @@ inline nfdresult_t SaveDialog(UniquePathU8& outPath, } inline nfdresult_t PickFolder(UniquePathU8& outPath, - const nfdu8char_t* defaultPath = nullptr) noexcept { + const nfdu8char_t* defaultPath = nullptr, const nfdu8char_t* title = nullptr) noexcept { nfdu8char_t* out; - nfdresult_t res = PickFolder(out, defaultPath); + nfdresult_t res = PickFolder(out, defaultPath, title); if (res == NFD_OKAY) { outPath.reset(out); } diff --git a/src/nfd_cocoa.m b/src/nfd_cocoa.m index 5d74b13..43c0241 100644 --- a/src/nfd_cocoa.m +++ b/src/nfd_cocoa.m @@ -236,7 +236,7 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, return result; } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const cnfdnchar_t* title) { nfdresult_t result = NFD_CANCEL; @autoreleasepool { NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow]; @@ -247,6 +247,11 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) [dialog setCanCreateDirectories:YES]; [dialog setCanChooseFiles:NO]; + if (title) + { + [dialog setTitle:[NSString stringWithUTF8String:title]]; + } + // Set the starting directory SetDefaultPath(dialog, defaultPath); diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp index dda56a8..28c4d96 100644 --- a/src/nfd_gtk.cpp +++ b/src/nfd_gtk.cpp @@ -534,8 +534,21 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, } } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { - GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder", +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { + + GtkWidget* widget; + const char* folderTitle = title; + if (title) + widget = gtk_file_chooser_dialog_new(folderTitle, + nullptr, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Select", + GTK_RESPONSE_ACCEPT, + nullptr); + else + widget = gtk_file_chooser_dialog_new("Select folder", nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "_Cancel", diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index f5a9302..5a72a06 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -128,6 +128,9 @@ void AppendOpenFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER); } +template +void AppendOpenFileQueryTitle(DBusMessageIter&, const char* title); + void AppendSaveFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SAVE_FILE); } @@ -1028,7 +1031,8 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, template nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount) { + nfdfiltersize_t filterCount, + const char* title = nullptr) { const char* handle_token_ptr; char* handle_obj_path = MakeUniqueObjectPath(&handle_token_ptr); Free_Guard handle_obj_path_guard(handle_obj_path); @@ -1052,6 +1056,8 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, DBusMessage_Guard query_guard(query); AppendOpenFileQueryParams( query, handle_token_ptr, filterList, filterCount); + if (title) + AppendOpenFileQueryTitle(query, title); DBusMessage* reply = dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err); @@ -1319,12 +1325,12 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, #endif } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { (void)defaultPath; // Default path not supported for portal backend DBusMessage* msg; { - const nfdresult_t res = NFD_DBus_OpenFile(msg, nullptr, 0); + const nfdresult_t res = NFD_DBus_OpenFile(msg, nullptr, 0, title); if (res != NFD_OKAY) { return res; } diff --git a/src/nfd_win.cpp b/src/nfd_win.cpp index 772dfb2..2fb1f6c 100644 --- a/src/nfd_win.cpp +++ b/src/nfd_win.cpp @@ -1,969 +1,980 @@ -/* - 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 -#include -#include -#include -#include -#include "nfd.h" - -namespace { - -/* current error */ -const char* g_errorstr = nullptr; - -void NFDi_SetError(const char* msg) { - g_errorstr = msg; -} - -template -T* NFDi_Malloc(size_t bytes) { - void* ptr = malloc(bytes); - if (!ptr) NFDi_SetError("NFDi_Malloc failed."); - - return static_cast(ptr); -} - -template -void NFDi_Free(T* ptr) { - assert(ptr); - free(static_cast(ptr)); -} - -/* guard objects */ -template -struct Release_Guard { - T* data; - Release_Guard(T* releasable) noexcept : data(releasable) {} - ~Release_Guard() { data->Release(); } -}; - -template -struct Free_Guard { - T* data; - Free_Guard(T* freeable) noexcept : data(freeable) {} - ~Free_Guard() { NFDi_Free(data); } -}; - -template -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(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(-1); --index) { - NFDi_Free(const_cast(_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(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(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(sizeof(nfdnchar_t) * (numChars + 1)); - if (!extnBuf) { - return NFD_ERROR; - } - Free_Guard 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 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(&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(&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(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(&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(&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(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(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(pathSet)); - - ::IEnumShellItems* pesiPaths; - if (!SUCCEEDED(psiaPathSet->EnumItems(&pesiPaths))) { - NFDi_SetError("Could not get enumerator."); - return NFD_ERROR; - } - - outEnumerator->ptr = static_cast(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(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(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(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(-1); --index) { - NFDi_Free(const_cast(data[index].spec)); - NFDi_Free(const_cast(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(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(filterItemsN[index].name)); - if (!res) { - return NFD_ERROR; - } - res = CopyCharToWChar(filterList[index].spec, - const_cast(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(filterItemsN[index].name)); - return NFD_ERROR; - } - } - } - return NFD_OKAY; -} -nfdresult_t ConvertU8ToNative(const nfdu8char_t* u8Text, FreeCheck_Guard& 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 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 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 defaultPathNGuard; - ConvertU8ToNative(defaultPath, defaultPathNGuard); - NormalizePathSeparator(defaultPathNGuard.data); - - // convert the default name, but only if it is not nullptr - FreeCheck_Guard 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 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; -} +/* + 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 +#include +#include +#include +#include +#include "nfd.h" + +namespace { + +/* current error */ +const char* g_errorstr = nullptr; + +void NFDi_SetError(const char* msg) { + g_errorstr = msg; +} + +template +T* NFDi_Malloc(size_t bytes) { + void* ptr = malloc(bytes); + if (!ptr) NFDi_SetError("NFDi_Malloc failed."); + + return static_cast(ptr); +} + +template +void NFDi_Free(T* ptr) { + assert(ptr); + free(static_cast(ptr)); +} + +/* guard objects */ +template +struct Release_Guard { + T* data; + Release_Guard(T* releasable) noexcept : data(releasable) {} + ~Release_Guard() { data->Release(); } +}; + +template +struct Free_Guard { + T* data; + Free_Guard(T* freeable) noexcept : data(freeable) {} + ~Free_Guard() { NFDi_Free(data); } +}; + +template +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(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(-1); --index) { + NFDi_Free(const_cast(_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(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(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(sizeof(nfdnchar_t) * (numChars + 1)); + if (!extnBuf) { + return NFD_ERROR; + } + Free_Guard 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 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(&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(&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(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(&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, const nfdnchar_t* title) { + ::IFileOpenDialog* fileOpenDialog; + + // Create dialog + if (!SUCCEEDED(::CoCreateInstance(::CLSID_FileOpenDialog, + nullptr, + CLSCTX_ALL, + ::IID_IFileOpenDialog, + reinterpret_cast(&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; + } + + // Set the dialog title + if (title) + { + fileOpenDialog->SetTitle(title); + } + + // 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(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(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(pathSet)); + + ::IEnumShellItems* pesiPaths; + if (!SUCCEEDED(psiaPathSet->EnumItems(&pesiPaths))) { + NFDi_SetError("Could not get enumerator."); + return NFD_ERROR; + } + + outEnumerator->ptr = static_cast(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(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(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(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(-1); --index) { + NFDi_Free(const_cast(data[index].spec)); + NFDi_Free(const_cast(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(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(filterItemsN[index].name)); + if (!res) { + return NFD_ERROR; + } + res = CopyCharToWChar(filterList[index].spec, + const_cast(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(filterItemsN[index].name)); + return NFD_ERROR; + } + } + } + return NFD_OKAY; +} +nfdresult_t ConvertU8ToNative(const nfdu8char_t* u8Text, FreeCheck_Guard& 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 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 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 defaultPathNGuard; + ConvertU8ToNative(defaultPath, defaultPathNGuard); + NormalizePathSeparator(defaultPathNGuard.data); + + // convert the default name, but only if it is not nullptr + FreeCheck_Guard 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, const nfdu8char_t* title) { + // convert and normalize the default path, but only if it is not nullptr + FreeCheck_Guard defaultPathNGuard; + ConvertU8ToNative(defaultPath, defaultPathNGuard); + NormalizePathSeparator(defaultPathNGuard.data); + + // convert and normalize the title, but only if it is not nullptr + FreeCheck_Guard titleNGuard; + ConvertU8ToNative(title, titleNGuard); + NormalizePathSeparator(titleNGuard.data); + + // call the native function + nfdnchar_t* outPathN; + nfdresult_t res = NFD_PickFolderN(&outPathN, defaultPathNGuard.data, titleNGuard.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; +} From c0d679c82325c94a695a9296ffdc0cb9828c7b7f Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:38:22 +0530 Subject: [PATCH 02/10] Fix compile issues on cocoa and portal * Typo in cocoa --- src/nfd_cocoa.m | 2 +- src/nfd_portal.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/nfd_cocoa.m b/src/nfd_cocoa.m index 43c0241..27080f3 100644 --- a/src/nfd_cocoa.m +++ b/src/nfd_cocoa.m @@ -236,7 +236,7 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, return result; } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const cnfdnchar_t* title) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { nfdresult_t result = NFD_CANCEL; @autoreleasepool { NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow]; diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index 5a72a06..e24168b 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -532,13 +532,17 @@ template void AppendOpenFileQueryParams(DBusMessage* query, const char* handle_token, const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount) { + nfdfiltersize_t filterCount, + const char* title) { DBusMessageIter iter; dbus_message_iter_init_append(query, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY); - AppendOpenFileQueryTitle(iter); + if (title) + AppendOpenFileQueryTitle(iter, title); + else + AppendOpenFileQueryTitle(iter); DBusMessageIter sub_iter; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub_iter); @@ -1056,8 +1060,6 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, DBusMessage_Guard query_guard(query); AppendOpenFileQueryParams( query, handle_token_ptr, filterList, filterCount); - if (title) - AppendOpenFileQueryTitle(query, title); DBusMessage* reply = dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err); From c4a7ec8f1b6c1f7753341de1cd6be44dfacedc20 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:41:07 +0530 Subject: [PATCH 03/10] Fix the tests for pickFolder --- test/test_pickfolder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_pickfolder.c b/test/test_pickfolder.c index 12df199..ff40efb 100644 --- a/test/test_pickfolder.c +++ b/test/test_pickfolder.c @@ -14,7 +14,7 @@ int main(void) { nfdchar_t* outPath; // show the dialog - nfdresult_t result = NFD_PickFolder(&outPath, NULL); + nfdresult_t result = NFD_PickFolder(&outPath, NULL, NULL); if (result == NFD_OKAY) { puts("Success!"); puts(outPath); From 598ea50571576a634b49b715eb84db4536722ef5 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:43:13 +0530 Subject: [PATCH 04/10] Fix nfd_portal.cpp --- src/nfd_portal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index e24168b..1e050f7 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -1059,7 +1059,7 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, "OpenFile"); DBusMessage_Guard query_guard(query); AppendOpenFileQueryParams( - query, handle_token_ptr, filterList, filterCount); + query, handle_token_ptr, filterList, filterCount, title); DBusMessage* reply = dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err); From 85ec325cc11aafb2768ea4749ec7c64548ee7e20 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:45:34 +0530 Subject: [PATCH 05/10] Fix nfd_portal.cpp --- src/nfd_portal.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index 1e050f7..e32983b 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -128,7 +128,6 @@ void AppendOpenFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER); } -template void AppendOpenFileQueryTitle(DBusMessageIter&, const char* title); void AppendSaveFileQueryTitle(DBusMessageIter& iter) { @@ -542,7 +541,7 @@ void AppendOpenFileQueryParams(DBusMessage* query, if (title) AppendOpenFileQueryTitle(iter, title); else - AppendOpenFileQueryTitle(iter); + AppendOpenFileQueryTitle(iter); DBusMessageIter sub_iter; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub_iter); From abce8a667b6db19ce94dbb86eebb30a2d4371fd0 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:48:16 +0530 Subject: [PATCH 06/10] Fix nfd_cocoa.h --- src/nfd_portal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index e32983b..353311e 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -539,9 +539,9 @@ void AppendOpenFileQueryParams(DBusMessage* query, dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY); if (title) - AppendOpenFileQueryTitle(iter, title); + AppendOpenFileQueryTitle(iter, title); else - AppendOpenFileQueryTitle(iter); + AppendOpenFileQueryTitle(iter); DBusMessageIter sub_iter; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub_iter); From cb4ed72b96f119d025bab44c1bf6d84729b74a98 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:49:44 +0530 Subject: [PATCH 07/10] Add definition of custom fileDialog title --- src/nfd_portal.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index 353311e..d52acc9 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -128,7 +128,9 @@ void AppendOpenFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER); } -void AppendOpenFileQueryTitle(DBusMessageIter&, const char* title); +void AppendOpenFileQueryTitle(DBusMessageIter&, const char* title) { + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &title); +} void AppendSaveFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SAVE_FILE); From 223bc35f0de1f9a8f5de8b4f53f4df70989e72a5 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 21:51:24 +0530 Subject: [PATCH 08/10] Fix nfd_portal.cpp --- src/nfd_portal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index d52acc9..7c09432 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -128,7 +128,7 @@ void AppendOpenFileQueryTitle(DBusMessageIter& iter) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER); } -void AppendOpenFileQueryTitle(DBusMessageIter&, const char* title) { +void AppendOpenFileQueryTitle(DBusMessageIter& iter, const char* title) { dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &title); } From 79020af3b1706d891fc5790e29f72ffd31d4a727 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 22:06:15 +0530 Subject: [PATCH 09/10] Fix formatting --- src/include/nfd.h | 8 ++++++-- src/include/nfd.hpp | 11 +++++++---- src/nfd_cocoa.m | 12 +++++++----- src/nfd_gtk.cpp | 41 +++++++++++++++++++++-------------------- src/nfd_portal.cpp | 16 +++++++++------- src/nfd_win.cpp | 20 ++++++++++++-------- 6 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/include/nfd.h b/src/include/nfd.h index 25530e3..21b2fc3 100644 --- a/src/include/nfd.h +++ b/src/include/nfd.h @@ -108,7 +108,9 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, /* 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, const nfdnchar_t* title); +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, + const nfdnchar_t* defaultPath, + const nfdnchar_t* title); /* Get last error -- set when nfdresult_t returns NFD_ERROR */ /* Returns the last error that was set, or NULL if there is no error. */ @@ -207,7 +209,9 @@ nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, /* 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, const nfdu8char_t* title); +nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, + const nfdu8char_t* defaultPath, + const nfdu8char_t* title); /* Get the UTF-8 path at offset index */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns diff --git a/src/include/nfd.hpp b/src/include/nfd.hpp index 05359f0..9ac490a 100644 --- a/src/include/nfd.hpp +++ b/src/include/nfd.hpp @@ -57,7 +57,8 @@ inline nfdresult_t SaveDialog(nfdnchar_t*& outPath, } inline nfdresult_t PickFolder(nfdnchar_t*& outPath, - const nfdnchar_t* defaultPath = nullptr, const nfdnchar_t* title = nullptr) noexcept { + const nfdnchar_t* defaultPath = nullptr, + const nfdnchar_t* title = nullptr) noexcept { return ::NFD_PickFolderN(&outPath, defaultPath, title); } @@ -121,7 +122,7 @@ inline nfdresult_t SaveDialog(nfdu8char_t*& outPath, inline nfdresult_t PickFolder(nfdu8char_t*& outPath, const nfdu8char_t* defaultPath = nullptr, - const nfdu8char_t* title = nullptr) noexcept { + const nfdu8char_t* title = nullptr) noexcept { return ::NFD_PickFolderU8(&outPath, defaultPath, title); } @@ -221,7 +222,8 @@ inline nfdresult_t SaveDialog(UniquePathN& outPath, } inline nfdresult_t PickFolder(UniquePathN& outPath, - const nfdnchar_t* defaultPath = nullptr, const nfdnchar_t* title = nullptr) noexcept { + const nfdnchar_t* defaultPath = nullptr, + const nfdnchar_t* title = nullptr) noexcept { nfdnchar_t* out; nfdresult_t res = PickFolder(out, defaultPath, title); if (res == NFD_OKAY) { @@ -269,7 +271,8 @@ inline nfdresult_t SaveDialog(UniquePathU8& outPath, } inline nfdresult_t PickFolder(UniquePathU8& outPath, - const nfdu8char_t* defaultPath = nullptr, const nfdu8char_t* title = nullptr) noexcept { + const nfdu8char_t* defaultPath = nullptr, + const nfdu8char_t* title = nullptr) noexcept { nfdu8char_t* out; nfdresult_t res = PickFolder(out, defaultPath, title); if (res == NFD_OKAY) { diff --git a/src/nfd_cocoa.m b/src/nfd_cocoa.m index 27080f3..0e34c05 100644 --- a/src/nfd_cocoa.m +++ b/src/nfd_cocoa.m @@ -236,7 +236,9 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, return result; } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, + const nfdnchar_t* defaultPath, + const nfdnchar_t* title) { nfdresult_t result = NFD_CANCEL; @autoreleasepool { NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow]; @@ -247,10 +249,10 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, [dialog setCanCreateDirectories:YES]; [dialog setCanChooseFiles:NO]; - if (title) - { - [dialog setTitle:[NSString stringWithUTF8String:title]]; - } + if (title) + { + [dialog setTitle:[NSString stringWithUTF8String:title]]; + } // Set the starting directory SetDefaultPath(dialog, defaultPath); diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp index 28c4d96..7ecc184 100644 --- a/src/nfd_gtk.cpp +++ b/src/nfd_gtk.cpp @@ -534,28 +534,29 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, } } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { - - GtkWidget* widget; - const char* folderTitle = title; - if (title) - widget = gtk_file_chooser_dialog_new(folderTitle, - nullptr, - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, - "_Cancel", - GTK_RESPONSE_CANCEL, - "_Select", - GTK_RESPONSE_ACCEPT, - nullptr); +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, + const nfdnchar_t* defaultPath, + const nfdnchar_t* title) { + + GtkWidget* widget; + if (title) + widget = gtk_file_chooser_dialog_new(title, + nullptr, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Select", + GTK_RESPONSE_ACCEPT, + nullptr); else widget = gtk_file_chooser_dialog_new("Select folder", - nullptr, - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, - "_Cancel", - GTK_RESPONSE_CANCEL, - "_Select", - GTK_RESPONSE_ACCEPT, - nullptr); + 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); diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp index 7c09432..9dc01f5 100644 --- a/src/nfd_portal.cpp +++ b/src/nfd_portal.cpp @@ -129,7 +129,7 @@ void AppendOpenFileQueryTitle(DBusMessageIter& iter) { } void AppendOpenFileQueryTitle(DBusMessageIter& iter, const char* title) { - dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &title); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &title); } void AppendSaveFileQueryTitle(DBusMessageIter& iter) { @@ -534,16 +534,16 @@ void AppendOpenFileQueryParams(DBusMessage* query, const char* handle_token, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, - const char* title) { + const char* title) { DBusMessageIter iter; dbus_message_iter_init_append(query, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY); - if (title) + if (title) AppendOpenFileQueryTitle(iter, title); - else - AppendOpenFileQueryTitle(iter); + else + AppendOpenFileQueryTitle(iter); DBusMessageIter sub_iter; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub_iter); @@ -1037,7 +1037,7 @@ template nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, - const char* title = nullptr) { + const char* title = nullptr) { const char* handle_token_ptr; char* handle_obj_path = MakeUniqueObjectPath(&handle_token_ptr); Free_Guard handle_obj_path_guard(handle_obj_path); @@ -1328,7 +1328,9 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, #endif } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, + const nfdnchar_t* defaultPath, + const nfdnchar_t* title) { (void)defaultPath; // Default path not supported for portal backend DBusMessage* msg; diff --git a/src/nfd_win.cpp b/src/nfd_win.cpp index 2fb1f6c..f65ba70 100644 --- a/src/nfd_win.cpp +++ b/src/nfd_win.cpp @@ -541,7 +541,9 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, } } -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, + const nfdnchar_t* defaultPath, + const nfdnchar_t* title) { ::IFileOpenDialog* fileOpenDialog; // Create dialog @@ -566,11 +568,11 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, return NFD_ERROR; } - // Set the dialog title - if (title) - { - fileOpenDialog->SetTitle(title); - } + // Set the dialog title + if (title) + { + fileOpenDialog->SetTitle(title); + } // Show the dialog to the user const HRESULT result = fileOpenDialog->Show(nullptr); @@ -907,13 +909,15 @@ nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, /* 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, const nfdu8char_t* title) { +nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, + const nfdu8char_t* defaultPath, + const nfdu8char_t* title) { // convert and normalize the default path, but only if it is not nullptr FreeCheck_Guard defaultPathNGuard; ConvertU8ToNative(defaultPath, defaultPathNGuard); NormalizePathSeparator(defaultPathNGuard.data); - // convert and normalize the title, but only if it is not nullptr + // convert and normalize the title, but only if it is not nullptr FreeCheck_Guard titleNGuard; ConvertU8ToNative(title, titleNGuard); NormalizePathSeparator(titleNGuard.data); From ea70ec81c4d4422007def065174e5d8cec3b2284 Mon Sep 17 00:00:00 2001 From: dropTableUsers42 Date: Fri, 27 May 2022 22:08:26 +0530 Subject: [PATCH 10/10] Fix formatting --- src/nfd_cocoa.m | 3 +-- src/nfd_gtk.cpp | 1 - src/nfd_win.cpp | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/nfd_cocoa.m b/src/nfd_cocoa.m index 0e34c05..c864e98 100644 --- a/src/nfd_cocoa.m +++ b/src/nfd_cocoa.m @@ -249,8 +249,7 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, [dialog setCanCreateDirectories:YES]; [dialog setCanChooseFiles:NO]; - if (title) - { + if (title) { [dialog setTitle:[NSString stringWithUTF8String:title]]; } diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp index 7ecc184..fb84eae 100644 --- a/src/nfd_gtk.cpp +++ b/src/nfd_gtk.cpp @@ -537,7 +537,6 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath, const nfdnchar_t* title) { - GtkWidget* widget; if (title) widget = gtk_file_chooser_dialog_new(title, diff --git a/src/nfd_win.cpp b/src/nfd_win.cpp index f65ba70..9b0b574 100644 --- a/src/nfd_win.cpp +++ b/src/nfd_win.cpp @@ -569,8 +569,7 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, } // Set the dialog title - if (title) - { + if (title) { fileOpenDialog->SetTitle(title); }