Skip to content

Commit

Permalink
New feature: Implement PickFolderMultiple
Browse files Browse the repository at this point in the history
  • Loading branch information
btzy committed May 18, 2024
1 parent ef4aa47 commit 38da115
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 3 deletions.
51 changes: 51 additions & 0 deletions src/include/nfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,55 @@ inline nfdresult_t NFD_PickFolderU8_With(nfdu8char_t** outPath, nfdpickfolderu8a
return NFD_PickFolderU8_With_Impl(NFD_INTERFACE_VERSION, outPath, &args);
}

/** Select multiple folder dialog
*
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeN() if this function
* returns NFD_OKAY.
* @param[out] outPaths
* @param defaultPath If null, the operating system will decide. */
NFD_API nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths,
const nfdnchar_t* defaultPath);

/** Select multiple folder dialog
*
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeU8() if this function
* returns NFD_OKAY.
* @param[out] outPaths
* @param defaultPath If null, the operating system will decide. */
NFD_API nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths,
const nfdu8char_t* defaultPath);

/** This function is a library implementation detail. Please use NFD_PickFolderMultipleN_With()
* instead. */
NFD_API nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfoldernargs_t* args);

/** Select multiple folder dialog, with additional parameters.
*
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeN() if this function
* returns NFD_OKAY. See documentation of nfdopendialogargs_t for details. */
inline nfdresult_t NFD_PickFolderMultipleN_With(const nfdpathset_t** outPaths,
nfdpickfoldernargs_t args) {
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

/** This function is a library implementation detail. Please use NFD_PickFolderMultipleU8_With()
* instead.
*/
NFD_API nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfolderu8args_t* args);

