github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/darwin/dialog.go (about) 1 //go:build darwin 2 // +build darwin 3 4 package darwin 5 6 /* 7 #cgo CFLAGS: -x objective-c 8 #cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit 9 #import <Foundation/Foundation.h> 10 #import "Application.h" 11 #import "WailsContext.h" 12 */ 13 import "C" 14 15 import ( 16 "encoding/json" 17 "fmt" 18 "strings" 19 "sync" 20 "unsafe" 21 22 "github.com/leaanthony/slicer" 23 "github.com/secoba/wails/v2/internal/frontend" 24 ) 25 26 // Obj-C dialog methods send the response to this channel 27 var ( 28 messageDialogResponse = make(chan int) 29 openFileDialogResponse = make(chan string) 30 saveFileDialogResponse = make(chan string) 31 dialogLock sync.Mutex 32 ) 33 34 // OpenDirectoryDialog prompts the user to select a directory 35 func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) { 36 results, err := f.openDialog(&options, false, false, true) 37 if err != nil { 38 return "", err 39 } 40 var selected string 41 if len(results) > 0 { 42 selected = results[0] 43 } 44 return selected, nil 45 } 46 47 func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool, allowfiles bool, allowdirectories bool) ([]string, error) { 48 dialogLock.Lock() 49 defer dialogLock.Unlock() 50 51 c := NewCalloc() 52 defer c.Free() 53 title := c.String(options.Title) 54 defaultFilename := c.String(options.DefaultFilename) 55 defaultDirectory := c.String(options.DefaultDirectory) 56 allowDirectories := bool2Cint(allowdirectories) 57 allowFiles := bool2Cint(allowfiles) 58 canCreateDirectories := bool2Cint(options.CanCreateDirectories) 59 treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories) 60 resolveAliases := bool2Cint(options.ResolvesAliases) 61 showHiddenFiles := bool2Cint(options.ShowHiddenFiles) 62 allowMultipleFileSelection := bool2Cint(multiple) 63 64 var filterStrings slicer.StringSlicer 65 if options.Filters != nil { 66 for _, filter := range options.Filters { 67 thesePatterns := strings.Split(filter.Pattern, ";") 68 for _, pattern := range thesePatterns { 69 pattern = strings.TrimSpace(pattern) 70 if pattern != "" { 71 filterStrings.Add(pattern) 72 } 73 } 74 } 75 filterStrings.Deduplicate() 76 } 77 filters := filterStrings.Join(";") 78 C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters)) 79 80 result := <-openFileDialogResponse 81 82 var parsedResults []string 83 err := json.Unmarshal([]byte(result), &parsedResults) 84 85 return parsedResults, err 86 } 87 88 // OpenFileDialog prompts the user to select a file 89 func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) { 90 results, err := f.openDialog(&options, false, true, false) 91 if err != nil { 92 return "", err 93 } 94 var selected string 95 if len(results) > 0 { 96 selected = results[0] 97 } 98 return selected, nil 99 } 100 101 // OpenMultipleFilesDialog prompts the user to select a file 102 func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) { 103 return f.openDialog(&options, true, true, false) 104 } 105 106 // SaveFileDialog prompts the user to select a file 107 func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) { 108 dialogLock.Lock() 109 defer dialogLock.Unlock() 110 111 c := NewCalloc() 112 defer c.Free() 113 title := c.String(options.Title) 114 defaultFilename := c.String(options.DefaultFilename) 115 defaultDirectory := c.String(options.DefaultDirectory) 116 canCreateDirectories := bool2Cint(options.CanCreateDirectories) 117 treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories) 118 showHiddenFiles := bool2Cint(options.ShowHiddenFiles) 119 120 var filterStrings slicer.StringSlicer 121 if options.Filters != nil { 122 for _, filter := range options.Filters { 123 thesePatterns := strings.Split(filter.Pattern, ";") 124 for _, pattern := range thesePatterns { 125 pattern = strings.TrimSpace(pattern) 126 if pattern != "" { 127 filterStrings.Add(pattern) 128 } 129 } 130 } 131 filterStrings.Deduplicate() 132 } 133 filters := filterStrings.Join(";") 134 C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters)) 135 136 result := <-saveFileDialogResponse 137 138 return result, nil 139 } 140 141 // MessageDialog show a message dialog to the user 142 func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) { 143 dialogLock.Lock() 144 defer dialogLock.Unlock() 145 146 c := NewCalloc() 147 defer c.Free() 148 dialogType := c.String(string(options.Type)) 149 title := c.String(options.Title) 150 message := c.String(options.Message) 151 defaultButton := c.String(options.DefaultButton) 152 cancelButton := c.String(options.CancelButton) 153 const MaxButtons = 4 154 var buttons [MaxButtons]*C.char 155 for index, buttonText := range options.Buttons { 156 if index == MaxButtons { 157 return "", fmt.Errorf("max %d buttons supported (%d given)", MaxButtons, len(options.Buttons)) 158 } 159 buttons[index] = c.String(buttonText) 160 } 161 162 var iconData unsafe.Pointer 163 var iconDataLength C.int 164 if options.Icon != nil { 165 iconData = unsafe.Pointer(&options.Icon[0]) 166 iconDataLength = C.int(len(options.Icon)) 167 } 168 169 C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength) 170 171 result := <-messageDialogResponse 172 173 selectedC := buttons[result] 174 var selected string 175 if selectedC != nil { 176 selected = options.Buttons[result] 177 } 178 return selected, nil 179 } 180 181 //export processMessageDialogResponse 182 func processMessageDialogResponse(selection int) { 183 messageDialogResponse <- selection 184 } 185 186 //export processOpenFileDialogResponse 187 func processOpenFileDialogResponse(cselection *C.char) { 188 selection := C.GoString(cselection) 189 openFileDialogResponse <- selection 190 } 191 192 //export processSaveFileDialogResponse 193 func processSaveFileDialogResponse(cselection *C.char) { 194 selection := C.GoString(cselection) 195 saveFileDialogResponse <- selection 196 }