diff --git a/dialog.go b/dialog.go index 9c2ad2c..a275a64 100644 --- a/dialog.go +++ b/dialog.go @@ -2,9 +2,40 @@ package ui +// Dialog is an interface adopted by all dialogs. +type Dialog struct { + Response() Response // response code from the dialog + Selection() interface{} // currently nil +} + +// Response denotes a response from the user to a Dialog. +type Response uint +const ( + NotDone Response = iota + OK +) + +// basic return +type dialogret struct { + res Response + sel interface{} +} +func (d *dialogret) Response() Response { return d.res } +func (d *dialogret) Selection() interface{} { return d.sel } + // sentinel (not nil so programmer errors don't go undetected) // this window is invalid and cannot be used directly -var dialogWindow = new(Window) +// notice the support it uses +var dialogWindow = &Window{ + sysData: &sysData{ + winhandler: dh, + } +} +type dhandler chan *dialogret +func (d dhandler) Event(e Event, dat interface{}) { + d <- dat.(*dialogret) +} +var dh = make(dhandler) // MsgBox displays an informational message box to the user with just an OK button. // primaryText should be a short string describing the message, and will be displayed with additional emphasis on platforms that support it. @@ -13,13 +44,14 @@ var dialogWindow = new(Window) // On platforms that allow for the message box window to have a title, os.Args[0] is used. // // See "On Dialogs" in the package overview for behavioral information. -func MsgBox(primaryText string, secondaryText string) { - <-dialogWindow.msgBox(primaryText, secondaryText) +func MsgBox(primaryText string, secondaryText string) Response { + dialogWindow.msgBox(primaryText, secondaryText) + return (<-dh).res } // MsgBox is the Window method version of the package-scope function MsgBox. // See that function's documentation and "On Dialogs" in the package overview for more information. -func (w *Window) MsgBox(primaryText string, secondaryText string) (done chan struct{}) { +func (w *Window) MsgBox(primaryText string, secondaryText string) Dialog { if !w.created { panic("parent window passed to Window.MsgBox() before it was created") } @@ -30,15 +62,13 @@ func (w *Window) MsgBox(primaryText string, secondaryText string) (done chan str // Otherwise, it behaves like MsgBox. // // See "On Dialogs" in the package overview for more information. -func MsgBoxError(primaryText string, secondaryText string) { - <-dialogWindow.msgBoxError(primaryText, secondaryText) +func MsgBoxError(primaryText string, secondaryText string) Dialog { + dialogWindow.msgBoxError(primaryText, secondaryText) + return (<-dh).res } // MsgBoxError is the Window method version of the package-scope function MsgBoxError. // See that function's documentation and "On Dialogs" in the package overview for more information. -func (w *Window) MsgBoxError(primaryText string, secondaryText string) (done chan struct{}) { - if !w.created { - panic("parent window passed to MsgBoxError() before it was created") - } +func (w *Window) MsgBoxError(primaryText string, secondaryText string) Dialog { return w.msgBoxError(primaryText, secondaryText) } diff --git a/dialog_windows.go b/dialog_windows.go index c6da94b..5422f1c 100644 --- a/dialog_windows.go +++ b/dialog_windows.go @@ -11,7 +11,11 @@ var ( _messageBox = user32.NewProc("MessageBoxW") ) -func _msgBox(parent *Window, primarytext string, secondarytext string, uType uint32) (result chan int) { +var dialogResponse = map[uintptr]Response{ + _IDOK: OK, +} + +func _msgBox(parent *Window, primarytext string, secondarytext string, uType uint32) (result int) { // http://msdn.microsoft.com/en-us/library/windows/desktop/aa511267.aspx says "Use task dialogs whenever appropriate to achieve a consistent look and layout. Task dialogs require Windows Vista® or later, so they aren't suitable for earlier versions of Windows. If you must use a message box, separate the main instruction from the supplemental instruction with two line breaks." text := primarytext if secondarytext != "" { @@ -26,43 +30,28 @@ func _msgBox(parent *Window, primarytext string, secondarytext string, uType uin } else { uType |= _MB_TASKMODAL // make modal to every window in the program (they're all windows of the uitask, which is a single thread) } - retchan := make(chan int) - go func() { - ret := make(chan uiret) - defer close(ret) - uitask <- &uimsg{ - call: _messageBox, - p: []uintptr{ - uintptr(parenthwnd), - utf16ToArg(ptext), - utf16ToArg(ptitle), - uintptr(uType), - }, - ret: ret, - } - r := <-ret - if r.ret == 0 { // failure - panic(fmt.Sprintf("error displaying message box to user: %v\nstyle: 0x%08X\ntitle: %q\ntext:\n%s", r.err, uType, os.Args[0], text)) - } - retchan <- int(r.ret) - }() - return retchan + r1, _, err := _messageBox.Call( + uintptr(parenthwnd), + utf16ToArg(ptext), + utf16ToArg(ptitle), + uintptr(uType)) + if r1 == 0 { // failure + panic(fmt.Sprintf("error displaying message box to user: %v\nstyle: 0x%08X\ntitle: %q\ntext:\n%s", err, uType, os.Args[0], text)) + } + w.sysData.winhandler.Event(Dismissed, &dialogret{ + res: dialogResponses[r1], + }) } -func (w *Window) msgBox(primarytext string, secondarytext string) (done chan struct{}) { - done = make(chan struct{}) - go func() { - <-_msgBox(w, primarytext, secondarytext, _MB_OK) - done <- struct{}{} - }() - return done +func (w *Window) msgBox(primarytext string, secondarytext string) { + // send to uitask so the function can return immediately + touitask(func() { + _msgBox(w, primarytext, secondarytext, _MB_OK) + }) } -func (w *Window) msgBoxError(primarytext string, secondarytext string) (done chan struct{}) { - done = make(chan struct{}) - go func() { - <-_msgBox(w, primarytext, secondarytext, _MB_OK|_MB_ICONERROR) - done <- struct{}{} - }() - return done +func (w *Window) msgBoxError(primarytext string, secondarytext string) { + touitask(func() { + _msgBox(w, primarytext, secondarytext, _MB_OK|_MB_ICONERROR) + }) } diff --git a/init.go b/init.go index 2e59301..0474612 100644 --- a/init.go +++ b/init.go @@ -17,13 +17,10 @@ func Go(main func()) error { return ui(main) } -// AppQuit is pulsed when the user decides to quit the program if their operating system provides a facility for quitting an entire application, rather than merely close all windows (for instance, Mac OS X via the Dock icon). -// You should assign one of your Windows's Closing to this variable so the user choosing to quit the application is treated the same as closing that window. -// If you do not respond to this signal, nothing will happen; regardless of whether or not you respond to this signal, the application will not quit. -// Do not merely check this channel alone; it is not guaranteed to be pulsed on all systems or in all conditions. -var AppQuit chan struct{} - -func init() { - // don't expose this in the documentation - AppQuit = newEvent() +// This function is a simple helper functionn that basically pushes the effect of a function call for later. This allows the selected safe Window methods to be safe. +// It's also currently used by the various dialog box functions on Windows to allow them to return instantly, rather than wait for the dialog box to finish (which both GTK+ and Mac OS X let you do). I consider this a race condition bug. TODO (also TODO document the /intended/ behavior) +func touitask(f func()) { + go func() { // to avoid locking uitask itself + uitask <- f + }() } diff --git a/window.go b/window.go index 9b58937..3cf52b0 100644 --- a/window.go +++ b/window.go @@ -37,6 +37,7 @@ type Event int const ( Closing Event = iota // Window close Clicked // Button click + Dismissed // Dialog closed CustomEvent = 5000 // very high number; higher than the package would ever need, anyway )