#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#
# Copyright 2007-2011 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
# DNSSEC-Tools:  rollrec-editor
#
#	rollrec-editor provides the capability for easy management of rollrec
#	files in a GUI.  New rollrecs may be created and existing rollrecs may
#	be modified or deleted from rollrec-editor.  Only a subset of rollrec
#	fields may be modified by rollrec-editor.
#
#
#	TODO:
#		- undo command
#
#		- quit editor command works, but the exit(0) call generates
#		  a segmentation fault
#

use strict;

use Net::DNS::SEC::Tools::dnssectools;
use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Tools::rollmgr;
use Net::DNS::SEC::Tools::rolllog;
use Net::DNS::SEC::Tools::rollrec;
use Net::DNS::SEC::Tools::BootStrap;

use Getopt::Long qw(:config no_ignore_case_always);

#
# Version information.
#
my $NAME   = "rollrec-editor";
my $VERS   = "$NAME version: 1.9.2";
my $DTVERS = "DNSSEC-Tools Version: 1.9";

######################################################################
#
# DESTROY THESE DATA!!!
#
#	These data are part of the undo code that was carried over from
#	signset-editor.  When the undo code is rewritten here, these
#	variables should be either deleted or renamed.
#

my %key_lists;
my @ssnames;
my @krnames;

######################################################################
#
# Detect required Perl modules.
#
dnssec_tools_load_mods(
			'Tk'			=> "",
			'Tk::Dialog'		=> "",
			'Tk::DialogBox'		=> "",
			'Tk::FileSelect'	=> "",
			'Tk::Pane'		=> "",
			'Tk::Table'		=> "",
		      );

#######################################################################
#
#			program (non-GUI) data
#

#
# Variables for command options.
#
my %options = ();				# Filled option array.
my @opts =
(
	"ignore-warns",				# Ignore-edit-warnings flag.
	"no-filter",				# No-name-filtering flag.
	"columns=i",				# Columns in button window.

	"Version",				# Display the version number.
	"help",					# Give a usage message and exit.
);

#
# Flags for options.
#
my $givewarnings = 1;				# Give-warnings flag.
my $namefilter	 = 1;				# Use-name-filter flag (FS).

#
# File name variables.
#
my $rrfile  = "dummy";				# Rollrec file we're examining.
my $curnode = "dummy";				# Node of rrfile.

#
# Lists of rollrecs.
#
my %rollrecs = ();				# Rollrec hash.
my @rollrecs = ();				# Rollrec array.
my @rrnames = ();				# List of rollrec names.
my $numrrnames;					# Number of rollrec names.
my $modified	 = 0;				# Modified-rrfile flag.
my %modified	 = ();				# Modified-rollrecs hash.

###########################################################################
#
# Undo data.
#

my @undo_stack;					# Stack for undo commands.

###########################################################################
#
#			Tk GUI data
#

########################################################
#
# Main window data.
#
my $MAINTITLE	= "DNSSEC-Tools Rollrec Editor";

my $title   = "dummy";				# Node for title.
my $modset  = 0;				# Flag for (modified) title.

#
# The main window and help window.
#
my $wm;						# Main window.
my $helpwin;					# Help window.
my $inhelpwind	 = 0;				# Showing help window flag.

#
# The contents of the main window and its frames.
#
my $mbar;					# Menubar frame.
my $rrfl;					# Keyrec frame.
my $body;					# Window body frame.
my $null;					# Empty frame.

########################################################
#
# Menubar and menu data.
#

#
# Menu item widgets.
#
my $fm_open;					# Open keyrec file item.
my $fm_save;					# Save keyrec file item.
my $fm_svas;					# Save-as keyrec file item.
my $fm_quit;					# Quit file item.

my $em_undo;					# Undo edit item.

my $cm_create;					# Create a rollrec.
my $cm_delete;					# Delete selected rollrecs.
my $cm_modify;					# Modify selected rollrecs.
my $cm_merger;					# Merge two rollrec files.
my $cm_rename;					# Rename selected rollrec.
my $cm_verify;					# Verify a rollrec file.

my $vw_selall;					# Select all rollrecs set item.
my $vw_closeall;				# Close rollrec edit windows.
my $vw_raiser;					# Raise rollrec edit windows.

my $tog_ignwarn;				# Ignore-Edit-Warning toggle.
my $tog_namefilt = 1;				# Use-name-filter toggle.

my $hm_help;					# Help item.

#
# Labels for toggles in the Options menu.
#
my $IGNORE_ON	= "Ignore Edit Warnings";
my $IGNORE_OFF	= "Don't Ignore Edit Warnings";
my $FILTER_OFF	= "Don't Filter Name Selection";
my $FILTER_ON	= "Filter Name Selection";

########################################################
#
# Button window data.
#

#
# Column and row data for button windows.
#
my $DEFCOLS = 4;			# Default columns in button table.
my $MAXCOLS = 12;			# Maximum allowable button columns.
my $MINCOLS = 3;			# Minimum allowable button columns.

my $numcols;				# Current number of table cols.
my $numrows;				# Current number of table rows.

my $columncount = $DEFCOLS;		# Column count.

#
# Font data for text in button window.
#
my $fontsize    = 18;
my $font        = "*-*-bold-r-*-*-$fontsize-*-*-*-*-*-*-*";

#
# Colors for selected/unselected button in button window.
#
my $RRTAB_UNSEL	= 'white';
my $RRTAB_SEL	= 'blue';

########################################################
#
# Edit window data.
#

#
# Row and column constants for the edit-window tables.
#
my $COL_FIELD	 = 0;				# Edit table's label column.
my $COL_DATA	 = 1;				# Edit table's data column.

my $ROW_TYPE	 = 0;				# Type row.
my $ROW_ZONENAME = 1;				# Zonename row.
my $ROW_ZONEFILE = 2;				# Zonefile row.
my $ROW_KEYREC	 = 3;				# Keyrec row.
my $ROW_ADMIN	 = 4;				# Administrator's email row.
my $ROW_DIR	 = 5;				# Directory row.
my $ROW_DISPLAY	 = 6;				# Display row.
my $ROW_LOG	 = 7;				# Loglevel col.
my $ROW_MAXTTL	 = 8;				# Maxttl row.
my $ROW_ZSARGS	 = 9;				# Zsargs row.

my $ROW_FIRST	 = $ROW_TYPE;			# Edit table's first row.
my $ROW_LAST	 = $ROW_ZSARGS;			# Edit table's last row.

my $ROW_MSG	 = ($ROW_LAST + 1);		# Message line - after ROW_LAST.

#
# Labels for the field-column entries of the edit-window tables.
#
#	These MUST correspond to the $ROW_ constants!
#
my @ET_LABELS =
(
	"Type",
	"Zonename",
	"Zonefile",
	"Keyrec",
	"Admin Email",
	"Directory",
	"Display Flag",
	"Logging Level",
	"Max-TTL",
	"Zonesigner Arguments",
	"dummy",	# Dummy label, just ensures message row is created.
);

#
# These are the rollrec fields that are editable.
#
my @EDITFIELDS =
		(
			'administrator',
			'directory',
			'display',
			'keyrec',
			'loglevel',
			'maxttl',
			'zonefile',
			'zonename',
			'zsargs'
		);

#
# Constants for message text colors.
#
my $NORMALCOLOR	 = "black";			# Color for normal messages.
my $WARNCOLOR	 = "yellow";			# Color for warning messages.
my $ERRCOLOR	 = "red";			# Color for error messages.

my $rrnametab;					# Rollrec name table widget.

my %editwinds;					# Rollrec edit windows.
my %edittabs = ();				# Edit-area tables.
my %renwinds = ();				# Rollrec rename windows.
my %rentabs  = ();				# Rename something or another.

my %editrbs;			# Type radiobutton values for edit windows.
my %editdfs;			# Display flag radiobutton vals for edit winds.

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

main();
exit(0);

#---------------------------------------------------------------------------
# Routine:	main()
#
sub main
{
	my $argc = @ARGV;

	erraction(ERR_EXIT);

	#
	# Check our options.
	#
	doopts();

	#
	# Get the rollrec filename and the path's node.
	#
	$rrfile = $ARGV[0];
	$curnode = getnode($rrfile);
	settitle($curnode);

	#
	# Ensure this rollrec file actually exists.
	#
	if(! -e $rrfile)
	{
		print STDERR "$rrfile does not exist\n";
		exit(1);
	}

	#
	# Build the main window.
	#
	buildmainwind();

	#
	# Start the whole shebang rollin'.
	#
	MainLoop();
}

#-----------------------------------------------------------------------------
# Routine:	doopts()
#
# Purpose:	This routine gets the options from the command line.
#
sub doopts
{
	my $argc = @ARGV;		# Number of command line arguments.

	usage()   if($argc == 0);

	GetOptions(\%options,@opts) || usage();

	#
	# Show the version number or help info if requested.
	#
	version() if(defined($options{'Version'}));
	usage()   if(defined($options{'help'}));

	#
	# Set some flags based on the command line.
	#
	$givewarnings = 0 if(defined($options{'ignore-warns'}));
	$namefilter   = 0 if(defined($options{'no-filter'}));

	#
	# Check for the column-count option.
	#
	if(defined($options{'columns'}))
	{
		$columncount  = $options{'columns'};
		if(($columncount < $MINCOLS) || ($columncount > $MAXCOLS))
		{
			print STDERR "column count must be between $MINCOLS and $MAXCOLS\n";
			exit(1);
		}
	}

}


