package dialog import ( "fmt" "reflect" "syscall" "unicode/utf16" "unsafe" "github.com/TheTitanrain/w32" ) type WinDlgError int func (e WinDlgError) Error() string { return fmt.Sprintf("CommDlgExtendedError: %#x", e) } func err() error { e := w32.CommDlgExtendedError() if e == 0 { return ErrCancelled } return WinDlgError(e) } func (b *MsgBuilder) yesNo() bool { r := w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Confirm?"), w32.MB_YESNO) return r == w32.IDYES } func (b *MsgBuilder) info() { w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Information"), w32.MB_OK|w32.MB_ICONINFORMATION) } func (b *MsgBuilder) error() { w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Error"), w32.MB_OK|w32.MB_ICONERROR) } type filedlg struct { buf []uint16 filters []uint16 opf *w32.OPENFILENAME } func (d filedlg) Filename() string { i := 0 for i < len(d.buf) && d.buf[i] != 0 { i++ } return string(utf16.Decode(d.buf[:i])) } func (b *FileBuilder) load() (string, error) { d := openfile(w32.OFN_FILEMUSTEXIST|w32.OFN_NOCHANGEDIR, b) if w32.GetOpenFileName(d.opf) { return d.Filename(), nil } return "", err() } func (b *FileBuilder) save() (string, error) { d := openfile(w32.OFN_OVERWRITEPROMPT|w32.OFN_NOCHANGEDIR, b) if w32.GetSaveFileName(d.opf) { return d.Filename(), nil } return "", err() } /* syscall.UTF16PtrFromString not sufficient because we need to encode embedded NUL bytes */ func utf16ptr(utf16 []uint16) *uint16 { if utf16[len(utf16)-1] != 0 { panic("refusing to make ptr to non-NUL terminated utf16 slice") } h := (*reflect.SliceHeader)(unsafe.Pointer(&utf16)) return (*uint16)(unsafe.Pointer(h.Data)) } func utf16slice(ptr *uint16) []uint16 { hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ptr)), Len: 1, Cap: 1} slice := *((*[]uint16)(unsafe.Pointer(&hdr))) i := 0 for slice[len(slice)-1] != 0 { i++ } hdr.Len = i slice = *((*[]uint16)(unsafe.Pointer(&hdr))) return slice } func openfile(flags uint32, b *FileBuilder) (d filedlg) { d.buf = make([]uint16, w32.MAX_PATH) if b.StartFile != "" { initialName, _ := syscall.UTF16FromString(b.StartFile) for i := 0; i < len(initialName) && i < w32.MAX_PATH; i++ { d.buf[i] = initialName[i] } } d.opf = &w32.OPENFILENAME{ File: utf16ptr(d.buf), MaxFile: uint32(len(d.buf)), Flags: flags, } d.opf.StructSize = uint32(unsafe.Sizeof(*d.opf)) if b.StartDir != "" { d.opf.InitialDir, _ = syscall.UTF16PtrFromString(b.StartDir) } if b.Dlg.Title != "" { d.opf.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title) } for _, filt := range b.Filters { /* build utf16 string of form "Music File\0*.mp3;*.ogg;*.wav;\0" */ d.filters = append(d.filters, utf16.Encode([]rune(filt.Desc))...) d.filters = append(d.filters, 0) for _, ext := range filt.Extensions { s := fmt.Sprintf("*.%s;", ext) d.filters = append(d.filters, utf16.Encode([]rune(s))...) } d.filters = append(d.filters, 0) } if d.filters != nil { d.filters = append(d.filters, 0, 0) // two extra NUL chars to terminate the list d.opf.Filter = utf16ptr(d.filters) } return d } type dirdlg struct { bi *w32.BROWSEINFO } const ( bffm_INITIALIZED = 1 bffm_SELCHANGED = 2 bffm_VALIDATEFAILEDA = 3 bffm_VALIDATEFAILEDW = 4 bffm_SETSTATUSTEXTA = (w32.WM_USER + 100) bffm_SETSTATUSTEXTW = (w32.WM_USER + 104) bffm_ENABLEOK = (w32.WM_USER + 101) bffm_SETSELECTIONA = (w32.WM_USER + 102) bffm_SETSELECTIONW = (w32.WM_USER + 103) bffm_SETOKTEXT = (w32.WM_USER + 105) bffm_SETEXPANDED = (w32.WM_USER + 106) bffm_SETSTATUSTEXT = bffm_SETSTATUSTEXTW bffm_SETSELECTION = bffm_SETSELECTIONW bffm_VALIDATEFAILED = bffm_VALIDATEFAILEDW ) func callbackDefaultDir(hwnd w32.HWND, msg uint, lParam, lpData uintptr) int { if msg == bffm_INITIALIZED { _ = w32.SendMessage(hwnd, bffm_SETSELECTION, w32.TRUE, lpData) } return 0 } func selectdir(b *DirectoryBuilder) (d dirdlg) { d.bi = &w32.BROWSEINFO{Flags: w32.BIF_RETURNONLYFSDIRS | w32.BIF_NEWDIALOGSTYLE} if b.Dlg.Title != "" { d.bi.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title) } if b.StartDir != "" { s16, _ := syscall.UTF16PtrFromString(b.StartDir) d.bi.LParam = uintptr(unsafe.Pointer(s16)) d.bi.CallbackFunc = syscall.NewCallback(callbackDefaultDir) } return d } func (b *DirectoryBuilder) browse() (string, error) { d := selectdir(b) res := w32.SHBrowseForFolder(d.bi) if res == 0 { return "", ErrCancelled } return w32.SHGetPathFromIDList(res), nil }