#!/bin/sh
#-*- mode: Tcl;time-stamp-start:"TimeStamp[ 	]+\\\\?[\"<]+";-*-
# the next line restarts using wish \
exec /usr/local/bin/wish8.4 $0 -- $@
set TimeStamp "2006-11-21 17:33:57 poser"
#
# Copyright (C) 2005, 2006 William J. Poser (billposer@alum.mit.edu)
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is contained in the
# procedure "License" in this file.
# If it is not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# or go to the web page:  http://www.gnu.org/licenses/gpl.txt.

set Version "1.5"
encoding system utf-8
fconfigure stdout -encoding utf-8
package require Iwidgets
package require msgcat
proc _ {s} {return [::msgcat::mc $s]};	# Shorthand for gettext
set DebugP 0;

set TimeStamps(LastSaveTime) 0;
set TimeStamps(GenerationTime) 0;

set Options(DebugP) 0;
set Options(AlphabetizeP) 0;		# By default, write out in order generated, in which
					# monosyllables precede disyllables etc.
set Options(AttachHeaderP) 1;			# Attach header to saved words?
set Options(SeparateSyllablesP) 0;
set Options(RandomP) 0;
set Options(SampleSize) 100;
set Options(SyllableSeparator) "-"
set Options(MaximumSyllables) 1;
#If this is 0, only characters with glyphs in Code2000, Everson Mono, and SIL Doulos IPA
#are included. If this is 1, additional characters are included which, if no glyphs are
#available, will make the chart look crummy. 
set UseNoGlyphs 0


set WordLimit 1000000

set BalloonHelpP 1;
set MSGFR   .mf;
set MSG     .mf.msg;
set SYLCOMP .pl
set SYLPAT  .pat
set OPT     .opt
set TXT	    .wds
set MAXFR   .maxsyl
set MAXSYL  .maxsyl.max
set WORDCNT .wdcnt
set CNTDET  .cntdet
set COREENT $SYLPAT.core.ent
set SOLEENT $SYLPAT.sole.ent
set INITIALENT $SYLPAT.initial.ent
set FINALENT $SYLPAT.final.ent

set ALPHA   $OPT.opts.alpha
set ATTHDR  $OPT.opts.hdr
set SEP     $OPT.opts.sep
set RAND    $MAXFR.rand
set RANDSEP $MAXFR.rsep

set MAJXPAD 6
set MAJYPAD 4
array set PopupList {};
set NonBinPath [file join /usr share WordGenerator];

set ColorSpecs(CharacterEntry,Background)		"\#FFFFFF";
set ColorSpecs(CharacterEntry,Foreground)		"\#000000";
set ColorSpecs(CharacterEntryHeadings,Background)		"\#09ffb2";
set ColorSpecs(CheckButton,ActiveBackground) firebrick1
set ColorSpecs(CheckButton,ActiveForeground) black
set ColorSpecs(CheckButton,Background) "\#A0A0FF"
set ColorSpecs(CheckButton,SelectedBackground) red
set ColorSpecs(ConfirmationDialogue,Background)	 		"\#DFC0C0";
set ColorSpecs(ConfirmationDialogueLabel,Background)	 		"\#c36176";
set ColorSpecs(ConfirmationDialogueLabel,Foreground)	 		"\#DFC0C0";
set ColorSpecs(ConfirmationDialogueTitle,Background)	 		"\#c36176";
set ColorSpecs(Default,Background) "\#F1DFCA"
set ColorSpecs(Default,Foreground) black
set ColorSpecs(Entry,Background)	"navajo white"
set ColorSpecs(Entry,DisabledBackground)		blue
set ColorSpecs(Frame,Background)	"\#AFAFFF"
set ColorSpecs(IPAHeadings,Background)		"\#09ffb2";
set ColorSpecs(Match,Background)		white
set ColorSpecs(Menu,ActiveBackground)	        NavajoWhite
set ColorSpecs(Menu,ActiveForeground)	        maroon
set ColorSpecs(Menu,Background)			"\#FFC192"
set ColorSpecs(Menu,Foreground)			black
set ColorSpecs(Menu,Select)			blue
set ColorSpecs(Menubar,Background)	 	"\#c36176";
set ColorSpecs(Menubar,Foreground)		"\#fee4a9";
set ColorSpecs(Menubar,ActiveBackground)	"\#fee4a9";
set ColorSpecs(Menubar,ActiveForeground)	"\#c36176";
set ColorSpecs(Message,Background) 		"\#e9c4dc";
set ColorSpecs(Message,Foreground) 		"\#000000";
set ColorSpecs(OutputText,Background)		"\#FFD6A5"
set ColorSpecs(OutputText,Foreground) 		"\#000000";
set ColorSpecs(PopupWidgetDefault,Background)    "\#fee4a9";
set ColorSpecs(PopupWidgetDefault,Foreground)    "\#fee4a9";
set ColorSpecs(PopupWidgetButton,Background)    "\#FBAA9A"
set ColorSpecs(PopupWidgetButton,Foreground)    black
set ColorSpecs(PopupWidgetCheckbutton,Foreground)    black
set ColorSpecs(PopupWidgetEntry,Background)    "\#BBBBFF"
set ColorSpecs(PopupWidgetEntry,Foreground)    black
set ColorSpecs(PopupWidgetLabel,Background)    "\#fee4a9";
set ColorSpecs(PopupWidgetLabel,Foreground)    black

option add *Background $ColorSpecs(Default,Background) 60;
option add *Foreground $ColorSpecs(Default,Foreground) 60;
option add *Entry.Background  $::ColorSpecs(Entry,Background) 100; 
option add *DisabledBackground "\#949CD2" 60;
option add *Button.activeBackground "\#FF0044" 60;
option add *Checkbutton.activeBackground $::ColorSpecs(CheckButton,ActiveBackground) 100;
option add *Checkbutton.activeForeground $::ColorSpecs(CheckButton,ActiveForeground) 100;
option add *Checkbutton.selectColor  $::ColorSpecs(CheckButton,SelectedBackground) 100;
option add *Menu.Background $ColorSpecs(Menu,Background) 100
option add *Menu.Foreground $ColorSpecs(Menu,Foreground) 100
option add *Menu*selectColor $ColorSpecs(Menu,Select) 100
option add *Menu.activeBackground $ColorSpecs(Menu,ActiveBackground) 100
option add *Menu.activeForeground  $ColorSpecs(Menu,ActiveForeground) 100

#Internal state flags, not options
set AbortP 0;
set InProgressP 0;

set SyllableStructures(Core)	[list]
set SyllableStructures(Sole)	[list]
set SyllableStructures(Initial)	[list]
set SyllableStructures(Final) 	[list]

set WordCountText "0";

proc ShowMessage {msg} {
    if {[winfo exists $::MSG]} {
	$::MSG configure -state normal;
	$::MSG delete 1.0 end;
	$::MSG insert 1.0 $msg;
	$::MSG configure -state disabled;
	return
    }
    puts stderr $msg;
}

proc ClearMessageWindow {} {
    if {[winfo exists $::MSG]} {
	$::MSG configure -state normal;
	$::MSG delete 1.0 end;
	$::MSG configure -state disabled;
    }
}

proc dmsg {msg} {
    if {$::Options(DebugP)} {
	puts stderr $msg;
    }
}

#Portability
#Figure out what system we are running on
if {[string equal $tcl_platform(platform) windows]} {
    set SystemInfo(System) MSWindows;
    dmsg "Running under MS Windows";
} elseif {[string equal $tcl_platform(platform) unix]} {
    if {[string equal $tcl_platform(os) Darwin]} {
	set SystemInfo(System) MacOSX;
	dmsg "Running under Mac OS X";
    } else {
	set SystemInfo(System) Unix;
	dmsg "Running under Unix";
    }
}

if {[string match $SystemInfo(System) MSWindows]} {
    set NonBinPath [file dirname [info script]];
}

proc SetupEvents {sys} {
    switch $sys {
	Unix {
	    event add <<B3>> <ButtonPress-3>
	    event add <<B3Release>> <ButtonRelease-3>
	}
	MacOSX {
	    event add <<B3>> <Control-ButtonPress-1>
	    event add <<B3Release>> <Control-ButtonRelease-1>
	}
	MSWindows {
	    event add <<B3>> <ButtonPress-3>
	    event add <<B3Release>> <ButtonRelease-3>
	}
    }
}

proc DetermineGraphicsSystem {} {
    global SystemInfo

    if {[string match X11*  [winfo server .]]} {
	set SystemInfo(AquaP) 0
	set SystemInfo(WindowSystem) X11
    } else {
	if {[string match $SystemInfo(System) MSWindows]} {
	    set SystemInfo(AquaP) 0;
	    set SystemInfo(WindowSystem) MSWindows;
	}
	if {[string match $SystemInfo(System) MacOSX]} {
	    set SystemInfo(AquaP) 1
	    set SystemInfo(WindowSystem) Aqua
	}
    }
}

proc LoadMessageCatalog {} {
    global DebugP;

    if { $DebugP } {
	if { [::msgcat::mcload "msgs"] == 0} {
	    puts "No message catalog loaded."
	} else {
	    puts "Message catalog loaded."
	}
    } else  {
	::msgcat::mcload [file join [file dirname [info script]] WordGeneratorMsgs];
    }
}

#Put a title in the frame.
proc SetTitle {version TimeStamp} {
    if {$::Options(DebugP)} {
	set sts [split $TimeStamp]
	set ts "[lindex $sts 0] [lindex $sts 1]"
	wm title . [format "WordGenerator %s \[%s\]" $version $ts];
    } else {
	wm title . [format "WordGenerator %s" $version];
    }

}

#dummy, at least for now
proc BindKeys {w} {
    return;
}

#Returns the user's home directory.
proc GetHomeDir {} {
    set cwd [pwd];
    cd;
    set hd [pwd];
    cd $cwd;
    return $hd;
}

#Returns the file URL corresponding to a path name.
proc PathToFileURL {p} {
    return [format "file://%s" $p];
}

#If the filename passed as argument is a pathname
#leading to a file in the current working directory,
#return just the basename+extension. Otherwise
#return the argument.
proc MinimizeFileName {s} {
    set cwd [pwd];
    set sdir [file dirname $s]
    if {[string equal $cwd $sdir]} {
	return [file tail $s]
    } else {
	return $s;
    }
}

set DefaultBrowser dillo
#For this purpose dillo is in many respects arguably the best choice because
#it is very lightweight and starts up fast. However, it isn't as fully
#featured as the others, and in particular, doesn't handle the full range of
#Unicode characters.
#To select a different default browser - uncomment (delete the initial #-sign from) one of the
#following lines:
#set DefaultBrowser firefox
#set DefaultBrowser epiphany;
#set DefaultBrowser galeon;
#set DefaultBrowser konqueror;
#set DefaultBrowser mozilla;
#set DefaultBrowser netscape;
#set DefaultBrowser opera;
set BrowserList [list firefox mozilla epiphany galeon konqueror dillo netscape opera]
set BrowserPIDS [list];

set WhichFontToSet MainFont
#Font system begins here

# We set these outside the namespace since FontInfo has to be global.
# Default defaults
set FontInfo(family) courier
set FontInfo(size) 12
set FontInfo(weight) normal
set FontInfo(slant) roman
set FontInfo(underline) 0
set FontInfo(overstrike) 0

namespace eval fontsel {
    variable FontPropertyList [list family size weight slant underline overstrike]
    variable FontPropertyClass
    variable FSCColors

    foreach p $FontPropertyList {
	set FontPropertyClass($p) generic;
    }
    set FontPropertyClass(underline) boolean
    set FontPropertyClass(overstrike) boolean

    #We check the existence of the variable so that this
    #can safely be called AFTER non-default values are set.
    proc SetFontInfoDefaults {} {
	global FontInfo;
	foreach ft $::FontList {
	    if {![info exist FontInfo($ft,family)]} {
		set FontInfo($ft,family)	$FontInfo(family)
	    }
	    if {![info exist FontInfo($ft,size)]} {
		set FontInfo($ft,size)	$FontInfo(size)
	    }
	    if {![info exist FontInfo($ft,weight)]} {
		set FontInfo($ft,weight)	$FontInfo(weight)
	    }
	    if {![info exist FontInfo($ft,slant)]} {
		set FontInfo($ft,slant)	$FontInfo(slant)
	    }
	    if {![info exist FontInfo($ft,underline)]} {
		set FontInfo($ft,underline)	$FontInfo(underline)
	    }
	    if {![info exist FontInfo($ft,overstrike)]} {
		set FontInfo($ft,overstrike)	$FontInfo(overstrike)
	    }
	    if {![info exist FontInfo($ft,gloss)]} {
		set FontInfo($ft,gloss)	$ft;
	    }
	    if {![info exist FontInfo($ft,help)]} {
		set FontInfo($ft,help)	"?"
	    }
	}
    }

    #For a given font assign font-specific properties from generics.
    proc FontSet {which} {
	global FontInfo
	variable FontPropertyList
	foreach prop $FontPropertyList {
	    set FontInfo($which,$prop) $FontInfo($prop);
	}
    }

    #Assign the proprety values of a specified font to the generics.
    proc InverseFontSet {which} {
	global FontInfo
	variable FontPropertyList
	foreach prop $FontPropertyList {
	    set FontInfo($prop) $FontInfo($which,$prop);
	}
    }

    #Configure a specified font according to the font-specific properties.
    proc ConfigureFont {which} {
	global FontInfo
	variable FontPropertyList
	foreach prop $FontPropertyList {
	    font configure $which -$prop $FontInfo($which,$prop)
	}
    }

    #Record as default values the font-specific values of properties at the
    #time of the call. The recorded values are intended for use by
    #ResetToDefaults 
    proc RecordDefaults {} {
	global FontInfo
	global FontList;
	variable FontPropertyList

	foreach f $FontList {
	    foreach p $FontPropertyList {
		set FontInfo($f,$p,Default) $FontInfo($f,$p);
	    }
	}
    }

    #Create all of the fonts on the font list and configure them
    #according to the generic values of the properties, which are
    #also assigned at this time to the font-specific variables.
    proc CreateFonts {} {
	global FontList;

	foreach ft $FontList {
	    font create $ft;
	    InverseFontSet $ft;
	    ConfigureFont $ft;
	}
    
    }

    #Get the values from the three scales, combine them into a hex RGB spec,
    #assign its value to FontExampleColor, and configure the two examples.
    proc UpdateFontExampleColor {x} {
	variable FSCColors
	global FontExampleColorConfigureWhich

	UpdateFontExampleColorHex;
	.selectFont.curcan.att.r1c1 configure \
	    -$::FontExampleColorConfigureWhich  \#$::FontExampleColor
	.selectFont.curcan.att.r2c1 configure \
	    -$::FontExampleColorConfigureWhich \#$::FontExampleColor
	set FSCColors(red,$FontExampleColorConfigureWhich)   $::FontRed;
	set FSCColors(green,$FontExampleColorConfigureWhich) $::FontGreen;
	set FSCColors(blue,$FontExampleColorConfigureWhich)  $::FontBlue;
    }

    proc UpdateFontExampleColorHex {} {
	set ::FontExampleColor [format "%02X%02X%02X" $::FontRed $::FontGreen $::FontBlue];
    }

    proc RestoreCSColors {} {
	variable FSCColors
	global FontExampleColorConfigureWhich
	set ::FontRed   $FSCColors(red,$FontExampleColorConfigureWhich)
	set ::FontGreen $FSCColors(green,$FontExampleColorConfigureWhich)
	set ::FontBlue  $FSCColors(blue,$FontExampleColorConfigureWhich)
	UpdateFontExampleColorHex;
    }

    proc CloneMainFontAsFontControlPanelFont {} {
	variable FontPropertyList
	global FontInfo

	foreach p $FontPropertyList {
	    font configure FontControlPanelFont -$p $FontInfo(MainFont,$p);
	}
    }
					

    #We create this separately because we don't want it on the FontList.
    font create FontControlPanelFont;

    #This is the main procedure of this package.
    #It creates the font control panel.
    proc CreateFontControlPanel {args} {
	global FontInfo;
	global WhichFontToSet
	global FontList;
	variable FontPropertyList
	variable FSCColors

	set General 1
	set exwid 10

	set w .selectFont
	if {[winfo exists $w]} {
	    wm deiconify $w;
	    raise $w;
	    return ;
	}
	toplevel $w
	wm title $w [_ "Font Selection"]
#	BindKeys $w
	wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
	
	#The idea is that in general we want to use the main
	#font here, but we keep them separate so that we
	#can wait before switching the control panel to
	#the new main font configuration so that
	#the user isn't confronted with an unusable
	#control panel if he or she sets the mainfont to something
	#crazy.
	CloneMainFontAsFontControlPanelFont;

	if {[llength $args] > 0} {
	    set ::WhichFontToSet [lindex $args 0];
	    set General 0;
	} 
	set FontTypes [llength $FontList];
	if {$FontTypes == 1} {
	    set ::WhichFontToSet [lindex $FontList 0];
	    set General 0;
	}

	frame $w.which -relief ridge -border 3
	if {$General} {
	    set msg [_ "For what aspect of the program\ndo you wish to set the font?"]
	    balloonhelp_for $w.which $msg
	    label $w.which.title -text [_ "Use for which to set font"] \
		-anchor w -font FontControlPanelFont
	    frame $w.which.btns;
	    set k 0;
	    set PerRow 4;
	    set ypad 3
	    set xpad 10
	    foreach ft $FontList {
		set bn $w.which.btns.f$ft;
		radiobutton $bn -text [_ $FontInfo($ft,gloss)]  \
		    -variable WhichFontToSet -value $ft \
		    -command fontsel::SetFontSelectionDefaults \
		    -font FontControlPanelFont \
		    -indicatoron 0 -selectcolor $::ColorSpecs(Default,Background)
		balloonhelp_for $w.which.btns.f$ft $FontInfo($ft,help)
		set row [expr 1+ $k/$PerRow]
		set col [expr $k%$PerRow]
		if {$FontTypes >= $PerRow} {
		    grid $w.which.btns.f$ft -row $row -column $col \
			-sticky we -ipadx 3 -padx $xpad -pady $ypad
		}
		incr k
	    }
	    if {$FontTypes < $PerRow} {
		foreach ft $FontList {
		    pack $w.which.btns.f$ft -side left -expand 1 -fill x \
			-ipadx 5 -ipady 3 -padx 40 -pady 3
		}
	    }
	    frame $w.which.pad  -height 4
	    pack $w.which.title -side top -expand 1 -fill both -anchor w
	    pack $w.which.btns  -side top -expand 1 -fill both -anchor w
	    pack $w.which.pad   -side top -expand 0 -fill x    -anchor w

	}

	frame $w.mid 
	frame $w.mid.opts  -relief ridge -border 3
	frame $w.mid.families  -relief ridge -border 3
	label $w.mid.families.lab -text [_ "Family"] -relief ridge \
	    -justify center -font FontControlPanelFont
	listbox $w.mid.families.lb -height 1 -exportselection 0 \
	    -yscrollcommand "$w.mid.families.sbar set" -selectmode single \
	    -font FontControlPanelFont
	scrollbar $w.mid.families.sbar -orient vertical -command "$w.mid.families.lb yview"
	bind $w.mid.families.sbar <<B3>> \
	    "ScrollbarMoveBigIncrement $w.mid.families.sbar 0.20 %x %y"

	bind $w.mid.families.lb <<B3>> ProvideFontDescription

	pack $w.mid.families.lab  -side top -expand 0 -fill x
	pack $w.mid.families.lb   -side left -expand 1 -fill both
	pack $w.mid.families.sbar -side left -expand 1 -fill y

	label $w.mid.opts.lab -text [_ "Attributes"] -relief ridge \
	    -justify center -font FontControlPanelFont
	label $w.mid.opts.sizel -text [_ "Size:"]  -font FontControlPanelFont
	scale $w.mid.opts.size -orient h -digit 1 -from 5 -to 55 \
	    -variable FontInfo(size)  -tickinterval 0 -length 150 \
	    -font FontControlPanelFont
	bind $w.mid.opts.size <<B3>> "ScaleMoveBigIncrement $w.mid.opts.size 5 %x %y"

	label $w.mid.opts.weightl -text [_ "Bold:"] \
	    -font FontControlPanelFont	    
	checkbutton $w.mid.opts.weight -variable FontInfo(weight) \
	    -onvalue bold -offvalue normal  -font FontControlPanelFont

	label $w.mid.opts.slantl -text [_ "Italic:"] \
	    -font FontControlPanelFont
	checkbutton $w.mid.opts.slant -variable FontInfo(slant) \
	    -onvalue italic -offvalue roman -font FontControlPanelFont

	label $w.mid.opts.ulinel -text [_ "Underline:"] \
	    -font FontControlPanelFont
	checkbutton $w.mid.opts.uline -variable FontInfo(underline) \
	    -onvalue 1 -offvalue 0  -font FontControlPanelFont

	label $w.mid.opts.strkovl -text [_ "Overstrike:"] \
	    -font FontControlPanelFont
	checkbutton $w.mid.opts.strkov -variable FontInfo(overstrike) \
	    -onvalue 1 -offvalue 0  -font FontControlPanelFont

	grid $w.mid.opts.lab   -row 0 -column 0 -columnspan 2 -sticky ew
	grid $w.mid.opts.sizel -row 1 -column 0 -sticky w
	grid $w.mid.opts.size -row 1 -column 1 -sticky w
	grid $w.mid.opts.weightl -row 2 -column 0 -sticky w
	grid $w.mid.opts.weight -row 2 -column 1 -sticky w
	grid $w.mid.opts.slantl -row 3 -column 0 -sticky w
	grid $w.mid.opts.slant -row 3 -column 1 -sticky w
	grid $w.mid.opts.ulinel -row 4 -column 0 -sticky w
	grid $w.mid.opts.uline -row 4 -column 1 -sticky w
	grid $w.mid.opts.strkovl -row 5 -column 0 -sticky w
	grid $w.mid.opts.strkov -row 5 -column 1 -sticky w

	pack $w.mid.families -side left -expand 1 -fill both -padx 5 -pady 2
	pack $w.mid.opts     -side left -expand 1 -fill both -padx 5 -pady 2

	eval $w.mid.families.lb insert 0 [lsort [font families]]

	bind $w.mid.families.lb <ButtonPress-1> {fontsel::SelectFontFamily %W %y}

	frame $w.cntls -relief ridge -border 3
	button $w.cntls.sav -text [_ "Apply"]  -command fontsel::FontSave \
	    -font FontControlPanelFont
	button $w.cntls.res -text [_ "Reset"] -command  fontsel::ResetToDefaults \
	    -font FontControlPanelFont
	button $w.cntls.can -text [_ "Dismiss"] -command "wm withdraw $w" \
	    -font FontControlPanelFont
	button $w.cntls.cpanel -text [_ "Here Too?"] \
	    -command fontsel::CloneMainFontAsFontControlPanelFont \
	-font FontControlPanelFont
	pack $w.cntls.cpanel -side left  -expand 1 -fill both -padx 3
	pack $w.cntls.can    -side left  -expand 1 -fill both -padx 3
	pack $w.cntls.res    -side left  -expand 1 -fill both -padx 3
	pack $w.cntls.sav    -side right -expand 1 -fill both -padx 3

	set msg [_ "Press this button to dismiss the popup."];
	balloonhelp_for $w.cntls.can $msg
	set msg [_ "Press this button to reset the font to its defaults."];
	balloonhelp_for $w.cntls.res $msg
	set msg [_ "Press this button to make the changes\nyou have made take effect."]
	balloonhelp_for $w.cntls.sav $msg
	set msg [format \
	     [_ "Press this button to set the fonts\nin this control panel to the %s font."] \
		     [_ $::FontInfo(MainFont,gloss)]]
	balloonhelp_for $w.cntls.cpanel $msg

	frame $w.curcan -relief ridge -border 1
	set ATT [frame $w.curcan.att]
	set Row 0
	foreach r {Header Current Candidate} {
	    set Column 2
	    foreach p $FontPropertyList {
		set v $w.curcan.att.r${Row}c${Column};
		label $v  -font FontControlPanelFont
		incr Column;
	    } 
	    incr Row;
	}

	font create CurrentFontExampleFont \
	    -family $FontInfo($::WhichFontToSet,family) \
	    -size $FontInfo($::WhichFontToSet,size) \
	    -weight $FontInfo($::WhichFontToSet,weight) \
	    -slant $FontInfo($::WhichFontToSet,slant) \
	    -underline $FontInfo($::WhichFontToSet,underline) \
	    -overstrike $FontInfo($::WhichFontToSet,overstrike);

	font create CandidateFontExampleFont \
	    -family $FontInfo($::WhichFontToSet,family) \
	    -size $FontInfo($::WhichFontToSet,size) \
	    -weight $FontInfo($::WhichFontToSet,weight) \
	    -slant $FontInfo($::WhichFontToSet,slant) \
	    -underline $FontInfo($::WhichFontToSet,underline) \
	    -overstrike $FontInfo($::WhichFontToSet,overstrike);

	label $w.curcan.att.r0c0 -font FontControlPanelFont
	label $w.curcan.att.r0c1 -font FontControlPanelFont
	label $w.curcan.att.r1c0  -text [_ "Current"] -font FontControlPanelFont
	entry $w.curcan.att.r1c1 -font CurrentFontExampleFont -relief flat -width $exwid
	$w.curcan.att.r1c1 insert 0 [_ "example"]
	label $w.curcan.att.r2c0  -text [_ "Candidate"] -font FontControlPanelFont
	entry $w.curcan.att.r2c1 -font CandidateFontExampleFont -relief flat  -width $exwid
	$w.curcan.att.r2c1 insert 0 [_ "example"]

	#This is for use in conjunction with my character insertion library.
	#It arranges for the various character insertion widgets to insert
	#characters into the focussed window.
	if {[llength [info commands BindInsertionTarget]]} {
	    BindInsertionTarget $w.curcan.att.r1c1;
	    BindInsertionTarget $w.curcan.att.r2c1;
	}

	set CLR [frame $w.curcan.clr]
	set ln 60;
	set wd 10
	set trc \#D4B8C1
	scale $CLR.red -orient v -digit 1 -from 255 -to 0 -tickinterval 0 \
	    -variable FontRed  -showvalue 0 -length $ln -width $wd\
	    -troughcolor $trc -activebackground \#F0AAAA \
	    -bg red -fg yellow -command fontsel::UpdateFontExampleColor
	scale $CLR.grn -orient v -digit 1 -from 255 -to 0 -tickinterval 0 \
	    -variable FontGreen  -showvalue 0 -length $ln -width $wd\
	    -troughcolor $trc -activebackground \#AAF0AA \
	    -bg green -fg yellow -command fontsel::UpdateFontExampleColor
	scale $CLR.blu -orient v -digit 1 -from 255 -to 0 -tickinterval 0 \
	    -variable FontBlue  -showvalue 0 -length $ln -width $wd\
	    -troughcolor $trc -activebackground \#AAAAF0 \
	    -bg blue -fg yellow -command fontsel::UpdateFontExampleColor
	set FSCColors(red,bg)   0x00
	set FSCColors(green,bg) 0x00
	set FSCColors(blue,bg)  0x00
	set FSCColors(red,fg)   0x00
	set FSCColors(green,fg) 0x00
	set FSCColors(blue,fg)  0x00

	bind $CLR.red <<B3>> "ScaleMoveBigIncrement $CLR.red -20 %x %y"
	bind $CLR.grn <<B3>> "ScaleMoveBigIncrement $CLR.grn -20 %x %y"
	bind $CLR.blu <<B3>> "ScaleMoveBigIncrement $CLR.blu -20 %x %y"

	set ::FontRed   255
	set ::FontGreen 255
	set ::FontBlue  255
	set FontExampleColor \#FFFFFF
	label $CLR.rdt -textvariable FontExampleColor -width 7 -relief raised
	bind $CLR.rdt <Button-1> "pack forget $CLR"
	bind $ATT <Button-1> "pack $w.curcan.clr -before $ATT -side left -expand 1 -fill both"
	set msg [_ "Left click here to remove the color selector."]
	balloonhelp_for $CLR.rdt $msg;
	set msg [_ "Use this to set the colors in the example text boxes."]
	balloonhelp_for $CLR.red $msg;
	balloonhelp_for $CLR.grn $msg;
	balloonhelp_for $CLR.blu $msg;

	frame $CLR.whi
	set ::FontExampleColorConfigureWhich bg;
	radiobutton $CLR.whi.t -variable FontExampleColorConfigureWhich \
	    -value fg -command fontsel::RestoreCSColors -text [_ "fg"] \
	    -indicatoron 0 -activebackground gray -selectcolor gray \
	    -font FontControlPanelFont
	radiobutton $CLR.whi.b -variable FontExampleColorConfigureWhich \
	    -value bg -command fontsel::RestoreCSColors -text [_ "bg"] \
	    -indicatoron 0 -activebackground gray -selectcolor gray \
	    -font FontControlPanelFont
	pack $CLR.whi.t -side left -expand 1 -fill both
	pack $CLR.whi.b -side left -expand 1 -fill both

	pack $CLR.rdt -side top -expand 1  -fill both
	pack $CLR.whi -side bottom -expand 1  -fill both
	pack $CLR.red -side left -expand 1 -fill y
	pack $CLR.grn -side left -expand 1 -fill y
	pack $CLR.blu -side left -expand 1 -fill y

	set msg [_ "Choose the color for the sample text."]
	balloonhelp_for $CLR.whi.t $msg;
	set msg [_ "Choose the background color for the sample text."]
	balloonhelp_for $CLR.whi.b $msg;

	#Now grid them all
	set xp 5
	set yp 3
	set CLimit [expr [llength $FontPropertyList] + 2];
	for {set r 0} {$r < 3} {incr r} {
	    for {set c 0} {$c < $CLimit} {incr c} {
		set v $w.curcan.att.r${r}c${c};
		grid $v -row $r -column $c -sticky w -padx $xp -pady $yp
	    } 
	}

	set xp 3
	set yp 1
	if {!$General} {
	    $w.curcan.att.r0c0 configure -text [string totitle [_ $FontInfo($::WhichFontToSet,gloss)]]
	}

	pack $w.curcan.clr -side left -expand 1 -fill both -padx 9 -pady 5
	pack $w.curcan.att -side left -expand 1 -fill both -padx 3 -pady 5

	#Put it all together
	frame $w.botsep -height 4
	pack $w.which -side top -expand 1 -fill x -padx 8 -pady 6
	pack $w.curcan -side top -expand 1 -fill x -padx 8 -pady 6
	pack $w.mid   -side top -expand 1 -fill x -padx 8 -pady 1
	pack $w.cntls -side top -expand 1 -fill x -padx 8 -pady 2
	pack $w.botsep -side top -expand 1 -fill x -padx 8 -pady 2
	if {$General} {
	    $w.which.btns.f[lindex $FontList 0] invoke;#  Default to first on list
	}
	foreach f $FontPropertyList {
	    trace variable FontInfo($f) w fontsel::FontShow;
	}
	return $w;
    }