#---------------------------------------------------------------------------
# Routine:	buildmainwind()
#
# Purpose:	Create and initialize the main window.
#
sub buildmainwind
{
	my $file;					# File menu.
	my $edit;					# Edit menu.
	my $cmds;					# Commands menu.
	my $view;					# Rollrecs menu.
	my $opts;					# Options menu.
	my $help;					# Help menu.

	my $curfile;					# Current keyrec.
	my $nulline;					# Empty line.

	#
	# Create the main window and set its size.
	#
	$wm = MainWindow->new(-title => $MAINTITLE);

	#
	# Get the keyrec file info.  No error message; it was given below.
	#
	exit(1) if(readrrf($rrfile) == 0);

	#
	# Create the frames we'll need.
	#
	$mbar = $wm->Frame(-relief => 'raised', -borderwidth => 1);
	$rrfl = $wm->Frame(-relief => 'raised', -borderwidth => 1);
	$body = $wm->Frame(-relief => 'raised', -borderwidth => 1);
	$null = $wm->Frame(-relief => 'raised', -borderwidth => 1);

	$mbar->pack(-fill => 'x');
	$rrfl->pack(-fill => 'x');
	$body->pack(-fill => 'x');
	$null->pack(-fill => 'x');

	#
	# Create our menus.
	#
	$file = $mbar->Menubutton(-text => 'File',
				  -tearoff => 0,
				  -underline => 0);
	$edit = $mbar->Menubutton(-text => 'Edit',
				  -tearoff => 0,
				  -underline => 0);
	$cmds = $mbar->Menubutton(-text => 'Commands',
				  -tearoff => 0,
				  -underline => 0);
	$view = $mbar->Menubutton(-text => 'View',
				  -tearoff => 0,
				  -underline => 0);
	$opts = $mbar->Menubutton(-text => 'Options',
				  -tearoff => 0,
				  -underline => 0);
	$help = $mbar->Menubutton(-text => 'Help',
				  -tearoff => 0,
				  -underline => 0);

	##################################################
	#
	# Add the File menu entries.
	#
	$fm_open = $file->command(-label => 'Open...',
			          -command => \&file_open,
			          -accelerator => 'Ctrl+O',
			          -underline => 0);
	$fm_save = $file->command(-label => 'Save',
			          -command => \&file_save,
			          -accelerator => 'Ctrl+S',
			          -underline => 0);
	$fm_svas = $file->command(-label => 'Save As...',
			          -command => \&file_saveas,
			          -underline => 0);
	$file->separator();
	$fm_quit = $file->command(-label => 'Quit',
			          -command => \&file_quit,
			          -accelerator => 'Ctrl+Q',
			          -underline => 0);
	$file->pack(-side => 'left');

	$wm->bind('<Control-Key-o>',\&file_open);
	$wm->bind('<Control-Key-s>',\&file_save);
	$wm->bind('<Control-Key-q>',\&file_quit);

	##################################################
	#
	# Add the Edit menu entries.
	#
	$em_undo = $edit->command(-label	=> 'Undo Changes',
				  -command	=> \&edit_undo,
				  -accelerator	=> 'Ctrl+U',
				  -state	=> 'disabled',
				  -underline	=> 0);
	$edit->pack(-side => 'left');

	$wm->bind('<Control-Key-u>',\&edit_undo);

	##################################################
	#
	# Add the Commands menu entries.
	#
	$cm_create = $cmds->command(-label	 => 'New Rollrec...',
				    -command	 => \&cmds_create,
				    -accelerator => 'Ctrl+N',
				    -underline	 => 0);

	$cm_delete = $cmds->command(-label	 => 'Delete Selected Rollrecs',
				    -command	 => \&cmds_delete,
				    -accelerator => 'Ctrl+D',
				    -underline	 => 0);

	$cm_modify = $cmds->command(-label	 => 'Edit Selected Rollrecs...',
				    -command	 => \&cmds_modify,
				    -accelerator => 'Ctrl+E',
				    -underline	 => 0);
	$cmds->separator();

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

	$cm_rename = $cmds->command(-label	 => 'Rename Selected Rollrec...',
				    -command	 => \&cmds_rename,
				    -underline	 => 0);

	$cmds->separator();

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

	$cm_merger = $cmds->command(-label	 => 'Merge Rollrec Files...',
				    -command	 => \&cmds_merger,
				    -accelerator => 'Ctrl+M',
				    -underline	 => 0);

	$cm_verify = $cmds->command(-label	 => 'Verify Rollrec File',
				    -command	 => [\&cmds_verify, 0],
				    -accelerator => 'Ctrl+V',
				    -underline	 => 0);

	$cm_verify = $cmds->command(-label	 => 'Summarize Problems',
				    -command	 => [\&cmds_verify, 1],
				    -underline	 => 0);

	$cmds->pack(-side => 'left');

	$wm->bind('<Control-Key-n>',\&cmds_create);
	$wm->bind('<Control-Key-d>',\&cmds_delete);
	$wm->bind('<Control-Key-e>',\&cmds_modify);
	$wm->bind('<Control-Key-m>',\&cmds_merger);
	$wm->bind('<Control-Key-v>',\&cmds_verify);

	##################################################
	#
	# Add the Rollrecs menu entries.
	#
	$vw_selall = $view->command(-label => 'Select All Rollrecs',
			            -command => \&view_selall,
			            -accelerator => 'Ctrl+A',
			            -underline => 0);
	$vw_raiser = $view->command(-label => 'Reveal Rollrec Edit Windows',
			            -command => \&view_raiser,
			            -accelerator => 'Ctrl+R',
			            -underline => 0);
	$vw_closeall = $view->command(-label =>'Close Rollrec Edit Windows',
			              -command => \&view_closeall,
			              -accelerator => 'Ctrl+K',
			              -underline => 0);

	$view->pack(-side => 'left');

	$wm->bind('<Control-Key-a>',\&view_selall);
	$wm->bind('<Control-Key-k>',\&view_closeall);
	$wm->bind('<Control-Key-r>',\&view_raiser);

	##################################################
	#
	# Add the Options menu entries.
	#
	$tog_ignwarn = $opts->command(-label => $IGNORE_ON,
			              -command => \&toggle_ignwarn,
			              -underline => 0);
	$opts->pack(-side => 'left');

	$namefilter = 1;
	$tog_namefilt = $opts->command(-label => $FILTER_OFF,
					-command => \&toggle_namefilt,
					-underline => 0);
	$opts->pack(-side => 'left');

	$opts->command(-label => 'Columns in Button Window',
		       -command => [\&set_btncols, 0],
		       -underline => 0);
	$opts->pack(-side => 'left');


	#
	# Set the menu labels based on default values and options.
	#
	$givewarnings = !$givewarnings;
	toggle_ignwarn();

	##################################################
	#
	# Add the Help menu entries.
	#
	$hm_help = $help->command(-label => 'Help',
			          -command => \&help_help,
			          -accelerator => 'Ctrl+H',
			          -underline => 0);
	$help->pack(-side => 'right');

	$wm->bind('<Control-Key-h>',\&help_help);

	##################################################
	#
	# Create a line holding the current rollrec filename.
	#

	$curfile = $rrfl->Label(-text => "Editing Rollrec File:  ");
	$curfile->pack(-side => 'left');
	$curfile = $rrfl->Label(-textvariable => \$title);
	$curfile->pack(-side => 'left');
	$rrfl->pack(-side => 'top', -fill => 'x');

	##################################################
	#
	# Create a table to hold the button window. 
	#
	buildtable(42);

	#
	# Create a line holding the current rollrec filename.
	#
	$nulline = $null->Label(-text => " ");
	$nulline->pack();
	$null->pack(-side => 'top', -fill => 'x');

}

#---------------------------------------------------------------------------
# Routine:	buildtable()
#
# Purpose:	Rebuild the rollrec name table.  This also re-reads the
#		current rollrec file, so the list of rollrec names may
#		increase or shrink depending on the state of that file.
#
sub buildtable
{
	my $readflag = shift;			# Rollrec-read flag.

	my $cnt = 0;				# Count of rollrec names added.

	#
	# Create a brand new table.
	#
	maketable();

	#
	# Read the list of rollrec names.
	#
	readrrf($rrfile,1) if($readflag);
	return if($numrrnames == 0);

	#
	# Ensure the rollrec name list is sorted.
	#
	@rrnames = sort(@rrnames);

	#
	# Re-populate and update the table.
	#
	for(my $ind = 0; $ind < $numrrnames; $ind++)
	{
		my $btn;				# Button widget.

		my $row;				# Cell's row index.
		my $col;				# Cell's column index.

		#
		# Get the column and row indices.
		#
		($col,$row) = ind2cr($ind);

		$btn = $rrnametab->Button(-text => "$rrnames[$ind]",
					  -font => $font,
					  -anchor => 'w',
					  -state  => 'normal',
					  -command => [\&rrname_toggle,
						       $ind],
					  -background => $RRTAB_UNSEL);

		$rrnametab->put($row,$col,$btn);
	}

	$rrnametab->update();

	#
	# Pack it all up.
	#
	$rrnametab->pack(-fill => 'both', -expand => 1);
	$body->pack(-fill => 'both', -expand => 1);
}

#---------------------------------------------------------------------------
# Routine:      rrname_select()
#
# Purpose:      A rollrec name's button has been pushed.  Figure out what
#		to do with it...
#
sub rrname_select
{
	my $rrind = shift;			# Rollrec's name index.

	my $btn;				# Selected button.

	#
	# Get the rollrec's button.
	#
	$btn = getbutton($rrind);

	#
	# ... and set the button's color.
	#
	$btn->configure(-activebackground => $RRTAB_SEL, -background => $RRTAB_SEL);
}

#---------------------------------------------------------------------------
# Routine:      rrname_deselect()
#
# Purpose:      A rollrec name's button has been pushed.  Figure out what
#		to do with it...
#
sub rrname_deselect
{
	my $rrind = shift;			# Rollrec's name index.

	my $btn;				# Selected button.

	#
	# Get the rollrec's button.
	#
	$btn = getbutton($rrind);

	#
	# ... and set the button's color.
	#
	$btn->configure(-activebackground => $RRTAB_UNSEL, -background => $RRTAB_UNSEL);
}

#---------------------------------------------------------------------------
# Routine:      rrname_toggle()
#
# Purpose:      A rollrec name's button has been pushed.  Figure out what
#		to do with it...
#
sub rrname_toggle
{
	my $rrind = shift;			# Rollrec's name index.

	my $rowind;				# Cell's row index.
	my $colind;				# Cell's column index.

	my $btn;				# Selected button.
	my $bgclr;				# New button background color.

	#
	# Get the rollrec's button.
	#
	$btn = getbutton($rrind);

	#
	# Choose an appropriate background color given the button's
	# current state...
	#
	$bgclr = $btn->cget(-background);
	$bgclr = ($bgclr eq $RRTAB_SEL) ? $RRTAB_UNSEL : $RRTAB_SEL;

	#
	# ... and set the button's color.
	#
	$btn->configure(-activebackground => $bgclr, -background => $bgclr);
}

#---------------------------------------------------------------------------
# Routine:      getbutton()
#
# Purpose:      Return a rollrec's button widget given the name's index into
#		@rrnames.
#
sub getbutton
{
	my $rrind = shift;			# Rollrec's name index.

	my $rowind;				# Cell's row index.
	my $colind;				# Cell's column index.

	#
	# Get the column and row indices.
	#
	($colind,$rowind) = ind2cr($rrind);

	#
	# Return the proper button widget.
	#
	return($rrnametab->get($rowind,$colind));
}

#---------------------------------------------------------------------------
# Routine:      selected_rrnames()
#
# Purpose:      Return a list of the selected rollrec names buttons.
#
sub selected_rrnames
{
	my @selected = ();			# Selected rollrec names.

	my $col;				# Column index.
	my $row;				# Row index.
	my $btn;				# Rollrec button.

	for(my $ind=0; $ind < $numrrnames; $ind++)
	{
		($col,$row) = ind2cr($ind);
		$btn = $rrnametab->get($row,$col);

		push @selected, $ind  if($btn->cget(-background) eq $RRTAB_SEL);
	}

	return(sort(@selected));
}

#---------------------------------------------------------------------------
# Routine:      ind2cr()
#
# Purpose:      Convert a rollrec name table index to its table rows and
#		column indices.
#
sub ind2cr
{
	my $nind = shift;				# Rollrec name index.
	my $col;					# Column index.
	my $row;					# Row index.

	$col = int($nind / $numrows);
	$row = $nind % $numrows;

	return($col,$row);
}

#---------------------------------------------------------------------------
# Routine:      maketable()
#
# Purpose:      Create the rollrec name table.
#
sub maketable
{
	#
	# Don't do anything if we don't have any rollrec names.
	#
	return if($numrrnames == 0);

	#
	# Calculate the size of the button table.
	#
	if($numrrnames != 0)
	{
		$numrows = int($numrrnames / $columncount);
		$numrows++ if(($numrrnames % $columncount) != 0);

		$numcols = $columncount;
	}

	#
	# Destroy the rollrec-name table's widgets.
	#
	if($rrnametab)
	{
		$rrnametab->clear;
		$rrnametab->destroy;
	}

	#
	# Create the new button table.
	#
	$rrnametab = $body->Table(-rows		=> $numrows,
				  -columns	=> $numcols,
				  -scrollbars	=> 'e',
				  -relief	=> 'raised',
				  -borderwidth	=> 1,
				  -fixedrows	=> 0,
				  -takefocus	=> 1,
			         );

}

##############################################################################
#
# Menu widget interface routines.
#
##############################################################################

