dialog.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // Copyright 2010 The Walk Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build windows
  5. package walk
  6. import (
  7. "syscall"
  8. "unsafe"
  9. )
  10. import (
  11. "github.com/lxn/win"
  12. )
  13. const (
  14. DlgCmdNone = 0
  15. DlgCmdOK = win.IDOK
  16. DlgCmdCancel = win.IDCANCEL
  17. DlgCmdAbort = win.IDABORT
  18. DlgCmdRetry = win.IDRETRY
  19. DlgCmdIgnore = win.IDIGNORE
  20. DlgCmdYes = win.IDYES
  21. DlgCmdNo = win.IDNO
  22. DlgCmdClose = win.IDCLOSE
  23. DlgCmdHelp = win.IDHELP
  24. DlgCmdTryAgain = win.IDTRYAGAIN
  25. DlgCmdContinue = win.IDCONTINUE
  26. DlgCmdTimeout = win.IDTIMEOUT
  27. )
  28. const dialogWindowClass = `\o/ Walk_Dialog_Class \o/`
  29. func init() {
  30. MustRegisterWindowClass(dialogWindowClass)
  31. }
  32. type dialogish interface {
  33. DefaultButton() *PushButton
  34. CancelButton() *PushButton
  35. }
  36. type Dialog struct {
  37. FormBase
  38. result int
  39. defaultButton *PushButton
  40. cancelButton *PushButton
  41. centerInOwnerWhenRun bool
  42. }
  43. func NewDialog(owner Form) (*Dialog, error) {
  44. return newDialogWithStyle(owner, win.WS_THICKFRAME)
  45. }
  46. func NewDialogWithFixedSize(owner Form) (*Dialog, error) {
  47. return newDialogWithStyle(owner, 0)
  48. }
  49. func newDialogWithStyle(owner Form, style uint32) (*Dialog, error) {
  50. dlg := &Dialog{
  51. FormBase: FormBase{
  52. owner: owner,
  53. },
  54. }
  55. if err := InitWindow(
  56. dlg,
  57. owner,
  58. dialogWindowClass,
  59. win.WS_CAPTION|win.WS_SYSMENU|style,
  60. 0); err != nil {
  61. return nil, err
  62. }
  63. succeeded := false
  64. defer func() {
  65. if !succeeded {
  66. dlg.Dispose()
  67. }
  68. }()
  69. dlg.centerInOwnerWhenRun = owner != nil
  70. // This forces display of focus rectangles, as soon as the user starts to type.
  71. dlg.SendMessage(win.WM_CHANGEUISTATE, win.UIS_INITIALIZE, 0)
  72. dlg.result = DlgCmdNone
  73. succeeded = true
  74. return dlg, nil
  75. }
  76. func (dlg *Dialog) DefaultButton() *PushButton {
  77. return dlg.defaultButton
  78. }
  79. func (dlg *Dialog) SetDefaultButton(button *PushButton) error {
  80. if button != nil && !win.IsChild(dlg.hWnd, button.hWnd) {
  81. return newError("not a descendant of the dialog")
  82. }
  83. succeeded := false
  84. if dlg.defaultButton != nil {
  85. if err := dlg.defaultButton.setAndClearStyleBits(win.BS_PUSHBUTTON, win.BS_DEFPUSHBUTTON); err != nil {
  86. return err
  87. }
  88. defer func() {
  89. if !succeeded {
  90. dlg.defaultButton.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON)
  91. }
  92. }()
  93. }
  94. if button != nil {
  95. if err := button.setAndClearStyleBits(win.BS_DEFPUSHBUTTON, win.BS_PUSHBUTTON); err != nil {
  96. return err
  97. }
  98. }
  99. dlg.defaultButton = button
  100. succeeded = true
  101. return nil
  102. }
  103. func (dlg *Dialog) CancelButton() *PushButton {
  104. return dlg.cancelButton
  105. }
  106. func (dlg *Dialog) SetCancelButton(button *PushButton) error {
  107. if button != nil && !win.IsChild(dlg.hWnd, button.hWnd) {
  108. return newError("not a descendant of the dialog")
  109. }
  110. dlg.cancelButton = button
  111. return nil
  112. }
  113. func (dlg *Dialog) Result() int {
  114. return dlg.result
  115. }
  116. func (dlg *Dialog) Accept() {
  117. dlg.Close(DlgCmdOK)
  118. }
  119. func (dlg *Dialog) Cancel() {
  120. dlg.Close(DlgCmdCancel)
  121. }
  122. func (dlg *Dialog) Close(result int) {
  123. dlg.result = result
  124. dlg.FormBase.Close()
  125. }
  126. func firstFocusableDescendantCallback(hwnd win.HWND, lParam uintptr) uintptr {
  127. widget := windowFromHandle(hwnd)
  128. if widget == nil || !widget.Visible() || !widget.Enabled() {
  129. return 1
  130. }
  131. style := uint(win.GetWindowLong(hwnd, win.GWL_STYLE))
  132. // FIXME: Ugly workaround for NumberEdit
  133. _, isTextSelectable := widget.(textSelectable)
  134. if style&win.WS_TABSTOP > 0 || isTextSelectable {
  135. hwndPtr := (*win.HWND)(unsafe.Pointer(lParam))
  136. *hwndPtr = hwnd
  137. return 0
  138. }
  139. return 1
  140. }
  141. var firstFocusableDescendantCallbackPtr = syscall.NewCallback(firstFocusableDescendantCallback)
  142. func firstFocusableDescendant(container Container) Window {
  143. var hwnd win.HWND
  144. win.EnumChildWindows(container.Handle(), firstFocusableDescendantCallbackPtr, uintptr(unsafe.Pointer(&hwnd)))
  145. return windowFromHandle(hwnd)
  146. }
  147. type textSelectable interface {
  148. SetTextSelection(start, end int)
  149. }
  150. func (dlg *Dialog) focusFirstCandidateDescendant() {
  151. window := firstFocusableDescendant(dlg)
  152. if window == nil {
  153. return
  154. }
  155. if err := window.SetFocus(); err != nil {
  156. return
  157. }
  158. if textSel, ok := window.(textSelectable); ok {
  159. textSel.SetTextSelection(0, -1)
  160. }
  161. }
  162. func (dlg *Dialog) Show() {
  163. if dlg.owner != nil {
  164. var size Size
  165. if layout := dlg.Layout(); layout != nil {
  166. size = layout.MinSize()
  167. min := dlg.MinSize()
  168. size.Width = maxi(size.Width, min.Width)
  169. size.Height = maxi(size.Height, min.Height)
  170. } else {
  171. size = dlg.Size()
  172. }
  173. ob := dlg.owner.Bounds()
  174. if dlg.centerInOwnerWhenRun {
  175. dlg.SetBounds(Rectangle{
  176. ob.X + (ob.Width-size.Width)/2,
  177. ob.Y + (ob.Height-size.Height)/2,
  178. size.Width,
  179. size.Height,
  180. })
  181. }
  182. } else {
  183. dlg.SetBounds(dlg.Bounds())
  184. }
  185. dlg.FormBase.Show()
  186. dlg.focusFirstCandidateDescendant()
  187. }
  188. func (dlg *Dialog) Run() int {
  189. dlg.Show()
  190. if dlg.owner != nil {
  191. dlg.owner.SetEnabled(false)
  192. }
  193. dlg.FormBase.Run()
  194. return dlg.result
  195. }