2 # the next line restarts using wish \
6 # This script demonstrates the various widgets provided by Tk, along with many
7 # of the features of the Tk toolkit. This file only contains code to generate
8 # the main window for the application, which invokes individual
9 # demonstrations. The code for the actual demonstrations is contained in
10 # separate ".tcl" files is this directory, which are sourced by this script as
13 # RCS: @(#) $Id: widget,v 1.51 2008/03/11 22:30:17 das Exp $
15 package require Tcl
8.5
16 package require Tk
8.5
17 package require msgcat
20 eval destroy
[winfo child .
]
21 set tk_demoDirectory
[file join [pwd] [file dirname [info
script]]]
22 ::msgcat
::mcload
$tk_demoDirectory
23 namespace import
::msgcat
::mc
24 wm title .
[mc
"Widget Demonstration"]
25 if {[tk windowingsystem
] eq
"x11"} {
26 # This won't work everywhere, but there's no other way in core Tk at the
27 # moment to display a coloured icon.
28 image create photo TclPowered \
29 -file [file join $tk_library images logo64.gif
]
30 wm iconwindow .
[toplevel ._iconWindow
]
31 pack
[label ._iconWindow.i
-image TclPowered
]
32 wm iconname .
[mc
"tkWidgetDemo"]
35 if {"defaultFont" ni
[font names
]} {
36 # TIP #145 defines some standard named fonts
37 if {"TkDefaultFont" in [font names
] && "TkFixedFont" in [font names
]} {
38 # FIX ME: the following technique of cloning the font to copy it works
39 # fine but means that if the system font is changed by Tk
40 # cannot update the copied font. font alias might be useful
41 # here -- or fix the app to use TkDefaultFont etc.
42 font create mainFont
{*}[font configure TkDefaultFont
]
43 font create fixedFont
{*}[font configure TkFixedFont
]
44 font create boldFont
{*}[font configure TkDefaultFont
] -weight bold
45 font create titleFont
{*}[font configure TkDefaultFont
] -weight bold
46 font create statusFont
{*}[font configure TkDefaultFont
]
47 font create varsFont
{*}[font configure TkDefaultFont
]
48 if {[tk windowingsystem
] eq
"aqua"} {
49 font configure titleFont
-size 17
52 font create mainFont
-family Helvetica
-size 12
53 font create fixedFont
-family Courier
-size 10
54 font create boldFont
-family Helvetica
-size 12 -weight bold
55 font create titleFont
-family Helvetica
-size 18 -weight bold
56 font create statusFont
-family Helvetica
-size 10
57 font create varsFont
-family Helvetica
-size 14
64 image create photo
::img
::refresh
-format GIF
-data {
65 R0lGODlhEAAQAJEDAP
///wAAACpnKv
///yH5BAEAAAMALAAAAAAQABAAAAI63IKp
66 xgcPH2ouwgBCw1HIxHCQ4F3hSJKmwZXqWrmWxj7lKJ2dndcon9EBUq
+gz3brVXAR
70 image create photo
::img
::view
-format GIF
-data {
71 R0lGODlhEAAQAKIHAP
///wwMDAAAAMDAwNnZ2SYmJmZmZv
///yH5BAEAAAcALAAA
72 AAAQABAAAANMKLos90
+ASamDRxJCgw9YVnlDOXiQBgRDBRgHKE6sW8QR3doPKK27
73 yg33q
/GIOhdg6OsEJzeZykiBSUcs06e56Xx6np8ScIkFGuhQAgA7
76 image create photo
::img
::delete
-format GIF
-data {
77 R0lGODlhEAAQAIABAIQAAP
///yH5BAEAAAEALAAAAAAQABAAAAIjjI
+pmwAc3HGy
78 PUSvqYpuvWQg40FfSVacBa5nN6JYDI3mzRQAOw
==
81 image create photo
::img
::print
-format GIF
-data {
82 R0lGODlhEAAQALMKAAAAAP
///52VunNkl8C82Yl
+qldBgq
+pyrOzs1fYAP
///wAA
83 AAAAAAAAAAAAAAAAACH5BAEAAAoALAAAAAAQABAAAARGUMlJKwU4AztB
+ODGeUiJ
84 fGLlgeEYmGWQXmx7aXgmAUTv
/74N4EAsGhOJg1DAbDqbwoJ0Sp0KB9isNis0eL
/g
88 # Note that this is run through the message catalog! This is because this is
89 # actually an image of a word.
90 image create photo
::img
::new
-format GIF
-data [mc
{
91 R0lGODlhHgAOALMPALMAANyIiOu7u8dEROaqqvru7sxVVeGZmbgREfXd3b0iItZ3
92 d8IzM9FmZvDMzP
///yH5BAEAAA8ALAAAAAAeAA4AAASa8MlJq7046827WVOCHEkw
93 nANhUgJlEBIABJIwL3K
+4IcUALCHjfbItYZDSgJgkBiYPmBMAUAkkLPKs
/BAyLgM
94 wAQwOAAY2ByCaw4QAFQSoDEePJ6DmU1xInYZTw5nOEFFdgVUelkVDTIMd3AKFGQ1
95 MgI2AwEmQW8APZ0gdRONAks5nhIFVVxdAAkUAS2pAVwFl7ITB4UqHb0XEQA7
98 #----------------------------------------------------------------
99 # The code below create the main window, consisting of a menu bar and a text
100 # widget that explains how to use the program, plus lists all of the demos as
102 #----------------------------------------------------------------
104 menu .menuBar
-tearoff 0
106 if {[tk windowingsystem
] ne
"aqua"} {
107 # This is a tk-internal procedure to make i18n easier
108 ::tk
::AmpMenuArgs .menuBar add cascade
-label [mc
"&File"] \
110 menu .menuBar.
file -tearoff 0
111 ::tk
::AmpMenuArgs .menuBar.
file add
command -label [mc
"&About..."] \
112 -command {tkAboutDialog
} -accelerator [mc
"<F1>"]
113 bind .
<F1
> {tkAboutDialog
}
114 .menuBar.
file add sep
115 if {[string match win
* [tk windowingsystem
]]} {
116 # Windows doesn't usually have a Meta key
117 ::tk
::AmpMenuArgs .menuBar.
file add
command -label [mc
"&Quit"] \
118 -command {exit} -accelerator [mc
"Ctrl+Q"]
119 bind .
<[mc
"Control-q"]> {exit}
121 ::tk
::AmpMenuArgs .menuBar.
file add
command -label [mc
"&Quit"] \
122 -command {exit} -accelerator [mc
"Meta-Q"]
123 bind .
<[mc
"Meta-q"]> {exit}
127 . configure
-menu .menuBar
129 ttk
::frame .statusBar
130 ttk
::label .statusBar.lab
-text " " -anchor w
131 if {[tk windowingsystem
] eq
"aqua"} {
132 ttk
::separator .statusBar.sep
133 pack .statusBar.sep
-side top
-expand yes -fill x
-pady 0
135 pack .statusBar.lab
-side left
-padx 2 -expand yes -fill both
136 if {[tk windowingsystem
] ne
"aqua"} {
137 ttk
::sizegrip .statusBar.foo
138 pack .statusBar.foo
-side left
-padx 2
140 pack .statusBar
-side bottom
-fill x
-pady 2
144 set textheight
[expr {
145 ([winfo screenheight .
] * 0.7) /
146 [font metrics mainFont
-displayof .
-linespace]
150 ttk
::frame .textFrame
151 scrollbar .s
-orient vertical
-command {.t yview
} -takefocus 1
152 pack .s
-in .textFrame
-side right
-fill y
153 text .t
-yscrollcommand {.s
set} -wrap word
-width 70 -height $textheight \
154 -font mainFont
-setgrid 1 -highlightthickness 0 \
155 -padx 4 -pady 2 -takefocus 0
156 pack .t
-in .textFrame
-expand y
-fill both
-padx 1
157 pack .textFrame
-expand yes -fill both
158 if {[tk windowingsystem
] eq
"aqua"} {
159 pack configure .statusBar.lab
-padx {10 18} -pady {4 6}
160 pack configure .statusBar
-pady 0
161 .t configure
-padx 10 -pady 0
164 # Create a bunch of tags to use in the text widget, such as those for section
165 # titles and demo descriptions. Also define the bindings for tags.
167 .t tag configure title
-font titleFont
168 .t tag configure subtitle
-font titleFont
169 .t tag configure bold
-font boldFont
170 if {[tk windowingsystem
] eq
"aqua"} {
171 .t tag configure title
-spacing1 8
172 .t tag configure subtitle
-spacing3 3
175 # We put some "space" characters to the left and right of each demo
176 # description so that the descriptions are highlighted only when the mouse
177 # cursor is right over them (but not when the cursor is to their left or
180 .t tag configure demospace
-lmargin1 1c
-lmargin2 1c
182 if {[winfo depth .
] == 1} {
183 .t tag configure demo
-lmargin1 1c
-lmargin2 1c \
185 .t tag configure visited
-lmargin1 1c
-lmargin2 1c \
187 .t tag configure hot
-background black
-foreground white
189 .t tag configure demo
-lmargin1 1c
-lmargin2 1c \
190 -foreground blue
-underline 1
191 .t tag configure visited
-lmargin1 1c
-lmargin2 1c \
192 -foreground #303080 -underline 1
193 .t tag configure hot
-foreground red
-underline 1
195 .t tag
bind demo
<ButtonRelease-1
> {
196 invoke
[.t index
{@
%x
,%y
}]
199 .t tag
bind demo
<Enter
> {
200 set lastLine
[.t index
{@
%x
,%y linestart
}]
201 .t tag add hot
"$lastLine +1 chars" "$lastLine lineend -1 chars"
202 .t config
-cursor hand2
203 showStatus
[.t index
{@
%x
,%y
}]
205 .t tag
bind demo
<Leave
> {
206 .t tag remove hot
1.0 end
207 .t config
-cursor xterm
208 .statusBar.lab config
-text ""
210 .t tag
bind demo
<Motion
> {
211 set newLine
[.t index
{@
%x
,%y linestart
}]
212 if {$newLine ne
$lastLine} {
213 .t tag remove hot
1.0 end
214 set lastLine
$newLine
216 set tags
[.t tag names
{@
%x
,%y
}]
217 set i
[lsearch
-glob $tags demo-
*]
219 .t tag add hot
"$lastLine +1 chars" "$lastLine lineend -1 chars"
222 showStatus
[.t index
{@
%x
,%y
}]
225 ##############################################################################
226 # Create the text for the text widget.
228 # addFormattedText --
230 # Add formatted text (but not hypertext) to the text widget after first
231 # passing it through the message catalog to allow for localization.
232 # Lines starting with @@ are formatting directives (insert title, insert
233 # demo hyperlink, begin newline, or change style) and all other lines
234 # are literal strings to be inserted. Substitutions are performed,
235 # allowing processing pieces through the message catalog. Blank lines
238 proc addFormattedText
{formattedText
} {
243 foreach line
[split $formattedText \n] {
244 set line
[string trim
$line]
248 if {[string match @@
* $line]} {
249 set data
[string range
$line 2 end
]
250 set key
[lindex
$data 0]
251 set values
[lrange
$data 1 end
]
252 switch
-exact -- $key {
254 .t insert end
[mc
$values]\n title
\n normal
257 .t insert end
\n $style
261 .t insert end
"\n" {} [mc
$values] subtitle \
266 set description
[lassign
$values name
]
267 .t insert end
"[incr demoCount]. [mc $description]" \
268 [list demo demo-
$name]
270 .t image create end
-image ::img
::new
-padx 5
273 .t insert end
" \n " demospace
285 .t insert end
" " $style
288 .t insert end
[mc
$line] $style
293 @@title Tk Widget Demonstrations
295 This application provides a front end
for several short scripts
296 that demonstrate what you can
do with Tk widgets. Each of the
297 numbered lines below describes a demonstration
; you can click on
298 it to invoke the demonstration. Once the demonstration window
299 appears
, you can click the
303 button to see the Tcl
/Tk code that created the demonstration. If
304 you wish
, you can edit the code and click the
308 button
in the code window to reinvoke the demonstration with the
312 @@subtitle Labels
, buttons
, checkbuttons
, and radiobuttons
313 @@demo label Labels
(text and bitmaps
)
314 @@demo unicodeout Labels and UNICODE text
315 @@demo button Buttons
316 @@demo check Check-buttons
(select any of a group
)
317 @@demo radio Radio-buttons
(select one of a group
)
318 @@demo puzzle A
15-puzzle game made out of buttons
319 @@demo icon Iconic buttons that use bitmaps
320 @@demo image1 Two labels displaying images
321 @@demo image2 A simple user interface
for viewing images
322 @@demo labelframe Labelled frames
324 @@demo ttkbut The simple Themed Tk widgets
326 @@subtitle Listboxes and Trees
327 @@demo states The
50 states
328 @@demo colors Colors
: change the color scheme
for the application
329 @@demo sayings A collection of famous and infamous sayings
331 @@demo mclist A multi-column list of countries
333 @@demo tree A directory browser tree
335 @@subtitle Entries
, Spin-boxes and Combo-boxes
336 @@demo entry1 Entries without scrollbars
337 @@demo entry2 Entries with scrollbars
338 @@demo entry3 Validated entries and password fields
339 @@demo spin Spin-boxes
341 @@demo combo Combo-boxes
342 @@demo form Simple Rolodex-like form
345 @@demo text Basic editable text
346 @@demo style Text display styles
347 @@demo
bind Hypertext
(tag bindings
)
348 @@demo twind A text widget with embedded windows and other features
349 @@demo search A search tool built with a text widget
351 @@demo textpeer Peering text widgets
354 @@demo items The canvas item types
355 @@demo plot A simple
2-D plot
356 @@demo ctext Text items
in canvases
357 @@demo arrow An editor
for arrowheads on canvas lines
358 @@demo ruler A ruler with adjustable tab stops
359 @@demo floor A building floor plan
360 @@demo cscroll A simple scrollable canvas
362 @@demo knightstour A Knight
's tour of the chess board
364 @@subtitle Scales and Progress Bars
365 @@demo hscale Horizontal scale
366 @@demo vscale Vertical scale
368 @@demo ttkprogress Progress bar
370 @@subtitle Paned Windows and Notebooks
371 @@demo paned1 Horizontal paned window
372 @@demo paned2 Vertical paned window
374 @@demo ttkpane Themed nested panes
376 @@demo ttknote Notebook widget
378 @@subtitle Menus and Toolbars
379 @@demo menu Menus and cascades (sub-menus)
380 @@demo menubu Menu-buttons
382 @@demo ttkmenu Themed menu buttons
384 @@demo toolbar Themed toolbar
386 @@subtitle Common Dialogs
387 @@demo msgbox Message boxes
388 @@demo filebox File selection dialog
389 @@demo clrpick Color picker
393 @@demo anilabel Animated labels
395 @@demo aniwave Animated wave
397 @@demo pendulum Pendulum simulation
399 @@demo goldberg A celebration of Rube Goldberg
401 @@subtitle Miscellaneous
402 @@demo bitmap The built-in bitmaps
403 @@demo dialog1 A dialog box with a local grab
404 @@demo dialog2 A dialog box with a global grab
407 ##############################################################################
409 .t configure -state disabled
413 # Add "See Code" and "Dismiss" button frame, with optional "See Vars"
416 # w - The name of the frame to use.
418 proc addSeeDismiss {w show {vars {}} {extra {}}} {
419 ## See Code / Dismiss buttons
421 ttk::separator $w.sep
422 #ttk::frame $w.sep -height 2 -relief sunken
423 grid $w.sep -columnspan 4 -row 0 -sticky ew -pady 2
424 ttk::button $w.dismiss -text [mc "Dismiss"] \
425 -image ::img::delete -compound left \
426 -command [list destroy [winfo toplevel $w]]
427 ttk::button $w.code -text [mc "See Code"] \
428 -image ::img::view -compound left \
429 -command [list showCode $show]
430 set buttons [list x $w.code $w.dismiss]
431 if {[llength $vars]} {
432 ttk::button $w.vars -text [mc "See Variables"] \
433 -image ::img::view -compound left \
434 -command [concat [list showVars $w.dialog] $vars]
435 set buttons [linsert $buttons 1 $w.vars]
438 set buttons [linsert $buttons 1 [uplevel 1 $extra]]
440 grid {*}$buttons -padx 4 -pady 4
441 grid columnconfigure $w 0 -weight 1
442 if {[tk windowingsystem] eq "aqua"} {
443 foreach b [lrange $buttons 1 end] {$b configure -takefocus 0}
444 grid configure $w.sep -pady 0
445 grid configure {*}$buttons -pady {10 12}
446 grid configure [lindex $buttons 1] -padx {16 4}
447 grid configure [lindex $buttons end] -padx {4 18}
453 # This procedure is invoked by most of the demos to position a new demo
457 # w - The name of the window to position.
459 proc positionWindow w {
460 wm geometry $w +300+300
464 # Displays the values of one or more variables in a window, and updates the
465 # display whenever any of the variables changes.
468 # w - Name of new window to create for display.
469 # args - Any number of names of variables.
471 proc showVars {w args} {
474 wm title $w [mc "Variable values"]
476 set b [ttk::frame $w.frame]
478 set f [ttk::labelframe $b.title -text [mc "Variable values:"]]
480 ttk::label $f.n$var -text "$var:" -anchor w
481 ttk::label $f.v$var -textvariable $var -anchor w
482 grid $f.n$var $f.v$var -padx 2 -pady 2 -sticky w
484 ttk::button $b.ok -text [mc "OK"] \
485 -command [list destroy $w] -default active
486 bind $w <Return> [list $b.ok invoke]
487 bind $w <Escape> [list $b.ok invoke]
489 grid $f -sticky news -padx 4
490 grid $b.ok -sticky e -padx 4 -pady {6 4}
491 if {[tk windowingsystem] eq "aqua"} {
492 $b.ok configure -takefocus 0
493 grid configure $b.ok -pady {10 12} -padx {16 18}
494 grid configure $f -padx 10 -pady {10 0}
496 grid columnconfig $f 1 -weight 1
497 grid rowconfigure $f 100 -weight 1
498 grid columnconfig $b 0 -weight 1
499 grid rowconfigure $b 0 -weight 1
500 grid columnconfig $w 0 -weight 1
501 grid rowconfigure $w 0 -weight 1
505 # This procedure is called when the user clicks on a demo description. It is
506 # responsible for invoking the demonstration.
509 # index - The index of the character that the user clicked on.
512 global tk_demoDirectory
513 set tags [.t tag names $index]
514 set i [lsearch -glob $tags demo-*]
518 set cursor [.t cget -cursor]
519 .t configure -cursor watch
521 set demo [string range [lindex $tags $i] 5 end]
522 uplevel 1 [list source [file join $tk_demoDirectory $demo.tcl]]
524 .t configure -cursor $cursor
526 .t tag add visited "$index linestart +1 chars" "$index lineend -1 chars"
531 # Show the name of the demo program in the status bar. This procedure is
532 # called when the user moves the cursor over a demo description.
534 proc showStatus index {
535 set tags [.t tag names $index]
536 set i [lsearch -glob $tags demo-*]
537 set cursor [.t cget -cursor]
539 .statusBar.lab config -text " "
542 set demo [string range [lindex $tags $i] 5 end]
543 .statusBar.lab config -text [mc "Run the \"%s\" sample program" $demo]
546 if {$cursor ne $newcursor} {
547 .t config -cursor $newcursor
554 # w - Name of text widget containing code to eval
556 proc evalShowCode {w} {
557 set code [$w get 1.0 end-1c]
562 # This procedure creates a toplevel window that displays the code for a
563 # demonstration and allows it to be edited and reinvoked.
566 # w - The name of the demonstration's window
, which can be used to
567 # derive the name of the file containing its code.
570 global tk_demoDirectory
571 set file [string range
$w 1 end
].tcl
573 if {![winfo exists
$top]} {
577 set text
[text
$t.text
-font fixedFont
-height 24 -wrap word \
578 -xscrollcommand [list
$t.xscroll
set] \
579 -yscrollcommand [list
$t.yscroll
set] \
580 -setgrid 1 -highlightthickness 0 -pady 2 -padx 3]
581 scrollbar
$t.xscroll
-command [list
$t.text xview
] -orient horizontal
582 scrollbar
$t.yscroll
-command [list
$t.text yview
] -orient vertical
584 grid
$t.text
$t.yscroll
-sticky news
586 grid rowconfigure
$t 0 -weight 1
587 grid columnconfig
$t 0 -weight 1
589 set btns
[ttk
::frame
$top.btns
]
590 ttk
::separator
$btns.sep
591 grid
$btns.sep
-columnspan 4 -row 0 -sticky ew
-pady 2
592 ttk
::button
$btns.dismiss
-text [mc
"Dismiss"] \
593 -default active
-command [list destroy
$top] \
594 -image ::img
::delete
-compound left
595 ttk
::button
$btns.print
-text [mc
"Print Code"] \
596 -command [list printCode
$text $file] \
597 -image ::img
::print
-compound left
598 ttk
::button
$btns.rerun
-text [mc
"Rerun Demo"] \
599 -command [list evalShowCode
$text] \
600 -image ::img
::refresh
-compound left
601 set buttons
[list x
$btns.rerun
$btns.print
$btns.dismiss
]
602 grid
{*}$buttons -padx 4 -pady 4
603 grid columnconfigure
$btns 0 -weight 1
604 if {[tk windowingsystem
] eq
"aqua"} {
605 foreach b
[lrange
$buttons 1 end
] {$b configure
-takefocus 0}
606 grid configure
$btns.sep
-pady 0
607 grid configure
{*}$buttons -pady {10 12}
608 grid configure
[lindex
$buttons 1] -padx {16 4}
609 grid configure
[lindex
$buttons end
] -padx {4 18}
612 grid
$btns -sticky ew
613 grid rowconfigure
$top 0 -weight 1
614 grid columnconfig
$top 0 -weight 1
617 if {[winfo class
%W
] ne
"Text"} { .code.btns.dismiss invoke
}
619 bind $top <Escape
> [bind $top <Return
>]
624 wm title
$top [mc
"Demo code: %s" [file join $tk_demoDirectory $file]]
625 wm iconname
$top $file
626 set id
[open
[file join $tk_demoDirectory $file]]
627 $top.f.text delete
1.0 end
628 $top.f.text insert
1.0 [read $id]
629 $top.f.text mark
set insert
1.0
634 # Prints the source code currently displayed in the See Code dialog. Much
635 # thanks to Arjen Markus for this.
638 # w - Name of text widget containing code to print
639 # file - Name of the original file (implicitly for title)
641 proc printCode
{w
file} {
642 set code
[$w get
1.0 end-1c
]
645 if {[info exists
::env
(HOME
)]} {
646 set dir
"$::env(HOME)"
648 if {[info exists
::env
(TMP
)]} {
651 if {[info exists
::env
(TEMP
)]} {
655 set filename
[file join $dir "tkdemo-$file"]
656 set outfile
[open
$filename "w"]
660 switch
-- $
::tcl_platform
(platform
) {
662 if {[catch
{exec lp -c $filename} msg
]} {
663 tk_messageBox
-title "Print spooling failure" \
664 -message "Print spooling probably failed: $msg"
668 if {[catch
{PrintTextWin32
$filename} msg
]} {
669 tk_messageBox
-title "Print spooling failure" \
670 -message "Print spooling probably failed: $msg"
674 tk_messageBox
-title "Operation not Implemented" \
675 -message "Wow! Unknown platform: $::tcl_platform(platform)"
680 # Be careful to throw away the temporary file in a gentle manner ...
682 if {[file exists
$filename]} {
683 catch
{file delete
$filename}
688 # Print a file under Windows using all the "intelligence" necessary
691 # filename - Name of the file
694 # Taken from the Wiki page by Keith Vetter, "Printing text files under
697 # Do not execute the command in the background: that way we can dispose of the
700 proc PrintTextWin32
{filename
} {
701 package require registry
702 set app
[auto_execok notepad.exe
]
703 set pcmd
"$app /p %1"
705 set app
[registry get
{HKEY_CLASSES_ROOT\.txt
} {}]
706 set pcmd
[registry get \
707 {HKEY_CLASSES_ROOT
\\$app\\shell
\\print
\\command} {}]
710 regsub
-all {%1} $pcmd $filename pcmd
713 regsub
-all {\\} $pcmd {\\\\} pcmd
714 set command "[auto_execok start] /min $pcmd"
720 # Pops up a message box with an "about" message
722 proc tkAboutDialog
{} {
723 tk_messageBox
-icon info
-type ok
-title [mc
"About Widget Demo"] \
724 -message [mc
"Tk widget demonstration application"] -detail \
725 "[mc {Copyright (c) %s} {1996-1997 Sun Microsystems, Inc.}]
726 [mc {Copyright (c) %s} {1997-2000 Ajuba Solutions, Inc.}]
727 [mc {Copyright (c) %s} {2001-2007 Donal K. Fellows}]
728 [mc {Copyright (c) %s} {2002-2007 Daniel A. Steffen}]"