mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-06 20:01:37 +08:00

JS code ends up escaping Windows paths when data gets passed around from backend to the frontend, so those should be normalized beforehand.
271 lines
6.2 KiB
Go
271 lines
6.2 KiB
Go
//go:build windows
|
|
|
|
package application
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/go-common-file-dialog/cfd"
|
|
"github.com/wailsapp/wails/v3/pkg/w32"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func (m *windowsApp) showAboutDialog(title string, message string, _ []byte) {
|
|
about := newDialogImpl(&MessageDialog{
|
|
MessageDialogOptions: MessageDialogOptions{
|
|
DialogType: InfoDialogType,
|
|
Title: title,
|
|
Message: message,
|
|
},
|
|
})
|
|
about.UseAppIcon = true
|
|
about.show()
|
|
}
|
|
|
|
type windowsDialog struct {
|
|
dialog *MessageDialog
|
|
|
|
//dialogImpl unsafe.Pointer
|
|
UseAppIcon bool
|
|
}
|
|
|
|
func (m *windowsDialog) show() {
|
|
|
|
title := w32.MustStringToUTF16Ptr(m.dialog.Title)
|
|
message := w32.MustStringToUTF16Ptr(m.dialog.Message)
|
|
flags := calculateMessageDialogFlags(m.dialog.MessageDialogOptions)
|
|
var button int32
|
|
|
|
var parentWindow uintptr
|
|
var err error
|
|
if m.dialog.window != nil {
|
|
parentWindow, err = m.dialog.window.NativeWindowHandle()
|
|
if err != nil {
|
|
globalApplication.handleFatalError(err)
|
|
}
|
|
}
|
|
|
|
if m.UseAppIcon || m.dialog.Icon != nil {
|
|
// 3 is the application icon
|
|
button, err = w32.MessageBoxWithIcon(parentWindow, message, title, 3, windows.MB_OK|windows.MB_USERICON)
|
|
if err != nil {
|
|
globalApplication.handleFatalError(err)
|
|
}
|
|
} else {
|
|
button, err = windows.MessageBox(windows.HWND(parentWindow), message, title, flags|windows.MB_SYSTEMMODAL)
|
|
if err != nil {
|
|
globalApplication.handleFatalError(err)
|
|
}
|
|
}
|
|
// This maps MessageBox return values to strings
|
|
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
|
|
result := "Error"
|
|
if int(button) < len(responses) {
|
|
result = responses[button]
|
|
}
|
|
// Check if there's a callback for the button pressed
|
|
for _, buttonInDialog := range m.dialog.Buttons {
|
|
if buttonInDialog.Label == result {
|
|
if buttonInDialog.Callback != nil {
|
|
buttonInDialog.Callback()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func newDialogImpl(d *MessageDialog) *windowsDialog {
|
|
return &windowsDialog{
|
|
dialog: d,
|
|
}
|
|
}
|
|
|
|
type windowOpenFileDialog struct {
|
|
dialog *OpenFileDialogStruct
|
|
}
|
|
|
|
func newOpenFileDialogImpl(d *OpenFileDialogStruct) *windowOpenFileDialog {
|
|
return &windowOpenFileDialog{
|
|
dialog: d,
|
|
}
|
|
}
|
|
|
|
func getDefaultFolder(folder string) (string, error) {
|
|
if folder == "" {
|
|
return "", nil
|
|
}
|
|
return filepath.Abs(folder)
|
|
}
|
|
|
|
func (m *windowOpenFileDialog) show() (chan string, error) {
|
|
|
|
defaultFolder, err := getDefaultFolder(m.dialog.directory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config := cfd.DialogConfig{
|
|
Title: m.dialog.title,
|
|
Role: "PickFolder",
|
|
FileFilters: convertFilters(m.dialog.filters),
|
|
Folder: defaultFolder,
|
|
}
|
|
|
|
if m.dialog.window != nil {
|
|
config.ParentWindowHandle, err = m.dialog.window.NativeWindowHandle()
|
|
if err != nil {
|
|
globalApplication.handleFatalError(err)
|
|
}
|
|
}
|
|
|
|
var result []string
|
|
if m.dialog.allowsMultipleSelection && !m.dialog.canChooseDirectories {
|
|
temp, err := showCfdDialog(
|
|
func() (cfd.Dialog, error) {
|
|
return cfd.NewOpenMultipleFilesDialog(config)
|
|
}, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = temp.([]string)
|
|
} else {
|
|
if m.dialog.canChooseDirectories {
|
|
temp, err := showCfdDialog(
|
|
func() (cfd.Dialog, error) {
|
|
return cfd.NewSelectFolderDialog(config)
|
|
}, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = []string{temp.(string)}
|
|
} else {
|
|
temp, err := showCfdDialog(
|
|
func() (cfd.Dialog, error) {
|
|
return cfd.NewOpenFileDialog(config)
|
|
}, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = []string{temp.(string)}
|
|
}
|
|
}
|
|
|
|
files := make(chan string)
|
|
go func() {
|
|
defer handlePanic()
|
|
for _, file := range result {
|
|
files <- file
|
|
}
|
|
close(files)
|
|
}()
|
|
return files, nil
|
|
}
|
|
|
|
type windowSaveFileDialog struct {
|
|
dialog *SaveFileDialogStruct
|
|
}
|
|
|
|
func newSaveFileDialogImpl(d *SaveFileDialogStruct) *windowSaveFileDialog {
|
|
return &windowSaveFileDialog{
|
|
dialog: d,
|
|
}
|
|
}
|
|
|
|
func (m *windowSaveFileDialog) show() (chan string, error) {
|
|
files := make(chan string)
|
|
defaultFolder, err := getDefaultFolder(m.dialog.directory)
|
|
if err != nil {
|
|
close(files)
|
|
return files, err
|
|
}
|
|
|
|
config := cfd.DialogConfig{
|
|
Title: m.dialog.title,
|
|
Role: "SaveFile",
|
|
FileFilters: convertFilters(m.dialog.filters),
|
|
FileName: m.dialog.filename,
|
|
Folder: defaultFolder,
|
|
}
|
|
|
|
// Original PR for v2 by @almas1992: https://github.com/wailsapp/wails/pull/3205
|
|
if len(m.dialog.filters) > 0 {
|
|
config.DefaultExtension = strings.TrimPrefix(strings.Split(m.dialog.filters[0].Pattern, ";")[0], "*")
|
|
}
|
|
|
|
result, err := showCfdDialog(
|
|
func() (cfd.Dialog, error) {
|
|
return cfd.NewSaveFileDialog(config)
|
|
}, false)
|
|
go func() {
|
|
defer handlePanic()
|
|
files <- result.(string)
|
|
close(files)
|
|
}()
|
|
return files, err
|
|
}
|
|
|
|
func calculateMessageDialogFlags(options MessageDialogOptions) uint32 {
|
|
var flags uint32
|
|
|
|
switch options.DialogType {
|
|
case InfoDialogType:
|
|
flags = windows.MB_OK | windows.MB_ICONINFORMATION
|
|
case ErrorDialogType:
|
|
flags = windows.MB_ICONERROR | windows.MB_OK
|
|
case QuestionDialogType:
|
|
flags = windows.MB_YESNO
|
|
for _, button := range options.Buttons {
|
|
if strings.TrimSpace(strings.ToLower(button.Label)) == "no" && button.IsDefault {
|
|
flags |= windows.MB_DEFBUTTON2
|
|
}
|
|
}
|
|
case WarningDialogType:
|
|
flags = windows.MB_OK | windows.MB_ICONWARNING
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
func convertFilters(filters []FileFilter) []cfd.FileFilter {
|
|
var result []cfd.FileFilter
|
|
for _, filter := range filters {
|
|
result = append(result, cfd.FileFilter(filter))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalizeWindowsPath(path string) string {
|
|
return strings.ReplaceAll(path, "\\", "/")
|
|
}
|
|
|
|
func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
|
|
dlg, err := newDlg()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
err := dlg.Release()
|
|
if err != nil {
|
|
globalApplication.error("unable to release dialog: %w", err)
|
|
}
|
|
}()
|
|
|
|
if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
|
|
paths, err := multi.ShowAndGetResults()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, path := range paths {
|
|
paths[i] = normalizeWindowsPath(path)
|
|
}
|
|
return paths, nil
|
|
}
|
|
|
|
path, err := dlg.ShowAndGetResult()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return normalizeWindowsPath(path), nil
|
|
}
|