mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 22:20:06 +08:00
Support SaveDialog
This commit is contained in:
parent
8cd39f6a9a
commit
d7f832c00e
3
v2/.vscode/settings.json
vendored
3
v2/.vscode/settings.json
vendored
@ -6,6 +6,7 @@
|
||||
"__functional_03": "c",
|
||||
"functional": "c",
|
||||
"__locale": "c",
|
||||
"locale": "c"
|
||||
"locale": "c",
|
||||
"chrono": "c"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user