#---------------------------------------------------------------------------
# Routine:	file_open()
#
sub file_open
{
	my $fowin;					# File-open widget.
	my %fsopts = ();				# FileSelect() options.
	my $newfile;					# New file name.

	#
	# Warn the user if this file has been modified.  We'll also give
	# the user a chance to save the file, not save the file, or cancel
	# the process of opening the new file.
	#
	if($modified)
	{
		my $dlg;				# Warning dialog widget.
		my $ret;				# Warning response.

		$dlg = $wm->Dialog(-title => 'Warning',
				   -text  => "$curnode has been modified; save before proceeding?",
				   -buttons => ["Save","Don't Save","Cancel"]);
		$ret = $dlg->Show();

		return if($ret eq "Cancel");

		rollrec_close() if($ret eq "Save");
	}

	#
	# Set up options for FileSelect.
	#
	%fsopts = (-directory => '.');
	$fsopts{'-filter'} = '*.rrf' if($namefilter);

	#
	# Prompt for the new file.  Return to our caller if nothing was chosen.
	#
	$fowin = $wm->FileSelect(%fsopts);
	$newfile = $fowin->Show;
	return if($newfile eq "");

	#
	# Save the new filename and its node.
	#
	$rrfile = $newfile;
	$curnode = getnode($rrfile);
	settitle($curnode);

	#
	# Read the new rollrec file and rebuild the button window.
	#
	if(readrrf($rrfile))
	{
		buildtable(0);
	}
}

#---------------------------------------------------------------------------
# Routine:	file_save()
#
sub file_save
{
	return if(!$modified);

	#
	# Save and re-open the rollrec file.
	#
	rollrec_close();
	readrrf($rrfile);

	#
	# Mark the rollrecs as saved.
	#
	$modified = 0;
	$modset = 0;
	rrsaved();

	#
	# Disable the undo menu option, reset our undo stack, and mark
	# the file as unmodified.
	#
	$em_undo->configure(-state => 'disabled');
	@undo_stack = ();
}

#---------------------------------------------------------------------------
# Routine:	file_saveas()
#
sub file_saveas
{
	my $fowin;					# File-open widget.
	my $newfile;					# New file's name.
	my %fsopts;					# FileSelect() options.

	#
	# Set up options for FileSelect.
	#
	%fsopts = (-directory => '.');
	$fsopts{'-filter'} = '*.rrf' if($namefilter);

	#
	# Prompt for the new file.
	#
	$fowin = $wm->FileSelect(%fsopts);
	$newfile = $fowin->Show;
	return if($newfile eq "");

	#
	# Make sure the user *really* wants to overwrite an existing file.
	# Continue if they do, return if they don't.
	#
	if(-e $newfile)
	{
		my $dlg;				# Warning dialog widget.
		my $ret;				# Warning response.

		$dlg = $wm->Dialog(-title => 'Warning',
				   -text  => "$newfile already exists; overwrite?",
				   -buttons => ["Overwrite","Cancel"]);
		$ret = $dlg->Show();

		return if($ret eq "Cancel");
	}

	#
	# Save the file.
	#
	rollrec_saveas($newfile);
}

#---------------------------------------------------------------------------
# Routine:	file_quit()
#
sub file_quit
{
	#
	# Destroy the rollrec name table's widgets.
	#
	if($rrnametab)
	{
		$rrnametab->clear;
		$rrnametab->destroy;
	}

	#
	# Warn the user if this file has been modified.  We'll also give
	# the user a chance to save the file, not save the file, or cancel
	# the process of opening the new file.
	#
	if($modified)
	{
		my $dlg;				# Warning dialog widget.
		my $ret;				# Warning response.

		$dlg = $wm->Dialog(-title => 'Warning',
				   -text  => "$curnode has been modified; save before proceeding?",
				   -buttons => ["Save","Don't Save","Cancel"]);
		$ret = $dlg->Show();

		return if($ret eq "Cancel");

		rollrec_close() if($ret eq "Save");
	}
	else
	{
		rollrec_discard();
	}

	#
	# Destroy the main window.  This will cause MainLoop() to return,
	# leading to the program exiting.
	#
	$wm->destroy;
}

#---------------------------------------------------------------------------
# Routine:	edit_undo()
#
sub edit_undo
{
	my $undohash;				# First hash from undo stack.

	my $type;				# Undo type.
	my $op;					# Undo operation.
	my $field;				# Undo field.
	my $value;				# Undo value.

	#
	# Ensure we have something to undo.
	#
	return if(@undo_stack == 0);

	#
	# Get the undo entry fields.
	#
	$undohash = shift @undo_stack;
	$type  = $undohash->{'type'};
	$op    = $undohash->{'op'};
	$field = $undohash->{'field'};
	$value = $undohash->{'value'};

	#
	# Undo based on the operation.  This means that creates are deleted,
	# deletes are re-created, and modifies are returned to the old ways.
	#
	if($op eq "create")
	{
		my @signset;			# Signing set's keyrecs.

		#
		# Since we can only create signing sets, that's the only
		# create operation we'll be able to undo.
		#
		if($type eq "signing_set")
		{
			#
			# Delete the signing set name from each keyrec
			# in the set.
			#
			@signset = keyrec_signsets($field);
			foreach my $kr (@signset)
			{
				keyrec_signset_delkey($field,$kr);
			}

			#
			# Delete the signing set name from our list of names.
			#
			for(my $ind=0; $ind < @ssnames; $ind++)
			{
				if($ssnames[$ind] eq $field)
				{
					splice @ssnames, $ind, 1;
					last;
				}
			}
		}

	}
	elsif($op eq "delete")
	{
		my @signset;			# Signing set's keyrecs.

		#
		# If the signing set was deleted, we'll add the signing set
		# to each keyrec in the set.
		# If the keyrec was deleted, we'll add each signing set in
		# the record to the named keyrec.
		#
		if($type eq "signing_set")
		{
			#
			# Add the signing set name to each keyrec in the set.
			#
			@signset = split / /, $value;
			foreach my $kr (sort(@signset))
			{
				keyrec_signset_addkey($field,$kr);
			}

			#
			# Add the new set's name to the list of signing sets.
			#
			push @ssnames, $field;
			@ssnames = sort(@ssnames);
		}
		elsif($type eq "keyrec")
		{
			#
			# Add each signing set name to the keyrec.
			#
			@signset = split / /, $value;

			foreach my $ssn (@signset)
			{
				keyrec_signset_addkey($ssn,$field);
			}

			#
			# Add the keyrec's set list back to the keyrec list.
			#
			$key_lists{$field} = $value;
			push @krnames, $field;
			@krnames = sort(@krnames);
		}

	}
	elsif($op eq "modify")
	{
		#
		# Handle the modify command for signing sets.
		#
		if($type eq "signing_set")
		{
			my @keylist;		# List of keys.
			my $newlist;		# Keys to be replaced.

			#
			# Delete the keys from the signing set.
			#
			$newlist = keyrec_recval($field,'keys');
			@keylist = split / /, $newlist;
			foreach my $key (@keylist)
			{
				keyrec_signset_delkey($field,$key);
			}

			#
			# Restore the old keys to the signing set.
			#
			@keylist = split / /, $value;
			foreach my $key (@keylist)
			{
				keyrec_signset_addkey($field,$key);
			}
		}
		elsif($type eq "keyrec")
		{
			#
			# Handle the modify command for keys.
			#
			my $kk = $field;		# Key we're undoing.

			#
			# Go through all the signing sets to see which need
			# an undo operation.
			#
			foreach my $set (keyrec_signsets())
			{
				my $kfound;		# Key-found flag.
				my $sfound = 0;		# Set-found flag.
				my @undosets = split / /, $value;

				#
				# Figure out if this set holds this key.
				#
				$kfound = keyrec_signset_haskey($set,$kk);

				#
				# Go through the undo set to see if it
				# contains this set.
				#
				foreach my $sset (@undosets)
				{
					if($set eq $sset)
					{
						$sfound = 1;
						last;
					}
				}

				#
				# If the set is in the undo set and it doesn't
				# have the key, add it back in.
				# If the set is not in the undo set and it has
				# the key, take it back out.
				#
				if($sfound)
				{
					if(!$kfound)
					{
						keyrec_signset_addkey($set,$kk);
					}
				}
				else
				{
					if($kfound)
					{
						keyrec_signset_delkey($set,$kk);
					}
				}
			}
		}
	}

	#
	# Disable the undo menu option if this was the last undo item.
	#
	$em_undo->configure(-state => 'disabled') if(@undo_stack == 0);

	#
	# Update the window and decrement our modifications counter.
	#
	$modified--;
	settitle($curnode);
}

#---------------------------------------------------------------------------
# Routine:	undo_add()
#
sub undo_add
{
	my %undo = ();					# Undo hash.

	#
	# Add the arguments to our undo hash.
	#
	$undo{'type'}	= shift;
	$undo{'op'}	= shift;
	$undo{'field'}	= shift;
	$undo{'value'}	= shift;

	#
	# Add the undo hash to the beginning of the undo stack.
	#
	unshift @undo_stack, \%undo;

	#
	# Make sure the undo menu option is enabled.
	#
	$em_undo->configure(-state => 'normal');

	return;
}

#---------------------------------------------------------------------------
# Routine:	cmds_create()
#
sub cmds_create
{
	my $dlg;					# Dialog box.
	my $lab;					# Label widget.
	my $ent;					# Entry widget.
	my $ret;					# Dialog box return.

	my $rrn;					# New rollrec's name.

# print STDERR "cmds_create:  down in New Rollrec\n";

	#
	# Create a new dialog box to get the name of the new rollrec entry.
	#
	$dlg = $wm->DialogBox(-title => 'New Rollrec Name',
			      -buttons => ["Create", "Cancel" ]);

	#
	# Add a description...
	#
	$lab = $dlg->add('Label', -text => 'Enter new rollrec name:  ');
	$lab->pack(-side => 'left');

	#
	# ... and a text entry slot, focus on the entry ...
	#
	$ent = $dlg->add('Entry');
	$ent->pack(-side => 'left');
	$dlg->configure(-focus => $ent);

	#
	# ... mix, stir, and *voila*!  We've got a dialog box.
	#
	$ret = $dlg->Show();

	#
	# Drop out if the user changed their mind.
	#
	return if($ret eq "Cancel");

	#
	# Get the user's requested name.
	#
	$rrn = $ent->get();

	#
	# Give a warning if this rollrec already exists.
	#
	if(rollrec_exists($rrn,'zonefile'))
	{
		my $dlg;			# Warning dialog widget.
		my $ret;			# Warning response.

		$dlg = $wm->Dialog(-title => 'Warning',
				   -text  => "Rollrec $rrn already exists",
				   -buttons => ["Re-enter Name", "Cancel" ]);
		$ret = $dlg->Show();

		#
		# Drop out if the user changed their mind.
		#
		return if($ret eq "Cancel");

		#
		# Let the user try again.
		#
		cmds_create();
	}

	#
	# Set up an edit window for the zone name.
	#
	editwindow($rrn,1);
}

