wm withdraw .
set ::tool_name setuptool
# Copyright 1999-2004,2008-2010,2013 BitMover, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Platform specific setup for tcl scripts
# Copyright (c) 1999 Andrew Chang
# %W% %@%

proc bk_initPlatform {} \
{
	global	tcl_platform dev_null tmp_dir wish sdiffw file_rev
	global	file_start_stop file_stop line_rev keytmp file_old_new
	global 	bk_fs env 

	if [catch {wm withdraw .} err] {
		puts "DISPLAY variable not set correctly or not running X"
		exit 1
	}

	set sdiffw [list "bk" "ndiff" "--sdiff=1" "--ignore-trailing-cr"]
	set dev_null "/dev/null"
	set wish "wish"
	set tmp_dir  "/tmp"
	if {[info exists env(TMPDIR)] && [file writable $env(TMPDIR)]} {
		set tmp_dir $env(TMPDIR)
	}
	set keytmp "/var/bitkeeper"

	# Stuff related to the bk field seperator: ^A
	set bk_fs |
	set file_old_new {(.*)\|(.*)\|(.*)}
	set line_rev {([^\|]*)\|(.*)}

	set file_start_stop {(.*)@(.*)\.\.(.*)}
	set file_stop {(.*)@([0-9.]+$)}
	set file_rev {(.*)@([0-9].*)}
	set env(BK_GUI) "YES"
	catch { unset env(BK_NO_GUI_PROMPT) }

	# Determine the bk icon to associate with toplevel windows. If
	# we can't find the icon, don't set the global variable. This
	# way code that needs the icon can check for the existence of
	# the variable rather than checking the filesystem.
	set f [file join [exec bk bin] bk.xbm]
	if {[file exists $f]} {
		set ::wmicon $f
		# N.B. on windows, wm iconbitmap supports a -default option
		# that is not available on unix. Bummer. 
		catch {wm iconbitmap . @$::wmicon}
	}
}
# Copyright 2000-2013,2016 BitMover, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

proc gc {var {newValue ""}} \
{
	global	gc app

	## If we got a config var with no APP. prefix, check to see if
	## we have an app-specific config option first and force them
	## to use that if we do.
	if {[info exists app]
	    && ![string match "*.*" $var]
	    && [info exists gc($app.$var)]} {
		set var $app.$var
	}
	if {[llength [info level 0]] == 3} { set gc($var) $newValue }
	return $gc($var)
}

## An array of deprecated options and the warning to display to the
## user if they are found to be using said option.
array set deprecated {
"rev.showHistory"

"The rev.showHistory config option has been removed.
Please see 'bk help config-gui' for rev.showRevs and
rev.showCsetRevs for new options."
}

proc warn_deprecated_options {app} \
{
	global	gc deprecated

	foreach {opt desc} [array get deprecated $app.*] {
		if {![info exists gc($opt)]} { continue }
		puts ""
		foreach line [split $desc \n] {
			puts [string trim $line]
		}
	}
}

proc getConfig {prog} \
{
	global gc app env usergc

	# this causes variables like _RED_, _WHITE_, to exist in this proc
	defineSymbolicColors

	set app $prog

	option add *Label.borderWidth 1 100
	option add *Button.borderWidth 1 100
	option add *Menubutton.borderWidth 1 100
	option add *Menu.tearOff 0 widgetDefault
	option add *Menu.borderWidth 1 widgetDefault
	option add *Menu.highlightThickness 1 widgetDefault
	option add *Menu.activeBorderWidth 1 widgetDefault

	initFonts $app _d

	# colorscheme

	set _d(classicColors) 1		;# default to the old color scheme.

	set _d(tabwidth) 8		;# default width of a tab
	set _d(backup) ""		;# Make backups in ciedit: XXX NOTDOC 
	set _d(buttonColor) $SYSTEMBUTTONFACE	;# menu buttons
	set _d(diffOpts) ""		;# options to pass to diff
	set _d(diffHeight) 30		;# height of a diff window
	set _d(diffWidth) 65		;# width of side by side diffs
	set _d(geometry) ""		;# default size/location
	set _d(listBG) $GRAY91		;# topics / lists background
	set _d(mergeHeight) 24		;# height of a merge window
	set _d(mergeWidth) 80		;# width of a merge window
	set _d(newColor) #c4d7c5	;# color of new revision/diff
	set _d(noticeColor) #dbdfe6	;# messages, warnings
	set _d(oldColor) $GRAY88	;# color of old revision/diff
	set _d(searchColor) $ORANGE	;# highlight for search matches
	set _d(selectColor) $LIGHTBLUE	;# current file/item/topic
	set _d(statusColor) $LIGHTBLUE	;# various status windows
	set _d(minsize) 300		;# won't remember geometry if smaller
					 # than this width or height
	#XXX: Not documented yet
	set _d(logoBG) $WHITE		;# background for widget with logo
	set _d(selectBG) $NAVY		;# useful for highlighting text
	set _d(selectFG) $WHITE		;# useful for highlighting text
	set _d(altColumnBG) $BEIGE		;# alternate column background
	set _d(infoColor) $LIGHTYELLOW	;# color of info line in difflib
	set _d(textBG) $WHITE			;# text background
	set _d(textFG) $BLACK			;# text color
	set _d(scrollColor) $GRAY85		;# scrollbar bars
	set _d(troughColor) $LIGHTBLUE	;# scrollbar troughs
	set _d(warnColor) yellow		;# error messages
	set _d(emptyBG) black
	set _d(sameBG) white
	set _d(spaceBG) white
	set _d(changedBG) gray

	set _d(quit)	Control-q	;# binding to exit tool
	set _d(compat_4x) 0		;# maintain compatibility with 4x
					;# quirky bindings
	set _d(highlightOld) $YELLOW2	;# subline highlight color for old
	set _d(highlightNew) $YELLOW2	;# subline highlight color for new
	set _d(highlightsp) $ORANGE	;# subline highlight color in diffs
	set _d(topMargin) 2		;# top margin for diffs in a diff view
	set _d(diffColor) #ededed	;# color of diff lines
	set _d(activeDiffColor) $BKGREEN1 ;# active diff color
	set _d(activeOldColor) $_d(activeDiffColor)
	set _d(activeNewColor) $_d(activeDiffColor)
	set _d(newFont) bkFixedFont
	set _d(oldFont) bkFixedFont
	set _d(activeOldFont) bkFixedFont
	set _d(activeNewFont) bkFixedFont

	set _d(bug.popupBG) $BLUE
	set _d(support.popupBG) $BLUE
	set _d(ci.iconBG) $BKPALEOLIVE	;# background of some icons
	set _d(ci.csetIconBG) $BKBLUE1	;# background of some icons
	set _d(ci.quitSaveBG) $BKSLATEBLUE1	;# "quit but save" button
	set _d(ci.quitSaveActiveBG) $BKSLATEBLUE2	;# "quit but save" button
	set _d(ci.listBG) white	
	set _d(selectColor) #f0f0f0	;# current file/item/topic
	set _d(ci.saveBG) $GRAY94		;# background of save dialog
	set _d(ci.quitNosaveBG) $RED	;# "don't save" button
	set _d(ci.quitNosaveActiveBG) $WHITE ;# "don't save" button
	set _d(ci.dimFG) $GRAY50		;# dimmed text
	set _d(ci.progressBG) $WHITE		;# background of progress bar
	set _d(ci.progressColor) $BKSLATEBLUE1 ;# color of progress bar
	set _d(ci.editHeight) 30	;# editor height
	set _d(ci.editWidth) 80		;# editor width
	set _d(ci.excludeColor) $RED	;# color of the exclude X
	set _d(ci.editor) ciedit	;# editor: ciedit=builtin, else in xterm
	set _d(ci.display_bytes) 8192	;# number of bytes to show in new files
	set _d(ci.diffHeight) 30	;# number of lines in the diff window
	set _d(ci.rescan) 0		;# Do a second scan to see if anything
					;# changed. Values 0 - off 1 - on
	set _d(ci.csetBG) $GRAY94

	set _d(cset.listHeight) 12
	set _d(cset.annotation) ""   ;# annotation options (eg: "-aum")
	set _d(cset.doubleclick) 100 ;# XXX: NOTDOC

	set _d(diff.diffHeight) 50
	set _d(diff.searchColor) $LIGHTBLUE	;# highlight for search matches

	set _d(fm.redoBG) $PINK
	set _d(fm3.conflictBG) gray		;# Color for conflict message
	set _d(fm3.unmergeBG) gray	;# Color for unmerged message
	set _d(fm3.annotate) 1		;# show annotations
	set _d(fm3.comments) 1		;# show comments window
	set _d(fm3.escapeButtonFG) $YELLOW	;# foreground of escape button
	set _d(fm3.escapeButtonBG) $BLACK	;# background of escape button
	set _d(fm3.firstDiff) minus
	set _d(fm3.lastDiff) plus
	set _d(fm3.mergeColor) #b4b6cb	;# color of merge choices in merge win
	set _d(fm3.handColor) $_d(fm3.mergeColor) ;# color of hand merged choices
	set _d(fm3.nextConflict) braceright
	set _d(fm3.nextDiff) bracketright
	set _d(fm3.prevConflict) braceleft
	set _d(fm3.prevDiff) bracketleft
	set _d(fm3.sameColor) #efefef	;# color of unchanged line
	set _d(fm3.showEscapeButton) 1	;# show escape button?
	set _d(fm3.spaceColor) $BLACK	;# color of spacer lines
	set _d(fm3.toggleGCA) x		;# key to toggle GCA info
	set _d(fm3.toggleAnnotations) z	;# key to toggle annotations
	set _d(fm3.undo) u
	set _d(fm3.animateScrolling) 1	;# Use an animated scrolling effect
					;# when jumping conflict diff blocks.

	set _d(help.linkColor) $BLUE	;# hyperlinks
	set _d(help.topicsColor) $ORANGE	;# highlight for topic search matches
	set _d(help.height) 50		;# number of rows to display
	set _d(help.width) 79		;# number of columns to display
	set _d(help.helptext) ""	;# -f<helptextfile> - undocumented
	set _d(help.exact) 0		;# helpsearch, allows partial matches
	set _d(help.scrollbars) RR	;# sides for each scrollbar

	set _d(rename.listHeight) 8

	# N.B. 500ms is the hard-coded constant in tk used to detect
	# double clicks. We need a number slightly larger than that. The
	# book Practical Programming in Tcl/Tk, 4th ed. recommends 600. This
	# results in a noticable delay before a single-click is processed 
	# but there really is no other solution when a double-click must
	# override a single click, or a single-click action will take more
	# than 500ms and therefore preventing double-clicks from ever being
	# noticed.
	set _d(rev.doubleclick) 600  ;# XXX: NOTDOC
	set _d(rev.sashBG) $BLACK
	set _d(rev.canvasBG) #9fb6b8	  	;# graph background
	set _d(rev.commentBG) $LIGHTBLUE	;# background of comment text
	set _d(rev.arrowColor) $DARKBLUE	;# arrow color
	set _d(rev.mergeOutline) $DARKBLUE	;# merge rev outlines
	set _d(rev.revOutline) $DARKBLUE	;# regular rev outlines
	set _d(rev.revColor) $BKCADETBLUE	;# unselected box fills
	set _d(rev.localColor) $GREEN	;# local node (for resolve)
	set _d(rev.remoteColor) $RED	;# remote node (for resolve)
	set _d(rev.gcaColor) $WHITE		;# gca node (for resolve)
	set _d(rev.tagOutline) $YELLOW	;# outline of tagged nodes
	set _d(rev.badColor) $RED		;# color for "bad" revision numbers
	set _d(rev.selectColor) $BKSTEELBLUE ;# highlight color for selected tag
	set _d(rev.dateLineColor) $LIGHTBLUE ;# line that separates dates
	set _d(rev.dateColor) $BKBLACK1	;# dates at the bottom of graph
	set _d(rev.commentHeight) 5       ;# height of comment text widget
	set _d(rev.textWidth) 92	  ;# width of text windows
	set _d(rev.textHeight) 30	  ;# height of lower window
	set _d(rev.showRevs) 250	  ;# Num of revs to show in graph 
	set _d(rev.showCsetRevs) 50	  ;# Num of revs to show for a cset
	# XXX: not documented yet
	set _d(rev.savehistory) 5	  ;# Max # of files to save in file list
	set _d(rev.hlineColor) $WHITE	;# Color of highlight lines XXX:NOTDOC
	set _d(rev.annotate) "-Aur"	  ;# Options given to annotate

	set _d(setup.stripeColor) $BLUE ;# color of horizontal separator
	set _d(setup.mandatoryColor) $BKSLATEGRAY1 ;# mandatory fields
	set _d(bug.mandatoryColor) $BKSLATEGRAY1 ;# mandatory fields
	set _d(support.mandatoryColor) $BKSLATEGRAY1 ;# mandatory fields
	set _d(entryColor) $WHITE	   ;# Color of input fields

	set _d(search.width)		15
	set _d(search.buttonWidth)	15

	set _d(ignoreWhitespace)	0
	set _d(ignoreAllWhitespace)	0

	set _d(hlPercent)	0.5
	set _d(chopPercent)	0.5

	set _d(windows) 0
	set _d(aqua) 0
	set _d(x11) 0

	switch -exact -- [tk windowingsystem] {
	    win32 {
		set _d(windows) 1
		set _d(handCursor) "hand2"
		set _d(cset.leftWidth) 40
		set _d(cset.rightWidth) 80
		set _d(ci.filesHeight) 8
		set _d(ci.commentsHeight) 7	;# height of comment window
		set _d(buttonColor) $SYSTEMBUTTONFACE	;# menu buttons
		set _d(BG) $SYSTEMBUTTONFACE		;# default background
		# usable space
		set _d(padTop)		0
		set _d(padBottom)	28		; # someday, we'll 
							  # need to figure
							  # out the size
							  # of the taskbar
		set _d(padRight)	0
		set _d(padLeft)		0
		set _d(titlebarHeight)	20
		set rcfile [exec bk dotbk _bkgui config-gui]
	    } 
	    aqua {
		set _d(aqua) 1
		set _d(handCursor) "pointinghand"
		set _d(cset.leftWidth) 40
		set _d(cset.rightWidth) 80
		set _d(search.width) 4
		set _d(search.buttonWidth) 12
		set _d(ci.filesHeight) 8
		set _d(ci.commentsHeight) 7	;# height of comment window
		set _d(buttonColor) $SYSTEMBUTTONFACE	;# menu buttons
		set _d(BG) $SYSTEMBUTTONFACE		;# default background
		set _d(listBG) $WHITE
		#usable space
		set _d(padTop)		22              ; # someday we'll need
							  # to compute the
							  # size of the menubar
		set _d(padBottom)	0
		set _d(padRight)	0
		set _d(padLeft)		0
		set _d(titlebarHeight)	22
		set rcfile [exec bk dotbk .bkgui config-gui]
	    }
	    x11 {
		set _d(x11) 1

		option add *Scrollbar.borderWidth 1 100
		set _d(handCursor) "hand2"
		set _d(cset.leftWidth) 55
		set _d(cset.rightWidth) 80
		set _d(scrollWidth) 12		;# scrollbar width
		set _d(ci.filesHeight) 9	;# num files to show in top win
		set _d(ci.commentsHeight) 8	;# height of comment window
		set _d(fm.editor) "fm2tool"
		set _d(buttonColor) $SYSTEMBUTTONFACE	;# menu buttons
		set _d(BG) $GRAY85		;# default background
		# usable space (all of it in X11)
		set _d(padTop)		0
		set _d(padBottom)	0
		set _d(padRight)	0
		set _d(padLeft)		0
		set _d(titlebarHeight)	0
		if {$::tcl_platform(os) eq "Darwin"} {
			# We might be in X11 under Aqua, so leave room
			# for the menubar
			set _d(padTop)		22
			set _d(titlebarHeight)	22
		}
		set rcfile [exec bk dotbk .bkgui config-gui]
	    }
	    default {
		puts "Unknown windowing system"
		exit
	    }
	}

	set gc(activeNewOnly) 1

	set gc(bkdir) [file dirname $rcfile]
	if {[file readable $rcfile]} {
		source $rcfile
		warn_deprecated_options $app
	}

	## Save a copy of the gc array exactly as the user specified it.
	array set usergc [array get gc]

	## If the user specified some global option in their config-gui
	## file, write that same value into the app-specific value for
	## the current tool.  This ensures that all values from the user
	## overwrite any values we've set in here.
	foreach var [array names usergc] {
		if {[string match "*.*" $var]} { continue }
		set gc($app.$var) $usergc($var)
	}

	if {[info exists gc(classicColors)]} {
		set _d(classicColors) $gc(classicColors)
	}
	set _d($app.classicColors) $_d(classicColors)
	foreach p [list "" "$prog."] {
		if {[info exists gc(${p}classicColors)]} {
			set _d(${p}classicColors) $gc(${p}classicColors)
		}
	}

	if {[string is true -strict $_d($app.classicColors)]} {
		# set "classic" color scheme. Setting it this way
		# still lets users override individual colors by
		# setting both gc(classicColors) _and_ the color they
		# want to change
		set _d(oldColor) #C8A0FF
		set _d(newColor) #BDEDF5
		set _d(activeOldColor) $_d(oldColor)
		set _d(activeNewColor) $_d(newColor)
		set _d(activeOldFont) bkFixedBoldFont
		set _d(activeNewFont) bkFixedBoldFont
		set _d(noticeColor) $BKBLUE1
		set _d(searchColor) $YELLOW
		set _d(infoColor) $POWDERBLUE
		set _d(warnColor) $YELLOW
		set _d(highlight) $YELLOW2
		set _d(diffColor) $GRAY88
		set _d(activeDiffColor) $BKGREEN1
		set _d(fm3.conflictBG) $RED
		set _d(fm3.unmergeBG) $LIGHTYELLOW
		set _d(fm3.mergeColor) $LIGHTBLUE
		set _d(fm3.handColor) $LIGHTYELLOW
		set _d(fm3.sameColor) $BKTURQUOISE1
	}

	## Set these colors regardless of classic or not.
	set _d(diff.activeOldColor) $_d(activeDiffColor)
	set _d(diff.activeNewColor) $_d(activeDiffColor)
	set _d(diff.activeOldFont) bkFixedFont
	set _d(diff.activeNewFont) bkFixedFont

	set _d(cset.activeOldColor) $_d(activeDiffColor)
	set _d(cset.activeNewColor) $_d(activeDiffColor)
	set _d(cset.activeOldFont) bkFixedFont
	set _d(cset.activeNewFont) bkFixedFont

	set _d(fm.activeOldColor) $_d(activeDiffColor)
	set _d(fm.activeNewColor) $_d(activeDiffColor)
	set _d(fm.activeOldFont) bkFixedFont
	set _d(fm.activeNewFont) bkFixedFont

	## If the user specified showRevs but not showCsetRevs, use showRevs
	## for both values for backward compatibility.
	foreach p [list "" "$prog."] {
		if {[info exists gc(${p}showRevs)]
		    && ![info exists gc(${p}showCsetRevs)]} {
			set _d(${p}showCsetRevs) $gc(${p}showRevs)
		}
	}

	if {$prog eq "fm3"} {
		## For backward compatibility, if the user has specified
		## charColor in fm3tool, we'll use that as our subline
		## highlight color if they haven't also specified a
		## highlight color to use.
		foreach p [list "" "$prog."] {
			if {[info exists gc(${p}charColor)]
			    && ![info exists gc(${p}highlight)]} {
				set _d(${p}highlight) $gc(${p}charColor)
			}
		}
	}

	## If the user specified activeDiffColor but didn't give us anything
	## for activeNewColor or activeOldColor, fill in the activeDiffColor
	## for those values.
	foreach p [list "" "$prog."] {
		if {![info exists gc(${p}activeDiffColor)]} { continue }
		if {![info exists gc(${p}activeOldColor)]} {
			set _d(${p}activeOldColor) $gc(${p}activeDiffColor)
		}
		if {![info exists gc(${p}activeNewColor)]} {
			set _d(${p}activeNewColor) $gc(${p}activeDiffColor)
		}
	}

	# Pass one just copies all the defaults into gc unless they are set
	# already by the config file
	foreach index [array names _d] {
		if {! [info exists gc($index)]} {
			set gc($index) $_d($index)
			#puts "gc\($index) = $_d($index) (default)"
		}
	}

	# Pass to converts from global field to prog.field
	foreach index [array names gc] {
		if {[string first "." $index] == -1} {
			set i "$prog.$index"
			if {![info exists gc($i)]} {
				set gc($i) $gc($index)
				#puts "gc\($i) = $gc($i) from $index"
			}
		}
    	}

	if {![info exists gc(rev.graphFont)]} {
		if {$gc(fixedFont) eq "bkFixedFont"} {
			set gc(rev.graphFont) $gc(default.fixedFont)
		} else {
			set gc(rev.graphFont) $gc(fixedFont)
		}
	}
	if {![info exists gc(rev.graphBoldFont)]} {
		if {$gc(fixedBoldFont) eq "bkFixedBoldFont"} {
			set gc(rev.graphBoldFont) $gc(default.fixedBoldFont)
		} else {
			set gc(rev.graphBoldFont) $gc(fixedBoldFont)
		}
	}

	foreach var [array names gc *Font] {
		switch -- $gc($var) {
			"default" {
				set gc($var) bkButtonFont
			}
			"default bold" {
				set gc($var) bkBoldFont
			}
			"fixed" {
				set gc($var) bkFixedFont
			}
			"fixed bold" {
				set gc($var) bkFixedBoldFont
			}
		}
	}

	configureFonts $app

	option add *Text.tabStyle wordprocessor

	if {$gc($app.tabwidth) != 8} {
		option add *Text.tabs \
		    [expr {$gc($app.tabwidth) * [font measure bkFixedFont 0]}]
	}
}

