Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiselect #21

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions osdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern "C" {

#include <stdint.h>
#include <stddef.h>
#include <string.h>


#ifndef OSDIALOG_MALLOC
Expand Down Expand Up @@ -55,6 +56,11 @@ typedef enum {
OSDIALOG_OPEN,
OSDIALOG_OPEN_DIR,
OSDIALOG_SAVE,
/**
Select multiple files.
Results returned as a null-seperated list of null-terminated strings, ending with a final '\0'.
*/
OSDIALOG_OPEN_MULTIPLE,
} osdialog_file_action;

/** Linked list of patterns. */
Expand Down
46 changes: 41 additions & 5 deletions osdialog_gtk2.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
acceptText = "Open";
gtkAction = GTK_FILE_CHOOSER_ACTION_OPEN;
}
else if (action == OSDIALOG_OPEN_MULTIPLE) {
title = "Open Files";
acceptText = "Open";
gtkAction = GTK_FILE_CHOOSER_ACTION_OPEN;
}
else if (action == OSDIALOG_OPEN_DIR) {
title = "Open Folder";
acceptText = "Open Folder";
Expand Down Expand Up @@ -115,6 +120,9 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), fileFilter);
}

if (action == OSDIALOG_OPEN_MULTIPLE)
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);

if (action == OSDIALOG_SAVE)
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);

Expand All @@ -124,16 +132,44 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
if (action == OSDIALOG_SAVE && filename)
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);

char* chosen_filename = NULL;
GSList* chosenFiles = NULL;
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
chosen_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
chosenFiles = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
}
gtk_widget_destroy(dialog);

