Skip to content

Commit

Permalink
New feature: Implement PickFolderMultiple (#135)
Browse files Browse the repository at this point in the history
We have OpenFile, OpenFileMultiple, and PickFolder. It's only reasonable to also add PickFolderMultiple, since all backends support it. It does the expected thing on all backends.
  • Loading branch information
btzy authored Jun 23, 2024
1 parent 53de8bd commit ec07834
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 ec07834

Please sign in to comment.