#---------------------------------------------------------------------------
# Routine:	cmds_delete()
#
sub cmds_delete
{
	my @selected = ();			# Selected rollrec indices.
	my @selnames = ();			# Selected rollrec names
	my $numsel;				# Number of selected rollrecs.

	my $ret;				# Warning response.

	my $dlg;				# Warning dialog widget.
	my $nametab;				# Name table.
	my $lab;				# Label for table.
	my $rowind = 0;				# Name table row index.

# print STDERR "cmds_delete:  down in Delete Selected Rollrecs\n";

	#
	# Get the list of selected rollrec indices.
	#
	@selected = selected_rrnames();
	$numsel = @selected;

	#
	# Build the dialog box.
	#
	$dlg = $wm->DialogBox(-title	=> 'Delete Warning',
			      -buttons	=> ["Delete", "Cancel" ]);

	#
	# Add a description.
	#
	$lab = $dlg->add('Label', -text => '   Delete the following rollrecs:');
	$lab->pack(-anchor => 'w', -side => 'top');

	#
	# Build a table to hold the rollrec names.
	#
	$nametab = $dlg->Table(-rows		=> $numsel,
			       -columns		=> 1,
			       -relief		=> 'raised',
			       -scrollbars	=> '',
			       -borderwidth	=> 1,
			       -fixedrows	=> 0,
			       -takefocus	=> 1,
			      );

	#
	# Add the selected rollrec names to the table.
	#
	foreach my $ind (sort {$a <=> $b} @selected)
	{
		my $txt;				# Label text.

		$txt = "    $rrnames[$ind]";

		$lab = $nametab->Label(-text => $txt,-anchor => 'w');
		$lab->pack(-fill => 'x', -side => 'top');

		$nametab->put($rowind,0,$lab);
		$rowind++;
	}

	#
	# Pack the name table.
	#
	$nametab->pack(-side => 'top');

	#
	# Display the dialog box.
	#
	$ret = $dlg->Show();

	#
	# Check for a "don't do this" response.
	#
	return if($ret eq "Cancel");

	#
	# Delete and deselect the selected rollrecs.
	#
	foreach my $rrind (sort {$a <=> $b} @selected)
	{
		rollrec_del($rrnames[$rrind]);
		rrname_deselect($rrind);
	}

	#
	# Delete the selected rollrec names from the list of rollrecs.
	#
	foreach my $rrind (sort {$b <=> $a} @selected)
	{
		splice @rrnames, $rrind, 1;
		$numrrnames--;
	}

	#
	# Rebuild the button table.
	#
	buildtable(0);

	#
	# Set the modified flag.
	#
	$modified++;
	settitle($title);
}

#---------------------------------------------------------------------------
# Routine:	cmds_modify()
#
# Purpose:	This routine creates edit windows for the selected rollrecs.
#
sub cmds_modify
{
	my @selected = ();			# Selected rollrec names.
	my $numsel;				# Number of selected rollrecs.

# print STDERR "cmds_modify:  down in Edit Selected Rollrecs\n";

	#
	# Get the list of selected rollrec names.
	#
	@selected = selected_rrnames();
	$numsel = @selected;

	#
	# Create the edit windows.
	#
	foreach my $rrind (sort(@selected))
	{
		editwindow($rrnames[$rrind],0);
	}
}

#---------------------------------------------------------------------------
# Routine:	cmds_merger()
#
# Purpose:	This routine merges two rollrec files.
#
sub cmds_merger
{
	my $curfile = $rrfile;			# Current rollrec file.
	my $newfile;				# New rollrec file.

	my @curnames = ();			# Rollrec names from cur file.
	my %curnames = ();			# Rollrec name hash -- cur file.
	my @newnames = ();			# Rollrec names from new file.

	my @dupnames = ();			# Duplicated rollrec names.

	my %currrs = ();			# Saved rollrecs from cur file.
	my %newrrs = ();			# Saved rollrecs from new file.

	my $fswin;				# FileSelect window widget.
	my %fsopts;				# Options for FileSelect.
	my $name;				# Rollrec name.
	my $rref;				# Rollrec reference.

# print STDERR "cmds_merger:  down in Merge Rollrec Files\n";

	#
	# Ensure that the currently loaded rollrec matches the version on
	# disk.  If not, don't let the merge happen.
	#
	if($modified)
	{
		errorbox_multi("$curfile has been modified.  Unable to merge until changes have been saved");
		return;
	}

	#
	# Save the rollrec data from the currently open rollrec file.
	#
	@curnames = rollrec_names();
	foreach $name (@curnames)
	{
		$rref = rollrec_fullrec($name);
		$currrs{$name} = $rref;
		$curnames{$name} = 1;
	}

	#
	# Set up options for FileSelect.
	#
	%fsopts = (
			-directory => '.',
			-filelabel => 'File To Be Merged'
		  );
	$fsopts{'-filter'} = '*.rrf' if($namefilter);

	#
	# Get the name of the file to be merged with the current file.
	# We'll call this the "new" file.
	#
	while(42)
	{
		my $cdev;		# Current rollrec's filesys devicenum.
		my $cino;		# Current rollrec's inode number.
		my $ndev;		# New rollrec's filesys devicenum.
		my $nino;		# New rollrec's inode number.

		#
		# Let the user select a new file.
		#
		$fswin = $wm->FileSelect(%fsopts);
		$newfile = $fswin->Show;

		#
		# Return if the user canceled out of the dialog.
		#
		return if($newfile eq '');

		#
		# Get the file info for the current and new rollrec files.
		#
		($cdev,$cino) = stat($curfile);
		($ndev,$nino) = stat($newfile);

		#
		# Drop out of the loop if the two files have different
		# inode numbers or file system device numbers.
		#
		last if(($cino != $nino) || ($cdev != $ndev));

		#
		# Give an error message about the two files.
		#
		errorbox_multi("Cannot merge a rollrec file with itself.\nCurrent rollrec - $curfile\nNew rollrec - $newfile");
	}

	#
	# Ensure the new file is actually a rollrec file.
	#
	if(dt_filetype($newfile) ne "rollrec")
	{
		my $curnode = getnode($newfile);

		errorbox("$curnode is not a rollrec file; unable to continue merge");
		return;
	}

	#
	# Read the new file.  If we couldn't read it, we'll put up a dialog
	# box explaining the problem.
	#
	if(rollrec_read($newfile) < 0)
	{
		my $curnode = getnode($newfile);

		errorbox_multi("Unable to read rollrec file $curnode.  Check to ensure the file exists and is readable.\nComplete path - $newfile");
		return;
	}

	#
	# Save the rollrec data from the new rollrec file.
	#
	@newnames = rollrec_names();
	foreach $name (@newnames)
	{
		$rref = rollrec_fullrec($name);
		$newrrs{$name} = $rref;
	}

	#
	# Check for overlapping names in the two rollrecs.
	#
	foreach $name (@newnames)
	{
		push @dupnames, $name  if(defined($curnames{$name}));
	}

	#
	# Re-read the original file.
	#
	rollrec_close();
	rollrec_read($rrfile);

	#
	# If we've got any duplicate names, we'll report them and let the
	# user decide whether or not to continue.
	#
	if(@dupnames)
	{
		my $ret;				# Dialog's value.

		$ret = dbx_dups(@dupnames);

		return if($ret eq "Cancel");
	}

	#
	# Add the non-overlapping rollrecs to the current rollrec file.
	#
	foreach $name (@newnames)
	{
		my $rref;				# Ref to rollrec.

		#
		# Skip the overlapping rollrecs.
		#
		next if(defined($curnames{$name}));

		#
		# Add rollrec to the in-memory version of the rollrec file.
		#
		$rref = $newrrs{$name};
		rollrec_add($rref->{'rollrec_type'},$name,$rref);
	}

	#
	# Rebuild the button table.
	#
	@rrnames = rollrec_names();
	$numrrnames = @rrnames;
	buildtable(0);

	#
	# Set the modified flag.
	#
	$modified++;
	settitle($title);

}

#---------------------------------------------------------------------------
# Routine:	cmds_rename()
#
# Purpose:	This routine creates the rename rollrec window for the
#		selected rollrec.
#
sub cmds_rename
{
	my @selected = ();			# Selected rollrec names.
	my $numsel;				# Number of selected rollrecs.
	my $rrind;				# Index of selected rollrec.
	my $dbx;				# Error dialog box.

# print STDERR "cmds_rename:  down in Rename Selected Rollrec\n";

	#
	# Get the list of selected rollrec names.
	#
	@selected = selected_rrnames();
	$numsel = @selected;

	#
	# Complain if there wasn't a selected rollrec.
	#
	if($numsel == 0)
	{
		$dbx = $wm->Dialog(-title   => 'Rename Selected Rollrec',
				   -text    => "One rollrec must be selected to use the Rename Rollrec command",
				   -buttons => ["Okay"]);
		$dbx->Show();
		return;
	}

	#
	# Complain if there was more than one selected rollrec.
	#
	if($numsel > 1)
	{
		$dbx = $wm->Dialog(-title   => 'Rename Selected Rollrec',
				   -text    => "A single rollrec must be selected to use the Rename Rollrec command",
				   -buttons => ["Okay"]);
		$dbx->Show();
		return;
	}

	#
	# Let user know that modified rollrecs must be saved prior to the
	# rollrec rename.  If the user is okay with this, we'll save the
	# contents of the rollrec.
	#
	if($modified)
	{
		my $dret;			# Return value from dialog.

		$dbx = $wm->Dialog(-title   => 'Rename Selected Rollrec',
				   -text    => "The rollrec file has been modified.  In order to rename the selected rollrec, the changes will be saved to the rollrec file",
				   -buttons => ["Okay", "Cancel"]);

		$dret = $dbx->Show();
		return if($dret eq 'Cancel');

		file_save();
	}

	#
	# Create the rename-rollrec window.
	#
	$rrind = $selected[0];
	renamewindow($rrnames[$rrind]);
}

#---------------------------------------------------------------------------
# Routine:	cmds_verify()
#
# Purpose:	This routine verifies a rollrec file.  If the summary flag
#		was given, then a dialog box will be created that contains
#		the names and error counts of each bad rollrec.  If the
#		summary flag wasn't given, then an edit window will be
#		put up for each bad rollrec.
#
sub cmds_verify
{
	my $summary = shift;			# Summary-only flag.

	my %errcnts = ();			# Counts of errors.
	my $errcnt  = 0;			# Rollrecs with errors.

	my $dbx;				# Dialog box.
	my $rrtab;				# Bad rollrec table.
	my $lab;				# Label for table.
	my $rowind = 0;				# Error table row index.

	my $errrows;				# Rows in table.
	my $sbars  = 'e';			# Scrollbars.

# print STDERR "cmds_verify:  down in Verify Rollrec File\n";

	#
	# See if any rollrecs have problems.
	#
	foreach my $rrn (reverse(sort(@rrnames)))
	{
		my $errors = 0;			# Errors for rollrec.
		my $warns  = 0;			# Warnings for rollrec.
		my $problems;			# Errs and/or warns for rollrec.

		#
		# Check this rollrec.
		#
		($warns,$errors) = goodrollrec($rrn,0);

		#
		# Get the count of rollrec problems.  If there were any
		# problems, add 'em to the error count hash.
		#
		$problems = $errors;
		$problems += $warns if($givewarnings);
		if($problems)
		{
			$errcnts{$rrn}{'warns'}  = $warns;
			$errcnts{$rrn}{'errors'} = $errors;

			$errcnt++;
		}

		#
		# If there were problems and the user wants more than a
		# summary, we'll put up an edit window for the rollrec
		# and mark up its problem fields.
		#
		if($problems && !$summary)
		{
			editwindow($rrn,0);
			goodrollrec($rrn,1);
		}
	}

	#
	# If there weren't any errors, we'll put up a dialog box saying so
	# and then return once it's dismissed.
	#
	if($errcnt == 0)
	{
		$dbx = $wm->Dialog(-title   => 'Rollrecs Valid',
				   -text    => "Rollrecs passed validity checks",
				   -buttons => ["Done"]);
		$dbx->Show();
		return;
	}

	#
	# Return if the user doesn't want an error summary.
	#
	return if(!$summary);

	#
	# Get the row count, which is the number of erring rollrecs plus
	# a header row.
	# If there's only a few rows, we'll dump the scrollbar.
	#
	$errrows = $errcnt + 1;
	$sbars	= '' if($errrows < 4);

	#
	# Build a dialog box to hold an error summary.
	#
	$dbx = $wm->DialogBox(-title   => 'Rollrecs With Errors',
			      -buttons => ["Done"]);

	#
	# Build a table to hold the rollrec names.
	#
	$rrtab = $dbx->Table(-rows		=> $errrows,
			     -columns		=> 3,
			     -relief		=> 'raised',
			     -scrollbars	=> $sbars,
			     -borderwidth	=> 1,
			     -fixedrows		=> 0,
			     -takefocus		=> 1,
			    );

	#
	# Add some column headers.
	#
	$lab = $rrtab->Label(-text => 'Rollrec');
	$rrtab->put($rowind,0,$lab);
	$lab = $rrtab->Label(-text => 'Warnings');
	$rrtab->put($rowind,1,$lab);
	$lab = $rrtab->Label(-text => 'Errors');
	$rrtab->put($rowind,2,$lab);
	$rowind++;

	#
	# Add the selected rollrec names to the table.
	#
	foreach my $rrn (sort(keys(%errcnts)))
	{
		my $err = $errcnts{$rrn}{'errors'};
		my $wrn = $errcnts{$rrn}{'warns'};

		$lab = $rrtab->Label(-text => $rrn);
		$rrtab->put($rowind,0,$lab);
		$lab = $rrtab->Label(-text => $wrn);
		$rrtab->put($rowind,1,$lab);
		$lab = $rrtab->Label(-text => $err);
		$rrtab->put($rowind,2,$lab);
		$rowind++;
	}

	#
	# Pack the error table.
	#
	$rrtab->pack(-side => 'top');

	#
	# Display the dialog box.
	#
	$dbx->Show();

}

