5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 22:20:06 +08:00

Support SaveDialog

This commit is contained in:
Lea Anthony 2020-09-28 21:13:57 +10:00
parent 8cd39f6a9a
commit d7f832c00e
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
10 changed files with 200 additions and 39 deletions

View File

@ -6,6 +6,7 @@
"__functional_03": "c",
"functional": "c",
"__locale": "c",
"locale": "c"
"locale": "c",
"chrono": "c"
}
}

View File

@ -29,5 +29,6 @@ extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern void ToggleFullscreen(void *app);
extern void DisableFrame(void *app);
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filter, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories);
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories);
extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
#endif

View File

@ -124,7 +124,8 @@ func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filter),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
@ -135,3 +136,17 @@ func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}

View File

@ -407,17 +407,8 @@ void SetPosition(struct Application *app, int x, int y) {
)
}
// OpenFileDialog opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char* OpenFileDialog(struct Application *app, char *title, char *filter) {
Debug("OpenFileDialog Called");
char *filename = concat("","BogusOpenFilename");
return filename;
}
// OpenDialog opens a dialog to select files/directories
// NOTE: The result is a string that will need to be freed!
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filter, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories) {
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories) {
Debug("OpenDialog Called with callback id: %s", callbackID);
// Create an open panel
@ -430,9 +421,8 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
msg(dialog, s("setTitle:"), str(title));
// Filters
if( filter != NULL && strlen(filter) > 0) {
Debug("Using filter: %s", filter);
id filterString = msg(str(filter), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str(""));
if( filters != NULL && strlen(filters) > 0) {
id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str(""));
filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str(""));
id filterList = msg(filterString, s("componentsSeparatedByString:"), str(","));
msg(dialog, s("setAllowedFileTypes:"), filterList);
@ -446,9 +436,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
}
// Default Filename
// if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) {
// msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename));
// }
if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) {
msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename));
}
// Setup Options
msg(dialog, s("setCanChooseFiles:"), allowFiles);
@ -488,7 +478,7 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
json_delete(response);
// Construct callback message. Format "D<callbackID>|<json array of strings>"
const char *callback = concat("D", callbackID);
const char *callback = concat("DO", callbackID);
const char *header = concat(callback, "|");
const char *responseMessage = concat(header, encoded);
@ -505,6 +495,75 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
)
}
// SaveDialog opens a dialog to select files/directories
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
Debug("SaveDialog Called with callback id: %s", callbackID);
// Create an open panel
ON_MAIN_THREAD(
// Create the dialog
id dialog = msg(c("NSSavePanel"), s("savePanel"));
// Valid but appears to do nothing.... :/
msg(dialog, s("setTitle:"), str(title));
// Filters
if( filters != NULL && strlen(filters) > 0) {
id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str(""));
filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str(""));
id filterList = msg(filterString, s("componentsSeparatedByString:"), str(","));
msg(dialog, s("setAllowedFileTypes:"), filterList);
} else {
msg(dialog, s("setAllowsOtherFileTypes:"), YES);
}
// Default Directory
if( defaultDir != NULL && strlen(defaultDir) > 0 ) {
msg(dialog, s("setDirectoryURL:"), url(defaultDir));
}
// Default Filename
if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) {
msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename));
}
// Setup Options
msg(dialog, s("setShowsHiddenFiles:"), showHiddenFiles);
msg(dialog, s("setCanCreateDirectories:"), canCreateDirectories);
msg(dialog, s("setTreatsFilePackagesAsDirectories:"), treatPackagesAsDirectories);
// Setup callback handler
msg(dialog, s("beginSheetModalForWindow:completionHandler:"), app->mainWindow, ^(id result) {
// Default is blank
const char *filename = "";
// If the user selected some files
if( result == (id)1 ) {
// Grab the URL returned
id url = msg(dialog, s("URL"));
filename = (const char *)msg(msg(url, s("path")), s("UTF8String"));
}
// Construct callback message. Format "DS<callbackID>|<json array of strings>"
const char *callback = concat("DS", callbackID);
const char *header = concat(callback, "|");
const char *responseMessage = concat(header, filename);
// Send message to backend
app->sendMessageToBackend(responseMessage);
// Free memory
free((void*)header);
free((void*)callback);
free((void*)responseMessage);
});
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
)
}
const char *invoke = "window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}};";
// DisableFrame disables the window frame

View File

