bgerror.tcl 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # bgerror.tcl --
  2. #
  3. # Implementation of the bgerror procedure. It posts a dialog box with
  4. # the error message and gives the user a chance to see a more detailed
  5. # stack trace, and possible do something more interesting with that
  6. # trace (like save it to a log). This is adapted from work done by
  7. # Donal K. Fellows.
  8. #
  9. # Copyright (c) 1998-2000 by Ajuba Solutions.
  10. # Copyright (c) 2007 by ActiveState Software Inc.
  11. # Copyright (c) 2007 Daniel A. Steffen <das@users.sourceforge.net>
  12. # Copyright (c) 2009 Pat Thoyts <patthoyts@users.sourceforge.net>
  13. namespace eval ::tk::dialog::error {
  14. namespace import -force ::tk::msgcat::*
  15. namespace export bgerror
  16. option add *ErrorDialog.function.text [mc "Save To Log"] \
  17. widgetDefault
  18. option add *ErrorDialog.function.command [namespace code SaveToLog]
  19. option add *ErrorDialog*Label.font TkCaptionFont widgetDefault
  20. if {[tk windowingsystem] eq "aqua"} {
  21. option add *ErrorDialog*background systemAlertBackgroundActive \
  22. widgetDefault
  23. option add *ErrorDialog*info.text.background \
  24. systemTextBackgroundColor widgetDefault
  25. option add *ErrorDialog*Button.highlightBackground \
  26. systemAlertBackgroundActive widgetDefault
  27. }
  28. }
  29. proc ::tk::dialog::error::Return {which code} {
  30. variable button
  31. .bgerrorDialog.$which state {active selected focus}
  32. update idletasks
  33. after 100
  34. set button $code
  35. }
  36. proc ::tk::dialog::error::Details {} {
  37. set w .bgerrorDialog
  38. set caption [option get $w.function text {}]
  39. set command [option get $w.function command {}]
  40. if {($caption eq "") || ($command eq "")} {
  41. grid forget $w.function
  42. }
  43. lappend command [$w.top.info.text get 1.0 end-1c]
  44. $w.function configure -text $caption -command $command
  45. grid $w.top.info - -sticky nsew -padx 3m -pady 3m
  46. }
  47. proc ::tk::dialog::error::SaveToLog {text} {
  48. if {$::tcl_platform(platform) eq "windows"} {
  49. set allFiles *.*
  50. } else {
  51. set allFiles *
  52. }
  53. set types [list \
  54. [list [mc "Log Files"] .log] \
  55. [list [mc "Text Files"] .txt] \
  56. [list [mc "All Files"] $allFiles] \
  57. ]
  58. set filename [tk_getSaveFile -title [mc "Select Log File"] \
  59. -filetypes $types -defaultextension .log -parent .bgerrorDialog]
  60. if {$filename ne {}} {
  61. set f [open $filename w]
  62. puts -nonewline $f $text
  63. close $f
  64. }
  65. return
  66. }
  67. proc ::tk::dialog::error::Destroy {w} {
  68. if {$w eq ".bgerrorDialog"} {
  69. variable button
  70. set button -1
  71. }
  72. }
  73. proc ::tk::dialog::error::DeleteByProtocol {} {
  74. variable button
  75. set button 1
  76. }
  77. proc ::tk::dialog::error::ReturnInDetails w {
  78. bind $w <Return> {}; # Remove this binding
  79. $w invoke
  80. return -code break
  81. }
  82. # ::tk::dialog::error::bgerror --
  83. #
  84. # This is the default version of bgerror.
  85. # It tries to execute tkerror, if that fails it posts a dialog box
  86. # containing the error message and gives the user a chance to ask
  87. # to see a stack trace.
  88. #
  89. # Arguments:
  90. # err - The error message.
  91. #
  92. proc ::tk::dialog::error::bgerror {err {flag 1}} {
  93. global errorInfo
  94. variable button
  95. set info $errorInfo
  96. set ret [catch {::tkerror $err} msg];
  97. if {$ret != 1} {return -code $ret $msg}
  98. # The application's tkerror either failed or was not found
  99. # so we use the default dialog. But on Aqua we cannot display
  100. # the dialog if the background error occurs in an idle task
  101. # being processed inside of [NSView drawRect]. In that case
  102. # we post the dialog as an after task instead.
  103. set windowingsystem [tk windowingsystem]
  104. if {$windowingsystem eq "aqua"} {
  105. if $flag {
  106. set errorInfo $info
  107. after 500 [list bgerror "$err" 0]
  108. return
  109. }
  110. }
  111. set ok [mc OK]
  112. # Truncate the message if it is too wide (>maxLine characters) or
  113. # too tall (>4 lines). Truncation occurs at the first point at
  114. # which one of those conditions is met.
  115. set displayedErr ""
  116. set lines 0
  117. set maxLine 45
  118. foreach line [split $err \n] {
  119. if {[string length $line] > $maxLine} {
  120. append displayedErr "[string range $line 0 $maxLine-3]..."
  121. break
  122. }
  123. if {$lines > 4} {
  124. append displayedErr "..."
  125. break
  126. } else {
  127. append displayedErr "${line}\n"
  128. }
  129. incr lines
  130. }
  131. set title [mc "Application Error"]
  132. set text [mc "Error: %1\$s" $displayedErr]
  133. set buttons [list ok $ok dismiss [mc "Skip Messages"] \
  134. function [mc "Details >>"]]
  135. # 1. Create the top-level window and divide it into top
  136. # and bottom parts.
  137. set dlg .bgerrorDialog
  138. set bg [ttk::style lookup . -background]
  139. destroy $dlg
  140. toplevel $dlg -class ErrorDialog -background $bg
  141. wm withdraw $dlg
  142. wm title $dlg $title
  143. wm iconname $dlg ErrorDialog
  144. wm protocol $dlg WM_DELETE_WINDOW [namespace code DeleteByProtocol]
  145. if {$windowingsystem eq "aqua"} {
  146. ::tk::unsupported::MacWindowStyle style $dlg moveableAlert {}
  147. } elseif {$windowingsystem eq "x11"} {
  148. wm attributes $dlg -type dialog
  149. }
  150. ttk::frame $dlg.bot
  151. ttk::frame $dlg.top
  152. pack $dlg.bot -side bottom -fill both
  153. pack $dlg.top -side top -fill both -expand 1
  154. set W [ttk::frame $dlg.top.info]
  155. text $W.text -setgrid true -height 10 -wrap char \
  156. -yscrollcommand [list $W.scroll set]
  157. if {$windowingsystem ne "aqua"} {
  158. $W.text configure -width 40
  159. }
  160. ttk::scrollbar $W.scroll -command [list $W.text yview]
  161. pack $W.scroll -side right -fill y
  162. pack $W.text -side left -expand yes -fill both
  163. $W.text insert 0.0 "$err\n$info"
  164. $W.text mark set insert 0.0
  165. bind $W.text <Button-1> {focus %W}
  166. $W.text configure -state disabled
  167. # 2. Fill the top part with bitmap and message
  168. # Max-width of message is the width of the screen...
  169. set wrapwidth [winfo screenwidth $dlg]
  170. # ...minus the width of the icon, padding and a fudge factor for
  171. # the window manager decorations and aesthetics.
  172. set wrapwidth [expr {$wrapwidth-60-[winfo pixels $dlg 9m]}]
  173. ttk::label $dlg.msg -justify left -text $text -wraplength $wrapwidth
  174. ttk::label $dlg.bitmap -image ::tk::icons::error
  175. grid $dlg.bitmap $dlg.msg -in $dlg.top -row 0 -padx 3m -pady 3m
  176. grid configure $dlg.bitmap -sticky ne
  177. grid configure $dlg.msg -sticky nsw -padx {0 3m}
  178. grid rowconfigure $dlg.top 1 -weight 1
  179. grid columnconfigure $dlg.top 1 -weight 1
  180. # 3. Create a row of buttons at the bottom of the dialog.
  181. set i 0
  182. foreach {name caption} $buttons {
  183. ttk::button $dlg.$name -text $caption -default normal \
  184. -command [namespace code [list set button $i]]
  185. grid $dlg.$name -in $dlg.bot -column $i -row 0 -sticky ew -padx 10
  186. grid columnconfigure $dlg.bot $i -weight 1
  187. # We boost the size of some Mac buttons for l&f
  188. if {$windowingsystem eq "aqua"} {
  189. if {($name eq "ok") || ($name eq "dismiss")} {
  190. grid columnconfigure $dlg.bot $i -minsize 90
  191. }
  192. grid configure $dlg.$name -pady 7
  193. }
  194. incr i
  195. }
  196. # The "OK" button is the default for this dialog.
  197. $dlg.ok configure -default active
  198. bind $dlg <Return> [namespace code {Return ok 0}]
  199. bind $dlg <Escape> [namespace code {Return dismiss 1}]
  200. bind $dlg <Destroy> [namespace code {Destroy %W}]
  201. bind $dlg.function <Return> [namespace code {ReturnInDetails %W}]
  202. $dlg.function configure -command [namespace code Details]
  203. # 6. Withdraw the window, then update all the geometry information
  204. # so we know how big it wants to be, then center the window in the
  205. # display (Motif style) and de-iconify it.
  206. ::tk::PlaceWindow $dlg
  207. # 7. Set a grab and claim the focus too.
  208. ::tk::SetFocusGrab $dlg $dlg.ok
  209. # 8. Ensure that we are topmost.
  210. raise $dlg
  211. if {[tk windowingsystem] eq "win32"} {
  212. # Place it topmost if we aren't at the top of the stacking
  213. # order to ensure that it's seen
  214. if {[lindex [wm stackorder .] end] ne "$dlg"} {
  215. wm attributes $dlg -topmost 1
  216. }
  217. }
  218. # 9. Wait for the user to respond, then restore the focus and
  219. # return the index of the selected button. Restore the focus
  220. # before deleting the window, since otherwise the window manager
  221. # may take the focus away so we can't redirect it. Finally,
  222. # restore any grab that was in effect.
  223. vwait [namespace which -variable button]
  224. set copy $button; # Save a copy...
  225. ::tk::RestoreFocusGrab $dlg $dlg.ok destroy
  226. if {$copy == 1} {
  227. return -code break
  228. }
  229. }
  230. namespace eval :: {
  231. # Fool the indexer
  232. proc bgerror err {}
  233. rename bgerror {}
  234. namespace import ::tk::dialog::error::bgerror
  235. }