5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 20:01:37 +08:00
wails/v3/pkg/application/dialogs_windows.go
Gabriel Lima 3f22e1fe0c
Normalize Windows paths by default
JS code ends up escaping Windows paths when data gets passed around from backend to the frontend, so those should be normalized beforehand.
2025-04-06 22:36:22 -03:00

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
}