#---------------------------------------------------------------------------
# Routine:	view_selall()
#
# Purpose:	This routine selects/deselects all rollrec names.
#		If any names are selected, the remainder will be selected.
#		If all names are selected, they will all be deselected.
#
sub view_selall
{
	my @selected = ();			# Selected rollrec names.

# print STDERR "view_selall:  down in Select All Rollrecs\n";

	#
	# Get the list of selected rollrec names.
	#
	@selected = selected_rrnames();

	#
	# If all the names are selected, we'll deselect everything.
	# If not all the names are selected, we'll select them all.
	#
	if(@selected == $numrrnames)
	{
		#
		# Unselect all names.
		#
		for(my $ind = 0; $ind < $numrrnames; $ind++)
		{
			rrname_deselect($ind);
		}
		$vw_selall->configure(-label => 'Select All Rollrecs');
	}
	else
	{
		#
		# Select all names.
		#
		for(my $ind=0; $ind < $numrrnames; $ind++)
		{
			my $col;			# Column index.
			my $row;			# Row index.
			my $btn;			# Rollrec button.

			($col,$row) = ind2cr($ind);
			$btn = $rrnametab->get($row,$col);

			rrname_select($ind)  if($btn->cget(-background) eq $RRTAB_UNSEL);
		}
		$vw_selall->configure(-label => 'Unselect All Rollrecs');

	}
}

#---------------------------------------------------------------------------
# Routine:	view_raiser()
#
# Purpose:	This routine brings the rollrec edit windows to the front.
#
sub view_raiser
{

# print STDERR "view_raiser:  down in Raise Rollrec Edit Windows\n";

	#
	# Raise the edit windows.
	#
	foreach my $rrn (reverse(sort(keys(%editwinds))))
	{
		$editwinds{$rrn}->raise;
	}
}

#---------------------------------------------------------------------------
# Routine:	view_closeall()
#
# Purpose:	This routine closes the selected rollrec edit windows.
#		The rollrec's buttons are deselected as well.
#
sub view_closeall
{
# print STDERR "view_closeall:  down in Close All Selected Rollrecs\n";

	#
	# Destroy all edit windows and remove them from the edit-window list.
	#
	foreach my $rrn (sort(keys(%editwinds)))
	{
		editbegone($rrn);
	}
}

#---------------------------------------------------------------------------
# Routine:	view_closeone()
#
# Purpose:	This routine closes the current rollrec edit window.
#		The rollrec's button is deselected as well.
#
sub view_closeone
{
	my $rrn = shift;

# print STDERR "view_closeone:  down in Close Selected Rollrec\n";
	editbegone($rrn);
}

#---------------------------------------------------------------------------
# Routine:	editwindow()
#
# Purpose:	This routine creates a new rollrec-edit window.
#
sub editwindow
{
	my $rrn = shift;			# Name of rollrec to edit.
	my $newflag = shift;			# New-rollrec flag.

	my $editwin;				# Edit window.
	my $eframe;				# Edit frame.
	my $edittab;				# Edit table.
	my $etlen = @ET_LABELS;			# Row-length of table.

	my $frm;				# Frame widget.
	my $lab;				# Label widget.
	my $ent;				# Entry widget.
	my $rb;					# Radiobutton widget.
	my $btn;				# Button widget.
	my $casca;				# Cascading menu widget.

	my $rrec;				# Rollrec reference.
	my $llev;				# Logging level.

# print STDERR "editwindow:  down in Edit Selected Rollrecs\n";

	#
	# If we've already got an edit window for this rollrec, we'll raise
	# that window and return.
	#
	if(defined($editwinds{$rrn}))
	{
		$editwinds{$rrn}->focus;
		return;
	}

	#
	# Get this rollrec's data.
	#
	$rrec = rollrec_fullrec($rrn);

	#
	# If this is a new rollrec, we'll set some defaults.
	#
	if(!rollrec_exists($rrn))
	{
		$rrec->{'rollrec_type'}	 = 'roll';
		$rrec->{'keyrec'}	 = "$rrn.krf";
		$rrec->{'zonefile'}	 = "$rrn.signed";
		$rrec->{'zonename'}	 = "$rrn";
	}

	#
	# Add the rollrec's data to the %rollrecs hash.
	#
	$rollrecs{$rrn}{'administrator'} = $rrec->{'administrator'};
	$rollrecs{$rrn}{'directory'}	 = $rrec->{'directory'};
	$rollrecs{$rrn}{'display'}	 = $rrec->{'display'};
	$rollrecs{$rrn}{'keyrec'}	 = $rrec->{'keyrec'};
	$rollrecs{$rrn}{'loglevel'}	 = $rrec->{'loglevel'};
	$rollrecs{$rrn}{'maxttl'}	 = $rrec->{'maxttl'};
	$rollrecs{$rrn}{'rollrec_type'}	 = $rrec->{'rollrec_type'};
	$rollrecs{$rrn}{'zonefile'}	 = $rrec->{'zonefile'};

	if(!defined($rrec->{'zonename'}))
	{
		$rollrecs{$rrn}{'zonename'} = $rrn;
	}
	else
	{
		$rollrecs{$rrn}{'zonename'} = $rrec->{'zonename'};
	}

	#
	# Set up the radio buttons' data areas for the record type and
	# the display flag.
	#
	$editrbs{$rrn} = $rollrecs{$rrn}{'rollrec_type'} eq 'roll' ? 1 : 0;
	if(!exists($rollrecs{$rrn}{'display'})	||
	   !defined($rollrecs{$rrn}{'display'}))
	{
		$editdfs{$rrn} = -1;
	}
	elsif($rollrecs{$rrn}{'display'} == 0)
	{
		$editdfs{$rrn} = 0;
	}
	elsif($rollrecs{$rrn}{'display'} == 1)
	{
		$editdfs{$rrn} = 1;
	}

	#
	# Create a new window to hold our edit session.  Bind up some
	# key accelerators, too.
	#
	$editwin = MainWindow->new(-relief	=> 'raised',
				   -title	=> "$rrn",
				   -borderwidth => 1);

	$editwin->bind('<Control-Key-q>', \&file_quit);
	$editwin->bind('<Control-Key-w>', [\&editbegone, $rrn]);
	$editwin->bind('<Control-Key-c>', \&view_closeone);
	$editwin->bind('<Control-Key-k>', \&view_closeall);

	#
	# Now make the containers for the window.
	#
	$eframe = $editwin->Frame(-relief => 'raised', -borderwidth => 1);
	$eframe->pack(-fill => 'x');

	#
	# Create a table to hold the rollrec data.
	#
	$edittab = $eframe->Table(-rows		=> $etlen,
				  -columns	=> 2,
				  -relief	=> 'raised',
				  -scrollbars	=> '',
				  -borderwidth	=> 1,
				  -fixedrows	=> 0,
				  -takefocus	=> 1,
			         );

	#----------------
	#
	# Set the field column.
	#
	for(my $ind = $ROW_FIRST; $ind <= $ROW_LAST; $ind++)
	{
		$lab = $eframe->Label(-text => $ET_LABELS[$ind],-anchor => 'w');
		$lab->pack(-fill => 'x', -side => 'top');
		$edittab->put($ind,$COL_FIELD,$lab);
	}

	#----------------
	#
	# Record type:  Add the stuff for the record type.
	#
	$frm = $eframe->Frame(-relief => 'raised', -borderwidth => 1);
	$rb = $frm->Radiobutton(-text	   => 'Roll',
				-variable => \$editrbs{$rrn},
				-value	   => 1);
	$rb->pack(-fill => 'x', -side => 'left');
	$rb = $frm->Radiobutton(-text	   => 'Skip',
				-variable => \$editrbs{$rrn},
				-value	   => 0);
	$rb->pack(-fill => 'x', -side => 'left');
	$frm->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_TYPE,$COL_DATA,$frm);

	#----------------
	#
	# Zonename:  Add the stuff for the rollrec's zonename.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'zonename'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_ZONENAME,$COL_DATA,$ent);

	#----------------
	#
	# Zonefile:  Add the stuff for the rollrec's zonefile.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'zonefile'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_ZONEFILE,$COL_DATA,$ent);

	#----------------
	#
	# Keyrec:  Add the stuff for the rollrec's keyrec.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'keyrec'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_KEYREC,$COL_DATA,$ent);

	#----------------
	#
	# Administrator:  Add the stuff for the rollrec's admin mail.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'administrator'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_ADMIN,$COL_DATA,$ent);

	#----------------
	#
	# Directory:  Add the stuff for the rollrec's directory.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'directory'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_DIR,$COL_DATA,$ent);

	#----------------
	#
	# Display:  Add the stuff for the rollrec's display flag.
	#
	$frm = $eframe->Frame(-relief => 'raised', -borderwidth => 1);
	$rb = $frm->Radiobutton(-text	   => 'On',
				-variable => \$editdfs{$rrn},
				-value	   => 1);
	$rb->pack(-fill => 'x', -side => 'left');
	$rb = $frm->Radiobutton(-text	   => 'Off',
				-variable => \$editdfs{$rrn},
				-value	   => 0);
	$rb->pack(-fill => 'x', -side => 'left');
	$rb = $frm->Radiobutton(-text	   => 'Default',
				-variable => \$editdfs{$rrn},
				-value	   => -1);
	$rb->pack(-fill => 'x', -side => 'left');
	$frm->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_DISPLAY,$COL_DATA,$frm);

	#----------------
	#
	# Logging level:  Add the stuff for the rollrec's log level.
	#

	#
	# Get the label for the logging level's menu button.  If it's defined
	# in the rollrec, we'll make sure we have the string value.  If we
	# can't convert it, we'll use a dummy error value.  If it isn't
	# defined in the rollrec, we'll use "default" for the button label.
	#
	if(defined($rollrecs{$rrn}{'loglevel'}))
	{
		$llev = rolllog_str($rollrecs{$rrn}{'loglevel'});
		if($llev eq '')
		{
			$llev = '<bad value in rollrec file>';
		}
	}
	else
	{
		$llev = 'default';
	}

	#
	# Create the menu button.
	#
	$ent = $eframe->Menubutton(-text    => $llev,
				   -tearoff => 0);

	#
	# Add a new radiobutton for each logging level.
	#
	$casca = $ent->cascade(-label => '');
	foreach $llev (rolllog_levels())
	{
		$casca->radiobutton(-label    => "$llev",
				    -variable => \$rollrecs{$rrn}{'loglevel'},
				    -command  => [\&setloglvl, $ent, $llev]);
	}

	#
	# Add a default entry.
	#
	$casca->radiobutton(-label    => "default",
			    -variable => \$rollrecs{$rrn}{'loglevel'},
			    -command  => [\&setloglvl, $ent, 'default']);

	#
	# Make it all available to the user.
	#
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_LOG,$COL_DATA,$ent);

	#----------------
	#
	# Max-TTL:  Add the stuff for the rollrec's maximum TTL.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'maxttl'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_MAXTTL,$COL_DATA,$ent);

	#----------------
	#
	# ZSArgs:  Add the stuff for the rollrec's zonesigner arguments.
	#
	$ent = $eframe->Entry(-textvariable => \$rollrecs{$rrn}{'zsargs'});
	$ent->pack(-fill => 'x', -side => 'top');
	$edittab->put($ROW_ZSARGS,$COL_DATA,$ent);

	#----------------
	#
	# Done adding our data.  Do another pack, just for kicks.
	#
	$edittab->pack(-side => 'top');

	#
	# Add a button to dismiss the window.
	#
	$btn = $eframe->Button(-text	 => 'Save',
			       -command => [\&editsaver, $rrn, $newflag]);
	$btn->pack(-side => 'left', -fill => 'x', -expand => 1);
	$btn = $eframe->Button(-text	 => 'Cancel',
			       -command => [\&editbegone, $rrn]);
	$btn->pack(-side => 'left', -fill => 'x', -expand => 1);

	#
	# Save the new edit window in the hash table of edit windows.
	#
	$editwinds{$rrn} = $editwin;
	$edittabs{$rrn}  = $edittab;

}

