File selection tweaks, options, API (windows).

This commit is contained in:
Nuno Cruces 2020-01-12 03:18:22 +00:00
parent 4ac77d1334
commit 58dae10a83
6 changed files with 123 additions and 91 deletions

View File

@ -37,6 +37,8 @@ var (
multiple bool multiple bool
directory bool directory bool
confirmOverwrite bool confirmOverwrite bool
confirmCreate bool
showHidden bool
filename string filename string
separator string separator string
fileFilters FileFilters fileFilters FileFilters
@ -104,6 +106,8 @@ func setupFlags() {
flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected") flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected")
flag.BoolVar(&directory, "directory", false, "Activate directory-only selection") flag.BoolVar(&directory, "directory", false, "Activate directory-only selection")
flag.BoolVar(&confirmOverwrite, "confirm-overwrite", false, "Confirm file selection if filename already exists") flag.BoolVar(&confirmOverwrite, "confirm-overwrite", false, "Confirm file selection if filename already exists")
flag.BoolVar(&confirmCreate, "confirm-create", false, "Confirm file selection if filename does not yet exist (Windows only)")
flag.BoolVar(&showHidden, "show-hidden", false, "Show hidden files (Windows and macOS only)")
flag.StringVar(&filename, "filename", "", "Set the filename") flag.StringVar(&filename, "filename", "", "Set the filename")
flag.StringVar(&separator, "separator", "|", "Set output separator character") flag.StringVar(&separator, "separator", "|", "Set output separator character")
flag.Var(&fileFilters, "file-filter", "Set a filename filter (NAME | PATTERN1 PATTERN2 ...)") flag.Var(&fileFilters, "file-filter", "Set a filename filter (NAME | PATTERN1 PATTERN2 ...)")
@ -158,24 +162,30 @@ func loadFlags() []zenity.Option {
options = append(options, zenity.CancelLabel(cancelLabel)) options = append(options, zenity.CancelLabel(cancelLabel))
options = append(options, zenity.ExtraButton(extraButton)) options = append(options, zenity.ExtraButton(extraButton))
if noWrap { if noWrap {
options = append(options, zenity.NoWrap) options = append(options, zenity.NoWrap())
} }
if ellipsize { if ellipsize {
options = append(options, zenity.Ellipsize) options = append(options, zenity.Ellipsize())
} }
if defaultCancel { if defaultCancel {
options = append(options, zenity.DefaultCancel) options = append(options, zenity.DefaultCancel())
} }
// File selection options // File selection options
options = append(options, fileFilters.New()) options = append(options, fileFilters.Build())
options = append(options, zenity.Filename(filename)) options = append(options, zenity.Filename(filename))
if directory { if directory {
options = append(options, zenity.Directory) options = append(options, zenity.Directory())
} }
if confirmOverwrite { if confirmOverwrite {
options = append(options, zenity.ConfirmOverwrite) options = append(options, zenity.ConfirmOverwrite())
}
if confirmCreate {
options = append(options, zenity.ConfirmCreate())
}
if showHidden {
options = append(options, zenity.ShowHidden())
} }
cmd.Separator = separator cmd.Separator = separator

View File

@ -12,7 +12,7 @@ func ExampleSelectFile() {
{"Go files", []string{"*.go"}}, {"Go files", []string{"*.go"}},
{"Web files", []string{"*.html", "*.js", "*.css"}}, {"Web files", []string{"*.html", "*.js", "*.css"}},
{"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}}, {"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}},
}.New()) }.Build())
// Output: // Output:
} }
@ -23,32 +23,32 @@ func ExampleSelectFileMutiple() {
{"Go files", []string{"*.go"}}, {"Go files", []string{"*.go"}},
{"Web files", []string{"*.html", "*.js", "*.css"}}, {"Web files", []string{"*.html", "*.js", "*.css"}},
{"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}}, {"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}},
}.New()) }.Build())
// Output: // Output:
} }
func ExampleSelectFileSave() { func ExampleSelectFileSave() {
zenity.SelectFileSave( zenity.SelectFileSave(
zenity.ConfirmOverwrite, zenity.ConfirmOverwrite(),
zenity.Filename(defaultName), zenity.Filename(defaultName),
zenity.FileFilters{ zenity.FileFilters{
{"Go files", []string{"*.go"}}, {"Go files", []string{"*.go"}},
{"Web files", []string{"*.html", "*.js", "*.css"}}, {"Web files", []string{"*.html", "*.js", "*.css"}},
{"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}}, {"Image files", []string{"*.png", "*.gif", "*.ico", "*.jpg", "*.webp"}},
}.New()) }.Build())
// Output: // Output:
} }
func ExampleSelectFile_directory() { func ExampleSelectFile_directory() {
zenity.SelectFile( zenity.SelectFile(
zenity.Filename(defaultPath), zenity.Filename(defaultPath),
zenity.Directory) zenity.Directory())
// Output: // Output:
} }
func ExampleSelectFileMutiple_directory() { func ExampleSelectFileMutiple_directory() {
zenity.SelectFileMutiple( zenity.SelectFileMutiple(
zenity.Filename(defaultPath), zenity.Filename(defaultPath),
zenity.Directory) zenity.Directory())
// Output: // Output:
} }

View File

@ -14,7 +14,7 @@ import (
// //
// Returns an empty string on cancel. // Returns an empty string on cancel.
// //
// Valid options: Title, Directory, Filename, FileFilters. // Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
func SelectFile(options ...Option) (string, error) { func SelectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
@ -47,7 +47,7 @@ func SelectFile(options ...Option) (string, error) {
// //
// Returns a nil slice on cancel. // Returns a nil slice on cancel.
// //
// Valid options: Title, Directory, Filename, FileFilters. // Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
func SelectFileMutiple(options ...Option) ([]string, error) { func SelectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
@ -80,7 +80,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
// //
// Returns an empty string on cancel. // Returns an empty string on cancel.
// //
// Valid options: Title, Filename, ConfirmOverwrite, FileFilters. // Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden, FileFilter(s).
func SelectFileSave(options ...Option) (string, error) { func SelectFileSave(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)

View File

@ -28,11 +28,14 @@ func SelectFile(options ...Option) (string, error) {
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 = 0x81008 // OFN_NOCHANGEDIR|OFN_FILEMUSTEXIST|OFN_EXPLORER
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
} }
if opts.hidden {
args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN
}
if opts.filters != nil { if opts.filters != nil {
args.Filter = &windowsFilters(opts.filters)[0] args.Filter = &windowsFilters(opts.filters)[0]
} }
@ -40,7 +43,7 @@ func SelectFile(options ...Option) (string, error) {
res := [32768]uint16{} res := [32768]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:]) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
@ -61,11 +64,14 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
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 = 0x81208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_FILEMUSTEXIST|OFN_EXPLORER
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
} }
if opts.hidden {
args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN
}
if opts.filters != nil { if opts.filters != nil {
args.Filter = &windowsFilters(opts.filters)[0] args.Filter = &windowsFilters(opts.filters)[0]
} }
@ -73,7 +79,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
res := [32768 + 1024*256]uint16{} res := [32768 + 1024*256]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:]) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
@ -112,10 +118,14 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
func SelectFileSave(options ...Option) (string, error) { func SelectFileSave(options ...Option) (string, error) {
opts := optsParse(options) 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 = 0x88808 // OFN_NOCHANGEDIR|OFN_PATHMUSTEXIST|OFN_NOREADONLYRETURN|OFN_EXPLORER
if opts.title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title) args.Title = syscall.StringToUTF16Ptr(opts.title)
@ -123,6 +133,12 @@ func SelectFileSave(options ...Option) (string, error) {
if opts.overwrite { if opts.overwrite {
args.Flags |= 0x2 // OFN_OVERWRITEPROMPT args.Flags |= 0x2 // OFN_OVERWRITEPROMPT
} }
if opts.create {
args.Flags |= 0x2000 // OFN_CREATEPROMPT
}
if opts.hidden {
args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN
}
if opts.filters != nil { if opts.filters != nil {
args.Filter = &windowsFilters(opts.filters)[0] args.Filter = &windowsFilters(opts.filters)[0]
} }
@ -130,7 +146,7 @@ func SelectFileSave(options ...Option) (string, error) {
res := [32768]uint16{} res := [32768]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:]) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
@ -171,6 +187,9 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
if multi { if multi {
flgs |= 0x200 // FOS_ALLOWMULTISELECT flgs |= 0x200 // FOS_ALLOWMULTISELECT
} }
if opts.hidden {
flgs |= 0x10000000 // FOS_FORCESHOWHIDDEN
}
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 "", nil, syscall.Errno(hr) return "", nil, syscall.Errno(hr)
@ -239,11 +258,8 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
if int32(hr) < 0 { if int32(hr) < 0 {
return "", nil, syscall.Errno(hr) return "", nil, syscall.Errno(hr)
} }
for i := uintptr(0); i < uintptr(count); i++ { for i := uintptr(0); i < uintptr(count) && err == nil; i++ {
err = shellItemPath(&items._COMObject, items.vtbl.GetItemAt, i) err = shellItemPath(&items._COMObject, items.vtbl.GetItemAt, i)
if err != nil {
return
}
} }
} else { } else {
err = shellItemPath(&dialog._COMObject, dialog.vtbl.GetResult) err = shellItemPath(&dialog._COMObject, dialog.vtbl.GetResult)
@ -272,17 +288,21 @@ func browseForFolder(title string) (string, []string, error) {
return str, []string{str}, nil return str, []string{str}, nil
} }
func initDirAndName(filename string, name []uint16) (dir *uint16) { func initDirNameExt(filename string, name []uint16) (dir *uint16, ext *uint16) {
if filename != "" { if filename != "" {
d, n := splitDirAndName(filename) d, n := splitDirAndName(filename)
e := filepath.Ext(n)
if n != "" { if n != "" {
copy(name, syscall.StringToUTF16(n)) copy(name, syscall.StringToUTF16(n))
} }
if d != "" { if d != "" {
return syscall.StringToUTF16Ptr(d) dir = syscall.StringToUTF16Ptr(d)
}
if e != "" {
ext = syscall.StringToUTF16Ptr(e[1:])
} }
} }
return nil return
} }
func windowsFilters(filters []FileFilter) []uint16 { func windowsFilters(filters []FileFilter) []uint16 {

View File

@ -23,33 +23,33 @@ func main() {
var str strings.Builder var str strings.Builder
for _, file := range files { for _, file := range files {
if name := file.Name(); filepath.Ext(name) == ".gots" { name := file.Name()
str.WriteString("\n" + `{{define "`)
str.WriteString(strings.TrimSuffix(name, ".gots"))
str.WriteString(`"}}<script>`)
func() { str.WriteString("\n" + `{{define "`)
in, err := os.Open(filepath.Join(dir, name)) str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
if err != nil { str.WriteString(`"}}<script>`)
log.Fatal(err)
}
defer in.Close()
scanner := bufio.NewScanner(in) func() {
for scanner.Scan() { in, err := os.Open(filepath.Join(dir, name))
line := strings.TrimSpace(scanner.Text()) if err != nil {
if line != "" { log.Fatal(err)
str.WriteString(line) }
str.WriteRune('\n') defer in.Close()
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}()
str.WriteString("</script>{{end}}") scanner := bufio.NewScanner(in)
} for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
str.WriteString(line)
str.WriteRune('\n')
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}()
str.WriteString("</script>{{end}}")
} }
out, err := os.Create("generated.go") out, err := os.Create("generated.go")

View File

@ -20,6 +20,8 @@ type options struct {
filename string filename string
directory bool directory bool
overwrite bool overwrite bool
create bool
hidden bool
filters []FileFilter filters []FileFilter
// Message options // Message options
@ -32,7 +34,7 @@ type options struct {
defcancel bool defcancel bool
} }
// Options are arguments you pass to dialog functions to customize their behavior. // Options are arguments passed to dialog functions to customize their behavior.
type Option func(*options) type Option func(*options)
func optsParse(options []Option) (res options) { func optsParse(options []Option) (res options) {
@ -46,9 +48,7 @@ func optsParse(options []Option) (res options) {
// Option to set the dialog title. // Option to set the dialog title.
func Title(title string) Option { func Title(title string) Option {
return func(o *options) { return func(o *options) { o.title = title }
o.title = title
}
} }
// File selection options // File selection options
@ -57,41 +57,51 @@ func Title(title string) Option {
// //
// You can specify a file name, a directory path, or both. // You can specify a file name, a directory path, or both.
// Specifying a file name, makes it the default selected file. // Specifying a file name, makes it the default selected file.
// Specifying a directory path, make it the default dialog location. // Specifying a directory path, makes it the default dialog location.
func Filename(filename string) Option { func Filename(filename string) Option {
return func(o *options) { return func(o *options) { o.filename = filename }
o.filename = filename
}
} }
// Option to activate directory-only selection. // Option to activate directory-only selection.
func Directory(o *options) { func Directory() Option {
o.directory = true return func(o *options) { o.directory = true }
} }
// Option to confirm file selection if filename already exists. // Option to confirm file selection if filename already exists.
func ConfirmOverwrite(o *options) { func ConfirmOverwrite() Option {
o.overwrite = true return func(o *options) { o.overwrite = true }
}
// Option to confirm file selection if filename does not yet exist (Windows only).
func ConfirmCreate() Option {
return func(o *options) { o.create = true }
}
// Option to show hidden files (Windows and macOS only).
func ShowHidden() Option {
return func(o *options) { o.hidden = true }
} }
// FileFilter encapsulates a filename filter. // FileFilter encapsulates a filename filter.
//
// macOS hides filename filters from the user,
// and only supports filtering by extension (or "type").
type FileFilter struct { type FileFilter struct {
Name string // display string that describes the filter (optional) Name string // display string that describes the filter (optional)
Patterns []string // filter patterns for the display string Patterns []string // filter patterns for the display string
} }
// Build option to set a filename filter.
func (f FileFilter) Build() Option {
return func(o *options) { o.filters = append(o.filters, f) }
}
// FileFilters is a list of filename filters. // FileFilters is a list of filename filters.
//
// macOS hides filename filters from the user,
// and only supports filtering by extension (or "type").
// We make an effort to convert any "*.EXT" like patterns.
type FileFilters []FileFilter type FileFilters []FileFilter
// Creates Option to set the filename filter list. // Build option to set filename filters.
func (f FileFilters) New() Option { func (f FileFilters) Build() Option {
return func(o *options) { return func(o *options) { o.filters = append(o.filters, f...) }
o.filters = f
}
} }
// Message options // Message options
@ -108,43 +118,35 @@ const (
// Option to set the dialog icon. // Option to set the dialog icon.
func Icon(icon MessageIcon) Option { func Icon(icon MessageIcon) Option {
return func(o *options) { return func(o *options) { o.icon = icon }
o.icon = icon
}
} }
// Option to set the label of the OK button. // Option to set the label of the OK button.
func OKLabel(ok string) Option { func OKLabel(ok string) Option {
return func(o *options) { return func(o *options) { o.ok = ok }
o.ok = ok
}
} }
// Option to set the label of the Cancel button. // Option to set the label of the Cancel button.
func CancelLabel(cancel string) Option { func CancelLabel(cancel string) Option {
return func(o *options) { return func(o *options) { o.cancel = cancel }
o.cancel = cancel
}
} }
// Option to add an extra button. // Option to add an extra button.
func ExtraButton(extra string) Option { func ExtraButton(extra string) Option {
return func(o *options) { return func(o *options) { o.extra = extra }
o.extra = extra
}
} }
// Option to disable enable text wrapping. // Option to disable enable text wrapping.
func NoWrap(o *options) { func NoWrap() Option {
o.nowrap = true return func(o *options) { o.nowrap = true }
} }
// Option to enable ellipsizing in the dialog text. // Option to enable ellipsizing in the dialog text.
func Ellipsize(o *options) { func Ellipsize() Option {
o.ellipsize = true return func(o *options) { o.ellipsize = true }
} }
// Option to give Cancel button focus by default. // Option to give Cancel button focus by default.
func DefaultCancel(o *options) { func DefaultCancel() Option {
o.defcancel = true return func(o *options) { o.defcancel = true }
} }