Skip to content

Commit ec07834

Browse files
authored
New feature: Implement PickFolderMultiple (#135)
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.
1 parent 53de8bd commit ec07834

File tree

9 files changed

+428
-3
lines changed

9 files changed

+428
-3
lines changed

src/include/nfd.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,55 @@ NFD_INLINE nfdresult_t NFD_PickFolderU8_With(nfdu8char_t** outPath,
373373
return NFD_PickFolderU8_With_Impl(NFD_INTERFACE_VERSION, outPath, args);
374374
}
375375

376+
/** Select multiple folder dialog
377+
*
378+
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeN() if this function
379+
* returns NFD_OKAY.
380+
* @param[out] outPaths
381+
* @param defaultPath If null, the operating system will decide. */
382+
NFD_API nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths,
383+
const nfdnchar_t* defaultPath);
384+
385+
/** Select multiple folder dialog
386+
*
387+
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeU8() if this function
388+
* returns NFD_OKAY.
389+
* @param[out] outPaths
390+
* @param defaultPath If null, the operating system will decide. */
391+
NFD_API nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths,
392+
const nfdu8char_t* defaultPath);
393+
394+
/** This function is a library implementation detail. Please use NFD_PickFolderMultipleN_With()
395+
* instead. */
396+
NFD_API nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
397+
const nfdpathset_t** outPaths,
398+
const nfdpickfoldernargs_t* args);
399+
400+
/** Select multiple folder dialog, with additional parameters.
401+
*
402+
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeN() if this function
403+
* returns NFD_OKAY. See documentation of nfdopendialogargs_t for details. */
404+
NFD_INLINE nfdresult_t NFD_PickFolderMultipleN_With(const nfdpathset_t** outPaths,
405+
const nfdpickfoldernargs_t* args) {
406+
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, args);
407+
}
408+
409+
/** This function is a library implementation detail. Please use NFD_PickFolderMultipleU8_With()
410+
* instead.
411+
*/
412+
NFD_API nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
413+
const nfdpathset_t** outPaths,
414+
const nfdpickfolderu8args_t* args);
415+
416+
/** Select multiple folder dialog, with additional parameters.
417+
*
418+
* It is the caller's responsibility to free `outPaths` via NFD_PathSet_FreeU8() if this function
419+
* returns NFD_OKAY. See documentation of nfdpickfolderargs_t for details. */
420+
NFD_INLINE nfdresult_t NFD_PickFolderMultipleU8_With(const nfdpathset_t** outPaths,
421+
const nfdpickfolderu8args_t* args) {
422+
return NFD_PickFolderMultipleU8_With_Impl(NFD_INTERFACE_VERSION, outPaths, args);
423+
}
424+
376425
/** Get the last error
377426
*
378427
* This is set when a function returns NFD_ERROR.
@@ -465,6 +514,7 @@ typedef nfdnfilteritem_t nfdfilteritem_t;
465514
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
466515
#define NFD_SaveDialog NFD_SaveDialogN
467516
#define NFD_PickFolder NFD_PickFolderN
517+
#define NFD_PickFolderMultiple NFD_PickFolderMultipleN
468518
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
469519
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
470520
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
@@ -476,6 +526,7 @@ typedef nfdu8filteritem_t nfdfilteritem_t;
476526
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleU8
477527
#define NFD_SaveDialog NFD_SaveDialogU8
478528
#define NFD_PickFolder NFD_PickFolderU8
529+
#define NFD_PickFolderMultiple NFD_PickFolderMultipleU8
479530
#define NFD_PathSet_GetPath NFD_PathSet_GetPathU8
480531
#define NFD_PathSet_FreePath NFD_PathSet_FreePathU8
481532
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextU8

src/include/nfd.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ inline nfdresult_t PickFolder(nfdnchar_t*& outPath,
6565
return ::NFD_PickFolderN_With(&outPath, &args);
6666
}
6767

68+
inline nfdresult_t PickFolderMultiple(const nfdpathset_t*& outPaths,
69+
const nfdnchar_t* defaultPath = nullptr) noexcept {
70+
const nfdpickfoldernargs_t args{defaultPath};
71+
return ::NFD_PickFolderMultipleN_With(&outPaths, &args);
72+
}
73+
6874
inline const char* GetError() noexcept {
6975
return ::NFD_GetError();
7076
}
@@ -132,6 +138,12 @@ inline nfdresult_t PickFolder(nfdu8char_t*& outPath,
132138
return ::NFD_PickFolderU8_With(&outPath, &args);
133139
}
134140

141+
inline nfdresult_t PickFolderMultiple(const nfdpathset_t*& outPaths,
142+
const nfdu8char_t* defaultPath = nullptr) noexcept {
143+
const nfdpickfolderu8args_t args{defaultPath};
144+
return ::NFD_PickFolderMultipleU8_With(&outPaths, &args);
145+
}
146+
135147
namespace PathSet {
136148
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
137149
nfdpathsetsize_t index,
@@ -237,6 +249,16 @@ inline nfdresult_t PickFolder(UniquePathN& outPath,
237249
return res;
238250
}
239251

252+
inline nfdresult_t PickFolderMultiple(UniquePathSet& outPaths,
253+
const nfdnchar_t* defaultPath = nullptr) noexcept {
254+
const nfdpathset_t* out;
255+
nfdresult_t res = PickFolderMultiple(out, defaultPath);
256+
if (res == NFD_OKAY) {
257+
outPaths.reset(out);
258+
}
259+
return res;
260+
}
261+
240262
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
241263
inline nfdresult_t OpenDialog(UniquePathU8& outPath,
242264
const nfdu8filteritem_t* filterList = nullptr,
@@ -284,6 +306,16 @@ inline nfdresult_t PickFolder(UniquePathU8& outPath,
284306
}
285307
return res;
286308
}
309+
310+
inline nfdresult_t PickFolderMultiple(UniquePathSet& outPaths,
311+
const nfdu8char_t* defaultPath = nullptr) noexcept {
312+
const nfdpathset_t* out;
313+
nfdresult_t res = PickFolderMultiple(out, defaultPath);
314+
if (res == NFD_OKAY) {
315+
outPaths.reset(out);
316+
}
317+
return res;
318+
}
287319
#endif
288320

289321
namespace PathSet {

src/nfd_cocoa.m

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,59 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
437437
return NFD_PickFolderN_With_Impl(version, outPath, args);
438438
}
439439

440+
nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
441+
nfdpickfoldernargs_t args = {0};
442+
args.defaultPath = defaultPath;
443+
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
444+
}
445+
446+
nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
447+
const nfdpathset_t** outPaths,
448+
const nfdpickfoldernargs_t* args) {
449+
// We haven't needed to bump the interface version yet.
450+
(void)version;
451+
452+
nfdresult_t result = NFD_CANCEL;
453+
@autoreleasepool {
454+
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
455+
456+
NSOpenPanel* dialog = [NSOpenPanel openPanel];
457+
[dialog setAllowsMultipleSelection:YES];
458+
[dialog setCanChooseDirectories:YES];
459+
[dialog setCanCreateDirectories:YES];
460+
[dialog setCanChooseFiles:NO];
461+
462+
// Set the starting directory
463+
SetDefaultPath(dialog, args->defaultPath);
464+
465+
if ([dialog runModal] == NSModalResponseOK) {
466+
const NSArray* urls = [dialog URLs];
467+
468+
if ([urls count] > 0) {
469+
// have at least one URL, we return this NSArray
470+
[urls retain];
471+
*outPaths = (const nfdpathset_t*)urls;
472+
result = NFD_OKAY;
473+
}
474+
}
475+
476+
// return focus to the key window (i.e. main window)
477+
[keyWindow makeKeyAndOrderFront:nil];
478+
}
479+
return result;
480+
}
481+
482+
nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths,
483+
const nfdu8char_t* defaultPath) {
484+
return NFD_PickFolderMultipleN(outPaths, defaultPath);
485+
}
486+
487+
nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
488+
const nfdpathset_t** outPaths,
489+
const nfdpickfolderu8args_t* args) {
490+
return NFD_PickFolderMultipleN_With_Impl(version, outPaths, args);
491+
}
492+
440493
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
441494
const NSArray* urls = (const NSArray*)pathSet;
442495
*count = [urls count];

src/nfd_gtk.cpp

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version,
622622
// We haven't needed to bump the interface version yet.
623623
(void)version;
624624

625-
GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder",
625+
GtkWidget* widget = gtk_file_chooser_dialog_new("Select Folder",
626626
nullptr,
627627
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
628628
"_Cancel",
@@ -655,6 +655,52 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
655655
const nfdpickfolderu8args_t* args)
656656
__attribute__((alias("NFD_PickFolderN_With_Impl")));
657657

658+
nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
659+
nfdpickfoldernargs_t args{};
660+
args.defaultPath = defaultPath;
661+
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
662+
}
663+
664+
nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
665+
const nfdpathset_t** outPaths,
666+
const nfdpickfoldernargs_t* args) {
667+
// We haven't needed to bump the interface version yet.
668+
(void)version;
669+
670+
GtkWidget* widget = gtk_file_chooser_dialog_new("Select Folders",
671+
nullptr,
672+
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
673+
"_Cancel",
674+
GTK_RESPONSE_CANCEL,
675+
"_Select",
676+
GTK_RESPONSE_ACCEPT,
677+
nullptr);
678+
679+
// guard to destroy the widget when returning from this function
680+
Widget_Guard widgetGuard(widget);
681+
682+
/* Set the default path */
683+
SetDefaultPath(GTK_FILE_CHOOSER(widget), args->defaultPath);
684+
685+
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
686+
// write out the file name
687+
GSList* fileList = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
688+
689+
*outPaths = static_cast<void*>(fileList);
690+
return NFD_OKAY;
691+
} else {
692+
return NFD_CANCEL;
693+
}
694+
}
695+
696+
nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath)
697+
__attribute__((alias("NFD_PickFolderMultipleN")));
698+
699+
nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
700+
const nfdpathset_t** outPaths,
701+
const nfdpickfolderu8args_t* args)
702+
__attribute__((alias("NFD_PickFolderMultipleN_With_Impl")));
703+
658704
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
659705
assert(pathSet);
660706
// const_cast because methods on GSList aren't const, but it should act