#---------------------------------------------------------------------------
# Routine:	renamewindow()
#
# Purpose:	This routine creates a new rollrec-rename window.
#
sub renamewindow
{
	my $rrn = shift;			# Name of rollrec to rename.
	my $dbx;				# Error dialog box.

	my $renwin;				# Rename window.

	##########

	my $rframe;				# Rename frame.
	my $rentab;				# Rename table.

	my $frm;				# Frame widget.
	my $lab;				# Label widget.
	my $ent;				# Entry widget.
	my $btn;				# Button widget.

# print STDERR "renamewindow:  down in Rename Rollrec\n";

	#
	# If there's a rename window for this rollrec, we'll give an error
	# and return.
	#
	if(defined($renwinds{$rrn}))
	{
		$dbx = $wm->Dialog(-title   => 'Rename Selected Rollrec',
				   -text    => "This rollrec is already being renamed",
				   -buttons => ["Okay"]);
		$dbx->Show();
		return;
	}

	#
	# If there's an edit window for this rollrec, we'll give an error
	# and return.
	#
	if(defined($editwinds{$rrn}))
	{
		$dbx = $wm->Dialog(-title   => 'Rename Selected Rollrec',
				   -text    => "Unable to rename a rollrec while it is being edited",
				   -buttons => ["Okay"]);
		$dbx->Show();
		return;
	}

	#
	# Create a new window to hold our rename session.  Bind up some
	# key accelerators, too.
	#
	$renwin = MainWindow->new(-relief	=> 'raised',
				   -title	=> "Rename $rrn",
				   -borderwidth => 1);

	$renwin->bind('<Control-Key-q>', \&file_quit);
	$renwin->bind('<Control-Key-w>', [\&renamebegone, $rrn]);

	#
	# Now make the containers for the window.
	#
	$rframe = $renwin->Frame(-relief => 'raised', -borderwidth => 1);
	$rframe->pack(-fill => 'x');

	#
	# Create a table to hold the rollrec data.
	#
	$rentab = $rframe->Table(-rows		=> 2,
				 -columns	=> 2,
				 -relief	=> 'raised',
				 -scrollbars	=> '',
				 -borderwidth	=> 1,
				 -fixedrows	=> 0,
				 -takefocus	=> 1,
			        );

	#
	# Add the label column.
	#
	$lab = $rframe->Label(-text => 'New rollrec name',-anchor => 'w');
	$lab->pack(-fill => 'x', -side => 'top');
	$rentab->put(0,0,$lab);

	#
	# Add the empty field for the rollrec's new name.
	#
	$rollrecs{$rrn}{'newname'} = '';
	$ent = $rframe->Entry(-textvariable => \$rollrecs{$rrn}{'newname'});
	$ent->pack(-fill => 'x', -side => 'top');
	$rentab->put(0,1,$ent);

	#
	# Done adding our data.  Do another pack, just for kicks.
	#
	$rentab->pack(-side => 'top');

	#
	# Add a button to dismiss the window.
	#
	$btn = $rframe->Button(-text	 => 'Rename',
			       -command => [\&renamesaver, $rrn]);
	$btn->pack(-side => 'left', -fill => 'x', -expand => 1);
	$btn = $rframe->Button(-text	 => 'Cancel',
			       -command => [\&renamebegone, $rrn]);
	$btn->pack(-side => 'left', -fill => 'x', -expand => 1);

	#
	# Save the new rename window in the hash table of rename windows.
	#
	$renwinds{$rrn} = $renwin;
	$rentabs{$rrn}  = $rentab;
}

#---------------------------------------------------------------------------
# Routine:	setloglvl()
#
# Purpose:	Set the logging level label in an edit window's menu button.
#
sub setloglvl
{
	my $ent = shift;				# Menu label to change.
	my $llev = shift;				# New menu label.

	$ent->configure(-text => $llev);
}

##############################################################################
#
# Utility routines
#
##############################################################################

#---------------------------------------------------------------------------
# Routine:	readrrf()
#
sub readrrf
{
	my $rrf = shift;				# Rollrec file.
	my @names;					# Rollrec names.

	#
	# If the specified file doesn't exist, ask the user if we should
	# continue or quit.
	#
	if(! -e $rrf)
	{
		my $dlg;			# Warning dialog widget.
		my $ret;			# Warning response.

		$dlg = $wm->Dialog(-title => 'Warning',
				   -text  => "$curnode does not exist",
				   -buttons => ["Continue", "Quit" ]);
		$ret = $dlg->Show();

		return(1) if($ret eq "Continue");

		file_quit();
	}

	#
	# Ensure the file is actually a rollrec file.
	#
	if(dt_filetype($rrf) ne "rollrec")
	{
		my $curnode = getnode($rrf);

		errorbox("$curnode is not a rollrec file; unable to continue");
		return(0);
	}

	#
	# Zap the old data.
	#
	@rrnames = ();
	@rollrecs = ();

	#
	# Get data from the rollrec file.
	#
	rollrec_read($rrf);
	@rrnames = rollrec_names();
	$numrrnames = @rrnames;

	#
	# Initialize the rollrecs hash for each rollrec.
	#
	foreach my $rrn (@rrnames)
	{
		my $rrec;				# This rollrec's data.

		#
		# Get this rollrec's data.
		#
		$rrec = rollrec_fullrec($rrn);

		#
		# Add the rollrec's data to the %rollrecs hash.
		#
		$rollrecs{$rrn}{'administrator'} = $rrec->{'administrator'};
		$rollrecs{$rrn}{'directory'}	 = $rrec->{'directory'};
		$rollrecs{$rrn}{'display'}	 = $rrec->{'display'};
		$rollrecs{$rrn}{'keyrec'}	 = $rrec->{'keyrec'};
		$rollrecs{$rrn}{'loglevel'}	 = $rrec->{'loglevel'};
		$rollrecs{$rrn}{'maxttl'}	 = $rrec->{'maxttl'};
		$rollrecs{$rrn}{'rollrec_type'}	 = $rrec->{'rollrec_type'};
		$rollrecs{$rrn}{'zonefile'}	 = $rrec->{'zonefile'};
		$rollrecs{$rrn}{'zonename'}	 = $rrec->{'zonename'};
		$rollrecs{$rrn}{'zsargs'}	 = $rrec->{'zsargs'};

		#
		# Set up the radio buttons' data areas for the record type and
		# the display flag.
		#
		$editrbs{$rrn} = $rollrecs{$rrn}{'rollrec_type'} eq 'roll' ? 1 : 0;
		if(!exists($rollrecs{$rrn}{'display'})	||
		   !defined($rollrecs{$rrn}{'display'}))
		{
			$editdfs{$rrn} = -1;
		}
		elsif($rollrecs{$rrn}{'display'} == 0)
		{
			$editdfs{$rrn} = 0;
		}
		elsif($rollrecs{$rrn}{'display'} == 1)
		{
			$editdfs{$rrn} = 1;
		}
	}

	#
	# Reset the modified-keyrec file flag.
	#
	$modified = 0;
	settitle($curnode);
	return(1);
}

#---------------------------------------------------------------------------
# Routine:	getnode()
#
# Purpose:	Get the node of a pathname.
#
sub getnode
{
	my $path = shift;				# Path to nodify.

	my @pathelts;					# Path elements.
	my $pathnode;					# Last path elements.

	@pathelts = split /\//, $path;
	$pathnode = pop @pathelts;

	return($pathnode);
}

#---------------------------------------------------------------------------
# Routine:	editsaver()
#
# Purpose:	Save a rollrec and get rid of its edit window.
#
sub editsaver
{
	my $rrn = shift;			# Name of rollrec to save.
	my $newflag = shift;			# New-rollrec flag.

# print STDERR "editsaver:  saver rollrec edits for $rrn\n";

	$rollrecs{$rrn}{'display'} = $editdfs{$rrn};

	#
	# If this is a valid rollrec, we'll get rid of the edit window
	# and save the data.  If this is a new rollrec, we'll also add
	# a rollrec button to the main window.
	#
	if(goodrollrec($rrn,1))
	{
		#
		# If this is a new rollrec, add the name to the rollrec
		# list and then rebuild the main button table.
		#
		if($newflag)
		{
			push @rrnames, $rrn;
			$numrrnames++;
			buildtable(0);
		}

		#
		# Get rid of the edit window and save the rollrec data.
		#
		editbegone($rrn);
		saverollrec($rrn);
	}

}

#---------------------------------------------------------------------------
# Routine:	renamesaver()
#
# Purpose:	Save a rollrec and get rid of its rename window.
#
sub renamesaver
{
	my $rrn = shift;			# Name of rollrec to save.
	my $newname;				# New rollrec name.
	my $newname2;				# Copy of new rollrec name.

# print STDERR "renamesaver:  rename rollrec edits for $rrn\n";

	$newname = $rollrecs{$rrn}{'newname'};

	#
	# Ensure a new name was given.
	#
	if($newname eq '')
	{
		errorbox("A new rollrec name must be given");
		delete $renwinds{$rrn};
		delete $rentabs{$rrn};
		return;
	}

	#
	# Ensure a *valid* new name was given.
	#
	$newname2 = $newname;
	$newname2 =~ s/[a-zA-Z0-9\/\-+_.,: \@\t]//g;
	if(length($newname2) > 0)
	{
		errorbox("\"$newname\" is an invalid rollrec name");
		delete $renwinds{$rrn};
		delete $rentabs{$rrn};
		return;
	}

	#
	# Rename the rollrec.
	#
	rollrec_rename($rrn,$newname);
	$modified = 1;

	#
	# Write the rollrec file contents and rebuild our rollrec list.
	#
	rollrec_close();
	buildtable(1);

	#
	# Get rid of the rename window.
	#
	renamebegone($rrn,$newname);
	$modified = 0;
}

#-----------------------------------------------------------------------------
# Routine:	goodrollrec()
#
# Purpose:	Ensure that the rollrec has valid data.
#
#		The following fields may be empty:
#			- administrator
#			- directory
#			- display
#			- loglevel
#			- maxttl
#
#		Error checks are:
#			- empty zonename field
#			- invalid logging level
#			- non-positive maxttl
#			- invalid display value
#			- zone file is not a regular file
#			- keyrec file is not a regular file
#
#		Warning checks are:
#			- zone directory doesn't exist
#			- zone directory isn't a directory
#			- zone file doesn't exist
#			- zone file isn't a regular file
#			- keyrec file doesn't exist
#			- keyrec file isn't a regular file
#
sub goodrollrec
{
	my $rrn = shift;			# Name of the rollrec.
	my $upd = shift;			# Update-window flag.

# print STDERR "goodrollrec:  down in  ($rrn)\n";

	my $errors = 0;				# Error count.
	my $warns = 0;				# Warning count.
	my $problems = 0;			# Problem count.

	my $admin;				# Administrator email address.
	my $dispflag;				# Display flag.
	my $krfile;				# Keyrec file.
	my $loglevel;				# Logging level.
	my $maxttl;				# Maximum TTL.
	my $zonedir;				# Directory.
	my $zonefile;				# Zonefile.
	my $zonename;				# Zonename.
	my $zsargs;				# Zonesigner arguments.

	#
	# Reset the message fields.
	#
	if($upd)
	{
		addmsg($rrn,$COL_FIELD,0,' ');
		addmsg($rrn,$COL_DATA,0,' ');
	}

	#
	# Strip all whitespace from the beginning and end of the fields.
	#
	foreach my $fld (@EDITFIELDS)
	{
		next if(!exists($rollrecs{$rrn}{$fld}) ||
			($rollrecs{$rrn}{$fld} eq ''));
		$rollrecs{$rrn}{$fld} =~ s/^[ \t]*//;
		$rollrecs{$rrn}{$fld} =~ s/[ \t]*$//;
	}

	#
	# Get the rollrec data.
	#
	$admin	  = $rollrecs{$rrn}{'administrator'};
	$dispflag = $rollrecs{$rrn}{'display'};
	$krfile	  = $rollrecs{$rrn}{'keyrec'};
	$loglevel = $rollrecs{$rrn}{'loglevel'};
	$maxttl	  = $rollrecs{$rrn}{'maxttl'};
	$zonedir  = $rollrecs{$rrn}{'directory'};
	$zonefile = $rollrecs{$rrn}{'zonefile'};
	$zsargs	  = $rollrecs{$rrn}{'zsargs'};
	if(!defined($rollrecs{$rrn}{'zonename'}))
	{
		$zonename = $rrn;
	}
	else
	{
		$zonename = $rollrecs{$rrn}{'zonename'};
	}

	#
	# Reset all the label colors.
	#
	if($upd)
	{
		fieldcolor($ROW_ZONEFILE,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_KEYREC,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_ADMIN,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_DIR,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_DISPLAY,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_LOG,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_MAXTTL,$rrn,$NORMALCOLOR);
		fieldcolor($ROW_ZSARGS,$rrn,$NORMALCOLOR);
	}

	#
	# Check the zonename.
	#
	if($zonename eq '')
	{
		errval($ROW_ZONENAME,$rrn,"no zone name specified") if($upd);
		$errors++;
	}

	#
	# Check the zone file.
	#
	if($zonefile eq '')
	{
		errval($ROW_ZONEFILE,$rrn,"no zone file specified") if($upd);
		$errors++;
	}
	else
	{
		if((!-e $zonefile) && $givewarnings)
		{
			warnval($ROW_ZONEFILE,$rrn,"zone file \"$zonefile\" does not exist") if($upd);
			$warns++;
		}
		elsif((!-f $zonefile) && $givewarnings)
		{
			warnval($ROW_ZONEFILE,$rrn,"zone file \"$zonefile\" is not a regular file") if($upd);
			$warns++;
		}
	}

	#
	# Check the keyrec file.
	#
	if($krfile eq '')
	{
		errval($ROW_KEYREC,$rrn,"no keyrec file specified") if($upd);
		$errors++;
	}
	else
	{
		if((!-e $krfile) && $givewarnings)
		{
			warnval($ROW_KEYREC,$rrn,"keyrec file \"$krfile\" does not exist") if($upd);
			$warns++;
		}
		elsif((!-f $krfile) && $givewarnings)
		{
			warnval($ROW_KEYREC,$rrn,"keyrec file \"$krfile\" is not a regular file") if($upd);
			$warns++;
		}
	}

	#
	# No validity checks for the administrator.
	#

	#
	# Check the directory.
	#
	if(exists($rollrecs{$rrn}{'directory'}))
	{
		if(($rollrecs{$rrn}{'directory'}) ne '')
		{
			if(!-e $zonedir)
			{
				warnval($ROW_DIR,$rrn,"directory \"$zonedir\" does not exist") if($upd);
				$warns++;
			}
			elsif(!-d $zonedir)
			{
				warnval($ROW_DIR,$rrn,"directory \"$zonedir\" is not a directory") if($upd);
				$warns++;
			}
		}
	}

	#
	# Make sure we've got a valid display flag.
	#
	if(defined($dispflag))
	{
		if(($dispflag != 0) && ($dispflag != 1) && ($dispflag != -1))
		{
			errval($ROW_DISPLAY,$rrn,"invalid display flag \"$dispflag\"") if($upd);
			$errors++;
		}
	}

	#
	# Make sure we've got a valid logging level.
	#
	if(exists($rollrecs{$rrn}{'loglevel'}))
	{
		if(($rollrecs{$rrn}{'loglevel'} ne '') &&
		   ($rollrecs{$rrn}{'loglevel'} ne 'default'))
		{
			if(rolllog_level($loglevel,0) < 0)
			{
				errval($ROW_LOG,$rrn,"invalid loglevel \"$loglevel\"") if($upd);
				$errors++;
			}
		}
	}

	#
	# Make sure we've got a valid maximum TTL.
	#
	if(exists($rollrecs{$rrn}{'maxttl'}))
	{
		if($rollrecs{$rrn}{'maxttl'} ne '')
		{
			if($maxttl < 1)
			{
				errval($ROW_MAXTTL,$rrn,"invalid maxttl \"$maxttl\"") if($upd);
				$errors++;
			}
		}
	}

	#
	# No error checks for zonesigner arguments.
	#

	#
	# If we found any errors or warnings, we'll add a message about them.
	#
	if($upd)
	{
		addmsg($rrn,$COL_FIELD,$errors,"error") if($errors);
		addmsg($rrn,$COL_DATA,$warns,"warn") if($warns);
	}

	#
	# Add the warning count to the error count if we shouldn't ignore
	# warnings
	#
	$problems  = $errors;
	$problems += $warns if($givewarnings);

	#
	# If we've found this rollrec is good, we'll make some adjustments.
	#
	if(!$errors)
	{
		#
		# Delete the administrator if it's empty.
		#
		if($rollrecs{$rrn}{'administrator'} eq '')
		{
			delete $rollrecs{$rrn}{'administrator'};
		}

		#
		# Delete the directory if it's empty.
		#
		if(($rollrecs{$rrn}{'directory'}) eq '')
		{
			delete $rollrecs{$rrn}{'directory'};
		}

		#
		# Delete the loglevel if it's empty or the default was chosen.
		#
		if(($rollrecs{$rrn}{'loglevel'} eq '')	||
		   ($rollrecs{$rrn}{'loglevel'} eq 'default'))
		{
			delete $rollrecs{$rrn}{'loglevel'};
		}

		#
		# Delete the max-ttl if it's empty.
		#
		if($rollrecs{$rrn}{'maxttl'} eq '')
		{
			delete $rollrecs{$rrn}{'maxttl'};
		}

		#
		# Delete the zsargs if it's empty.
		#
		if($rollrecs{$rrn}{'zsargs'} eq '')
		{
			delete $rollrecs{$rrn}{'zsargs'};
		}

	}

	return($warns,$errors) if(!$upd);
	return(!$problems);
}

#-----------------------------------------------------------------------------
# Routine:	saverollrec()
#
# Purpose:	Save the rollrec's data into the rollrec module data.
#
#
sub saverollrec
{
	my $rrn = shift;			# Name of the rollrec.

	my $typestr;				# Calculated rollrec type.

# print STDERR "saverollrec:  down in  ($rrn)\n";

	#
	# Get the rollrec type from %editrbs.
	#
	$typestr = $editrbs{$rrn} ? "roll" : "skip";

	#
	# Get the display flag from %editdfs.
	#
	if(!exists($editdfs{$rrn}) || !defined($editdfs{$rrn}) ||
	   ($editdfs{$rrn} == -1))
	{
		delete($rollrecs{$rrn}{'display'});
	}
	elsif($editdfs{$rrn} == 0)
	{
		$rollrecs{$rrn}{'display'} = 0;
	}
	elsif($editdfs{$rrn} == 1)
	{
		$rollrecs{$rrn}{'display'} = 1;
	}

	#
	# Set the rollrec's type.
	#
	rollrec_rectype($rrn,$typestr);

	#
	# Save the fields into the rollrec itself.  If the field exists
	# in %rollrecs{$rrn}, we'll save the value.  If the field does
	#  not exist, we'll delete the field.
	#
	foreach my $field (@EDITFIELDS)
	{
		if(exists($rollrecs{$rrn}{$field}))
		{
			rollrec_setval($rrn,$field,$rollrecs{$rrn}{$field});
		}
		else
		{
			rollrec_delfield($rrn,$field);
		}
	}

	#
	# Mark this rollrec as having been modified.
	#
	rrmodified($rrn);
}

#---------------------------------------------------------------------------
# Routine:	rrmodified()
#
# Purpose:	Mark a rollrec as having been modified.
#
sub rrmodified
{
	my $rrn = shift;				# Modified rollrec.

	#
	# Increment the modified-keyrec flag.
	#
	$modified++;
	$modified{$rrn} = 1;

	settitle($title);

	#
	# Re-populate and update the table.
	#
	for(my $ind = 0; $ind < $numrrnames; $ind++)
	{
		my $row;				# Cell's row index.
		my $col;				# Cell's column index.

		my $btn;				# Button widget.
		my $name;				# Label of button.

		#
		# Get the column and row indices.
		#
		($col,$row) = ind2cr($ind);

		#
		# Get this button's label.
		#
		$btn = $rrnametab->get($row,$col);
		$name = $btn->cget(-text);

		#
		# Modify the name of just the button we're looking for.
		#
		next if($name ne $rrn);
		$btn->configure(-text => "* $name");
	}

}

#---------------------------------------------------------------------------
# Routine:	rrsaved()
#
# Purpose:	Mark the rollrecs as having been saved.
#
sub rrsaved
{
	#
	# Re-populate and update the table.
	#
# print STDERR "rrsaved:  numrrnames - $numrrnames\n";
	for(my $ind = 0; $ind < $numrrnames; $ind++)
	{
		my $row;				# Cell's row index.
		my $col;				# Cell's column index.

		my $btn;				# Button widget.
		my $name;				# Label of button.

		#
		# Get the column and row indices.
		#
		($col,$row) = ind2cr($ind);

		#
		# Get this button's label.
		#
		$btn = $rrnametab->get($row,$col);
		$name = $btn->cget(-text);
		$name =~ s/^\* //;

		#
		# Skip buttons that haven't been modified.
		#
		next if(!$modified{$name});

		#
		# Modify the the button's label.
		#
		$btn->configure(-text => $name);
	}

	#
	# Reset the modified-keyrec flag.
	#
	$modified = 0;
	%modified = ();

	settitle($title);
}

