From 44cd5fa43bb398d8e64f2fdfdf45c9a2aaccab69 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 23 Jan 2020 11:44:28 +0000 Subject: [PATCH] Improve documentation. --- README.md | 4 +- color.go | 22 +++ color_darwin.go | 2 +- color_unix.go | 7 +- color_windows.go | 2 +- file.go | 81 +++++++++++ file_darwin.go | 6 +- file_unix.go | 21 +-- file_windows.go | 6 +- internal/zenutil/env_unix.go | 1 + internal/zenutil/osa_generated.go | 2 +- internal/zenutil/osascripts/color.applescript | 2 +- msg.go | 97 +++++++++++++ msg_darwin.go | 32 +--- msg_unix.go | 51 ++----- msg_windows.go | 32 +--- zenity.go | 137 ++---------------- 17 files changed, 259 insertions(+), 246 deletions(-) create mode 100644 color.go create mode 100644 msg.go diff --git a/README.md b/README.md index 723b14e..b5bdc6d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![GoDoc](https://godoc.org/github.com/ncruces/zenity?status.svg)](https://godoc.org/github.com/ncruces/zenity) -This repo includes both a cross-platform Go package providing [Zenity](https://help.gnome.org/users/zenity/)-like dialogs, +This repo includes both a cross-platform Go package providing +[Zenity](https://help.gnome.org/users/zenity/)-like dialogs +(simple dialogs that interact graphically with the user), as well as a *“port”* of the `zenity` command to both Windows and macOS based on that library. **This is a work in progress.** diff --git a/color.go b/color.go new file mode 100644 index 0000000..9ce00cf --- /dev/null +++ b/color.go @@ -0,0 +1,22 @@ +package zenity + +import "image/color" + +// SelectColor displays the color selection dialog. +// +// Returns nil on cancel. +// +// Valid options: Title, Color, ShowPalette. +func SelectColor(options ...Option) (color.Color, error) { + return selectColor(options...) +} + +// Color returns an Option to set the color. +func Color(c color.Color) Option { + return func(o *options) { o.color = c } +} + +// ShowPalette returns an Option to show the palette. +func ShowPalette() Option { + return func(o *options) { o.palette = true } +} diff --git a/color_darwin.go b/color_darwin.go index 7ea7238..461a8f4 100644 --- a/color_darwin.go +++ b/color_darwin.go @@ -7,7 +7,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func SelectColor(options ...Option) (color.Color, error) { +func selectColor(options ...Option) (color.Color, error) { opts := optsParse(options) var data zenutil.Color diff --git a/color_unix.go b/color_unix.go index d68514d..0225122 100644 --- a/color_unix.go +++ b/color_unix.go @@ -9,12 +9,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -// Display color selection dialog. -// -// Returns nil on cancel. -// -// Valid options: Title, Color, ShowPalette. -func SelectColor(options ...Option) (color.Color, error) { +func selectColor(options ...Option) (color.Color, error) { opts := optsParse(options) args := []string{"--color-selection"} diff --git a/color_windows.go b/color_windows.go index df22a97..8999ee3 100644 --- a/color_windows.go +++ b/color_windows.go @@ -20,7 +20,7 @@ func init() { } } -func SelectColor(options ...Option) (color.Color, error) { +func selectColor(options ...Option) (color.Color, error) { opts := optsParse(options) // load custom colors diff --git a/file.go b/file.go index dad808e..7d502d1 100644 --- a/file.go +++ b/file.go @@ -5,6 +5,87 @@ import ( "path/filepath" ) +// SelectFile displays the file selection dialog. +// +// Returns an empty string on cancel. +// +// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). +func SelectFile(options ...Option) (string, error) { + return selectFile(options...) +} + +// SelectFileMutiple displays the multiple file selection dialog. +// +// Returns a nil slice on cancel. +// +// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). +func SelectFileMutiple(options ...Option) ([]string, error) { + return selectFileMutiple(options...) +} + +// SelectFileSave displays the save file selection dialog. +// +// Returns an empty string on cancel. +// +// Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden, +// FileFilter(s). +func SelectFileSave(options ...Option) (string, error) { + return selectFileSave(options...) +} + +// Filename returns an Option to set the filename. +// +// 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, makes it the default dialog location. +func Filename(filename string) Option { + return func(o *options) { o.filename = filename } +} + +// Directory returns an Option to activate directory-only selection. +func Directory() Option { + return func(o *options) { o.directory = true } +} + +// ConfirmOverwrite returns an Option to confirm file selection if filename +// already exists. +func ConfirmOverwrite() Option { + return func(o *options) { o.overwrite = true } +} + +// ConfirmCreate returns an Option to confirm file selection if filename does +// not yet exist (Windows only). +func ConfirmCreate() Option { + return func(o *options) { o.create = true } +} + +// ShowHidden returns an 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 returns an 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. +type FileFilters []FileFilter + +// Build returns an Option to set filename filters. +func (f FileFilters) Build() Option { + return func(o *options) { o.filters = append(o.filters, f...) } +} + func splitDirAndName(path string) (dir, name string) { path = filepath.Clean(path) fi, err := os.Stat(path) diff --git a/file_darwin.go b/file_darwin.go index 565ff98..4c727b4 100644 --- a/file_darwin.go +++ b/file_darwin.go @@ -7,7 +7,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func SelectFile(options ...Option) (string, error) { +func selectFile(options ...Option) (string, error) { opts := optsParse(options) data := zenutil.File{ @@ -35,7 +35,7 @@ func SelectFile(options ...Option) (string, error) { return string(out), nil } -func SelectFileMutiple(options ...Option) ([]string, error) { +func selectFileMutiple(options ...Option) ([]string, error) { opts := optsParse(options) data := zenutil.File{ @@ -68,7 +68,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) { return strings.Split(string(out), zenutil.Separator), nil } -func SelectFileSave(options ...Option) (string, error) { +func selectFileSave(options ...Option) (string, error) { opts := optsParse(options) data := zenutil.File{ diff --git a/file_unix.go b/file_unix.go index a51228a..b0f1bfe 100644 --- a/file_unix.go +++ b/file_unix.go @@ -9,12 +9,7 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -// Display file selection dialog. -// -// Returns an empty string on cancel. -// -// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). -func SelectFile(options ...Option) (string, error) { +func selectFile(options ...Option) (string, error) { opts := optsParse(options) args := []string{"--file-selection"} @@ -42,12 +37,7 @@ func SelectFile(options ...Option) (string, error) { return string(out), nil } -// Display multiple file selection dialog. -// -// Returns a nil slice on cancel. -// -// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s). -func SelectFileMutiple(options ...Option) ([]string, error) { +func selectFileMutiple(options ...Option) ([]string, error) { opts := optsParse(options) args := []string{"--file-selection", "--multiple", "--separator", zenutil.Separator} @@ -75,12 +65,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) { return strings.Split(string(out), zenutil.Separator), nil } -// Display save file selection dialog. -// -// Returns an empty string on cancel. -// -// Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden, FileFilter(s). -func SelectFileSave(options ...Option) (string, error) { +func selectFileSave(options ...Option) (string, error) { opts := optsParse(options) args := []string{"--file-selection", "--save"} diff --git a/file_windows.go b/file_windows.go index 7b0d069..12d4096 100644 --- a/file_windows.go +++ b/file_windows.go @@ -17,7 +17,7 @@ var ( shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName") ) -func SelectFile(options ...Option) (string, error) { +func selectFile(options ...Option) (string, error) { opts := optsParse(options) if opts.directory { res, _, err := pickFolders(opts, false) @@ -53,7 +53,7 @@ func SelectFile(options ...Option) (string, error) { return syscall.UTF16ToString(res[:]), nil } -func SelectFileMutiple(options ...Option) ([]string, error) { +func selectFileMutiple(options ...Option) ([]string, error) { opts := optsParse(options) if opts.directory { _, res, err := pickFolders(opts, true) @@ -114,7 +114,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) { return split, nil } -func SelectFileSave(options ...Option) (string, error) { +func selectFileSave(options ...Option) (string, error) { opts := optsParse(options) if opts.directory { res, _, err := pickFolders(opts, false) diff --git a/internal/zenutil/env_unix.go b/internal/zenutil/env_unix.go index 977b44a..87f852e 100644 --- a/internal/zenutil/env_unix.go +++ b/internal/zenutil/env_unix.go @@ -1,5 +1,6 @@ // +build !windows,!darwin +// Package zenutil is internal. DO NOT USE. package zenutil const LineBreak = "\n" diff --git a/internal/zenutil/osa_generated.go b/internal/zenutil/osa_generated.go index c5c8b62..22368fe 100644 --- a/internal/zenutil/osa_generated.go +++ b/internal/zenutil/osa_generated.go @@ -13,7 +13,7 @@ var scripts = template.Must(template.New("").Funcs(template.FuncMap{"json": func {{define "color" -}}tell application (path to frontmost application as text) activate {{if .Color -}} -set c to choose color default color { {{index .Color 0}}, {{index .Color 1}}, {{index .Color 2}} } +set c to choose color default color { {{index .Color 0}},{{index .Color 1}},{{index .Color 2}} } {{else -}} set c to choose color {{end}} diff --git a/internal/zenutil/osascripts/color.applescript b/internal/zenutil/osascripts/color.applescript index 53b5eeb..127055c 100644 --- a/internal/zenutil/osascripts/color.applescript +++ b/internal/zenutil/osascripts/color.applescript @@ -1,7 +1,7 @@ tell application (path to frontmost application as text) activate {{if .Color -}} - set c to choose color default color { {{index .Color 0}}, {{index .Color 1}}, {{index .Color 2}} } + set c to choose color default color { {{index .Color 0}},{{index .Color 1}},{{index .Color 2}} } {{else -}} set c to choose color {{end}} diff --git a/msg.go b/msg.go new file mode 100644 index 0000000..a370a0c --- /dev/null +++ b/msg.go @@ -0,0 +1,97 @@ +package zenity + +// ErrExtraButton is returned by dialog functions when the extra button is +// pressed. +const ErrExtraButton = constError("Extra button pressed.") + +// Question displays the question dialog. +// +// Returns true on OK, false on Cancel, or ErrExtraButton. +// +// Valid options: Title, Icon, OKLabel, CancelLabel, ExtraButton, NoWrap, +// Ellipsize, DefaultCancel. +func Question(text string, options ...Option) (bool, error) { + return message(questionKind, text, options) +} + +// Info displays the info dialog. +// +// Returns true on OK, false on dismiss, or ErrExtraButton. +// +// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. +func Info(text string, options ...Option) (bool, error) { + return message(infoKind, text, options) +} + +// Warning displays the warning dialog. +// +// Returns true on OK, false on dismiss, or ErrExtraButton. +// +// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. +func Warning(text string, options ...Option) (bool, error) { + return message(warningKind, text, options) +} + +// Error displays the error dialog. +// +// Returns true on OK, false on dismiss, or ErrExtraButton. +// +// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. +func Error(text string, options ...Option) (bool, error) { + return message(errorKind, text, options) +} + +type messageKind int + +const ( + questionKind messageKind = iota + infoKind + warningKind + errorKind +) + +// MessageIcon is the enumeration for message dialog icons. +type MessageIcon int + +// Icons for +const ( + ErrorIcon MessageIcon = iota + 1 + WarningIcon + InfoIcon + QuestionIcon +) + +// Icon returns an Option to set the dialog icon. +func Icon(icon MessageIcon) Option { + return func(o *options) { o.icon = icon } +} + +// OKLabel returns an Option to set the label of the OK button. +func OKLabel(ok string) Option { + return func(o *options) { o.ok = ok } +} + +// CancelLabel returns an Option to set the label of the Cancel button. +func CancelLabel(cancel string) Option { + return func(o *options) { o.cancel = cancel } +} + +// ExtraButton returns an Option to add an extra button. +func ExtraButton(extra string) Option { + return func(o *options) { o.extra = extra } +} + +// NoWrap returns an Option to disable enable text wrapping. +func NoWrap() Option { + return func(o *options) { o.nowrap = true } +} + +// Ellipsize returns an Option to enable ellipsizing in the dialog text. +func Ellipsize() Option { + return func(o *options) { o.ellipsize = true } +} + +// DefaultCancel returns an Option to give Cancel button focus by default. +func DefaultCancel() Option { + return func(o *options) { o.defcancel = true } +} diff --git a/msg_darwin.go b/msg_darwin.go index ae55bff..649904b 100644 --- a/msg_darwin.go +++ b/msg_darwin.go @@ -6,26 +6,10 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -func Question(text string, options ...Option) (bool, error) { - return message(0, text, options) -} - -func Info(text string, options ...Option) (bool, error) { - return message(1, text, options) -} - -func Warning(text string, options ...Option) (bool, error) { - return message(2, text, options) -} - -func Error(text string, options ...Option) (bool, error) { - return message(3, text, options) -} - -func message(typ int, text string, options []Option) (bool, error) { +func message(kind messageKind, text string, options []Option) (bool, error) { opts := optsParse(options) data := zenutil.Msg{Text: text} - dialog := typ == 0 || opts.icon != 0 + dialog := kind == questionKind || opts.icon != 0 if dialog { data.Operation = "displayDialog" @@ -46,17 +30,17 @@ func message(typ int, text string, options []Option) (bool, error) { data.Text = opts.title } - switch typ { - case 1: + switch kind { + case infoKind: data.As = "informational" - case 2: + case warningKind: data.As = "warning" - case 3: + case errorKind: data.As = "critical" } } - if typ != 0 { + if kind != questionKind { if dialog { opts.ok = "OK" } @@ -69,7 +53,7 @@ func message(typ int, text string, options []Option) (bool, error) { if opts.cancel == "" { opts.cancel = "Cancel" } - if typ == 0 { + if kind == questionKind { if opts.extra == "" { data.Buttons = []string{opts.cancel, opts.ok} data.Default = 2 diff --git a/msg_unix.go b/msg_unix.go index a4a79aa..1855e3f 100644 --- a/msg_unix.go +++ b/msg_unix.go @@ -8,47 +8,20 @@ import ( "github.com/ncruces/zenity/internal/zenutil" ) -// Display question dialog. -// -// Returns true on OK, false on Cancel, or ErrExtraButton. -// -// Valid options: Title, Icon, OKLabel, CancelLabel, ExtraButton, NoWrap, -// Ellipsize, DefaultCancel. -func Question(text string, options ...Option) (bool, error) { - return message("--question", text, options) -} - -// Display info dialog. -// -// Returns true on OK, false on dismiss, or ErrExtraButton. -// -// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. -func Info(text string, options ...Option) (bool, error) { - return message("--info", text, options) -} - -// Display warning dialog. -// -// Returns true on OK, false on dismiss, or ErrExtraButton. -// -// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. -func Warning(text string, options ...Option) (bool, error) { - return message("--warning", text, options) -} - -// Display error dialog. -// -// Returns true on OK, false on dismiss, or ErrExtraButton. -// -// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize. -func Error(text string, options ...Option) (bool, error) { - return message("--error", text, options) -} - -func message(arg, text string, options []Option) (bool, error) { +func message(kind messageKind, text string, options []Option) (bool, error) { opts := optsParse(options) - args := []string{arg} + var args []string + switch kind { + case questionKind: + args = append(args, "--question") + case infoKind: + args = append(args, "--info") + case warningKind: + args = append(args, "--warning") + case errorKind: + args = append(args, "--error") + } if text != "" { args = append(args, "--text", text, "--no-markup") } diff --git a/msg_windows.go b/msg_windows.go index 9586c34..092a097 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -10,31 +10,15 @@ var ( messageBox = user32.NewProc("MessageBoxW") ) -func Question(text string, options ...Option) (bool, error) { - return message(0, text, options) -} - -func Info(text string, options ...Option) (bool, error) { - return message(1, text, options) -} - -func Warning(text string, options ...Option) (bool, error) { - return message(2, text, options) -} - -func Error(text string, options ...Option) (bool, error) { - return message(3, text, options) -} - -func message(typ int, text string, options []Option) (bool, error) { +func message(kind messageKind, text string, options []Option) (bool, error) { opts := optsParse(options) var flags, caption uintptr switch { - case typ == 0 && opts.extra != "": + case kind == questionKind && opts.extra != "": flags |= 0x3 // MB_YESNOCANCEL - case typ == 0 || opts.extra != "": + case kind == questionKind || opts.extra != "": flags |= 0x1 // MB_OKCANCEL } @@ -49,7 +33,7 @@ func message(typ int, text string, options []Option) (bool, error) { flags |= 0x40 // MB_ICONINFORMATION } - if typ == 0 && opts.defcancel { + if kind == questionKind && opts.defcancel { if opts.extra == "" { flags |= 0x100 // MB_DEFBUTTON2 } else { @@ -65,7 +49,7 @@ func message(typ int, text string, options []Option) (bool, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - hook, err := hookMessageLabels(typ, opts) + hook, err := hookMessageLabels(kind, opts) if hook == 0 { return false, err } @@ -79,7 +63,7 @@ func message(typ int, text string, options []Option) (bool, error) { if n == 0 { return false, err } - if n == 7 || n == 2 && typ != 0 { // IDNO + if n == 7 || n == 2 && kind != questionKind { // IDNO return false, ErrExtraButton } if n == 1 || n == 6 { // IDOK, IDYES @@ -88,7 +72,7 @@ func message(typ int, text string, options []Option) (bool, error) { return false, nil } -func hookMessageLabels(typ int, opts options) (hook uintptr, err error) { +func hookMessageLabels(kind messageKind, opts options) (hook uintptr, err error) { tid, _, _ := getCurrentThreadId.Call() hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { @@ -107,7 +91,7 @@ func hookMessageLabels(typ int, opts options) (hook uintptr, err error) { case 1, 6: // IDOK, IDYES text = opts.ok case 2: // IDCANCEL - if typ == 0 { + if kind == questionKind { text = opts.cancel } else if opts.extra != "" { text = opts.extra diff --git a/zenity.go b/zenity.go index 2b54ae8..e7ea01a 100644 --- a/zenity.go +++ b/zenity.go @@ -1,19 +1,21 @@ +// Package zenity provides cross-platform access to simple dialogs that interact +// graphically with the user. +// +// It is inspired by, and closely follows the API of, the zenity program, which +// it uses to provide the functionality on various Unixes. See: +// +// https://help.gnome.org/users/zenity/ +// +// This package does not require cgo, and it does not impose any threading or +// initialization requirements. package zenity import "image/color" -// Errors - type constError string func (e constError) Error() string { return string(e) } -// Message errors - -const ErrExtraButton = constError("Extra button pressed.") - -// Options - type options struct { // General options title string @@ -40,7 +42,8 @@ type options struct { defcancel bool } -// Options are arguments passed to dialog functions to customize their behavior. +// An Option is an argument passed to dialog functions to customize their +// behavior. type Option func(*options) func optsParse(options []Option) (res options) { @@ -50,121 +53,7 @@ func optsParse(options []Option) (res options) { return } -// General options - -// Option to set the dialog title. +// Title returns an Option to set the dialog title. func Title(title string) Option { return func(o *options) { o.title = title } } - -// File selection options - -// Option to set the filename. -// -// 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, makes it the default dialog location. -func Filename(filename string) Option { - return func(o *options) { o.filename = filename } -} - -// Option to activate directory-only selection. -func Directory() Option { - return func(o *options) { o.directory = true } -} - -// Option to confirm file selection if filename already exists. -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. -type FileFilters []FileFilter - -// Build option to set filename filters. -func (f FileFilters) Build() Option { - return func(o *options) { o.filters = append(o.filters, f...) } -} - -// Color selection options - -// Option to set the color. -func Color(c color.Color) Option { - return func(o *options) { o.color = c } -} - -// Option to show the palette. -func ShowPalette() Option { - return func(o *options) { o.palette = true } -} - -// Message options - -// MessageIcon is the enumeration for message dialog icons. -type MessageIcon int - -const ( - ErrorIcon MessageIcon = iota + 1 - WarningIcon - InfoIcon - QuestionIcon -) - -// Option to set the dialog icon. -func Icon(icon MessageIcon) Option { - 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 } -} - -// Option to set the label of the Cancel button. -func CancelLabel(cancel string) Option { - 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 } -} - -// Option to disable enable text wrapping. -func NoWrap() Option { - return func(o *options) { o.nowrap = true } -} - -// Option to enable ellipsizing in the dialog text. -func Ellipsize() Option { - return func(o *options) { o.ellipsize = true } -} - -// Option to give Cancel button focus by default. -func DefaultCancel() Option { - return func(o *options) { o.defcancel = true } -}