src/nfd_portal.cpp

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ constexpr const char* STR_OPEN_FILE = "Open File";
120120
constexpr const char* STR_OPEN_FILES = "Open Files";
121121
constexpr const char* STR_SAVE_FILE = "Save File";
122122
constexpr const char* STR_SELECT_FOLDER = "Select Folder";
123+
constexpr const char* STR_SELECT_FOLDERS = "Select Folders";
123124
constexpr const char* STR_HANDLE_TOKEN = "handle_token";
124125
constexpr const char* STR_MULTIPLE = "multiple";
125126
constexpr const char* STR_DIRECTORY = "directory";
@@ -149,6 +150,10 @@ template <>
149150
void AppendOpenFileQueryTitle<false, true>(DBusMessageIter& iter) {
150151
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDER);
151152
}
153+
template <>
154+
void AppendOpenFileQueryTitle<true, true>(DBusMessageIter& iter) {
155+
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_SELECT_FOLDERS);
156+
}
152157

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

1550-
(void)args; // Default path not supported for portal backend
1551-
15521555
{
15531556
dbus_uint32_t version;
15541557
const nfdresult_t res = NFD_DBus_GetVersion(version);
@@ -1593,6 +1596,61 @@ nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version,
15931596
const nfdpickfolderu8args_t* args)
15941597
__attribute__((alias("NFD_PickFolderN_With_Impl")));
15951598

