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 Jun 23, 2024
1 parent 53de8bd commit 982a226
Show file tree
Hide file tree
Showing 9 changed files with 428 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 @@ -373,6 +373,55 @@ NFD_INLINE nfdresult_t NFD_PickFolderU8_With(nfdu8char_t** outPath,
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. */
NFD_INLINE nfdresult_t NFD_PickFolderMultipleN_With(const nfdpathset_t** outPaths,
const 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. */
NFD_INLINE nfdresult_t NFD_PickFolderMultipleU8_With(const nfdpathset_t** outPaths,
const 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 @@ -465,6 +514,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 @@ -476,6 +526,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
32 changes: 32 additions & 0 deletions src/include/nfd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ inline nfdresult_t PickFolder(nfdnchar_t*& outPath,
return ::NFD_PickFolderN_With(&outPath, &args);
}

inline nfdresult_t PickFolderMultiple(const nfdpathset_t*& outPaths,
const nfdnchar_t* defaultPath = nullptr) noexcept {
const nfdpickfoldernargs_t args{defaultPath};
return ::NFD_PickFolderMultipleN_With(&outPaths, &args);
}

inline const char* GetError() noexcept {
return ::NFD_GetError();
}
Expand Down Expand Up @@ -132,6 +138,12 @@ inline nfdresult_t PickFolder(nfdu8char_t*& outPath,
return ::NFD_PickFolderU8_With(&outPath, &args);
}

inline nfdresult_t PickFolderMultiple(const nfdpathset_t*& outPaths,
const nfdu8char_t* defaultPath = nullptr) noexcept {
const nfdpickfolderu8args_t args{defaultPath};
return ::NFD_PickFolderMultipleU8_With(&outPaths, &args);
}

namespace PathSet {
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
Expand Down Expand Up @@ -237,6 +249,16 @@ inline nfdresult_t PickFolder(UniquePathN& outPath,
return res;
}

inline nfdresult_t PickFolderMultiple(UniquePathSet& outPaths,
const nfdnchar_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = PickFolderMultiple(out, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}

#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
inline nfdresult_t OpenDialog(UniquePathU8& outPath,
const nfdu8filteritem_t* filterList = nullptr,
Expand Down Expand Up @@ -284,6 +306,16 @@ inline nfdresult_t PickFolder(UniquePathU8& outPath,
}
return res;
}

inline nfdresult_t PickFolderMultiple(UniquePathSet& outPaths,
const nfdu8char_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = PickFolderMultiple(out, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}
#endif

namespace PathSet {
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
Loading

0 comments on commit 982a226

Please sign in to comment.