#---------------------------------------------------------------------------
# Routine:	errval()
#
# Purpose:	Take an error action.
#
sub errval
{
	my $row = shift;				# Errant rollrec's row.
	my $rrn = shift;				# Errant rollrec's name.
	my $msg = shift;				# Error message.

#	print STDERR "errval:  <$rrn>\t$msg\n";

	fieldcolor($row,$rrn,$ERRCOLOR);
}

#---------------------------------------------------------------------------
# Routine:	warnval()
#
# Purpose:	Take a warning action.
#
sub warnval
{
	my $row = shift;				# Errant rollrec's row.
	my $rrn = shift;				# Errant rollrec's name.
	my $msg = shift;				# Warning message.

#	print STDERR "warnval:  <$rrn>\t$msg\n";

	fieldcolor($row,$rrn,$WARNCOLOR);
}

#---------------------------------------------------------------------------
# Routine:	fieldcolor()
#
# Purpose:	Change the text color of a given edit window field.
#
sub fieldcolor
{
	my $row = shift;				# Rollrec's row.
	my $rrn = shift;				# Rollrec's name.
	my $clr = shift;				# New color.

	my $tbl;					# Table to modify.
	my $wij;					# Widget to modify.

	#
	# Get the label widget for the field.
	#
	$tbl = $edittabs{$rrn};
	$wij = $tbl->get($row,$COL_FIELD);

	#
	# Turn the label widget's color.
	#
	$wij->configure(-foreground => $clr);
}

#---------------------------------------------------------------------------
# Routine:	addmsg()
#
# Purpose:	Add a message to one of the edit window's message columns.
#		If the count parameter is zero, then we'll zap the column.
#
sub addmsg
{
	my $rrn = shift;				# Rollrec name.
	my $col = shift;				# Column to modify.
	my $cnt = shift;				# Thing count.
	my $thing = shift;				# Thing.

	my $tbl;					# Table to modify.
	my $lab;					# New label.
	my $str = ' ';					# Error message.

	if($cnt)
	{
		$str = "$cnt $thing";
		$str = $str . 's' if($cnt != 1);
	}

	$tbl = $edittabs{$rrn};
	$lab = $tbl->Label(-text => $str, -anchor => 'w');
	$lab->pack(-fill => 'x', -side => 'top');
	$tbl->put($ROW_MSG,$col,$lab);
}

#---------------------------------------------------------------------------
# Routine:	toggle_ignwarn()
#
# Purpose:	Toggle on /off the "Ignore Edit Warnings" menu command.
#
#		If we're turning off edit warnings, we'll also go through
#		any open edit windows and turn off any "warned" lines.
#		Error lines will remain marked.
#
sub toggle_ignwarn
{
	if($givewarnings)
	{
		$givewarnings = 0;
		$tog_ignwarn->configure(-label => $IGNORE_OFF);

		#
		# Turn off all the warning labels in all the edit windows.
		#
		foreach my $rrn (keys(%edittabs))
		{
			for(my $row = $ROW_FIRST; $row <= $ROW_LAST; $row++)
			{
				my $clr;		# Current row color.
				my $wij;		# Row widget.

				#
				# Skip non-warning rows.
				#
				$wij = $edittabs{$rrn}->get($row,$COL_FIELD);
				$clr = $wij->cget(-foreground);
				next if($clr ne $WARNCOLOR);

				#
				# Change the label color.
				#
				fieldcolor($row,$rrn,$NORMALCOLOR);
			}

			addmsg($rrn,$COL_DATA,0,' ');
		}
	}
	else
	{
		$givewarnings = 1;
		$tog_ignwarn->configure(-label => $IGNORE_ON);
	}
}

#---------------------------------------------------------------------------
# Routine:	toggle_namefilt()
#
# Purpose:	Toggle on /off the "Use Name Filter" menu command.
#
sub toggle_namefilt
{
	if($namefilter)
	{
		$namefilter = 0;
		$tog_namefilt->configure(-label => $FILTER_OFF);
	}
	else
	{
		$namefilter = 1;
		$tog_namefilt->configure(-label => $FILTER_ON);
	}
}

#---------------------------------------------------------------------------
# Routine:	set_btncols()
#
# Purpose:	
#
sub set_btncols
{
	my $err = shift;				# Error flag.

	my $dlg;					# Dialog widget.
	my $lab;					# Label for dialog box.
	my $ent;					# Entry for dialog box.

	my $col;					# New column count.
	my $ret;					# Dialog box return.

	#
	# Create the new dialog box.
	#
	$dlg = $wm->DialogBox(-title	=> 'Set Columns for Button Window',
			      -buttons	=> ["Okay", "Cancel" ]);

	#
	# Add a label to the dialog.
	#
	$lab = $dlg->Label(-text => 'Enter New Column Count:');
	$lab->pack(-side => 'left');

	#
	# Add a text entry slot and focus on the entry.
	#
	$ent = $dlg->add('Entry');
	$ent->pack(-side => 'left');
	$dlg->configure(-focus => $ent);

	#
	# Add a potential error location to the dialog.
	#
	$lab = $dlg->Label(-text => ' ');
	$lab->pack(-side => 'bottom');
	if($err)
	{
		$lab->configure(-text => "Count must be between 1 and $MAXCOLS",
				-foreground => 'red')
	}

	#
	# Show the dialog box and handle cancellations.
	#
	$ret = $dlg->Show();
	return if($ret eq 'Cancel');

	#
	# Get the user's column size.
	#
	$col = $ent->get();

	#
	# If this is an invalid column count, give an error message and
	# ask again.
	#
	set_btncols(1) if(($col < 1) || ($col > $MAXCOLS));

	#
	# Save the new column count and rebuild the table.
	#
	$columncount = $col;
	buildtable(0);
}

#---------------------------------------------------------------------------
# Routine:	editbegone()
#
# Purpose:	Destroy an edit window.  In addition to deleting the window
#		itself, the rollrec is removed from several tables.  These
#		tables are the list of edit windows (%editwinds), and the
#		edit window radio buttons (%editrbs and %editdfs).
#
#		In some cases, this routine will be passed the edit window
#		widget instead of the rollrec name.  If we couldn't find
#		the argument in the edit-window hash, we'll look up the
#		widget itself and recurse with the associated name.
#
sub editbegone
{
	my $rrname = shift;	# Name of rollrec whose editwin we'll destroy.
	my $editwin;		# Edit window to destroy.

# print STDERR "editbegone:  destroying edit window for $rrname\n";

	#
	# Don't do anything for a null name.
	#
	return if($rrname eq '');

	#
	# Deselect the rollrec's button.
	#
	for(my $ind = 0; $ind < $numrrnames; $ind++)
	{
		#
		# Destroy the window and remove it from our edit-window list.
		#
		if($rrnames[$ind] eq $rrname)
		{
			rrname_deselect($ind);
			$editwinds{$rrname}->destroy();
			delete($editwinds{$rrname});
			delete($edittabs{$rrname});

			return;
		}
	}

	#
	# Didn't find the name in the list of rollrec names, so maybe it's
	# a widget itself.  If we find it in the list of edit windows,
	# we'll delete it here.
	#
	foreach my $rrn (keys(%editwinds))
	{
		if($rrn == $rrname)
		{
			$editwinds{$rrname}->destroy();
			delete($editwinds{$rrname});
			delete($edittabs{$rrname});

			return;
		}
	}
}

#---------------------------------------------------------------------------
# Routine:	renamebegone()
#
# Purpose:	Destroy a rename window.  In addition to deleting the window
#		itself, the rollrec is removed from several tables.  These
#		tables are the list of rename windows (%renwinds).
#
#		In some cases, this routine will be passed the rename window
#		widget instead of the rollrec name.  If we couldn't find
#		the argument in the rename-window hash, we'll look up the
#		widget itself and recurse with the associated name.
#
sub renamebegone
{
	my $oldname = shift;				# Old name of rollrec.
	my $newname = shift;				# New name of rollrec.

# print STDERR "renamebegone:  destroying rename window for $oldname\n";

	#
	# Don't do anything for a null name.
	#
	return if($oldname eq '');
	return if($newname eq '');

	#
	# Destroy the old name's rename window and remove it from our
	# rename-window list.
	#
	if(exists($renwinds{$oldname}))
	{
		$renwinds{$oldname}->destroy();
		delete($renwinds{$oldname});
		delete($rentabs{$oldname});
	}

	#
	# Remove the new name from our rename-window list.
	#
	delete($renwinds{$newname});
	delete($rentabs{$newname});
}

#---------------------------------------------------------------------------
# Routine:	helpbegone()
#
# Purpose:	Destroy a help window.
#
sub helpbegone
{
	$helpwin->destroy();
	$inhelpwind = 0;
}

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

#---------------------------------------------------------------------------
# Routine:	errorbox()
#
# Purpose:	Display an error dialog box.
#
sub errorbox
{
	my $msg  = shift;			# Warning message.
	my $dlg;				# Warning dialog widget.

	$dlg = $wm->Dialog(-title => "$NAME Error",
			   -text  => $msg,
			   -default_button => "Okay",
			   -buttons => ["Okay"]);
	$dlg->Show();
}

#---------------------------------------------------------------------------
# Routine:	errorbox_multi()
#
# Purpose:	Display a multiline error dialog box.  Newlines in the message
#		signal a new line (implemented with a new label) in the dialog
#		box.
#
sub errorbox_multi
{
	my $msgs  = shift;			# Messages to display.

	my $dlg;				# Warning dialog widget.
	my $lab;				# Label for table.

	my @lines;				# Lines in message.
	my $line;				# Individual message line.

	$dlg = $wm->DialogBox(-title	=> "$NAME Error",
			      -buttons	=> ["Okay"]);

	@lines = split /\n/, $msgs;

	foreach $line (@lines)
	{
		$lab = $dlg->Label(-text => $line);
		$lab->pack(-side => 'top');
	}

	$dlg->Show();
}

#---------------------------------------------------------------------------
# Routine:	dbx_dups()
#
# Purpose:	Puts up a dialog box with names of the duplicated rollrecs.
#		This is used for the "Merge Rollrecs" command.
#
sub dbx_dups
{
	my @dupnames = @_;			# Duplicated rollrecs.

	my $dbx;				# Dialog box.
	my $rrtab;				# Dup'd rollrec table.
	my $lab;				# Label for table.
	my $rowind = 1;				# Table row index.

	my $rows = @dupnames + 1;		# Rows in table.
	my $sbars  = 'e';			# Scrollbars.
	my $ret;				# Dialog return.

	#
	# Build a dialog box to hold an error summary.
	#
	$dbx = $wm->DialogBox(-title   => 'Duplicated Rollrecs',
			      -buttons => ["Merge", "Cancel" ]);

	#
	# If there's only a few rows, we'll dump the scrollbar.
	#
	$sbars	= '' if($rows < 4);

	#
	# Build a table to hold the duplicated rollrec names.
	#
	$rrtab = $dbx->Table(-rows		=> $rows,
			     -columns		=> 1,
			     -relief		=> 'raised',
			     -scrollbars	=> $sbars,
			     -borderwidth	=> 1,
			     -fixedrows		=> 0,
			     -takefocus		=> 1,
			    );

	#
	# Add a column header.
	#
	$lab = $rrtab->Label(-text => 'Duplicated Rollrecs');
	$rrtab->put(0,0,$lab);

	#
	# Add the selected rollrec names to the table.
	#
	foreach my $rrn (sort {$a <=> $b} @dupnames)
	{
		$lab = $rrtab->Label(-text => $rrn, -anchor => 'w');
		$rrtab->put($rowind,0,$lab);
		$rowind++;
	}

	#
	# Pack the error table and display the dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub help_help
{
	my $hframe;				dialog box.
	#
	$rrtab->pack(-side => 'top');
	$ret = $dbx->Show();

	return($ret);
}

#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display a help window.
#
sub