proc initFonts {app var} \
{
	upvar 1 $var _d

	## Twiddle the default Tk fonts more to our liking.
	switch -- [tk windowingsystem] {
		"x11" {
			font configure TkTextFont -size -14
			font configure TkDefaultFont -size -14
		}
		"win32" {
			font configure TkFixedFont -size 8
		}
	}

	set font [font configure TkTextFont]
	set bold [dict replace $font -weight bold]

	set fixed     [font configure TkFixedFont]
	set fixedBold [dict replace $fixed -weight bold]

	set fonts [font names]
	if {"bkBoldFont" ni $fonts} {
		font create bkBoldFont {*}$bold
	}
	if {"bkButtonFont" ni $fonts} {
		font create bkButtonFont {*}$font
	}
	if {"bkNoticeFont" ni $fonts} {
		font create bkNoticeFont {*}$bold
	}
	if {"bkFixedFont" ni $fonts} {
		font create bkFixedFont {*}$fixed
	}
	if {"bkFixedBoldFont" ni $fonts} {
		font create bkFixedBoldFont {*}$fixedBold
	}

	set _d(default.boldFont) $bold
	set _d(default.buttonFont) $font
	set _d(default.noticeFont) $bold
	set _d(default.fixedFont) $fixed
	set _d(default.fixedBoldFont) $fixedBold

	set _d(boldFont)	bkBoldFont
	set _d(buttonFont)	bkButtonFont
	set _d(noticeFont)	bkNoticeFont
	set _d(fixedFont)	bkFixedFont
	set _d(fixedBoldFont)	bkFixedBoldFont

	if {[tk windowingsystem] eq "aqua"} {
		bind all <Command-0>     "adjustFontSizes 0"
		bind all <Command-plus>  "adjustFontSizes 1"
		bind all <Command-equal> "adjustFontSizes 1"
		bind all <Command-minus> "adjustFontSizes -1"
	} else {
		bind all <Control-0>     "adjustFontSizes 0"
		bind all <Control-plus>  "adjustFontSizes 1"
		bind all <Control-equal> "adjustFontSizes 1"
		bind all <Control-minus> "adjustFontSizes -1"
	}
}

proc configureFonts {app} \
{
	global	gc usergc

	set res [getScreenSize]
	foreach font {boldFont buttonFont noticeFont fixedFont fixedBoldFont} {
		set name bk[string toupper $font 0]

		## If they have a saved state for this font, use it.
		## Otherwise we use whatever is configured either from
		## initFonts or potentially from config-gui.
		if {[info exists usergc($font)]} {
			set f $usergc($font)
		} elseif {[info exists ::State($font@$res)]} {
			set f $::State($font@$res)
		} else {
			set f $gc($font)
		}
		if {[string index $f 0] ne "-"} { set f [font actual $f] }
		font configure $name {*}$f
		set gc($font) $name
		set gc($app.$font) $name
	}
}

proc adjustFontSizes {n} \
{
	global	gc

	set res [getScreenSize]
	foreach font {fixedFont fixedBoldFont} {
		set name bk[string toupper $font 0]
		if {$n == 0} {
			set opts $gc(default.$font)
		} else {
			set opts [font configure $name]
			set size [dict get $opts -size]
			if {$size >= 0} {
				if {$size <= 6 && $n < 0} { return }
				dict incr opts -size $n
			} else {
				if {$size >= -6 && $n < 0} { return }
				dict incr opts -size [expr {-$n}]
			}
		}
		font configure $name {*}$opts
		set ::State($font@$res) $opts
	}
}

# At one point, the bk guis used symbolic color names to define some
# widget colors. Experience has shown that some color names aren't
# portable across platforms. Bug <2002-12-09-004> shows that the color 
# "darkblue", for instance, doesn't exist on some platforms.
#
# Hard-coding hex values in getConfig() is more portable, but hard
# to read. Thus, this proc defines varibles which can be used in lieu
# of hex values. The names and their definitions are immutable; if you
# want to change a color in a GUI, don't change it here. Define a new
# symbolic name, or pick an existing symbolic name. Don't redefine an
# existing color name.
proc defineSymbolicColors {} \
{
	uplevel {
		# these are taken from X11's rgb.txt file
		set BEIGE		#f5f5dc
		set BLACK		#000000
		set BLUE		#0000ff
		set DARKBLUE		#00008b
		set GRAY50		#7f7f7f
		set GRAY85		#d9d9d9
		set GRAY88		#e0e0e0
		set GRAY91		#e8e8e8
		set GRAY94		#f0f0f0
		set GREEN		#00ff00
		set LIGHTBLUE		#add8e6
		set LIGHTYELLOW	#ffffe0
		set NAVY		#000080
		set ORANGE		#ffa500
		set PINK		#ffc0cb
		set POWDERBLUE	#b0e0e6
		set RED		#ff0000
		set WHITE		#ffffff
		set YELLOW		#ffff00
		set YELLOW2		#fffd56

		# This is used for menubuttons, and is based on the
		# "SystemButtonFace" on windows. 
		if {[tk windowingsystem] eq "win32"} {
#			set SYSTEMBUTTONFACE #d4d0c8
                        set SYSTEMBUTTONFACE systembuttonface
		} elseif {[tk windowingsystem] eq "aqua"} {
			set SYSTEMBUTTONFACE $WHITE
		} else {
			set SYSTEMBUTTONFACE [ttk::style lookup . -background]
		}

		# these are other colors for which no official name exists;
		# there were once hard-coded into getConfig(), but I've
		# given them symbolic names to be consistent with all of
		# the other colors. I tried to visually match them to a
		# similar color in rgb.txt and added a BK prefix
		set BKBLACK1		#181818
		set BKBLUE1		#b0b0e0
		set BKBLUE2		#a8d8e0
		set BKBLUE3		#b4e0ff
		set BKCADETBLUE		#9fb6b8
		set BKGREEN1		#2fedad
		set BKGREEN2		#a2dec3
		set BKPALEOLIVE		#e8f8a6
		set BKPLUM		#dfafdf
		set BKSLATEBLUE1	#a0a0ff
		set BKSLATEBLUE2	#c0c0ff
		set BKSLATEGRAY1	#deeaf4
		set BKSTEELBLUE		#adb8f6
		set BKTURQUOISE1	#1cc7d0
		set BKVIOLET1		#b48cff
	}
}
# Copyright 2011,2016 BitMover, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
namespace eval ttk::theme::bk {

    package provide ttk::theme::bk 0.1

    variable colors ; array set colors {
	-disabledfg	"#999999"

	-frame  	"#EEEEEE"
        -lighter        "#FBFBFB"
        -dark           "#DEDEDE"
        -darker         "#CDCDCD"
        -darkest        "#979797"
        -pressed        "#C5C5C5"
	-lightest 	"#ffffff"
	-selectbg	"#447BCD"
	-selectfg	"#ffffff"

        -activebutton   "#E5E8ED"
    }

    ttk::style theme create bk -parent clam -settings {
	ttk::style configure "." \
	    -background $colors(-frame) \
	    -foreground black \
	    -bordercolor $colors(-darkest) \
	    -darkcolor $colors(-dark) \
	    -lightcolor $colors(-lighter) \
	    -troughcolor $colors(-darker) \
	    -selectbackground $colors(-selectbg) \
	    -selectforeground $colors(-selectfg) \
	    -selectborderwidth 0 \
	    -font TkDefaultFont \
            -indicatorsize 12 \
	    ;

	ttk::style map "." \
	    -background [list disabled $colors(-frame) \
			     active $colors(-lighter)] \
	    -foreground [list disabled $colors(-disabledfg)] \
	    -selectbackground [list !focus $colors(-darkest)] \
	    -selectforeground [list !focus white] \
	    ;

	ttk::style configure TButton \
            -anchor center -width -8 -padding 1 -relief raised
	ttk::style map TButton \
	    -background [list \
			     disabled $colors(-frame) \
			     pressed $colors(-pressed) \
			     active $colors(-activebutton)] \
	    -lightcolor [list \
                            disabled $colors(-lighter) \
                            pressed "#C5C5C5" \
                            active  "#A9C1E7"] \
	    -darkcolor [list \
                            disabled $colors(-dark) \
                            pressed "#D6D6D6" \
                            active "#7BA1DA"] \
	    ;

	ttk::style configure Toolbutton -anchor center -padding 1 -relief flat
	ttk::style map Toolbutton \
	    -relief [list \
                        disabled flat \
                        selected sunken \
                        pressed  sunken \
                        active   raised] \
	    -background [list \
			     disabled $colors(-frame) \
			     pressed $colors(-pressed) \
			     active $colors(-activebutton)] \
	    -lightcolor [list \
                            pressed "#C5C5C5" \
                            active  "#A9C1E7"] \
	    -darkcolor [list \
                            pressed "#D6D6D6" \
                            active "#7BA1DA"] \
	    ;

	ttk::style configure TCheckbutton \
	    -indicatorbackground "#ffffff" -indicatormargin {1 1 4 1}
	ttk::style configure TRadiobutton \
	    -indicatorbackground "#ffffff" -indicatormargin {1 1 4 1}
	ttk::style map TCheckbutton -indicatorbackground \
	    [list  disabled $colors(-frame)  pressed $colors(-frame)]
	ttk::style map TRadiobutton -indicatorbackground \
	    [list  disabled $colors(-frame)  pressed $colors(-frame)]

	ttk::style configure TMenubutton \
            -width -8 -padding 1 -relief raised

	ttk::style configure TEntry -padding 1 -insertwidth 1
	ttk::style map TEntry \
	    -background [list  readonly $colors(-frame)] \
	    -bordercolor [list  focus $colors(-selectbg)] \
	    -lightcolor [list  focus #6490D2] \
	    -darkcolor [list  focus #60A0FF]

	ttk::style configure TCombobox -padding 1 -insertwidth 1
	ttk::style map TCombobox \
	    -background [list active $colors(-lighter) \
			     pressed $colors(-lighter)] \
	    -fieldbackground [list {readonly focus} $colors(-selectbg)] \
	    -foreground [list {readonly focus} $colors(-selectfg)] \
	    ;

	ttk::style configure TNotebook.Tab -padding {6 0 6 2}
	ttk::style map TNotebook.Tab \
	    -padding [list selected {6 2 6 2}] \
	    -background [list active $colors(-activebutton)] \
	    -lightcolor [list \
                active #6490D2 selected $colors(-lighter) {} $colors(-dark) \
            ] \
	    -darkcolor  [list active #60A0FF] \
	    ;

    	ttk::style configure TLabelframe \
	    -labeloutside true -labelinset 0 -labelspace 2 \
	    -borderwidth 2 -relief raised

	ttk::style configure TProgressbar -background #437BCC \
            -darkcolor #546F98 -lightcolor #467ED7

	ttk::style configure Sash -sashthickness 6 -gripcount 10
    }
}
# tooltip.tcl --
#
#       Balloon help
#
# Copyright (c) 1996-2007 Jeffrey Hobbs
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# 
# RCS: @(#) $Id: tooltip.tcl,v 1.16 2008/12/01 23:37:16 hobbs Exp $
#
# Initiated: 28 October 1996


package require Tk 8.4
package require msgcat

#------------------------------------------------------------------------
# PROCEDURE
#	tooltip::tooltip
#
# DESCRIPTION
#	Implements a tooltip (balloon help) system
#
# ARGUMENTS
#	tooltip <option> ?arg?
#
# clear ?pattern?
#	Stops the specified widgets (defaults to all) from showing tooltips
#
# delay ?millisecs?
#	Query or set the delay.  The delay is in milliseconds and must
#	be at least 50.  Returns the delay.
#
# disable OR off
#	Disables all tooltips.
#
# enable OR on
#	Enables tooltips for defined widgets.
#
# <widget> ?-index index? ?-items id? ?-tag tag? ?message?
#	If -index is specified, then <widget> is assumed to be a menu
#	and the index represents what index into the menu (either the
#	numerical index or the label) to associate the tooltip message with.
#	Tooltips do not appear for disabled menu items.
#	If -item is specified, then <widget> is assumed to be a listbox
#	or canvas and the itemId specifies one or more items.
#	If -tag is specified, then <widget> is assumed to be a text
#	and the tagId specifies a tag.
#	If message is {}, then the tooltip for that widget is removed.
#	The widget must exist prior to calling tooltip.  The current
#	tooltip message for <widget> is returned, if any.
#
# RETURNS: varies (see methods above)
#
# NAMESPACE & STATE
#	The namespace tooltip is used.
#	Control toplevel name via ::tooltip::wname.
#
# EXAMPLE USAGE:
#	tooltip .button "A Button"
#	tooltip .menu -index "Load" "Loads a file"
#
#------------------------------------------------------------------------

namespace eval ::tooltip {
    namespace export -clear tooltip
    variable labelOpts
    variable tooltip
    variable G

    if {![info exists G]} {
        array set G {
            enabled     1
            fade        1
            FADESTEP    0.2
            FADEID      {}
            DELAY       500
            AFTERID     {}
            LAST        -1
            TOPLEVEL    .__tooltip__
        }
        if {[tk windowingsystem] eq "x11"} {
            set G(fade) 0 ; # don't fade by default on X11
        }
    }
    if {![info exists labelOpts]} {
	# Undocumented variable that allows users to extend / override
	# label creation options.  Must be set prior to first registry
	# of a tooltip, or destroy $::tooltip::G(TOPLEVEL) first.
	set labelOpts [list -highlightthickness 0 -relief solid -bd 1 \
			   -background lightyellow -fg black]
    }

    # The extra ::hide call in <Enter> is necessary to catch moving to
    # child widgets where the <Leave> event won't be generated
    bind Tooltip <Enter> [namespace code {
	#tooltip::hide
	variable tooltip
	variable G
	set G(LAST) -1
	if {$G(enabled) && [info exists tooltip(%W)]} {
	    set G(AFTERID) \
		[after $G(DELAY) [namespace code [list _show %W $tooltip(%W) cursor]]]
	}
    }]

    bind Menu <<MenuSelect>>	[namespace code { menuMotion %W }]
    bind Tooltip <Leave>	[namespace code [list hide 1]] ; # fade ok
    bind Tooltip <Any-KeyPress>	[namespace code hide]
    bind Tooltip <Any-Button>	[namespace code hide]
}

proc ::tooltip::tooltip {w args} {
    variable tooltip
    variable G
    switch -- $w {
	clear	{
	    if {[llength $args]==0} { set args .* }
	    clear $args
	}
	delay	{
	    if {[llength $args]} {
		if {![string is integer -strict $args] || $args<50} {
		    return -code error "tooltip delay must be an\
			    integer greater than 50 (delay is in millisecs)"
		}
		return [set G(DELAY) $args]
	    } else {
		return $G(DELAY)
	    }
	}
	fade	{
	    if {[llength $args]} {
		set G(fade) [string is true -strict [lindex $args 0]]
	    }
	    return $G(fade)
	}
	off - disable	{
	    set G(enabled) 0
	    hide
	}
	on - enable	{
	    set G(enabled) 1
	}
	default {
	    set i $w
	    if {[llength $args]} {
		set i [uplevel 1 [namespace code "register [list $w] $args"]]
	    }
	    set b $G(TOPLEVEL)
	    if {![winfo exists $b]} {
		variable labelOpts

		toplevel $b -class Tooltip
		if {[tk windowingsystem] eq "aqua"} {
		    ::tk::unsupported::MacWindowStyle style $b help none
		} else {
		    wm overrideredirect $b 1
		}
		catch {wm attributes $b -topmost 1}
		# avoid the blink issue with 1 to <1 alpha on Windows
		catch {wm attributes $b -alpha 0.99}
		wm positionfrom $b program
		wm withdraw $b
		eval [linsert $labelOpts 0 label $b.label]
		pack $b.label -ipadx 1
	    }
	    if {[info exists tooltip($i)]} { return $tooltip($i) }
	}
    }
}

proc ::tooltip::register {w args} {
    variable tooltip
    set key [lindex $args 0]
    while {[string match -* $key]} {
	switch -- $key {
	    -index	{
		if {[catch {$w entrycget 1 -label}]} {
		    return -code error "widget \"$w\" does not seem to be a\
			    menu, which is required for the -index switch"
		}
		set index [lindex $args 1]
		set args [lreplace $args 0 1]
	    }
	    -item - -items {
                if {[winfo class $w] eq "Listbox"} {
                    set items [lindex $args 1]
                } else {
                    set namedItem [lindex $args 1]
                    if {[catch {$w find withtag $namedItem} items]} {
                        return -code error "widget \"$w\" is not a canvas, or\
			    item \"$namedItem\" does not exist in the canvas"
                    }
                }
		set args [lreplace $args 0 1]
	    }
            -tag {
                set tag [lindex $args 1]
                set r [catch {lsearch -exact [$w tag names] $tag} ndx]
                if {$r || $ndx == -1} {
                    return -code error "widget \"$w\" is not a text widget or\
                        \"$tag\" is not a text tag"
                }
                set args [lreplace $args 0 1]
            }
	    -command {
		set command [lindex $args 1]
		set args [lreplace $args 0 1]
	    }
	    default	{
		return -code error "unknown option \"$key\":\
			should be -command, -index, -items or -tag"
	    }
	}
	set key [lindex $args 0]
    }
    if {[llength $args] != 1} {
	return -code error "wrong # args: should be \"tooltip widget\
		?-index index? ?-items item? ?-tag tag? message\""
    }
    if {$key eq ""} {
	clear $w
    } else {
	if {![winfo exists $w]} {
	    return -code error "bad window path name \"$w\""
	}
	set d [dict create message $key]
	if {[info exists command]} {
	    dict set d command $command
	}
	if {[info exists index]} {
	    set tooltip($w,$index) $d
	    return $w,$index
	} elseif {[info exists items]} {
	    foreach item $items {
		set tooltip($w,$item) $d
		if {[winfo class $w] eq "Listbox"} {
		    enableListbox $w $item
		} else {
		    enableCanvas $w $item
		}
	    }
	    # Only need to return the first item for the purposes of
	    # how this is called
	    return $w,[lindex $items 0]
        } elseif {[info exists tag]} {
            set tooltip($w,t_$tag) $d
            enableTag $w $tag
            return $w,$tag
	} else {
	    set tooltip($w) $d
	    bindtags $w [linsert [bindtags $w] end "Tooltip"]
	    return $w
	}
    }
}

proc ::tooltip::clear {{pattern .*}} {
    variable tooltip
    # cache the current widget at pointer
    set ptrw [winfo containing [winfo pointerx .] [winfo pointery .]]
    foreach w [array names tooltip $pattern] {
	unset tooltip($w)
	if {[winfo exists $w]} {
	    set tags [bindtags $w]
	    if {[set i [lsearch -exact $tags "Tooltip"]] != -1} {
		bindtags $w [lreplace $tags $i $i]
	    }
	    ## We don't remove TooltipMenu because there
	    ## might be other indices that use it

	    # Withdraw the tooltip if we clear the current contained item
	    if {$ptrw eq $w} { hide }
	}
    }
}

proc ::tooltip::show {w msg {i {}}} {
    if {![winfo exists $w]} { return }

    # Use string match to allow that the help will be shown when
    # the pointer is in any child of the desired widget
    if {([winfo class $w] ne "Menu")
	&& ![string match $w* [eval [list winfo containing] \
				   [winfo pointerxy $w]]]} {
	return
    }

    variable G

    after cancel $G(FADEID)
    set b $G(TOPLEVEL)
    # Use late-binding msgcat (lazy translation) to support programs
    # that allow on-the-fly l10n changes
    $b.label configure -text [::msgcat::mc $msg] -justify left
    update idletasks
    set screenw [winfo screenwidth $w]
    set screenh [winfo screenheight $w]
    set reqw [winfo reqwidth $b]
    set reqh [winfo reqheight $b]
    # When adjusting for being on the screen boundary, check that we are
    # near the "edge" already, as Tk handles multiple monitors oddly
    if {$i eq "cursor"} {
	set y [expr {[winfo pointery $w]+20}]
	if {($y < $screenh) && ($y+$reqh) > $screenh} {
	    set y [expr {[winfo pointery $w]-$reqh-5}]
	}
    } elseif {$i ne ""} {
	set y [expr {[winfo rooty $w]+[winfo vrooty $w]+[$w yposition $i]+25}]
	if {($y < $screenh) && ($y+$reqh) > $screenh} {
	    # show above if we would be offscreen
	    set y [expr {[winfo rooty $w]+[$w yposition $i]-$reqh-5}]
	}
    } else {
	set y [expr {[winfo rooty $w]+[winfo vrooty $w]+[winfo height $w]+5}]
	if {($y < $screenh) && ($y+$reqh) > $screenh} {
	    # show above if we would be offscreen
	    set y [expr {[winfo rooty $w]-$reqh-5}]
	}
    }
    if {$i eq "cursor"} {
	set x [winfo pointerx $w]
    } else {
	set x [expr {[winfo rootx $w]+[winfo vrootx $w]+
		     ([winfo width $w]-$reqw)/2}]
    }
    # only readjust when we would appear right on the screen edge
    if {$x<0 && ($x+$reqw)>0} {
	set x 0
    } elseif {($x < $screenw) && ($x+$reqw) > $screenw} {
	set x [expr {$screenw-$reqw}]
    }
    if {[tk windowingsystem] eq "aqua"} {
	set focus [focus]
    }
    # avoid the blink issue with 1 to <1 alpha on Windows, watch half-fading
    catch {wm attributes $b -alpha 0.99}
    wm geometry $b +$x+$y
    wm deiconify $b
    raise $b
    if {[tk windowingsystem] eq "aqua" && $focus ne ""} {
	# Aqua's help window steals focus on display
	after idle [list focus -force $focus]
    }
}

proc ::tooltip::_show {w d {i ""}} {
    set message [dict get $d message]
    if {[dict exists $d command]} {
	set message [uplevel #0 [dict get $d command]]
	if {$message eq ""} { return }
    }
    show $w $message $i
}

proc ::tooltip::menuMotion {w} {
    variable G

    if {$G(enabled)} {
	variable tooltip

        # Menu events come from a funny path, map to the real path.
        set m [string map {"#" "."} [winfo name $w]]
	set cur [$w index active]

	# The next two lines (all uses of LAST) are necessary until the
	# <<MenuSelect>> event is properly coded for Unix/(Windows)?
	if {$cur == $G(LAST)} return
	set G(LAST) $cur
	# a little inlining - this is :hide
	after cancel $G(AFTERID)
	catch {wm withdraw $G(TOPLEVEL)}
	if {[info exists tooltip($m,$cur)] || \
		(![catch {$w entrycget $cur -label} cur] && \
		[info exists tooltip($m,$cur)])} {
	    set G(AFTERID) [after $G(DELAY) \
		    [namespace code [list _show $w $tooltip($m,$cur) cursor]]]
	}
    }
}

proc ::tooltip::hide {{fadeOk 0}} {
    variable G

    after cancel $G(AFTERID)
    after cancel $G(FADEID)
    if {$fadeOk && $G(fade)} {
	fade $G(TOPLEVEL) $G(FADESTEP)
    } else {
	catch {wm withdraw $G(TOPLEVEL)}
    }
}

proc ::tooltip::fade {w step} {
    if {[catch {wm attributes $w -alpha} alpha] || $alpha <= 0.0} {
        catch { wm withdraw $w }
        catch { wm attributes $w -alpha 0.99 }
    } else {
	variable G
        wm attributes $w -alpha [expr {$alpha-$step}]
        set G(FADEID) [after 50 [namespace code [list fade $w $step]]]
    }
}

proc ::tooltip::wname {{w {}}} {
    variable G
    if {[llength [info level 0]] > 1} {
	# $w specified
	if {$w ne $G(TOPLEVEL)} {
	    hide
	    destroy $G(TOPLEVEL)
	    set G(TOPLEVEL) $w
	}
    }
    return $G(TOPLEVEL)
}

proc ::tooltip::listitemTip {w x y} {
    variable tooltip
    variable G

    set G(LAST) -1
    set item [$w index @$x,$y]
    if {$G(enabled) && [info exists tooltip($w,$item)]} {
	set G(AFTERID) [after $G(DELAY) \
		[namespace code [list _show $w $tooltip($w,$item) cursor]]]
    }
}

# Handle the lack of <Enter>/<Leave> between listbox items using <Motion>
proc ::tooltip::listitemMotion {w x y} {
    variable tooltip
    variable G
    if {$G(enabled)} {
        set item [$w index @$x,$y]
        if {$item ne $G(LAST)} {
            set G(LAST) $item
            after cancel $G(AFTERID)
            catch {wm withdraw $G(TOPLEVEL)}
            if {[info exists tooltip($w,$item)]} {
                set G(AFTERID) [after $G(DELAY) \
                   [namespace code [list _show $w $tooltip($w,$item) cursor]]]
            }
        }
    }
}

# Initialize tooltip events for Listbox widgets
proc ::tooltip::enableListbox {w args} {
    if {[string match *listitemTip* [bind $w <Enter>]]} { return }
    bind $w <Enter> +[namespace code [list listitemTip %W %x %y]]
    bind $w <Motion> +[namespace code [list listitemMotion %W %x %y]]
    bind $w <Leave> +[namespace code [list hide 1]] ; # fade ok
    bind $w <Any-KeyPress> +[namespace code hide]
    bind $w <Any-Button> +[namespace code hide]
}

proc ::tooltip::itemTip {w args} {
    variable tooltip
    variable G

    set G(LAST) -1
    set item [$w find withtag current]
    if {$G(enabled) && [info exists tooltip($w,$item)]} {
	set G(AFTERID) [after $G(DELAY) \
		[namespace code [list _show $w $tooltip($w,$item) cursor]]]
    }
}

proc ::tooltip::enableCanvas {w args} {
    if {[string match *itemTip* [$w bind all <Enter>]]} { return }
    $w bind all <Enter> +[namespace code [list itemTip $w]]
    $w bind all <Leave>	+[namespace code [list hide 1]] ; # fade ok
    $w bind all <Any-KeyPress> +[namespace code hide]
    $w bind all <Any-Button> +[namespace code hide]
}

proc ::tooltip::tagTip {w tag} {
    variable tooltip
    variable G
    set G(LAST) -1
    if {$G(enabled) && [info exists tooltip($w,t_$tag)]} {
        if {[info exists G(AFTERID)]} { after cancel $G(AFTERID) }
        set G(AFTERID) [after $G(DELAY) \
            [namespace code [list _show $w $tooltip($w,t_$tag) cursor]]]
    }
}

proc ::tooltip::enableTag {w tag} {
    if {[string match *tagTip* [$w tag bind $tag]]} { return }
    $w tag bind $tag <Enter> +[namespace code [list tagTip $w $tag]]
    $w tag bind $tag <Leave> +[namespace code [list hide 1]] ; # fade ok
    $w tag bind $tag <Any-KeyPress> +[namespace code hide]
    $w tag bind $tag <Any-Button> +[namespace code hide]
}

package provide tooltip 1.4.4
# Copyright 1999-2006,2008-2016 BitMover, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

if {[info exists ::env(BK_DEBUG_GUI)]} {
	proc InCommand {} {
		uplevel {puts "[string repeat { } [expr {[info level] - 1}]][info level 0]"}
	}

	proc newproc {name args body} {
		set body "InCommand\n$body"
		realproc $name $args $body
	}

	rename proc realproc
	rename newproc proc
}

if {![info exists ::env(BK_GUI_LEVEL)]
    || ![string is integer -strict $::env(BK_GUI_LEVEL)]} {
	set ::env(BK_GUI_LEVEL) 0
}
incr ::env(BK_GUI_LEVEL)

proc bk_toolname {} {
	if {[info exists ::tool_name]} {
		return $::tool_name
	}
	return [file tail [info script]]
}

proc bk_toplevel {} {
	if {[bk_toolname] eq "citool"} { return ".citool" }
	return "."
}

proc bk_initTheme {} \
{
	switch -- [tk windowingsystem] {
		"aqua" {
			set bg "systemSheetBackground"
		}

		"x11" {
			ttk::setTheme bk
		}
	}

	set bg [ttk::style lookup . -background]

	. configure -background $bg
	option add *background	$bg

	option add *Frame.background	$bg
	option add *Label.background	$bg
	option add *Toplevel.background	$bg
	option add *Listbox.background	#FFFFFF
	option add *Entry.background	#FFFFFF
	option add *Entry.borderWidth	1
	option add *Text.background	#FFFFFF
	## Work around a Tk bug in OS X.
	if {[tk windowingsystem] == "aqua"} {
		option add *Menu.background systemMenu
	}

	## Make the ReadOnly tag
	foreach event [bind Text] {
		set script [bind Text $event]
		if {[regexp -nocase {text(paste|insert|transpose)} $script]
		    || [regexp -nocase {%W (insert|delete|edit)} $script]} {
			continue
		}
		set script [string map {tk_textCut tk_textCopy} $script]
		bind ReadonlyText $event $script
	}
	bind ReadonlyText <Up>	    "%W yview scroll -1 unit; break"
	bind ReadonlyText <Down>    "%W yview scroll  1 unit; break"
	bind ReadonlyText <Left>    "%W xview scroll -1 unit; break"
	bind ReadonlyText <Right>   "%W xview scroll  1 unit; break"
	bind ReadonlyText <Prior>   "%W yview scroll -1 page; break"
	bind ReadonlyText <Next>    "%W yview scroll  1 page; break"
	bind ReadonlyText <Home>    "%W yview moveto 0; break"
	bind ReadonlyText <End>	    "%W yview moveto 1; break"
}

proc bk_init {} {
	set tool [bk_toolname]

	bk_initPlatform

	bk_initTheme

	## Include our tool name and Toplevel tags for .
	bindtags . [list $tool . Toplevel all]

	## Remove default Tk mouse wheel bindings.
	foreach event {MouseWheel 4 5} {
		foreach mod {"" Shift- Control- Command- Alt- Option-} {
			catch {bind Text <$mod$event> ""}
			catch {bind Listbox <$mod$event> ""}
		}
	}

	## Mouse wheel bindings
	if {[tk windowingsystem] eq "x11"} {
		bind all <4> {scrollMouseWheel %W y %X %Y -1}
		bind all <5> {scrollMouseWheel %W y %X %Y  1}
		bind all <Shift-4> {scrollMouseWheel %W x %X %Y -1}
		bind all <Shift-5> {scrollMouseWheel %W x %X %Y  1}

		bind wheel <4> {scrollMouseWheel %W y %X %Y -1}
		bind wheel <5> {scrollMouseWheel %W y %X %Y  1}
		bind wheel <Shift-4> {scrollMouseWheel %W x %X %Y -1}
		bind wheel <Shift-5> {scrollMouseWheel %W x %X %Y  1}
	} else {
		bind all <MouseWheel> {scrollMouseWheel %W y %X %Y %D}
		bind all <Shift-MouseWheel> {scrollMouseWheel %W x %X %Y %D}

		bind wheel <MouseWheel> {scrollMouseWheel %W y %X %Y %D}
		bind wheel <Shift-MouseWheel> {scrollMouseWheel %W x %X %Y %D}
	}

	if {[tk windowingsystem] eq "aqua"} {
		event add <<Redo>> <Command-Shift-z> <Command-Shift-Z>
	}

	bind Entry  <KP_Enter> {event generate %W <Return>}
	bind TEntry <KP_Enter> {event generate %W <Return>}
}

# Try to find the project root, limiting ourselves to 40 directories
proc cd2root { {startpath {}} } \
{
	set n 40
	if {$startpath != ""} {
		set dir $startpath
	} else {
		set dir "."
	}
	while {$n > 0} {
		set path [file join $dir BitKeeper etc]
		if {[file isdirectory $path]} {
			cd $dir
			return
		}
		set dir [file join $dir ..]
		incr n -1
	}
	return -1
}

proc cd2product {{path ""}} {
	set cmd [list exec bk root]
	if {$path ne ""} { lappend cmd $path }
	if {[catch { cd [{*}$cmd] } err]} {
		puts "Could not change directory to product root."
		exit 1
	}
}

proc resolveSymlink {filename} {
	catch {
		set original_path [file dirname $filename]
		set link_path [file readlink $filename]
		set filename [file join $original_path $link_path]
		# once we upgrade to tcl 8.4 we should also call 
		# [file normalize]...
	}
	return $filename
}

proc displayMessage {msg {exit {}}} \
{
	if {$exit != ""} {
		set title "Error"
		set icon "error"
	} else {
		set title "Info"
		set icon "info"
	}
	tk_messageBox -title $title -type ok -icon $icon -message $msg \
	    -parent [bk_toplevel]
	if {$exit == 1} {
		exit 1
	} else {
		return
	}
}

proc message {message args} \
{
	if {[dict exists $args -exit]} {
		set exit [dict get $args -exit]
		dict unset args -exit
	}

	if {![dict exists $args -parent]} {
		dict set args -parent [bk_toplevel]
	}

	set forceGui 0
	if {[dict exists $args -gui]} {
	    set forceGui 1
	    dict unset args -gui
	}

	if {!$forceGui && ([info exists ::env(BK_REGRESSION)]
	    || ($::env(BK_GUI_LEVEL) == 1
		&& $::tcl_platform(platform) ne "windows"))} {
		if {[info exists ::env(BK_REGRESSION)]
		    && $::env(BK_GUI_LEVEL) > 1} {
			append message " (LEVEL: $::env(BK_GUI_LEVEL))"
		}
		puts stderr $message
	} else {
		tk_messageBox {*}$args -message $message
	}

	if {[info exists exit]} {
		exit $exit
	}
}

# usage: centerWindow pathName ?width height?
#
# If width and height are supplied the window will be set to
# that size and that size will be used to compute the location
# of the window. Otherwise the requested width and height of the
# window will be used.
proc centerWindow {w args} \
{

	set w [winfo toplevel $w]

	if {[llength $args] > 0} {
		set width [lindex $args 0]
		set height [lindex $args 1]
	} else {
		set width [winfo reqwidth $w]
		set height [winfo reqheight $w]
	}
	set x [expr {round(([winfo vrootwidth $w] - $width) /2)}]
	set y [expr {round(([winfo vrootheight $w] - $height) /2)}]

	wm geometry $w +${x}+${y}
}

# this proc attempts to center a given line number in a text widget;
# similar to the widget's "see" option but with the requested line
# always centered, if possible. The text widget "see" command only
# attempts to center a line if it is "far out of view", so we first
# try to scroll the requested line as far away as possible, then
# scroll it back. Kludgy, but it seems to work.
proc centerTextLine {w line} \
{
	set midline "[expr {int([$w index end-1c] / 2)}].0"
	if {$line > $midline} {
		$w see 1.0
	} else {
		$w see end
	}
	update idletasks
	$w see $line
}

# From a Cameron Laird post on usenet
proc print_stacktrace {} \
{
	set depth [info level]
	puts "Current call stack shows"
	for {set i 1} {$i < $depth} {incr i} {
		puts "\t[info level $i]"
	}
}

proc tmpfile {name} \
{
	global	tmp_dir tmp_files tmp_filecount

	set prefix [file join $tmp_dir "bk_${name}_[pid]"]
	set filename "${prefix}_[incr tmp_filecount].tmp"
	while {[file exists $filename]} {
		set filename "${prefix}_[incr tmp_filecount].tmp"
	}
	lappend tmp_files $filename
	return $filename
}

## Setup a trace to cleanup any temporary files as we exit.
proc cleanupTmpfiles {args} \
{
	catch {
		global	tmp_files
		foreach file $tmp_files {
			file delete -force $file
		}
	}
}
trace add exec exit enter cleanupTmpfiles

proc loadState {appname} \
{
	catch {::appState load $appname ::State}
}

proc saveState {appname} \
{
	catch {::appState save $appname ::State}
}

proc getScreenSize {{w .}} \
{
	return [winfo vrootwidth $w]x[winfo vrootheight $w]
}

proc trackGeometry {w1 w2 width height} \
{	
	global	gc app

	# The event was caused by a different widget
	if {$w1 ne $w2} {return}

	# We don't want to save the geometry if the user maximized
	# the window, so only save if it's a 'normal' resize operation.
	# XXX: Only works on MS Windows
	if {[wm state $w1] eq "normal"} {
		set min $gc($app.minsize)
		set res [getScreenSize $w1]
		if {$width < $min || $height < $min} {
			debugGeom "Geometry ${width}x${height} too small"
			return
		}
		# We can't get width/height from wm geometry because if the 
		# app is gridded, we'll get grid units instead of pixels.
		# The parameters (%w %h) however, seem to 
		# be correct on all platforms.
		foreach {- - ox x oy y} [goodGeometry [wm geometry $w1]] {break}
		set ::State(geometry@$res) "${width}x${height}${ox}${x}${oy}${y}"
		debugGeom "Remembering $::State(geometry@$res)"
	}
}

# See if a geometry string is good. Returns a list with 
# [width, height, ox, x, oy , y] where ox and oy are the sign
# of the geometry string (+|-)
proc goodGeometry {geometry} \
{
	if {[regexp \
    	    {(([0-9]+)[xX]([0-9]+))?(([\+\-])([\+\-]?[0-9]+)([\+\-])([\+\-]?[0-9]+))?} \
	    $geometry - - width height - ox x oy y]} {
		return [list $width $height $ox $x $oy $y]
	}
	return ""
}

proc debugGeom {args} \
{
	global env

	if {[info exists env(BK_DEBUG_GEOMETRY)]} {
		puts stderr [join $args " "]
	}
}

proc restoreGeometry {app {w .}} \
{
	global State gc env

	debugGeom "start"
	# track geometry changes 
	bindtags $w [concat "geometry" [bindtags $w]]
	bind geometry <Configure> [list trackGeometry $w %W %w %h]

	set rwidth [winfo vrootwidth $w]
	set rheight [winfo vrootheight $w]
	set res ${rwidth}x${rheight}
	debugGeom "res $res"

	# get geometry from the following priority list (most to least)
	# 1. -geometry on the command line (which means ::geometry)
	# 2. _BK_GEOM environment variable
	# 3. State(geometry@res) (see loadState && saveState)
	# 4. gc(app.geometry) (see config.tcl)
	# 5. App request (whatever Tk wants)
	# We stop at the first usable geometry...

	if {[info exists ::geometry] && 
	    ([set g [goodGeometry $::geometry]] ne "")} {
		debugGeom "Took ::geometry"
	} elseif {[info exists env(_BK_GEOM)] &&
	    ([set g [goodGeometry $env(_BK_GEOM)]] ne "")} {
		debugGeom "Took _BK_GEOM"
	} elseif {[info exists State(geometry@$res)] &&
	    ([set g [goodGeometry $State(geometry@$res)]] ne "")} {
		debugGeom "Took State"
	} elseif {[info exists gc($app.geometry)] &&
	    ([set g [goodGeometry $gc($app.geometry)]] ne "")} {
		debugGeom "Took app.geometry"
	}
	
	# now get the variables
	if {[info exists g]} {
		foreach {width height ox x oy y} $g {break}
		debugGeom "config: $width $height $ox $x $oy $y"
	} else {
		set width ""
		set x ""
	}
	
	if {$width eq ""} {
		# We need to call update to force the recalculation of
		# geometry. We're assuming the state of the widget is
		# withdrawn so this won't cause a screen update.
		update
		set width [winfo reqwidth $w]
		set height [winfo reqheight $w]
	}
	
	if {$x eq ""} {
		foreach {- - ox x oy y} [goodGeometry [wm geometry $w]] {break}
	}
	debugGeom "using: $width $height $ox $x $oy $y"
	
	# The geometry rules are different for each platform.
	# E.g. in Mac OS X negative positions for the geometry DO NOT
	# correspond to the lower right corner of the app, it's ALWAYS
	# the top left corner. (This will change with Tk-8.4.12 XXX)
	# Thus, we ALWAYS specify the geometry as top left corner for
	# BOTH the app and the screen. The math may be harder, but it'll
	# be right.

	# Usable space
	set ux $gc(padLeft)
	set uy $gc(padTop)
	set uwidth [expr {$rwidth - $gc(padLeft) - $gc(padRight)}]
	set uheight [expr {$rheight - $gc(padTop) 
	    - $gc(padBottom) - $gc(titlebarHeight)}]
	debugGeom "ux: $ux uy: $uy uwidth: $uwidth uheight: $uheight"
	debugGeom "padLeft $gc(padLeft) padRight $gc(padRight)"
	debugGeom "padTop $gc(padTop) padBottom $gc(padBottom)"
	debugGeom "titlebarHeight $gc(titlebarHeight)"

	# Normalize the app's position. I.e. (x, y) is top left corner of app
	if {$ox eq "-"} {set x [expr {$rwidth - $x - $width}]}
	if {$oy eq "-"} {set y [expr {$rheight - $y - $height}]}

	if {![info exists env(BK_GUI_OFFSCREEN)]} {
		# make sure 100% of the GUI is visible
		debugGeom "Size start $width $height"
		set width [expr {($width > $uwidth)?$uwidth:$width}]
		set height [expr {($height > $uheight)?$uheight:$height}]
		debugGeom "Size end $width $height"

		debugGeom "Pos start $x $y"
		if {$x < $ux} {set x $ux}
		if {$y < $uy} {set y $uy}
		if {($x + $width) > ($ux + $uwidth)} {
			debugGeom "1a $ox $x $oy $y"
			set x [expr {$ux + $uwidth - $width}]
			debugGeom "1b $ox $x $oy $y"
		}
		if {($y + $height) > ($uy + $uheight)} {
			debugGeom "2a $ox $x $oy $y"
			set y [expr {$uy + $uheight - $height}]
			debugGeom "2b $ox $x $oy $y"
		}
		debugGeom "Pos end $x $y"
	} else {
		debugGeom "Pos start offscreen $x $y"
		# make sure at least some part of the window is visible
		# i.e. we don't care about size, only position
		# if the app is offscreen, we pull it so that 1/10th of it
		# is visible
		if {$x > ($ux + $uwidth)} {
			set x [expr {$ux + $uwidth - int($uwidth/10)}]
		} elseif {($x + $width) < $ux} {
			set x $ux
		}
		if {$y > ($uy + $uheight)} {
			set y [expr {$uy + $uheight - int($uheight/10)}]
		} elseif {($y + $height) < $uy} {
			set y $uy
		}
		debugGeom "Pos end offscreen $x $y"
	}


	# Since we are setting the size of the window we must turn
	# geometry propagation off
	catch {grid propagate $w 0}
	catch {pack propagate $w 0}

	debugGeom "${width}x${height}"
	# Don't use [wm geometry] for width and height because it 
	# treats the arguments as grid units if the widget is in grid mode.
	$w configure -width $width -height $height

	debugGeom "+$x +$y"
	wm geometry $w +${x}+${y}
}

# this removes hardcoded newlines from paragraphs so that the paragraphs
# will wrap when placed in a widget that wraps (such as the description
# of a step)
proc wrap {text} \
{
	if {$::tcl_version >= 8.2} {
		set text [string map [list \n\n \001 \n { }] $text]
		set text [string map [list \001 \n\n] $text]
	} else {
		regsub -all "\n\n" $text \001 text
		regsub -all "\n" $text { } text
		regsub -all "\001" $text \n\n text
	}
	return $text
}

# get a message from the bkmsg.doc message catalog
proc getmsg {key args} \
{
	# do we want to return something like "lookup failed for xxx"
	# if the lookup fails? What we really need is something more
	# like real message catalogs, where I can supply messages that
	# have defaults.
	set data ""
	set cmd [list bk getmsg $key]
	if {[llength $args] > 0} {
		lappend cmd [lindex $args 0]
	}
	set err [catch {set data [eval exec $cmd]}]
	return $data
}

# usage: bgExec ?options? command ?arg? ?arg ..?
#
# this command exec's a program, waits for it to finish, and returns
# the exit code of the exec'd program.  Unlike a normal "exec" call, 
# while the pipe is running the event loop is active, allowing the 
# calling GUI to be refreshed as necessary. However, this proc will 
# not allow the user to interact with the calling program until it 
# returns, by doing a grab on an invisible window.
#
# Upon completion, stdout from the command is available in the global
# variable bgExec(stdout), and stderr is in bgExec(stderr)
#
#
# Options:
# 
# -output proc
#
#    proc is the name of a proc to be called whenever output is
#    generated by the command. The proc will be called with two
#    arguments: the file descriptor (useful as a unique identifier) and
#    the data that was read in.
#
# example: 
#
#    text .t ; pack .t
#    proc showOutput {f string} {
#        .t insert end $string
#        return $string
#    }
#    set exitStatus [bgExec -output showOutput ls]
#
# Side effects:
#
# while running, this creates a temporary window named .__grab__<fd>,
# where <fd> is the file description of the open pipe

namespace eval ::bgExec {}
interp alias {} ::bgExec {} ::bgExec::bgExec

proc ::bgExec::bgExec {args} \
{
	global bgExec errorCode

	set outhandler ""
	while {[llength $args] > 1} {
		set arg [lindex $args 0]
		switch -exact -- $arg {
			-output {
				set outhandler [lindex $args 1]
				set args [lrange $args 2 end]
			}
			--	{
				set args [lrange $args 1 end]
				break
			}
			default	{break}
		}
	}

	set stderrFile [tmpfile "bgexec-stderr"]
	set run_fd [open |[list {*}$args 2> $stderrFile] "r"]
	fconfigure $run_fd -blocking false
	fileevent $run_fd readable [namespace code [list readFile $run_fd]]

	set bgExec(handler) $outhandler
	set bgExec(stdout) ""
	set bgExec(stderr) ""
	set bgExec(status) 0

	# Create a small, invisible window, and do a grab on it
	# so the user can't interact with the main program.
	set grabWin .__grab__$run_fd
	frame $grabWin -width 1 -height 1 -background {} -borderwidth 0
	place $grabWin -relx 1.0 -x -2 -rely 1.0 -y -2
	after idle "if {\[winfo exists $grabWin]} {grab $grabWin}"

	# This variable is set by the code that gets run via the 
	# fileevent handler when we get EOF on the pipe.
	vwait bgExec(status)

	catch {destroy $grabWin}

	# The pipe must be reconfigured to blocking mode before
	# closing, or close won't wait for the process to end. If
	# close doesn't wait, we can't get the exit status.
	fconfigure $run_fd -blocking true
	set ::errorCode [list NONE]
	catch {close $run_fd}
	if {[info exists ::errorCode] && 
	    [lindex $::errorCode 0] == "CHILDSTATUS"} {
		set exitCode [lindex $::errorCode 2]
	} else {
		set exitCode 0
	}

	if {[file exists $stderrFile]} {
		set f [open $stderrFile r]
		set bgExec(stderr) [read $f]
		close $f
		file delete $stderrFile
	}

	unset bgExec(handler)
	unset bgExec(status)

	return $exitCode
}

proc ::bgExec::handleOutput {f string} \
{
	global bgExec

	if {[info exists bgExec(handler)] && $bgExec(handler) != ""} {
		set tmp [$bgExec(handler) $f $string]
		append bgExec(stdout) $tmp
	} else {
		append bgExec(stdout) $string
	}
}

proc ::bgExec::readFile {f} \
{
	global bgExec

	# The channel is readable; try to read it.
	set status [catch { gets $f line } result]

	if { $status != 0 } {
		# Error on the channel
		set bgExec(status) $status

	} elseif { $result >= 0 } {
		# Successfully read the channel
		handleOutput $f "$line\n"

	} elseif { [eof $f] } {
		# End of file on the channel
		set bgExec(status) 1

	} elseif { [fblocked $f] } {
		# Read blocked.  Just return

	} else {
		# Something else; should never happen.
		set bgExec(status) 2
	}
}

proc popupMessage {args} \
{
	if {[llength $args] == 1} {
		set option ""
		set message [lindex $args 0]
	} else {
		set option [lindex $args 0]
		set message [lindex $args 1]
	}

	# export BK_MSG_GEOM so the popup will show in the right
	# place...
	if {[winfo viewable .] || [winfo viewable .citool]} {
		set x [expr {[winfo rootx .] + 40}]
		set y [expr {[winfo rooty .] + 40}]
		set ::env(BK_MSG_GEOM) "+${x}+${y}"
	}

	set tmp [tmpfile msg]
	set fp [open $tmp w]
	puts $fp $message
	close $fp

	# hopefully someday we'll turn the msgtool code into a library
	# so we don't have to exec. For now, though, exec works just fine.
	if {[info exists ::env(BK_REGRESSION)]} {
		# we are running in test mode; spew to stderr
		puts stderr $message
	} else {
		exec bk msgtool {*}$option -F $tmp
	}
}

# License Functions

proc checkLicense {license licsign1 licsign2 licsign3} \
{
	global dev_null

	# bk _eula -v has the side effect of popping up a messagebox
	# warning the user if the license is invalid. 
	set f [open "|bk _eula -v > $dev_null" w]
	puts $f "
	    license: $license
	    licsign1: $licsign1
	    licsign2: $licsign2
	    licsign3: $licsign3
	"

	set ::errorCode NONE
	catch {close $f}
		      
	if {($::errorCode == "NONE") || 
	    ([lindex $::errorCode 0] == "CHILDSTATUS" &&
	     [lindex $::errorCode 2] == 0)} {
		return 1
	}
	return 0
}

# Side Effect: the license data is put in the environment variable BK_CONFIG
proc getEulaText {license licsign1 licsign2 licsign3 text} \
{
	global env
	upvar $text txt

	# need to override any config currently in effect...
	set BK_CONFIG "logging:none!;"
	append BK_CONFIG "license:$license!;"
	append BK_CONFIG "licsign1:$licsign1!;"
	append BK_CONFIG "licsign2:$licsign2!;"
	append BK_CONFIG "licsign3:$licsign3!;"
	append BK_CONFIG "single_user:!;single_host:!;"
	set env(BK_CONFIG) $BK_CONFIG
	set r [catch {exec bk _eula -u} txt]
	if {$r} {set txt ""}
	return $r
}

proc normalizePath {path} \
{
	return [file join {*}[file split $path]]
}

# run 'script' for each line in the text widget
# binding 'var' to the contents of the line
# e.g.
#
# EACH_LINE .t myline {
#	puts $myline
# }
#
# would dump the contents of the text widget on stdout
# Note that 'myline' will still exist after the script
# is done. Also, if 'myline' existed before EACH_LINE
# is called, it will be stomped on.
proc EACH_LINE {widget var script} {
	upvar 1 $var line
	set lno 1.0
	while {[$widget compare $lno < [$widget index end]]} {
		set line [$widget get $lno "$lno lineend"]
		set lno [$widget index "$lno + 1 lines"]
		# careful, the script must be run at the end
		# because of 'continue' and 'break'
		uplevel 1 $script
	}
}

# Aqua stuff

proc AboutAqua {} \
{
	if {[winfo exists .aboutaqua]} {return}
	set version [exec bk version]
	toplevel .aboutaqua
	wm title .aboutaqua ""
	frame .aboutaqua.f
	::tk::unsupported::MacWindowStyle style .aboutaqua document {closeBox}
	label .aboutaqua.f.title \
	    -text "The BitKeeper Configuration Management System" \
	    -font {Helvetica 14 bold} \
	    -justify center
	label .aboutaqua.f.v \
	    -text $version \
	    -font {Helvetica 12 normal} \
	    -justify left
	label .aboutaqua.f.copyright \
	    -text "Copyright 2015 BitKeeper Inc." \
	    -font {Helvetica 11 normal} \
	    -justify center
	grid .aboutaqua.f.title -pady 2
	grid .aboutaqua.f.v -pady 2
	grid .aboutaqua.f.copyright -pady 2 -sticky we
	grid .aboutaqua.f  -padx 20 -pady 20 -sticky nswe
}

proc AquaMenus {} \
{
	menu .mb
	. configure -menu .mb
	menu .mb.apple -tearoff 0
	.mb.apple add command -label "About BitKeeper" -command AboutAqua
	.mb add cascade -menu .mb.apple
	menu .mb.help -tearoff 0
	.mb add cascade -menu .mb.help
	.mb.help add command \
	    -label "BitKeeper Help" -command {exec bk helptool &}
}

# Mac OS X needs a _real_ menubar 
if {[tk windowingsystem] eq "aqua"} {
	AquaMenus
}

proc GetTerminal {} {
	set term xterm
	if {[info exists ::env(TERMINAL)]} { set term $::env(TERMINAL) }
	if {[auto_execok $term] eq ""} { return }
	return $term
}

proc isComponent {path} {
	catch {exec bk repotype [file dirname $path]} res
	return [string equal $res "component"]
}

proc isChangeSetFile {path} {
	if {[file tail $path] eq "ChangeSet"
	    && [file isdir [file join [file dirname $path] BitKeeper etc]]} {
		return 1
	}
	return 0
}

proc sccsFile {type file} {
	return [file join [file dirname $file] SCCS $type.[file tail $file]]
}

proc sccsFileExists {type file} {
	set file [sccsFile $type $file]
	if {[catch {exec bk _test -f $file}]} { return 0 }
	return 1
}

proc inComponent {} {
    catch {exec bk repotype} res
    return [string equal $res "component"]
}

proc inRESYNC {} {
    set dir [file tail [exec bk root -S]]
    return [string equal $dir "RESYNC"]
}

## Attach any number of widgets (usually 2 diff widgets) to a single scrollbar.
## We remember the list of widgets for a given scrollbar and then make sure
## those widgets stay in sync when one of them is scrolled.
proc attachScrollbar {sb args} \
{
	global	gc

	set xy x
	if {[$sb cget -orient]  eq "vertical"} { set xy y }

	## Configure the scrollbar to call our custom scrolling function.
	$sb configure -command [list scrollWidgets $sb ${xy}view]

	## Keep track of which widgets are attached to this scrollbar
	## and then tell each widget what its new X/Y scrollcommand is.
	dict set gc(scrollbar.widgets) $sb $args
	foreach w $args {
		$w configure -${xy}scrollcommand [list setScrollbar $sb $w]
	}
}

## This gets called when you actually manipulate the scrollbar itself.  We
## just take the scrollbar, grab the list of widgets associated with it
## and scroll them all with the command given.
proc scrollWidgets {sb args} \
{
	global	gc

	## Just scroll everyone attached to the scrollbar with the same
	## command.
	foreach widg [dict get $gc(scrollbar.widgets) $sb] {
		$widg {*}$args
	}
}

## This gets called by an attached widget anytime something in the widget
## view has changed and it wants to update the scrollbar to tell it where
## it should be.  This happens on things like mousewheel movement and drag
## scrolling.
##
## Since the widget being controlled will already be moving by the proper
## amount, we just take any other widget in our list and make it match the
## exact coordinates that the primary widget is already at.
proc setScrollbar {sb w first last} \
{
	global	gc

	## Tell the scrollbar what to look like.
	$sb set $first $last
	if {![dict exists $gc(scrollbar.widgets) $sb]} { return }

	## Grab the current coordinates for the primary widget being scrolled.
	set x [lindex [$w xview] 0]
	set y [lindex [$w yview] 0]

	## Move all widgets that aren't the primary widget to the same point.
	foreach widg [dict get $gc(scrollbar.widgets) $sb] {
		if {$widg eq $w} { continue }
		$widg xview moveto $x
		$widg yview moveto $y
	}
}

proc scrollMouseWheel {w dir x y delta} \
{
	set widg [winfo containing $x $y]
	if {$widg eq ""} { set widg $w }

	switch -- [tk windowingsystem] {
	    "aqua"  { set delta [expr {-$delta}] }
	    "x11"   { set delta [expr {$delta * 3}] }
	    "win32" { set delta [expr {($delta / 120) * -3}] }
	}

	## If we fail to scroll the widget the mouse is
	## over for some reason, just scroll the widget
	## with focus.
	if {[catch {$widg ${dir}view scroll $delta units}]} {
		catch {$w ${dir}view scroll $delta units}
	}
}

proc isBinary { filename } \
{
    global	gc

    set fd [open $filename rb]
    set x  [read $fd $gc(ci.display_bytes)]
    catch {close $fd}
    return [regexp {[\x00-\x08\x0b\x0e-\x1f]} $x]
}

proc display_text_sizes {{onOrOff ""}} {
	if {$onOrOff ne ""} { set ::display_text_sizes $onOrOff }
	return $::display_text_sizes
}

set ::display_text_sizes on

proc displayTextSize {text w h} \
{
	if {!$::display_text_sizes} { return }
	if {![info exists ::textWidgets($text,w)]} { return }

	set oldW $::textWidgets($text,w)
	set oldH $::textWidgets($text,h)

	## Check to see if size has changed.
	if {abs($w - $oldW) <= 2 && abs($h - $oldH) <= 2} { return }

	set ::textWidgets($text,w) $w
	set ::textWidgets($text,h) $h

	## Don't do anything on the initial draw.
	if {$oldW == 0 && $oldH == 0} { return }

	if {[info exists ::textSize($text)]} {
		after cancel $::textSize($text)
	}

	if {$w <= 1 && $h <= 1} { return }

	set font  [$text cget -font]
	set fontW [font measure $font 0]
	set fontH [dict get [font metrics $font] -linespace]

	set cwidth  [expr {$w / $fontW}]
	set cheight [expr {$h / $fontH}]

	if {$cwidth <= 1 || $cheight <= 1} { return }

	set label $text.__size
	place $label -x 0 -y 0
	$label configure -text "${cwidth}x${cheight}"

	set ::textSize($text) [after 1000 [list hideTextDisplaySize $text]]
}

proc hideTextDisplaySize {w} \
{
	place forget $w.__size
}

## Trace the text command to grab new text widgets as they are
## created and add bind their <Configure> event to show text
## size when they are changed.
proc traceTextWidget {cmd code widget event} \
{
	set ::textWidgets($widget,w) 0
	set ::textWidgets($widget,h) 0

	## Bind the <Map> event so that the first time the text widget
	## is drawn, we configure our display text size popups.  Note
	## that the reason we do this is so that we don't display the
	## text size popups on the initial draw of the widget but only
	## after they've been resized at some point by the user.
	bind $widget <Map> {
	    bind %W <Map> ""
	    bind %W <Configure> "displayTextSize %%W %%w %%h"
	}
	label $widget.__size -relief solid -borderwidth 1 -background #FEFFE6
}
trace add exec text leave traceTextWidget


## This is actually overriding a core Tk proc that is called whenever
## the X11 paste selection code is called.  Where that code moves the
## insertion cursor before pasting, we just want to paste where the
## insert cursor already is.
proc ::tk::TextPasteSelection {w x y} \
{
    if {![catch {::tk::GetSelection $w PRIMARY} sel]} {
	    set oldSeparator [$w cget -autoseparators]
	    if {$oldSeparator} {
		    $w configure -autoseparators 0
		    $w edit separator
	    }
	    $w insert insert $sel
	    if {$oldSeparator} {
		    $w edit separator
		    $w configure -autoseparators 1
	    }
    }
    if {[$w cget -state] eq "normal"} {
	    focus $w
    }
}

## Override another Tk core proc.  Tk seems to think that on X11 we should
## not delete any current selection when pasting.  The more modern behavior
## is to always replace any current selection with the clipboard contents.
proc ::tk_textPaste {w} \
{
	global tcl_platform
	if {![catch {::tk::GetSelection $w CLIPBOARD} sel]} {
		set oldSeparator [$w cget -autoseparators]
		if {$oldSeparator} {
			$w configure -autoseparators 0
			$w edit separator
		}
		catch { $w delete sel.first sel.last }
		$w insert insert $sel
		if {$oldSeparator} {
			$w edit separator
			$w configure -autoseparators 1
		}
	}
}

#lang L
typedef struct hunk {
	int	li, ri;		/* left/right indices */
	int	ll, rl;		/* left/right lengths */
} hunk;


/*
 *    chg   index
 *    0
 *    0
 *    1 <--- a
 *    1
 *    1
 *    0 <--- b
 *    0
 *    0 <--- c
 *    1
 *    1
 *    1 <--- d
 *    0
 */
void
shrink_gaps(string S, int &chg[])
{
	int	i, n;
	int	a, b, c, d;
	int	a1, b1, c1, d1;

	n = length(chg);
	i = 0;
	/* Find find non-zero line for 'a' */
	while ((i < n) && (chg[i] == 0)) i++;
	if (i == n) return;
	a = i;
	/* Find next zero line for 'b' */
	while ((i < n) && (chg[i] == 1)) i++;
	if (i == n) return;
	b = i;

	while (1) {
		/* The line before the next 1 is 'c' */
		while ((i < n) && (chg[i] == 0)) i++;
		if (i == n) return;
		c = i - 1;

		/* The last '1' is 'd' */
		while ((i < n) && (chg[i] == 1)) i++;
		/* hitting the end here is OK */
		d = i - 1;

	again:
		/* try to close gap between b and c */
		a1 = a; b1 = b; c1 = c; d1 = d;
		while ((b1 <= c1) && (S[a1] == S[b1])) {
			a1++;
			b1++;
		}
		while ((b1 <= c1) && (S[c1] == S[d1])) {
			c1--;
			d1--;
		}

		if (b1 > c1) {
			/* Bingo! commit it */
			while (a < b) chg[a++] = 0;  /* erase old block */
			a = a1;
			while (a < b1) chg[a++] = 1;  /* write new block */
			a = a1;
			b = b1;
			while (d > c) chg[d--] = 0;
			d = d1;
			while (d > c1) chg[d--] = 1;
			c = c1;
			d = d1;

			/*
			 * now search back for previous block and start over.
			 * The last gap "might" be closable now.
			 */
			--a;
			c = a;
			while ((a > 0) && (chg[a] == 0)) --a;
			if (chg[a] == 1) {
				/* found a previous block */
				b = a+1;
				while ((a > 0) && (chg[a] == 1)) --a;
				if (chg[a] == 0) ++a;
				/*
				 * a,b nows points at the previous block
				 * and c,d points at the newly merged block
				 */
				goto again;
			} else {
				/*
				 * We were already in the first block so just 
				 * go on.
				 */
				a = a1;
				b = d+1;
			}
		} else {
			a = c+1;
			b = d+1;
		}
	}

}

/*
 * Move any remaining diff blocks align to whitespace boundaries if
 * possible. Adapted from code by wscott in another RTI.
 */
void
align_blocks(string S, int &chg[])
{
	int	a, b;
	int	n;

	n = length(chg);
	a = 0;
	while (1) {
		int	up, down;

		/*
		 * Find a sections of 1's bounded by 'a' and 'b'
		 */
		while ((a < n) && (chg[a] == 0)) a++;
		if (a >= n) return;
		b = a;
		while ((b < n) && (chg[b] == 1)) b++;
		/* b 'might' be at end of file */

		/* Find the maximum distance it can be shifted up */
		up = 0;
		while ((a-up > 0) && (S[a-1-up] == S[b-1-up]) &&
		    (chg[a-1-up] == 0)) {
			++up;
		}
		/* Find the maximum distance it can be shifted down */
		down = 0;
		while ((b+down < n) && (S[a+down] == S[b+down]) &&
		    (chg[b+down] == 0)) {
			++down;
		}
		if (up + down > 0) {
			int	best = 65535;
			int	bestalign = 0;
			int	i;

			/* for all possible alignments ... */
			for (i = -up; i <= down; i++) {
				int	a1 = a + i;
				int	b1 = b + i;
				int	cost = 0;

				/* whitespace at the beginning costs 2 */
				while (a1 < b1 && isspace(S[a1])) {
					cost += 2;
					++a1;
				}

				/* whitespace at the end costs only 1 */
				while (b1 > a1 && isspace(S[b1-1])) {
					cost += 1;
					--b1;
				}
				/* Any whitespace in the middle costs 3 */
				while (a1 < b1) {
					if (isspace(S[a1])) {
						cost += 3;
					}
					++a1;
				}
				/*
				 * Find the alignment with the lowest cost and
				 * if all things are equal shift down as far as
				 * possible.
				 */
				if (cost <= best) {
					best = cost;
					bestalign = i;
				}
			}
			if (bestalign != 0) {
				int	a1 = a + bestalign;
				int	b1 = b + bestalign;

				/* remove old marks */
				while (a < b) chg[a++] = 0;
				/* add new marks */
				while (a1 < b1) chg[a1++] = 1;
				b = b1;
			}
		}
		a = b;
	}
}

/*
 * Align the hunks such that if we find one char in common between
 * changed regions that are longer than one char, we mark the single
 * char as changed even though it didn't. This prevents the sl highlight
 * from matching stuff like foo|b|ar to a|b|alone #the b is common.
 */
hunk[]
align_hunks(string A, string B, hunk[] hunks)
{
	hunk	h, h1, nhunks[];
	int	x, y, lastrl, lastll;

	x = y = lastll = lastrl = 0;
	foreach (h in hunks) {
		if ((((h.li - x) <= h.ll) && ((h.li - x) <= lastll) &&
			isalpha(A[x..h.li - 1])) ||
		    (((h.ri - y) <= h.rl) && ((h.ri - y) <= lastrl) &&
			 isalpha(B[y..h.ri - 1]))) {
			h1.li = x;
			h1.ri = y;
			h1.ll = (h.li - x) + h.ll;
			h1.rl = (h.ri - y) + h.rl;
		} else {
			h1.li = h.li;
			h1.ri = h.ri;
			h1.ll = h.ll;
			h1.rl = h.rl;
		}
		lastll = h1.ll;
		lastrl = h1.rl;
		x = h.li + h.ll;
		y = h.ri + h.rl;
		push(&nhunks, h1);
	}
	return (nhunks);
}

/*
 * Compute the shortest edit distance using the algorithm from
 * "An O(NP) Sequence Comparison Algorithm" by Wu, Manber, and Myers.
 */
hunk[]
diff(string A, string B)
{
	int	M = length(A);
	int	N = length(B);
	int	D;
	int	reverse = (M > N) ? 1: 0;
	int	fp[], path[];
	struct {
		int	x;
		int	y;
		int	k;
	}	pc[];
	struct {
		int	x;
		int	y;
	}	e[];
	int	x, y, ya, yb;
	int	ix, iy;
	int	i, k, m, p, r;
	int	chgA[], chgB[], itmp[];
	string	tmp;
	hunk	hunks[], h;

	if (reverse) {
		tmp = A;
		A = B;
		B = tmp;
		M = length(A);
		N = length(B);
	}

	p = -1;
	fp = lrepeat(M+N+3, -1);
	path = lrepeat(M+N+3, -1);
	m = M + 1;
	D = N - M;

	do {
		p++;
		for (k = -p; k <= (D - 1); k++) {
			ya = fp[m+k-1] + 1;
			yb = fp[m+k+1];
			if (ya > yb) {
				fp[m+k] = y = snake(A, B, k, ya);
				r = path[m+k-1];
			} else {
				fp[m+k] = y = snake(A, B, k, yb);
				r = path[m+k+1];
			}
			path[m+k] = length(pc);
			push(&pc, {y - k, y, r});
		}
		for (k = D + p; k >= (D + 1); k--) {
			ya = fp[m+k-1] + 1;
			yb = fp[m+k+1];
			if (ya > yb) {
				fp[m+k] = y = snake(A, B, k, ya);
				r = path[m+k-1];
			} else {
				fp[m+k] = y = snake(A, B, k, yb);
				r = path[m+k+1];
			}
			path[m+k] = length(pc);
			push(&pc, {y - k, y, r});
		}
		ya = fp[m+D-1] + 1;
		yb = fp[m+D+1];
		if (ya > yb) {
			fp[m+D] = y = snake(A, B, D, ya);
			r = path[m+D-1];
		} else {
			fp[m+D] = y = snake(A, B, D, yb);
			r = path[m+D+1];
		}
		path[m+D] = length(pc);
		push(&pc, {y - D, y, r});
	} while (fp[m+D] < N);
	r = path[m+D];
	e = {};
	while (r != -1) {
		push(&e, {pc[r].x, pc[r].y});
		r = pc[r].k;
	}

	ix = iy = 0;
	x = y = 0;
	chgA = lrepeat(M, 0);
	chgB = lrepeat(N, 0);
	for (i = length(e)-1; i >= 0; i--) {
		while (ix < e[i].x || iy < e[i].y) {
			if (e[i].y - e[i].x > y - x) {
				chgB[iy] = 1;
				iy++; y++;
			} else if (e[i].y - e[i].x < y - x) {
				chgA[ix] = 1;
				ix++; x++;
			} else {
				ix++; x++; iy++; y++;
			}
		}
	}
	if (reverse) {
		tmp = A;
		A = B;
		B = tmp;
		itmp = chgA;
		chgA = chgB;
		chgB = itmp;
	}
	M = length(A);
	N = length(B);

	/* Now we need to minimize the changes by closing gaps */
	shrink_gaps(A, &chgA);
	shrink_gaps(B, &chgB);
	align_blocks(A, &chgA);
	align_blocks(B, &chgB);

	/* edit script length: D + 2 * p */
	for (x = 0, y = 0; (x < M) || (y < N); x++, y++) {
		if (((x < M) && chgA[x]) || ((y < N) && chgB[y])) {
			h.li = x;
			h.ri = y;
			for (; (x < M) && chgA[x]; x++);
			for (; (y < N) && chgB[y]; y++);
			h.ll = x - h.li;
			h.rl = y - h.ri;
			push(&hunks, h);
		}
	}
	hunks = align_hunks(A, B, hunks);
	return(hunks);
}

int
snake(string A, string B, int k, int y)
{
	int	x;
	int	M = length(A);
	int	N = length(B);

	x = y - k;
	while ((x < M) && (y < N) && (A[x] == B[y])) {
		x++;
		y++;
	}
	return (y);
}

int
slhSkip(hunk hunks[], int llen, string left, int rlen, string right)
{
	/* 
	 * If the subline highlight is more than this fraction
	 * of the line length, skip it. 
	 */
	float	hlfactor = gc("hlPercent");
	/*
	 * If the choppiness is more than this fraction of 
	 * line length, skip it.
	 */
	float	chopfactor = gc("chopPercent");

	/*
	 * Highlighting too much? Don't bother.
	 */
	if (llen > (hlfactor*length(left)) ||
	    rlen > (hlfactor*length(right))) {
		return (1);
	}
	/*
	 * Too choppy? Don't bother
	 */
	if ((length(hunks) > (chopfactor*length(left))) ||
	    (length(hunks) > (chopfactor*length(right)))) {
		return (1);
	}
	return (0);
}

// Do subline highlighting on two side-by-side diff widgets.
void
highlightSideBySide(widget left, widget right, string start, string stop, int prefix)
{
	int	i, line;
	string	llines[] = split(/\n/, (string)Text_get(left, start, stop));
	string	rlines[] = split(/\n/, (string)Text_get(right, start, stop));
	hunk	hunks[], h;
	int	llen, rlen;
	int	loff, roff;
	int	allspace;
	string	sl, sr;

	line = idx2line(start);
	for (i = 0; i < length(llines); ++i, ++line) {
		if ((llines[i][0..prefix] == " ") ||
		    (rlines[i][0..prefix] == " ")) continue;
		hunks = diff(llines[i][prefix..END], rlines[i][prefix..END]);
		unless (defined(hunks)) continue;
		llen = rlen = 0;
		allspace = 1;
		foreach (h in hunks) {
			llen += h.ll;
			rlen += h.rl;
			sl = llines[i][prefix+h.li..prefix+h.li+h.ll-1];
			sr = rlines[i][prefix+h.ri..prefix+h.ri+h.rl-1];
			if (sl != "") allspace = allspace && isspace(sl);
			if (sr != "") allspace = allspace && isspace(sr);
		}
		unless (allspace) {
			if (slhSkip(hunks,
			    llen, llines[i], rlen, rlines[i])) continue;
			foreach (h in hunks) {
				Text_tagAdd(left, "highlightold",
				    "${line}.${prefix + h.li}",
				    "${line}.${prefix + h.li + h.ll}");
				Text_tagAdd(right, "highlightnew",
				    "${line}.${prefix + h.ri}",
				    "${line}.${prefix + h.ri + h.rl}");
			}
		} else {
			loff = roff = 0;
			foreach (h in hunks) {
				h.li += loff;
				h.ri += roff;
				sl = Text_get(left,
				    "${line}.${prefix + h.li}",
				    "${line}.${prefix + h.li + h.ll}");
				sl = String_map({" ", "\u2423"}, sl);
				loff += length(sl);
				Text_tagAdd(left, "userData",
				    "${line}.${prefix + h.li}",
				    "${line}.${prefix + h.li + h.ll}");
				Text_insert(left,
				    "${line}.${prefix + h.li + h.ll}",
				    sl, "highlightsp bkMetaData");
				sr = Text_get(right,
				    "${line}.${prefix + h.ri}",
				    "${line}.${prefix + h.ri + h.rl}");
				sr = String_map({" ", "\u2423"}, sr);
				roff += length(sr);
				Text_tagAdd(right, "userData",
				    "${line}.${prefix + h.ri}",
				    "${line}.${prefix + h.ri + h.rl}");
				Text_insert(right,
				    "${line}.${prefix + h.ri + h.rl}",
				    sr, "highlightsp bkMetaData");
			}
		}
	}
}

// Do subline highlighting on stacked diff output in a single text widget.
void
highlightStacked(widget w, string start, string stop, int prefix)
{
	string	line;
	string	lines[];
	int	l = 0, hunkstart = 0;
	string	addlines[], sublines[];

	lines = split(/\n/, (string)Text_get(w, start, stop));
	/*
	 * Since the diffs are stacked, we don't want to highlight regions
	 * that are too big.
	 */
	//	if (length(lines) > 17) return;
	foreach (line in lines) {
		++l;
		if (line[0] == "+") {
			push(&addlines, line[prefix..END]);
			if (!hunkstart) hunkstart = l;
			if (l < length(lines)) continue;
		}
		if (line[0] == "-") {
			push(&sublines, line[prefix..END]);
			if (!hunkstart) hunkstart = l;
			if (l < length(lines)) continue;
		}
		if (defined(addlines) && defined(sublines)) {
			int	i;
			hunk	h, hunks[];
			int	lineA, lineB;
			int	llen, rlen;

			for (i = 0; hunkstart < l; ++hunkstart, ++i) {
				unless (defined(addlines[i])) break;
				unless (defined(sublines[i])) break;
				if (strlen(sublines[i]) > 1000 ||
				    strlen(addlines[i]) > 1000) break;
				hunks = diff(sublines[i], addlines[i]);
				lineA = hunkstart;
				lineB = hunkstart + length(sublines);
				unless (defined(hunks)) continue;
				llen = rlen = 0;
				foreach (h in hunks) {
					llen += h.ll;
					rlen += h.rl;
				}

				if (slhSkip(hunks,
				    llen, sublines[i], rlen, addlines[i])) {
					continue;
				}

				foreach (h in hunks) {
					Text_tagAdd(w, "highlightold",
					    "${lineA}.${h.li+prefix}",
					    "${lineA}.${h.li + h.ll +prefix}");
					Text_tagAdd(w, "highlightnew",
					    "${lineB}.${h.ri + prefix}",
					    "${lineB}.${h.ri + h.rl +prefix}");
				}
			}
		}
		hunkstart = 0;
		addlines = undef;
		sublines = undef;
	}
}

// getUserText
//
// Get data from a text widget as it actually is from the user. This means
// hiding any special characters or other bits we've inserted into the user's
// view and just returning them the real data.
//
// Currently this is only used for highlighted spaces as a result of the
// subline highlighting code, but this is where we want to add stuff in
// the future anytime we alter the user's view of the data.
string
getUserText(widget w, string idx1, string idx2)
{
	string	data;

	// Hide any BK characters we've inserted into the view and
	// show the actual user data as it was inserted.
	Text_tagConfigure(w, "userData", elide: 0);
	Text_tagConfigure(w, "bkMetaData", elide: 1);
	data = Text_get(w, displaychars: idx1, idx2);
	Text_tagConfigure(w, "userData", elide: 1);
	Text_tagConfigure(w, "bkMetaData", elide: 0);
	return (data);
}

/*
 * Windows gui doesn't have a stdout and stderr.
 * A straight system("foo") won't run foo because of this.
 * So put in a string and let the user know it happened.
 */
int
bk_system(string cmd)
{
	string	out, err;
	int	rc;

	if (!defined(rc = system(cmd, undef, &out, &err))) {
		tk_messageBox(title: "bk system",
			      message: "command: ${cmd}\n" . stdio_lasterr);
		return (undef);
	}
	if ((defined(out) && (out != "")) ||
	    (defined(err) && (err != ""))) {
		if (defined(out)) out = "stdout:\n" . out;
		if (defined(err)) err = "stderr:\n" . err;
		if (rc) {
			bk_error("bk system",
				 "command:\n${cmd}\n"
				 "${out}"
				 "${err}");
		} else {
			bk_message("bk system",
				 "command:\n${cmd}\n"
				 "${out}"
				 "${err}");
		}
	}
	return (rc);
}

/*
 * given "line.col" return just line
 */
int
idx2line(string idx)
{
	return ((int)split(/\./, idx)[0]);
}

void
configureDiffWidget(string app, widget w, ...args)
{
	string	which = args[0];

	// Old diff tag.
	Text_tagConfigure(w, "oldDiff",
	    font: gc("${app}.oldFont"),
	    background: gc("${app}.oldColor"));

	// New diff tag.
	Text_tagConfigure(w, "newDiff",
	    font: gc("${app}.newFont"),
	    background: gc("${app}.newColor"));

	if (defined(which)) {
		string	oldOrNew = which;

		// Standard diff tag.
		Text_tagConfigure(w, "diff",
		    font: gc("${app}.${oldOrNew}Font"),
		    background: gc("${app}.${oldOrNew}Color"));

		// Active diff tag.
		if (!gc("${app}.activeNewOnly") || oldOrNew == "new") {
			oldOrNew[0] = String_toupper(oldOrNew[0]);
			Text_tagConfigure(w, "d",
			    font: gc("${app}.active${oldOrNew}Font"),
			    background: gc("${app}.active${oldOrNew}Color"));
		}
	}

	// Highlighting tags.
	Text_tagConfigure(w, "select", background: gc("${app}.selectColor"));
	Text_tagConfigure(w, "highlightold",
			  background: gc("${app}.highlightOld"));
	Text_tagConfigure(w, "highlightnew",
			  background: gc("${app}.highlightNew"));
	Text_tagConfigure(w, "highlightsp",
	    background: gc("${app}.highlightsp"));
	Text_tagConfigure(w, "userData", elide: 1);
	Text_tagConfigure(w, "bkMetaData", elide: 0);

	// Message tags.
	Text_tagConfigure(w, "warning", background: gc("${app}.warnColor"));
	Text_tagConfigure(w, "notice", background: gc("${app}.noticeColor"));

	// Various other diff tags.
	Text_tagConfigure(w, "empty", background: gc("${app}.emptyBG"));
	Text_tagConfigure(w, "same", background: gc("${app}.sameBG"));
	Text_tagConfigure(w, "space", background: gc("${app}.spaceBG"));
	Text_tagConfigure(w, "changed", background: gc("${app}.changedBG"));

	if (defined(which)) {
		Text_tagConfigure(w, "minus",
		    background: gc("${app}.${which}Color"));
		Text_tagConfigure(w, "plus",
		    background: gc("${app}.${which}Color"));
		if (!gc("${app}.activeNewOnly") || which == "new") {
			Text_tagRaise(w, "d");
		}
	}

	Text_tagRaise(w, "highlightold");
	Text_tagRaise(w, "highlightnew");
	Text_tagRaise(w, "highlightsp");
	Text_tagRaise(w, "sel");
}

private int	debug{string};
private	FILE	debugf = undef;

void
debug_enter(string cmd, string op)
{
	unless (cmd) return;

	op = op;
	debug{cmd} = Clock_microseconds();
	fprintf(debugf, "ENTER ${cmd}\n");
	flush(debugf);
}

void
debug_leave(string cmd, int code, string result, string op)
{
	float	t;

	unless (cmd) return;

	op = op;
	code = code;
	result = result;
	if (defined(debug{cmd})) {
		t = Clock_microseconds() - debug{cmd};
		undef(debug{cmd});
	}

	if (defined(t)) {
		fprintf(debugf, "LEAVE ${cmd} (${t} usecs)\n");
	} else {
		fprintf(debugf, "LEAVE ${cmd}\n");
	}
	flush(debugf);
}

void
debug_init(string var)
{
	string	proc, procs[];

	unless (defined(var) && length(var)) return;
	if (var =~ m|^/|) debugf = fopen(var, "w");
	unless (defined(debugf)) debugf = stderr;

	procs = Info_procs("::*");
	foreach (proc in procs) {
		if (proc =~ /^::auto_/) continue;
		if (proc =~ /^::debug_/) continue;
		if (proc =~ /^::unknown/) continue;
		if (proc =~ /^::fprintf/) continue;
		Trace_addExec(proc, "enter", &debug_enter);
		Trace_addExec(proc, "leave", &debug_leave);
	}
}

string
bk_repogca(int standalone, string url, string &err)
{
	string	gca;
	string	opts = "--only-one";

	if (standalone) opts .= " -S";
	if (url && length(url)) opts .= " '${url}'";
	if (system("bk repogca ${opts}", undef, &gca, &err) != 0) {
		return (undef);
	}
	return (trim(gca));
}

void
bk_message(string title, string message)
{
	if (defined(getenv("BK_REGRESSION"))) {
		puts("stdout", message);
	} else {
		tk_messageBox(title: title, message: message);
	}
}

void
bk_error(string title, string message)
{
	if (defined(getenv("BK_REGRESSION"))) {
		puts("stderr", message);
	} else {
		tk_messageBox(title: title, message: message);
	}
}

void
bk_die(string message, int exitCode)
{
	bk_message("BitKeeper", message);
	exit(exitCode);
}

void
bk_dieError(string message, int exitCode)
{
	bk_error("BitKeeper Error", message);
	exit(exitCode);
}

void
bk_usage()
{
	string	usage;
	string	tool = basename(Info_script());

	system("bk help -s ${tool}", undef, &usage, undef);
	puts(usage);
	exit(1);
}
#lang tcl
# Copyright (c) 2001, Bryan Oakley
# All Rights Reservered
#
# Bryan Oakley
# oakley@bardo.clearlight.com
#
# tkwizard 1.01
#
# this code is freely distributable without restriction, and is 
# provided as-is with no warranty expressed or implied. 
#
# Notes: 
#
# styles are used to define the overall organization. Most notably,
# the button placement. But styles could also add other areas to the
# wizard, too. 
#
# layouts are used to define the look of a particular step -- all the 
# stuff that is different for each step.
#
# this file is slightly modified from the version Bryan keeps on his
# external website. Primarily, it has been reformatted slightly (proc
# opening curly braces have been moved down a line. )

interp alias {} debug {} tk_messageBox -message

package require Tk 8.0
package provide tkwizard 1.00

# create the package namespace, and do some basic initialization
namespace eval ::tkwizard {

    variable initialized 0
    variable updateID    {}
    variable layoutData
    variable styleData 

    namespace export tkwizard
    
    set ns ::tkwizard

    # define class bindings
    bind Wizard <<WizNextStep>> [list ${ns}::handleEvent %W <<WizNextStep>>]
    bind Wizard <<WizBackStep>> [list ${ns}::handleEvent %W <<WizBackStep>>]
    bind Wizard <<WizCancel>>   [list ${ns}::handleEvent %W <<WizCancel>>]
    bind Wizard <<WizFinish>>   [list ${ns}::handleEvent %W <<WizFinish>>]
    bind Wizard <<WizSelectStep>> \
        [list ${ns}::handleEvent %W <<WizSelectStep>> %\#]
    
    # create a default image
    image create photo ::tkwizard::logo -data {
       R0lGODlhIAAgALMAANnZ2QAAwAAA/wBAwAAAAICAgAAAgGBggKCgpMDAwP//
       /////////////////////yH5BAEAAAAALAAAAAAgACAAAAT/EMhJq60hhHDv
       pVCQYohAIJBzFgpKoSAEAYcUIRAI5JSFlkJBCGLAMYYIIRAI5ASFFiqDgENK
       EUIwBAI5ywRlyhAEHFKKEEIgEMgJyiwUBAGHnCKEEAyBQM4yy5RhCDikFDBI
       SSCQExRKwxBDjAGHgEFKQyCQk9YgxBBjDAGDnAQCOWkNQgwxxDgwyGkIBHJS
       GoQQYohRYJDTEAjkpDWIIYQQBQY5A4FATlqDEEIMgWCQMxgCgZy0BiikRDDI
       GQyBQE5aAxRSIhjkNIRAICetAQop04BBTgOBnLTKIIQQacAgZzAQyEkrCEII
       kQYMckoDgZy0giCESAMGOaWBQMoydeeUQYhUYJBTGgikLHNOGYRACQY5pYFA
       yjLnnEGgNGCQMxgAACgFAjnpFEUNGOQ0BgI5Z6FUFlVgkJNAICctlMqiyggB
       BkMIBHLOUiidSUEiJwRyzlIopbJQSilFURJUIJCTVntlKhhjCwsEctJqr0wF
       Y0xhBAA7
    }

    # Make a class binding to do some housekeeping
    bind Wizard <Destroy> [list ${ns}::wizard-destroy %W]

    # add a few handy option db entries for some decorative frames
    option add *WizSeparator*BorderWidth        2 startupFile
    option add *WizSeparator*Relief        groove startupFile
    option add *WizSeparator*Height             2 startupFile

    option add *WizSpacer*Height                2 startupFile
    option add *WizSpacer*BorderWidth           0 startupFile
    option add *WizSpacer*Relief           groove startupFile
}

## 
# tkwizard
#
# usage: tkwizard pathName ?options?
#
# creates a tkwizard
#
proc ::tkwizard::tkwizard {name args} \
{

    set body {}

    # the argument -style is a create-time-only option. Thus, if it
    # appears in args we need to handle it specially.
    set i [lsearch -exact $args "-style"]
    if {$i >= 0} {

        set j [expr {$i + 1}]
        set style [lindex $args $j]
        set args [lreplace $args $i $j]
        init $name $style

    } else {
        init $name default
    }

    if {[catch "\$name configure $args" message]} {
        destroy $name
        return -code error $message
    }

    return $name
}

##
# info
#
# usage: tkwizard::info option
#
# option: 
#   layouts               returns the list of known layouts
#   layout <type> <name>  eg: info layout description Basic-1
#   styles                returns the list of known layouts
#   style <type> <name>   eg: info style description Basic-1

proc ::tkwizard::info {option args} \
{
    variable layoutData
    variable styleData

    switch -exact -- $option {

        layouts {
            set layouts {}
            foreach key [array names layoutData *,-description] {
                set name [lindex [split $key ,] 0]
                lappend layouts $name
            }
            return $layouts
        }

        layout {
            switch -exact -- [lindex $args 0] {
                "description" {
                    return $layoutData([lindex $args 1],-description)
                }
            }
        }

        styles {
            set styles {}
            foreach key [array names styleData *,-description] {
                set name [lindex [split $key ,] 0]
                lappend styles $name
            }
            return $styles
        }

        style {
            switch -exact -- [lindex $args 0] {
                "description" {
                    return $styleData([lindex $args 1],-description)
                }
            }
        }
    }
}

##
# wizard-destroy
#
# does cleanup of the wizard when it is destroyed. Specifically,
# it destroys the associated namespace and command alias
# 
proc ::tkwizard::wizard-destroy {name} \
{

    upvar #0 ::tkwizard::@$name-state wizState

    if {[::info exists wizState]} {

        set w $wizState(window)
        interp alias {} $wizState(alias) {}
        catch {namespace delete $wizState(namespace)} message
    }

    return ""
}


##
# wizProxy
#
# this is the procedure that represents the wizard object; each
# wizard will be aliased to this proc; the wizard name will be
# provided as the first argument (this is transparent to the caller)

proc ::tkwizard::wizProxy {name args} \
{

    # No args? Throw an error.
    if {[llength $args] == 0} {
        return -code error "wrong \# args: should be $name \"$option\" ?args?"
    }

    # The first argument is the widget subcommand. Make sure it's valid.
    set command [lindex $args 0]
    set commands [::info commands ::tkwizard::wizProxy-*]

    if {[lsearch -glob $commands *wizProxy-$command] == -1} {
        set allowable [list]
        foreach c [lsort $commands] {
            regexp {[^-]+-(.*)$} $c -> tmp
            lappend allowable $tmp
        }
        return -code error "bad option \"$command\":\
                            must be [join $allowable {, }]"
    }

    # Call the worker proc
    eval wizProxy-$command $name [lrange $args 1 end]
}

##
# wizProxy-cget 
#
# usage: pathName cget option
#
proc ::tkwizard::wizProxy-cget {name args} \
{
    upvar #0 ::tkwizard::@$name-config wizConfig

    # Check for valid number of arguments
    if {[llength $args] != 1} {
        return -code error "wrong \# args: should be \"$name cget option\""
    }

    # Fetch requested value
    set option [lindex $args 0]
    if {[::info exists wizConfig($option)]} {
        return $wizConfig($option)
    } 

    # Apparently the caller gave us an unknown option. So, we'll throw 
    # a pie in their face...
    return -code error "unknown option \"$option\""
}

##
# wizProxy-configure
#
# usage: pathName configure ?option? ?value option value ...?
#
proc ::tkwizard::wizProxy-configure {name args} \
{
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-state  wizState

    # If we were given no arguments we must return all options. This
    # isn't fully functioning yet; but is good enough for the alpha
    # versions of the code
    if {[llength $args] == 0} {
        set result [list]
        foreach item [lsort [array names wizConfig]] {
            lappend result [list $item $wizConfig($item) [string trimleft $item -]]
        }
        return $result

    }

    # One argument is synonymous with the cget method
    if {[llength $args] == 1} {
        return [uplevel $name cget [lindex $args 0]]

    }
    
    # More than one argument means we have to set some values. 
    foreach {option value} $args {

        # First we'll do some validation
        if {![::info exists wizConfig($option)]} {
            return -code error "unknown option \"$option\""
        }

        # and one sanity check
        if {[string match $option* "-style"]} {
            return -code error "can't modify -style after widget is created"
        }

        # set the value; only do this if it has changed in case there
        # are triggers set on the variable
        if {"$wizConfig($option)" == "$value"} {
            continue
        }
        set wizConfig($option) $value

        # Some attributes require additional processing
        switch -exact -- $option {
	    -height -
	    -width {
		$wizConfig(toplevel) configure $option $value
	    }
            -background {
                $wizConfig(toplevel) configure -background $value
            }
            -title {
                # Set the wizard title
                wm title $name $value
            }
            -step {
                showStep $name $wizConfig(-path) $wizConfig(-step)
            }
            -defaultbutton -
            -icon -
            -sequential -
            -path {
                updateDisplay $name
            }
            -state {
                # The trailing "0" means "do this right now; don't wait
                # for an idle handler". We do this because the state 
                # change might require the setting of a cursor or
                # statusbar...
                updateDisplay $name 0
            }
        }
    }

    # generate an event so other interested parties
    # (eg: the layout and style code) can adjust their
    # colors accordingly
    event generate $name <<WizConfig>>

}

##
# wizProxy-default
#
# sets the default button and binds <Return> to invoke that button

proc ::tkwizard:wizProxy-default {name button} \
{

    if {[lsearch -exact {none next back cancel finish} $button] == -1} {
        return -code error "unknown button \"$button\": must be\
           one of back, cancel, finish, next or none"
    }

    eval ${name}::~style default $button

}

##
# wizProxy-hide
#
# usage: pathName hide
#
# Hides the wizard without destroying it. We do nothing with the
# state, but note that a subsequent 'show' will reset the state

proc ::tkwizard::wizProxy-hide {name args} \
{
    upvar #0 ::tkwizard::@$name-state wizState

    wm withdraw $wizState(window)
}

##
# wizProxy-add
# 
# usage: pathName add path name ?option value option value ...?
#        pathName add step name ?option value option value ...?
#
proc ::tkwizard::wizProxy-add {name type itemname args} \
{
   upvar #0 ::tkwizard::@$name-state wizState

   switch -- $type {
      "path" {
         if {[::info exists wizState(steps,$itemname)]} {
            return -code error "path \"$itemname\" already exists"
         }
         set wizState(steps,$itemname) [list]
         lappend wizState(paths) $itemname

          # If the caller provided additional arguments, pass them
          # to the pathconfigure method
         if {[llength $args] > 0} {
            eval wizProxy-pathconfigure \$name \$itemname $args
         }
      }

      "step" {
          eval newStep \$name \$itemname $args
      }
   }

   return $itemname
}

proc ::tkwizard::wizProxy-pathconfigure {name pathname args} \
{
   upvar #0 ::tkwizard::@$name-state wizState

   if {![::info exists wizState(steps,$pathname)]} {
      return -code error "path \"$pathname\" doesn't exist"
   }

   if {[llength $args] == 0} {
       return [list [list -steps $wizState(steps,$pathname)]]
   }

   if {[llength $args] == 1} {
       if {[equal [lindex $args 0] "-steps"]} {
         return $wizState(steps,$pathname)
      } else {
         return -code error "unknown option \"[lindex $args 0]\""
      }
   }

   foreach {name value} $args {
      switch -- $name {
         "-steps" {
            set wizState(steps,$pathname) $value
            updateDisplay $name
         }
         default {
            return -code error "unknown option \"$name\""
         }
      }
   }
}

proc ::tkwizard::wizProxy-delete {name type itemname} \
{
    upvar #0 ::tkwizard::@$name-state wizState

    switch -- $type {
	"path" {
	    if {![::info exists wizState(steps,$itemname)]} {
		return -code error "path \"$itemname\" doesn't exist"
	    }
	    unset wizState(steps,$itemname) 
	    set i [lsearch -exact $wizState(paths) $itemname]
	    if {$i >= 0} {
		# the above condition should always be true unless
		# our internal state gets corrupted.
		set wizState(paths) [lreplace $wizState(paths) $i $i]
	    }
	}
	"step" {
	    return -code error "not implemented yet"
	}
    }
    
}

##
# newStep
#
# implements the "step" method of the wizard object. The body
# argument is code that will be run when the step identified by
# 'stepName' is to be displayed in the wizard
#
# usage: wizHandle step stepName ?option value ...? 
# options: -description, -layout, -title, -icon, -body
#

proc ::tkwizard::newStep {name stepName args} \
{

    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    set stepConfig($stepName,-icon)         $wizConfig(-icon)
    set stepConfig($stepName,-description) ""
    set stepConfig($stepName,-layout)      default
    set stepConfig($stepName,-title)       $stepName
    set stepConfig($stepName,-body)        ""
        
    # Store the step options
    eval wizProxy-stepconfigure \$name \$stepName $args

    # wizState(steps) is the master list of steps
    lappend wizState(steps) $stepName

    # wizState(steps,Default) is the default path, and contains all
    # of the steps. It seems redundant to keep all steps in two places,
    # but it is possible for someone to redefine the steps in the Default
    # step, and we don't want that to affect the master list of steps
    lappend wizState(steps,Default) $stepName
}

# this code executes the body of a step in the appropriate context
proc ::tkwizard::initializeStep  {name stepName} \
{
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    namespace eval ::tkwizard::${name} [list set this $name]
    namespace eval ::tkwizard::${name} $stepConfig($stepName,-body)
}

##
# wizProxy-widget
#
# Returns the path to an internal widget, or executes the
# an internal widget command
#
# usage: wizHandle widget widgetName ?args?
#
# if [llength $args] > 0 it will run the widget command with
# the args. Otherwise it will return the widget path

proc ::tkwizard::old-wizProxy-widget {name args} \
{
    upvar #0 ::tkwizard::@$name-state wizState

    if {[llength $args] == 0} {
        # return a list of all widget names
        set result [list]
        foreach item [array names wizState widget,*] {
            regsub {widget,} $item {} item
            lappend result $item
        }
        return $result
    }

    set widgetname [lindex $args 0]
    set args [lrange $args 1 end]

    if {![::info exists wizState(widget,$widgetname)]} {
        return -code error "unknown widget: \"$widgetname\""
    }

    if {[llength $args] == 0} {
        return $wizState(widget,$widgetname)
    }

    # execute the widget command
    eval [list $wizState(widget,$widgetname)] $args
}

##
# wizProxy-info
#
# Returns the information in the state array
# 
# usage: pathName info steps ?pattern?
#        pathName info paths ?pattern?
#        pathName info workarea
#        pathName info namespace
#
# pattern is a glob-style pattern; if not supplied, "*" is used

proc ::tkwizard::wizProxy-info {name args} \
{

    upvar #0 ::tkwizard::@$name-state  wizState
    upvar #0 ::tkwizard::@$name-widget wizWidget

    if {[llength $args] == 0} {
        return -code error "wrong \# args: should be \"$name info option\""
    }

    switch -exact -- [lindex $args 0] {
        paths {
            set result [list]
            set pattern "*"
            if {[llength $args] > 1} {set pattern [lindex $args 1]}
            foreach path $wizState(paths) {
                if {[string match $pattern $path]} {
                    lappend result $path
                }
            }
            return $result
        }

        steps {
            set result [list]
            set pattern "*"
            if {[llength $args] > 1} {set pattern [lindex $args 1]}
            foreach step $wizState(steps) {
                if {[string match $pattern $step]} {
                    lappend result $step
                }
            }
            return $result
        }

        workarea {
            return [${name}::~layout workarea]
        }

        namespace {
            set ns ::tkwizard::${name}
            return $ns
        }

        default {
            return -code error "bad option \"[lindex $args 0]\":\
                                should be workarea, paths or steps"
        }
    }
}

##
# wizProxy-eval 
#
# usage: pathName eval arg ?arg ...?
#
proc ::tkwizard::wizProxy-eval {name code} \
{
    set ns ::tkwizard::${name}
    namespace eval $ns $code
}
    
##
# wizProxy-show
# 
# Causes the wizard to be displayed in it's initial state
#
# usage: wizHandle show
#
# This is where all of the widgets are created, though eventually
# I'll probably move the widget drawing to a utility proc...
proc ::tkwizard::wizProxy-show {name args} \
{
    variable initialized

    upvar #0 ::tkwizard::@$name-state  wizState
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-widget wizWidget

    # reset the wizard state
    set wizState(history)         [list]

    set currentPath $wizConfig(-path)
    set steps $wizState(steps,$currentPath)
    set firstStep [lindex $steps 0]
    if {[llength $steps] == 0} {
        # no steps? Just show it as-is.
        wm deiconify $name
        return
    }

    set initialized 1

    # show the first step
    showStep $name $currentPath $firstStep

    # make it so, Number One
    wm deiconify $wizState(window)

    # This makes sure closing the window with the window manager control
    # Does The Right Thing (I *think* this is what The Right Thing is...)
    wm protocol $name WM_DELETE_WINDOW \
        [list event generate $name <<WizCancel>>]

    return ""
}

# Called with no second argument, just register an after idle handler. 
# Called with a second arg of zero (which is what the after idle event
# does) causes the display to be updated
proc ::tkwizard::updateDisplay {name {schedule 1}} \
{
    variable initialized
    variable updateID

    if {$schedule} {
        catch {after cancel $updateID} message
        set command "[list updateDisplay $name 0]; [list set updateID {}]"
        set updateID [after idle [namespace code $command]]
        return
    }


    if {!$initialized} return

    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    set step   $wizConfig(-step)
    set layout $stepConfig($step,-layout)
    set path   $wizConfig(-path)

    # update the map
    ${name}::~layout updatemap

    # update the step data
    ${name}::~layout updatestep $step

    # update the wizard buttons
    updateButtons $name
}

# Causes a step to be built by clearing out the current contents of
# the client window and then executing the initialization code for
# the given step

proc ::tkwizard::buildStep {name step}  \
{
    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig
    upvar #0 ::tkwizard::@$name-widget     wizWidget

    set step $wizConfig(-step)
    set layout $stepConfig($step,-layout)

    # reset the state of the windows in the wizard
    ${name}::~layout clearstep

    # update the visual parts of the wizard
    updateDisplay $name

    # initialize the step
    initializeStep $name $step

}

# This block of code is common to all wizard actions. 
# (ie: it is the target of the -command option for wizard buttons)
proc ::tkwizard::xcmd {command name {arg {}}} \
{

    upvar #0 ::tkwizard::@$name-state  wizState
    upvar #0 ::tkwizard::@$name-config wizConfig

    switch $command {
        Next       {event generate $name <<WizNextStep>>}
        Previous   {event generate $name <<WizBackStep>>}
        Finish     {event generate $name <<WizFinish>>}
        Cancel     {event generate $name <<WizCancel>>}
        SelectStep {
            event generate $name <<WizSelectStep>> -serial $arg
        }

        default {
            # This should never happen since we have control over how
            # this proc is called. It doesn't hurt to put in the default
            # case, if for no other reason to document this fact.
            puts "'$command' not implemented yet"
        }
    }
}

# Since I'm striving for compatibility with tcl/tk 8.0 I can't make
# use of [string equal], so this makes the code more readable and 
# more portable
proc ::tkwizard::equal {string1 string2} \
{
    if {[string compare $string1 $string2] == 0} {
        return 1
    } else {
        return 0
    }
}

proc ::tkwizard::handleEvent {name event args} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    # define some shorthand
    set path      $wizConfig(-path)
    set steps     $wizState(steps,$path)
    set stepIndex [lsearch -exact $steps $wizConfig(-step)]

    switch $event {

        <<WizNextStep>> {
            if {![equal $wizConfig(-step) [lindex $steps end]]} {
                set i [expr {$stepIndex + 1}]
                set step [lindex $wizState(steps,$path) $i]
                showStep $name $path $step
            }
        }

        <<WizBackStep>> {

            incr stepIndex -1
            set step [lindex $steps $stepIndex]
            set wizConfig(-step) $step
            showStep $name $path $step
        }

        <<WizFinish>> {

            wizProxy-hide $name
        }

        <<WizCancel>> {

            wizProxy-hide $name
        }

        <<WizSelectStep>> {
            if {!$wizConfig(-sequential) && \
                    [equal $wizConfig(-state) "normal"]} {
                set n [lindex $args 0]
                set path $wizConfig(-path)
                set steps $wizState(steps,$path)
                set wizConfig(-step) [lindex $steps $n]
                showStep $name
            }
        }

        default {
            puts "'$event' not implemented yet"
        }
    }
}

proc ::tkwizard::showStep {name args} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    if {[llength $args] > 0} {
        set path [lindex $args 0]
        set step [lindex $args 1]
    } else {
        set path $wizConfig(-path)
        set step $wizConfig(-step)
    }

    set wizConfig(-step) $step

    if {![::info exists stepConfig($step,-layout)]} {
	    set stepConfig($step,-layout) "default"
    }
    set layout $stepConfig($step,-layout)

    # Build the appropriate layout
    buildLayout $name $layout

    # Set the state to "normal". The step can override this if necessary,
    # but this is a reasonable behavior for most simple wizards
    set wizConfig(-state) "normal"

    # Build the step
    buildStep $name $step

    # focus on the default button; the step may override
    ${name}::~style focus $wizConfig(-defaultbutton)
}

proc ::tkwizard::init {name {style {default}}} \
{

    # name should be a widget path
    set w $name

    # create variables in this namespace to keep track
    # of the state of this wizard. We do this here to 
    # avoid polluting the namespace of the widget. We'll
    # create local aliases for the variables to make the
    # code easier to read and write

    # this variable contains state information about the 
    # wizard, such as the wizard title, the name of the 
    # window and namespace associated with the wizard, the
    # list of steps, and so on.
    variable "@$name-state"
    upvar \#0 ::tkwizard::@$name-state wizState

    # this variable contains all of the parameters associated
    # with the wizard and settable with the "configure" method
    variable "@$name-config"
    upvar \#0 ::tkwizard::@$name-config wizConfig

    # this variable contains information on the wizard buttons
    variable "@$name-buttonConfig"
    upvar \#0 ::tkwizard::@$name-config buttonConfig

    # this contains step-specific data, such as the step title,
    # icon, etc. All elements are unset prior to rendering a given 
    # step. It is each step's responsibility to set it appropriately, 
    # and it is each step type's responsibility to use the data.
    variable "@$name-stepConfig"
    upvar \#0 ::tkwizard::@$name-stepConfig  stepConfig

    #---
    # do some state initialization; more will come later when
    # the wizard is actually built
    #---

    # These are internal values managed by the widget code and are
    # not directly settable by the user
    # window:    widget path of wizard toplevel window
    # namespace: name of namespace associated with wizard
    # toplevel:  name of actual toplevel widget proc, which will have
    #            been renamed into the wizard namespace
    set wizState(paths)        [list Basic]
    set wizState(steps,Basic)  [list]

    set wizState(title)        ""
    set wizState(window)       $w
    set wizState(namespace)    ::tkwizard::$name
    set wizState(name)         $name
    set wizState(toplevel)     {}

    # These relate to options settable via the "configure" subcommand
    # -sequential if true, wizard steps must be accessed sequentially
    # -path:      the current path
    # -step:      the current step
    # -title:     string to show in titlebar 
    # -state:     state of wizard; "normal" or "busy"
    set wizConfig(-defaultbutton) "next"
    set wizConfig(-sequential)     1
    set wizConfig(-cursor)         {}
    set wizConfig(-path)           Default
    set wizConfig(-step)           ""
    set wizConfig(-state)          normal
    set wizConfig(-title)          ""
    set wizConfig(-icon) 	      ::tkwizard::logo

    if {[string length $style] > 0} {
        # use the style given to us
        set wizConfig(-style) $style
    } else {
        # use the default style
        set wizConfig(-style) "default"
    }

    # create the wizard shell (ie: everything except for the step pages)
    buildDialog $name

    # this establishes a namespace for this wizard; this namespace
    # will contain wizard-specific data managed by the creator of
    # the wizard
    namespace eval $name {}

    # this creates the instance command by first renaming the widget
    # command associated with our toplevel, then making an alias 
    # to our own command
    set wizState(toplevel) $wizState(namespace)::originalWidgetCommand
    rename $w $wizState(toplevel)
    interp alias {} ::$w {} ::tkwizard::wizProxy $name
    set wizState(alias) ::$w

    # set some useful configuration values
    set wizConfig(-background) \
        [$wizState(namespace)::originalWidgetCommand cget -background]
}

# my long term plan is to perhaps some day ship a bevy of styles,
# and create a wizard wizard that will show available styles to
# let you pick from them. The register command is to facilitate that.
proc ::tkwizard::registerStyle {styleName args} \
{
    variable styleData
    
    set styleData($styleName,-description) ""
    set styleData($styleName,-command)     ""

    foreach {arg value} $args {
        switch -- $arg {
            -description {
                set styleData($styleName,$arg) $value
            }
            -command {
                set styleData($styleName,-command) \
                    "[uplevel [list namespace current]]::$value"
            }
            default {
                return -code error "invalid style attribute \"$arg\":\
                                    must be <whatever>"
            }
        }
    }
}

# my long term plan is to perhaps some day ship a bevy of layouts,
# and create a wizard wizard that will show available layouts to
# let you pick from them. The register command is to facilitate that.
proc ::tkwizard::registerLayout {layoutName args} \
{
    variable layoutData

    # set some defaults
    set layoutData($layoutName,-description) ""
    set layoutData($layoutName,-command)     ""
    
    # overwrite defaults with values passed in
    foreach {arg value} $args {
        switch -exact -- $arg {
            -description {
                set layoutData($layoutName,$arg) $value
            }
            -command {
                set layoutData($layoutName,-command) \
                    "[uplevel [list namespace current]]::$value"
            }
            default {
                return -code error "invalid layout attribute \"$arg\":\
                                    must be -command or -description"
            }
       }
    }
}

proc ::tkwizard::buildDialog {name} \
{

    variable styleData

    upvar #0 ::tkwizard::@$name-state wizState
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-widget wizWidget

    set wizState(visible,nextButton)    1
    set wizState(visible,backButton)    1
    set wizState(visible,cancelButton)  1
    set wizState(visible,finishButton)  1

    # create the toplevel window. "." is treated specially. Any other
    # value must not exist as a window, but we want the ability to 
    # make "." a wizard so folks can write standalone wizard apps
    set w $wizState(window)
    if {$w == "."} {
        . configure -bd 2 -relief groove -cursor $wizConfig(-cursor)
        bindtags . [list . Wizard all]
    } else {
        toplevel $w -class Wizard \
            -bd 2 -relief groove -cursor $wizConfig(-cursor)
    }
    wm title $w $wizConfig(-title)

    # create an alias to the public interface of the current
    # style, then initialize the dialog using the style public interface.
    # We can create this alias here because, unlike layouts, styles can
    # only be set when the widget is first created. It's not possible to
    # change styles at runtime.
    interp alias {} ::tkwizard::${name}::~style \
                 {} $styleData($wizConfig(-style),-command) $name

    set wizWidget(path,layoutFrame) [${name}::~style init]

    # return the name of the toplevel, for lack of a better idea...
    return $wizState(window)
}

proc ::tkwizard::buildLayout {name layoutName} \
{
    variable layoutData

    upvar #0 ::tkwizard::@$name-state wizState
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-widget wizWidget

    set w $wizState(window)
    set lf $wizWidget(path,layoutFrame)

    # reset the layout alias to point to this layout
    # (with the assumption being, we are in this proc because
    # we are setting this particular layout to the current layout...)

    interp alias {} ::tkwizard::${name}::~layout \
                 {} $layoutData($layoutName,-command) $name

    ${name}::~layout init $lf

    # remove any previously displayed layout
    eval pack forget [winfo children $lf]

    # ... and display this one.
    pack $lf.frame-$layoutName -side top -fill both -expand y

}

##
# wizProxy-stepconfigure
#
# usage: pathName stepconfigure stepName ?options?
#
# options:
#   -icon         name of an image to associate with this step
#   -description  string to describe the step
#   -layout       name of layout to use for displaying the wizard step
#   -title        string 
#   -body         code to execute when the step is displayed

proc ::tkwizard::wizProxy-stepconfigure {name step args} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig
    upvar #0 ::tkwizard::@$name-widget     wizWidget

    # no arguments: return all known values
    if {[llength $args] == 0} {
        set result [list]
        foreach element [lsort [array names stepConfig $step,*]] {
            set option [lindex [split $element ,] 1]
            lappend result [list $option $stepConfig($element)]
        }
        return $result
    }

    # one argument: return the value
    if {[llength $args] == 1} {
        set option [lindex $args 0]
        if {[::info exists stepConfig($step,$option)]} {
            return $stepConfig($step,$option)
        } else {
            return -code error "unknown step option \"$option\""
        }
    }

    # More than one argument? 
    foreach {option value} $args {

        if {![::info exists stepConfig($step,$option)]} {
            return -code error "unknown step option \"$option\""
        }

        set stepConfig($step,$option) $value
    }

    # Update the layout with the new data if this is the current step.
    if {[equal $step $wizConfig(-step)]} {
        set layout $stepConfig($step,-layout)
        ${name}::~layout updatestep $step
#        layout-${layout}::updateStep $name $step
    }

}

##
# updateButtons
#
# updates the visual state of the buttons based on the current state
# of the wizard (wizard state, current step, current path, etc.)
#
proc ::tkwizard::updateButtons {name args} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig
    upvar #0 ::tkwizard::@$name-widget     wizWidget

    set path      $wizConfig(-path)
    set steps     $wizState(steps,$path)
    set stepIndex [lsearch -exact $steps $wizConfig(-step)]

    if {$wizConfig(-defaultbutton) == "none"} {
        ${name}::~style buttonconfigure finish -default normal
        ${name}::~style buttonconfigure next   -default normal
        ${name}::~style buttonconfigure back   -default normal
        ${name}::~style buttonconfigure cancel -default normal
        bind $name <Return> {}
    } else {
        foreach b {next back cancel finish} {
            if {[string match $b $wizConfig(-defaultbutton) ]} {
                bind $name <Return> \
                    [namespace code [list ${name}::~style invokebutton $b]]
                ${name}::~style buttonconfigure $b -default active
            } else { 
                ${name}::~style buttonconfigure $b -default normal
            }
        }
    }

    switch -exact -- $wizConfig(-state) {

        "disabled" {

            $name configure -cursor $wizConfig(-cursor)
            ${name}::~style buttonconfigure cancel config -cursor {}

            # Disable all buttons. I doubt anyone will ever use this 
            # feature, but you never know...
            ${name}::~style buttonconfigure finish -state disabled
            ${name}::~style buttonconfigure next   -state disabled
            ${name}::~style buttonconfigure back   -state disabled
            ${name}::~style buttonconfigure cancel -state disabled
        }            

        "normal" {

            $name configure -cursor $wizConfig(-cursor)
            ${name}::~style buttonconfigure cancel -cursor {}

            # Configure next and finish buttons depending on whether
            # there is a next step or not
            if {[equal [lindex $steps end] $wizConfig(-step)]} {
                ${name}::~style buttonconfigure finish -state normal
                ${name}::~style buttonconfigure next   -state disabled
            } else {
                ${name}::~style buttonconfigure finish -state disabled
                ${name}::~style buttonconfigure next   -state normal
            }

            # enable the previous button if we are not on the first step
            if {$stepIndex > 0} {
                ${name}::~style buttonconfigure back -state normal
            } else {
                ${name}::~style buttonconfigure back -state disabled
            }
        }
        "pending" {
            $name configure -cursor $wizConfig(-cursor)
            ${name}::~style buttonconfigure cancel -cursor {}

            # Forward progress is disabled
            ${name}::~style buttonconfigure finish -state disabled
            ${name}::~style buttonconfigure next   -state disabled

            # cancelling is allowed
            ${name}::~style buttonconfigure cancel -state normal

            # enable the previous button if we are not on the first step
            if {$stepIndex > 0} {
                ${name}::~style buttonconfigure back -state normal
            } else {
                ${name}::~style buttonconfigure back -state disabled
            }
        }

        "busy" {
            $name configure -cursor watch
            ${name}::~style buttonconfigure cancel -cursor left_ptr

            # Disable everything but the rather important "Cancel" 
            # button.
            ${name}::~style buttonconfigure finish -state disabled
            ${name}::~style buttonconfigure next   -state disabled
            ${name}::~style buttonconfigure back   -state disabled
            ${name}::~style buttonconfigure cancel -state normal
            update
        }

    }
}

##
# wizProxy-buttonconfigure
#
# usage: pathName buttonconfigure buttonName args
#
# args is any valid argument to the configure method of a button
# (eg: -text, -borderwidth, etc)

proc ::tkwizard::wizProxy-buttonconfigure {name button args} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig
    upvar #0 ::tkwizard::@$name-widget     wizWidget