    #This resets the properties of the font specified by WhichFontToSet
    #to their default values, where "default" means whatever values
    #are stored in FontInfo(<font>,<property>,Default). The intention
    #is that these values will have been set by RecordDefaults at
    #a suitable point. A suitable point might be program startup or it
    #might be after init files have been read.
    #After resetting the attributes for the current font to their
    #default values, it configures the fonts for both the current and candidate
    #examples in the control panel to match.
    proc ResetToDefaults {} {
	global FontInfo;
	global WhichFontToSet
	variable FontPropertyList

	foreach prop $FontPropertyList {
	    set FontInfo($::WhichFontToSet,$prop) $FontInfo($::WhichFontToSet,$prop,Default);
	    set FontInfo($prop) $FontInfo($::WhichFontToSet,$prop,Default);
	}
	ConfigureFont $::WhichFontToSet;
	ConfigureCurrentExampleFont;
	ConfigureCandidateExampleFont;
	DescribeBothFonts;
    }

    #This is the procedure called from the control panel when the user decides
    #to apply and save the values set on the control panel. It sets the font-specific
    #attributes from the generic ones, configures the target font as well as the
    #current example font, and updates the description of the current font
    #in the control panel.
    proc FontSave {} {
	global FontInfo

	set f $::WhichFontToSet;
	FontSet $f;
	ConfigureFont $f
	ConfigureCurrentExampleFont;
	DescribeBothFonts;
    } 

    #This procedure configures the font used to display the candidate example
    #in the control panel. It is intended to be called whenever a property
    #is changed in the control panel or when the target font is changed.
    proc ConfigureCandidateExampleFont {} {
	global FontInfo
	global WhichFontToSet
	variable FontPropertyList

	foreach prop $FontPropertyList {
	    font configure CandidateFontExampleFont -$prop $FontInfo($prop)
	}
    }

    #This procedure configures the font used to display the current example
    #in the control panel. It is intended to be called when the user
    #applies new values or when the target font is changed.
    proc ConfigureCurrentExampleFont {} {
	global FontInfo
	global WhichFontToSet
	variable FontPropertyList

	foreach prop $FontPropertyList {
	    font configure CurrentFontExampleFont -$prop $FontInfo($::WhichFontToSet,$prop)
	}
    }

    #This procedure configures the candidate example font#and updates the
    #description of the candidate font in the control panel. It is intended
    #to be called whenever a proprty of the candidate font is changed.
    proc FontShow {args} {
	ConfigureCandidateExampleFont;
	DescribeBothFonts;
    }

    #This procedure generates the descriptions of the current and candidate
    #fonts that are shown in the control panel.
    proc DescribeBothFonts {} {
	global FontInfo
	global WhichFontToSet;
	variable FontPropertyList

	set f $::WhichFontToSet;
	set c 2;
	foreach prop $FontPropertyList {
	    .selectFont.curcan.att.r0c$c configure -text  [string totitle [_ $prop]] 
	    set value $FontInfo($f,$prop)
	    if {$value == 1} {set value [_ "Yes"]} else {
		if {$value == 0} {set value [_ "No"]}}
	    .selectFont.curcan.att.r1c$c configure -text $value;
	    set value $FontInfo($prop)
	    if {$value == 1} {set value [_ "Yes"]} else {
		if {$value == 0} {set value [_ "No"]}}
	    .selectFont.curcan.att.r2c$c configure -text $value; 
	    incr c;
	}
    }

#This is to be called when we change font targets.
#It resets the control panel to show the current
#configuration for the newly targeted font.
    proc SetFontSelectionDefaults {} {
	global FontInfo;
	global WhichFontToSet;
	variable FontPropertyList

	foreach prop $FontPropertyList {
	    set FontInfo($prop) $FontInfo($WhichFontToSet,$prop);
	}
	set FontChoices [.selectFont.mid.families.lb size];
	set k 1;
	while {$k <= $FontChoices} {
	    set Family [.selectFont.mid.families.lb get $k];
	    if {[string equal $Family [string tolower $FontInfo($WhichFontToSet,family)]]} {
		.selectFont.mid.families.lb selection clear 0 end;
		.selectFont.mid.families.lb see $k
		.selectFont.mid.families.lb selection set $k $k;
		.selectFont.mid.families.lb activate $k;
		set FontInfo(family) [.selectFont.mid.families.lb get $k]
		break
	    }
	    incr k;
	}
	ConfigureCandidateExampleFont
	ConfigureCurrentExampleFont;
	DescribeBothFonts;
    }

    #This is the callback that extracts a new font family
    #setting from the control panel.
    proc SelectFontFamily {w y} {
	global FontInfo

	set index [$w nearest $y]
	if {$index != ""} {
	    set FontInfo(family) [$w get $index]
	}
    }

    #This is the callback that extracts a new font size
    #from the control panel.
    proc SelectFontSize {w} {
	global WhichFontToSet
	global FontInfo

	set size [$w get]
	if {[catch {expr round($size)} size] == 0} {
	    if { ($size > 0) && ($size <= 60) } {
		set FontInfo(size) $size
		return ;
	    }
	}
	$w delete 0 end
	$w insert 0 $FontInfo($WhichFontToSet,size)
    }

    #This is presently a stub.
    #It is intended to be called on an event, probably <B3>,
    #in the font family listbox in the control panel.
    #Its purpose is to provide the user with information about
    #the particular font. 
    proc ProvideFontDescription {w y} {
	variable FontDescriptions

	set index [$w nearest $y]
	if {$index != ""} {
	    FontDescriptions([$w get $index])
	}
    }

    #Create a command for setting each combination of font and property.
    proc DefineFontSettingProcs {} {
	variable FontPropertyList
	variable FontPropertyClass
	global FontList;
	global FontProcList;

	foreach ft $FontList {
	    foreach prop $fontsel::FontPropertyList {
		if {[string equal $FontPropertyClass($prop) boolean]} {
		    set ProcName  [format "::fontsel::Set%s%sP" $ft [string totitle $prop]];
		    lappend FontProcList $ProcName;
		    set cmd [list "proc"  $ProcName  "\{v\}" \
			 "set ::FontInfo($ft,$prop) \[SlaveBoolean \$v\];\nConfigureFont $ft;"]
		} else {
		    set ProcName  [format "::fontsel::Set%s%s" $ft [string totitle $prop]];
		    lappend FontProcList $ProcName;
		    set cmd [list "proc" $ProcName "\{v\}" \
			 "set ::FontInfo($ft,$prop) \$v;\nConfigureFont $ft;"]
		}
		eval $cmd;
	    }
	}
    }

    #Create aliases in the named daughter interpreter for the font setting commands
    proc AliasFontSettings {interp} {
	variable FontPropertyList
	variable FontPropertyClass
	global FontList;
	
	foreach ft $FontList {
	    foreach prop $FontPropertyList {
		set Tprop [string totitle $prop]
		set Tprop2 $Tprop;
		if {[string equal $FontPropertyClass($prop) boolean]} {
		    set Tprop ${Tprop}P
		}
		eval [split [format "%s alias %s%s ::fontsel::Set%s%s" \
			 $interp $ft $Tprop $ft $Tprop2]]
	    }
	}
    }

    #Returns a list of init file commands representing the current font settings.
    proc SaveFontSettings {} {
	variable FontPropertyList
	variable FontPropertyClass
	global FontInfo
	global FontList;
	
	set cl [list]
	foreach ft $FontList {
	    foreach prop $FontPropertyList {
		set Tprop [string totitle $prop]
		if {[string equal $FontPropertyClass($prop) boolean]} {
		    set Tprop ${Tprop}P
		}
		lappend cl [format "%s%s %s" $ft $Tprop [list $FontInfo($ft,$prop)]]
	    }
	}
	return [lsort $cl];
    }
}

# End of namespace fontsel

# This is the stuff specific to this program.

set FontList [list MainFont BalloonHelpFont MenuFont CharacterEntryFont TextFont \
	  AccentedLetterFont DiacriticFont];

set FontInfo(family) "courier"
set FontInfo(MainFont,family) "courier"
set FontInfo(MainFont,weight) bold
set FontInfo(BalloonHelpFont,family) lucida
set FontInfo(MenuFont,family) courier

set FontInfo(AccentedLetterFont,size) 14
set FontInfo(AccentedLetterFont,family) "code2000"

set FontInfo(CharacterEntryLabelFont,family) bible
set FontInfo(CharacterEntryLabelFont,size) 13
set FontInfo(CharacterEntryFont,size) 14
set FontInfo(CharacterEntryFont,family) "doulos sil"
set FontInfo(RegexpFont,size) 13
set FontInfo(RegexpFont,family)  "doulos sil"
set FontInfo(DiacriticFont,size)  [expr $FontInfo(CharacterEntryFont,size) + 40];

set FontInfo(CharacterEntryFont,size) 14
set FontInfo(DiacriticFont,size) 54

proc SetFontGlossHelpTranslations {} {
    global FontInfo;

    set FontInfo(BalloonHelpFont,gloss) [_ "balloon help"]
    set FontInfo(BalloonHelpFont,help) [_ "This font is used in help balloons."]

    set FontInfo(TextFont,gloss) [_ "text"]
    set FontInfo(TextFont,help) [_ "This font is used for segment sets and for the words generated."]

    set FontInfo(MainFont,gloss) [_ "general"]
    set FontInfo(MainFont,help) [_ "This font is used for most things."]

    set FontInfo(MenuFont,gloss) [_ "menu"]
    set FontInfo(MenuFont,help) [_ "This font is used for menu labels."]

    set FontInfo(CharacterEntryFont,gloss) [_ "character entry"]
    set FontInfo(CharacterEntryFont,help) \
	[_ "This font is used on the labels of\ncharacter insertion widgets."]
    set FontInfo(AccentedLetterFont,gloss) [_ "accented letters"]
    set FontInfo(AccentedLetterFont,help) \
	[_ "This font is used on the labels of the\naccented character insertion widget."]

    set FontInfo(DiacriticFont,gloss) [_ "diacritics"]
    set FontInfo(DiacriticFont,help) \
	[_ "This font is used on the labels of the\ndiacritic insertion widget."]
}

SetFontGlossHelpTranslations;
fontsel::SetFontInfoDefaults;
fontsel::CreateFonts;

option add *selectFont*Font FontControlPanelFont 100
option add *Label.Font MainFont 100
option add *Menu.Font MenuFont 100
option add *Checkbutton.Font MainFont 100
option add *Scale.Font MainFont 100



#Character entry code begins here
#Set this to the path to character entry menu
set CEM .menubar.charentry

set CharentryMenuItems 6;
set IPAAIsDisplayedP 0;
set IPACIsDisplayedP 0;
set IPAVIsDisplayedP 0;
set IPADIsDisplayedP 0;
set CharEntryByCodeIsDisplayedP 0;

set DisplayConsonantChartColumnLabelsP 1
set DisplayConsonantChartRowLabelsP 1
set DisplayVowelChartColumnLabelsP 1
set DisplayVowelChartRowLabelsP 1

proc BindInsertionTarget {v} {
    bind $v <FocusIn> "+SetInsertionTargets $v"
}

proc SetCharacterEntryOptions {} {
    global ColorSpecs

    option add *ipaec.Button.font CharacterEntryFont 100
    option add *ipaec.Label.background  $ColorSpecs(CharacterEntry,Background)
    option add *ipaec.Button.background $ColorSpecs(CharacterEntry,Background)
    option add *ipaec.Button.foreground $ColorSpecs(CharacterEntry,Foreground)
    option add *ipaec.Label.relief raised

    option add *ipaev.Button.font CharacterEntryFont 100
    option add *ipaev.Label.background  $ColorSpecs(CharacterEntry,Background)
    option add *ipaev.Button.background $ColorSpecs(CharacterEntry,Background)
    option add *ipaev.Button.foreground $ColorSpecs(CharacterEntry,Foreground)
    option add *ipaev.Label.relief raised

    option add *ipaea.Button.font AccentedLetterFont 100
    option add *ipaea.Label.background  "\#E0E0E0"
    option add *ipaea.Button.background "\#FFFFFF"
    option add *ipaea.Button.foreground "\#000000"
    option add *ipaea.Label.relief raised

    option add *ipaed.Button.font DiacriticFont 100
    option add *ipaed.Button.background "\#E0E0FF"
    option add *ipaed.Label.background "\#E0E0E0"
    option add *ipaed.Button.foreground "\#000000"
    option add *ipaed.Label.relief raised

    option add *ipae*.*Label.font CharacterEntryLabelFont 100
    option add *ipaec.*Button.font CharacterEntryFont 100
}

proc SetInsertionTargets {tgt} {
    set ::InsertionTarget $tgt;
    set ::CharByCodeInsertionTarget $tgt;
}
#Set the default insertion target here.
SetInsertionTargets .pat.core.ent

#Insert character entry stuff here
set CustomCEMEntries 0;
namespace eval ipaentry {
    variable IPAECColumnLabelList [list];
    variable IPAECRowLabelList [list];
    variable IPAEVColumnLabelList [list];
    variable IPAEVRowLabelList [list];

    set row 0;
    variable poa $row;
    incr row;
    variable vlstop $row;
    incr row;
    variable vdstop $row;
    incr row;
    variable vdimplosive $row;
    incr row;
    variable click $row;
    incr row;
    variable nasal  $row;
    incr row;
    variable trill $row;
    incr row;
    variable tap $row;
    incr row;
    variable vlfric $row;
    incr row;
    variable vdfric $row;
    incr row;
    variable vlaffric $row;
    incr row;
    variable vdaffric $row;
    incr row;
    variable approx $row;
    incr row; 
    variable latapprox $row;
    variable lastrow $row;

    set col 0;
    variable manner $col; 
    incr col;
    variable labial $col; 
    incr col;
    variable labiodental $col; 
    incr col;
    variable dental $col; 
    incr col;
    variable alveolar $col;
    incr col;
    variable postalveolar $col; 
    incr col;
    variable retroflex $col; 
    incr col;
    variable palatal $col; 
    incr col;
    variable velar $col; 
    incr col;
    variable uvular $col; 
    incr col;
    variable pharyngeal $col; 
    incr col;
    variable epiglottal $col; 
    incr col;
    variable glottal $col;
    variable lastcolumn $col;

