Multiple directory selection.
This commit is contained in:
parent
242a3aa975
commit
0c7d5c4c8b
7 changed files with 153 additions and 112 deletions
|
@ -65,8 +65,6 @@ func main() {
|
|||
strResult(zenity.SelectFile(opts...))
|
||||
case save:
|
||||
strResult(zenity.SelectFileSave(opts...))
|
||||
case directory:
|
||||
strResult(zenity.SelectDirectory(opts...))
|
||||
case multiple:
|
||||
lstResult(zenity.SelectFileMutiple(opts...))
|
||||
}
|
||||
|
@ -173,6 +171,9 @@ func loadFlags() []zenity.Option {
|
|||
|
||||
options = append(options, fileFilters.New())
|
||||
options = append(options, zenity.Filename(filename))
|
||||
if directory {
|
||||
options = append(options, zenity.Directory)
|
||||
}
|
||||
if confirmOverwrite {
|
||||
options = append(options, zenity.ConfirmOverwrite)
|
||||
}
|
||||
|
|
|
@ -10,13 +10,19 @@ import (
|
|||
|
||||
func SelectFile(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
dir, _ := splitDirAndName(opts.filename)
|
||||
out, err := osa.Run("file", osa.File{
|
||||
Operation: "chooseFile",
|
||||
|
||||
data := osa.File{
|
||||
Prompt: opts.title,
|
||||
Type: appleFilters(opts.filters),
|
||||
Location: dir,
|
||||
})
|
||||
}
|
||||
if opts.directory {
|
||||
data.Operation = "chooseFolder"
|
||||
} else {
|
||||
data.Operation = "chooseFile"
|
||||
data.Type = appleFilters(opts.filters)
|
||||
}
|
||||
data.Location, _ = splitDirAndName(opts.filename)
|
||||
|
||||
out, err := osa.Run("file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
@ -31,15 +37,21 @@ func SelectFile(options ...Option) (string, error) {
|
|||
|
||||
func SelectFileMutiple(options ...Option) ([]string, error) {
|
||||
opts := optsParse(options)
|
||||
dir, _ := splitDirAndName(opts.filename)
|
||||
out, err := osa.Run("file", osa.File{
|
||||
Operation: "chooseFile",
|
||||
|
||||
data := osa.File{
|
||||
Multiple: true,
|
||||
Prompt: opts.title,
|
||||
Separator: cmd.Separator,
|
||||
Type: appleFilters(opts.filters),
|
||||
Location: dir,
|
||||
})
|
||||
}
|
||||
if opts.directory {
|
||||
data.Operation = "chooseFolder"
|
||||
} else {
|
||||
data.Operation = "chooseFile"
|
||||
data.Type = appleFilters(opts.filters)
|
||||
}
|
||||
data.Location, _ = splitDirAndName(opts.filename)
|
||||
|
||||
out, err := osa.Run("file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -57,33 +69,15 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
|
|||
|
||||
func SelectFileSave(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
dir, name := splitDirAndName(opts.filename)
|
||||
out, err := osa.Run("file", osa.File{
|
||||
|
||||
data := osa.File{
|
||||
Operation: "chooseFileName",
|
||||
Prompt: opts.title,
|
||||
Location: dir,
|
||||
Name: name,
|
||||
})
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return "", nil
|
||||
Type: appleFilters(opts.filters),
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(out) > 0 {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
data.Location, data.Name = splitDirAndName(opts.filename)
|
||||
|
||||
func SelectDirectory(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
dir, _ := splitDirAndName(opts.filename)
|
||||
out, err := osa.Run("file", osa.File{
|
||||
Operation: "chooseFolder",
|
||||
Prompt: opts.title,
|
||||
Location: dir,
|
||||
})
|
||||
out, err := osa.Run("file", data)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
|
12
file_test.go
12
file_test.go
|
@ -47,7 +47,17 @@ func TestSelectFileSave(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectDirectory(t *testing.T) {
|
||||
res, err := SelectDirectory(Filename(defaultPath))
|
||||
res, err := SelectFile(Directory, Filename(defaultPath))
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%#v", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectDirectoryMultiple(t *testing.T) {
|
||||
res, err := SelectFileMutiple(Directory, Filename(defaultPath))
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
30
file_unix.go
30
file_unix.go
|
@ -14,6 +14,9 @@ func SelectFile(options ...Option) (string, error) {
|
|||
opts := optsParse(options)
|
||||
|
||||
args := []string{"--file-selection"}
|
||||
if opts.directory {
|
||||
args = append(args, "--directory")
|
||||
}
|
||||
if opts.title != "" {
|
||||
args = append(args, "--title", opts.title)
|
||||
}
|
||||
|
@ -39,6 +42,9 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
|
|||
opts := optsParse(options)
|
||||
|
||||
args := []string{"--file-selection", "--multiple", "--separator", cmd.Separator}
|
||||
if opts.directory {
|
||||
args = append(args, "--directory")
|
||||
}
|
||||
if opts.title != "" {
|
||||
args = append(args, "--title", opts.title)
|
||||
}
|
||||
|
@ -88,30 +94,6 @@ func SelectFileSave(options ...Option) (string, error) {
|
|||
return string(out), nil
|
||||
}
|
||||
|
||||
func SelectDirectory(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
|
||||
args := []string{"--file-selection", "--directory"}
|
||||
if opts.title != "" {
|
||||
args = append(args, "--title", opts.title)
|
||||
}
|
||||
if opts.filename != "" {
|
||||
args = append(args, "--filename", opts.filename)
|
||||
}
|
||||
|
||||
out, err := zen.Run(args)
|
||||
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() != 255 {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(out) > 0 {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func zenityFilters(filters []FileFilter) []string {
|
||||
var res []string
|
||||
for _, f := range filters {
|
||||
|
|
|
@ -20,11 +20,16 @@ var (
|
|||
)
|
||||
|
||||
func SelectFile(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
if opts.directory {
|
||||
res, _, err := pickFolders(opts, false)
|
||||
return res, err
|
||||
}
|
||||
|
||||
var args _OPENFILENAME
|
||||
args.StructSize = uint32(unsafe.Sizeof(args))
|
||||
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
|
||||
|
||||
opts := optsParse(options)
|
||||
if opts.title != "" {
|
||||
args.Title = syscall.StringToUTF16Ptr(opts.title)
|
||||
}
|
||||
|
@ -48,11 +53,16 @@ func SelectFile(options ...Option) (string, error) {
|
|||
}
|
||||
|
||||
func SelectFileMutiple(options ...Option) ([]string, error) {
|
||||
opts := optsParse(options)
|
||||
if opts.directory {
|
||||
_, res, err := pickFolders(opts, true)
|
||||
return res, err
|
||||
}
|
||||
|
||||
var args _OPENFILENAME
|
||||
args.StructSize = uint32(unsafe.Sizeof(args))
|
||||
args.Flags = 0x80208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER
|
||||
|
||||
opts := optsParse(options)
|
||||
if opts.title != "" {
|
||||
args.Title = syscall.StringToUTF16Ptr(opts.title)
|
||||
}
|
||||
|
@ -101,11 +111,12 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
|
|||
}
|
||||
|
||||
func SelectFileSave(options ...Option) (string, error) {
|
||||
opts := optsParse(options)
|
||||
|
||||
var args _OPENFILENAME
|
||||
args.StructSize = uint32(unsafe.Sizeof(args))
|
||||
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
|
||||
|
||||
opts := optsParse(options)
|
||||
if opts.title != "" {
|
||||
args.Title = syscall.StringToUTF16Ptr(opts.title)
|
||||
}
|
||||
|
@ -131,20 +142,18 @@ func SelectFileSave(options ...Option) (string, error) {
|
|||
return syscall.UTF16ToString(res[:]), nil
|
||||
}
|
||||
|
||||
func SelectDirectory(options ...Option) (string, error) {
|
||||
func pickFolders(opts options, multi bool) (str string, lst []string, err error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
hr, _, _ := coInitializeEx.Call(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE
|
||||
if hr != 0x80010106 { // RPC_E_CHANGED_MODE
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
defer coUninitialize.Call()
|
||||
}
|
||||
|
||||
opts := optsParse(options)
|
||||
|
||||
var dialog *_IFileOpenDialog
|
||||
hr, _, _ = coCreateInstance.Call(
|
||||
_CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL
|
||||
|
@ -157,11 +166,14 @@ func SelectDirectory(options ...Option) (string, error) {
|
|||
var flgs int
|
||||
hr, _, _ = dialog.Call(dialog.vtbl.GetOptions, uintptr(unsafe.Pointer(&flgs)))
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
if multi {
|
||||
flgs |= 0x200 // FOS_ALLOWMULTISELECT
|
||||
}
|
||||
hr, _, _ = dialog.Call(dialog.vtbl.SetOptions, uintptr(flgs|0x68)) // FOS_NOCHANGEDIR|FOS_PICKFOLDERS|FOS_FORCEFILESYSTEM
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
|
||||
if opts.title != "" {
|
||||
|
@ -185,16 +197,17 @@ func SelectDirectory(options ...Option) (string, error) {
|
|||
|
||||
hr, _, _ = dialog.Call(dialog.vtbl.Show, 0)
|
||||
if hr == 0x800704c7 { // ERROR_CANCELLED
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
|
||||
shellItemPath := func(obj *_COMObject, trap uintptr, a ...uintptr) error {
|
||||
var item *_IShellItem
|
||||
hr, _, _ = dialog.Call(dialog.vtbl.GetResult, uintptr(unsafe.Pointer(&item)))
|
||||
hr, _, _ := obj.Call(trap, append(a, uintptr(unsafe.Pointer(&item)))...)
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return syscall.Errno(hr)
|
||||
}
|
||||
defer item.Call(item.vtbl.Release)
|
||||
|
||||
|
@ -203,15 +216,42 @@ func SelectDirectory(options ...Option) (string, error) {
|
|||
0x80058000, // SIGDN_FILESYSPATH
|
||||
uintptr(unsafe.Pointer(&ptr)))
|
||||
if int32(hr) < 0 {
|
||||
return "", syscall.Errno(hr)
|
||||
return syscall.Errno(hr)
|
||||
}
|
||||
defer coTaskMemFree.Call(ptr)
|
||||
|
||||
res := reflect.SliceHeader{Data: ptr, Len: 32768, Cap: 32768}
|
||||
return syscall.UTF16ToString(*(*[]uint16)(unsafe.Pointer(&res))), nil
|
||||
str = syscall.UTF16ToString(*(*[]uint16)(unsafe.Pointer(&res)))
|
||||
lst = append(lst, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
if multi {
|
||||
var items *_IShellItemArray
|
||||
hr, _, _ = dialog.Call(dialog.vtbl.GetResults, uintptr(unsafe.Pointer(&items)))
|
||||
if int32(hr) < 0 {
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
defer items.Call(items.vtbl.Release)
|
||||
|
||||
var count uint32
|
||||
hr, _, _ = items.Call(items.vtbl.GetCount, uintptr(unsafe.Pointer(&count)))
|
||||
if int32(hr) < 0 {
|
||||
return "", nil, syscall.Errno(hr)
|
||||
}
|
||||
for i := uintptr(0); i < uintptr(count); i++ {
|
||||
err = shellItemPath(&items._COMObject, items.vtbl.GetItemAt, i)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = shellItemPath(&dialog._COMObject, dialog.vtbl.GetResult)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func browseForFolder(title string) (string, error) {
|
||||
func browseForFolder(title string) (string, []string, error) {
|
||||
var args _BROWSEINFO
|
||||
args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
|
||||
|
||||
|
@ -221,14 +261,15 @@ func browseForFolder(title string) (string, error) {
|
|||
|
||||
ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args)))
|
||||
if ptr == 0 {
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
defer coTaskMemFree.Call(ptr)
|
||||
|
||||
res := [32768]uint16{}
|
||||
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
|
||||
|
||||
return syscall.UTF16ToString(res[:]), nil
|
||||
str := syscall.UTF16ToString(res[:])
|
||||
return str, []string{str}, nil
|
||||
}
|
||||
|
||||
func initDirAndName(filename string, name []uint16) (dir *uint16) {
|
||||
|
@ -344,6 +385,11 @@ type _IShellItem struct {
|
|||
vtbl *_IShellItemVtbl
|
||||
}
|
||||
|
||||
type _IShellItemArray struct {
|
||||
_COMObject
|
||||
vtbl *_IShellItemArrayVtbl
|
||||
}
|
||||
|
||||
type _IFileOpenDialogVtbl struct {
|
||||
_IFileDialogVtbl
|
||||
GetResults uintptr
|
||||
|
@ -391,6 +437,17 @@ type _IShellItemVtbl struct {
|
|||
Compare uintptr
|
||||
}
|
||||
|
||||
type _IShellItemArrayVtbl struct {
|
||||
_IUnknownVtbl
|
||||
BindToHandler uintptr
|
||||
GetPropertyStore uintptr
|
||||
GetPropertyDescriptionList uintptr
|
||||
GetAttributes uintptr
|
||||
GetCount uintptr
|
||||
GetItemAt uintptr
|
||||
EnumItems uintptr
|
||||
}
|
||||
|
||||
type _IUnknownVtbl struct {
|
||||
QueryInterface uintptr
|
||||
AddRef uintptr
|
||||
|
|
|
@ -24,22 +24,13 @@ func Warning(text string, options ...Option) (bool, error) {
|
|||
|
||||
func message(typ int, text string, options []Option) (bool, error) {
|
||||
opts := optsParse(options)
|
||||
|
||||
dialog := opts.icon != 0 || typ == 2
|
||||
var op string
|
||||
if dialog {
|
||||
op = "displayDialog"
|
||||
} else {
|
||||
op = "displayAlert"
|
||||
}
|
||||
|
||||
data := osa.Msg{
|
||||
Operation: op,
|
||||
Text: text,
|
||||
Title: opts.title,
|
||||
}
|
||||
data := osa.Msg{Text: text}
|
||||
dialog := typ == 2 || opts.icon != 0
|
||||
|
||||
if dialog {
|
||||
data.Operation = "displayDialog"
|
||||
data.Title = opts.title
|
||||
|
||||
switch opts.icon {
|
||||
case ErrorIcon:
|
||||
data.Icon = "stop"
|
||||
|
@ -49,6 +40,12 @@ func message(typ int, text string, options []Option) (bool, error) {
|
|||
data.Icon = "caution"
|
||||
}
|
||||
} else {
|
||||
data.Operation = "displayAlert"
|
||||
if opts.title != "" {
|
||||
data.Message = text
|
||||
data.Text = opts.title
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case 0:
|
||||
data.As = "critical"
|
||||
|
@ -57,11 +54,6 @@ func message(typ int, text string, options []Option) (bool, error) {
|
|||
case 3:
|
||||
data.As = "warning"
|
||||
}
|
||||
|
||||
if opts.title != "" {
|
||||
data.Text = opts.title
|
||||
data.Message = text
|
||||
}
|
||||
}
|
||||
|
||||
if typ != 2 {
|
||||
|
|
|
@ -18,6 +18,7 @@ type options struct {
|
|||
|
||||
// File selection options
|
||||
filename string
|
||||
directory bool
|
||||
overwrite bool
|
||||
filters []FileFilter
|
||||
|
||||
|
@ -56,6 +57,10 @@ func Filename(filename string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func Directory(o *options) {
|
||||
o.directory = true
|
||||
}
|
||||
|
||||
func ConfirmOverwrite(o *options) {
|
||||
o.overwrite = true
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue