# bgerror.tcl --
#
-# This file contains a default version of the bgerror procedure. It
-# posts a dialog box with the error message and gives the user a chance
-# to see a more detailed stack trace.
+# Implementation of the bgerror procedure. It posts a dialog box with
+# the error message and gives the user a chance to see a more detailed
+# stack trace, and possible do something more interesting with that
+# trace (like save it to a log). This is adapted from work done by
+# Donal K. Fellows.
#
+# Copyright (c) 1998-2000 by Ajuba Solutions.
+# All rights reserved.
+#
# RCS: @(#) $Id$
-#
-# Copyright (c) 1992-1994 The Regents of the University of California.
-# Copyright (c) 1994-1996 Sun Microsystems, Inc.
-#
-# See the file "license.terms" for information on usage and redistribution
-# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# $Id$
+namespace eval ::tk {
+ namespace eval dialog {
+ namespace eval error {
+ namespace import ::tk::msgcat::*
+ namespace export bgerror
+ option add *ErrorDialog.function.text [mc "Save To Log"] \
+ widgetDefault
+ option add *ErrorDialog.function.command [namespace code SaveToLog]
+ }
+ }
+}
-# bgerror --
+proc ::tk::dialog::error::Return {} {
+ variable button
+
+ .bgerrorDialog.ok configure -state active -relief sunken
+ update idletasks
+ after 100
+ set button 0
+}
+
+proc ::tk::dialog::error::Details {} {
+ set w .bgerrorDialog
+ set caption [option get $w.function text {}]
+ set command [option get $w.function command {}]
+ if { ($caption eq "") || ($command eq "") } {
+ grid forget $w.function
+ }
+ $w.function configure -text $caption -command \
+ "$command [list [.bgerrorDialog.top.info.text get 1.0 end]]"
+ grid $w.top.info - -sticky nsew -padx 3m -pady 3m
+}
+
+proc ::tk::dialog::error::SaveToLog {text} {
+ if { $::tcl_platform(platform) eq "windows" } {
+ set allFiles *.*
+ } else {
+ set allFiles *
+ }
+ set types [list \
+ [list [mc "Log Files"] .log] \
+ [list [mc "Text Files"] .txt] \
+ [list [mc "All Files"] $allFiles] \
+ ]
+ set filename [tk_getSaveFile -title [mc "Select Log File"] \
+ -filetypes $types -defaultextension .log -parent .bgerrorDialog]
+ if {![string length $filename]} {
+ return
+ }
+ set f [open $filename w]
+ puts -nonewline $f $text
+ close $f
+}
+
+proc ::tk::dialog::error::Destroy {w} {
+ if {$w eq ".bgerrorDialog"} {
+ variable button
+ set button -1
+ }
+}
+
+# ::tk::dialog::error::bgerror --
# This is the default version of bgerror.
# It tries to execute tkerror, if that fails it posts a dialog box containing
# the error message and gives the user a chance to ask to see a stack
# Arguments:
# err - The error message.
-proc bgerror err {
+proc ::tk::dialog::error::bgerror err {
global errorInfo tcl_platform
-
- # save errorInfo which would be erased in the catch below otherwise.
- set info $errorInfo ;
-
- # For backward compatibility :
- # Let's try to execute "tkerror" (using catch {tkerror ...}
- # instead of searching it with info procs so the application gets
- # a chance to auto load it using its favorite "unknown" mecanism.
- # (we do the default dialog only if we get a TCL_ERROR (=1) return
- # code from the tkerror trial, other ret codes are passed back
- # to our caller (tcl background error handler) so the called "tkerror"
- # can still use return -code break, to skip remaining messages
- # in the error queue for instance)
-
- set ret [catch {tkerror $err} msg];
+ variable button
+
+ set info $errorInfo
+
+ set ret [catch {::tkerror $err} msg];
if {$ret != 1} {return -code $ret $msg}
# Ok the application's tkerror either failed or was not found
# we use the default dialog then :
- if {$tcl_platform(platform) == "macintosh"} {
- set ok Ok
+ if {($tcl_platform(platform) eq "macintosh")
+ || ([tk windowingsystem] eq "aqua")} {
+ set ok [mc Ok]
+ set messageFont system
+ set textRelief flat
+ set textHilight 0
} else {
- set ok OK
+ set ok [mc OK]
+ set messageFont {Times -18}
+ set textRelief sunken
+ set textHilight 1
}
- set button [tk_dialog .bgerrorDialog "Error in Tcl Script" \
- "Error: $err" error 0 $ok "Skip Messages" "Stack Trace"]
- if {$button == 0} {
- return
- } elseif {$button == 1} {
- return -code break
+
+
+ # Truncate the message if it is too wide (longer than 30 characacters) or
+ # too tall (more than 4 newlines). Truncation occurs at the first point at
+ # which one of those conditions is met.
+ set displayedErr ""
+ set lines 0
+ foreach line [split $err \n] {
+ if { [string length $line] > 30 } {
+ append displayedErr "[string range $line 0 29]..."
+ break
+ }
+ if { $lines > 4 } {
+ append displayedErr "..."
+ break
+ } else {
+ append displayedErr "${line}\n"
+ }
+ incr lines
+ }
+
+ set w .bgerrorDialog
+ set title [mc "Application Error"]
+ set text [mc {Error: %1$s} $err]
+ set buttons [list ok $ok dismiss [mc "Skip Messages"] \
+ function [mc "Details >>"]]
+
+ # 1. Create the top-level window and divide it into top
+ # and bottom parts.
+
+ catch {destroy .bgerrorDialog}
+ toplevel .bgerrorDialog -class ErrorDialog
+ wm title .bgerrorDialog $title
+ wm iconname .bgerrorDialog ErrorDialog
+ wm protocol .bgerrorDialog WM_DELETE_WINDOW { }
+
+ if {($tcl_platform(platform) eq "macintosh")
+ || ([tk windowingsystem] eq "aqua")} {
+ ::tk::unsupported::MacWindowStyle style .bgerrorDialog dBoxProc
}
- set w .bgerrorTrace
- catch {destroy $w}
- toplevel $w -class ErrorTrace
- wm minsize $w 1 1
- wm title $w "Stack Trace for Error"
- wm iconname $w "Stack Trace"
- button $w.ok -text OK -command "destroy $w" -default active
- if {![string compare $tcl_platform(platform) "macintosh"]} {
- text $w.text -relief flat -bd 2 -highlightthickness 0 -setgrid true \
- -yscrollcommand "$w.scroll set" -width 60 -height 20
+ frame .bgerrorDialog.bot
+ frame .bgerrorDialog.top
+ if {[tk windowingsystem] eq "x11"} {
+ .bgerrorDialog.bot configure -relief raised -bd 1
+ .bgerrorDialog.top configure -relief raised -bd 1
+ }
+ pack .bgerrorDialog.bot -side bottom -fill both
+ pack .bgerrorDialog.top -side top -fill both -expand 1
+
+ set W [frame $w.top.info]
+ text $W.text \
+ -bd 2 \
+ -yscrollcommand [list $W.scroll set]\
+ -setgrid true \
+ -width 40 \
+ -height 10 \
+ -state normal \
+ -relief $textRelief \
+ -highlightthickness $textHilight \
+ -wrap char
+
+ scrollbar $W.scroll -relief sunken -command [list $W.text yview]
+ pack $W.scroll -side right -fill y
+ pack $W.text -side left -expand yes -fill both
+ $W.text insert 0.0 "$err\n$info"
+ $W.text mark set insert 0.0
+ bind $W.text <ButtonPress-1> { focus %W }
+ $W.text configure -state disabled
+
+ # 2. Fill the top part with bitmap and message
+
+ # Max-width of message is the width of the screen...
+ set wrapwidth [winfo screenwidth .bgerrorDialog]
+ # ...minus the width of the icon, padding and a fudge factor for
+ # the window manager decorations and aesthetics.
+ set wrapwidth [expr {$wrapwidth-60-[winfo pixels .bgerrorDialog 9m]}]
+ label .bgerrorDialog.msg -justify left -text $text -font $messageFont \
+ -wraplength $wrapwidth
+ if {($tcl_platform(platform) eq "macintosh")
+ || ([tk windowingsystem] eq "aqua")} {
+ # On the Macintosh, use the stop bitmap
+ label .bgerrorDialog.bitmap -bitmap stop
} else {
- text $w.text -relief sunken -bd 2 -yscrollcommand "$w.scroll set" \
- -setgrid true -width 60 -height 20
+ # On other platforms, make the error icon
+ canvas .bgerrorDialog.bitmap -width 32 -height 32 -highlightthickness 0
+ .bgerrorDialog.bitmap create oval 0 0 31 31 -fill red -outline black
+ .bgerrorDialog.bitmap create line 9 9 23 23 -fill white -width 4
+ .bgerrorDialog.bitmap create line 9 23 23 9 -fill white -width 4
}
- scrollbar $w.scroll -relief sunken -command "$w.text yview"
- pack $w.ok -side bottom -padx 3m -pady 2m
- pack $w.scroll -side right -fill y
- pack $w.text -side left -expand yes -fill both
- $w.text insert 0.0 $info
- $w.text mark set insert 0.0
+ grid .bgerrorDialog.bitmap .bgerrorDialog.msg \
+ -in .bgerrorDialog.top \
+ -row 0 \
+ -padx 3m \
+ -pady 3m
+ grid configure .bgerrorDialog.msg -sticky nsw -padx {0 3m}
+ grid rowconfigure .bgerrorDialog.top 1 -weight 1
+ grid columnconfigure .bgerrorDialog.top 1 -weight 1
- bind $w <Return> "destroy $w"
- bind $w.text <Return> "destroy $w; break"
+ # 3. Create a row of buttons at the bottom of the dialog.
- # Center the window on the screen.
+ set i 0
+ foreach {name caption} $buttons {
+ button .bgerrorDialog.$name \
+ -text $caption \
+ -default normal \
+ -command [namespace code "set button $i"]
+ grid .bgerrorDialog.$name \
+ -in .bgerrorDialog.bot \
+ -column $i \
+ -row 0 \
+ -sticky ew \
+ -padx 10
+ grid columnconfigure .bgerrorDialog.bot $i -weight 1
+ # We boost the size of some Mac buttons for l&f
+ if {($tcl_platform(platform) eq "macintosh")
+ || ([tk windowingsystem] eq "aqua")} {
+ if {($name eq "ok") || ($name eq "dismiss")} {
+ grid columnconfigure .bgerrorDialog.bot $i -minsize 79
+ }
+ }
+ incr i
+ }
+ # The "OK" button is the default for this dialog.
+ .bgerrorDialog.ok configure -default active
- wm withdraw $w
+ bind .bgerrorDialog <Return> [namespace code Return]
+ bind .bgerrorDialog <Destroy> [namespace code [list Destroy %W]]
+ .bgerrorDialog.function configure -command [namespace code Details]
+
+ # 6. Withdraw the window, then update all the geometry information
+ # so we know how big it wants to be, then center the window in the
+ # display and de-iconify it.
+
+ wm withdraw .bgerrorDialog
update idletasks
- set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
- - [winfo vrootx [winfo parent $w]]}]
- set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
- - [winfo vrooty [winfo parent $w]]}]
- wm geom $w +$x+$y
- wm deiconify $w
-
- # Be sure to release any grabs that might be present on the
- # screen, since they could make it impossible for the user
- # to interact with the stack trace.
-
- if {[string compare [grab current .] ""]} {
- grab release [grab current .]
+ set parent [winfo parent .bgerrorDialog]
+ set width [winfo reqwidth .bgerrorDialog]
+ set height [winfo reqheight .bgerrorDialog]
+ set x [expr {([winfo screenwidth .bgerrorDialog] - $width )/2 - \
+ [winfo vrootx $parent]}]
+ set y [expr {([winfo screenheight .bgerrorDialog] - $height)/2 - \
+ [winfo vrooty $parent]}]
+ .bgerrorDialog configure -width $width
+ wm geometry .bgerrorDialog +$x+$y
+ wm deiconify .bgerrorDialog
+
+ # 7. Set a grab and claim the focus too.
+
+ set oldFocus [focus]
+ set oldGrab [grab current .bgerrorDialog]
+ if {$oldGrab != ""} {
+ set grabStatus [grab status $oldGrab]
}
-}
+ grab .bgerrorDialog
+ focus .bgerrorDialog.ok
+
+ # 8. Wait for the user to respond, then restore the focus and
+ # return the index of the selected button. Restore the focus
+ # before deleting the window, since otherwise the window manager
+ # may take the focus away so we can't redirect it. Finally,
+ # restore any grab that was in effect.
+ vwait [namespace which -variable button]
+ set copy $button; # Save a copy...
+ catch {focus $oldFocus}
+ catch {destroy .bgerrorDialog}
+ if {$oldGrab ne ""} {
+ if {$grabStatus eq "global"} {
+ grab -global $oldGrab
+ } else {
+ grab $oldGrab
+ }
+ }
+
+ if {$copy == 1} {
+ return -code break
+ }
+}
+namespace eval :: {
+ # Fool the indexer
+ proc bgerror err {}
+ rename bgerror {}
+ namespace import ::tk::dialog::error::bgerror
+}