    if {[lsearch -exact {next back cancel finish} $button] == -1} {
        return -code error "unknown button \"$button\": must be\
           one of back, cancel, finish or next"
    }
    
    if {[llength $args] == 1} {
	    if {[equal [lindex $args 0] -state]} {
		    return $wizState(buttonState,$button)
	    } else {
		    set option [lindex $args 0]
		    return [${name}::~style buttonconfigure $button $option]
	    }
    } 

    set newArgs [list]
    foreach {arg value} $args {
        if {[string match "${arg}*" "-state"]} {
            if {[string match "${value}*" "hidden"]} {
                # we'll set the internal state to hidden, and the
                # actual button state to disabled
                set wizState(buttonState,$button) "hidden"
                lappend newArgs -state disabled
            } else {
                set wizState(buttonState,$button) "visible"
                lappend newArgs $arg $value
            }
        } else {
            # set the arg to the 
            lappend newArgs $arg $value
        }
    }

    eval ${name}::~style buttonconfigure $button $newArgs
#    eval ~\${button}Button configure $newArgs
}

proc ::tkwizard::wizProxy-dump {name} \
{

    upvar #0 ::tkwizard::@$name-state      wizState
    upvar #0 ::tkwizard::@$name-config     wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    puts "wizState:"
    parray wizState

    puts "wizConfig:"
    parray wizConfig

    puts "stepConfig:"
    parray stepConfig
}

