Multiple directory selection.

This commit is contained in:
Nuno Cruces 2020-01-10 01:46:53 +00:00
parent 242a3aa975
commit 0c7d5c4c8b
7 changed files with 153 additions and 112 deletions

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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
}