1599+
nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) {
1600+
nfdpickfoldernargs_t args{};
1601+
args.defaultPath = defaultPath;
1602+
return NFD_PickFolderMultipleN_With_Impl(NFD_INTERFACE_VERSION, outPaths, &args);
1603+
}
1604+
1605+
nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,
1606+
const nfdpathset_t** outPaths,
1607+
const nfdpickfoldernargs_t* args) {
1608+
// We haven't needed to bump the interface version yet.
1609+
(void)version;
1610+
1611+
{
1612+
dbus_uint32_t version;
1613+
const nfdresult_t res = NFD_DBus_GetVersion(version);
1614+
if (res != NFD_OKAY) {
1615+
return res;
1616+
}
1617+
if (version < 3) {
1618+
NFDi_SetFormattedError(
1619+
"The xdg-desktop-portal installed on this system does not support a folder picker; "
1620+
"at least version 3 of the org.freedesktop.portal.FileChooser interface is "
1621+
"required but the installed interface version is %u.",
1622+
version);
1623+
return NFD_ERROR;
1624+
}
1625+
}
1626+
1627+
DBusMessage* msg;
1628+
{
1629+
const nfdresult_t res = NFD_DBus_OpenFile<true, true>(msg, nullptr, 0, args->defaultPath);
1630+
if (res != NFD_OKAY) {
1631+
return res;
1632+
}
1633+
}
1634+
1635+
DBusMessageIter uri_iter;
1636+
const nfdresult_t res = ReadResponseUris(msg, uri_iter);
1637+
if (res != NFD_OKAY) {
1638+
dbus_message_unref(msg);
1639+
return res;
1640+
}
1641+
1642+
*outPaths = msg;
1643+
return NFD_OKAY;
1644+
}
1645+
1646+
nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath)
1647+
__attribute__((alias("NFD_PickFolderMultipleN")));
1648+
1649+
nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version,
1650+
const nfdpathset_t** outPaths,
1651+
const nfdpickfolderu8args_t* args)
1652+
__attribute__((alias("NFD_PickFolderMultipleN_With_Impl")));
1653+
15961654
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
15971655
assert(pathSet);
15981656
DBusMessage* msg = const_cast<DBusMessage*>(static_cast<const DBusMessage*>(pathSet));

0 commit comments

Comments
 (0)