@ -15,6 +15,7 @@ type Client interface {
NotifyEvent(message string)
CallResult(message string)
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
WindowSetTitle(title string)
WindowShow()
WindowHide()

View File

@ -15,31 +15,38 @@ func dialogMessageParser(message string) (*parsedMessage, error) {
}
var topic = "bad topic from dialogMessageParser"
var data []string
var responseMessage *parsedMessage
// Switch the event type (with or without data)
switch message[0] {
// Format of Dialog response messages: D<callbackID>|<[]string as json encoded string>
case 'D':
idx := strings.IndexByte(message[1:], '|')
dialogType := message[1]
message = message[2:]
idx := strings.IndexByte(message, '|')
if idx < 0 {
return nil, fmt.Errorf("Invalid dialog response message format")
}
callbackID := message[1 : idx+1]
jsonData := message[idx+2:]
topic = "dialog:openselected:" + callbackID
callbackID := message[:idx+1]
payloadData := message[idx+1:]
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
return nil, err
switch dialogType {
case 'O':
var data []string
topic = "dialog:openselected:" + callbackID
err := json.Unmarshal([]byte(payloadData), &data)
if err != nil {
return nil, err
}
responseMessage = &parsedMessage{Topic: topic, Data: data}
case 'S':
topic = "dialog:saveselected:" + callbackID
responseMessage = &parsedMessage{Topic: topic, Data: payloadData}
}
default:
return nil, fmt.Errorf("Invalid message to dialogMessageParser()")
}
// Create a new parsed message struct
parsedMessage := &parsedMessage{Topic: topic, Data: data}
return parsedMessage, nil
return responseMessage, nil
}

View File

@ -342,9 +342,27 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
for _, client := range d.clients {
client.frontend.OpenDialog(dialogOptions, callbackID)
}
case "save":
dialogOptions, ok := result.Data().(*options.SaveDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
return
}
// This is hardcoded in the sender too
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
for _, client := range d.clients {
client.frontend.SaveDialog(dialogOptions, callbackID)
}
default:
d.logger.Error("Unknown dialog command: %s", command)
d.logger.Error("Unknown dialog type: %s", dialogType)
}
default:
d.logger.Error("Unknown dialog command: %s", command)
}
}

View File

@ -11,6 +11,7 @@ import (
// Dialog defines all Dialog related operations
type Dialog interface {
Open(dialogOptions *options.OpenDialog) []string
Save(dialogOptions *options.SaveDialog) string
}
// dialog exposes the Dialog interface
@ -66,3 +67,28 @@ func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
return result.Data().([]string)
}
// Save prompts the user to select a file
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:saveselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
message := "dialog:select:save:" + uniqueCallback
r.bus.Publish(message, dialogOptions)
// Wait for result
var result *servicebus.Message = <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}

View File

@ -5,7 +5,7 @@ type OpenDialog struct {
DefaultDirectory string
DefaultFilename string
Title string
Filter string
Filters string
AllowFiles bool
AllowDirectories bool
AllowMultiple bool
@ -14,3 +14,14 @@ type OpenDialog struct {
ResolveAliases bool
TreatPackagesAsDirectories bool
}
// SaveDialog contains the options for the SaveDialog runtime method
type SaveDialog struct {
DefaultDirectory string
DefaultFilename string
Title string
Filters string
ShowHiddenFiles bool
CanCreateDirectories bool
TreatPackagesAsDirectories bool
}

View File

@ -71,7 +71,7 @@ func (r *RuntimeTest) SetColour(colour int) {
func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
Title: title,
Filter: filter,
Filters: filter,
AllowFiles: true,
}
return r.runtime.Dialog.Open(dialogOptions)
@ -81,7 +81,7 @@ func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
Title: title,
Filter: filter,
Filters: filter,
AllowDirectories: true,
}
return r.runtime.Dialog.Open(dialogOptions)
@ -91,7 +91,7 @@ func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string
func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
Title: title,
Filter: filter,
Filters: filter,
AllowDirectories: true,
AllowFiles: true,
}
@ -102,7 +102,7 @@ func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
func (r *RuntimeTest) OpenDialogMultiple(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
Title: title,
Filter: filter,
Filters: filter,
AllowDirectories: true,
AllowFiles: true,
AllowMultiple: true,
@ -115,7 +115,7 @@ func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, def
dialogOptions := &options.OpenDialog{
DefaultDirectory: defaultDir,
DefaultFilename: defaultFilename,
Filter: filter,
Filters: filter,
AllowFiles: true,
AllowDirectories: true,
ShowHiddenFiles: true,
@ -126,6 +126,28 @@ func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, def
return r.runtime.Dialog.Open(dialogOptions)
}
// SaveFileDialog will call the Runtime.Dialog.SaveDialog method requesting a File selection
func (r *RuntimeTest) SaveFileDialog(title string, filter string) string {
dialogOptions := &options.SaveDialog{
Title: title,
Filters: filter,
}
return r.runtime.Dialog.Save(dialogOptions)
}
// SaveDialogAllOptions will call the Runtime.Dialog.SaveDialog method allowing multiple selection
func (r *RuntimeTest) SaveDialogAllOptions(filter string, defaultDir string, defaultFilename string) string {
dialogOptions := &options.SaveDialog{
DefaultDirectory: defaultDir,
DefaultFilename: defaultFilename,
Filters: filter,
ShowHiddenFiles: true,
CanCreateDirectories: true,
TreatPackagesAsDirectories: true,
}
return r.runtime.Dialog.Save(dialogOptions)
}
// HideWindow will call the Runtime.Window.Hide method and then call
// Runtime.Window.Show 3 seconds later.
func (r *RuntimeTest) HideWindow() {