    proc cfesh {} {
	variable vlfric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vlfric $postalveolar] configure -text "\u0161"];
    }
    proc cresh {} {
	variable vlfric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vlfric $postalveolar] configure -text "\u0283"];
    }

    proc cfeshr {} {
	variable vlfric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlfric $retroflex] configure -text "\u1E63"];
    }
    proc creshr {} {
	variable vlfric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlfric $retroflex] configure -text "\u0282"];
    }

    proc cryod {} {
	variable approx
	variable palatal
	eval [list [format ".ipaec.r%dc%d" $approx $palatal] configure -text "j"];
    }

    proc cfyod {} {
	variable approx
	variable palatal
	eval [list [format ".ipaec.r%dc%d" $approx $palatal] configure -text "y"];
    }

    proc crny {} {
	variable nasal
	variable palatal
	eval [list [format ".ipaec.r%dc%d" $nasal $palatal] configure -text "\u0272"];
    }
    proc cfny {} {
	variable nasal
	variable palatal
	eval [list [format ".ipaec.r%dc%d" $nasal $palatal] configure -text "\u00F1"];
    }

    proc crch {} {
	variable vlaffric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vlaffric $postalveolar] configure -text "t\u0283"];
    }
    proc cfch {} {
	variable vlaffric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vlaffric $postalveolar] configure -text "\u010D"];
    }

    proc crchr {} {
	variable vlaffric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlaffric $retroflex] configure -text "\u0288\u0282"];
    }

    proc cfchr {} {
	variable vlaffric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlaffric $retroflex] configure -text "\u1E6D\u1E63"];
    }


    proc crdj {} {
	eval [list [format ".ipaec.r%dc%d" $ipaentry::vdaffric $ipaentry::postalveolar] configure -text "d\u0292"];
    }

    proc cfdj {} {
	eval [list [format ".ipaec.r%dc%d" $ipaentry::vdaffric $ipaentry::postalveolar] configure -text "\u01C6"];
    }

    proc crdjr {} {
	eval [list [format ".ipaec.r%dc%d" $ipaentry::vdaffric $ipaentry::retroflex] configure -text "\u0256\u0290"];
    }

    proc cfdjr {} {
	eval [list [format ".ipaec.r%dc%d" $ipaentry::vdaffric $ipaentry::retroflex] configure -text "\u1E0D\u1E93"];
    }

    proc crzh {} {
	variable vdfric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vdfric $postalveolar] configure -text "\u0292"];
    }
    proc cfzh {} {
	variable vdfric
	variable postalveolar
	eval [list [format ".ipaec.r%dc%d" $vdfric $postalveolar] configure -text "\u017E"];
    }

    proc crtr {} {
	variable vlstop
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlstop $retroflex] configure -text "\u0288"];
    }
    proc cftr {} {
	variable vlstop
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vlstop $retroflex] configure -text "\u1E6D"];
    }

    proc crdr {} {
	variable vdstop
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vdstop $retroflex] configure -text "\u0256"];
    }
    proc cfdr {} {
	variable vdstop
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vdstop $retroflex] configure -text "\u1E0D"];
    }


    proc crnr {} {
	variable nasal
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $nasal $retroflex] configure -text "\u0273"];
    }
    proc cfnr {} {
	variable nasal
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $nasal $retroflex] configure -text "\u1E47"];
    }


    proc crzhr {} {
	variable vdfric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vdfric $retroflex] configure -text "\u0290"];
    }
    proc cfzhr {} {
	variable vdfric
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $vdfric $retroflex] configure -text "\u1E93"];
    }

    proc crflapr {} {
	variable tap;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $tap $retroflex] configure -text "\u027D"];
    }
    proc cfflapr {} {
	variable tap;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $tap $retroflex] configure -text "\u1E5B"];
    }

    proc crlr {} {
	variable latapprox;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $latapprox $retroflex] configure -text "\u026D"];
    }
    proc cflr {} {
	variable latapprox;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $latapprox $retroflex] configure -text "\u1E37"];
    }

    proc crrr {} {
	variable approx;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $approx $retroflex] configure -text "\u027B"];
    }
    proc cfrr {} {
	variable approx;
	variable retroflex
	eval [list [format ".ipaec.r%dc%d" $approx $retroflex] configure -text "\u1E5B"];
    }

    proc bhcham {w} {
	balloonhelp_for $w "The standard IPA character is shown by default. When the mouse\npointer is over the button, the \"North American\" variant is shown.\nRight click to insert the variant."
    }

    proc bhsp {w} {
	balloonhelp_for $w "The characters below and to the right are characters\nthat do not fit neatly into the chart."
    }

    proc UnpackConsonantColumnLabels {} {
	variable IPAECColumnLabelList;
	foreach l $IPAECColumnLabelList {
	    grid forget $l;
	}
    }

    proc PackConsonantColumnLabels {} {
	destroy .ipaec
	PopupIPAEntryC;
    }

    proc UnpackConsonantRowLabels {} {
	variable IPAECRowLabelList;
	foreach l $IPAECRowLabelList {
	    grid forget $l;
	}
    }

    proc PackConsonantRowLabels {} {
	destroy .ipaec;
	PopupIPAEntryC;
    }

    proc UnpackVowelColumnLabels {} {
	variable IPAEVColumnLabelList;
	foreach l $IPAEVColumnLabelList {
	    grid forget $l;
	}
    }

    proc PackVowelColumnLabels {} {
	destroy .ipaev;
	PopupIPAEntryV;
    }

    proc UnpackVowelRowLabels {} {
	variable IPAEVRowLabelList;
	foreach l $IPAEVRowLabelList {
	    grid forget $l;
	}
    }

    proc PackVowelRowLabels {} {
	destroy .ipaev;
	PopupIPAEntryV;
    }

    proc PopupIPAEntryC {} {
	variable poa
	variable vlstop 
	variable vdstop
	variable vdimplosive 
	variable click 
	variable nasal  
	variable trill 
	variable tap 
	variable vlfric 
	variable vdfric 
	variable vlaffric 
	variable vdaffric 
	variable approx 
	variable latapprox
	variable lastrow

	variable manner 
	variable labial 
	variable labiodental 
	variable dental 
	variable alveolar 
	variable postalveolar 
	variable retroflex 
	variable palatal 
	variable velar 
	variable uvular 
	variable pharyngeal 
	variable epiglottal 
	variable glottal
	variable lastcolumn

	variable IPAECColumnLabelList;
	variable IPAECRowLabelList;
	global DisplayConsonantChartColumnLabelsP
	global DisplayConsonantChartRowLabelsP

	set IPAECColumnLabelList [list];
	set IPAECRowLabelList [list];
	
	set xp 2;
	set yp 3;
	set spcolor 	\#c46276;
	toplevel .ipaec -borderwidth 3 -class CharEntry
	wm title .ipaec [_ "Consonant Symbols"]
	BindKeys .ipaec;
	set DownMsg [_ "Display IPA Consonant Chart"];
	set UpMsg   [_ "Remove IPA Consonant Chart"];
	bind .ipaec <Destroy> \
	    "set ::IPACIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPACIndex -label \"$DownMsg\""
	bind .ipaec <Unmap> \
	    "set ::IPACIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPACIndex -label \"$DownMsg\""
	bind .ipaec <Map> \
	    "set ::IPACIsDisplayedP 1;$::CEM entryconfigure $::ToggleIPACIndex -label \"$UpMsg\""
	set tmp [label [format ".ipaec.r%dc%d" $manner $poa] \
	  -text [_ "IPA\nConsonants"]  -padx 5 -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList $tmp;
	lappend IPAECRowLabelList $tmp;
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $labial] \
	    -text [_ "labial"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $labiodental] \
	    -text [_ "labio\ndental"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $dental] \
	    -text [_ "dental"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $alveolar] \
	    -text [_ "alveolar"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $postalveolar] \
	    -text [_ "post\nalveolar"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $retroflex] \
	    -text [_ "retro\nflex"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $palatal] \
	    -text [_ "palatal"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $velar] \
	    -text [_ "velar"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $uvular] \
	    -text [_ "uvular"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $pharyngeal] \
	    -text [_ "pharyn\ngeal"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $epiglottal] \
	    -text [_ "epi\nglottal"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAECColumnLabelList [label [format ".ipaec.r%dc%d" $manner $glottal] \
	    -text [_ "glottal"]   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]

	#Voiceless oral stops
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vlstop $poa] \
	       -text [_ "voiceless stops"] -anchor w  -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list p]}
	set bn [format ".ipaec.r%dc%d"  $vlstop $labial]
	button $bn  -text "p" -padx $xp -pady $yp -command  $cmd \
	    -fg $::ColorSpecs(CharacterEntry,Foreground);
	balloonhelpd_for $bn [_ "voiceless labial stop"]

	label [format ".ipaec.r%dc%d" $vlstop $labiodental] \
	    -text " " -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list t\u032A]}
	set bn [format ".ipaec.r%dc%d"  $vlstop $dental]
	button $bn  -text "t\u032A"   -padx $xp -pady $yp -command  $cmd
	balloonhelpd_for $bn [_ "voiceless dental stop"]

	set cmd {$::InsertionTarget insert insert [list t]}
	set bn [format ".ipaec.r%dc%d"  $vlstop $alveolar]
	button $bn -text "t"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless alveolar stop"]

	label [format ".ipaec.r%dc%d"  $vlstop $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0288]}
	set bn [format ".ipaec.r%dc%d"  $vlstop $retroflex]
	button $bn  -text \u0288   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E6D]}
	bind $bn <Enter> {ipaentry::cftr}
	bind $bn <Leave> {ipaentry::crtr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiceless retroflex stop"]

	set cmd {$::InsertionTarget insert insert [list c]}
	set bn [format ".ipaec.r%dc%d"  $vlstop $palatal]
	button $bn -text "c"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless palatal stop"]

	set cmd {$::InsertionTarget insert insert [list k]}
	set bn [format ".ipaec.r%dc%d" $vlstop $velar]
	button $bn -text "k" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless velar stop"]

	set cmd {$::InsertionTarget insert insert [list q]}
	set bn [format ".ipaec.r%dc%d" $vlstop $uvular]
	button $bn  -text "q"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless uvular stop"]

	label [format ".ipaec.r%dc%d" $vlstop $pharyngeal] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $vlstop $epiglottal] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0294]}
	set bn [format ".ipaec.r%dc%d" $vlstop $glottal]
	button $bn -text \u0294   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless glottal stop"]

	#Voiced oral stops
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vdstop $poa] \
	       -text [_ "voiced stops"] -anchor w   -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list b]}
	set bn [format ".ipaec.r%dc%d" $vdstop $labial]
	button $bn -text "b"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced bilabial stop"]

	label [format ".ipaec.r%dc%d" $vdstop $labiodental] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list d\u032A]}
	set bn [format ".ipaec.r%dc%d" $vdstop $dental]
	button $bn -text "d\u032A"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced dental stop"]

	set cmd {$::InsertionTarget insert insert [list d]}
	set bn [format ".ipaec.r%dc%d" $vdstop $alveolar]
	button  $bn -text "d"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced alveolar stop"]

	label [format ".ipaec.r%dc%d"  $vdstop $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0256]}
	set bn [format ".ipaec.r%dc%d"  $vdstop $retroflex]
	button $bn  -text \u0256   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E0D]}
	bind $bn <Enter> {ipaentry::cfdr}
	bind $bn <Leave> {ipaentry::crdr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiced retroflex stop"]

	set cmd {$::InsertionTarget insert insert [list \u025F]}
	set bn [format ".ipaec.r%dc%d" $vdstop $palatal]
	button $bn -text \u025F   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced palatal stop"]


	set cmd {$::InsertionTarget insert insert [list g]}
	set bn [format ".ipaec.r%dc%d" $vdstop $velar]
	button $bn -text "g"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced velar stop"]

	set cmd {$::InsertionTarget insert insert [list \u0262]}
	set bn [format ".ipaec.r%dc%d" $vdstop $uvular]
	button $bn -text \u0262  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced uvular stop"]

	label [format ".ipaec.r%dc%d" $vdstop $pharyngeal] -text " "  -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u02A1]}
	set bn [format ".ipaec.r%dc%d" $vdstop $epiglottal]
	button $bn -text \u02A1  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced epiglottal stop"]

	label [format ".ipaec.r%dc%d" $vdstop $glottal] -text " "  -padx $xp -pady $yp

	#Nasals
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $nasal $poa] \
	       -text [_ "nasals"] -anchor w -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list m]}
	set bn [format ".ipaec.r%dc%d" $nasal $labial]
	button $bn -text "m" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "bilabial nasal"]

	set cmd {$::InsertionTarget insert insert [list \u0271]}
	set bn [format ".ipaec.r%dc%d" $nasal $labiodental]
	button $bn -text \u0271   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "labiodental nasal"]

	set cmd {$::InsertionTarget insert insert [list n\u032A]}
	set bn [format ".ipaec.r%dc%d" $nasal $dental]
	button $bn -text n\u032A   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "dental nasal"]

	set cmd {$::InsertionTarget insert insert [list n]}
	set bn [format ".ipaec.r%dc%d" $nasal $alveolar]
	button $bn -text "n"      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "alveolar nasal"]

	label [format ".ipaec.r%dc%d"  $nasal $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0273]}
	set bn [format ".ipaec.r%dc%d"  $nasal $retroflex]
	button $bn  -text \u0273   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E47]}
	bind $bn <Enter> {ipaentry::cfnr}
	bind $bn <Leave> {ipaentry::crnr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "retroflex nasal"]

	set cmd {$::InsertionTarget insert insert [list \u0272]}
	set bn [format ".ipaec.r%dc%d" $nasal $palatal]
	button $bn  -text \u0272    -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u00F1]}
	bind $bn <Enter> {ipaentry::cfny}
	bind $bn <Leave> {ipaentry::crny}
	bhcham $bn;
	balloonhelpd_for $bn [_ "palatal nasal"]

	set cmd {$::InsertionTarget insert insert [list \u014B]}
	set bn [format ".ipaec.r%dc%d" $nasal $velar]
	button $bn -text \u014B    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "velar nasal"]

	set cmd {$::InsertionTarget insert insert [list \u0274]}
	set bn [format ".ipaec.r%dc%d" $nasal $uvular]
	button $bn -text \u0274    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "uvular nasal"]

	label [format ".ipaec.r%dc%d" $nasal $pharyngeal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $nasal $epiglottal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $nasal $glottal] -text " "    -padx $xp -pady $yp

	#Voiceless fricatives
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vlfric $poa] \
	       -text [_ "voiceless fricatives"] -anchor w  -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list \u03C6]}
	set bn [format ".ipaec.r%dc%d" $vlfric $labial]
	button $bn -text \u03C6  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless bilabial fricative"]

	set cmd {$::InsertionTarget insert insert [list f]}
	set bn [format ".ipaec.r%dc%d" $vlfric $labiodental]
	button $bn  -text "f"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless labiodental fricative"]

	set cmd {$::InsertionTarget insert insert [list \u03B8]}
	set bn [format ".ipaec.r%dc%d" $vlfric $dental]
	button $bn -text \u03B8   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless dental fricative"]

	set cmd {$::InsertionTarget insert insert [list "s"]}
	set bn [format ".ipaec.r%dc%d" $vlfric $alveolar]
	button $bn -text "s"  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless alveolar fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0283]}
	set bn [format ".ipaec.r%dc%d" $vlfric $postalveolar];
	button $bn -text \u0283   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u0161]}
	bind $bn <Enter> {ipaentry::cfesh}
	bind $bn <Leave> {ipaentry::cresh}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiceless postalveolar fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0282]}
	set bn [format ".ipaec.r%dc%d" $vlfric $retroflex];
	button $bn -text \u0282   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E63]}
	bind $bn <Enter> {ipaentry::cfeshr}
	bind $bn <Leave> {ipaentry::creshr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiceless retroflex fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0255]}
	set bn [format ".ipaec.r%dc%d" $vlfric $palatal]
	button $bn -text \u0255  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless palatal fricative"]

	set cmd {$::InsertionTarget insert insert [list "x"]}
	set bn [format ".ipaec.r%dc%d" $vlfric $velar]
	button $bn -text "x" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless velar fricative"]

	set cmd {$::InsertionTarget insert insert [list "\u03C7"]}
	set bn [format ".ipaec.r%dc%d" $vlfric $uvular]
	button $bn -text "\u03C7"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless uvular fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0127]}
	set bn [format ".ipaec.r%dc%d" $vlfric $pharyngeal]
	button $bn -text \u0127 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless pharyngeal fricative"]

	set cmd {$::InsertionTarget insert insert [list \u029C]}
	set bn [format ".ipaec.r%dc%d" $vlfric $epiglottal]
	button $bn -text \u029C   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless epiglottal fricative"]

	set cmd {$::InsertionTarget insert insert [list "h"]}
	set bn [format ".ipaec.r%dc%d" $vlfric $glottal]
	button $bn -text "h" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless glottal fricative"]

	#Voiced fricatives
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vdfric $poa] \
	       -text [_ "voiced fricatives"] -anchor w \
	       -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list \u03B2]}
	set bn [format ".ipaec.r%dc%d" $vdfric $labial]
	button $bn -text \u03B2  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced bilabial fricative"]

	set cmd {$::InsertionTarget insert insert [list v]}
	set bn [format ".ipaec.r%dc%d" $vdfric $labiodental]
	button $bn -text "v" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced labiodental fricative"]

	set cmd {$::InsertionTarget insert insert [list "\u00F0"]}
	set bn [format ".ipaec.r%dc%d" $vdfric $dental]
	button $bn -text "\u00F0"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced dental fricative"]

	set cmd {$::InsertionTarget insert insert [list "z"]}
	set bn [format ".ipaec.r%dc%d" $vdfric $alveolar]
	button $bn -text "z"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced alveolar fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0292]}
	set bn [format ".ipaec.r%dc%d" $vdfric $postalveolar]
	button $bn -text \u0292  -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u017E]}
	bind $bn <Enter> {ipaentry::cfzh}
	bind $bn <Leave> {ipaentry::crzh}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiced postalveolar fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0290]}
	set bn [format ".ipaec.r%dc%d" $vdfric $retroflex]
	button $bn -text \u0290   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E93]}
	bind $bn <Enter> {ipaentry::cfzhr}
	bind $bn <Leave> {ipaentry::crzhr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiced retroflex fricative"]

	set cmd {$::InsertionTarget insert insert [list \u029D]}
	set bn [format ".ipaec.r%dc%d" $vdfric $palatal]
	button $bn -text  \u029D  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced palatal fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0263]}
	set bn [format ".ipaec.r%dc%d" $vdfric $velar]
	button $bn -text \u0263  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced velar fricative"]

	set cmd {$::InsertionTarget insert insert [list "\u0281"]}
	set bn [format ".ipaec.r%dc%d" $vdfric $uvular]
	button $bn -text "\u0281" -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced uvular fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0295]}
	set bn [format ".ipaec.r%dc%d" $vdfric $pharyngeal]
	button $bn -text \u0295 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced pharyngeal fricative"]

	set cmd {$::InsertionTarget insert insert [list \u02A2]}
	set bn [format ".ipaec.r%dc%d" $vdfric $epiglottal] 
	button $bn -text \u02A2    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced epiglottal fricative"]

	set cmd {$::InsertionTarget insert insert [list \u0266]}
	set bn [format ".ipaec.r%dc%d" $vdfric $glottal]
	button $bn -text \u0266    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced glottal fricative"]

	#trills
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $trill $poa] \
	       -text [_ "trills"] -anchor w   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list \u0299]}
	set bn [format ".ipaec.r%dc%d" $trill $labial]
	button  $bn -text \u0299      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "bilabial trill"]

	label [format ".ipaec.r%dc%d" $trill $labiodental] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $trill $dental] -text " "   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list r]}
	set bn [format ".ipaec.r%dc%d" $trill $alveolar]
	button $bn -text "r"      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "alveolar trill"]

	label [format ".ipaec.r%dc%d"  $trill $postalveolar] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d"  $trill $retroflex] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $trill $palatal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $trill $velar] -text " "    -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0280]}
	set bn [format ".ipaec.r%dc%d" $trill $uvular]
	button $bn -text \u0280    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "uvular trill"]

	label [format ".ipaec.r%dc%d" $trill $pharyngeal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $trill $epiglottal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $trill $glottal] -text " "    -padx $xp -pady $yp

	#taps and flaps
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $tap $poa] \
	       -text [_ "taps/flaps"] -anchor w   -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	label [format ".ipaec.r%dc%d" $tap $labial] -text " "      -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $labiodental] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $dental] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u027E]}
	set bn [format ".ipaec.r%dc%d" $tap $alveolar]
	button $bn -text \u027E -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "uvular trill"]

	label [format ".ipaec.r%dc%d"  $tap $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u027D]}
	set bn [format ".ipaec.r%dc%d"  $tap $retroflex];
	button $bn -text \u027D   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E5B]}
	bind $bn <Enter> {ipaentry::cfflapr}
	bind $bn <Leave> {ipaentry::crflapr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "retroflex tap"]

	label [format ".ipaec.r%dc%d" $tap $palatal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $velar] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $uvular] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $pharyngeal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $epiglottal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $tap $glottal] -text " "    -padx $xp -pady $yp

	#approximants
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $approx $poa] \
	       -text [_ "approximants"] -anchor w \
	       -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	label [format ".ipaec.r%dc%d" $approx $labial] -text " "      -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u028B]}
	set bn [format ".ipaec.r%dc%d" $approx $labiodental]
	button $bn -text \u028B   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "labiodental approximant"]

	label [format ".ipaec.r%dc%d" $approx $dental] -text " "   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u0279]}
	set bn [format ".ipaec.r%dc%d" $approx $alveolar]
	button $bn -text  \u0279 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "alveolar approximant"]

	label [format ".ipaec.r%dc%d"  $approx $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u027B]}
	set bn [format ".ipaec.r%dc%d"  $approx $retroflex]
	button $bn -text \u027B   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E5B]}
	bind $bn <Enter> {ipaentry::cfrr}
	bind $bn <Leave> {ipaentry::crrr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "retroflex approximant"]

	set cmd {$::InsertionTarget insert insert [list "j"]}
	set bn [format ".ipaec.r%dc%d" $approx $palatal]
	button $bn -text "j"    -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list "y"]}
	bind $bn <Enter> {ipaentry::cfyod}
	bind $bn <Leave> {ipaentry::cryod}
	bhcham $bn;
	balloonhelpd_for $bn [_ "palatal approximant"]

	set cmd {$::InsertionTarget insert insert [list \u0270]}
	set bn [format ".ipaec.r%dc%d" $approx $velar]
	button $bn  -text \u0270    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "velar approximant"]

	label [format ".ipaec.r%dc%d" $approx $uvular] -text " "  \
	    -padx $xp -pady $yp -bg $spcolor
	label [format ".ipaec.r%dc%d" $approx $pharyngeal] -text " "  \
	    -padx $xp -pady $yp -bg $spcolor

	#label [format ".ipaec.r%dc%d" $approx $epiglottal] -text " "  \
	    -padx $xp -pady $yp -bg $spcolor
	#label [format ".ipaec.r%dc%d" $approx $glottal] -text " "   \
	    -padx $xp -pady $yp -bg $spcolor

	#lateral approximants
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $latapprox $poa] \
	       -text [_ "lateral approximants"] -anchor w \
	       -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	label [format ".ipaec.r%dc%d" $latapprox $labial] -text " "      -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $latapprox $labiodental] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $latapprox $dental] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list "l"]}
	set bn [format ".ipaec.r%dc%d" $latapprox $alveolar]
	button $bn -text "l"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "alveolar lateral approximant"]

	label [format ".ipaec.r%dc%d"  $latapprox $postalveolar] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u026D]}
	set bn [format ".ipaec.r%dc%d"  $latapprox $retroflex]
	button $bn  -text \u026D   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E37]}
	bind $bn <Enter> {ipaentry::cflr}
	bind $bn <Leave> {ipaentry::crlr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "retroflex lateral approximant"]

	set cmd {$::InsertionTarget insert insert [list \u028E]}
	set bn [format ".ipaec.r%dc%d" $latapprox $palatal]
	button $bn -text \u028E    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "palatal lateral approximant"]

	set cmd {$::InsertionTarget insert insert [list \u029F]}
	set bn [format ".ipaec.r%dc%d" $latapprox $velar]
	button $bn -text \u029F    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "velar lateral approximant"]

	label [format ".ipaec.r%dc%d" $latapprox $uvular] -text " "  \
	    -padx $xp -pady $yp -bg $spcolor
	#label [format ".ipaec.r%dc%d" $latapprox $pharyngeal] -text " "  \
	    -padx $xp -pady $yp -bg $spcolor
	#label [format ".ipaec.r%dc%d" $latapprox $epiglottal] -text " "  \
	    -padx $xp -pady $yp
	#label [format ".ipaec.r%dc%d" $latapprox $glottal] -text " "  \
	    -padx $xp -pady $yp

	#voiced implosives
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vdimplosive $poa] \
	       -text [_ "voiced implosives"] -anchor w \
	       -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list \u0253]}
	set bn [format ".ipaec.r%dc%d" $vdimplosive $labial]
	button $bn -text \u0253 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced bilabial implosive"]

	label [format ".ipaec.r%dc%d" $vdimplosive $labiodental] -text " " \
	    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $vdimplosive $dental] -text " "  \
	    -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u0257]}
	set bn [format ".ipaec.r%dc%d" $vdimplosive $alveolar]
	button $bn -text \u0257   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced alveolar implosive"]

	label [format ".ipaec.r%dc%d"  $vdimplosive $postalveolar] -text " " \
	    -padx $xp -pady $yp

	#Not officially recognized by IPA yet.
	set cmd {$::InsertionTarget insert insert [list \u1D91]}
	set bn [format ".ipaec.r%dc%d"  $vdimplosive $retroflex]
	button $bn -text \u1D91  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced retroflex implosive"]

	set cmd {$::InsertionTarget insert insert [list \u0284]}
	set bn [format ".ipaec.r%dc%d" $vdimplosive $palatal]
	button $bn -text \u0284  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced palatal implosive"]

	set cmd {$::InsertionTarget insert insert [list \u0260]}
	set bn [format ".ipaec.r%dc%d" $vdimplosive $velar]
	button $bn -text \u0260 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced velar implosive"]

	set cmd {$::InsertionTarget insert insert [list \u029B]}
	set bn [format ".ipaec.r%dc%d" $vdimplosive $uvular]
	button $bn -text \u029B    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced uvular implosive"]

	label [format ".ipaec.r%dc%d" $vdimplosive $pharyngeal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $vdimplosive $epiglottal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $vdimplosive $glottal] -text " "    -padx $xp -pady $yp

	#clicks
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $click $poa] \
	       -text [_ "clicks"] -anchor w  -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list \u0298]}
	set bn [format ".ipaec.r%dc%d" $click $labial]
	button $bn -text \u0298      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "labial click"]

	label [format ".ipaec.r%dc%d" $click $labiodental] -text " "   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list \u01C0]}
	set bn [format ".ipaec.r%dc%d" $click $dental]
	button $bn -text \u01C0   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "dental click"]

	set cmd {$::InsertionTarget insert insert [list \u01C2]}
	set bn [format ".ipaec.r%dc%d" $click $alveolar]
	button $bn -text \u01C2   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "alveolar click"]

	set cmd {$::InsertionTarget insert insert [list \u01C3]}
	set bn [format ".ipaec.r%dc%d"  $click $postalveolar]
	button $bn -text \u01C3 \
	    -padx $xp -pady $yp  -command $cmd
	balloonhelpd_for $bn [_ "postalveolar click"]

	label [format ".ipaec.r%dc%d"  $click $retroflex] -text " "   -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $click $palatal] -text " "  -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $click $velar] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $click $uvular] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $click $pharyngeal] -text " "    -padx $xp -pady $yp
	label [format ".ipaec.r%dc%d" $click $epiglottal] -text " "    -padx $xp -pady $yp
	set cmd {BackDelete $::InsertionTarget}
	set bn [format ".ipaec.r%dc%d" $click $glottal];
	button $bn -text [_ "Delete"]  -padx $xp -pady $yp -command $cmd -background "\#E0D0FF";
	set DeleteButton $bn;


	#Voiceless affricates
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vlaffric $poa] \
	       -text [_ "voiceless affricates"] -anchor w -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list p\u03C6]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $labial]
	button $bn -text "p\u03C6"      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless bilabial affricate"]

	label [format ".ipaec.r%dc%d" $vlaffric $labiodental] -text "" -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list t\u03B8]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $dental]
	button $bn -text "t\u03B8"   -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless dental affricate"]

	set cmd {$::InsertionTarget insert insert [list "ts"]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $alveolar]
	button $bn -text "ts"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless alveolar affricate"]

	set cmd {$::InsertionTarget insert insert [list t\u0283]}
	set bn [format ".ipaec.r%dc%d"  $vlaffric $postalveolar]
	button $bn -text "t\u0283"   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u010D]}
	bind $bn <Enter> {ipaentry::cfch}
	bind $bn <Leave> {ipaentry::crch}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiceless postalveolar affricate"]

	set cmd {$::InsertionTarget insert insert [list \u0288\u0282]}
	set bn [format ".ipaec.r%dc%d"  $vlaffric $retroflex]
	button $bn -text \u0288\u0282   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E6D\u1E63]}
	bind $bn <Enter> {ipaentry::cfchr}
	bind $bn <Leave> {ipaentry::crchr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiceless retroflex affricate"]

	set cmd {$::InsertionTarget insert insert [list t\u0255]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $palatal]
	button $bn -text t\u0255 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless palatal affricate"]

	set cmd {$::InsertionTarget insert insert [list "kx"]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $velar]
	button $bn -text "kx"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless velar affricate"]

	set cmd {$::InsertionTarget insert insert [list "q\u03C7"]}
	set bn [format ".ipaec.r%dc%d" $vlaffric $uvular]
	button $bn -text "q\u03C7"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiceless uvular affricate"]

	label [format ".ipaec.r%dc%d" $vlaffric $pharyngeal] -text "" \
	    -padx $xp -pady $yp -bg $spcolor
	label [format ".ipaec.r%dc%d" $vlaffric $epiglottal] -text "" \
	    -padx $xp -pady $yp -bg $spcolor
	label [format ".ipaec.r%dc%d" $vlaffric $glottal] -text "" \
	    -padx $xp -pady $yp -bg $spcolor

	#Voiced affricate
	lappend IPAECRowLabelList [label [format ".ipaec.r%dc%d" $vdaffric $poa] \
	       -text [_ "voiced affricates"] -anchor w -padx $xp -pady $yp \
	       -bg $::ColorSpecs(IPAHeadings,Background)]

	set cmd {$::InsertionTarget insert insert [list b\u03B2]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $labial]
	button $bn -text b\u03B2      -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced bilabial affricate"]

	label [format ".ipaec.r%dc%d" $vdaffric $labiodental] -text ""   -padx $xp -pady $yp

	set cmd {$::InsertionTarget insert insert [list "d\u00F0"]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $dental]
	button $bn -text "d\u00F0"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced dental affricate"]

	set cmd {$::InsertionTarget insert insert [list "dz"]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $alveolar]
	button  $bn -text "dz"  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced alveolar affricate"]

	set cmd {$::InsertionTarget insert insert [list d\u0292]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $postalveolar]
	button $bn -text d\u0292  -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u01C6]}
	bind $bn <Enter> {ipaentry::cfdj}
	bind $bn <Leave> {ipaentry::crdj}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiced postalveolar affricate"]

	set cmd {$::InsertionTarget insert insert [list \u0256\u0290]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $retroflex]
	button $bn -text \u0256\u0290   -padx $xp -pady $yp -command $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u1E0D\u1E93]}
	bind $bn <Enter> {ipaentry::cfdjr}
	bind $bn <Leave> {ipaentry::crdjr}
	bhcham $bn;
	balloonhelpd_for $bn [_ "voiced retroflex affricate"]

	set cmd {$::InsertionTarget insert insert [list d\u029D]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $palatal]
	button $bn -text  d\u029D  -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced palatal affricate"]

	set cmd {$::InsertionTarget insert insert [list g\u0263]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $velar]
	button $bn -text g\u0263    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced velar affricate"]

	set cmd {$::InsertionTarget insert insert [list "\u0262\u0281"]}
	set bn [format ".ipaec.r%dc%d" $vdaffric $uvular]
	button $bn -text "\u0262\u0281"    -padx $xp -pady $yp -command $cmd
	balloonhelpd_for $bn [_ "voiced uvular affricate"]

	label [format ".ipaec.r%dc%d" $vdaffric $pharyngeal] -text "" \
	    -padx $xp -pady $yp -bg $spcolor
	#label [format ".ipaec.r%dc%d" $vdaffric $epiglottal] -text "" \
	    -padx $xp -pady $yp -bg $spcolor
	#label [format ".ipaec.r%dc%d" $vdaffric $glottal] -text ""   \
	    -padx $xp -pady $yp -bg $spcolor

	set cmd {$::InsertionTarget insert insert [list "\u028D"]}
	button .ipaec.r13c10 -text \u028D -padx $xp -pady $yp -command $cmd
	balloonhelpd_for .ipaec.r13c10  [_ "Voiceless labio-velar fricative"]
	set cmd {$::InsertionTarget insert insert [list "\u0265"]}
	button .ipaec.r11c11 -text \u0265 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for .ipaec.r11c11  [_ "Voiced labio-palatal approximant"]
	set cmd {$::InsertionTarget insert insert [list "\u0077"]}
	button .ipaec.r11c12 -text \u0077 -padx $xp -pady $yp -command $cmd
	balloonhelpd_for .ipaec.r11c12  [_ "Voiced labio-velar approximant"]
	set cmd {$::InsertionTarget insert insert [list "\u01C1"]}
	button .ipaec.r12c11 -text \u01C1 -padx $xp -pady $yp -command $cmd
	set cmd {$::InsertionTarget insert insert [list "\u02A0"]}
	button .ipaec.r12c12 -text \u02A0 -padx $xp -pady $yp -command $cmd
	set cmd {$::InsertionTarget insert insert [list "\u026C"]}
	button .ipaec.r13c11 -text \u026C -padx $xp -pady $yp -command $cmd
	set cmd {$::InsertionTarget insert insert [list "\u026E"]}
	button .ipaec.r13c12 -text \u026E -padx $xp -pady $yp -command $cmd

	for {set row 0} {$row <= $lastrow} {incr row} {
	    set line [list];
	    for {set col 0} {$col <= $lastcolumn} {incr col} {
		set cell [format ".ipaec.r%dc%d" $row $col]
		lappend line $cell
		if {[string equal [winfo class $cell] "Label"]} {
		    balloonhelp_for $cell  [_ "Left click to insert standard characters.\nRight click to insert alternative characters."]
		}
	    }
	    eval grid $line -sticky news;
	}
	balloonhelpd_for .ipaec.r12c11  [_ "Lateral click"]
	balloonhelpd_for .ipaec.r12c12  [_ "Voiceless uvular implosive"]
	balloonhelpd_for .ipaec.r13c11  [_ "Voiceless lateral fricative"]
	balloonhelpd_for .ipaec.r13c12  [_ "Voiced lateral fricative"]
	bhsp [format ".ipaec.r%dc%d" $latapprox $uvular]
	bhsp [format ".ipaec.r%dc%d" $approx $uvular]
	bhsp [format ".ipaec.r%dc%d" $approx $pharyngeal]
	bhsp [format ".ipaec.r%dc%d" $vlaffric $pharyngeal]
	bhsp [format ".ipaec.r%dc%d" $vlaffric $epiglottal]
	bhsp [format ".ipaec.r%dc%d" $vlaffric $glottal]
	bhsp [format ".ipaec.r%dc%d" $vdaffric $pharyngeal]
	balloonhelpd_for $DeleteButton [_ "No, there isn't a glottal click  called \"Delete\".\nThis is so that you can correct mistakes while using\nthe mouse, without having to go back to the keyboard."]
	after idle {
	    update idletasks
	    raise .ipaec;
	}
	if {!$DisplayConsonantChartColumnLabelsP} {UnpackConsonantColumnLabels}
	if {!$DisplayConsonantChartRowLabelsP} {UnpackConsonantRowLabels}
    }

    set row 0;
    variable frontness $row;
    incr row;
    variable close $row;
    incr row;
    variable closeclosemid $row;
    incr row;
    variable closemid $row;
    incr row;
    variable midmid $row;
    incr row;
    variable openmid $row;
    incr row;
    variable openopenmid $row;
    incr row;
    variable open $row;
    variable lastvrow $row;

    set col 0;
    variable height $col; 
    incr col;
    variable fronturd $col;
    incr col;
    variable frontrnd $col;
    incr col;
    variable centurd $col;
    incr col;
    variable centrnd $col;
    incr col;
    variable backurd $col;
    incr col;
    variable backrnd $col;
    variable lastvcolumn $col;

    proc vry {} {
	variable close
	variable frontrnd
	eval [list [format ".ipaev.r%dc%d" $close $frontrnd] configure -text "y"];
	}
    
    proc vfy {} {
	variable close
	variable frontrnd
	eval [list [format ".ipaev.r%dc%d" $close $frontrnd] configure -text "\u00FC"];
	}

    proc vrphi {} {
	variable frontrnd;
	variable closemid
	eval [list [format ".ipaev.r%dc%d" $closemid $frontrnd] configure -text "\u00F8"];
    }
    
    proc vfphi {} {
	variable frontrnd;
	variable closemid
	eval [list [format ".ipaev.r%dc%d" $closemid $frontrnd] configure -text "o\u0308"];
    }

    proc vroe {} {
	variable frontrnd;
	variable openmid
	eval [list [format ".ipaev.r%dc%d" $openmid $frontrnd] configure -text "\u0153"];
    }

    proc vfoe {} {
	variable frontrnd;
	variable openmid
	eval [list [format ".ipaev.r%dc%d" $openmid $frontrnd] configure -text "\u0254\0308"];
    }


    proc PopupIPAEntryV {} {
	variable frontness
	variable close
	variable closeclosemid
	variable closemid
	variable openmid
	variable midmid
	variable openopenmid
	variable open
	variable lastvrow

	variable height
	variable fronturd
	variable frontrnd
	variable centurd
	variable centrnd
	variable backurd
	variable backrnd
	variable lastvcolumn

	variable IPAEVColumnLabelList
	variable IPAEVRowLabelList
	global DisplayVowelChartColumnLabelsP
	global DisplayVowelChartRowLabelsP

	set xp 2;
	set yp 3;
	toplevel .ipaev -borderwidth 3
	wm title .ipaev [_ "Vowel Symbols"]
	BindKeys .ipaev;
	set DownMsg [_ "Display IPA Vowel Chart"];
	set UpMsg   [_ "Remove IPA Vowel Chart"];
	bind .ipaev <Destroy> \
	    "set ::IPAVIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPAVIndex -label \"$DownMsg\""
	bind .ipaev <Unmap> \
	    "set ::IPAVIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPAVIndex -label \"$DownMsg\""
	bind .ipaev <Map> \
	    "set ::IPAVIsDisplayedP 1;$::CEM entryconfigure $::ToggleIPAVIndex -label \"$UpMsg\""

	#Column headers
	set tmp [label [format ".ipaev.r%dc%d" $height $frontness] \
	     -text [_ "IPA\nVowels"]  -padx $xp -pady $yp -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList $tmp;
	lappend IPAEVRowLabelList $tmp;
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $fronturd] \
	     -text [_ "Front\nUnrounded"]  -padx $xp -pady $yp \
     	     -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $frontrnd] \
	     -text [_ "Front\nRounded"]  -padx $xp -pady $yp \
  	     -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $centurd] \
	     -text [_ "Central\nUnrounded"]  -padx $xp -pady $yp \
	     -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $centrnd] \
	     -text [_ "Central\nRounded"]  -padx $xp -pady $yp \
   	     -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $backurd] \
	     -text [_ "Back\nUnrounded"]  -padx $xp -pady $yp \
  	     -bg $::ColorSpecs(IPAHeadings,Background)]
	lappend IPAEVColumnLabelList [label [format ".ipaev.r%dc%d" $height $backrnd] \
	     -text [_ "Back\nRounded"]  -padx $xp -pady $yp \
  	     -bg $::ColorSpecs(IPAHeadings,Background)]


	#Close vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $close $frontness] \
	   -text [_ "Close"] -anchor w  -padx $xp -pady $yp \
  	   -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list i]}
	button [format ".ipaev.r%dc%d"  $close $fronturd] \
	     -text "i"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list y]}
	set bn  [format ".ipaev.r%dc%d"  $close $frontrnd]
	button $bn -text "y"   -padx $xp -pady $yp -command  $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u00FC]}
	bind $bn <Enter> {ipaentry::vfy}
	bind $bn <Leave> {ipaentry::vry}
	bhcham $bn;

	set cmd {$::InsertionTarget insert insert [list \u0268]}
	button [format ".ipaev.r%dc%d"  $close $centurd] \
	     -text "\u0268"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0289]}
	button [format ".ipaev.r%dc%d"  $close $centrnd] \
	     -text "\u0289"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0268]}
	button [format ".ipaev.r%dc%d"  $close $backurd] \
	     -text "\u0268"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list u]}
	button [format ".ipaev.r%dc%d"  $close $backrnd] \
	     -text "u"   -padx $xp -pady $yp -command  $cmd

	# Close close mid vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $closeclosemid $frontness] \
	     -text [_ "Close Close Mid"] -anchor w  -padx $xp -pady $yp \
	  -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list I]}
	button [format ".ipaev.r%dc%d"  $closeclosemid $fronturd] \
	     -text "I"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list Y]}
	button [format ".ipaev.r%dc%d"  $closeclosemid $frontrnd] \
	     -text "Y"   -padx $xp -pady $yp -command  $cmd
	label [format ".ipaev.r%dc%d"  $closeclosemid $centurd] \
	     -text ""   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u028A]}
	button [format ".ipaev.r%dc%d"  $closeclosemid $centrnd] \
	     -text "\u028A"   -padx $xp -pady $yp -command  $cmd
	label [format ".ipaev.r%dc%d"  $closeclosemid $backurd] \
	     -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $closeclosemid $backrnd] \
	     -text ""   -padx $xp -pady $yp

	# Close mid vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $closemid $frontness] \
	     -text [_ "Close Mid"] -anchor w  -padx $xp -pady $yp \
	  -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list e]}
	button [format ".ipaev.r%dc%d"  $closemid $fronturd] \
	     -text "e"   -padx $xp -pady $yp -command  $cmd

	set cmd {$::InsertionTarget insert insert [list \u00F8]}
	set bn [format ".ipaev.r%dc%d"  $closemid $frontrnd]
	button $bn -text "\u00F8"   -padx $xp -pady $yp -command  $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list o\u0308]}
	bind $bn <Enter> {ipaentry::vfphi}
	bind $bn <Leave> {ipaentry::vrphi}
	bhcham $bn;

	set cmd {$::InsertionTarget insert insert [list \u0258]}
	button [format ".ipaev.r%dc%d"  $closemid $centurd] \
	     -text "\u0258"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0275]}
	button [format ".ipaev.r%dc%d"  $closemid $centrnd] \
	     -text "\u0275"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0264]}
	button [format ".ipaev.r%dc%d"  $closemid $backurd] \
	     -text "\u0264"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list o]}
	button [format ".ipaev.r%dc%d"  $closemid $backrnd] \
	     -text "o"   -padx $xp -pady $yp -command  $cmd

	# Mid mid vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $midmid $frontness] \
	     -text [_ "Mid Mid"] -anchor w  -padx $xp -pady $yp \
	  -bg $::ColorSpecs(IPAHeadings,Background)]
	label [format ".ipaev.r%dc%d"  $midmid $fronturd] \
	     -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $midmid $frontrnd] \
	     -text ""   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u0259]}
	button [format ".ipaev.r%dc%d"  $midmid $centurd] \
	     -text "\u0259"   -padx $xp -pady $yp -command  $cmd
	set cmd {BackDelete $::InsertionTarget}
	set bn [format ".ipaev.r%dc%d" $midmid $centrnd];
	button $bn -text [_ "Delete"]  \
	     -padx $xp -pady $yp -command $cmd -background "\#E0D0FF";
	set DeleteButton $bn;
	label [format ".ipaev.r%dc%d"  $midmid $backurd] -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $midmid $backrnd] -text ""   -padx $xp -pady $yp

	# Open mid vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $openmid $frontness] \
	     -text [_ "Open Mid"] -anchor w  -padx $xp -pady $yp \
  	     -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list \u025B]}
	button [format ".ipaev.r%dc%d"  $openmid $fronturd] \
	     -text \u025B   -padx $xp -pady $yp -command  $cmd

	set cmd {$::InsertionTarget insert insert [list \u0153]}
	set bn [format ".ipaev.r%dc%d"  $openmid $frontrnd]
	button $bn -text "\u0153"   -padx $xp -pady $yp -command  $cmd
	bind $bn <<B3>> {$::InsertionTarget insert insert [list \u0254\u0308]}
	bind $bn <Enter> {ipaentry::vfoe}
	bind $bn <Leave> {ipaentry::vroe}
	bhcham $bn;

	set cmd {$::InsertionTarget insert insert [list \u025C]}
	button [format ".ipaev.r%dc%d"  $openmid $centurd] \
	     -text "\u025C"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u025E]}
	button [format ".ipaev.r%dc%d"  $openmid $centrnd] \
	     -text "\u025E"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u028C]}
	button [format ".ipaev.r%dc%d"  $openmid $backurd] \
	     -text "\u028C"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0254]}
	button [format ".ipaev.r%dc%d"  $openmid $backrnd] \
	     -text "\u0254"   -padx $xp -pady $yp -command  $cmd

	# Open open mid vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $openopenmid $frontness] \
	     -text [_ "Open Open Mid"] -anchor w  -padx $xp -pady $yp \
  	     -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list \u00E6]}
	button [format ".ipaev.r%dc%d"  $openopenmid $fronturd] \
	     -text \u00E6   -padx $xp -pady $yp -command  $cmd
	label [format ".ipaev.r%dc%d"  $openopenmid $frontrnd] -text ""   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u0250]}
	button [format ".ipaev.r%dc%d"  $openopenmid $centurd] \
	     -text "\u0250"   -padx $xp -pady $yp -command  $cmd
	label [format ".ipaev.r%dc%d"  $openopenmid $centrnd] \
		     -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $openopenmid $backurd] \
	     -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $openopenmid $backrnd] \
	     -text ""   -padx $xp -pady $yp

	# Open  vowels
	lappend IPAEVRowLabelList [label [format ".ipaev.r%dc%d" $open $frontness] \
	   -text [_ "Open"] -anchor w  -padx $xp -pady $yp \
	   -bg $::ColorSpecs(IPAHeadings,Background)]
	set cmd {$::InsertionTarget insert insert [list a]}
	button [format ".ipaev.r%dc%d"  $open $fronturd] \
	     -text "a"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0276]}
	button [format ".ipaev.r%dc%d"  $open $frontrnd] \
	     -text "\u0276"   -padx $xp -pady $yp -command  $cmd
	label [format ".ipaev.r%dc%d"  $open $centurd] \
	     -text ""   -padx $xp -pady $yp
	label [format ".ipaev.r%dc%d"  $open $centrnd] \
	     -text ""   -padx $xp -pady $yp
	set cmd {$::InsertionTarget insert insert [list \u0251]}
	button [format ".ipaev.r%dc%d"  $open $backurd] \
	     -text "\u0251"   -padx $xp -pady $yp -command  $cmd
	set cmd {$::InsertionTarget insert insert [list \u0252]}
	button [format ".ipaev.r%dc%d"  $open  $backrnd] \
	     -text "\u0252"   -padx $xp -pady $yp -command  $cmd

	for {set row 0} {$row <= $lastvrow} {incr row} {
	    set line [list];
	    for {set col 0} {$col <= $lastvcolumn} {incr col} {
		set cell [format ".ipaev.r%dc%d" $row $col]
		lappend line $cell
		balloonhelp_for $cell  \
		    [_ "Left click to insert standard characters.\n\
Right click to insert alternative characters."]
	    }
	    eval grid $line -sticky news;
	}
	balloonhelpd_for $DeleteButton [_ "No, there isn't a mid-mid central rounded vowel called \"Delete\".\nThis is so that you can correct mistakes while using\nthe mouse, without having to go back to the keyboard."]
	if {!$DisplayVowelChartColumnLabelsP} {UnpackVowelColumnLabels}
	if {!$DisplayVowelChartRowLabelsP} {UnpackVowelRowLabels}
    }

