203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
// Copyright 2010 The Walk Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows
|
|
|
|
package walk
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
import (
|
|
"github.com/lxn/win"
|
|
)
|
|
|
|
type FileDialog struct {
|
|
Title string
|
|
FilePath string
|
|
FilePaths []string
|
|
InitialDirPath string
|
|
Filter string
|
|
FilterIndex int
|
|
Flags uint32
|
|
ShowReadOnlyCB bool
|
|
}
|
|
|
|
func (dlg *FileDialog) show(owner Form, fun func(ofn *win.OPENFILENAME) bool, flags uint32) (accepted bool, err error) {
|
|
ofn := new(win.OPENFILENAME)
|
|
|
|
ofn.LStructSize = uint32(unsafe.Sizeof(*ofn))
|
|
if owner != nil {
|
|
ofn.HwndOwner = owner.Handle()
|
|
}
|
|
|
|
filter := make([]uint16, len(dlg.Filter)+2)
|
|
copy(filter, syscall.StringToUTF16(dlg.Filter))
|
|
// Replace '|' with the expected '\0'.
|
|
for i, c := range filter {
|
|
if byte(c) == '|' {
|
|
filter[i] = uint16(0)
|
|
}
|
|
}
|
|
ofn.LpstrFilter = &filter[0]
|
|
ofn.NFilterIndex = uint32(dlg.FilterIndex)
|
|
|
|
ofn.LpstrInitialDir = syscall.StringToUTF16Ptr(dlg.InitialDirPath)
|
|
ofn.LpstrTitle = syscall.StringToUTF16Ptr(dlg.Title)
|
|
ofn.Flags = win.OFN_FILEMUSTEXIST | flags | dlg.Flags
|
|
|
|
if !dlg.ShowReadOnlyCB {
|
|
ofn.Flags |= win.OFN_HIDEREADONLY
|
|
}
|
|
|
|
var fileBuf []uint16
|
|
if flags&win.OFN_ALLOWMULTISELECT > 0 {
|
|
fileBuf = make([]uint16, 65536)
|
|
} else {
|
|
fileBuf = make([]uint16, 1024)
|
|
copy(fileBuf, syscall.StringToUTF16(dlg.FilePath))
|
|
}
|
|
ofn.LpstrFile = &fileBuf[0]
|
|
ofn.NMaxFile = uint32(len(fileBuf))
|
|
|
|
if !fun(ofn) {
|
|
errno := win.CommDlgExtendedError()
|
|
if errno != 0 {
|
|
err = newError(fmt.Sprintf("Error %d", errno))
|
|
}
|
|
return
|
|
}
|
|
|
|
dlg.FilterIndex = int(ofn.NFilterIndex)
|
|
|
|
if flags&win.OFN_ALLOWMULTISELECT > 0 {
|
|
split := func() [][]uint16 {
|
|
var parts [][]uint16
|
|
|
|
from := 0
|
|
for i, c := range fileBuf {
|
|
if c == 0 {
|
|
if i == from {
|
|
return parts
|
|
}
|
|
|
|
parts = append(parts, fileBuf[from:i])
|
|
from = i + 1
|
|
}
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
parts := split()
|
|
|
|
if len(parts) == 1 {
|
|
dlg.FilePaths = []string{syscall.UTF16ToString(parts[0])}
|
|
} else {
|
|
dirPath := syscall.UTF16ToString(parts[0])
|
|
dlg.FilePaths = make([]string, len(parts)-1)
|
|
|
|
for i, fp := range parts[1:] {
|
|
dlg.FilePaths[i] = filepath.Join(dirPath, syscall.UTF16ToString(fp))
|
|
}
|
|
}
|
|
} else {
|
|
dlg.FilePath = syscall.UTF16ToString(fileBuf)
|
|
}
|
|
|
|
accepted = true
|
|
|
|
return
|
|
}
|
|
|
|
func (dlg *FileDialog) ShowOpen(owner Form) (accepted bool, err error) {
|
|
return dlg.show(owner, win.GetOpenFileName, 0)
|
|
}
|
|
|
|
func (dlg *FileDialog) ShowOpenMultiple(owner Form) (accepted bool, err error) {
|
|
return dlg.show(owner, win.GetOpenFileName, win.OFN_ALLOWMULTISELECT|win.OFN_EXPLORER)
|
|
}
|
|
|
|
func (dlg *FileDialog) ShowSave(owner Form) (accepted bool, err error) {
|
|
return dlg.show(owner, win.GetSaveFileName, 0)
|
|
}
|
|
|
|
func pathFromPIDL(pidl uintptr) (string, error) {
|
|
var path [win.MAX_PATH]uint16
|
|
if !win.SHGetPathFromIDList(pidl, &path[0]) {
|
|
return "", newError("SHGetPathFromIDList failed")
|
|
}
|
|
|
|
return syscall.UTF16ToString(path[:]), nil
|
|
}
|
|
|
|
// We use this callback to disable the OK button in case of "invalid" selections.
|
|
func browseFolderCallback(hwnd win.HWND, msg uint32, lp, wp uintptr) uintptr {
|
|
const BFFM_SELCHANGED = 2
|
|
if msg == BFFM_SELCHANGED {
|
|
_, err := pathFromPIDL(lp)
|
|
var enabled uintptr
|
|
if err == nil {
|
|
enabled = 1
|
|
}
|
|
|
|
const BFFM_ENABLEOK = win.WM_USER + 101
|
|
|
|
win.SendMessage(hwnd, BFFM_ENABLEOK, 0, enabled)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
var browseFolderCallbackPtr uintptr
|
|
|
|
func init() {
|
|
AppendToWalkInit(func() {
|
|
browseFolderCallbackPtr = syscall.NewCallback(browseFolderCallback)
|
|
})
|
|
}
|
|
|
|
func (dlg *FileDialog) ShowBrowseFolder(owner Form) (accepted bool, err error) {
|
|
// Calling OleInitialize (or similar) is required for BIF_NEWDIALOGSTYLE.
|
|
if hr := win.OleInitialize(); hr != win.S_OK && hr != win.S_FALSE {
|
|
return false, newError(fmt.Sprint("OleInitialize Error: ", hr))
|
|
}
|
|
defer win.OleUninitialize()
|
|
|
|
var ownerHwnd win.HWND
|
|
if owner != nil {
|
|
ownerHwnd = owner.Handle()
|
|
}
|
|
|
|
// We need to put the initial path into a buffer of at least MAX_LENGTH
|
|
// length, or we may get random crashes.
|
|
var buf [win.MAX_PATH]uint16
|
|
copy(buf[:], syscall.StringToUTF16(dlg.InitialDirPath))
|
|
|
|
const BIF_NEWDIALOGSTYLE = 0x00000040
|
|
|
|
bi := win.BROWSEINFO{
|
|
HwndOwner: ownerHwnd,
|
|
LpszTitle: syscall.StringToUTF16Ptr(dlg.Title),
|
|
UlFlags: BIF_NEWDIALOGSTYLE,
|
|
Lpfn: browseFolderCallbackPtr,
|
|
}
|
|
|
|
win.SHParseDisplayName(&buf[0], 0, &bi.PidlRoot, 0, nil)
|
|
|
|
pidl := win.SHBrowseForFolder(&bi)
|
|
if pidl == 0 {
|
|
return false, nil
|
|
}
|
|
defer win.CoTaskMemFree(pidl)
|
|
|
|
dlg.FilePath, err = pathFromPIDL(pidl)
|
|
accepted = dlg.FilePath != ""
|
|
return
|
|
}
|