char* result = NULL;
if (chosen_filename) {
result = osdialog_strndup(chosen_filename, strlen(chosen_filename));
g_free(chosen_filename);
if (chosenFiles) {
if (action != OSDIALOG_OPEN_MULTIPLE) {
result = osdialog_strndup(chosenFiles->data, strlen(chosenFiles->data));
g_free(chosenFiles->data);
}
else {
// calculate result length
int resultLen = 2;// include space for full terminator in case of an empty list
GSList* item = chosenFiles;
while (item) {
resultLen += strlen(item->data) + 1;
item = item->next;
}

result = OSDIALOG_MALLOC(resultLen);
memset(result, 0, resultLen);
char* resultPos = result;

item = chosenFiles;
while (item) {
int len = strlen(item->data);
memcpy(resultPos, item->data, len + 1);
resultPos += len + 1;

g_free(item->data);
item = item->next;
}
// final terminator was nulled via memset earlier
}

g_slist_free(chosenFiles);
}

while (gtk_events_pending())
Expand Down
36 changes: 31 additions & 5 deletions osdialog_mac.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ int osdialog_message(osdialog_message_level level, osdialog_message_buttons butt
// NSOpenPanel is a subclass of NSSavePanel. Not defined for OSDIALOG_SAVE.
NSOpenPanel* open_panel;

if (action == OSDIALOG_OPEN || action == OSDIALOG_OPEN_DIR) {
if (action == OSDIALOG_OPEN || action == OSDIALOG_OPEN_DIR || action == OSDIALOG_OPEN_MULTIPLE) {
panel = open_panel = [NSOpenPanel openPanel];
}
else {
Expand Down Expand Up @@ -140,6 +140,10 @@ int osdialog_message(osdialog_message_level level, osdialog_message_buttons butt
if (action == OSDIALOG_OPEN || action == OSDIALOG_OPEN_DIR) {
[open_panel setAllowsMultipleSelection:NO];
}
else if (action == OSDIALOG_OPEN_MULTIPLE) {
[open_panel setAllowsMultipleSelection:YES];
}

if (action == OSDIALOG_OPEN) {
[open_panel setCanChooseDirectories:NO];
[open_panel setCanChooseFiles:YES];
Expand Down Expand Up @@ -170,10 +174,32 @@ int osdialog_message(osdialog_message_level level, osdialog_message_buttons butt
#define OK NSOKButton
#endif
if (response == OK) {
NSURL* result_url = [panel URL];
NSString* result_str = [result_url path];
// Don't use NSString.length because it returns the number of the UTF-16 code units, not the number of bytes.
result = osdialog_strdup([result_str UTF8String]);
if (action != OSDIALOG_OPEN_MULTIPLE) {
NSURL* result_url = [panel URL];
NSString* result_str = [result_url path];
// Don't use NSString.length because it returns the number of the UTF-16 code units, not the number of bytes.
result = osdialog_strdup([result_str UTF8String]);
}
else {
// note: can't use NSMutableString and appendString; it doens't handle the null bytes right
NSArray<NSURL*>* results = [panel URLs];

int resultLen = 2;
for (NSURL *fileURL in results) {
resultLen += strlen([[fileURL path] UTF8String]) + 1;
}

result = OSDIALOG_MALLOC(resultLen);
memset(result, 0, resultLen);
char* resultPos = result;

for (NSURL *fileURL in results) {
const char* pathUTF8 = [[fileURL path] UTF8String];
int len = strlen(pathUTF8);
memcpy(resultPos, pathUTF8, len + 1);
resultPos += len + 1;
}
}
}

[keyWindow makeKeyAndOrderFront:nil];
Expand Down
87 changes: 82 additions & 5 deletions osdialog_win.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include <shlobj.h>
#include "osdialog.h"

#define snprintf(buf, len, format,...) _snprintf_s(buf, len, len, format, __VA_ARGS__)
#define snwprintf(buf, len, format,...) _snwprintf_s(buf, len, len, format, __VA_ARGS__)


static char* wchar_to_utf8(const wchar_t* s) {
if (!s)
Expand Down Expand Up @@ -265,14 +268,15 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;

// filename
wchar_t strFile[MAX_PATH] = L"";
#define MAX_RESULT_SIZE (1024 * 10)
wchar_t strFile[MAX_RESULT_SIZE] = L"";
if (filename) {
wchar_t* filenameW = utf8_to_wchar(filename);
snwprintf(strFile, MAX_PATH, L"%S", filenameW);
snwprintf(strFile, MAX_RESULT_SIZE, L"%lS", filenameW);
OSDIALOG_FREE(filenameW);
}
ofn.lpstrFile = strFile;
ofn.nMaxFile = MAX_PATH;
ofn.nMaxFile = MAX_RESULT_SIZE;

// dir
wchar_t strInitialDir[MAX_PATH] = L"";
Expand All @@ -292,14 +296,17 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi

for (; filters; filters = filters->next) {
fLen += snprintf(fBuf + fLen, sizeof(fBuf) - fLen, "%s", filters->name);
if (fLen + 1 >= sizeof(fBuf)) return NULL;
fBuf[fLen++] = '\0';
for (osdialog_filter_patterns* patterns = filters->patterns; patterns; patterns = patterns->next) {
fLen += snprintf(fBuf + fLen, sizeof(fBuf) - fLen, "*.%s", patterns->pattern);
if (patterns->next)
fLen += snprintf(fBuf + fLen, sizeof(fBuf) - fLen, ";");
}
if (fLen + 1 >= sizeof(fBuf)) return NULL;
fBuf[fLen++] = '\0';
}
if (fLen + 1 >= sizeof(fBuf)) return NULL;
fBuf[fLen++] = '\0';

// Don't use utf8_to_wchar() because this is not a NULL-terminated string.
Expand All @@ -310,7 +317,11 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
}

BOOL success;
if (action == OSDIALOG_OPEN) {
if (action == OSDIALOG_OPEN_MULTIPLE) {
ofn.Flags |= OFN_ALLOWMULTISELECT;
success = GetOpenFileNameW(&ofn);
}
else if (action == OSDIALOG_OPEN) {
success = GetOpenFileNameW(&ofn);
}
else {
Expand All @@ -324,7 +335,73 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
if (!success) {
return NULL;
}
return wchar_to_utf8(strFile);

if (action == OSDIALOG_OPEN_MULTIPLE) {
// GetOpenFileNameW returns [dirname, file, file, ...] as a null-seperated list of null-terminated strings

if (strFile[0] == 0 && strFile[1] == 0) return NULL;

const wchar_t* dirname = strFile;
int dirnameUTF8Len = WideCharToMultiByte(CP_UTF8, 0, dirname, -1, NULL, 0, NULL, NULL) - 1;

// get stats
int filesUTF8Len = 0;
int numFiles = 0;
const wchar_t* lastItemStart = strFile;
const wchar_t* firstFile = NULL;
for (int i = 0; i < MAX_RESULT_SIZE - 1; i++) {
if (strFile[i] == 0) {
++numFiles;
if (!firstFile && lastItemStart != dirname) firstFile = lastItemStart;
filesUTF8Len += WideCharToMultiByte(CP_UTF8, 0, lastItemStart, -1, NULL, 0, NULL, NULL) - 1;

lastItemStart = &strFile[i + 1];
if (strFile[i + 1] == 0) break; // end of list
}
}

if (numFiles == 1) {
// ...but if there's only one file GetOpenFileNameW just returns a single item with the dirname added
return wchar_to_utf8(strFile);
}

// right now numFiles/filesUTF8Len include dirname (first entry), correct
filesUTF8Len -= dirnameUTF8Len;
--numFiles;

int resultUTF8Len = (dirnameUTF8Len + 2) * numFiles + filesUTF8Len + 1;// dirname + '/' + file + '\0'
char* ret = OSDIALOG_MALLOC(resultUTF8Len);
memset(ret, 0, resultUTF8Len);

// convert chars and build result
lastItemStart = firstFile;
char* retPos = ret;
for (int i = firstFile - strFile; i < MAX_RESULT_SIZE - 1; i++) {
if (strFile[i] == 0) {// i is at the end of a filename
// write dir
WideCharToMultiByte(CP_UTF8, 0, dirname, -1, retPos, dirnameUTF8Len, NULL, NULL);
retPos += dirnameUTF8Len;

*retPos = '\\'; ++retPos;

// write file
int fileUTF8Len = WideCharToMultiByte(CP_UTF8, 0, lastItemStart, -1, NULL, 0, NULL, NULL) - 1;
WideCharToMultiByte(CP_UTF8, 0, lastItemStart, -1, retPos, fileUTF8Len, NULL, NULL);
retPos += fileUTF8Len;

*retPos = '\0'; ++retPos;

lastItemStart = &strFile[i + 1];

if (strFile[i + 1] == 0) break; // end of list, memset earlier already wrote a final '\0'
}
}

return ret;
}
else {
return wchar_to_utf8(strFile);
}
}
}

Expand Down
38 changes: 32 additions & 6 deletions osdialog_zenity.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
if (action == OSDIALOG_OPEN) {
// This is the default
}
else if (action == OSDIALOG_OPEN_MULTIPLE) {
args[argIndex++] = osdialog_strdup("--multiple");
// separate file names with \x1E "record seperator" because the default "|" can casually appear in
// filenames and using \x00 for a seperator isn't an option
#define FILE_SEP_CHAR '\x1E'
args[argIndex++] = osdialog_strdup("--separator=\x1E");
}
else if (action == OSDIALOG_OPEN_DIR) {
args[argIndex++] = osdialog_strdup("--directory");
}
Expand Down Expand Up @@ -205,17 +212,36 @@ char* osdialog_file(osdialog_file_action action, const char* dir, const char* fi
}

args[argIndex++] = NULL;
char outBuf[4096 + 1];
char outBuf[1024 * 10];
int ret = string_list_exec(zenityBin, (const char* const*) args, outBuf, sizeof(outBuf), NULL, 0);
string_list_clear(args);
if (ret != 0)
return NULL;

// Remove trailing newline
size_t outLen = strlen(outBuf);
if (outLen > 0)
outBuf[outLen - 1] = '\0';
return osdialog_strdup(outBuf);
if (action != OSDIALOG_OPEN_MULTIPLE) {
// Remove trailing newline
size_t outLen = strlen(outBuf);
if (outLen > 0)
outBuf[outLen - 1] = '\0';
return osdialog_strdup(outBuf);
}
else {
if (!strlen(outBuf)) return NULL;

// nix the ending newline, and make sure there's a \x00\x00 at the end
size_t resultLen = strlen(outBuf) + (2 - 1);
char* result = OSDIALOG_MALLOC(resultLen);
memcpy(result, outBuf, resultLen - 2);
result[resultLen - 1] = 0;
result[resultLen - 2] = 0;

// Convert \x1E to \x00,
for (size_t i = 0; i < resultLen; i++) {
if (result[i] == FILE_SEP_CHAR) result[i] = 0;
}

return result;
}
}


Expand Down
17 changes: 17 additions & 0 deletions test.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,22 @@ int main(int argc, char* argv[]) {
fprintf(stderr, "\t#%02x%02x%02x%02x\n", color.r, color.g, color.b, color.a);
}

// Open multiple files
if (test < 0 || test == 10) {
fprintf(stderr, "multi file open in cwd\n");
char* filename = osdialog_file(OSDIALOG_OPEN_MULTIPLE, ".", "こんにちは", NULL);
if (filename) {
char* file = filename;
while (strlen(file)) {
fprintf(stderr, "\t%s\n", file);
file += strlen(file) + 1;
}
free(filename);
}
else {
fprintf(stderr, "\tCanceled\n");
}
}

return 0;
}