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...)) strResult(zenity.SelectFile(opts...))
case save: case save:
strResult(zenity.SelectFileSave(opts...)) strResult(zenity.SelectFileSave(opts...))
case directory:
strResult(zenity.SelectDirectory(opts...))
case multiple: case multiple:
lstResult(zenity.SelectFileMutiple(opts...)) lstResult(zenity.SelectFileMutiple(opts...))
} }
@ -173,6 +171,9 @@ func loadFlags() []zenity.Option {
options = append(options, fileFilters.New()) options = append(options, fileFilters.New())
options = append(options, zenity.Filename(filename)) options = append(options, zenity.Filename(filename))
if directory {
options = append(options, zenity.Directory)
}
if confirmOverwrite { if confirmOverwrite {
options = append(options, zenity.ConfirmOverwrite) options = append(options, zenity.ConfirmOverwrite)
} }

View file

@ -10,13 +10,19 @@ import (
func SelectFile(options ...Option) (string, error) { func SelectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
dir, _ := splitDirAndName(opts.filename)
out, err := osa.Run("file", osa.File{ data := osa.File{
Operation: "chooseFile", Prompt: opts.title,
Prompt: opts.title, }
Type: appleFilters(opts.filters), if opts.directory {
Location: dir, 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return "", nil return "", nil
} }
@ -31,15 +37,21 @@ func SelectFile(options ...Option) (string, error) {
func SelectFileMutiple(options ...Option) ([]string, error) { func SelectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
dir, _ := splitDirAndName(opts.filename)
out, err := osa.Run("file", osa.File{ data := osa.File{
Operation: "chooseFile",
Multiple: true, Multiple: true,
Prompt: opts.title, Prompt: opts.title,
Separator: cmd.Separator, 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 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return nil, nil return nil, nil
} }
@ -57,33 +69,15 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
func SelectFileSave(options ...Option) (string, error) { func SelectFileSave(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
dir, name := splitDirAndName(opts.filename)
out, err := osa.Run("file", osa.File{ data := osa.File{
Operation: "chooseFileName", Operation: "chooseFileName",
Prompt: opts.title, Prompt: opts.title,
Location: dir, Type: appleFilters(opts.filters),
Name: name,
})
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return "", nil
} }
if err != nil { data.Location, data.Name = splitDirAndName(opts.filename)
return "", err
}
if len(out) > 0 {
out = out[:len(out)-1]
}
return string(out), nil
}
func SelectDirectory(options ...Option) (string, error) { out, err := osa.Run("file", data)
opts := optsParse(options)
dir, _ := splitDirAndName(opts.filename)
out, err := osa.Run("file", osa.File{
Operation: "chooseFolder",
Prompt: opts.title,
Location: dir,
})
if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 { if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 1 {
return "", nil return "", nil
} }

View file

@ -47,7 +47,17 @@ func TestSelectFileSave(t *testing.T) {
} }
func TestSelectDirectory(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 { if err != nil {
t.Error(err) t.Error(err)

View file

@ -14,6 +14,9 @@ func SelectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--file-selection"} args := []string{"--file-selection"}
if opts.directory {
args = append(args, "--directory")
}
if opts.title != "" { if opts.title != "" {
args = append(args, "--title", opts.title) args = append(args, "--title", opts.title)
} }
@ -39,6 +42,9 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--file-selection", "--multiple", "--separator", cmd.Separator} args := []string{"--file-selection", "--multiple", "--separator", cmd.Separator}
if opts.directory {
args = append(args, "--directory")
}
if opts.title != "" { if opts.title != "" {
args = append(args, "--title", opts.title) args = append(args, "--title", opts.title)
} }
@ -88,30 +94,6 @@ func SelectFileSave(options ...Option) (string, error) {
return string(out), nil 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 { func zenityFilters(filters []FileFilter) []string {
var res []string var res []string
for _, f := range filters { for _, f := range filters {

View file

@ -20,11 +20,16 @@ var (
) )
func SelectFile(options ...Option) (string, error) { func SelectFile(options ...Option) (string, error) {
opts := optsParse(options)
if opts.directory {
res, _, err := pickFolders(opts, false)
return res, err
}
var args _OPENFILENAME var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
opts := optsParse(options)
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
} }
@ -48,11 +53,16 @@ func SelectFile(options ...Option) (string, error) {
} }
func SelectFileMutiple(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 var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x80208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER args.Flags = 0x80208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER
opts := optsParse(options)
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
} }
@ -101,11 +111,12 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
} }
func SelectFileSave(options ...Option) (string, error) { func SelectFileSave(options ...Option) (string, error) {
opts := optsParse(options)
var args _OPENFILENAME var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
opts := optsParse(options)
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
} }
@ -131,20 +142,18 @@ func SelectFileSave(options ...Option) (string, error) {
return syscall.UTF16ToString(res[:]), nil 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() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
hr, _, _ := coInitializeEx.Call(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE hr, _, _ := coInitializeEx.Call(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE
if hr != 0x80010106 { // RPC_E_CHANGED_MODE if hr != 0x80010106 { // RPC_E_CHANGED_MODE
if int32(hr) < 0 { if int32(hr) < 0 {
return "", syscall.Errno(hr) return "", nil, syscall.Errno(hr)
} }
defer coUninitialize.Call() defer coUninitialize.Call()
} }
opts := optsParse(options)
var dialog *_IFileOpenDialog var dialog *_IFileOpenDialog
hr, _, _ = coCreateInstance.Call( hr, _, _ = coCreateInstance.Call(
_CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL _CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL
@ -157,11 +166,14 @@ func SelectDirectory(options ...Option) (string, error) {
var flgs int var flgs int
hr, _, _ = dialog.Call(dialog.vtbl.GetOptions, uintptr(unsafe.Pointer(&flgs))) hr, _, _ = dialog.Call(dialog.vtbl.GetOptions, uintptr(unsafe.Pointer(&flgs)))
if int32(hr) < 0 { 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 hr, _, _ = dialog.Call(dialog.vtbl.SetOptions, uintptr(flgs|0x68)) // FOS_NOCHANGEDIR|FOS_PICKFOLDERS|FOS_FORCEFILESYSTEM
if int32(hr) < 0 { if int32(hr) < 0 {
return "", syscall.Errno(hr) return "", nil, syscall.Errno(hr)
} }
if opts.title != "" { if opts.title != "" {
@ -185,33 +197,61 @@ func SelectDirectory(options ...Option) (string, error) {
hr, _, _ = dialog.Call(dialog.vtbl.Show, 0) hr, _, _ = dialog.Call(dialog.vtbl.Show, 0)
if hr == 0x800704c7 { // ERROR_CANCELLED if hr == 0x800704c7 { // ERROR_CANCELLED
return "", nil return "", nil, nil
} }
if int32(hr) < 0 { if int32(hr) < 0 {
return "", syscall.Errno(hr) return "", nil, syscall.Errno(hr)
} }
var item *_IShellItem shellItemPath := func(obj *_COMObject, trap uintptr, a ...uintptr) error {
hr, _, _ = dialog.Call(dialog.vtbl.GetResult, uintptr(unsafe.Pointer(&item))) var item *_IShellItem
if int32(hr) < 0 { hr, _, _ := obj.Call(trap, append(a, uintptr(unsafe.Pointer(&item)))...)
return "", syscall.Errno(hr) if int32(hr) < 0 {
} return syscall.Errno(hr)
defer item.Call(item.vtbl.Release) }
defer item.Call(item.vtbl.Release)
var ptr uintptr var ptr uintptr
hr, _, _ = item.Call(item.vtbl.GetDisplayName, hr, _, _ = item.Call(item.vtbl.GetDisplayName,
0x80058000, // SIGDN_FILESYSPATH 0x80058000, // SIGDN_FILESYSPATH
uintptr(unsafe.Pointer(&ptr))) uintptr(unsafe.Pointer(&ptr)))
if int32(hr) < 0 { if int32(hr) < 0 {
return "", syscall.Errno(hr) return syscall.Errno(hr)
} }
defer coTaskMemFree.Call(ptr) defer coTaskMemFree.Call(ptr)
res := reflect.SliceHeader{Data: ptr, Len: 32768, Cap: 32768} 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 var args _BROWSEINFO
args.Flags = 0x1 // BIF_RETURNONLYFSDIRS args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
@ -221,14 +261,15 @@ func browseForFolder(title string) (string, error) {
ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args))) ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args)))
if ptr == 0 { if ptr == 0 {
return "", nil return "", nil, nil
} }
defer coTaskMemFree.Call(ptr) defer coTaskMemFree.Call(ptr)
res := [32768]uint16{} res := [32768]uint16{}
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0) 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) { func initDirAndName(filename string, name []uint16) (dir *uint16) {
@ -344,6 +385,11 @@ type _IShellItem struct {
vtbl *_IShellItemVtbl vtbl *_IShellItemVtbl
} }
type _IShellItemArray struct {
_COMObject
vtbl *_IShellItemArrayVtbl
}
type _IFileOpenDialogVtbl struct { type _IFileOpenDialogVtbl struct {
_IFileDialogVtbl _IFileDialogVtbl
GetResults uintptr GetResults uintptr
@ -391,6 +437,17 @@ type _IShellItemVtbl struct {
Compare uintptr Compare uintptr
} }
type _IShellItemArrayVtbl struct {
_IUnknownVtbl
BindToHandler uintptr
GetPropertyStore uintptr
GetPropertyDescriptionList uintptr
GetAttributes uintptr
GetCount uintptr
GetItemAt uintptr
EnumItems uintptr
}
type _IUnknownVtbl struct { type _IUnknownVtbl struct {
QueryInterface uintptr QueryInterface uintptr
AddRef 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) { func message(typ int, text string, options []Option) (bool, error) {
opts := optsParse(options) opts := optsParse(options)
data := osa.Msg{Text: text}
dialog := opts.icon != 0 || typ == 2 dialog := typ == 2 || opts.icon != 0
var op string
if dialog {
op = "displayDialog"
} else {
op = "displayAlert"
}
data := osa.Msg{
Operation: op,
Text: text,
Title: opts.title,
}
if dialog { if dialog {
data.Operation = "displayDialog"
data.Title = opts.title
switch opts.icon { switch opts.icon {
case ErrorIcon: case ErrorIcon:
data.Icon = "stop" data.Icon = "stop"
@ -49,6 +40,12 @@ func message(typ int, text string, options []Option) (bool, error) {
data.Icon = "caution" data.Icon = "caution"
} }
} else { } else {
data.Operation = "displayAlert"
if opts.title != "" {
data.Message = text
data.Text = opts.title
}
switch typ { switch typ {
case 0: case 0:
data.As = "critical" data.As = "critical"
@ -57,11 +54,6 @@ func message(typ int, text string, options []Option) (bool, error) {
case 3: case 3:
data.As = "warning" data.As = "warning"
} }
if opts.title != "" {
data.Text = opts.title
data.Message = text
}
} }
if typ != 2 { if typ != 2 {

View file

@ -18,6 +18,7 @@ type options struct {
// File selection options // File selection options
filename string filename string
directory bool
overwrite bool overwrite bool
filters []FileFilter filters []FileFilter
@ -56,6 +57,10 @@ func Filename(filename string) Option {
} }
} }
func Directory(o *options) {
o.directory = true
}
func ConfirmOverwrite(o *options) { func ConfirmOverwrite(o *options) {
o.overwrite = true o.overwrite = true
} }