github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/dialog.go (about) 1 //go:build windows 2 // +build windows 3 4 package windows 5 6 import ( 7 "path/filepath" 8 "strings" 9 "syscall" 10 11 "github.com/secoba/wails/v2/internal/frontend" 12 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32" 13 "github.com/secoba/wails/v2/internal/go-common-file-dialog/cfd" 14 "golang.org/x/sys/windows" 15 ) 16 17 func (f *Frontend) getHandleForDialog() w32.HWND { 18 if f.mainWindow.IsVisible() { 19 return f.mainWindow.Handle() 20 } 21 return 0 22 } 23 24 func getDefaultFolder(folder string) (string, error) { 25 if folder == "" { 26 return "", nil 27 } 28 return filepath.Abs(folder) 29 } 30 31 // OpenDirectoryDialog prompts the user to select a directory 32 func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) { 33 34 defaultFolder, err := getDefaultFolder(options.DefaultDirectory) 35 if err != nil { 36 return "", err 37 } 38 39 config := cfd.DialogConfig{ 40 Title: options.Title, 41 Role: "PickFolder", 42 Folder: defaultFolder, 43 } 44 45 result, err := f.showCfdDialog( 46 func() (cfd.Dialog, error) { 47 return cfd.NewSelectFolderDialog(config) 48 }, false) 49 50 if err != nil && err != cfd.ErrCancelled { 51 return "", err 52 } 53 return result.(string), nil 54 } 55 56 // OpenFileDialog prompts the user to select a file 57 func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) { 58 defaultFolder, err := getDefaultFolder(options.DefaultDirectory) 59 if err != nil { 60 return "", err 61 } 62 63 config := cfd.DialogConfig{ 64 Folder: defaultFolder, 65 FileFilters: convertFilters(options.Filters), 66 FileName: options.DefaultFilename, 67 Title: options.Title, 68 } 69 70 result, err := f.showCfdDialog( 71 func() (cfd.Dialog, error) { 72 return cfd.NewOpenFileDialog(config) 73 }, false) 74 75 if err != nil && err != cfd.ErrCancelled { 76 return "", err 77 } 78 return result.(string), nil 79 } 80 81 // OpenMultipleFilesDialog prompts the user to select a file 82 func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) { 83 84 defaultFolder, err := getDefaultFolder(options.DefaultDirectory) 85 if err != nil { 86 return nil, err 87 } 88 89 config := cfd.DialogConfig{ 90 Title: options.Title, 91 Role: "OpenMultipleFiles", 92 FileFilters: convertFilters(options.Filters), 93 FileName: options.DefaultFilename, 94 Folder: defaultFolder, 95 } 96 97 result, err := f.showCfdDialog( 98 func() (cfd.Dialog, error) { 99 return cfd.NewOpenMultipleFilesDialog(config) 100 }, true) 101 102 if err != nil && err != cfd.ErrCancelled { 103 return nil, err 104 } 105 return result.([]string), nil 106 } 107 108 // SaveFileDialog prompts the user to select a file 109 func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) { 110 111 defaultFolder, err := getDefaultFolder(options.DefaultDirectory) 112 if err != nil { 113 return "", err 114 } 115 116 config := cfd.DialogConfig{ 117 Title: options.Title, 118 Role: "SaveFile", 119 FileFilters: convertFilters(options.Filters), 120 FileName: options.DefaultFilename, 121 Folder: defaultFolder, 122 } 123 124 result, err := f.showCfdDialog( 125 func() (cfd.Dialog, error) { 126 return cfd.NewSaveFileDialog(config) 127 }, false) 128 129 if err != nil && err != cfd.ErrCancelled { 130 return "", err 131 } 132 return result.(string), nil 133 } 134 135 func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) { 136 return invokeSync(f.mainWindow, func() (any, error) { 137 dlg, err := newDlg() 138 if err != nil { 139 return nil, err 140 } 141 defer func() { 142 err := dlg.Release() 143 if err != nil { 144 println("ERROR: Unable to release dialog:", err.Error()) 145 } 146 }() 147 148 dlg.SetParentWindowHandle(f.getHandleForDialog()) 149 if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect { 150 return multi.ShowAndGetResults() 151 } 152 return dlg.ShowAndGetResult() 153 }) 154 } 155 156 func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 { 157 var flags uint32 158 159 switch options.Type { 160 case frontend.InfoDialog: 161 flags = windows.MB_OK | windows.MB_ICONINFORMATION 162 case frontend.ErrorDialog: 163 flags = windows.MB_ICONERROR | windows.MB_OK 164 case frontend.QuestionDialog: 165 flags = windows.MB_YESNO 166 if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" { 167 flags |= windows.MB_DEFBUTTON2 168 } 169 case frontend.WarningDialog: 170 flags = windows.MB_OK | windows.MB_ICONWARNING 171 } 172 173 return flags 174 } 175 176 // MessageDialog show a message dialog to the user 177 func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) { 178 179 title, err := syscall.UTF16PtrFromString(options.Title) 180 if err != nil { 181 return "", err 182 } 183 message, err := syscall.UTF16PtrFromString(options.Message) 184 if err != nil { 185 return "", err 186 } 187 188 flags := calculateMessageDialogFlags(options) 189 190 button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL) 191 // This maps MessageBox return values to strings 192 responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"} 193 result := "Error" 194 if int(button) < len(responses) { 195 result = responses[button] 196 } 197 return result, nil 198 } 199 200 func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter { 201 var result []cfd.FileFilter 202 for _, filter := range filters { 203 result = append(result, cfd.FileFilter(filter)) 204 } 205 return result 206 }