### This is the default step layout
tkwizard::registerLayout "default" \
    -description {The default layout} \
    -command tkwizard::layout-default::layoutCommand

namespace eval ::tkwizard::layout-default {

    # This is an array containing the significant widget paths
    # used by this layout. It saves us from having to hard-code
    # widget pathnames everywhere
    variable widget
    array set widget {}
}

# this is the public interface to this layout, and is accessible via
# an interpreter alias created by the init command. Note that in all
# procs, the wizard name is the first argument. This is done by 
# the wizard code, and is a required argument. All other arguments
# are (or can be) unique to the step

proc ::tkwizard::layout-default::layoutCommand {name command args} \
{
    variable widget

    switch -- $command {
        init       {eval init $name $args}
        updatestep {eval updateStep \$name $args}
        updatemap  {eval updateMap \$name $args}
        clearstep  {eval clearStep \$name $args}
	workarea   {return $widget(workArea)}
        update    {
            eval updateStep \$name $args
            eval updateMap  \$name $args
        }
        default    {
            return -code error "unknown layout command \"$command\"\
                                for layout \"default\""
        }
    }
}

proc ::tkwizard::layout-default::init {name layoutFrame} \
{
    variable widget

    upvar #0 ::tkwizard::@$name-state  wizState
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-widget wizWidget

    # The first time this layout is initialized we need to define
    # the widget paths
    if {![::info exists widget(frame)]} {
        
	set layout $layoutFrame.frame-default

        set widget(frame)        $layout
        set widget(workArea)     $layout.workArea
        set widget(icon)         $layout.icon
        set widget(title)        $layout.title
        set widget(description)  $layout.description

        set widget(buttonFrame)  $layout.buttonFrame
        set widget(nextButton)   $widget(buttonFrame).nextButton
        set widget(backButton)   $widget(buttonFrame).backButton
        set widget(cancelButton) $widget(buttonFrame).cancelButton
        set widget(finishButton) $widget(buttonFrame).finishButton
    }

    # Likewise, if this is the first time this proc is called we need
    # to create the widgets
    if {![winfo exists $widget(frame)]} {
        build $name
    }

}

