github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/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/AlpineAIO/wails/v2/internal/frontend" 12 "github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/winc/w32" 13 "github.com/AlpineAIO/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 if len(options.Filters) > 0 { 125 config.DefaultExtension = strings.TrimPrefix(strings.Split(options.Filters[0].Pattern, ";")[0], "*") 126 } 127 128 result, err := f.showCfdDialog( 129 func() (cfd.Dialog, error) { 130 return cfd.NewSaveFileDialog(config) 131 }, false) 132 133 if err != nil && err != cfd.ErrCancelled { 134 return "", err 135 } 136 return result.(string), nil 137 } 138 139 func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) { 140 return invokeSync(f.mainWindow, func() (any, error) { 141 dlg, err := newDlg() 142 if err != nil { 143 return nil, err 144 } 145 defer func() { 146 err := dlg.Release() 147 if err != nil { 148 println("ERROR: Unable to release dialog:", err.Error()) 149 } 150 }() 151 152 dlg.SetParentWindowHandle(f.getHandleForDialog()) 153 if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect { 154 return multi.ShowAndGetResults() 155 } 156 return dlg.ShowAndGetResult() 157 }) 158 } 159 160 func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 { 161 var flags uint32 162 163 switch options.Type { 164 case frontend.InfoDialog: 165 flags = windows.MB_OK | windows.MB_ICONINFORMATION 166 case frontend.ErrorDialog: 167 flags = windows.MB_ICONERROR | windows.MB_OK 168 case frontend.QuestionDialog: 169 flags = windows.MB_YESNO 170 if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" { 171 flags |= windows.MB_DEFBUTTON2 172 } 173 case frontend.WarningDialog: 174 flags = windows.MB_OK | windows.MB_ICONWARNING 175 } 176 177 return flags 178 } 179 180 // MessageDialog show a message dialog to the user 181 func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) { 182 183 title, err := syscall.UTF16PtrFromString(options.Title) 184 if err != nil { 185 return "", err 186 } 187 message, err := syscall.UTF16PtrFromString(options.Message) 188 if err != nil { 189 return "", err 190 } 191 192 flags := calculateMessageDialogFlags(options) 193 194 button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL) 195 // This maps MessageBox return values to strings 196 responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"} 197 result := "Error" 198 if int(button) < len(responses) { 199 result = responses[button] 200 } 201 return result, nil 202 } 203 204 func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter { 205 var result []cfd.FileFilter 206 for _, filter := range filters { 207 result = append(result, cfd.FileFilter(filter)) 208 } 209 return result 210 }