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
directory bool
confirmOverwrite bool
confirmCreate bool
showHidden bool
filename string
separator string
fileFilters FileFilters
@ -104,6 +106,8 @@ func setupFlags() {
flag.BoolVar(&multiple, "multiple", false, "Allow multiple files to be selected")
flag.BoolVar(&directory, "directory", false, "Activate directory-only selection")
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(&separator, "separator", "|", "Set output separator character")
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.ExtraButton(extraButton))
if noWrap {
options = append(options, zenity.NoWrap)
options = append(options, zenity.NoWrap())
}
if ellipsize {
options = append(options, zenity.Ellipsize)
options = append(options, zenity.Ellipsize())
}
if defaultCancel {
options = append(options, zenity.DefaultCancel)
options = append(options, zenity.DefaultCancel())
}
// File selection options
options = append(options, fileFilters.New())
options = append(options, fileFilters.Build())
options = append(options, zenity.Filename(filename))
if directory {
options = append(options, zenity.Directory)
options = append(options, zenity.Directory())
}
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

View file

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

View file

@ -14,7 +14,7 @@ import (
//
// 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) {
opts := optsParse(options)
@ -47,7 +47,7 @@ func SelectFile(options ...Option) (string, error) {
//
// 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) {
opts := optsParse(options)
@ -80,7 +80,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
//
// 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) {
opts := optsParse(options)

View file

@ -28,11 +28,14 @@ func SelectFile(options ...Option) (string, error) {
var args _OPENFILENAME
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 != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title)
}
if opts.hidden {
args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN
}
if opts.filters != nil {
args.Filter = &windowsFilters(opts.filters)[0]
}
@ -40,7 +43,7 @@ func SelectFile(options ...Option) (string, error) {
res := [32768]uint16{}
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:])
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@ -61,11 +64,14 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
var args _OPENFILENAME
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 != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title)
}
if opts.hidden {
args.Flags |= 0x10000000 // OFN_FORCESHOWHIDDEN
}
if opts.filters != nil {
args.Filter = &windowsFilters(opts.filters)[0]
}
@ -73,7 +79,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
res := [32768 + 1024*256]uint16{}
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:])
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@ -112,10 +118,14 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
func SelectFileSave(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
args.Flags = 0x88808 // OFN_NOCHANGEDIR|OFN_PATHMUSTEXIST|OFN_NOREADONLYRETURN|OFN_EXPLORER
if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(opts.title)
@ -123,6 +133,12 @@ func SelectFileSave(options ...Option) (string, error) {
if opts.overwrite {
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 {
args.Filter = &windowsFilters(opts.filters)[0]
}
@ -130,7 +146,7 @@ func SelectFileSave(options ...Option) (string, error) {
res := [32768]uint16{}
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir = initDirAndName(opts.filename, res[:])
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@ -171,6 +187,9 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
if multi {
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
if int32(hr) < 0 {
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 {
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)
if err != nil {
return
}
}
} else {
err = shellItemPath(&dialog._COMObject, dialog.vtbl.GetResult)
@ -272,17 +288,21 @@ func browseForFolder(title string) (string, []string, error) {
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 != "" {
d, n := splitDirAndName(filename)
e := filepath.Ext(n)
if n != "" {
copy(name, syscall.StringToUTF16(n))
}
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 {

View file

@ -23,33 +23,33 @@ func main() {
var str strings.Builder
for _, file := range files {
if name := file.Name(); filepath.Ext(name) == ".gots" {
str.WriteString("\n" + `{{define "`)
str.WriteString(strings.TrimSuffix(name, ".gots"))
str.WriteString(`"}}<script>`)
name := file.Name()
func() {
in, err := os.Open(filepath.Join(dir, name))
if err != nil {
log.Fatal(err)
}
defer in.Close()
str.WriteString("\n" + `{{define "`)
str.WriteString(strings.TrimSuffix(name, filepath.Ext(name)))
str.WriteString(`"}}<script>`)
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)
}
}()
func() {
in, err := os.Open(filepath.Join(dir, name))
if err != nil {
log.Fatal(err)
}
defer in.Close()
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")

View file

@ -20,6 +20,8 @@ type options struct {
filename string
directory bool
overwrite bool
create bool
hidden bool
filters []FileFilter
// Message options
@ -32,7 +34,7 @@ type options struct {
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)
func optsParse(options []Option) (res options) {
@ -46,9 +48,7 @@ func optsParse(options []Option) (res options) {
// Option to set the dialog title.
func Title(title string) Option {
return func(o *options) {
o.title = title
}
return func(o *options) { o.title = title }
}
// File selection options
@ -57,41 +57,51 @@ func Title(title string) Option {
//
// You can specify a file name, a directory path, or both.
// 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 {
return func(o *options) {
o.filename = filename
}
return func(o *options) { o.filename = filename }
}
// Option to activate directory-only selection.
func Directory(o *options) {
o.directory = true
func Directory() Option {
return func(o *options) { o.directory = true }
}
// Option to confirm file selection if filename already exists.
func ConfirmOverwrite(o *options) {
o.overwrite = true
func ConfirmOverwrite() Option {
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.
//
// macOS hides filename filters from the user,
// and only supports filtering by extension (or "type").
type FileFilter struct {
Name string // display string that describes the filter (optional)
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.
//
// 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
// Creates Option to set the filename filter list.
func (f FileFilters) New() Option {
return func(o *options) {
o.filters = f
}
// Build option to set filename filters.
func (f FileFilters) Build() Option {
return func(o *options) { o.filters = append(o.filters, f...) }
}
// Message options
@ -108,43 +118,35 @@ const (
// Option to set the dialog icon.
func Icon(icon MessageIcon) Option {
return func(o *options) {
o.icon = icon
}
return func(o *options) { o.icon = icon }
}
// Option to set the label of the OK button.
func OKLabel(ok string) Option {
return func(o *options) {
o.ok = ok
}
return func(o *options) { o.ok = ok }
}
// Option to set the label of the Cancel button.
func CancelLabel(cancel string) Option {
return func(o *options) {
o.cancel = cancel
}
return func(o *options) { o.cancel = cancel }
}
// Option to add an extra button.
func ExtraButton(extra string) Option {
return func(o *options) {
o.extra = extra
}
return func(o *options) { o.extra = extra }
}
// Option to disable enable text wrapping.
func NoWrap(o *options) {
o.nowrap = true
func NoWrap() Option {
return func(o *options) { o.nowrap = true }
}
// Option to enable ellipsizing in the dialog text.
func Ellipsize(o *options) {
o.ellipsize = true
func Ellipsize() Option {
return func(o *options) { o.ellipsize = true }
}
// Option to give Cancel button focus by default.
func DefaultCancel(o *options) {
o.defcancel = true
func DefaultCancel() Option {
return func(o *options) { o.defcancel = true }
}