proc ::tkwizard::layout-default::clearStep {name} \
{
    variable widget

    if {[::info exists widget(workArea)] && [winfo exists $widget(workArea)]} {
        eval destroy [winfo children $widget(workArea)]
    }
}

proc ::tkwizard::layout-default::build {name} \
{
    variable widget

    upvar #0 ::tkwizard::@$name-state wizState
    upvar #0 ::tkwizard::@$name-config wizConfig

    # using the option database saves me from hard-coding it for
    # every widget. I guess I'm just lazy.
    # FIXME: shouldn't those priorities be startupFile? I don't
    # recall why I set them to interactive originally...
    option add *WizLayoutDefault*Label.justify           left interactive
    option add *WizLayoutDefault*Label.anchor              nw interactive
    option add *WizLayoutDefault*Label.highlightThickness   0 interactive
    option add *WizLayoutDefault*Label.borderWidth          0 interactive
    option add *WizLayoutDefault*Label.padX                 5 interactive
    option add *WizLayoutDefault.titleBackground      #ffffff startupFile

    frame $widget(frame) -class WizLayoutDefault
    frame $widget(frame).sep1 -class WizSeparator 
    $widget(frame).sep1 configure \
        -background [option get $widget(frame).sep1 stripe Background]

    # Client area. This is where the caller places its widgets.
    frame $widget(workArea) -bd 2 -relief flat
    frame $widget(frame).sep2 -class WizSeparator

    # title and icon
    set background [option get $widget(frame) titleBackground Background]
    frame $widget(frame).titleframe \
	-bd 4 -relief flat -background $background

    label $widget(title) \
	-background $background \
	-anchor w \
	-width 40

    # we'll use a default icon, but the user can always override it
    label $widget(icon) \
	-borderwidth 0 \
	-image ::tkwizard::logo \
	-background $background \
	-anchor c

    set font [font configure TkDefaultFont]
    switch -- [tk windowingsystem] {
	"win32" { set size  18 }
	"aqua"  { set size  24 }
	"x11"   { set size -24 }
    }
    dict set font -size $size
    dict set font -weight bold
    $widget(title) configure -font $font

    set tf $widget(frame).titleframe
    grid $widget(title)    -in $tf -row 0 -column 0 -sticky nsew
    grid $widget(icon)     -in $tf -row 0 -column 1 -padx 8

    grid columnconfigure $tf 0 -weight 1
    grid columnconfigure $tf 1 -weight 0

    # Step description. We'll pick rough estimates on the size of this
    # area. I noticed that if I didn't give it a width and height, and a
    # step defined a really, really long string, the label would try to
    # accomodate the longest string possible, making the widget unnaturally
    # wide.

    label $widget(description)  -width 40  -bd 0

    # when our label widgets change size we want to reset the
    # wraplength to that same size.
    foreach w {title description} {
	bind $widget($w) <Configure> {
	    # yeah, I know this looks weird having two after idle's, but
	    # it helps prevent the geometry manager getting into a tight
	    # loop under certain circumstances
	    #
	    # note that subtracting 10 is just a somewhat arbitrary number
	    # to provide a little padding...
	    after idle {after 1 {%W configure -wraplength [expr {%w -10}]}}
	}
    }
    
    grid $widget(frame).titleframe   -row 0 -column 0 -sticky nsew -padx 0
    
    grid $widget(frame).sep1  -row 1 -sticky ew 
    grid $widget(description) -row 2 -sticky nsew -pady 8 -padx 8
    grid $widget(workArea)    -row 3 -sticky nsew -padx 8 -pady 8

    grid columnconfigure $widget(frame) 0 -weight 1

    grid rowconfigure $widget(frame) 0 -weight 0
    grid rowconfigure $widget(frame) 1 -weight 0
    grid rowconfigure $widget(frame) 2 -weight 0
    grid rowconfigure $widget(frame) 3 -weight 1

    # the description text will initially not be visible. It will pop 
    # into existence if it is configured to have a value
    grid remove $widget(description)

}