#Rearrange into functional groups, e.g. tone marks, vowel quality marks,
#etc. Separate IPA and non-IPA.

    proc PopupIPAEntryD {} {
	set PerRow 10;
	set BaseChar "o";
	set xp 2;
	set yp 3;
	toplevel .ipaed -borderwidth 3
	wm title .ipaed [_ "Phonetic Diacritics"]
	BindKeys .ipaed
	set DownMsg [_ "Display IPA Diacritic Chart"];
	set UpMsg   [_ "Remove IPA Diacritic Chart"];
	bind .ipaed <Destroy> \
	    "set ::IPADIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPADIndex -label \"$DownMsg\""
	bind .ipaed <Unmap> \
	    "set ::IPADIsDisplayedP 0;$::CEM entryconfigure $::ToggleIPADIndex -label \"$DownMsg\""
	bind .ipaed <Map> \
	    "set ::IPADIsDisplayedP 1;$::CEM entryconfigure $::ToggleIPADIndex -label \"$UpMsg\""
	array set Diacritics [list\
				  0  {"\u02D0" "long"}\
				  1  {"\u02D1" "half long"}\
				  2  {"\u0303" "nasalized"}\
				  3  {"\u0324" "breathy voice"}\
				  4  {"\u0325" "voiceless"}\
				  5  {"\u032C" "voiced"}\
				  6  {"\u031A" "unreleased"}\
				  7  {"\u031C" "lower/open variety of vowel"}\
				  8  {"\u031D" "raised/closed variety of vowel"}\
				  9  {"\u031E" "lower/open variety of vowel"}\
				  10  {"\u031F" "advanced/fronted"}\
				  11  {"\u0320" "retracted/backed"}\
				  12  {"\u0321" "palatalized"}\
				  13  {"\u0322" "retroflex"}\
				  14  {"\u0323" "closer variety of vowel/retroflex"}\
				  15  {"\u0329" "syllabic"}\
				  16  {"\u032A" "dental"}\
				  17  {"\u032B" "labialized"}\
				  18  {"\u0330" "creaky voice"}\
				  19  {"\u0334" "velarized/pharyngealized"}\
				  20  {"\u0346" "dentolabial"}\
				  21  {"\u0347" "alveolar"}\
				  22  {"\u0348" "strong articulation"}\
				  23  {"\u0349" "weak articulation"}\
				  24  {"\u02CA" "high tone"}\
				  25  {"\u02CB" "low tone"}\
				  26  {"\u0302" "falling tone"}\
				  27  {"\u0304" "long"}\
				  28  {"\u0306" "short"}\
				  29  {"\u0308" "non-canonical backness"}\
				  30  {"\u02CC" "secondary stress"}\
				  31  {"\u02C8" "primary stress/downstep"}\
				  32  {"\u02B8" "palatalized"}\
				  33  {"\u02DE" "rhotacized"}\
				  34  {"\u0328" "nasalized"}\
				  35  {"\u034A" "denasal"}\
				  36  {"\u034B" "nasal escape"}\
				  37  {"\u034C" "velopharyngeal friction"}\
				  38  {"\u034D" "labial spreading"}\
				  39  {"\u034E" "whistled"}
				 ];

	set DiacriticCnt [llength [array names Diacritics]];
	set Rows [expr ceil(double($DiacriticCnt)/double($PerRow))]
	set Total [expr $PerRow * $Rows]
	#Generate buttons with blank labels for empty padding slots
	for {set k 0} {$k < $Total} {incr k} {
	    set row [expr $k/$PerRow]
	    set col [expr $k%$PerRow]
	    if {[info exist Diacritics($k)]} {
		button .ipaed.r${row}c${col} -text "$BaseChar[lindex $Diacritics($k) 0]"\
		    -padx $xp -pady $yp\
		    -command "\$::InsertionTarget insert insert  [lindex $Diacritics($k) 0]"
		balloonhelpd_for .ipaed.r${row}c${col} [_ [lindex $Diacritics($k) 1]]  
	    } else {
		label .ipaed.r${row}c${col} -text "";
	    }
	    set LastRow $row;
	    set LastCol $col;
	}
	#Lay the buttons and labels out in a grid.
	for {set row 0} {$row <= $LastRow} {incr row} {
	    set line [list];
	    for {set col 0} {$col <= $LastCol} {incr col} {
		set cell [format ".ipaed.r%dc%d" $row $col]
		lappend line $cell
	    }
	    eval grid $line -sticky news;
	}
    }

#Accented letters
    proc PopupIPAEntryA {} {
	set xp 3;
	set yp 3;
	toplevel .ipaea -borderwidth 4
	wm title .ipaea [_ "Accented Letters"]
	BindKeys .ipaea
	bind .ipaea <Destroy> {set ::IPAAIsDisplayedP 0}
	set DownMsg [_ "Display Accented Letter Chart"]
	set UpMsg   [_ "Remove Accented Letter Chart"]
	set AccentedLetters [list \
				 {"\u00E0" "a with grave" "\u00C0"}\
				 {"\u00E1" "a with acute" "\u00C1"}\
				 {"\u00E2" "a with circumflex" "\u00C2"}\
				 {"\u00E3" "a with tilde" "\u00C3"}\
				 {"\u00E4" "a with diaresis" "\u00C4"}\
				 {"\u00E5" "a with ring above" "\u00C5"}\
				 {"\u0101" "a with macron" "\u0100"}\
				 {"\u0103" "a with breve" "\u0102"}\
				 {"\u0105" "a with ogonek" "\u0104"}\
				 {"\u01CE" "a with caron" "A\u030C"}\
				 {"\u2C65" "a with stroke" "\u023A"}\
				 {"\u0227" "a with dot above" "\u0226"}\
				 {"\u1EA1" "a with dot below" "\u1EA0"}\
				 {"\u0201" "a with double grave" "\u0200"}\
				 {"\u0203" "a with inverted breve" "\u0202"}\
				 {"\u01DF" "a with diaresis and macron" "\u01DE"}\
				 {"\u01E1" "a with dot above and macron" "\u01E0"}\
				 {"\u01FB" "a with ring above and acute" "\u01FA"}\
				 {"\u01E3" "ash with macron" "\u01E2"}\
				 {"\u01FD" "ash with acute" "\u01FC"}\
				 {"\u0180" "b with stroke" "\u0243"}\
				 {"\u0183" "b with topbar" "\u0182"}\
				 {"\u1E03" "b with dot above" "\u1E02"}\
				 {"\u1E05" "b with dot below" "\u1E04"}\
				 {"\u1E07" "b with line below" "\u1E06"}\
				 {"\u0188" "c with hook" "\u0187"}\
				 {"\u0107" "c with acute" "\u0106"}\
				 {"\u0109" "c with circumflex" "\u0108"}\
				 {"\u010B" "c with dot above" "\u010A"}\
				 {"\u00E7" "c with cedilla" "\u00C7"}\
				 {"\u1E09" "c with cedilla and acute" "\u1E08"}\
				 {"\u010D" "c with caron" "\u010C"}\
				 {"\u023C" "c with stroke" "\u023B"}\
				 {"\u018C" "d with topbar" "\u018B"}\
				 {"\u010F" "d with caron" "\u010E"}\
				 {"\u0111" "d with stroke" "\u0110"}\
				 {"\u0221" "d with curl" "\uFFFD"}\
				 {"\u1E0B" "d with dot above" "\u1E0A"}\
				 {"\u1E0D" "d with dot below" "\u1E0C"}\
				 {"\u1E0F" "d with line below" "\u1E0E"}\
				 {"\u00E8" "e with grave" "\u00C8"}\
				 {"\u00E9" "e with acute" "\u00C9"}\
				 {"\u00EA" "e with circumflex" "\u00CA"}\
				 {"e\u0303" "e with tilde" "E\u0303"}\
				 {"\u00EB" "e with diaresis" "\u00CB"}\
				 {"\u0113" "e with macron" "\u0112"}\
				 {"\u0115" "e with breve" "\u0114"}\
				 {"\u0117" "e with dot above" "\u0116"}\
				 {"\u1EB9" "e with dot below" "\u1EB8"}\
				 {"\u0119" "e with ogonek" "\u0118"}\
				 {"\u011B" "e with caron" "\u011A"}\
				 {"\u0247" "e with stroke" "\u0246"}\
				 {"\u0229" "e with cedilla" "\u0228"}\
				 {"\u0205" "e with double grave" "\u0204"}\
				 {"\u0207" "e with inverted breve" "\u0206"}\
				 {"\u01F5" "g with acute" "\u01F4"}\
				 {"\u011D" "g with circumflex" "\u011C"}\
				 {"\u011F" "g with breve" "\u011E"}\
				 {"\u0121" "g with dot above" "\u0000"}\
				 {"\u0123" "g with cedilla" "\u0122"}\
				 {"\u01E5" "g with stroke" "\u01E4"}\
				 {"\u01E7" "g with caron" "\u01E6"}\
				 {"\u021F" "h with caron" "\u021E"}\
				 {"\u0125" "h with circumflex" "\u0124"}\
				 {"\u0127" "h with stroke" "\u0126"}\
				 {"\u1E25" "h with dot below" "\u1E24"}\
				 {"\u00EC" "i with grave" "\u00CC"}\
				 {"\u00ED" "i with acute" "\u00CD"}\
				 {"\u00EE" "i with circumflex" "\u00CE"}\
				 {"\u00EF" "i with diaresis" "\u00CF"}\
				 {"\u0129" "i with tilde" "\u0128"}\
				 {"\u012B" "i with macron" "\u012A"}\
				 {"\u012D" "i with breve" "\u012C"}\
				 {"\u012F" "i with ogonek" "\u012E"}\
				 {"\u01D0" "i with caron" "\u01CF"}\
				 {"\u0209" "i with double grave" "\u0208"}\
				 {"\u020B" "i with inverted breve" "\u020A"}\
				 {"\u1ECB" "i with dot below" "\u1ECA"}\
				 {"\u0131" "dotless i" "\u0049"}\
				 {"\u0135" "j with circumflex" "\u0134"}\
				 {"\u01F0" "j with caron" "J\u030C"}\
				 {"\u0249" "j with stroke" "J\u0248"}\
				 {"\u01E9" "k with caron" "\u01E8"}\
				 {"\u0199" "k with hook" "\u0198"}\
				 {"\u0137" "k with cedilla " "\u0136"}\
				 {"\u1E33" "k with dot below" "\u1E32"}\
				 {"\u013A" "l with acute" "\u0139"}\
				 {"\u013C" "l with cedilla" "\u013B"}\
				 {"\u013E" "l with caron" "\u013D"}\
				 {"\u0140" "l with middle dot" "\u013F"}\
				 {"\u0142" "l with stroke" "\u0141"}\
				 {"\u019A" "l with bar" "\u023D"}\
				 {"\u0234" "l with curl" "\uFFFD"}\
				 {"\u1E37" "l with dot below" "\u1E36"}\
				 {"\u1E39" "l with dot below and macron" "\u01E38"}\
				 {"\u1E3B" "l with line below" "\u1E3A"}\
				 {"\u019B" "lambda with stroke" "\uFFFD"}\
				 {"\u1E3F" "m with acute" "\u1E3E"}\
				 {"\u1E43" "m with dot below" "\u1E42"}\
				 {"\u1E41" "m with dot above" "\u1E40"}\
				 {"\u0271" "m with hook" "\uFFFD"}\
				 {"\u0235" "n with a curl" "\uFFFD"}\
				 {"\u0146" "n with cedilla" "\u0145"}\
				 {"\u0148" "n with caron" "\u0147"}\
				 {"\u01F9" "n with grave" "\u01F8"}\
				 {"\u00F1" "n with tilde" "\u00D1"}\
				 {"\u019E" "n with long right leg" "\u0220"}\
				 {"\u0144" "n with acute" "\u0143"}\
				 {"\u1E45" "n with dot above" "\u1E44"}\
				 {"\u1E47" "n with dot below" "\u1E46"}\
				 {"\u1E49" "n with line below" "\u1E48"}\
				 {"\u00F2" "o with grave" "\u00D2"}\
				 {"\u00F3" "o with acute" "\uD3"}\
				 {"\u00F4" "o with circumflex" "\u00D4"}\
				 {"\u00F5" "o with tilde" "\u00D5"}\
				 {"\u00F6" "o with diaresis" "\u00D6"}\
				 {"\u00F8" "o with stroke" "\u00D8"}\
				 {"\u014D" "o with macron" "\u014C"}\
				 {"\u014F" "o with breve" "\u014E"}\
				 {"\u0151" "o with double acute" "\u0150"}\
				 {"\u01A1" "o with horn" "\u01A0"}\
				 {"\u01EB" "o with ogonek" "\u01EA"}\
				 {"\u01ED" "o with ogonek and macron" "\u01EC"}\
				 {"\u01D2" "o with caron" "\u01D1"}\
				 {"\u022B" "o with diaresis and macron" "\u022A"}\
				 {"\u022D" "o with tilde and macron" "\u022C"}\
				 {"\u022F" "o with dot above" "\u022E"}\
				 {"\u01FF" "o with stroke and acute" "\u01FE"}\
				 {"\u020D" "o with double grave" "\u020C"}\
				 {"\u020F" "o with inverted breve" "\u020E"}\
				 {"\u0231" "o with dot above and macron" "\u0230"}\
				 {"\u1ECD" "o with dot below" "\u1ECC"}\
				 {"\u0223" "ou" "\u0222"}\
			         {"\u01A5" "p with hook" "\u01A4"}\
				 {"\u0155" "r with acute" "\u0154"}\
				 {"\u0157" "r with cedilla" "\u0156"}\
				 {"\u0159" "r with caron" "\u0158"}\
				 {"\u024D" "r with stroke" "\u024C"}\
				 {"\u0211" "r with double grave" "\u0210"}\
				 {"\u0213" "r with inverted breve" "\u0214"}\
				 {"\u1E5B" "r with dot below" "\u1E5A"}\
				 {"\u027F" "reversed r with fishhook" "\uFFFD"}\
				 {"\u015B" "s with acute" "\u015A"}\
				 {"\u015D" "s with circumflex" "\u015C"}\
				 {"\u015F" "s with cedilla" "\u015E"}\
				 {"\u0161" "s with caron" "\u0160"}\
				 {"\u0219" "s with comma below" "\u0218"}\
				 {"\u1E63" "s with dot below" "\u1E62"}\
				 {"\u021B" "t with comma below" "\u021A"}\
				 {"\u01AB" "t with palatal hook" "\uFFFD"}\
				 {"\u01AD" "t with hook" "\u01AC"}\
				 {"\u0163" "t wwith cedilla" "\u0162"}\
				 {"\u0165" "t with caron" "\u0164"}\
				 {"\u0167" "t with stroke" "\u0166"}\
				 {"\u2C66" "t with diagonal stroke" "\u023E"}\
				 {"\u00F9" "u with grave" "\u00D9"}\
				 {"\u00FA" "u with acute" "\u00DA"}\
				 {"\u00FB" "u with circumflex" "\u00DB"}\
				 {"\u00FC" "u with diaresis" "\u00DC"}\
				 {"\u01D4" "u with caron" "\u01D3"}\
				 {"\u01D6" "u with diaresis and macron" "\u01D5"}\
				 {"\u01D8" "u with diaresis and acute" "\u01D7"}\
				 {"\u01DA" "u with diaresis and caron" "\u01D9"}\
				 {"\u01DC" "u with diaresis and grave" "\u01DB"}\
				 {"\u01B0" "u with horn" "\u01AF"}\
				 {"\u0169" "u with tilde" "\u0168"}\
				 {"\u016B" "u with macron" "\u016A"}\
				 {"\u016D" "u with breve" "\u016C"}\
				 {"\u016F" "u with ring above" "\u016E"}\
				 {"\u0171" "u with double acute" "\u0170"}\
				 {"\u0173" "u with ogonek" "\u0172"}\
				 {"\u0215" "u with double grave" "\u0214"}\
				 {"\u0217" "u with inverted breve" "\u0216"}\
				 {"\u1EE5" "u with dot below" "\u1EE4"}\
				 {"\u2C74" "v with curl" "\uFFFD"}\
				 {"\u0175" "w with circumflex" "\u0174"}\
				 {"\u01B4" "y with hook" "\u01B3"}\
				 {"\u00FD" "y with acute" "\u00DD"}\
				 {"\u00FF" "y with diaresis" "\u0278"}\
				 {"\u0233" "y with macron" "\u0232"}\
				 {"\u0177" "y with circumflex" "\u0176"}\
				 {"\u024F" "y with stroke" "\u024E"}\
				 {"\u017A" "z with acute" "\u0179"}\
				 {"\u017C" "z with dot above" "\u017B"}\
				 {"\u017E" "z with caron" "\u017D"}\
				 {"\u01B6" "z with stroke" "\u01B5"}\
				 {"\u0225" "z with hook" "\u0224"}\
				 {"\u0236" "t with curl" "\uFFFD"}\
				 {"\u01EF" "zh with caron" "\u01EE"}]

	if {$::UseNoGlyphs} {
	    lappend AccentedLetters {"\u1D8D" "x with palatal hook" "X\u0321"}\
				 {"\u1D6F" "m with middle tilde" "\uFFFD"}
	}
	
	#Sort by gloss
	set AccentedLetters [lsort -index 1 $AccentedLetters]

#Done through 0236

	set AccentedLetterCnt [llength $AccentedLetters];
	if {[info exists ::AccentedLettersPerRow]} {
	    set PerRow $::AccentedLettersPerRow
	} else {
	    set PerRow [expr int(1.0 * ceil(sqrt($AccentedLetterCnt)))]
	}
	set Rows [expr ceil(double($AccentedLetterCnt)/double($PerRow))]
	set Total [expr $PerRow * $Rows]
	#Generate buttons with blank labels for empty padding slots
	for {set k 0} {$k < $Total} {incr k} {
	    set row [expr $k/$PerRow]
	    set col [expr $k%$PerRow]
	    if {$k < $AccentedLetterCnt} {
		button .ipaea.r${row}c${col} -text "[lindex [lindex $AccentedLetters $k] 0]"\
		    -padx $xp -pady $yp\
		    -command "\$::InsertionTarget insert insert  [lindex [lindex $AccentedLetters $k] 0]"
		balloonhelpd_for .ipaea.r${row}c${col} [_ [lindex [lindex $AccentedLetters $k] 1] ]  
		set cap [lindex [lindex $AccentedLetters $k] 2]
		bind .ipaea.r${row}c${col} <<B3>> "\$::InsertionTarget insert insert $cap"
	    } else {
		label .ipaea.r${row}c${col} -text "";
	    }
	}
	set LastRow $row;
	set LastCol $col;
	set LastCell .ipaea.r${row}c${col}
	destroy $LastCell
	button $LastCell -image $::LeftArrowImage -command {BackDelete $::InsertionTarget}
	balloonhelpd_for $LastCell  [_ "Delete, so that you can correct mistakes while using\nthe mouse, without having to go back to the keyboard."]
	
	#Lay the buttons and labels out in a grid.
	for {set row 0} {$row <= $LastRow} {incr row} {
	    set line [list];
	    for {set col 0} {$col <= $LastCol} {incr col} {
		set cell [format ".ipaea.r%dc%d" $row $col]
		lappend line $cell
	    }
	    eval grid $line -sticky news;
	}
    }
}


