Implemented the new MsgBox()/MsgBoxError() behavior on Windows.

This commit is contained in:
Pietro Gagliardi 2014-06-08 08:50:52 -04:00
parent 7cb8fc1e32
commit 641d11c6fe
3 changed files with 77 additions and 35 deletions

View File

@ -19,7 +19,7 @@ var dialogWindow *Window
// Whether or not resizing Windows will still be allowed is implementation-defined; if the implementation does allow it, resizes will still work properly.
// Whether or not the message box stays above all other W+indows in the program is also implementation-defined.
func MsgBox(primaryText string, secondaryText string) {
dialogWindow.msgBox(parent, primaryText, secondaryText)
<-dialogWindow.msgBox(primaryText, secondaryText)
}
// MsgBox behaves like the package-scope MsgBox function, except the message box is modal to w only.
@ -28,24 +28,26 @@ func MsgBox(primaryText string, secondaryText string) {
// Whether w can be resized while the message box is displayed is implementation-defined, but will work properly if allowed.
// If w has not yet been created, MsgBox() panics.
// If w has not been shown yet or is currently hidden, what MsgBox does is implementation-defined.
func (w *Window) MsgBox(primaryText string, secondaryText string) {
//
// On return, done will be a channel that is pulsed when the message box is dismissed.
func (w *Window) MsgBox(primaryText string, secondaryText string) (done chan struct{}) {
if !w.created {
panic("parent window passed to Window.MsgBox() before it was created")
}
w.msgBox(primaryText, secondaryText)
return w.msgBox(primaryText, secondaryText)
}
// MsgBoxError displays a message box to the user with just an OK button and an icon indicating an error.
// Otherwise, it behaves like MsgBox.
func MsgBoxError(primaryText string, secondaryText string) {
dialogWindow.msgBoxError(parent, primaryText, secondaryText)
<-dialogWindow.msgBoxError(primaryText, secondaryText)
}
// MsgBoxError displays a message box to the user with just an OK button and an icon indicating an error.
// Otherwise, it behaves like Window.MsgBox.
func (w *Window) MsgBoxError(primaryText string, secondaryText string) {
func (w *Window) MsgBoxError(primaryText string, secondaryText string) (done chan struct{}) {
if !w.created {
panic("parent window passed to MsgBoxError() before it was created")
}
w.msgBoxError(primaryText, secondaryText)
return w.msgBoxError(primaryText, secondaryText)
}

View File

@ -11,7 +11,7 @@ var (
_messageBox = user32.NewProc("MessageBoxW")
)
func _msgBox(parent *Window, primarytext string, secondarytext string, uType uint32) (result int) {
func _msgBox(parent *Window, primarytext string, secondarytext string, uType uint32) (result chan 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 != "" {
@ -19,36 +19,50 @@ func _msgBox(parent *Window, primarytext string, secondarytext string, uType uin
}
ptext := toUTF16(text)
ptitle := toUTF16(os.Args[0])
ret := make(chan uiret)
defer close(ret)
parenthwnd := _HWND(_NULL)
if parent != nil {
if parent != dialogWindow {
parenthwnd = parent.sysData.hwnd
uType |= _MB_APPLMODAL // only for this window
} else {
uType |= _MB_TASKMODAL // make modal to every window in the program (they're all windows of the uitask, which is a single thread)
}
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))
}
return int(r.ret)
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
}
func msgBox(parent *Window, primarytext string, secondarytext string) {
_msgBox(parent, primarytext, secondarytext, _MB_OK)
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 msgBoxError(parent *Window, primarytext string, secondarytext string) {
_msgBox(parent, primarytext, secondarytext, _MB_OK | _MB_ICONERROR)
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
}

View File

@ -49,7 +49,7 @@ var macCrashTest = flag.Bool("maccrash", false, "attempt crash on Mac OS X on de
func invalidTest(c *Combobox, l *Listbox, s *Stack, g *Grid) {
x := func(what string ) {
if j := recover(); j == nil {
MsgBoxError(nil, "test", fmt.Sprintf("%s: no panic", what))
MsgBoxError("test", fmt.Sprintf("%s: no panic", what))
panic("invalid test fail")
} else {
println("got", j.(error).Error())
@ -143,7 +143,7 @@ func invalidTest(c *Combobox, l *Listbox, s *Stack, g *Grid) {
defer x("Area.SetSize() " + q.msg); a.SetSize(q.x, q.y); panic(nil)
}()
}
MsgBox(nil, "test", "all working as intended")
MsgBox("test", "all working as intended")
}
var invalidBefore = flag.Bool("invalid", false, "run invalid test before opening window")
@ -226,7 +226,7 @@ func areaTest() {
if err != nil { println(err); continue }
a.SetSize(width, height)
case <-modaltest.Clicked:
MsgBox(nil, "Modal Test", "")
MsgBox("Modal Test", "")
}
}
}
@ -260,6 +260,8 @@ func areaboundsTest() {
<-w.Closing
}
var dialogTest = flag.Bool("dialog", false, "add Window.MsgBox() channel test window")
func myMain() {
if *doArea {
areaTest()
@ -284,6 +286,9 @@ func myMain() {
cb2 := NewCombobox("You can't edit me!", "No you can't!", "No you won't!")
e := NewLineEdit("Enter text here too")
l := NewLabel("This is a label")
resetl := func() {
l.SetText("This is a label")
}
b3 := NewButton("List Info")
s3 := NewHorizontalStack(l, b3)
s3.SetStretchy(0)
@ -330,6 +335,17 @@ func myMain() {
ticker := time.Tick(time.Second)
dialog_bMsgBox := NewButton("MsgBox()")
dialog_bMsgBoxError := NewButton("MsgBoxError()")
dialog_win := NewWindow("Dialogs", 200, 200)
if *dialogTest {
dialog_win.Open(NewVerticalStack(
dialog_bMsgBox,
dialog_bMsgBoxError))
}
var dialog_sret chan struct{} = nil
mainloop:
for {
select {
@ -368,7 +384,7 @@ _=curtime
if c.Checked() {
f = MsgBoxError
}
f(nil, "List Info",
f("List Info",
fmt.Sprintf("cb1: %d %q (len %d)\ncb2: %d %q (len %d)\nlb1: %d %q (len %d)\nlb2: %d %q (len %d)",
cb1.SelectedIndex(), cb1.Selection(), cb1.Len(),
cb2.SelectedIndex(), cb2.Selection(), cb2.Len(),
@ -393,8 +409,18 @@ _=curtime
case <-invalidButton.Clicked:
invalidTest(cb1, lb1, nil, nil)
case <-bmsg.Clicked:
MsgBox(nil, "Title Only, no parent", "")
MsgBox(w, "Title and Text", "parent")
MsgBox("Title Only, no parent", "")
MsgBox("Title and Text", "parent")
// dialogs
case <-dialog_bMsgBox.Clicked:
dialog_sret = dialog_win.MsgBox("Message Box", "Dismiss")
l.SetText("DIALOG")
case <-dialog_bMsgBoxError.Clicked:
dialog_sret = dialog_win.MsgBoxError("Message Box", "Dismiss")
l.SetText("DIALOG")
case <-dialog_sret:
dialog_sret = nil
resetl()
}
}
w.Hide()