Improve documentation.

This commit is contained in:
Nuno Cruces 2020-01-23 11:44:28 +00:00
parent 0728bdf5ad
commit 44cd5fa43b
17 changed files with 259 additions and 246 deletions

View file

@ -2,7 +2,9 @@
[![GoDoc](https://godoc.org/github.com/ncruces/zenity?status.svg)](https://godoc.org/github.com/ncruces/zenity) [![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. as well as a *“port”* of the `zenity` command to both Windows and macOS based on that library.
**This is a work in progress.** **This is a work in progress.**

22
color.go Normal file
View file

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

View file

@ -7,7 +7,7 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
func SelectColor(options ...Option) (color.Color, error) { func selectColor(options ...Option) (color.Color, error) {
opts := optsParse(options) opts := optsParse(options)
var data zenutil.Color var data zenutil.Color

View file

@ -9,12 +9,7 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
// Display color selection dialog. func selectColor(options ...Option) (color.Color, error) {
//
// Returns nil on cancel.
//
// Valid options: Title, Color, ShowPalette.
func SelectColor(options ...Option) (color.Color, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--color-selection"} args := []string{"--color-selection"}

View file

@ -20,7 +20,7 @@ func init() {
} }
} }
func SelectColor(options ...Option) (color.Color, error) { func selectColor(options ...Option) (color.Color, error) {
opts := optsParse(options) opts := optsParse(options)
// load custom colors // load custom colors

81
file.go
View file

@ -5,6 +5,87 @@ import (
"path/filepath" "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) { func splitDirAndName(path string) (dir, name string) {
path = filepath.Clean(path) path = filepath.Clean(path)
fi, err := os.Stat(path) fi, err := os.Stat(path)

View file

@ -7,7 +7,7 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
func SelectFile(options ...Option) (string, error) { func selectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
data := zenutil.File{ data := zenutil.File{
@ -35,7 +35,7 @@ func SelectFile(options ...Option) (string, error) {
return string(out), nil return string(out), nil
} }
func SelectFileMutiple(options ...Option) ([]string, error) { func selectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
data := zenutil.File{ data := zenutil.File{
@ -68,7 +68,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
return strings.Split(string(out), zenutil.Separator), nil return strings.Split(string(out), zenutil.Separator), nil
} }
func SelectFileSave(options ...Option) (string, error) { func selectFileSave(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
data := zenutil.File{ data := zenutil.File{

View file

@ -9,12 +9,7 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
// Display file selection dialog. func selectFile(options ...Option) (string, error) {
//
// Returns an empty string on cancel.
//
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
func SelectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--file-selection"} args := []string{"--file-selection"}
@ -42,12 +37,7 @@ func SelectFile(options ...Option) (string, error) {
return string(out), nil return string(out), nil
} }
// Display multiple file selection dialog. func selectFileMutiple(options ...Option) ([]string, error) {
//
// Returns a nil slice on cancel.
//
// Valid options: Title, Directory, Filename, ShowHidden, FileFilter(s).
func SelectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--file-selection", "--multiple", "--separator", zenutil.Separator} 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 return strings.Split(string(out), zenutil.Separator), nil
} }
// Display save file selection dialog. func selectFileSave(options ...Option) (string, error) {
//
// Returns an empty string on cancel.
//
// Valid options: Title, Filename, ConfirmOverwrite, ConfirmCreate, ShowHidden, FileFilter(s).
func SelectFileSave(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
args := []string{"--file-selection", "--save"} args := []string{"--file-selection", "--save"}

View file

@ -17,7 +17,7 @@ var (
shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName") shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName")
) )
func SelectFile(options ...Option) (string, error) { func selectFile(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
if opts.directory { if opts.directory {
res, _, err := pickFolders(opts, false) res, _, err := pickFolders(opts, false)
@ -53,7 +53,7 @@ func SelectFile(options ...Option) (string, error) {
return syscall.UTF16ToString(res[:]), nil return syscall.UTF16ToString(res[:]), nil
} }
func SelectFileMutiple(options ...Option) ([]string, error) { func selectFileMutiple(options ...Option) ([]string, error) {
opts := optsParse(options) opts := optsParse(options)
if opts.directory { if opts.directory {
_, res, err := pickFolders(opts, true) _, res, err := pickFolders(opts, true)
@ -114,7 +114,7 @@ func SelectFileMutiple(options ...Option) ([]string, error) {
return split, nil return split, nil
} }
func SelectFileSave(options ...Option) (string, error) { func selectFileSave(options ...Option) (string, error) {
opts := optsParse(options) opts := optsParse(options)
if opts.directory { if opts.directory {
res, _, err := pickFolders(opts, false) res, _, err := pickFolders(opts, false)

View file

@ -1,5 +1,6 @@
// +build !windows,!darwin // +build !windows,!darwin
// Package zenutil is internal. DO NOT USE.
package zenutil package zenutil
const LineBreak = "\n" const LineBreak = "\n"

View file

@ -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) {{define "color" -}}tell application (path to frontmost application as text)
activate activate
{{if .Color -}} {{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 -}} {{else -}}
set c to choose color set c to choose color
{{end}} {{end}}

View file

@ -1,7 +1,7 @@
tell application (path to frontmost application as text) tell application (path to frontmost application as text)
activate activate
{{if .Color -}} {{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 -}} {{else -}}
set c to choose color set c to choose color
{{end}} {{end}}

97
msg.go Normal file
View file

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

View file

@ -6,26 +6,10 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
func Question(text string, options ...Option) (bool, error) { func message(kind messageKind, 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) {
opts := optsParse(options) opts := optsParse(options)
data := zenutil.Msg{Text: text} data := zenutil.Msg{Text: text}
dialog := typ == 0 || opts.icon != 0 dialog := kind == questionKind || opts.icon != 0
if dialog { if dialog {
data.Operation = "displayDialog" data.Operation = "displayDialog"
@ -46,17 +30,17 @@ func message(typ int, text string, options []Option) (bool, error) {
data.Text = opts.title data.Text = opts.title
} }
switch typ { switch kind {
case 1: case infoKind:
data.As = "informational" data.As = "informational"
case 2: case warningKind:
data.As = "warning" data.As = "warning"
case 3: case errorKind:
data.As = "critical" data.As = "critical"
} }
} }
if typ != 0 { if kind != questionKind {
if dialog { if dialog {
opts.ok = "OK" opts.ok = "OK"
} }
@ -69,7 +53,7 @@ func message(typ int, text string, options []Option) (bool, error) {
if opts.cancel == "" { if opts.cancel == "" {
opts.cancel = "Cancel" opts.cancel = "Cancel"
} }
if typ == 0 { if kind == questionKind {
if opts.extra == "" { if opts.extra == "" {
data.Buttons = []string{opts.cancel, opts.ok} data.Buttons = []string{opts.cancel, opts.ok}
data.Default = 2 data.Default = 2

View file

@ -8,47 +8,20 @@ import (
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
// Display question dialog. func message(kind messageKind, text string, options []Option) (bool, error) {
//
// 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) {
opts := optsParse(options) 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 != "" { if text != "" {
args = append(args, "--text", text, "--no-markup") args = append(args, "--text", text, "--no-markup")
} }

View file

@ -10,31 +10,15 @@ var (
messageBox = user32.NewProc("MessageBoxW") messageBox = user32.NewProc("MessageBoxW")
) )
func Question(text string, options ...Option) (bool, error) { func message(kind messageKind, 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) {
opts := optsParse(options) opts := optsParse(options)
var flags, caption uintptr var flags, caption uintptr
switch { switch {
case typ == 0 && opts.extra != "": case kind == questionKind && opts.extra != "":
flags |= 0x3 // MB_YESNOCANCEL flags |= 0x3 // MB_YESNOCANCEL
case typ == 0 || opts.extra != "": case kind == questionKind || opts.extra != "":
flags |= 0x1 // MB_OKCANCEL flags |= 0x1 // MB_OKCANCEL
} }
@ -49,7 +33,7 @@ func message(typ int, text string, options []Option) (bool, error) {
flags |= 0x40 // MB_ICONINFORMATION flags |= 0x40 // MB_ICONINFORMATION
} }
if typ == 0 && opts.defcancel { if kind == questionKind && opts.defcancel {
if opts.extra == "" { if opts.extra == "" {
flags |= 0x100 // MB_DEFBUTTON2 flags |= 0x100 // MB_DEFBUTTON2
} else { } else {
@ -65,7 +49,7 @@ func message(typ int, text string, options []Option) (bool, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
hook, err := hookMessageLabels(typ, opts) hook, err := hookMessageLabels(kind, opts)
if hook == 0 { if hook == 0 {
return false, err return false, err
} }
@ -79,7 +63,7 @@ func message(typ int, text string, options []Option) (bool, error) {
if n == 0 { if n == 0 {
return false, err return false, err
} }
if n == 7 || n == 2 && typ != 0 { // IDNO if n == 7 || n == 2 && kind != questionKind { // IDNO
return false, ErrExtraButton return false, ErrExtraButton
} }
if n == 1 || n == 6 { // IDOK, IDYES if n == 1 || n == 6 { // IDOK, IDYES
@ -88,7 +72,7 @@ func message(typ int, text string, options []Option) (bool, error) {
return false, nil 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() tid, _, _ := getCurrentThreadId.Call()
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET
syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { 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 case 1, 6: // IDOK, IDYES
text = opts.ok text = opts.ok
case 2: // IDCANCEL case 2: // IDCANCEL
if typ == 0 { if kind == questionKind {
text = opts.cancel text = opts.cancel
} else if opts.extra != "" { } else if opts.extra != "" {
text = opts.extra text = opts.extra

137
zenity.go
View file

@ -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 package zenity
import "image/color" import "image/color"
// Errors
type constError string type constError string
func (e constError) Error() string { return string(e) } func (e constError) Error() string { return string(e) }
// Message errors
const ErrExtraButton = constError("Extra button pressed.")
// Options
type options struct { type options struct {
// General options // General options
title string title string
@ -40,7 +42,8 @@ type options struct {
defcancel bool 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) type Option func(*options)
func optsParse(options []Option) (res options) { func optsParse(options []Option) (res options) {
@ -50,121 +53,7 @@ func optsParse(options []Option) (res options) {
return return
} }
// General options // Title returns an 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) { o.title = title } 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 }
}