proc SetupImages {} {
    global LeftArrowImage

    set LeftArrowImage [image create photo -data {
	R0lGODlhFAAUAIAAAAAAAL+/vyH5BAEAAAEALAAAAAAUABQAAAIqjI+py43gGIAxTVrNxXVz
	54WTIopWGZ7oRq7X4o4wm2lvbX+ZjGv9/ysAADs=
    }]
}
SetupImages;

proc BackDelete {w} {
    if {[string equal [winfo class $w] "Text"]} {
	set Insert [$w index insert]
	set FirstHalf  [$w get 1.0 $Insert-1chars]
	set SecondHalf [string trimright [$w get insert end]]
	$w delete 1.0 end;
	$w insert 1.0 $FirstHalf$SecondHalf;
    } else {
	set delind [expr [$w index insert] -1];
	$w delete $delind;
    }
}

proc PopupCharEntryByCode {} {
    toplevel .charent -borderwidth 4 -relief raised
    BindKeys .charent;
    wm title .charent "Entry By Codepoint";
    after idle {
	update idletasks
	set xmax [winfo screenwidth .charent]
	set ymax [winfo screenheight .charent]
	set x0 [expr 1 * ($xmax -[winfo reqwidth .charent])/3];
	set y0 [expr 1 * ($ymax -[winfo reqheight .charent])/3];
	wm geometry .charent "+$x0+$y0";
    }
    label .charent.title -text [_ "Insert Character by Numerical Code"]
    frame .charent.ef;
    entry .charent.ef.ent -width 8 -font MainFont -relief flat -bg \#E0EEFF;
    label .charent.ef.prefix -text "0x" -font MainFont
    pack .charent.ef.prefix -side left  -expand 1 -fill x -anchor e
    pack .charent.ef.ent    -side right -expand 1 -fill x -anchor w
    button .charent.d  -text [_ "Dismiss"] -command {destroy .charent}
    button .charent.x  -text [_ "Delete"] -command [list BackDelete $::CharByCodeInsertionTarget]
    button .charent.i  -text [_ "Insert"] -command {InsertUnicode}
    pack .charent.title -side top
    pack .charent.ef -side top
    pack .charent.d -side left -expand 1 -fill both
    pack .charent.x -side left -expand 1 -fill both
    pack .charent.i -side right -expand 1 -fill both
    focus .charent.ef.ent;
    set DownMsg [_ "Display Widget for Entering Characters by Unicode Code"];
    set UpMsg   [_ "Remove Widget for Entering Characters by Unicode Code"];
    bind .charent <Destroy> \
	"set ::CharEntryByCodeIsDisplayedP 0;$::CEM entryconfigure $::ToggleCharEntryByCodeIndex -label \"$DownMsg\""
    bind .charent <Unmap> \
	"set ::CharEntryByCodeIsDisplayedP 0;$::CEM entryconfigure $::ToggleCharEntryByCodeIndex -label \"$DownMsg\""
    bind .charent <Map> \
	"set ::CharEntryByCodeIsDisplayedP 1;$::CEM entryconfigure $::ToggleCharEntryByCodeIndex -label \"$UpMsg\""
    bind .charent.ef.ent <Destroy> {set ::InsertionTarget .pat.core.ent}
    bind .charent.ef.ent <Return> {InsertUnicode}
    bind .charent.ef.ent <Control-k> {.charent.ef.ent delete 0 end;break}


    set charentbh  [_ "Enter a character by its Unicode code, as a sequence of four hexadecimal digits.\nFor example, you may specify \'\u0298\', the International Phonetic Alphabet\nsymbol for the bilabial click, as 0298, and the Chinese character \'\u4E39\' \"egg\" as 4E39.\nDo not type the prefix 0x shown to the left of the entry box.\nIt is entered for you automatically. Return inserts the current character.\nControl-k erases the current character code."]
    balloonhelp_for .charent $charentbh
    balloonhelp_for .charent.title $charentbh
    balloonhelp_for .charent.ef.ent $charentbh
    balloonhelp_for .charent.i $charentbh
    balloonhelp_for .charent.d $charentbh
    balloonhelp_for .charent.x $charentbh
    set ::CharEntryByCodeIsDisplayedP 1;
}

#Get entry from popup, validate it, and insert it into the insertion target.
proc InsertUnicode {} {
    ClearMessageWindow;
    set str [.charent.ef.ent get];
    if {[string length $str] == 0} {
	ShowMessage [_ "The empty string is not a valid Unicode codepoint"]
	return ;
    }
    #Validate
    set BadValueP 0;
    #Make sure all digits are hex and that prefix is appropriate.
    if {[regexp {[[:xdigit:]]{4,4}} $str] == 0} {
	ShowMessage [_ "$str is not a well-formed hexadecimal Unicode value"]
	return;
    }
    set str [format "0x%s" $str]
    if {[scan $str "%x" num] < 1} {
	ShowMessage [format [_ "Ill-formed code %s"] $str]
	return;
    }

    #Reject illegal codepoints.
    if {$num  >  65535} {
	ShowMessage [_ "Tcl/Tk may not support codepoints outside the BMP (Plane 0)."]
    }
    set BadValueP 0;
    set BadRanges [list\
  	       0x0750 0x077F\
	       0x07C0 0x08FF\
	       0x1380 0x139F\
	       0x18B0 0x18FF\
	       0x1980 0x19DF\
	       0x1A00 0x1CFF\
	       0x1D80 0x1DFF\
	       0x2C00 0x2E7F\
	       0x2FE0 0x2FEF\
	       0x31C0 0x31EF\
	       0x9FB0 0x9FFF\
	       0xA4D0 0xABFF\
	       0xD7B0 0xD7FF\
	       0xD800 0xDBFF\
	       0xDC00 0xDFFF\
	       0xFE10 0xFE1F\
	       0x10140 0x102FF\
	       0x104B0 0x107FF\
	       0x10840 0x1CFFF\
	       0x1D200 0x1D2FF\
	       0x1D360 0x1D3FF\
	       0x1D800 0x1FFFF\
	       0x2A6E0 0x2F7FF\
	       0x2FAB0 0x2FFFF\
	       0xE0080 0xE00FF\
	       0xE01F0 0xEFFFF\
	       0xFFFFE 0xFFFFF];

    for {set k 0} {$k < [llength $BadRanges]} {incr k} {
	if { ($num >= [lindex $BadRanges [expr 2 * $k]]) &&\
		 ($num <= [lindex $BadRanges [expr (2 * $k) + 1]])} { 
	    set BadValueP 1;
	    break; 
	}
    }
    if {$BadValueP} {
	ShowMessage [format [_ "%s is not a valid Unicode codepoint"] $str]
	return;
    }
    if {$num >= 1114110} {
	ShowMessage [_ "Warning: codepoints above 0x10FFFD have not been assigned as of version 4.0."]
    }

    #Insert
    $::CharByCodeInsertionTarget insert insert [format "%c" $num]
}

proc IndicateIPACUp {} {
	$::CEM entryconfigure $::ToggleIPACIndex \
	    -label [_ "Remove IPA Consonant Chart"];
	set ::IPACIsDisplayedP 1;
}

proc IndicateIPACDown {} {
	$::CEM entryconfigure $::ToggleIPACIndex \
	    -label [_ "Display IPA Consonant Chart"];
	set ::IPACIsDisplayedP 0;
}

proc ToggleIPAC {} {
    global IPACIsDisplayedP;
    global m;

    if { $IPACIsDisplayedP == 0} {
	if {[winfo exists .ipaec]} {
	    wm deiconify .ipaec;
	} else { 
	    ipaentry::PopupIPAEntryC;
	}
	IndicateIPACUp;
    } else {
	wm iconify .ipaec;
	IndicateIPACDown;
    }
}

proc IndicateIPAVUp {} {
	$::CEM entryconfigure $::ToggleIPAVIndex \
	    -label [_ "Remove IPA Vowel Chart"];
	set ::IPAVIsDisplayedP 1;
}

proc IndicateIPAVDown {} {
	$::CEM entryconfigure $::ToggleIPAVIndex \
	    -label [_ "Display IPA Vowel Chart"];
	set ::IPAVIsDisplayedP 0;
}

proc ToggleIPAV {} {
    global IPAVIsDisplayedP;
    global m;

    if { $IPAVIsDisplayedP == 0} {
	if {[winfo exists .ipaev]} {
	    wm deiconify .ipaev;
	} else { 
	    ipaentry::PopupIPAEntryV;
	}
	$::CEM entryconfigure $::ToggleIPAVIndex -label [_ "Remove IPA Vowel Chart"];
	set IPAVIsDisplayedP 1;
    } else {
	wm iconify .ipaev;
	$::CEM entryconfigure $::ToggleIPAVIndex -label [_ "Display IPA Vowel Chart"];
	set IPAVIsDisplayedP 0;
    }
}

proc IndicateIPADUp {} {
	$::CEM entryconfigure $::ToggleIPADIndex \
	    -label [_ "Remove IPA Diacritic Chart"];
	set ::IPADIsDisplayedP 1;
}

proc IndicateIPADDown {} {
	$::CEM entryconfigure $::ToggleIPADIndex \
	    -label [_ "Display IPA Diacritic Chart"];
	set ::IPADIsDisplayedP 0;
}

proc ToggleIPAD {} {
    global IPADIsDisplayedP;
    global m;

    if { $IPADIsDisplayedP == 0} {
	if {[winfo exists .ipaed]} {
	    wm deiconify .ipaed;
	} else { 
	    ipaentry::PopupIPAEntryD;
	}
	IndicateIPADUp
    } else {
	wm iconify .ipaed;
	IndicateIPADDown;
    }
}

proc IndicateIPAAUp {} {
	$::CEM entryconfigure $::ToggleIPAAIndex \
	    -label [_ "Remove Accented Letter Chart"];
	set ::IPAAIsDisplayedP 1;
}

proc IndicateIPAADown {} {
	$::CEM entryconfigure $::ToggleIPAAIndex \
	    -label [_ "Display Accented Letter Chart"];
	set ::IPAAIsDisplayedP 0;
}

proc ToggleIPAA {} {
    global m;

    if { $::IPAAIsDisplayedP == 0} {
	if {[winfo exists .ipaea]} {
	    wm deiconify .ipaea;
	} else { 
	    ipaentry::PopupIPAEntryA;
	}
	IndicateIPAAUp;
    } else {
	wm iconify .ipaea;
	IndicateIPAADown;
    }
}


proc IndicateCharEntryByCodeUp {} {
	$::CEM entryconfigure $::ToggleCharEntryByCodeIndex \
	    -label [_ "Remove Widget for Entering Characters by Unicode Code"];
	set ::CharEntryByCodeIsDisplayedP 1;
}

proc IndicateCharEntryByCodeDown {} {
	$::CEM entryconfigure $::ToggleCharEntryByCodeIndex \
	    -label [_ "Display Widget for Entering Characters by Unicode Code"];
	set ::CharEntryByCodeIsDisplayedP 0;
}

proc ToggleCharEntryByCode {} {
    global CharEntryByCodeIsDisplayedP;
    global m;

    if { $CharEntryByCodeIsDisplayedP == 0} {
	if {[winfo exists .charent]} {
	    wm deiconify .charent;
	    raise .charent;
	} else { 
	    PopupCharEntryByCode;
	}
	IndicateCharEntryByCodeUp;
    } else {
	wm iconify .charent;
	IndicateCharEntryByCodeDown;
    }
}


proc SetDisplayConsonantChartColumnLabelsP {b} {
    global DisplayConsonantChartColumnLabelsP
    set DisplayConsonantChartColumnLabelsP [Boolean $b];
}

proc SetDisplayConsonantChartRowLabelsP {b} {
    global DisplayConsonantChartRowLabelsP
    set DisplayConsonantChartRowLabelsP [Boolean $b];
}

proc SetDisplayVowelChartColumnLabelsP {b} {
    global DisplayVowelChartColumnLabelsP
    set DisplayVowelChartColumnLabelsP [Boolean $b]
}

proc SetDisplayVowelChartRowLabelsP {b} {
    global DisplayVowelChartRowLabelsP
    set DisplayVowelChartRowLabelsP [Boolean $b]
}

proc ControlDisplayConsonantChartColumnLabels {} {
    if {$::DisplayConsonantChartColumnLabelsP} {
	ipaentry::PackConsonantColumnLabels;
    } else {
	ipaentry::UnpackConsonantColumnLabels;
    }
}

proc ControlDisplayConsonantChartRowLabels {} {
    if {$::DisplayConsonantChartRowLabelsP} {
	ipaentry::PackConsonantRowLabels;
    } else {
	ipaentry::UnpackConsonantRowLabels;
    }
}

proc ControlDisplayVowelChartColumnLabels {} {
    if {$::DisplayVowelChartColumnLabelsP} {
	ipaentry::PackVowelColumnLabels;
    } else {
	ipaentry::UnpackVowelColumnLabels;
    }
}

proc ControlDisplayVowelChartRowLabels {} {
    if {$::DisplayVowelChartRowLabelsP} {
	ipaentry::PackVowelRowLabels;
    } else {
	ipaentry::UnpackVowelRowLabels;
    }
}

####################################################

#File     -> LineList	LoadCustomCharacterChart
#LineList -> CDEF	DefineCustomCharacterChart
#CDEF     -> Popup      PopupSpecialPalette

proc ReadCustomCharacterChartPopup {args} {
    if {[llength $args]} {
	set dl [LoadCustomCharacterChart [lindex $args 0]]
    } else {
	set dl [LoadCustomCharacterChart];
    }
    PopupSpecialPalette [DefineCustomCharacterChart $dl]
}

proc DefineCustomCharacterChartPopup {ll} {
    PopupSpecialPalette [DefineCustomCharacterChart $ll];
}

#Returns a linelist.
proc LoadCustomCharacterChart {args} {
    if {[llength $args]} {
	set fn [lindex $args 0]
    } else {
	set fn [tk_getOpenFile -title [_ "Load Custom Character Chart"]];
	if {[string equal $fn ""]} {
	    ShowMessage [_ "File selection cancelled."];
	    return ;
	}
    }
    if { [catch {open $fn "r"} fhd ] != 0} {
	ShowMessage [format [_ "Unable to open character chart definition file %s."] $fn];
	return ;
    }
    set LineCnt 0
    while { [gets $fhd line] > 0} {
	lappend Lines $line;
	incr LineCnt
    }
    close $fhd;
    if {$LineCnt < 1} {
	ShowMessage [_ "File %s is empty" $fn]
	return "";
    }
    ShowMessage [format [_ "Loaded custom character chart definition from %s."] $fn]
    return $Lines;
}


