5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 01:00:21 +08:00
wails/v3/pkg/application/dialogs.go
Johannes Lübke 14d6623c13
fix: unhandled error in dialogs.go
Does lead to deadlocks otherwise
2025-03-20 14:46:24 +01:00

495 lines
12 KiB
Go

package application
import (
"strings"
"sync"
)
type DialogType int
var dialogMapID = make(map[uint]struct{})
var dialogIDLock sync.RWMutex
func getDialogID() uint {
dialogIDLock.Lock()
defer dialogIDLock.Unlock()
var dialogID uint
for {
if _, ok := dialogMapID[dialogID]; !ok {
dialogMapID[dialogID] = struct{}{}
break
}
dialogID++
if dialogID == 0 {
panic("no more dialog IDs")
}
}
return dialogID
}
func freeDialogID(id uint) {
dialogIDLock.Lock()
defer dialogIDLock.Unlock()
delete(dialogMapID, id)
}
var openFileResponses = make(map[uint]chan string)
var saveFileResponses = make(map[uint]chan string)
const (
InfoDialogType DialogType = iota
QuestionDialogType
WarningDialogType
ErrorDialogType
)
type Button struct {
Label string
IsCancel bool
IsDefault bool
Callback func()
}
func (b *Button) OnClick(callback func()) *Button {
b.Callback = callback
return b
}
func (b *Button) SetAsDefault() *Button {
b.IsDefault = true
return b
}
func (b *Button) SetAsCancel() *Button {
b.IsCancel = true
return b
}
type messageDialogImpl interface {
show()
}
type MessageDialogOptions struct {
DialogType DialogType
Title string
Message string
Buttons []*Button
Icon []byte
window *WebviewWindow
}
type MessageDialog struct {
MessageDialogOptions
// platform independent
impl messageDialogImpl
}
var defaultTitles = map[DialogType]string{
InfoDialogType: "Information",
QuestionDialogType: "Question",
WarningDialogType: "Warning",
ErrorDialogType: "Error",
}
func newMessageDialog(dialogType DialogType) *MessageDialog {
return &MessageDialog{
MessageDialogOptions: MessageDialogOptions{
DialogType: dialogType,
},
impl: nil,
}
}
func (d *MessageDialog) SetTitle(title string) *MessageDialog {
d.Title = title
return d
}
func (d *MessageDialog) Show() {
if d.impl == nil {
d.impl = newDialogImpl(d)
}
InvokeSync(d.impl.show)
}
func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog {
d.Icon = icon
return d
}
func (d *MessageDialog) AddButton(s string) *Button {
result := &Button{
Label: s,
}
d.Buttons = append(d.Buttons, result)
return result
}
func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog {
d.Buttons = buttons
return d
}
func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog {
d.window = window.(*WebviewWindow)
return d
}
func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog {
for _, b := range d.Buttons {
b.IsDefault = false
}
button.IsDefault = true
return d
}
func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog {
for _, b := range d.Buttons {
b.IsCancel = false
}
button.IsCancel = true
return d
}
func (d *MessageDialog) SetMessage(message string) *MessageDialog {
d.Message = message
return d
}
type openFileDialogImpl interface {
show() (chan string, error)
}
type FileFilter struct {
DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)"
Pattern string // semicolon separated list of extensions, EG: "*.jpg;*.png"
}
type OpenFileDialogOptions struct {
CanChooseDirectories bool
CanChooseFiles bool
CanCreateDirectories bool
ShowHiddenFiles bool
ResolvesAliases bool
AllowsMultipleSelection bool
HideExtension bool
CanSelectHiddenExtension bool
TreatsFilePackagesAsDirectories bool
AllowsOtherFileTypes bool
Filters []FileFilter
Window *WebviewWindow
Title string
Message string
ButtonText string
Directory string
}
type OpenFileDialogStruct struct {
id uint
canChooseDirectories bool
canChooseFiles bool
canCreateDirectories bool
showHiddenFiles bool
resolvesAliases bool
allowsMultipleSelection bool
hideExtension bool
canSelectHiddenExtension bool
treatsFilePackagesAsDirectories bool
allowsOtherFileTypes bool
filters []FileFilter
title string
message string
buttonText string
directory string
window *WebviewWindow
impl openFileDialogImpl
}
func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct {
d.canChooseFiles = canChooseFiles
return d
}
func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct {
d.canChooseDirectories = canChooseDirectories
return d
}
func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct {
d.canCreateDirectories = canCreateDirectories
return d
}
func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct {
d.allowsOtherFileTypes = allowsOtherFileTypes
return d
}
func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct {
d.showHiddenFiles = showHiddenFiles
return d
}
func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct {
d.hideExtension = hideExtension
return d
}
func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct {
d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories
return d
}
func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct {
d.window = window.(*WebviewWindow)
return d
}
func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct {
d.resolvesAliases = resolvesAliases
return d
}
func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct {
d.title = title
return d
}
func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) {
d.allowsMultipleSelection = false
if d.impl == nil {
d.impl = newOpenFileDialogImpl(d)
}
var result string
selections, err := InvokeSyncWithResultAndError(d.impl.show)
if err == nil {
result = <-selections
}
return result, err
}
// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions.
// EG: AddFilter("Image Files", "*.jpg;*.png")
func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct {
d.filters = append(d.filters, FileFilter{
DisplayName: strings.TrimSpace(displayName),
Pattern: strings.TrimSpace(pattern),
})
return d
}
func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) {
d.allowsMultipleSelection = true
if d.impl == nil {
d.impl = newOpenFileDialogImpl(d)
}
selections, err := InvokeSyncWithResultAndError(d.impl.show)
if err != nil {
return nil, err
}
var result []string
for filename := range selections {
result = append(result, filename)
}
return result, err
}
func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct {
d.message = message
return d
}
func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct {
d.buttonText = text
return d
}
func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct {
d.directory = directory
return d
}
func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct {
d.canSelectHiddenExtension = canSelectHiddenExtension
return d
}
func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) {
d.title = options.Title
d.message = options.Message
d.buttonText = options.ButtonText
d.directory = options.Directory
d.canChooseDirectories = options.CanChooseDirectories
d.canChooseFiles = options.CanChooseFiles
d.canCreateDirectories = options.CanCreateDirectories
d.showHiddenFiles = options.ShowHiddenFiles
d.resolvesAliases = options.ResolvesAliases
d.allowsMultipleSelection = options.AllowsMultipleSelection
d.hideExtension = options.HideExtension
d.canSelectHiddenExtension = options.CanSelectHiddenExtension
d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories
d.allowsOtherFileTypes = options.AllowsOtherFileTypes
d.filters = options.Filters
d.window = options.Window
}
func newOpenFileDialog() *OpenFileDialogStruct {
return &OpenFileDialogStruct{
id: getDialogID(),
canChooseDirectories: false,
canChooseFiles: true,
canCreateDirectories: true,
resolvesAliases: false,
}
}
func newSaveFileDialog() *SaveFileDialogStruct {
return &SaveFileDialogStruct{
id: getDialogID(),
canCreateDirectories: true,
}
}
type SaveFileDialogOptions struct {
CanCreateDirectories bool
ShowHiddenFiles bool
CanSelectHiddenExtension bool
AllowOtherFileTypes bool
HideExtension bool
TreatsFilePackagesAsDirectories bool
Title string
Message string
Directory string
Filename string
ButtonText string
Filters []FileFilter
Window *WebviewWindow
}
type SaveFileDialogStruct struct {
id uint
canCreateDirectories bool
showHiddenFiles bool
canSelectHiddenExtension bool
allowOtherFileTypes bool
hideExtension bool
treatsFilePackagesAsDirectories bool
message string
directory string
filename string
buttonText string
filters []FileFilter
window *WebviewWindow
impl saveFileDialogImpl
title string
}
type saveFileDialogImpl interface {
show() (chan string, error)
}
func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) {
d.title = options.Title
d.canCreateDirectories = options.CanCreateDirectories
d.showHiddenFiles = options.ShowHiddenFiles
d.canSelectHiddenExtension = options.CanSelectHiddenExtension
d.allowOtherFileTypes = options.AllowOtherFileTypes
d.hideExtension = options.HideExtension
d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories
d.message = options.Message
d.directory = options.Directory
d.filename = options.Filename
d.buttonText = options.ButtonText
d.filters = options.Filters
d.window = options.Window
}
// AddFilter adds a filter to the dialog. The filter is a display name and a semicolon separated list of extensions.
// EG: AddFilter("Image Files", "*.jpg;*.png")
func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct {
d.filters = append(d.filters, FileFilter{
DisplayName: strings.TrimSpace(displayName),
Pattern: strings.TrimSpace(pattern),
})
return d
}
func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct {
d.canCreateDirectories = canCreateDirectories
return d
}
func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct {
d.canSelectHiddenExtension = canSelectHiddenExtension
return d
}
func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct {
d.showHiddenFiles = showHiddenFiles
return d
}
func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct {
d.message = message
return d
}
func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct {
d.directory = directory
return d
}
func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct {
d.window = window.(*WebviewWindow)
return d
}
func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) {
if d.impl == nil {
d.impl = newSaveFileDialogImpl(d)
}
var result string
selections, err := InvokeSyncWithResultAndError(d.impl.show)
if err == nil {
result = <-selections
}
return result, err
}
func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct {
d.buttonText = text
return d
}
func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct {
d.filename = filename
return d
}
func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct {
d.allowOtherFileTypes = allowOtherFileTypes
return d
}
func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct {
d.hideExtension = hideExtension
return d
}
func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct {
d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories
return d
}