/** Select multiple folder dialog, with additional parameters.
*
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeU8() if this function
* returns NFD_OKAY. See documentation of nfdpickfolderargs_t for details. */
inline nfdresult_t NFD_PickFolderMultipleU8_With(const nfdpathset_t** outPaths,
nfdpickfolderu8args_t args) {
return NFD_PickFolderMultipleU8_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

/** Get the last error
*
* This is set when a function returns NFD_ERROR.
Expand Down Expand Up @@ -456,6 +505,7 @@ typedef nfdnfilteritem_t nfdfilteritem_t;
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
#define NFD_SaveDialog NFD_SaveDialogN
#define NFD_PickFolder NFD_PickFolderN
#define NFD_PickFolderMultiple NFD_PickFolderMultipleN
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
Expand All @@ -467,6 +517,7 @@ typedef nfdu8filteritem_t nfdfilteritem_t;
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleU8
#define NFD_SaveDialog NFD_SaveDialogU8
#define NFD_PickFolder NFD_PickFolderU8
#define NFD_PickFolderMultiple NFD_PickFolderMultipleU8
#define NFD_PathSet_GetPath NFD_PathSet_GetPathU8
#define NFD_PathSet_FreePath NFD_PathSet_FreePathU8
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextU8
Expand Down
53 changes: 53 additions & 0 deletions src/nfd_cocoa.m
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,59 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
return NFD_PickFolderN_With_Impl(version, outPath, args);
}

nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
nfdpickfoldernargs_t args = {0};
args.defaultPath = defaultPath;
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfoldernargs_t* args) {
// We haven't needed to bump the interface version yet.
(void)version;

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];

NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
[dialog setCanChooseDirectories:YES];
[dialog setCanCreateDirectories:YES];
[dialog setCanChooseFiles:NO];

// Set the starting directory
SetDefaultPath(dialog, args->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_PickFolderMultipleU8(const nfdpathset_t** outPaths,
const nfdu8char_t* defaultPath) {
return NFD_PickFolderMultipleN(outPaths, defaultPath);
}

nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfolderu8args_t* args) {
return NFD_PickFolderMultipleN_With_Impl(version, outPaths, args);
}

nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
const NSArray* urls = (const NSArray*)pathSet;
*count = [urls count];
Expand Down
48 changes: 47 additions & 1 deletion src/nfd_gtk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version,
// We haven't needed to bump the interface version yet.
(void)version;

GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder",
GtkWidget* widget = gtk_file_chooser_dialog_new("Select Folder",
nullptr,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel",
Expand Down Expand Up @@ -655,6 +655,52 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
const nfdpickfolderu8args_t* args)
__attribute__((alias("NFD_PickFolderN_With_Impl")));

nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
nfdpickfoldernargs_t args{};
args.defaultPath = defaultPath;
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfoldernargs_t* args) {
// We haven't needed to bump the interface version yet.
(void)version;

GtkWidget* widget = gtk_file_chooser_dialog_new("Select Folders",
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), args->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_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath)
__attribute__((alias("NFD_PickFolderMultipleN")));

nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfolderu8args_t* args)
__attribute__((alias("NFD_PickFolderMultipleN_With_Impl")));

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
Expand Down
62 changes: 60 additions & 2 deletions src/nfd_portal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ constexpr const char* STR_OPEN_FILE = "Open File";
constexpr const char* STR_OPEN_FILES = "Open Files";
constexpr const char* STR_SAVE_FILE = "Save File";
constexpr const char* STR_SELECT_FOLDER = "Select Folder";
constexpr const char* STR_SELECT_FOLDERS = "Select Folders";
constexpr const char* STR_HANDLE_TOKEN = "handle_token";
constexpr const char* STR_MULTIPLE = "multiple";
constexpr const char* STR_DIRECTORY = "directory";
Expand Down Expand Up @@ -149,6 +150,10 @@ template <>
void AppendOpenFileQueryTitle<false, true>(DBusMessageIter& iter) {
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER);
}
template <>
void AppendOpenFileQueryTitle<true, true>(DBusMessageIter& iter) {
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDERS);
}

void AppendSaveFileQueryTitle(DBusMessageIter& iter) {
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SAVE_FILE);
Expand Down Expand Up @@ -1547,8 +1552,6 @@ nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version,
// We haven't needed to bump the interface version yet.
(void)version;

(void)args; // Default path not supported for portal backend

{
dbus_uint32_t version;
const nfdresult_t res = NFD_DBus_GetVersion(version);
Expand Down Expand Up @@ -1593,6 +1596,61 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
const nfdpickfolderu8args_t* args)
__attribute__((alias("NFD_PickFolderN_With_Impl")));

nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
nfdpickfoldernargs_t args{};
args.defaultPath = defaultPath;
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfoldernargs_t* args) {
// We haven't needed to bump the interface version yet.
(void)version;

{
dbus_uint32_t version;
const nfdresult_t res = NFD_DBus_GetVersion(version);
if (res != NFD_OKAY) {
return res;
}
if (version < 3) {
NFDi_SetFormattedError(
"The xdg-desktop-portal installed on this system does not support a folder picker; "
"at least version 3 of the org.freedesktop.portal.FileChooser interface is "
"required but the installed interface version is %u.",
version);
return NFD_ERROR;
}
}

DBusMessage* msg;
{
const nfdresult_t res = NFD_DBus_OpenFile<true, true>(msg, nullptr, 0, args->defaultPath);
if (res != NFD_OKAY) {
return res;
}
}

DBusMessageIter uri_iter;
const nfdresult_t res = ReadResponseUris(msg, uri_iter);
if (res != NFD_OKAY) {
dbus_message_unref(msg);
return res;
}

*outPaths = msg;
return NFD_OKAY;
}

nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath)
__attribute__((alias("NFD_PickFolderMultipleN")));

nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfolderu8args_t* args)
__attribute__((alias("NFD_PickFolderMultipleN_With_Impl")));

nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
assert(pathSet);
DBusMessage* msg = const_cast<DBusMessage*>(static_cast<const DBusMessage*>(pathSet));
Expand Down
84 changes: 84 additions & 0 deletions src/nfd_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,64 @@ nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version,
return NFD_OKAY;
}

nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
nfdpickfoldernargs_t args{};
args.defaultPath = defaultPath;
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfoldernargs_t* args) {
// We haven't needed to bump the interface version yet.
(void)version;

::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, args->defaultPath)) {
return NFD_ERROR;
}

// Allow multiple selection; only show items that are folders and on the file system
if (!AddOptions(fileOpenDialog,
::FOS_FORCEFILESYSTEM | ::FOS_PICKFOLDERS | ::FOS_ALLOWMULTISELECT)) {
return NFD_ERROR;
}

// Show the dialog.
const HRESULT result = fileOpenDialog->Show(nullptr);
if (SUCCEEDED(result)) {
::IShellItemArray* shellItems;
if (!SUCCEEDED(fileOpenDialog->GetResults(&shellItems))) {
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_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
Expand Down Expand Up @@ -1034,6 +1092,32 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
return res;
}

/* select multiple folders dialog */
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeU8() if this function
* returns NFD_OKAY. */
nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths,
const nfdu8char_t* defaultPath) {
nfdpickfolderu8args_t args{};
args.defaultPath = defaultPath;
return NFD_PickFolderMultipleU8_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
}

nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
const nfdpathset_t** outPaths,
const nfdpickfolderu8args_t* args) {
// We haven't needed to bump the interface version yet.
(void)version;

// convert and normalize the default path, but only if it is not nullptr
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
ConvertU8ToNative(args->defaultPath, defaultPathNGuard);
NormalizePathSeparator(defaultPathNGuard.data);

// call the native function
const nfdpickfoldernargs_t argsN{defaultPathNGuard.data};
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &argsN);
}

/* 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 */
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ set(TEST_LIST
test_pickfolder_native.c
test_pickfolder_with.c
test_pickfolder_native_with.c
test_pickfoldermultiple.c
test_pickfoldermultiple_native.c
test_savedialog.c
test_savedialog_native.c
test_savedialog_with.c
Expand Down
Loading

0 comments on commit 38da115

Please sign in to comment.