#This procedure takes a list of lines defining a custom
#character entry chart, which may have been read from a
#standalone file or may be an instant list in an
#init file, and generates an internal character chart
#definition, which it stores. It does not actualy
#create a display
proc DefineCustomCharacterChart {LineList {fn NONE}} {
    #The first line is special. It contains meta-information:
    #the title, the desired number of buttons per row,
    #and the proposed font family and size. Only the
    #title is obligatory.
    set line [lindex $LineList 0]
    set flds [split $line "|"]
    set FieldCnt [llength $flds];
    set Title [lindex $flds 0];
    #The remaining lines contain pairs of code sequences and glosses.
    set cd [list];
    set LineList [lrange $LineList 1 end]
    foreach line $LineList {
	set f [split $line "|"];
	set gloss [lindex $f 1];
	set c [string trim [lindex $f 0] \"];
	set cf [split $c];
	set str "\{\"";
	foreach n $cf {
	    append str [format "%s" $n]
	}
	append str "\""
	append str [format " \"%s\"\}" $gloss]
	lappend cd $str;
    }
    set info [list [join $cd]]
    if {$FieldCnt > 1} {
	lappend info [lindex $flds 1];	# Columns
    }
    if {$FieldCnt > 2} {
	lappend info [lindex $flds 2];	# Font family
    }
    if {$FieldCnt > 3} {
	lappend info [lindex $flds 3];	# Font size
    }
    if {![string equal $fn NONE]} {	# File name
	lappend info $fn;
    }
    set ::SpecialCharacterPalette($Title) $info;
    $::CEM add command -label $Title -command "PopupSpecialPalette $Title";
    incr ::CustomCEMEntries;
    return $Title;
}

#Creates a popup chart from a stored definition.
set SccCnt 0;
proc PopupSpecialPalette {Title} {
    set name [format ".scc%d" $::SccCnt] 
    if {[info exists ::SpecialCharacterPalette($Title,WidgetName)]} {
	set w $::SpecialCharacterPalette($Title,WidgetName);
	if {[winfo exists $w]} {
	    wm deiconify $w;
	    raise $w;
	    return ;
	}
    }
    set cdefs [lindex $::SpecialCharacterPalette($Title) 0];
    set Items [llength $::SpecialCharacterPalette($Title)];
    if {$Items > 1} {
	set PerRow [lindex $::SpecialCharacterPalette($Title) 1];
    } else {
	set PerRow 5;
    }
    if {$Items > 2} {
	set FontFamily [lindex $::SpecialCharacterPalette($Title) 2];
    } else {
	set FontFamily $::FontInfo(CharacterEntryFont,family)
    }
    if {$Items > 3} {
	set FontSize [lindex $::SpecialCharacterPalette($Title) 3];
    } else {
	set FontSize $::FontInfo(CharacterEntryFont,size);
    }
    incr ::SccCnt;
    set xp 3;
    set yp 3;
    toplevel $name -borderwidth 4
    set ::SpecialCharacterPalette($Title,WidgetName) $name; 
    wm title $name $Title
    iwidgets::scrolledframe $name.sf -vscrollmode dynamic -hscrollmode dynamic \
	-height 180 -width 250
    set tf [$name.sf childsite]
    pack $name.sf -expand 1 -fill both
    BindKeys $name
    set msg [_ "Left click to insert the lower-case character.\nRight click to insert the upper-case character."]
    balloonhelp_for $name.sf $msg
    bind [$name.sf component vertsb] <<B3>> \
	"ScrollbarMoveBigIncrement [$name.sf component vertsb] 0.20 %x %y"
    set CdefCnt [llength $cdefs];
    set Rows [expr int(ceil(double($CdefCnt)/double($PerRow)))]
    set Total [expr $PerRow * $Rows]
    if {$CdefCnt == $Total} {
	incr Rows;
	set Total [expr $PerRow * $Rows]
    }
    set fontname [string trimleft $name .]Font
    font create $fontname -family $FontFamily -size $FontSize
    for {set k 0} {$k < $Total} {incr k} {
	set row [expr $k/$PerRow]
	set col [expr $k%$PerRow]
	if {$k < $CdefCnt} {
	    set csalt "";
	    set entry [lindex $cdefs $k];
	    set chstr [lindex $entry 0];
	    set gloss [lindex $entry 1];
	    set chstrParts [split $chstr ":"];
	    if {[llength $chstrParts] > 1} {
		set cs    [lindex $chstrParts 0];
		set csalt [lindex $chstrParts 1];
	    } else {
		set cs [lindex $chstr 0];
	    }
	    button $tf.r${row}c${col} -text $cs\
		-padx $xp -pady $yp -font $fontname\
		-command "\$::InsertionTarget insert insert $cs"
	    if {![string equal $csalt ""]} {
		bind $tf.r${row}c${col} <<B3>> "\$::InsertionTarget insert insert $csalt"
	    }
	    balloonhelpd_for $tf.r${row}c${col} $gloss;
	} else {
	    set ln $tf.r${row}c${col};
	    #Generate buttons with blank labels for empty padding slots
	    label $ln  -text "";
	}
    }
    set LastRow $row;
    set LastCol $col;
    set LastCell $tf.r${row}c${col}
    destroy $LastCell
    button $LastCell -image $::LeftArrowImage -command {BackDelete $::InsertionTarget}
    balloonhelpd_for $LastCell  [_ "Delete, so that you can correct mistakes while using\nthe mouse, without having to go back to the keyboard."]
    
    #Lay the buttons and labels out in a grid.
    for {set row 0} {$row <= $LastRow} {incr row} {
	set line [list];
	for {set col 0} {$col <= $LastCol} {incr col} {
	    set cell [format "%s.r%dc%d" $tf $row $col]
	    lappend line $cell
	}
	eval grid $line -sticky news;
    }
    return $name;
}

#End of character insertion code

#Set up balloon help
option add *Balloonhelp*background white widgetDefault
option add *Balloonhelp*foreground black widgetDefault
option add *Balloonhelpinfo.wrapLength 3i  widgetDefault
option add *Balloonhelp.info.justify left widgetDefault
toplevel .balloonhelp -class Balloonhelp -background black -borderwidth 1 -relief flat
#label .balloonhelp.arrow -anchor nw -bitmap @arrow.xbm
#pack .balloonhelp.arrow -side left -fill y
label .balloonhelp.info -font BalloonHelpFont;
pack .balloonhelp.info -side left -fill y
wm overrideredirect .balloonhelp 1
wm withdraw .balloonhelp
set bhInfo(active) 1

proc balloonhelp_control {state} {
     global bhInfo
     if {$state} {
          set bhInfo(active) 1
     } else {
	balloonhelp_cancel
	set bhInfo(active) 0
     }
}

proc balloonhelp_for {win mesg} {
    global bhInfo
    set bhInfo($win) $mesg
    set ::bhOverlapP($win) 1; 
    bind $win <Enter> {+balloonhelp_pending %W}
    bind $win <Leave> {+balloonhelp_cancel}
}

proc balloonhelpd_for {win mesg} {
    global bhInfo
    set ::bhOverlapP($win) 0;
    set bhInfo($win) $mesg
    bind $win <Enter> {+balloonhelp_show %W}
}

proc balloonhelp_pending {win} {
     global bhInfo
     balloonhelp_cancel
     set bhInfo(pending) [after 1000 [list balloonhelp_show $win]]
}

proc balloonhelp_cancel {} {
    global bhInfo
    if { [info exists bhInfo(pending)]} {
	after cancel $bhInfo(pending)
	unset bhInfo(pending)
    }
    wm withdraw .balloonhelp
}

proc balloonhelp_show {win} {
    global bhInfo;
    global bhOverlapP;
    if {$bhOverlapP($win)} {
	set Overlap 25;
    } else {
	set Overlap -10;
    }
    if {[winfo exists $win]} {
	if {$bhInfo(active)} {
	    .balloonhelp.info configure -text $bhInfo($win) -font BalloonHelpFont
	    #Set abcissa
	    set MaxStringWidth 0;
	    foreach line [split $bhInfo($win) "\n"] {
		set StringWidth [font measure BalloonHelpFont -displayof .balloonhelp.info $line]
		if {$StringWidth > $MaxStringWidth} {
		    set MaxStringWidth $StringWidth;
		}
	    }
	    set ScreenWidth [winfo screenwidth $win]
	    set Width [winfo width $win];
	    set LeftEdge  [winfo rootx $win];
	    set RightEdge [expr $LeftEdge + $Width];
	    if {$ScreenWidth - $RightEdge < $MaxStringWidth} { 
		if {$LeftEdge > $MaxStringWidth} {
		    set x [expr $LeftEdge - $MaxStringWidth + $Overlap];
		} else {
		    if {$ScreenWidth - $MaxStringWidth > 0} {
			set x [expr $RightEdge - $MaxStringWidth];
		    } else {
			set x [expr $ScreenWidth - $MaxStringWidth];
		    }
		}
	    } else {
		set x [expr $RightEdge - $Overlap];
	    }
	    #Set ordinate
	    set Height [winfo height $win];
	    set TopEdge [winfo rooty $win];
	    set y [expr $TopEdge + ($Height/2)];
	    wm geometry .balloonhelp +$x+$y
	    wm deiconify .balloonhelp
	    raise .balloonhelp
	}
    }
    if {[info exist bhInfo(pending)]} {
	unset bhInfo(pending)
    }
}

proc PackMessageRegion {} {
    pack $::MSG -side left -expand 1 -fill both;
}

proc ScaleMoveBigIncrement {w k x y} {
    set part [$w identify $x $y]
    set inc 0;
    switch -exact -- $part {
	trough1 {
	    set dir -1;
	}
	trough2 {
	    set dir  1;
	}
	default {
	    return ;
	}
    }
    set Resolution [$w cget -resolution]
    set CurrentValue [$w get]
    set Delta [expr $dir * $k * $Resolution]
    $w set [expr $CurrentValue + $Delta]
}

proc ScrollbarMoveBigIncrement {w f x y} {
    set part [$w identify $x $y]
    switch -exact -- $part {
	trough1 {
	    set dir -1;
	}
	arrow1 {
	    set dir -1;
	}
	trough2 {
	    set dir  1;
	}
	arrow2 {
	    set dir  1;
	}
	default {
	    return ;
	}
    }
    set CurrentFraction [lindex [$w get] 0]
    set NewFraction [expr $CurrentFraction + ($dir * $f)]
    eval [concat [$w cget -command] moveto $NewFraction]
}

proc ShowWebPage {url} {
    global BrowserPIDS;

    if {[string equal $::SystemInfo(System) MacOSX]} {
	lappend BrowserPIDS [exec osascript -e "\"open location $url\""]
	return 
    }
    set BrowserFound 0;
    foreach Browser $::BrowserList {
	if { [string length [auto_execok $Browser]]} {
	    set BrowserFound 1;
	    break ;
	} else {
	    ShowMessage [format \
	     [_ "The browser %s is not available on this machine or not in your path."]\
		 $Browser];
	}
    }
    if {$BrowserFound} {
	lappend BrowserPIDS [exec $Browser $url &]
    } else {
	ShowMessage [_ "No browser on the browser list was located."]
    }
}

proc WebManual {} {
    global ManualPath;
    global ManualURL;

    if {[file exists $ManualPath] == 0} {
	ShowMessage [format [_ "Manual not found at location %s"] $ManualPath];
	return ;
    }
    ShowWebPage $ManualURL;
}

proc ConfirmationDialog {WindowName TitleText MainText LeftText RightText CancelHelp} {
    global wret;
    global MainFont;
    
    toplevel $WindowName -borderwidth 4 -relief raised -bg $::ColorSpecs(ConfirmationDialogueTitle,Background)
    wm title $WindowName "";
    label $WindowName.title -text $TitleText -font MainFont -bg $::ColorSpecs(ConfirmationDialogueTitle,Background) -fg \#00FFFF
    label $WindowName.txt -width 50 -height 1 -font MainFont\
	-relief flat -anchor c -text $MainText -bg $::ColorSpecs(ConfirmationDialogueLabel,Background) -fg \#00FFFF
    button $WindowName.right   -text $RightText\
	-bg $::ColorSpecs(ConfirmationDialogue,Background) -font MainFont -command {set ::wret 1}
    button $WindowName.left -text $LeftText\
	-bg $::ColorSpecs(ConfirmationDialogue,Background) -font MainFont -command {set ::wret 0}
    button $WindowName.cancel -text [_ "Cancel"]\
	-bg $::ColorSpecs(ConfirmationDialogue,Background) -font MainFont -command {set ::wret -1}
    grid $WindowName.title     -row 0 -column 0 -sticky ew -columnspan 3
    grid $WindowName.txt       -row 1 -column 0 -sticky ew -columnspan 3
    grid $WindowName.left      -row 2 -column 0
    grid $WindowName.right     -row 2 -column 2
    grid $WindowName.cancel    -row 2 -column 1
    balloonhelpd_for $WindowName.cancel $CancelHelp;
    tkwait variable wret;
    destroy $WindowName;
    return $wret;
}

proc DelimitNumber {number {delim ","} {GroupSize 3}} {
     # First, extract right hand part of number, up to and including decimal point
     set point [string last "." $number];
     if {$point >= 0} {
         set PostDecimal [string range $number [expr $point + 1] end];
         set PostDecimalP 1;
     } else {
         set point [expr [string length $number] + 1]
         set PostDecimal "";
         set PostDecimalP 0;
     }

     # Now extract any leading spaces.
     set ind 0;
     while {[string equal [string index $number $ind] \u0020]} {
         incr ind;
     }
     set FirstNonSpace $ind;
     set LastSpace [expr $FirstNonSpace - 1];
     set LeadingSpaces [string range $number 0 $LastSpace];

     # Now extract the non-fractional part of the number, omitting leading spaces.
     set MainNumber [string range $number $FirstNonSpace [expr $point -1]];

     # Insert commas into the non-fractional part.
     set Length [string length $MainNumber];
     set Phase  [expr $Length % $GroupSize]
     set PhaseMinusOne  [expr $Phase -1];
     set DelimitedMain "";

     #First we deal with the extra stuff.
     if {$Phase > 0} {
         append DelimitedMain [string range $MainNumber 0 $PhaseMinusOne];
     }
     set FirstInGroup $Phase;
     set LastInGroup [expr $FirstInGroup + $GroupSize -1];
     while {$LastInGroup < $Length} {
         if {$FirstInGroup > 0} {
             append DelimitedMain $delim;
         }
         append DelimitedMain [string range $MainNumber $FirstInGroup $LastInGroup];
         incr FirstInGroup $GroupSize
         incr LastInGroup  $GroupSize
     }

     # Reassemble the number.
     if {$PostDecimalP} {
         return [format "%s%s.%s" $LeadingSpaces $DelimitedMain $PostDecimal];
     } else {
         return [format "%s%s" $LeadingSpaces $DelimitedMain];
     }
 }

#Given a list of syllables as input, return the same list
#with a separator appended to each syllable.
proc AddSeparator {wl} {
	set nl [list]
	foreach wd $wl {
	    lappend nl ${wd}$::Options(SyllableSeparator)
	}
    return $nl;
}

#Return a list of the syllables conforming to the specification passed as argument.
proc GenSyl {sylspec} {
    global PartLists;

    set len [llength $sylspec];
    if {$len < 1} {return ""}
    set sl $PartLists([lindex $sylspec 0]);
    for {set pos 1} {$pos < $len} {incr pos} {
	set sl [CrossProduct $sl $PartLists([lindex $sylspec $pos])];
    }
    return $sl; 
}

#Return a list of the words consisting of a prefix belonging to
#the first argument list and a suffix belonging to the second
#argument list.
proc CrossProduct {X Y} {
    foreach x $X {
	update;
	if {$::AbortP} {error "Aborted by user."}
	foreach y $Y {
	    update;
	    if {$::AbortP} {error "Aborted by user."}
	    lappend sl $x$y;
	}
    }
    return $sl;
}

#Return the number of syllables of the specified pattern
proc CountSyllables {pattern} {
    global PartLists

    upvar \#0 $pattern pl;
    set cnt 0;

    if {[llength $pl]} {
	set cnt 1;
	foreach p $pl {
	    set cnt [expr $cnt * [llength $PartLists($p)]];
	}
    }
    return $cnt;
}

proc CountSyllableTypes {} {
    global SyllableCnts;
    set SyllableCnts(Core)    [CountSyllables ::SyllableStructures(Core)]
    set SyllableCnts(Sole)    [CountSyllables ::SyllableStructures(Sole)]
    set SyllableCnts(Initial) [CountSyllables ::SyllableStructures(Initial)]
    set SyllableCnts(Final)   [CountSyllables ::SyllableStructures(Final)]
}

#Return the number of words to be generated
proc CountWords {} {
    global SyllableCnts;

    if {$::Options(MaximumSyllables) < 1} {return 0}
    CountSyllableTypes;
    set ::WordCnts(1) $SyllableCnts(Sole);
    set WordCnt $::WordCnts(1);

    if {$::Options(MaximumSyllables) > 1} {
	set DisyllableCnt [expr $SyllableCnts(Initial) * $SyllableCnts(Final)]
	set ::WordCnts(2) $DisyllableCnt;
	incr WordCnt $DisyllableCnt;
    }

    #Words of three or more syllables K consist of an initial syllable,
    #a final syllable, and K-2 core syllables. The number of words containing
    #exactly K syllable is therefore |Initial| *|Final| * |Core|^(K-2).
    for {set syl 3} {$syl <= $::Options(MaximumSyllables)} {incr syl} { 
	set Count [expr $SyllableCnts(Initial) * $SyllableCnts(Final)];# |Initial| * |Final|
	set CoreSyllablesInWordOfKSyllables [expr $syl - 2];
	for {set i 1} {$i <= $CoreSyllablesInWordOfKSyllables} {incr i} {
	    set Count [expr $Count * $SyllableCnts(Core)];
	}
	incr WordCnt $Count;
	set ::WordCnts($syl) $Count;
    }
    return $WordCnt;
}

set Options(OldMaximumSyllables) 0;
proc CreateWordCountsBySyllable {e o n} {
    for {set i 1} {$i <= $::Options(OldMaximumSyllables)} {incr i} {
	destroy $::WORDCNT.s$i
    }
    for {set i 1} {$i <= $::Options(MaximumSyllables)} {incr i} {
	set w $::WORDCNT.s$i
	label $w -textvariable ::WordCountsBySyllable($i) -font MainFont
	pack $w -side top -expand 0 -fill none -anchor w
    }
    set ::Options(OldMaximumSyllables) $::Options(MaximumSyllables)
}

proc UpdateWordCountsBySyllable {} {
    for {set i 1} {$i <= $::Options(MaximumSyllables)} {incr i} {
	set ::WordCountsBySyllable($i) [format [_ "%6d\t%15s"] $i [DelimitNumber $::WordCnts($i)]]
    }
}

proc ShowWordCount {e o n} {
    set TotalWords [DelimitNumber [CountWords] "," 3];
    if {$::Options(RandomP)} {
	set ::WordCountText [format [_ "%d from a pool of %s"] $::Options(SampleSize) $TotalWords]
    } else {
	set ::WordCountText [format "%6s\t%15s" [_ "Total"] $TotalWords]
    }
    UpdateCoreCount
    UpdateSoleCount
    UpdateInitialCount
    UpdateFinalCount
    UpdateWordCountsBySyllable
}

#Randomly compare the two arguments. For use in generating a random sort.
proc rcomp {a b} {
    set cval [expr rand()];
    if { $cval < 0.5} {
	return -1;
    } else {
	return 1;
    }
}

proc Generate {} {
    global Options
    global PartLists;

    set ::InProgressP 1;
    set ::AbortP 0;
    set Words [CountWords];
    if {$Words > $::WordLimit} {
	ShowMessage [format [_ "Attempt to generate more than the current limit of %s words refused."] \
			 [DelimitNumber $::WordLimit "," 3]];
	return;
    }
    set ActualWords 0;
    #Clear text window
    $::TXT.txt configure -state normal
    $::TXT.txt delete 1.0 end;
    $::TXT.txt configure -state disabled
    ShowMessage "About to generate $Words words."
    
    #Generate monosyllables and other basic components.
    set Incomplete(1) [list];
    if {[catch {GenSyl $::SyllableStructures(Sole)} SoleSyllables] != 0} {
	ShowMessage [_ "Aborted by user."]
	set ::InProgressP 0;
	return;
    }
    if {[catch {GenSyl $::SyllableStructures(Initial)} InitialSyllables] != 0} {
	ShowMessage [_ "Aborted by user."]
	set ::InProgressP 0;
	return;
    }
    if {[catch {GenSyl $::SyllableStructures(Core)} CoreSyllables] != 0} {
	ShowMessage [_ "Aborted by user."]
	set ::InProgressP 0;
	return;
    }
    if {$::Options(SeparateSyllablesP)} {
	set InitialSyllables [AddSeparator $InitialSyllables]
	set CoreSyllables [AddSeparator $CoreSyllables]
    }
    if {[llength $SoleSyllables]} { 
	set Complete(1) $SoleSyllables;
    } else {
	ShowMessage [_ "No monosyllables are defined."]
	set ::InProgressP 0;
	return;
    }
    ShowMessage [_ "Monosyllables generated."]
    if {[catch {GenSyl $::SyllableStructures(Final)} FinalSyllables] != 0} {
	ShowMessage [_ "Aborted by user."]
	set ::InProgressP 0;
	return;
    }
    if {$::Options(MaximumSyllables) > 1} {
	if {[llength $InitialSyllables] < 1} {
	    ShowMessage [_ "No initial syllables are defined."]
	    set ::InProgressP 0;
	    return;
	}
	if {[llength $FinalSyllables] < 1} {
	    ShowMessage [_ "No final syllables are defined."]
	    set ::InProgressP 0;
	    return;
	}
	set Incomplete(2) $InitialSyllables
	if {[catch {CrossProduct $InitialSyllables $FinalSyllables} CP] != 0} {
	    ShowMessage [_ "Aborted by user."]
	    set ::InProgressP 0;
	    return;
	}
	set Complete(2) $CP;
	ShowMessage [_ "Disyllables generated."]
    }

    #Then all longer words
    if {$::Options(MaximumSyllables) > 2} {
	if {[llength $CoreSyllables] > 0} {
	    for {set syl 3} {$syl <= $::Options(MaximumSyllables)} {incr syl} { 
		if {[catch {CrossProduct $Incomplete([expr $syl - 1]) $CoreSyllables} \
			 Incomplete($syl)] != 0} {
		    ShowMessage [_ "Aborted by user."]
		    set ::InProgressP 0;
		    return;
		} 
		array unset Incomplete [expr $syl -1];# A little garbage collection
		if {[catch {CrossProduct $Incomplete($syl) $FinalSyllables} CP] != 0} {
		    ShowMessage [_ "Aborted by user."]
		    set ::InProgressP 0;
		    return;
		}
		set Complete($syl) $CP;
		ShowMessage [format [_ "Words of %d syllables generated."] $syl]
	    }
	} else {
	    ShowMessage [_ "No core syllables are defined."]
	    set ::InProgressP 0;
	    return;
	}
    }
    set ::TimeStamps(GenerationTime) [clock seconds]
    $::TXT.txt configure -state normal
    if {$::Options(RandomP)} {
	ShowMessage [_ "Drawing random sample..."]
	update;
	#Combine the lists by syllable count into a single list;
	set Total [list];
	for {set syl 1} {$syl <= $::Options(MaximumSyllables)} {incr syl} { 
	    set Total [concat $Total $Complete($syl)]
	}
	set TotalWords [llength $Total]
	#Do a random sort
	set Total [lsort -command rcomp $Total]
	#Now take the first SampleSize of these
	for {set i 0} {$i < $::Options(SampleSize)} {incr i} {
	    lappend olist [lindex $Total $i]
	    incr ActualWords;
	}
	ShowMessage [format [_ "Generated %s words."] [DelimitNumber $ActualWords "," 3]]
	if {$Options(AlphabetizeP)} {
	    set olist [lsort $olist];
	}
	foreach w $olist {
	    .wds.txt insert insert $w\n
	}
	$::TXT.txt configure -state disabled
	set ::InProgressP 0;
	return;
    }
    # If requested, write out list in alphabetical order.
    if {$Options(AlphabetizeP)} {
	ShowMessage [_ "Alphabetizing..."]
	update;
	#Combine the lists by syllable count into a single list;
	set Total [list];
	for {set syl 1} {$syl <= $::Options(MaximumSyllables)} {incr syl} { 
	    set Total [concat $Total $Complete($syl)]
	}
	set Total [lsort $Total];
	foreach wd  $Total {
	    .wds.txt insert insert ${wd}\n
	    incr ActualWords;
	}
    } else {
	#Otherwise write out in order generated.
	for {set syl 1} {$syl <= $::Options(MaximumSyllables)} {incr syl} { 
	    foreach wd  $Complete($syl) {
		.wds.txt insert insert ${wd}\n
		incr ActualWords;
	    }
	}
    }
    $::TXT.txt configure -state disabled
    ShowMessage [format [_ "Generated %s words."] [DelimitNumber $ActualWords "," 3]]
    set ::InProgressP 0;
}

proc ShutDown {} {
    if {$::TimeStamps(GenerationTime) > $::TimeStamps(LastSaveTime)} {
	set resp [ConfirmationDialog .sdt \
		      [_ "The Words Generated Have Not Been Saved"]\
		      [_ "Save?"] [_ "No, Don't Save"] [_ "Yes, Save"]\
		      [_ "Click here if you don't really want to quit after all."]]
	switch -exact -- $resp {
	    1 {SaveWords;after 1000}
	    -1 {return}
	    default {}
	}
    }
    foreach pid $::BrowserPIDS {
	catch {exec kill $pid};
    }
    exit 0;
}

proc Abort {} {
    if {$::InProgressP} {
	set ::AbortP 1;
    } else {
	ShowMessage [_ "Nothing is happening so there is nothing to abort."]
    }
}

proc new_dialog_create {class {win "auto"}} {
    if {$win == "auto"} {
        set count 0
        set win ".ndialog[incr count]"
        while {[winfo exists $win]} {
            set win ".ndialog[incr count]"
        }
    }
    toplevel $win -class $class;
    frame $win.info
    pack $win.info -expand yes -fill both -padx 4 -pady 4
    wm title $win $class
    wm group $win .

    after idle [format {
        update idletasks
        wm minsize %s [winfo reqwidth %s] [winfo reqheight %s]
    } $win $win $win]

    return $win
}

# The following code is taken from the Efftcl library by Mark Harrison and
# Michael McLennan, copyrighted by Mark Harrison and Lucent Technologies, available
# from http://www.awprofessional.com/content/images/0201634740/sourcecode/efftcl.zip.
# As the authors explicitly give permission to "steal the code for your own applications"
# the relevant portions are included here so as not to require the user to install
# to install the library. If you install the library, remove the following and
# uncomment the line "#package require Efftcl" by deleting the crosshatch.

#  Effective Tcl/Tk Programming
#    Mark Harrison, DSC Communications Corp.
#    Michael McLennan, Bell Labs Innovations for Lucent Technologies
#    Addison-Wesley Professional Computing Series
# ======================================================================
#  Copyright (c) 1996-1997  Lucent Technologies Inc. and Mark Harrison
# ======================================================================

proc dialog_info {win} {
    return "$win.info"
}

proc dialog_controls {win} {
    return "$win.controls"
}

proc dialog_wait {win varName} {
    dialog_safeguard $win

    set x [expr [winfo rootx .]+50]
    set y [expr [winfo rooty .]+50]
    wm geometry $win "+$x+$y"

    wm deiconify $win
    grab set $win

    vwait $varName

    grab release $win
    wm withdraw $win
}
bind modalDialog <ButtonPress> {
    wm deiconify %W
    raise %W
}

proc dialog_safeguard {win} {
    if {[lsearch [bindtags $win] modalDialog] < 0} {
        bindtags $win [linsert [bindtags $win] 0 modalDialog]
    }
}

proc CreateTextDisplay {title width height {bg "\#e6b483"} {fg "\#000080"} } {
    set top [new_dialog_create Textdisplay]
    wm title $top $title
    set info [dialog_info $top]
    scrollbar $info.sbar -command "$info.text yview" 
    pack $info.sbar -side right -fill y
    text $info.text -height $height -width $width -font MainFont \
	-wrap word -yscrollcommand "$info.sbar set" \
	-background $bg -foreground $fg -exportselection 1;
    pack $info.text -side left -expand yes -fill both
    $info.text configure -state disabled
    bind $info.sbar <<B3>> "ScrollbarMoveBigIncrement $info.sbar 0.2 %x %y"
    return $top
}

proc PopupDown {n} {
    global PopupList;
    if {[info exists PopupList($n)]} {
	if {[winfo exists $PopupList($n)]} {
	    destroy $PopupList($n);
	    return 1;
	}
    }
    return 0;
}

proc AppendToTextDisplay {top mesg} {
    set info [dialog_info $top]
    $info.text configure -state normal
    $info.text insert end $mesg
    $info.text configure -state disabled
}

set linkNum 0;
proc AppendLinkToTextDisplay {top mesg LinkCode} {
    global linkNum
    set info [dialog_info $top]
    $info.text configure -state normal
    set tag "link[incr linkNum]"
    bind $info.text <ButtonPress> break
    $info.text insert end $mesg [list body $tag]
    $info.text tag configure $tag -foreground red -underline 1
    $info.text tag bind $tag <Enter> \
        "$info.text tag configure $tag -foreground blue"
    $info.text tag bind $tag <Leave> \
        "$info.text tag configure $tag -foreground red"
    $info.text tag bind $tag <ButtonPress> \
        "$LinkCode"
    $info.text configure -state disabled
}

proc About {} {
    global Version;
    if {[PopupDown About] ==1} {return}
    set po [CreateTextDisplay [_ "About This Program"] 72 12]
    set ::PopupList(About) $po
    AppendToTextDisplay $po [format [_ "This is WordGenerator version %s. "] $Version];
    AppendToTextDisplay $po [_ "It generates possible words from a specification of their syllable structure. "];
    AppendToTextDisplay $po [_ "You can obtain the latest version of WordGenerator from: "];
    AppendLinkToTextDisplay $po "http://billposer.org/Software/WordGenerator.html." {ShowWebPage http://billposer.org/Software/WordGenerator.html};
    AppendToTextDisplay $po "\n\n";
    AppendToTextDisplay $po "Copyright (C) 2005, 2006 William J. Poser (billposer@alum.mit.edu). ";
    AppendToTextDisplay $po [_ "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version."];
    AppendToTextDisplay $po "\n\n";
    AppendToTextDisplay $po [_ "This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"];
    AppendToTextDisplay $po [_ "See the GNU General Public License for more details."];
    AppendToTextDisplay $po "\n\n";
    AppendToTextDisplay $po [_ "You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA."];
    bind [dialog_info $po].text <ButtonRelease-1> {PopupSelectionHandler %W}
}

proc BugReports {} {
    global Version;
    global tcl_platform;

    if {[PopupDown BugReports] ==1} {return}
    set po [CreateTextDisplay [_ "Bug Reports"] 50 21];
    set ::PopupList(BugReports) $po
    AppendToTextDisplay $po [_ "Report bugs to: billposer@alum.mit.edu.\n"];
    AppendToTextDisplay $po [_ "Please include the following information:\n\n"];
    AppendToTextDisplay $po [_ "\tWhat version of WordGenerator are you using?\n"];
    AppendToTextDisplay $po [format [_ "\t\t(This is version %s.)\n\n"] $Version];
    AppendToTextDisplay $po [_ "\tWhat operating system are you running?.\n"];
    set OS $tcl_platform(os);
    if {$OS == "Linux"} {set OS "GNU/Linux"};
    AppendToTextDisplay $po [format [_ "\t\t(This is %s  %s.)\n\n"] $OS  $tcl_platform(osVersion)];
    AppendToTextDisplay $po [_ "\tWhat window system are you using?.\n"];
    AppendToTextDisplay $po [format [_ "\t\t(This is %s.)\n\n"] $::SystemInfo(WindowSystem)]
    AppendToTextDisplay $po [_ "\tWhat version of tcl/tk are you using?.\n"];
    AppendToTextDisplay $po [format [_ "\t\t(This is version %s.)\n\n"] [info patchlevel]];
    AppendToTextDisplay $po [_ "Bug reports may be sent in any language that I can read without too much trouble or am trying to learn or improve. These include:\n\n"];
    AppendLinkToTextDisplay $po [_ "\tCatalan"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=cat};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tDakelh (Carrier)"] {ShowWebPage http://ydli.org}
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tDutch"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=nld};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tEnglish"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=eng};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tEsperanto"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=epo};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tFrench"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=fra};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tGerman"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=deu};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tItalian"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=ita};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tJapanese"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=jpn};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tKazakh"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=kaz};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tKorean"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=kor};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tLatin"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=lat};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tPortuguese"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=por};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tSpanish"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=spa};
    AppendToTextDisplay $po "\n";
    AppendLinkToTextDisplay $po [_ "\tTurkish"] {ShowWebPage http://www.ethnologue.com/show_language.asp?code=tur};
    AppendToTextDisplay $po "\n\n";
    AppendToTextDisplay $po [_ "Please note that in many cases although I can understand the language my ability to respond in it may be limited.\n"];
    AppendToTextDisplay $po "\n";
    bind [dialog_info $po].text <ButtonRelease-1> {PopupSelectionHandler %W}
}

proc PopupSelectionHandler {w} {
    clipboard append [$w get sel.first sel.last];
}

proc ShowGPL {} {
    if {[PopupDown ShowGPL] ==1} {return}
    set po [CreateTextDisplay [_ "License"] 70 24]
    set ::PopupList(ShowGPL) $po;
    AppendToTextDisplay $po [format "%s%s" [format "%s\n\t%s\n" [_ "For this license in your language see:"] [_ "http://www.gnu.org/copyleft/gpl.html"]]  "\
\

		    GNU GENERAL PUBLIC LICENSE\

   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
\

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The \"Program\", below,
refers to any such program or work, and a \"work based on the Program\"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term \"modification\".)  Each licensee is addressed as \"you\".
\

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
\

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
\

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
\

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
\

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.
\

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.
\

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)
\

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
\

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
\

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
\

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
\

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,
\

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,
\

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)
\

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
\

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
\

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
\

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
\

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
\

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
\

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
\

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
\

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
\

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.
\

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
\

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and \"any
later version\", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
\

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
\

			    NO WARRANTY
\

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
\

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES."]
}