proc ::tkwizard::layout-default::updateStep {name step} \
{

    variable widget

    # if the layout widget doesn't exist; do nothing. This will be
    # the case when the wizard is first defined but before the first
    # step has actually been shown.
    if {![::info exists widget(frame)] || 
	![::winfo exists $widget(frame)]} {
        return
    }

    upvar #0 ::tkwizard::@$name-state wizState
    upvar #0 ::tkwizard::@$name-config wizConfig
    upvar #0 ::tkwizard::@$name-stepConfig stepConfig

    $widget(title)       configure -text  $stepConfig($step,-title)
    $widget(icon)        configure -image $stepConfig($step,-icon)
    $widget(description) configure -text  $stepConfig($step,-description)

    # Hide description if it isn't being used
    if {[string length $stepConfig($step,-description)] > 0} {
        grid $widget(description)
    } else {
        grid remove $widget(description)
    }
}

proc ::tkwizard::layout-default::updateMap {name {option ""}} \
{
    # do nothing; this layout doesn't have a map
}

### Default style
tkwizard::registerStyle "default" \
    -description {This is the default style} \
    -command tkwizard::style-default::styleCommand



namespace eval tkwizard::style-default {

    # this will contain style-specific widget paths
    variable widget

}

proc ::tkwizard::style-default::styleCommand {name command args} \
{

    variable widget

    switch -- $command {

        init {
            eval init \$name $args
        }

        invokebutton {
            set w $widget([lindex $args 0]Button)
            if {[$w cget -state] == "disabled"} {
                bell
            } else {
                $w invoke
            }
        }

        focus {
            set name "[lindex $args 0]Button"
            focus $widget($name)
        }

        buttonconfigure {
            set w "[lindex $args 0]Button"
            if {[winfo exists $widget($w)]} {
                eval \$widget(\$w) configure [lrange $args 1 end]
            }
        }

        showbutton {
            foreach button $args {
                set w ${button}Button
                pack $widget($w)
            }
        }
        hidebutton {
            foreach button $args {
                set w ${button}Button
                pack forget $widget($w)
            }
        }

        default {
            return -code error "unknown style command \"$command\"\
                                for style \"default\""
        }
    }
}