proc HowTo {} {
    if {[PopupDown HowTo] ==1} {return}
    set po [CreateTextDisplay [_ "How to Use this Program"] 72 15]
    set ::PopupList(HowTo) $po;
    AppendToTextDisplay $po [_ "To generate a set of words you define the structure of the words and press the Generate button. This will insert the generated words into the window at the bottom of the display. If you are satisfied with the result, you can save it to a file using the 'Save Words' command on the File menu.\n\n"];
    AppendToTextDisplay $po [_ "To define the structure of the words you wish to generate you must specify the maximum number of syllables and what the syllables consist of. Syllable structure is defined by means of four templates for syllables in different positions in the word together with a set of syllable component definitions. The syllable component definitions define the meaning of the abstract components in terms of which syllable structure is defined. For example, if in your language you define a syllable as consisting of a C (for consonant) followed by a V (for vowel), the syllable component definitions for C and V will specify which consonants are included in C and which vowels are included in V.\n\n"];
    AppendToTextDisplay $po [_ "WordGenerator requires definitions for syllables in four positions in the word. In languages with polysyllabic words, most syllables will be of type Core. The first syllable in a word is of type Initial, the last syllable of type Final. These positions are distinguished from core syllables because in many languages they are special. For example, many languages allow consonant clusters at the beginning of the syllable only in word-initial position, or allow coda consonants only word-finally. The type Sole describes syllables that are the sole syllable of the word. These will typically combine the properties of initial syllables and final syllables.\n\n"];
    AppendToTextDisplay $po [_ "If your language has only monosyllables, you need only define the structure of Sole syllables. If your language has only monosyllables and disyllables, you need only define the structure of Sole, Initial, and Final syllables.\n\n"];
    AppendToTextDisplay $po [_ "Each component of a syllable is represented by a string of one or more letters separated by whitespace. For example, a CV syllable might be defined as 'C V'. Components must be separated by whitespace in order to allow multicharacter names for components. For example, in a language in which the consonants that can appear at the beginning of the syllable and at the end of a syllable are different, you might define a syllable as 'C1 V C2' or 'COnset V CCoda'.\n\n"];
    AppendToTextDisplay $po [_ "WordGenerator does not attempt to impose any particular theory of syllable structure. The examples we have given thus far suggest a theory based on CV slots, but WordGenerator is equally happy with other types of component. If you prefer a traditional Chinese approach in terms of Initials and Finals, you can define syllables as consisting of 'I F', or, similarly, you can define syllables as consisting of an onset and a rhyme, e.g. 'O R'. WordGenerator will combine the components that you specify; it does not care what they are.\n\n"];
    AppendToTextDisplay $po [_ "WordGenerator does not initially provide any place to enter syllable component definitions. You must first define one or more syllable types. WordGenerator will then display entry boxes in which to enter definitions of the components that you have mentioned in your syllable structure definitions.\n\n"];
    AppendToTextDisplay $po [_ "Each syllable component definition consists of a set of strings separated by whitespace. These strings may be single letters but need not be. You can enter characters directly or by specifying Unicode codepoints using the notation \\uXXXX where XXXX is the hexadecimal Unicode codepoint. For example, the velar nasal symbol \u014B may be entered as \\u014B.\n\n"];
    AppendToTextDisplay $po [_ "You can enter characters by typing directly, using whatever input methods your computer has available, by using a character map tool, or by using one of the character entry tools provided on the 'Character Entry' menu. This menu provides IPA consonant and vowel charts, an IPA diacritic chart, a tool containing a large number of letters with various accents and other diacritics, and a tool for entering characters by their Unicode codepoint. It also allows you to define custom character entry widgets by reading a simple definition from a file. Once defined, such widgets are added to the character entry menu.\n\n"];
    AppendToTextDisplay $po [_ "It is not difficult to define word structures that will generate enormous numbers of words. For example, if all syllables are of the form CV, where the consonants are: p t k b d g m n l j w f s x and the vowels are: a e i o u, there are 70 possible syllables. If words can be up to two syllables long, there are 4,970 possible words. If words can be three syllables long, there are 347,970 possible words. If words can be four syllables long, there are 24,357,970 possible words. Such large numbers of words can easily exhaust the memory of your computer, causing it to run very slowly or even to crash. For most uses of this program such large numbers of words are of no use. It is therefore wise to pay attention to the number of words you are going to generate.\n\n"];
    AppendToTextDisplay $po [_ "WordGenerator addresses this problem in three ways. First, the number of words that will be generated is constantly displayed. This number is updated whenever you change a parameter on which it depends. Second, WordGenerator refuses to generate more than a certain number of words, by default 1,000,000. You can change this limit if you wish to by setting the parameter 'Word Limit' in your configuration file or by editing the source code and changing the value of the variable WordLimit, which is defined near the beginning.  Finally, it is possible to abort word generation by pressing the Abort button.\n\n"];
    AppendToTextDisplay $po [_ "For some purposes it is useful to mark the division of words into syllables. To do this, select 'Separate Syllables'. The default marker is a hyphen. You may change it by editing the 'Separator' box.\n\n"];
    AppendToTextDisplay $po [_ "By default, the words generated are listed in the order in which they are generated. Shorter words, measured in syllables, precede longer words. For example, words of one syllable precede words of two syllables and words of two syllables precede words of three syllables.\n\n"]
    AppendToTextDisplay $po [_ "The order of words containing the same number of syllables is determined by the order in which their components are listed in the syllable component entry boxes. For example, the words 'po', 'potak', and 'sat' will appear in the order 'po', 'sat', 'potak' if the individual letters are in the usual order, but in the order 'sat', 'po', 'potak' if 's' is listed before 'p' in the entry for the initial consonants of word-initial syllables.\n\n"];
    AppendToTextDisplay $po [_ "You may instead choose to have the words generated listed in alphabetical order. In this case, words of all lengths (as measured in syllables) will be mixed. For example, the words 'po', 'potak', and 'sat' will appear in that order.\n\n"];
    AppendToTextDisplay $po [_ "You may read configuration information from a file instead of entering it by hand either by using the 'Read Configuration' command on the File menu or by executing WordGenerator with the name of the configuration file as its command line argument. Configuration files should contain one line for each paramater. Each line consists of two parts: the name of the parameter and its value, separated by a tab character. The header written at the beginning of output files is in the same format as a configuration file. Configuration file lines beginning with a crosshatch are taken to be comments and are ignored.\n\n"];
    AppendToTextDisplay $po [_ "Additional information about many aspects of the program may be had by right-clicking over the relevant region. For example, right-clicking over the 'Attach Headers?' option will pop up an explanation of what this option controls.\n\n"];
    AppendToTextDisplay $po [_ "Right-clicking in a scrollbar trough will generally scroll by a larger increment than left-clicking."]
    bind [dialog_info $po].text <ButtonRelease-1> {PopupSelectionHandler %W}

}

proc MacintoshNotes {} {
    global PopupList;
    if {[PopupDown Macintosh] ==1} {return}
    set po [CreateTextDisplay [_ "Notes for Macintosh Users"] 60 12]
    set PopupList(Macintosh) $po;
    AppendToTextDisplay $po [_ "This program works a bit differently on the Macintosh than on other platforms. One difference is in mouse usage. Several features of the program make use of right-clicks. Since the typical Macintosh uses a single-button mouse, if the program detects that it is running on a Macintosh, it interprets the combination Control-mouse click as equivalent to a right-click.\n\n"]
    AppendToTextDisplay $po [_ "The graphics/windowing system that WordGenerator uses is Tk, for which two implementations are available under MacOSX. One implementation uses X11 as the underlying graphics and window system. This version of Tk is almost identical to the Unix version for which WordGenerator was written. If you use this version of Tk, you should find that WordGenerator looks and behaves just as described in the documentation, which is based in the first instance on the GNU/Linux version of WordGenerator.\n\n"]
    AppendToTextDisplay $po [_ "The other implementation of Tk for MacOSX uses Aqua as the underlying graphics and window system. This version of Tk provides a more native Macintosh look and feel, at the expense of introducing differences between the look and behavior of programs under MacOSX and on other machines. In particular, the Macintosh user interface guidelines, which the Aqua implementation of Tk enforces, allow only menus, not commands, to be placed on the menu bar. As a result, if you are using Tk-Aqua, the File, Search, Character Entry, and Help menus will appear on the Macintosh menu, while the Generate, Abort, and Choose Font buttons will appear in a row at the top of the WordGenerator window.\n\n"]
    if {$::SystemInfo(AquaP)} {
	AppendToTextDisplay $po [_ "You are currently using the Aqua implementation of Tk. If you would prefer to have the standard interface for WordGenerator, you will need to install the X11 implementation of Tk as well as X11 itself. As of the Tiger release of Mac OS X, X11 is provided on the distribution CD as an optional componenent."]
    } else {
	AppendToTextDisplay $po [_ "You are currently using the X11 implementation of Tk. This provides the standard interface to WordGenerator. If you prefer a more native Macintosh look-and-feel you can switch to the Aqua implementation of Tk."]
    }
    bind [dialog_info $po].text <ButtonRelease-1> {PopupSelectionHandler %W}
}

proc CountChars {t} {
    return  [llength [split $t ""]];
}

namespace eval search {
    variable SearchOffset;
    variable SrchWCnt 0;
    variable GotoCharWCnt 0;
    namespace export GetSearchTarget

    proc SearchInitialize {w} {
	variable SearchOffset;
	set SearchOffset($w,f) 1.0;
	set SearchOffset($w,b) 1.0;
    }

    proc SearchResults {} {
	if {[string length [$::TXT.txt get 1.0 end]] <= 1 } {
	    ShowMessage [_ "There are no words to search."]
	    return
	}
	PopupSearch $::TXT.txt  [_ "Search Results"];
    }

    proc GotoCharResults {} {
	if {[string length [$::TXT.txt get 1.0 end]] <= 1 } {
	    ShowMessage [_ "There are no words to search."]
	    return
	}
	PopupGotoChar $::TXT.txt [_ "Go to Line/Character in Results"];
    }

    proc SearchText {w re forwardp cw} {
	variable SearchOffset;
	if {$forwardp} {
	    set df "-forward";
	    set Offset $SearchOffset($w,f);
	} else {
	    set df "-backward";
	    set Offset $SearchOffset($w,b);
	}
	set CurrentLine [lindex [split $Offset "."] 0]
	set CurrentChar [lindex [split $Offset "."] 1]
	set ind [$w search $df -regexp -count MatchLen $re $CurrentLine.$CurrentChar]
	if {$ind == ""} {
	    ShowMessage [_ "No match found"];
	    return 
	} else {
	    $w tag delete match;
	    $w tag configure "match" -background $::ColorSpecs(Match,Background);
	    $w tag add match $ind "$ind + $MatchLen chars"
	    $w see $ind
	    set MatchBegin [$w index match.first]
	    set MatchEnd   [$w index match.last-1chars];
	    set MatchBeginChar [CountChars [$w get 1.0 $MatchBegin-1chars]];
	    set MatchEndChar [expr $MatchBeginChar + [CountChars [$w get $MatchBegin $MatchEnd]]];
	    set msg [format [_ "Matched \[%s-%s\]  \[%s-%s\]"] $MatchBegin $MatchEnd $MatchBeginChar $MatchEndChar];
	    ShowMessage $msg
	    $cw.ri configure -text $msg;
	    set SearchOffset($w,f) "$ind + $MatchLen chars + 1 chars"
	    set SearchOffset($w,b) "$ind -1 chars"
	    return
	}
    }


    proc PopupSearch {w title} {
	global MainFont;
	variable SrchWCnt;
	incr SrchWCnt;

	set pn [format ".srch%d" $SrchWCnt];
	if {[winfo exists $pn]} {
	    raise $pn
	    return
	}
	toplevel $pn -borderwidth 4 -relief raised \
	    -bg $::ColorSpecs(PopupWidgetDefault,Background);
	wm title $pn $title;
	label $pn.ri  -bg $::ColorSpecs(PopupWidgetDefault,Background);
	entry $pn.ent -width 40 -font MainFont   \
	    -bg $::ColorSpecs(PopupWidgetDefault,Background);
	button $pn.f  -text [_ "Search Forward"] \
	    -command "search::SearchForEntry $w 1 $pn"  \
	    -bg $::ColorSpecs(PopupWidgetButton,Background);
	button $pn.b  -text [_ "Search Backward"] \
	    -command "search::SearchForEntry $w 0 $pn" \
	    -bg $::ColorSpecs(PopupWidgetButton,Background);
	button $pn.d  -text [_ "Dismiss"] \
	    -command "destroy $pn" \
	    -bg $::ColorSpecs(PopupWidgetButton,Background);
	pack $pn.ri -side top
	pack $pn.ent -side top
	pack $pn.f -side right
	pack $pn.d -side left
	pack $pn.b -side right
	raise $pn;
	set AfterIdleCmd [format {update idletasks
	    set TextLeftEdge [winfo rootx %s];
	    dmsg "TextLeftEdge=$TextLeftEdge"
	    set TextRightEdge [expr $TextLeftEdge +  [winfo width %s]];
	    dmsg "TextRightEdge=$TextRightEdge"
	    set PopupWidth [winfo width %s];
	    dmsg "PopupWidth=$PopupWidth"
	    set x [expr int($TextRightEdge - ((2.0/3.0) * $PopupWidth))]
	    dmsg "x=$x"
	    set TextTopEdge  [winfo rooty %s];
	    dmsg "TextTopEdge=$TextTopEdge"
	    set TextMidHeight [expr $TextTopEdge + ([winfo height %s]/2.0)]
	    dmsg "TextMidHeight=$TextMidHeight"
	    set PopupHeight [winfo height %s];
	    dmsg "PopupHeight=$PopupHeight"
	    set y [expr int ($TextMidHeight -((2.0/3.0) * $PopupHeight))]
	    dmsg "y=$y"
	    wm geometry %s "+$x+$y"
	} $w $w $pn $w $pn $w $pn];
	after idle $AfterIdleCmd;
	BindKeys $pn;
	bind $pn <FocusIn> "SetInsertionTargets $pn.ent"
	bind $pn.ent <FocusIn> "SetInsertionTargets $pn.ent"
	bind $pn.ent <Return> "search::SearchForEntry $w 1 $pn"
    }

    proc SearchForEntry {w d pn} {
	set tgt [$pn.ent get];
	SearchText $w $tgt $d $pn;
    }

    proc PopupGotoChar {w title} {
	global MainFont;
	variable GotoCharWCnt;
	incr GotoCharWCnt;

	set pn [format ".gotochar%s" [regsub -all "\\." $w "-"]]
	if {[winfo exists $pn]} {
	    raise $pn
	    return
	}
	toplevel $pn -borderwidth 4 -relief raised \
	    -bg $::ColorSpecs(PopupWidgetDefault,Background);
	wm title $pn "";
	label $pn.title -text $title \
	    -bg $::ColorSpecs(PopupWidgetDefault,Background);
	entry $pn.ent -width 40 -font MainFont \
    	    -bg $::ColorSpecs(PopupWidgetDefault,Background);
#	bind $pn <FocusIn> "SetInsertionTargets $pn.ent"
	button $pn.f  -text [_ "Go"] \
	    -command "search::GotoCharInText $w $pn" \
	    -bg $::ColorSpecs(PopupWidgetButton,Background);
	button $pn.d  -text [_ "Dismiss"] \
	    -command "destroy $pn" \
	    -bg $::ColorSpecs(PopupWidgetButton,Background);
	pack $pn.title -side top
	pack $pn.ent -side top
	pack $pn.f -side right
	pack $pn.d -side left
	raise $pn;
	set AfterIdleCmd [format {update idletasks
	    set TextLeftEdge [winfo rootx %s];
	    dmsg "TextLeftEdge=$TextLeftEdge"
	    set TextRightEdge [expr $TextLeftEdge +  [winfo width %s]];
	    dmsg "TextRightEdge=$TextRightEdge"
	    set PopupWidth [winfo width %s];
	    dmsg "PopupWidth=$PopupWidth"
	    set x [expr int($TextRightEdge - ((2.0/3.0) * $PopupWidth))]
	    dmsg "x=$x"
	    set TextTopEdge  [winfo rooty %s];
	    dmsg "TextTopEdge=$TextTopEdge"
	    set TextMidHeight [expr $TextTopEdge + ([winfo height %s]/2.0)]
	    dmsg "TextMidHeight=$TextMidHeight"
	    set PopupHeight [winfo height %s];
	    dmsg "PopupHeight=$PopupHeight"
	    set y [expr int ($TextMidHeight -((2.0/3.0) * $PopupHeight))]
	    dmsg "y=$y"
	    wm geometry %s "+$x+$y"
	} $w $w $pn $w $pn $w $pn];
	after idle $AfterIdleCmd;
	BindKeys $pn;
	bind $pn.ent <FocusIn> "SetInsertionTargets $pn.ent"
	bind $pn.ent <Return>  "search::GotoCharInText $w $pn"
    }

    #tw = text window in which to locate character
    #cw = calling window - the name of the popup from which to get the character position.
    proc GotoCharInText {tw cw} {
	#Handle both tcl line.char format indices and pure character offsets.
	set CharPos [$cw.ent get]
	$tw tag delete goto;
	$tw tag configure "goto" -background $::ColorSpecs(Match,Background);
	if {[scan $CharPos %d.%d line char] >= 2} {
	    $tw tag add goto $CharPos "$CharPos + 1 chars"
	    $tw see $CharPos;
	} else {
	    $tw tag add goto 1.0+${CharPos}chars 1.1+${CharPos}chars
	    $tw see 1.0+${CharPos}chars
	}
    }
}

proc WriteConfiguration {fh} {
    puts $fh [format "Maximum Syllables\t%d" $::Options(MaximumSyllables)]
    puts $fh [format "Core\t%s"  $::SyllableStructures(Core)]
    puts $fh [format "Sole\t%s" $::SyllableStructures(Sole)]
    puts $fh [format "Initial\t%s" $::SyllableStructures(Initial)]
    puts $fh [format "Final\t%s" $::SyllableStructures(Final)]

    foreach c $::SegmentSetNames {
	puts -nonewline $fh [format "%s\t" $c]
	foreach s $::PartLists($c) {
	    if {[string equal $s ""]} {
		puts -nonewline $fh "\"\" ";
	    } else {
		puts -nonewline $fh [format "%s " $s];
	    }
	}
	puts $fh "";#Tack on newline 
    }
    puts $fh [format "AlphabetizeP\t%d" $::Options(AlphabetizeP)]
    puts $fh [format "AttachHeaderP\t%d" $::Options(AttachHeaderP)]
    puts $fh [format "RandomP\t%d" $::Options(RandomP)]
    if {$::Options(RandomP)} {
	puts $fh [format "Sample Size\t%d" $::Options(SampleSize)]
    }
    puts $fh [format "SeparateSyllablesP\t%d" $::Options(SeparateSyllablesP)]
    if {$::Options(SeparateSyllablesP)} {
	puts $fh [format "Syllable Separator\t%s" $::Options(SyllableSeparator)]
    }
    puts $fh [format "Main Font Family\t%s" $::FontInfo(MainFont,family)]
    puts $fh [format "Main Font Size\t%s" $::FontInfo(MainFont,size)]
    puts $fh [format "Text Font Family\t%s" $::FontInfo(TextFont,family)]
    puts $fh [format "Text Font Size\t%s" $::FontInfo(TextFont,size)]
    puts $fh [format "Word Limit\t%s" $::WordLimit]
}

#Check whether the argument represents a Boolean value
#and interpret it if it is.
proc Boolean {s} {
     switch -regexp $s {
	 1	{return 1}
	 T.*	{return 1}
	 t.*	{return 1}
	 Y.*	{return 1}
	 y.*	{return 1}
	 ok	{return 1}
	 on	{return 1}
	 0 	{return 0}
	 F.*	{return 0}
	 f.*	{return 0}
	 N.*	{return 0}
	 n.*	{return 0}
	 off	{return 0}
	 default {error}
     }
}

proc LoadConfiguration {{fn ""}} {
    set CommentCharacter \#;
    if {$fn == ""} {
	set ConfigFile [tk_getOpenFile -title [_ "Read Configuration"]];
    } else {
	set ConfigFile $fn;
    }
    if {$ConfigFile != ""} {
	if { [catch {open $ConfigFile "r"} ConfigHandle ] != 0} {
	    ShowMessage [format [_ "Unable to open file %s."] $ConfigFile];
	    return ;
	}
	while { [gets $ConfigHandle line] > 0} {
	    if {[string index $line 0] == $CommentCharacter} {
		continue
	    }
	    set lp [split $line "\t"];
	    switch -exact [lindex $lp 0] {
		Core {
		    set ::SyllableStructures(Core) [lindex $lp 1];
		    $::COREENT delete 0 end
		    $::COREENT insert insert $::SyllableStructures(Core);
		}
		Sole {
		    set ::SyllableStructures(Sole) [lindex $lp 1];
		    $::SOLEENT delete 0 end
		    $::SOLEENT insert insert $::SyllableStructures(Sole);
		}
		Initial {
		    set ::SyllableStructures(Initial) [lindex $lp 1];
		    $::INITIALENT delete 0 end
		    $::INITIALENT insert insert $::SyllableStructures(Initial);
		}
		Final {
		    set ::SyllableStructures(Final) [lindex $lp 1];
		    $::FINALENT delete 0 end
		    $::FINALENT insert insert $::SyllableStructures(Final);
		}
		"Maximum Syllables" {
		    set ::Options(MaximumSyllables) [lindex $lp 1];
		    $::MAXSYL.ent delete 0 end
		    $::MAXSYL.ent insert insert $::Options(MaximumSyllables);
		}
		AlphabetizeP {
		    set ::Options(AlphabetizeP) [Boolean [lindex $lp 1]]
		}
		AttachHeaderP {
		    set ::Options(AttachHeaderP) [Boolean [lindex $lp 1]]
		}
		RandomP {
		    set ::Options(RandomP) [Boolean [lindex $lp 1]]
		}
		"Sample Size" {
		    set ::Options(SampleSize) [lindex $lp 1];
		    $::RAND.ent delete 0 end
		    $::RAND.ent insert insert $::Options(SampleSize)
		}
		SeparateSyllablesP {
		    set ::Options(SeparateSyllablesP) [Boolean [lindex $lp 1]]
		}
		"Syllable Separator" {
		    set ::Options(SyllableSeparator) [lindex $lp 1];
		    set ::Options(SeparateSyllablesP) 1;
		}
		"Text Font Family" {
		    set ::FontInfo(TextFont,family) [lindex $lp 1]
		}
		"Main Font Family" {
		    set ::FontInfo(MainFont,family) [lindex $lp 1]
		}
		"Text Font Size" {
		    set ::FontInfo(TextFont,size) [lindex $lp 1]
		}
		"Main Font Size" {
		    set ::FontInfo(MainFont,size) [lindex $lp 1]
		}
		"Word Limit" {
		    set val [lindex $lp 1]
		    if {[string is integer $val] && $val > 0} {
			set ::WordLimit $val
		    } else {
			ShowMessage [_ "Invalid word limit in configuration file."]
			exit 1;
		    }
		}
		default {
		    set SetName [lindex $lp 0]
		    set ::PartLists($SetName) [lindex $lp 1];
		    CreateSegmentSetEntry $SetName;
		}
	    }
	}
	close $ConfigHandle;
	ShowWordCount x y z; 

    } else {
	ShowMessage [_ "File selection cancelled."];
	return ;
}
    ShowMessage [format [_ "Read configuration from %s."] $ConfigFile]
    return ;
}

proc SaveWords {} {
    set Words [$::TXT.txt get 1.0 end];
    if {[llength $Words] < 2} {
	ShowMessage [_ "There is no output to save."];
	return ;
    }
    set OutputSaveFile [tk_getSaveFile -initialfile [_ "WordsGenerated"]];
    if {$OutputSaveFile == ""} {
	ShowMessage [_ "File selection cancelled."]
	return ;
    }
    if {[catch {open $OutputSaveFile "w"} OutputSaveHandle ] != 0} {
	ShowMessage [format [_ "Unable to open file %s to save words generated."] $OutputSaveFile];
	return ;
    }
    if {$::Options(AttachHeaderP)} {
	puts $OutputSaveHandle [format [_ "\#Generated  %s"] [clock format [clock seconds]]]
	WriteConfiguration $OutputSaveHandle;
	puts $OutputSaveHandle "\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#"
    }
    puts $OutputSaveHandle $Words;
    close $OutputSaveHandle;
    set ::TimeStamps(LastSaveTime) [clock seconds]
    ShowMessage [format [_ "Words saved in file %s."] [MinimizeFileName $OutputSaveFile]];
}

proc SaveConfiguration {} {
    set ConfigurationSaveFile [tk_getSaveFile -initialfile [_ "SavedConfiguration"]];
    if {$ConfigurationSaveFile == ""} {
	ShowMessage [_ "File selection cancelled."]
	return ;
    }
    if {[catch {open $ConfigurationSaveFile "w"} ConfigurationSaveHandle ] != 0} {
	ShowMessage [format [_ "Unable to open file %s to save configuration."] \
		 $ConfigurationSaveFile];
	return ;
    }
    puts $ConfigurationSaveHandle [format [_ "\#Generated  %s"] [clock format [clock seconds]]]
    WriteConfiguration $ConfigurationSaveHandle;
    close $ConfigurationSaveHandle;
    ShowMessage [format [_ "Configuration saved in file %s."] [MinimizeFileName $ConfigurationSaveFile]];
}

proc ExplainSyllableStructure {} {
    global PopupList;
    if {[PopupDown SyllableStructure] ==1} {return}
    set po [CreateTextDisplay [_ "Syllable Structure"] 60 8]
    set PopupList(SyllableStructure) $po;
    AppendToTextDisplay $po [_ "Here is where you specify the syllable structure of your language."];
    AppendToTextDisplay $po [_ "Each syllable type is described as a sequence of syllable components, using symbolic names for the components, such as C and V."];
    AppendToTextDisplay $po [_ "You will probably find it convenient to use traditional abbreviations such as C for 'consonant', V for 'vowel' and C2 for 'second consonant', but the choice is entirely up to you."]
    AppendToTextDisplay $po [_ "What these symbolic names represent is specified in the syllable component section."]
}

proc ExplainCore {} {
    global PopupList;
    if {[PopupDown Core] ==1} {return}
    set po [CreateTextDisplay [_ "Core Syllables"] 60 5]
    set PopupList(Core) $po;
    AppendToTextDisplay $po [_ "This is the specification for core syllables, that is, syllables that are not initial, final, or the only syllable in the word. "];
    AppendToTextDisplay $po [_ "Enter a sequence of syllable component names separated by whitespace, such as 'C V'"];
    AppendToTextDisplay $po [_ " You will be asked to define these syllable component names in the 'Syllable Components' section below."];
}

proc ExplainInitial {} {
    global PopupList;
    if {[PopupDown Initial] ==1} {return}
    set po [CreateTextDisplay [_ "Initial Syllables"] 60 5]
    set PopupList(Initial) $po;
    AppendToTextDisplay $po [_ "This is the specification for word-initial syllables."];
    AppendToTextDisplay $po [_ "Enter a sequence of syllable component names separated by whitespace, such as 'IP C V'"];
    AppendToTextDisplay $po [_ " You will be asked to define these syllable component names in the 'Syllable Components' section below."];
}

proc ExplainFinal {} {
    global PopupList;
    if {[PopupDown Final] ==1} {return}
    set po [CreateTextDisplay [_ "Final Syllables"] 60 5]
    set PopupList(Final) $po;
    AppendToTextDisplay $po [_ "This is the specification for word-final syllables."];
    AppendToTextDisplay $po [_ "Enter a sequence of syllable component names separated by whitespace, such as 'C V FC'"];
    AppendToTextDisplay $po [_ " You will be asked to define these syllable component names in the 'Syllable Components' section below."];
}

proc ExplainSole {} {
    global PopupList;
    if {[PopupDown Sole] ==1} {return}
    set po [CreateTextDisplay [_ "Sole Syllables"] 60 5]
    set PopupList(Sole) $po;
    AppendToTextDisplay $po [_ "This is the specification for the sole syllable of monosyllabic words."];
    AppendToTextDisplay $po [_ "Enter a sequence of syllable component names separated by whitespace, such as 'IP C V FC'"];
    AppendToTextDisplay $po [_ " You will be asked to define these syllable component names in the 'Syllable Components' section below."];
}

proc ExplainSyllableComponents {} {
    global PopupList;
    if {[PopupDown SyllableComponents] ==1} {return}
    set po [CreateTextDisplay [_ "Syllable Components"] 60 11]
    set PopupList(SyllableComponents) $po;
    AppendToTextDisplay $po [_ "This is where you list the segments of which each component of a syllable consists. Segments are separated by whitespace, so you can enter strings of more than one character if you like.\n\n"];
    AppendToTextDisplay $po [_ "If you wish to indicate that a component may be null, enter a null string by using two double-quotes in immediate succession, i.e. \"\". If you want such nulls to precede filled positions, which is the usual alphabetical order, when words are listed in the order in which they are generated, you should enter the null first."];
}

proc ExplainAlphabetize {} {
    global PopupList;
    if {[PopupDown Alphabetize] ==1} {return}
    set po [CreateTextDisplay [_ "Alphabetization"] 60 15]
    set PopupList(Alphabetize) $po;
    AppendToTextDisplay $po [_ "By default, the words generated are listed in the order in which they are generated. Shorter words, measured in syllables, precede longer words. For example, words of one syllable precede words of two syllables and words of two syllables precede words of three syllables.\n\n"]
    AppendToTextDisplay $po [_ "The order of words containing the same number of syllables is determined by the order in which their components are listed in the syllable component entry boxes. For example, the words 'po', 'potak', and 'sat' will appear in the order 'po', 'sat', 'potak' if the individual letters are in the usual order, but in the order 'sat', 'po', 'potak' if 's' is listed before 'p' in the entry for the initial consonants of word-initial syllables.\n\n"];
    AppendToTextDisplay $po [_ "You may instead choose to have the words generated listed in alphabetical order. In this case, words of all lengths (as measured in syllables) will be mixed. For example, the words 'po', 'potak', and 'sat' will appear in that order."];
}

proc ExplainAttachHeader {} {
    global PopupList;
    if {[PopupDown AttachHeader] ==1} {return}
    set po [CreateTextDisplay [_ "Attach Header?"] 60 6]
    set PopupList(AttachHeader) $po;
    AppendToTextDisplay $po [_ "By default, when the words generated are saved to a file they are preceded by a header containing a time stamp and the current configuration. If you intend to process the wordlist immediately you may prefer to omit the header."]
}

proc ExplainSeparateSyllables {} {
    global PopupList;
    if {[PopupDown SeparateSyllables] ==1} {return}
    set po [CreateTextDisplay [_ "Separating Syllables"] 60 6]
    set PopupList(SeparateSyllables) $po;
    AppendToTextDisplay $po [_ "By default the syllables of a word are written one after another. If you wish, the decomposition of a word into syllables may be shown by inserting a syllable separator between each pair of adjacent syllables. This separator defaults to a hyphen but you may change it to any string you wish."]
}

proc ExplainMaximumSyllables {} {
    global PopupList;
    if {[PopupDown MaximumSyllables] ==1} {return}
    set po [CreateTextDisplay [_ "Maximum Syllables"] 60 5]
    set PopupList(MaximumSyllables) $po;
    AppendToTextDisplay $po [_ "Words of up to the specified number of syllables will be generated."];
}

proc ExplainRandomSample {} {
    global PopupList;
    if {[PopupDown RandomSample] ==1} {return}
    set po [CreateTextDisplay [_ "Random Sample"] 60 5]
    set PopupList(RandomSample) $po;
    AppendToTextDisplay $po [_ "By default all possible words conforming to the specified syllable structure and containing no more than the specified maximum number of syllables will be generated. If you wish, you may request a random sample of the possible words. The number of words that you specify will be selected randomly from the pool of possible words."]
}

set MAJSEP 40
#This is where the main action begins
puts "WordGenerator $Version";
puts "Copyright (C) 2005 William J. Poser.";
puts [_ "This program is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 2
of the License, or (at your option) any later version."];

DetermineGraphicsSystem
SetupEvents $SystemInfo(System);

set HomeDir [GetHomeDir];
set ScriptPath [info script];
if {$Options(DebugP)} {
    set ManualDirectory [file join [file dir $ScriptPath] Manual];
} else {
    set ManualDirectory [file join $NonBinPath Manual];
}
set ManualPath [file join $ManualDirectory Manual.html];
set ManualURL [PathToFileURL $ManualPath];
#If the default browser is on the list, remove it.
set di [lsearch -exact $BrowserList $DefaultBrowser]
if {$di >= 0} {
    set BrowserList [lreplace $BrowserList $di $di]
}
#Add the default browser to the beginning of the list.
set BrowserList [linsert $BrowserList 0 $DefaultBrowser];
LoadMessageCatalog;

set m [menu .menubar -tearoff 0  -bg $ColorSpecs(Menubar,Background)\
	   -fg $ColorSpecs(Menubar,Foreground) -font MainFont\
	   -activeforeground $ColorSpecs(Menubar,Background)]
$m add cascade -label [_ "File"]   -menu [menu $m.file]
$m.file add command -label [_ "Read Configuration"]  -command LoadConfiguration
$m.file add command -label [_ "Save Configuration"]  -command SaveConfiguration
$m.file add separator 
$m.file add command -label [_ "Save Words Generated"]  -command SaveWords
$m.file add separator 
$m.file add command -label [_ "Quit"]  -command ShutDown
if {!$::SystemInfo(AquaP)} {
    $m add command -label [_ "Generate"]  -command Generate
    $m add command -label [_ "Abort"]  -command Abort
}
$m add cascade -label [_ "Search"] -menu [menu $m.search];
$m.search add command -label  [_ "Search"] -command search::SearchResults;
$m.search add command -label  [_ "Go to Line"] -command search::GotoCharResults;

set CharacterEntryIndex 0;
if {$SystemInfo(AquaP)} {incr CharacterEntryIndex -1}
$m add cascade -label [_ "Character Entry"] -menu [menu $m.charentry];

incr CharacterEntryIndex;
set CustomCharacterChartIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Load Custom Character Chart Definition"] \
    -command ReadCustomCharacterChartPopup;

incr CharacterEntryIndex;
set ToggleIPAAIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Display Accented Letter Chart"] -command ToggleIPAA;

incr CharacterEntryIndex;
set ToggleIPACIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Display IPA Consonant Chart"] -command ToggleIPAC;

incr CharacterEntryIndex;
set ToggleIPADIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Display IPA Diacritic Chart"] -command ToggleIPAD;

incr CharacterEntryIndex;
set ToggleIPAVIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Display IPA Vowel Chart"] -command ToggleIPAV;

incr CharacterEntryIndex;
set ToggleCharEntryByCodeIndex $CharacterEntryIndex;
$m.charentry add command -label [_ "Display Widget for Entering Characters by Unicode Code"] \
    -command ToggleCharEntryByCode;
if {!$::SystemInfo(AquaP)} {
    $m add command -label [_ "Choose Font"]  -command fontsel::CreateFontControlPanel;
}
$m add cascade -label [_ "Help"]  -menu [menu $m.help]
$m.help add command -label [_ "About"] -command About;
$m.help add command -label [_ "Bug Reports"]  -command BugReports
$m.help add command -label [_ "Home Page"] \
    -command {ShowWebPage http://billposer.org/Software/WordGenerator.html}
$m.help add command -label [_ "How to Use this Program"]  -command HowTo
#$m.help add command -label [_ "Illustrated Web Manual"] -command WebManual;
$m.help add command -label [_ "License"] -command ShowGPL;
if {[string equal $SystemInfo(System) MacOSX]} {
    $m.help add command -label [_ "Macintosh Notes"] -command MacintoshNotes;
}
. configure -menu .menubar

#The quasi-menu
if {$SystemInfo(AquaP)} {
    frame .cmnds
    button .cmnds.generate -text [_ "Generate"] -command Generate \
	-bg $::ColorSpecs(Menubar,Background) -fg $::ColorSpecs(Menubar,Foreground)
    button .cmnds.abort -text [_ "Abort"]  -command Abort \
	-bg $::ColorSpecs(Menubar,Background) -fg $::ColorSpecs(Menubar,Foreground)
    button .cmnds.font -text [_ "Choose Font"] -command fontsel::CreateFontControlPanel \
	-bg $::ColorSpecs(Menubar,Background) -fg $::ColorSpecs(Menubar,Foreground)
    pack .cmnds.generate -side left -expand 1 -fill both
    pack .cmnds.abort -side left -expand 1 -fill both
    pack .cmnds.font -side left -expand 1 -fill both
    BindKeys .cmnds
    pack .cmnds -side top                     -fill x  -padx $MAJXPAD -pady $MAJYPAD
}

frame .mf -border 1 -relief flat
text $::MSG -bg $ColorSpecs(Message,Background) -fg $ColorSpecs(Message,Foreground) -height 1\
    -font MainFont -state disabled -width 60
PackMessageRegion;


proc UpdateCore {} {
    set tmp [string trim [.pat.core.ent get]]
    if {![info exists tmp] || [string length $tmp] < 1}  {
	set tmp [list]
    }
    set ::SyllableStructures(Core) [split $tmp]
}

proc UpdateSole {} {
    set ::SyllableStructures(Sole) [split [string trim [.pat.sole.ent get]]]
}

proc UpdateInitial {} {
    set ::SyllableStructures(Initial) [split [string trim [.pat.initial.ent get]]]
}

proc UpdateFinal {} {
    set ::SyllableStructures(Final) [split [string trim [.pat.final.ent get]]]
}

proc UpdateMaximumSyllables {} {
    set n [$::MAXSYL.ent get]
    if {[string length $n]} {
	set ::Options(MaximumSyllables) $n
    }
}

proc UpdateCoreCount {} {
    set ::CoreCountText [format "%-s" [DelimitNumber [CountSyllables ::SyllableStructures(Core)] "," 3]]
}

proc UpdateSoleCount {} {
    set ::SoleCountText [format "%-s" [DelimitNumber [CountSyllables ::SyllableStructures(Sole)] "," 3]]
}

proc UpdateInitialCount {} {
    set ::InitialCountText [format "%-s" [DelimitNumber [CountSyllables ::SyllableStructures(Initial)] "," 3]]
}

proc UpdateFinalCount {} {
    set ::FinalCountText [format "%-s" [DelimitNumber [CountSyllables ::SyllableStructures(Final)] "," 3]]
}

#Syllable patterns
frame $SYLPAT -border 2 -relief ridge
label .pat.lab -text [_ "Syllable Structure"]
pack .pat.lab -expand 0 -fill none -anchor w -side top
bind $SYLPAT <<B3>> ExplainSyllableStructure
bind $SYLPAT.lab <<B3>> ExplainSyllableStructure

frame .pat.core
label .pat.core.lab -text [_ "Core"]
entry .pat.core.ent -width 10
label .pat.core.cnt  -textvariable CoreCountText
pack  .pat.core.cnt  -side bottom -expand 1 -fill both
pack .pat.core.lab -side left -expand 1 -fill both -padx 3
pack .pat.core.ent -side right -expand 1 -fill both
.pat.core.ent insert insert $SyllableStructures(Core)
bind .pat.core.ent <Return> UpdateCore
bind .pat.core.ent <FocusOut>  UpdateCore
bind .pat.core.ent <Leave>  UpdateCore
bind $SYLPAT.core <<B3>> ExplainCore
bind $SYLPAT.core.lab <<B3>> ExplainCore
bind $SYLPAT.core.ent <<B3>> ExplainCore
bind .pat.core.ent <FocusIn> "SetInsertionTargets .pat.core.ent"

frame .pat.sole
label .pat.sole.lab -text [_ "Sole"]
entry .pat.sole.ent -width 10
label .pat.sole.cnt  -textvariable SoleCountText
pack  .pat.sole.cnt  -side bottom -expand 1 -fill both
pack .pat.sole.lab -side left -expand 1 -fill both -padx 3
pack .pat.sole.ent -side right -expand 1 -fill both
.pat.sole.ent insert insert $SyllableStructures(Sole)
bind .pat.sole.ent <Return> UpdateSole
bind .pat.sole.ent <FocusOut> UpdateSole
bind .pat.sole.ent <Leave> UpdateSole
bind $SYLPAT.sole <<B3>> ExplainSole
bind $SYLPAT.sole.lab <<B3>> ExplainSole
bind $SYLPAT.sole.ent <<B3>> ExplainSole
bind .pat.sole.ent <FocusIn> "SetInsertionTargets .pat.sole.ent"

frame .pat.initial
label .pat.initial.lab -text [_ "Initial"]
entry .pat.initial.ent -width 10
label .pat.initial.cnt  -textvariable InitialCountText
pack  .pat.initial.cnt  -side bottom -expand 1 -fill both
pack .pat.initial.lab -side left -expand 1 -fill both -padx 3
pack .pat.initial.ent -side right -expand 1 -fill both
.pat.initial.ent insert insert $SyllableStructures(Initial)
bind .pat.initial.ent <Return> UpdateInitial
bind .pat.initial.ent <FocusOut> UpdateInitial
bind .pat.initial.ent <Leave> UpdateInitial
bind $SYLPAT.initial     <<B3>> ExplainInitial
bind $SYLPAT.initial.lab <<B3>> ExplainInitial
bind $SYLPAT.initial.ent <<B3>> ExplainInitial
bind .pat.initial.ent <FocusIn> "SetInsertionTargets .pat.initial.ent"

frame .pat.final
label .pat.final.lab -text [_ "Final"]
entry .pat.final.ent -width 10
label .pat.final.cnt  -textvariable FinalCountText
pack  .pat.final.cnt  -side bottom -expand 1 -fill both
pack .pat.final.lab -side left -expand 1 -fill both -padx 3
pack .pat.final.ent -side right -expand 1 -fill both
.pat.final.ent insert insert $SyllableStructures(Final)
bind .pat.final.ent <Return> UpdateFinal
bind .pat.final.ent <FocusOut> UpdateFinal
bind .pat.final.ent <Leave> UpdateFinal
bind $SYLPAT.final         <<B3>> ExplainFinal
bind $SYLPAT.final.lab     <<B3>> ExplainFinal
bind $SYLPAT.final.ent     <<B3>> ExplainFinal
bind .pat.final.ent <FocusIn> "SetInsertionTargets .pat.final.ent"

frame .pat.ipadx -width 6
pack .pat.ipadx		-side left -expand 0 -fill y -padx 5 -pady 5
pack .pat.core		-side left -expand 0 -fill y -padx 5 -pady 5
pack .pat.sole		-side left -expand 0 -fill y -padx 5 -pady 5
pack .pat.initial	-side left -expand 0 -fill y -padx 5 -pady 5
pack .pat.final		-side left -expand 0 -fill y -padx 5 -pady 5

frame $SYLCOMP -border 2 -relief ridge
frame $SYLCOMP.topmar -height 10
frame $SYLCOMP.botmar -height 10
label $SYLCOMP.lab -text [_ "Syllable Components"]
pack $SYLCOMP.lab -expand 0 -fill none -anchor w -side top
pack $SYLCOMP.topmar -expand 0 -fill none -side top
pack $SYLCOMP.botmar -expand 0 -fill none -side bottom
bind $SYLCOMP      <<B3>> ExplainSyllableComponents
bind $SYLCOMP.lab  <<B3>> ExplainSyllableComponents

set OldSegmentSetNames [list]
set SegmentSetNames [list]

proc UpdateSegmentSetText {s} {
    set ::PartListsText($s) [format "%3s %2d" $s [llength $::PartLists($s)]]
}

proc UpdatePL {w} {
    set n [string range  [lindex [split $w "."] 2] 2 end]
    set s [$w get]
    set ::PartLists($n) $s;
    UpdateSegmentSetText $n;
    ShowWordCount x y z;
}

set SegmentSetPathPrefix [format "%s.ss" $SYLCOMP]
proc RemoveSegmentSetEntry {s} {
    global SegmentSetPathPrefix
    destroy ${SegmentSetPathPrefix}$s
}

proc CreateSegmentSetEntry {SetName} {
    global SegmentSetPathPrefix
    set w ${SegmentSetPathPrefix}${SetName}
    destroy $w
    frame $w -relief flat -border 2
    frame $w.ipadx -width 6
    label $w.lab -width 7 -font MainFont \
	-textvariable ::PartListsText($SetName)
    entry $w.ent -width 65 -font TextFont
    if {[info exists ::PartLists($SetName)]} {
	$w.ent insert insert $::PartLists($SetName)
    } else {
	$w.ent insert insert \"\"
	set ::PartLists($SetName) "";
    }
    pack $w.ipadx -side left -expand 0 -fill y -padx 5 -pady 2
    pack $w.lab -side left -expand 0 -fill y -anchor w
    pack $w.ent -side left -expand 1 -fill y -anchor w -padx 2
    pack $w -side top -expand 1 -fill both -anchor w -pady 2
    bind $w.ent <Return> {UpdatePL %W}
    bind $w.ent <FocusOut> {UpdatePL %W}
    bind $w.ent <Leave> {UpdatePL %W}
    bind $w.ent <ButtonPress-1> {}
    bind $w   <<B3>> ExplainSyllableComponents
    bind $w.lab   <<B3>> ExplainSyllableComponents
    bind $w.ent   <<B3>> ExplainSyllableComponents
    bind $w.ent <FocusIn> "SetInsertionTargets $w.ent"
    UpdateSegmentSetText $SetName;
}

#This is less efficient than adding only the new ones,
#but it allows us to order them alphabetically.
proc UpdateSegmentSetEntries {e o n} {
    #First remove old ones
    foreach s $::OldSegmentSetNames {
	RemoveSegmentSetEntry $s;
    }
    #Now create new ones
    foreach s $::SegmentSetNames {
	CreateSegmentSetEntry $s;
	if {[info exists ::PartLists($s)] == 0} {
	    set ::PartLists($s) [list]
	}
    }
    ShowWordCount x y z;
}

# return the union of two lists L1 and L2
proc lu { L1 L2 } {
    foreach x $L1 {
	set IAR($x) 1;
    }
    foreach y $L2 {
	set IAR($y) 1;
    }
    return [array names IAR];
}

proc UpdateSegmentSetNames {e o n} {
    set ::OldSegmentSetNames $::SegmentSetNames;
    set ::SegmentSetNames [lsort [lu [lu [lu $::SyllableStructures(Core) $::SyllableStructures(Sole)] $::SyllableStructures(Initial)] $::SyllableStructures(Final)]]
}

proc ValidateMaximumSyllables {s type action} {
    if {[string equal $type focusout] || \
	    ([string equal $type key] && ($action == 1))} {
	if {[string length $s] == 0} {
	    if {[string equal $type focusout]} {focus $::MAXSYL.ent}
	    ShowMessage [_ "You must specify the maximum number of syllables."]
	    return 0;
	}
	if {![string is integer $s]} {
	    if {[string equal $type focusout]} {focus $::MAXSYL.ent}
	    ShowMessage [_ "The maximum number of syllables must be a positive integer."]
	    return 0
	}
	if {$s < 1} {
	    if {[string equal $type focusout]} {focus $::MAXSYL.ent}
	    ShowMessage [_ "You can't have less than one syllable."]
	    return 0;
	}
	if {$s > 6} {
	    ShowMessage [_ "Are you sure you want such long words?"]
	}
    }
    after 3000 {ClearMessageWindow}
    return 1;
}

frame $MAXFR -border 2 -relief ridge
#Maximum number of syllables
frame $MAXSYL
label $MAXSYL.lab -text [_ "Maximum Number of Syllables"]
entry $MAXSYL.ent -width 2 -border 1 -relief sunken -validate all \
    -validatecommand {ValidateMaximumSyllables %P %V %d} 
pack $MAXSYL.lab -side left -expand 0 -fill both -anchor w
pack $MAXSYL.ent -side left -expand 0 -fill none -anchor w -pady 5 -padx 5
$MAXSYL.ent insert insert $Options(MaximumSyllables);
bind $MAXSYL.ent <Return> UpdateMaximumSyllables
bind $MAXSYL.ent <FocusOut>  UpdateMaximumSyllables
bind $MAXSYL.ent <Leave>  UpdateMaximumSyllables
pack $MAXSYL -side left -expand 0 -fill both -anchor w
bind $MAXSYL <<B3>> ExplainMaximumSyllables
bind $MAXSYL.lab <<B3>> ExplainMaximumSyllables
bind $MAXSYL.ent <<B3>> ExplainMaximumSyllables

proc ValidateSampleSize {s type action} {
    if {[string equal $type focusout] || \
	    ([string equal $type key] && $action == 1)} {
	if {[string length $s] == 0} {
	    if {[string equal $type focusout]} {focus $::RAND.ent}
	    ShowMessage [_ "You must specify the sample size."]
	    return 0;
	}
	if {![string is integer $s]} {
	    if {[string equal $type focusout]} {focus $::RAND.ent}
	    ShowMessage [_ "The sample size must be a positive integer."]
	    return 0
	}
	if {$s < 1} {
	    if {[string equal $type focusout]} {focus $::RAND.ent}
	    ShowMessage [_ "You can't have a sample of fewer than one words."]
	    return 0;
	}
    }
    ClearMessageWindow
    return 1;
}

#Random sample

frame $RAND
checkbutton $RAND.randp -text [_ "Random Sample?"] -variable Options(RandomP) -onvalue 1 -offvalue 0
label       $RAND.elab -text [_ "Sample Size"]
entry       $RAND.ent -width 4 -validate all -validatecommand {ValidateSampleSize %P %V %d}
$RAND.ent insert insert $Options(SampleSize)
if {$Options(RandomP) == 0} {
    $RAND.ent configure -state disabled
} else {
    $RAND.ent configure -state normal
}
pack $RAND.randp -expand 0 -fill none -anchor w -side left -padx 5
pack $RAND.elab  -expand 0 -fill none -anchor w -side left -padx 5
pack $RAND.ent   -expand 0 -fill none -anchor w -side left
bind $RAND.ent <Return> UpdateSampleSize
bind $RAND.ent <FocusOut>  UpdateSampleSize
bind $RAND.ent <Leave>  UpdateSampleSize
bind $RAND <<B3>> ExplainRandomSample
bind $RAND.randp <<B3>> ExplainRandomSample
bind $RAND.elab  <<B3>> ExplainRandomSample
bind $RAND.ent  <<B3>> ExplainRandomSample

proc ControlSampleSizeEntryEnabled {e o n} {
    if {$::Options(RandomP)} {
	$::RAND.ent configure -state normal
    } else {
	$::RAND.ent configure -state disabled
    }
}

trace variable Options(RandomP) w ControlSampleSizeEntryEnabled

proc UpdateSampleSize {} {
    set n [$::RAND.ent get]
    if {[string length $n]} {
	set ::Options(SampleSize) $n;
    }
}

frame $RANDSEP -width $MAJSEP
pack $RANDSEP  -side left -expand 0 -fill none -anchor w
pack $RAND  -side left -expand 0 -fill both -anchor w -padx 2 -pady 5

#Word count
frame $WORDCNT -border 2 -relief ridge
label $WORDCNT.lab -text [_ "Words that will be Generated"] -anchor w
label $WORDCNT.cnt -textvariable WordCountText -width 40 -relief flat -border 2 \
    -anchor w
frame $WORDCNT.botsep
pack $WORDCNT.lab -side top -expand 0 -fill none -anchor w -pady 0
pack $WORDCNT.cnt -side bottom -expand 0 -fill none -anchor w
pack $WORDCNT.botsep -side bottom -expand 1 -fill both -anchor w -pady 2

#Options
frame $OPT -border 2 -relief ridge
label $OPT.lab -text [_ "Options"]
frame $OPT.opts
frame $OPT.opts.ipadx -width 2
#pack $OPT.opts.ipadx  -side left -expand 0 -fill y -padx 5 -pady 5
pack $OPT.lab  -expand 0 -fill none -anchor w -side top
pack $OPT.opts -expand 1 -fill both -anchor w -side top -pady 5

#Alphabetization
checkbutton $ALPHA -text [_ "Alphabetize?"] -variable Options(AlphabetizeP) -onvalue 1 -offvalue 0 -anchor w
bind $ALPHA <<B3>> ExplainAlphabetize

#Attach header to saved words?
checkbutton $ATTHDR -text [_ "Attach Header?"] -variable Options(AttachHeaderP) -onvalue 1 -offvalue 0 -anchor w
bind $ATTHDR <<B3>> ExplainAttachHeader

#Separate syllables
frame $SEP -border 2 -relief ridge
checkbutton $SEP.ckb -text [_ "Separate Syllables?"] -variable Options(SeparateSyllablesP) \
    -onvalue 1 -offvalue 0  -anchor w
label $SEP.lab -text [_ "Separator"]
entry $SEP.ent -width 1
pack $SEP.ckb -side left -expand 0 -fill none -anchor w -padx 0
pack $SEP.lab -side left -expand 0 -fill none -anchor w -padx 4
pack $SEP.ent -side left -expand 0 -fill none -anchor w -padx 2
$SEP.ent insert insert $Options(SyllableSeparator)
$SEP.ent configure -state disabled
bind $SEP.ent <Return> UpdateSyllableSeparator
bind $SEP.ent <FocusOut> UpdateSyllableSeparator
bind $SEP <<B3>> ExplainSeparateSyllables
bind $SEP.ckb <<B3>> ExplainSeparateSyllables
bind $SEP.lab <<B3>> ExplainSeparateSyllables
bind $SEP.ent <<B3>> ExplainSeparateSyllables

proc ControlSyllableSeparatorEntryEnabled {e o n} {
    if {$::Options(SeparateSyllablesP)} {
	$::SEP.ent configure -state normal
    } else {
	$::SEP.ent configure -state disabled
    }
}

proc UpdateSyllableSeparator {} {
    set ::Options(SyllableSeparator) [$::SEP.ent get]
}

trace variable Options(SeparateSyllablesP) w ControlSyllableSeparatorEntryEnabled

#frame $OPT.opts.ind -width 2
#pack $OPT.opts.ind -side left -expand 0 -fill none -anchor w
pack $ALPHA -side top -expand 0 -fill both -anchor w -padx 10 -pady 5
pack $ATTHDR -side top -expand 0 -fill both -anchor w -padx 10 -pady 5
pack $SEP   -side top -expand 0 -fill both -anchor w -padx 10 -pady 5
#frame $OPT.opts.sepsep
#pack $OPT.opts.sepsep -side bottom -expand 1 -fill both


#Generated words

frame .wds -border 2 -relief ridge
text  $TXT.txt -height 8 -width 40 \
    -bg $ColorSpecs(OutputText,Background) \
    -fg $ColorSpecs(OutputText,Foreground) \
    -exportselection 1 -font TextFont -wrap word \
    -yscrollcommand {.wds.sb set}
scrollbar .wds.sb -command {.wds.txt yview}  -troughcolor $ColorSpecs(OutputText,Background)\
    -bg $ColorSpecs(OutputText,Background)  -activebackground $ColorSpecs(OutputText,Background);
bind .wds.sb <<B3>> "ScrollbarMoveBigIncrement .wds.sb 0.05 %x %y"
$TXT.txt configure -state disabled
pack $TXT.txt -side left -expand 1 -fill both
pack .wds.sb  -side left -expand 0 -fill y
search::SearchInitialize $::TXT.txt;

frame .bsep -height 3 -bg $ColorSpecs(Frame,Background);

pack $MSGFR    -side top    -expand 0 -fill x     -padx $MAJXPAD -pady $MAJYPAD
pack $SYLPAT   -side top    -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack $SYLCOMP  -side top    -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack $MAXFR    -side top    -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack $TXT      -side bottom -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack .bsep     -side bottom -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack $WORDCNT  -side left   -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD
pack $OPT      -side right  -expand 1 -fill both  -padx $MAJXPAD -pady $MAJYPAD

. configure -bg $ColorSpecs(Frame,Background);
SetTitle $::Version $::TimeStamp

if {[llength $argv]} {
    LoadConfiguration [lindex $argv 0]
}
CreateWordCountsBySyllable x y z
UpdateSegmentSetNames x y z
UpdateSegmentSetEntries x y z
SetCharacterEntryOptions

#Note that it is important that ShowWordCount be called after UpdateSegmentSetNames,
#so traces to the former should be attached before traces to the latter.
trace variable Options(RandomP) w ShowWordCount
trace variable Options(SampleSize) w ShowWordCount
trace variable Options(MaximumSyllables) w ShowWordCount
trace variable SyllableStructures(Core) w ShowWordCount
trace variable SyllableStructures(Sole) w ShowWordCount
trace variable SyllableStructures(Initial) w ShowWordCount
trace variable SyllableStructures(Final) w ShowWordCount
trace variable SyllableStructures(Core) w UpdateSegmentSetNames
trace variable SyllableStructures(Sole) w UpdateSegmentSetNames
trace variable SyllableStructures(Initial) w UpdateSegmentSetNames
trace variable SyllableStructures(Final) w UpdateSegmentSetNames
trace variable SegmentSetNames w UpdateSegmentSetEntries
trace variable Options(MaximumSyllables) w CreateWordCountsBySyllable