proc ::tkwizard::style-default::init {name} \
{

    variable widget

    upvar #0 ::tkwizard::@$name-config wizConfig

    # the present design precludes prefix from ever being ".",
    # but we'll add this code just in case that changes some day...
    if {$name == "."} {
        set prefix ""
    } else {
        set prefix $name
    }

    frame $prefix.layoutFrame -bd 0
    frame $prefix.buttonFrame -bd 0
    frame $prefix.separator -class WizSeparator

    pack $prefix.buttonFrame -side bottom -fill x -expand n
    pack $prefix.separator -side bottom -fill x -expand n
    pack $prefix.layoutFrame -side top -fill both -expand y

    option add $prefix.buttonFrame*BorderWidth  2      widgetDefault
    option add $prefix.buttonFrame*Relief       groove widgetDefault

    # add control buttons
    set widget(nextButton)   $prefix.buttonFrame.nextButton
    set widget(backButton)   $prefix.buttonFrame.backButton
    set widget(cancelButton) $prefix.buttonFrame.cancelButton
    set widget(finishButton) $prefix.buttonFrame.finishButton

    ttk::button $widget(backButton) \
        -text "< Back" \
        -default normal \
        -command [list event generate $name <<WizBackStep>>]

    ttk::button $widget(nextButton) \
        -text "Next >" \
        -default normal \
        -command [list event generate $name <<WizNextStep>>]

    ttk::button $widget(finishButton) \
        -text "Finish" \
        -default normal \
        -command [list event generate $name <<WizFinish>>]

    ttk::button $widget(cancelButton) \
        -text "Cancel"   \
        -default normal \
        -command [list event generate $name <<WizCancel>>]

    pack $widget(cancelButton) -side right -pady 4 -padx 5 
    pack $widget(finishButton) -side right -pady 4 -padx 1
    pack $widget(nextButton)   -side right -pady 4 -padx 1
    pack $widget(backButton)   -side right -pady 4 -padx 1

    # return the name of the layout frame
    return $prefix.layoutFrame
}
# Copyright 2000-2006,2009-2011,2014-2016 BitMover, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# setuptool.tcl
#
# usage: bk setuptool ?options? ?reponame?
#
# options:
# -e                    Undocumented; passed through to "bk setup"
# -F                    force reponame to be read-only. Requires a
#                       repository on the command line
# -R name:value         Sets a config option and marks it as Readonly.
#                       The user will not be able to modify this value.
#                       Only some config options are presently supported.
# -S name:value         Sets config option 'name' to 'value'; user will
#                       be able to modify the value interactively
#
# Note that the following two are synonymous, the latter being supported
# for bacward compatibility
# 
#    bk setuptool -R repository:/foo
#    bk setuptool -F /foo
#
# At this time, -R and -S cannot be used to add arbitrary additional
# keywords to the config file. Unsupported keys are silently ignored.

proc main {} \
{
	global wizData bkuser eflag

	set bkuser [exec bk getuser]

	bk_init
	app_init
	widgets
	after idle [list focus -force .]

	if {$bkuser == "Administrator" || 
	    $bkuser == "root"} {
		. configure -step BadUser
		. configure -path BadUser
	} else {
		. configure -step Begin
	}
	. show

	bind . <<WizCancel>> {
		# This causes the main program to exit
		exit 1
	}

	bind . <<WizFinish>> {
		. configure -state busy

		# If we had previously set the finish button to say
		# "Done", we only need to exit
		set finishLabel [lindex [. buttonconfigure finish -text] end]
		if {$finishLabel == "Done"} {
			exit 0
		}

		# Finish  must really mean finish...
		if {![createRepo errorMessage]} {
			popupMessage -E $errorMessage
			# the break is necessary, because tkwidget's
			# default binding for <<WizFinish>> is to
			# withdraw the window. That would be bad.
			. configure -state normal
			break
		} else {
			popupMessage -I \
			    "The repository was successfully created."

			if {$::wizData(closeOnCreate)} {
				exit 0
			} else {
				. buttonconfigure finish -text Done
				. configure -state normal
				break
			}
		}
	}

	bind . <<WizBackStep>> {
		# The Finish step may have reconfigured the finish button
		# to say "Done". We want to reset that if the user is 
		# going  back through the wizard to possibly create 
		# another repo.
		if {[. cget -step] == "Finish"} {
			. buttonconfigure finish -text Finish
		}
	}

	bind . <<WizNextStep>> {
		switch -exact -- [. cget -step] {
			CheckoutMode {
				if {$::wizData(checkout) eq "edit"} {
					wizInsertStep Clock_Skew
				} else {
					wizRemoveStep Clock_Skew
				}
			}
		}
	}
}

proc app_init {} \
{
	global argv
	global bkuser
	global eflag
	global gc
	global option
	global readonly
	global wizData

	getConfig "setup"

	# these are specific to this app
	option add *Entry*BorderWidth            1 startupFile
	option add *WizSeparator*stripe          blue startupFile

	# The option database is used, so the user specified values don't
	# have to be hard-coded for each widget. It's just easier this way.
	# Presently we don't support setup.BG, because the wizard widget
	# isn't graceful about accepting anything other than default colors.
	# Bummer, huh?
	option add *Checkbutton.font      $gc(setup.buttonFont)  startupFile
	option add *Radiobutton.font      $gc(setup.buttonFont)  startupFile
	option add *Menubutton.font       $gc(setup.buttonFont)  startupFile
	option add *Button.font           $gc(setup.buttonFont)  startupFile
	option add *Label.font            $gc(setup.buttonFont)  startupFile
	option add *Entry.background      $gc(setup.entryColor)  startupFile
	option add *Text*background       $gc(setup.textBG)      startupFile
	option add *Text*foreground       $gc(setup.textFG)      startupFile
	option add *Text*font             $gc(setup.fixedFont)   startupFile

	# These are base-level defaults. Some will get overridden.
	# All of these will end up in the config file.
	array set wizData {
		autofix       "yes"
		checkout      "edit"
		clock_skew    "on"
		partial_check "on"
		closeOnCreate 1

		repository	""
	}

	# Override those with the config.template
	# If we want to modify this code to not only create repos but
	# to also edit existing repos, here's where we might read
	# in the config file of an existing repo
	array set wizData [readConfig template]

	# this list contains the names of the config variables which
	# may be defined on the command line with -F.  The reason only
	# some are allowed is simply that I haven't found the time to
	# disable the particular widgets or steps for the other
	# options.
	set allowedRO {checkout repository}

	# process command line args, which may also override some of
	# the defaults. Note that -F and -S aren't presently
	# documented. They are mainly for us, when calling setuptool
	# from some other process (such as an IDE).
	array set readonly {}
	set eflag 0
	set Fflag 0
	while {[string match {-*} [lindex $argv 0]]} {
		set arg [lindex $argv 0]
		set argv [lrange $argv 1 end]

		switch -- $arg {
			-- { break }
			-e {set eflag 1}
			-F {set readonly(repository) 1; set Fflag 1}
			-R {
				set tmp [lindex $argv 0]
				set argv [lrange $argv 1 end]
				set name ""; set value ""
				regexp {^([^:]+):(.*)} $tmp -> name value
				if {[lsearch -exact $allowedRO $name] == -1} {
					popupMessage -I \
					    "Only the following variables may\
					     be specified\nwith the $arg\
					     option:\n\n[join $allowedRO ,\ ]"
					exit 1
				}
				set wizData($name) $value
				set readonly($name) 1
			}
			-S {
				set tmp [lindex $argv 0]
				set argv [lrange $argv 1 end]
				set name ""; set value ""
				regexp {^([^:]+):(.*)} $tmp -> name value
				set wizData($name) $value
			}
			default {
				popupMessage -W "Unknown option \"$arg\""
				exit 1
			}
		}
	}

	set argc [llength $argv]
	if {$argc == 0 && $Fflag} {
		popupMessage -I "You must designate a repository with -F"
		exit 1

	} elseif {$argc > 1} {
		popupMessage -W "wrong # args: $argv"
		exit 1

	} elseif {$argc == 1} {
		set wizData(repository) [lindex $argv 0]

	}

	computeSize width height
	wm geometry  . ${width}x${height}
	centerWindow . $width $height

}

proc computeSize {wvar hvar} \
{
	upvar $wvar width 
	upvar $hvar height

	global gc

	# we want the GUI wide enough to show 80 characters in a fixed
	# width font. So, we'll create a dummy widget using that font,
	# ask TK what size that is, and add a fudge factor for scrollbars
	# and borders. That will be our width
	label .bogus -width 80  -height 28 -font $gc(setup.fixedFont)
	set width [expr {[winfo reqwidth .bogus] + 40}]

	# vertically we need enough space to show 28 or so lines of
	# text in the label/button font. We'll do the same sort of 
	# dance again, but with the label/button font
	.bogus configure -font $gc(setup.buttonFont)
	set height [winfo reqheight .bogus]
	destroy .bogus

	# under no circumstances do we make a window bigger than the
	# screen
	set maxwidth  [winfo screenwidth .]
	set maxheight [expr {round([winfo screenheight .] * .95)}]
	set width  [expr {$width > $maxwidth? $maxwidth : $width}]
	set height [expr {$height > $maxheight? $maxheight: $height}]
}

# Insert a step right after the current step
# Side Effect: The global variable paths is modified with the 
# new path
proc wizInsertStep {step} \
{
	global paths

	set curPath [. configure -path]
	set curStep [. configure -step]
	if {![info exists paths($curPath)]} {
		return -code error "paths($curPath) doesn't exist"
	}
	set i [lsearch -exact $paths($curPath) $curStep]
	incr i

	# Bail if the step was already in the path as the next step
	if {[lindex $paths($curPath) $i] eq $step} {return}

	# I don't know how to modify a path, so I just add a new one
	set newpath "${curPath}_${step}"
	set paths($newpath) [linsert $paths($curPath) $i $step]
	. add path $newpath -steps $paths($newpath)
	. configure -path $newpath
}

# Remove the next step if it matches Step otherwise it does nothing
# Side Effect: The global variable paths is modified with the 
# new path
proc wizRemoveStep {step} \
{
	global paths

	set curPath [. configure -path]
	set curStep [. configure -step]
	if {![info exists paths($curPath)]} {
		return -code error "paths($curPath) doesn't exist"
	}
	set i [lsearch -exact $paths($curPath) $curStep]
	incr i

	# Bail if the step is not what we meant to remove
	if {[lindex $paths($curPath) $i] ne $step} {return}

	# I don't know how to modify a path, so I just add a new one
	set newpath "${curPath}_${step}"
	set paths($newpath) [lreplace $paths($curPath) $i $i]
	. add path $newpath -steps $paths($newpath)
	. configure -path $newpath
}

proc widgets {} \
{
	global	bkuser readonly env

	::tkwizard::tkwizard . -title "BK Setup Assistant" -sequential 1 

	set image "$env(BK_BIN)/gui/images/bklogo.gif"
	if {[file exists $image]} {
		set bklogo [image create photo -file $image]
		. configure -icon $bklogo
	} 


	set common {
		RepoInfo
		CheckoutMode Partial_Check
		Finish
	}
	
	# remove readonly steps
	if {[info exists readonly(checkout)]} {
		set i [lsearch -exact $common "CheckoutMode"]
		set common [lreplace $common $i $i]
	}
	set ::paths(commercial) [concat Begin $common]
	. add path commercial  -steps $::paths(commercial)

	. add path BadUser -steps BadUser

	# We'll assume this for the moment; it may change later
	. configure -path commercial

	#-----------------------------------------------------------------------
	. add step BadUser \
	    -title "BK Setup Wizard" \
	    -description [wrap [getmsg setuptool_step_BadUser $bkuser]] \
	    -body {
		    $this configure -state pending
		    $this configure -defaultbutton cancel
	    }

	#-----------------------------------------------------------------------
	. add step Begin \
	    -title "BK Setup Wizard" \
	    -description [wrap [getmsg setuptool_step_Begin]] \
	    -body {$this configure -defaultbutton next}

	#-----------------------------------------------------------------------
	. add step RepoInfo \
	    -title "Repository Information" \
	    -description [wrap [getmsg setuptool_step_RepoInfo]]

	. stepconfigure RepoInfo -body {

		global wizData options readonly widgets

		$this configure -defaultbutton next

		set w [$this info workarea]
		set widgets(RepoInfo) $w

		trace variable wizData(repository) w {validate repoInfo}

		ttk::label $w.repoPathLabel -text "Repository Path:"
		ttk::label $w.descLabel     -text "Repository description:"
		ttk::label $w.emailLabel    -text "Contact email address:"
		ttk::entry $w.repoPathEntry \
		    -textvariable wizData(repository)
		ttk::entry $w.descEntry     \
		    -textvariable wizData(description)
		ttk::entry $w.emailEntry    \
		    -textvariable wizData(email)
		ttk::button $w.moreInfoRepoInfo \
		    -text "More info" \
		    -command [list moreInfo repoinfo "CONTACT INFORMATION"]

		grid $w.repoPathLabel -row 0 -column 0 -sticky e -pady 2
		grid $w.repoPathEntry -row 0 -column 1 -sticky ew -pady 2 \
		    -columnspan 2 
		grid $w.descLabel     -row 1 -column 0 -sticky e -pady 2
		grid $w.descEntry     -row 1 -column 1 -sticky ew -pady 2 \
		    -columnspan 2 
		grid $w.emailLabel    -row 2 -column 0 -sticky e -pady 2
		grid $w.emailEntry    -row 2 -column 1 -sticky ew -pady 2 \
		    -columnspan 2 
		grid $w.moreInfoRepoInfo -row 3 -column 0 -sticky e -pady 8

		grid columnconfigure $w 0 -weight 0
		grid columnconfigure $w 1 -weight 1
		grid columnconfigure $w 2 -weight 0

		# we rowconfigure an extra row to take up the slack; this
		# causes all of the widgets to be aligned to the top of
		# the container
		grid rowconfigure    $w 0 -weight 0
		grid rowconfigure    $w 1 -weight 0
		grid rowconfigure    $w 2 -weight 0
		grid rowconfigure    $w 3 -weight 0
		grid rowconfigure    $w 4 -weight 1

		if {[info exists readonly(repository)]} {
			$w.repoPathEntry configure -state disabled
			after idle [list focusEntry $w.descEntry]
		} else {
			after idle [list focusEntry $w.repoPathEntry]
		}

		# running the validate command will set the wizard buttons to 
		# the proper state
		validate repoInfo

	}

	#-----------------------------------------------------------------------
	. add step CheckoutMode \
	    -title "Checkout Mode" \
	    -description [wrap [getmsg setuptool_step_CheckoutMode]]

	. stepconfigure CheckoutMode -body {
		global widgets

		set w [$this info workarea]
		set widgets(CheckoutMode) $w

		$this configure -defaultbutton next

		ttk::label $w.checkoutLabel -text "Checkout Mode:"
		ttk::combobox $w.checkoutOptionMenu -state readonly -width 10 \
		    -textvariable wizData(checkout) -values {none get edit}
		ttk::button $w.checkoutMoreInfo \
		    -text "More info" \
		    -command [list moreInfo checkout "CHECKOUT MODE"]

		grid $w.checkoutLabel      -row 0 -column 0 -sticky e
		grid $w.checkoutOptionMenu -row 0 -column 1 -sticky w
		grid $w.checkoutMoreInfo   -row 0 -column 2 -sticky w 

		grid rowconfigure $w 0 -weight 0
		grid rowconfigure $w 1 -weight 1
		grid columnconfigure $w 0 -weight 0
		grid columnconfigure $w 1 -weight 1
	}
	#-----------------------------------------------------------------------
	. add step Clock_Skew \
	    -title "Timestamp Database" \
	    -description [wrap [getmsg setuptool_step_Clock_Skew]]

	. stepconfigure Clock_Skew -body {
		global widgets

		set w [$this info workarea]
		set widgets(Clock_Skew) $w

		$this configure -defaultbutton next

		ttk::label $w.clock_skewLabel -text "Timestamp Database:"
		ttk::combobox $w.clock_skewOptionMenu -state readonly -width 5 \
		    -textvariable wizData(clock_skew) -values {on off}
		ttk::button $w.clock_skewMoreInfo \
		    -text "More info" \
		    -command [list moreInfo clock_skew CLOCK]

		grid $w.clock_skewLabel      -row 0 -column 0 -sticky e
		grid $w.clock_skewOptionMenu -row 0 -column 1 -sticky w
		grid $w.clock_skewMoreInfo   -row 0 -column 2 -sticky w 

		grid rowconfigure $w 0 -weight 0
		grid rowconfigure $w 1 -weight 1
		grid columnconfigure $w 0 -weight 0
		grid columnconfigure $w 1 -weight 1
	}
	#-----------------------------------------------------------------------
	. add step Partial_Check \
	    -title "Partial Check" \
	    -description [wrap [getmsg setuptool_step_Partial_Check]]

	. stepconfigure Partial_Check -body {
		global widgets

		set w [$this info workarea]
		set widgets(Partial_Check) $w

		$this configure -defaultbutton next

		ttk::label $w.partial_checkLabel -text "Partial Check:"
		ttk::combobox $w.partial_checkOptionMenu -state readonly \
		    -width 5 -textvariable wizData(partial_check) \
		    -values {on off}
		ttk::button $w.partial_checkMoreInfo \
		    -text "More info" \
		    -command [list moreInfo partial_check "PARTIAL CHECK"]

		grid $w.partial_checkLabel      -row 0 -column 0 -sticky e
		grid $w.partial_checkOptionMenu -row 0 -column 1 -sticky w
		grid $w.partial_checkMoreInfo   -row 0 -column 2 -sticky w 

		grid rowconfigure $w 0 -weight 0
		grid rowconfigure $w 1 -weight 1
		grid columnconfigure $w 0 -weight 0
		grid columnconfigure $w 1 -weight 1
	}

	#-----------------------------------------------------------------------
	# See the binding to <<WizFinish>> to see where the repo is
	# actually created...
	. add step Finish \
	    -title "Create the Repository" \
	    -description [wrap [getmsg setuptool_step_Finish]]

	. stepconfigure Finish -body {
		global wizData widgets

		$this configure -defaultbutton finish
		$this buttonconfigure finish -text Finish

		set w [$this info workarea]
		set widgets(Finish) $w
		text $w.text
		ttk::scrollbar $w.vsb -command [list $w.text yview]
		$w.text configure -yscrollcommand [list $w.vsb set]
		ttk::checkbutton $w.closeOnFinish -text \
		    "Keep this window open after creating the repository" \
		    -onvalue 0 \
		    -offvalue 1 \
		    -variable wizData(closeOnCreate)
		frame $w.sep -height 2 -borderwidth 0

		grid $w.text          -row 0 -column 0 -sticky nsew
		grid $w.vsb           -row 0 -column 1 -sticky ns
		grid $w.sep           -row 1 -column 0 -columnspan 2
		grid $w.closeOnFinish -row 2 -column 0 -sticky w -columnspan 2

		grid rowconfigure    $w 0 -weight 1
		grid rowconfigure    $w 1 -weight 0 -minsize 3
		grid rowconfigure    $w 2 -weight 0
		grid rowconfigure    $w 3 -weight 0
		grid columnconfigure $w 0 -weight 1
		grid columnconfigure $w 1 -weight 0
		grid columnconfigure $w 2 -weight 0

		# createConfigData doesn't include the repository name
		# (path, whatever). So to give the user some piece of
		# mind we'll manually insert it. 
		$w.text insert end "repository: $wizData(repository)\n"
		$w.text insert end [createConfigData]
		$w.text configure -state disabled
	}

}

# we don't use args; this proc gets called via a variable trace which
# will add some args. We don't need 'em, but have to accept 'em.
proc validate {which args} \
{
	global wizData
	global gc

	switch $which {
		"repoInfo" {
			if {$wizData(repository) eq ""} {
				. configure -state pending
			} else {
				. configure -state normal
			}
		}
	}
}

proc readConfig {type {filename {}}} \
{
	array set result {}
	set f [open "|bk setup -p" r]
	while {[gets $f line] != -1} {
		if {[regexp {^ *#} $line]} continue
		if {[regexp {([^:]+) *: *(.*)} $line -> key value]} {
			set result($key) [string trim $value]
		}
	}
	return [array get result]
}

# The purpose of this proc is to take the wizard data and format it
# into a valid config file. This will *not* add the "repository"
# key, since that is done by setup
proc createConfigData {} \
{
	global wizData
	set configData ""

	foreach key {
		description email
		autofix checkout clock_skew partial_check
	} {
		append configData "$key: $wizData($key)\n"
	}

	return $configData
}

proc createRepo {errorVar} \
{
	global wizData option eflag env
	upvar $errorVar message

	set pid [pid]
	set filename [tmpfile setuptool]
	set f [open $filename w]
	puts -nonewline $f [createConfigData]
	close $f

	set command [list bk setup -a]
	if {$eflag}  {lappend command -e}
	lappend command -f -c$filename $wizData(repository)
	set err [catch { eval exec $command} message]

	catch {file delete $filename}

	if {$message != ""} {
		# It's annoying this gets appended to the real message.
		# Oh well. 
		regsub -all "\n*\[Cc\]hild process exited.*" \
		    $message {} message
		return 0
	}

	return 1
}

proc popupMessage {args} \
{
	if {[llength $args] == 1} {
		set option ""
		set message [lindex $args 0]
	} else {
		set option [lindex $args 0]
		set message [lindex $args 1]
	}

	# export BK_MSG_GEOM so the popup will show in the right
	# place...
	if {[winfo viewable .]} {
		set x [expr {[winfo rootx .] + 40}]
		set y [expr {[winfo rooty .] + 40}]
		set ::env(BK_MSG_GEOM) "+${x}+${y}"
	}

	# Messages look better with a little whitespace attached
	append message \n

	# hopefully someday we'll turn the msgtool code into a library
	# so we don't have to exec. For now, though, exec works just fine.
	if {[info exists ::env(BK_TEST_HOME)]} {
		# we are running in test mode; spew to stderr
		puts stderr $message
	} else {
		eval exec bk msgtool $option \$message
	}
}

# This not only sets the focus, but attempts to put the cursor in
# the right place
proc focusEntry {w} \
{
	catch {
		$w selection range 0 end
		$w icursor end
		focus $w
	}
}

proc moreInfo {which {search {}}} {
	global dev_null

	exec bk helptool config-etc $search 2> $dev_null &
}

main
