#!/usr/pkg/bin/perl

# Copyright (C) 2000-2005 Jay Beale
# Licensed under the GNU General Public License    
#
# Tk additions copyright (C) 2000 by Paul L. Allen and licensed
# under the GNU General Public License.
#
# Additional changes, corrections, and feature enhancements are
# (c) Copyright Hewlett-Packard Development Company, L.P. 2001-2003
# and licensed under the GNU General Public License.

# This is a Tk interface for Bastille.  It is derived from
# the Bastille-1.1.1 InteractiveBastille.pl script, with the logic
# warped into the events-and-callbacks programming model typical
# of X programming.  It requires the Tk.pm Perl module and at 
# least the X client runtime libraries.  The new version of the &Ask
# function is called from the callback attached to the Next button
# to display the next question.
#
# Control flow works like this:
#
# User interface implementation modules for Bastille have one
# externally-callable entry point: the do_Bastille function.  The
# purpose of do_Bastille is to initialize the interface, populate
# the Questions database, and start asking questions.
#
# In this Tk implementation, the contents of the config file is
# used to populated the answers in the Questions database, if there
# is a config file.
#
# &initializeGUI creates the main window and all the widgets.
# It then fills them in and returns.  After the Questions database
# is populated, &Ask is called to show the title page.
# The do_Bastille routine then calls Tk's MainLoop routine, which
# never returns.  Thus turning over control of the interface to the
# callback/event handling functions.
#
# Callback functions are attached to the Listbox and to the Back,
# Next, Default Answer and Detail buttons.  The Listbox shows all the
# modules in the database and allows the user to jump to the beginning
# of any module at any time.  (The callback basically looks up the
# index of the first question in the selected module and calls
# &Ask on it.)
#
# This version uses the underlying Bastille modules and questions
# database with no changes.
#

use Tk;
use Tk::LabFrame;
use Tk::DialogBox;
use Tk::HList;

# main window widget which all other widgets are tied
my $mw;

# reference to the Hlist widget where module names are shown
my $list; 
# list modules by index number for use with the HList widget
my @module_index;
# list of indexes by module for use with the HList widget
my %reverse_module_index;
# reference to the check-mark/no check-mark bitmaps for use with the Hlist widget
my ($completeBitmap,$incompleteBitmap); 

# reference to the question label GUI object
my $qe;

# a scrolled text widget where the long and short explanations are viewed
my $tw;

# widgets used to input into the answer_text variable,
#  ae - text-box widget used for user specified input for non-boolean questions
my $ae;
#  ab1,ab2 - Yes, No radio button widgets for boolean questions
my ($ab1,$ab2);
#  spacer - used to push the radio buttons apart.
my $spacer;
# where storage of the current question's answer is stored
my $answer_text;


# button widget for the back button calling the ask routine with the questions proper parent
my $backButton;

# button widget which resets the answer_text value to the Bastille default
my $defaultButton;

# button widget which is used to toggle between detailed levels of the $tw widget
my $db;
# text used in the $db widget to describe detail levels
my %detail_button = ( "low"  =>  "Explain More",
		      "high" => "Explain Less"   );
# initial detail level
my $detail_toggle="high";

# button widget which accepts the current answer text and Asks the current
# questions child
my $okButton; 



sub do_Bastille {
    # Builds the GUI, defining all widgets
    &initializeGUI("Title_Screen");
    # displays the question passed in as the question index, Title_Screen is the first question always.
    &Ask("Title_Screen");
    # Tk Subroutine which never returns and allows callback events to drive program execution
    &MainLoop;
}


sub initializeGUI($) {

    my $index = $_[0];
    my $frameopts  = ['-side' => 'top', '-padx' => 5, '-pady' => 5, '-ipadx' => 5, '-ipady' => 5, '-fill' => 'x'];
    my $eframeopts  = ['-side' => 'top', '-padx' => 5, '-pady' => 5, '-ipadx' => 5, '-ipady' => 5, '-fill' => 'both',
		       '-expand' => 1];
    my $lframeopts  = ['-side' => 'left', '-padx' => 5, '-pady' => 5, '-ipadx' => 5, '-ipady' => 5, '-fill' => 'y'];
    my $hlistopts  = ['-side' => 'left', '-padx' => 0, '-pady' => 0, '-ipadx' => 0, '-ipady' => 0, '-fill' => 'y'];

#
#	The main window
#
    $mw = MainWindow->new();
    $mw->title("Bastille");
    
#	Frames to hold the modules listbox, question, explanation,
#	answer, and buttons.
#
    my $lframe = $mw->LabFrame(
			    '-label' => "Modules",
			    '-labelside' => "acrosstop")->pack(@$lframeopts);
    my $qframe = $mw->LabFrame(
			    '-label' => "Question", 
			    '-labelside' => "acrosstop")->pack(@$frameopts);
    my $eframe = $mw->LabFrame(
			    '-label' => "Explanation", 
			    '-labelside' => "acrosstop")->pack(@$eframeopts);
    my $aframe = $mw->LabFrame(
			    '-label' => "Answer", 
			    '-labelside' => "acrosstop")->pack(@$frameopts);
    my $bframe = $mw->Frame()->pack(
				 @$frameopts);
    # defining module listbox widget attributes

    $list = $lframe->Scrolled( 'HList',
			       '-drawbranch'    => 0,
			       '-scrollbars' => 'e',
			       '-width'         => 25,
			       '-indent'        => 5,
			       '-selectmode'    => 'single')->pack(@$hlistopts);
    $list->bind("<ButtonRelease-1>", \&hlist_callback);
    $list->bind("<ButtonPress-1>", \&hlist_callback);
    
    $list->pack();

    $completeBitmap = $lframe->Bitmap('-file' => &getGlobal('BFILE',"complete.xbm"));
    $incompleteBitmap = $lframe->Bitmap('-file' => &getGlobal('BFILE',"incomplete.xbm"));

#	The question
#
    $qe  = $qframe->Entry('-width' => 80)->pack('-fill' => 'x');

#	A scrolled Text widget for the explanation
#
    $tw = $eframe->Scrolled('Text',
			    '-wrap' => 'word',
			    '-scrollbars' => 'e')->pack('-fill' => 'both','-expand' => 1);

#	The answer.  Note that there are three widgets defined here,
#	but their pack() methods have not been called.  This allows
#	us to show the text entry widget, the two yes/no radio
#	buttons, or neither, depending on where we are in the Questions
#	database.  (A widget can only appear when it has been packed, 
#	and it can be made to disappear by calling its pack method with
#	"forget" as an argument.)
#

    $answer_text = "";
    $ae = $aframe->Entry('-width' => 80,
			 '-textvariable' => \$answer_text);
    $ab1 = $aframe->Radiobutton('-text' => "Yes",
				'-value' => "Y",
				'-variable' => \$answer_text);
    $ab2 = $aframe->Radiobutton('-text' => "No",
				'-value' => "N",
				'-variable' => \$answer_text);
    $spacer = $aframe->Label('-width' => 5);

#	The OK, Back, and Detail buttons
#
    $backButton = $bframe->Button(
	    '-text' => "<- Back",
	    '-command' => \&back_button)->pack(
	    '-expand' => 1,
	    '-side' => 'left');
    $defaultButton = $bframe->Button(
	    '-text' => "Restore Default",
	    '-command' => \&default_button)->pack(
	    '-expand' => 1,
	    '-side' => 'left');
    $detail_toggle = "high";
    $db = $bframe->Button(
	    '-command' => \&expn_button)->pack(
	    '-expand' => 1,
	    '-side' => 'left');

    $okButton = $bframe->Button(
	    '-text' => "OK ->",
	    '-command' => \&OK_button)->pack(
	    '-expand' => 1,
	    '-side' => 'left');

#	Build the list of modules.  The @module_index array
#	translates a module number into the first index for
#	that module in the %Question hash.  This allows us to
#	look up the first question of a module based on a mouse
#	click in the modules listbox.  The %reverse_module_index
#	hash converts a module name into a module number, so we
#	can highlight the module we're currently in.
#
    my $mod = "";
    my $ndx = 0;
    my $nq = $index;
    while ($nq ne "RUN_SCRIPT") {
	if ($mod ne $Question{$nq}{'module'}) {
	    unless ($nq eq "End_Screen") {
		$mod = $Question{$nq}{'module'};
		my $shortMod = $Question{$nq}{'shortModule'};
		
		my ($incompleteModule, $incompleteKey) = &checkQtree($nq);
		
		if($incompleteModule eq $shortMod) {
		    $list->add($ndx, '-itemtype' => 'imagetext',
			       '-image' => $incompleteBitmap,
			       '-text' => $shortMod);
		}
		else {
		    $list->add($ndx, '-itemtype' => 'imagetext',
			       '-image' => $completeBitmap,
			       '-text' => $shortMod);
		}
		$module_index[$ndx]{'index'} = $nq;
		$reverse_module_index{$shortMod} = $ndx;
		$module_index[$ndx]{'done'} = 0;
		$ndx++;
	    }
	}
	$nq = $Question{$nq}{'yes_child'};
    }
    
    # determining if any questions are yet unanswered
    my ($incompleteModule, $incompleteKey) = &checkQtree($first_question);
    # indicate that all questions have been answered with a check-mark
    # on the end screen module header.
    if($incompleteModule ne "") {
	$list->add($ndx, '-itemtype' => 'imagetext',
		   '-image' => $incompleteBitmap,
		   '-text' => "End Screen");
    }
    else {
	$list->add($ndx, '-itemtype' => 'imagetext',
		   '-image' => $completeBitmap,
		   '-text' => "End Screen");
    }
    
    $module_index[$ndx]{'index'} = "End_Screen";
    $reverse_module_index{'End'} = $ndx;
    $module_index[$ndx]{'done'} = 0;
    
}


sub Ask($) {
# sub Ask (index to Question{} record)

##############################################################################
#
# &Ask($question_index);
#
# Given an index from the question hash all of the relevant information about
# that question will be displayed.  This includes module name, question,
# explanation, and radio or text input as appropriate.
#
# REQUIRES: %QUESTION
#           &checkQtree
#
##############################################################################

    $in_epilogue = 0;

    # defined as the current index of the QUESTION hash,
    # this value is always initially "Title_Screen" as called by do_Bastille
    $index=$_[0];
#
#	Skip null records.  
#
#    if (($Question{$index}{"short_exp"} eq "") &&
#	($Question{$index}{"long_exp"} eq "") &&
#	($Question{$index}{"question"} eq "")) {
  #	    print "Skipping null record: $index\n";
  #	    print Dumper($Question{$index}) . "\n";
#	    $index = $Question{$index}{"yes_child"};
#    }


    #   Load question into local variables    
    my $short_exp      =$Question{$index}{"short_exp"};
    my $long_exp       =$Question{$index}{"long_exp"};
    my $question       =$Question{$index}{"question"};
    my $toggle_confirm = $Question{$index}{"toggle_confirm"};
    my $toggle_yn      =$Question{$index}{"toggle_yn"};
    my $yes_epilogue   =$Question{$index}{"yes_epilogue"};
    my $no_epilogue    =$Question{$index}{"no_epilogue"};
    my $yes_child      =$Question{$index}{"yes_child"};
    my $no_child       =$Question{$index}{"no_child"};
    my $proper_parent  =$Question{$index}{"proper_parent"};
    my $answer     =$Question{$index}{"answer"};
    my $module     =$Question{$index}{"module"};

    # Updating HList check box for each module, as it may have changed since this
    # routine was last called.
    for(my $i=0; $i < $#module_index; $i++){
	my $currentKey = $module_index[$i]{'index'};
	my $currentShortMod = $Question{$currentKey}{'shortModule'};
	# checkQtree returns the name of the next incomplete module.
	my ($incompleteModule, $incompleteKey) = &checkQtree($currentKey);
	# if that module is the same as the module of the current index
	if($incompleteModule eq $currentShortMod) {
	    # then the module is incomplete
	    $list->entryconfigure($i, '-image' => $incompleteBitmap);
	}
	else {
	    # otherwise it is marked complete with a check-mark bitmap
	    $list->entryconfigure($i, '-image' => $completeBitmap);

	}
    }
    
    # Button control setup for this question, some buttons don't make since
    # for every question, e.g. you can't go back from the first question.
    # If this is the Title Screen, the question that has no real proper parent
    if($Question{$index}{'proper_parent'} eq $index){
	# gray out the back button
	$backButton->configure('-state' => 'disabled');
    }
    else {
	$backButton->configure('-state' => 'normal');
    }

    # If there is no default answer defined for this question 
    if(! defined $Question{$index}{'default_answer'}){
	# Grey out the button
	$defaultButton->configure('-state' => 'disabled');
    }
    else {
	$defaultButton->configure('-state' => 'normal');
    }

    # If this question only has a short expression then
    if(! defined $Question{$index}{'long_exp'}){
	# gray/disable the long/short toggle button
	$db->configure('-state' => 'disabled');
    }
    else {
	$db->configure('-state' => 'normal');
    }
	

    # reseting HList-Box selection as the module may have changed
    my $modulename = $Question{$index}{'shortModule'};
    $list->selectionClear('0', $reverse_module_index{"End"});
    $list->anchorClear;
    unless ($modulename eq "") {
    	$list->selectionSet($reverse_module_index{$modulename});
    }


    # setting detail button text
    $db->configure('-text' => $detail_button{$detail_toggle});


    # Explanation to display in routines -- we use short explanation if 
    # long is empty, since long explanation is optional
    
    my $explanation;
    if (($detail_toggle eq "high") and ($long_exp)) {
    	$explanation="$long_exp\n";
    } elsif ($short_exp) {
    	$explanation="$short_exp\n";
    } else {
        $explanation="";
    }

    # Now, clear the screen fields and insert the new values

    # show the new explanation
    $tw->configure('-state' => 'normal');
    $tw->delete('0.0', 'end');
    $tw->insert('0.0', $explanation);
    $tw->configure('-state' => 'disabled');

    # show the new question text
    #	Trim the default answer from the end of the question, since
    #	it might differ from the one we found in the config file.
    $question =~ s/\[.*\]$//;
    $qe->configure('-state' => 'normal');
    $qe->delete('0.0', 'end');
    $qe->insert('0.0', $question);
    $qe->configure('-state' => 'disabled');
   
    
    $answer_text = $answer;
    if ($question eq "") {
#
#	If there is no question, don't show any answer widgets.
#
	$ae->pack('forget');
	$ab1->pack('forget');
	$ab2->pack('forget');
    } else {
#
#	Else, show either the Entry or the Radio Buttons.
#
	if ($toggle_yn) {
	    $ae->pack('forget');
	    $spacer ->pack('-side' => 'right');
	    $ab1->pack('-side' => 'right');
	    $ab2->pack('-side' => 'right');
	} else {
	    $ab1->pack('forget');
	    $ab2->pack('forget');
	    $spacer ->pack('forget');
	    $ae->pack();
	}
    }
}

# This is the callback for the OK button
#
sub OK_button {
    # sucking answer_text, global variable, contents into a local answer variable
    my $answer  = $answer_text;
    # unless the current index is in an epilogue
    unless ($in_epilogue) {
	# for yes/no questions
	if ($Question{$index}{'toggle_yn'}) {
	    # if the answer if yes
	    if ($answer =~ /[Yy]/)  {
		# set the QUESTION hash answer to Yes, to be used for question output
		# and if we come back to this question this "answer" will appear in answer_text
		$Question{$index}{'answer'} = "Y";
		# set the GLOBAL_CONFIG hash answer to Yes, to be used in the case of a partial save
		$GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index} = "Y";
		# set the next index to be the yes child
		$next_index = $Question{$index}{'yes_child'};
		# set the current index to be the proper parent of the next index
		# this ensures a smooth traversal back, using the back button, through
		# the questions.
		$Question{$next_index}{'proper_parent'} = $index;
		# if there is an epilogue for the yes answer then show it.
		if ($Question{$index}{'yes_epilogue'} and not $in_epilogue) {
		    $in_epilogue = 1;
		    &show_epilogue ("yes_epilogue");
		    return;
		}
	    } 
	    # if the answer is No 
	    elsif ($answer =~ /[Nn]/)  {
		# set the QUESTION hash answer to NO, to be used for question output
		# and if we come back to this question this "answer" will appear in answer_text
		$Question{$index}{'answer'} = "N";
		# set the GLOBAL_CONFIG hash answer to Yes, to be used in the case of a partial save
		$GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index} = "N";
		# set the next index to be the no child
		$next_index = $Question{$index}{'no_child'};
		
		# If the End Screen is not the current index
		if($index ne "End_Screen") {
		    # set the current index to be the proper parent of the next index
		    # this ensures a smooth traversal back, using the back button, through
		    # the questions.
		    $Question{$next_index}{'proper_parent'} = $index;
		}

		# if there is an epilogue for the yes answer then show it.
		if ($Question{$index}{'no_epilogue'} and not $in_epilogue) {
		    $in_epilogue = 1;
		    &show_epilogue ("no_epilogue");
		    return;
		}
	    } 
	    else {
		$mw->bell();
		return;
	    }
	} 
	# we have a user input answer
	else {
	    # ensure that the user input answer follows the regular expression for
	    # answers to this question as defined in the Question Hash
	    if(&validateAnswer($index,$answer)) {
		# if the answer matched the regular expression then save off the answer
		$Question{$index}{'answer'} = $answer;
		$GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index} = $answer;
		# set the next index to be the yes child, it is required that all user input
		# questions have a yes child, and if you've answered the question then
		# the yes child is the route to go.
		$next_index = $Question{$index}{'yes_child'};
		# set the current index to be the proper parent of the next index
		# this ensures a smooth traversal back, using the back button, through
		# the questions.		
		$Question{$next_index}{'proper_parent'} = $index;
		# if there is an epilogue for the answer then show it.
		if ($Question{$index}{'yes_epilogue'} and not $in_epilogue) {
		    $in_epilogue = 1;
		    &show_epilogue ("yes_epilogue");
		    return;
		}
	    }
	    else {
		# if the answer did not match the regular expression defined for it,
		# then we will save the answer, so it can be modified as it stands
		$Question{$index}{'answer'} = $answer;
		# but we will delete it from the GLOBAL_CONFIG, this ensures that a 
		# module cannot be marked complete until all answers there in match
		# the regular expressions defined for them.
		delete $GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index};
		# show the validation error, showing samples of answer syntax
		&show_validateError;
		# ask the question again
		$next_index = $index;
	    }
	}
    }
    
    # If the user is attempting to exit the program then check to see how to
    # proceed
    if ($next_index eq "RUN_SCRIPT") {
	
	my ($incompleteModule, $incompleteKey) = &checkQtree($first_question);
	# if all questions have been successfully answered
	if($incompleteModule eq "") {
	    # then ask them to save
	    &promptSave;  # save changes made to config?
	    return;
	}
	else {
	    # otherwise the will be asked to partial save or go back and finish.
	    $next_index = &notFinished($incompleteModule,$incompleteKey);
	}
    }

    &Ask($next_index);
}

sub show_epilogue {
    my $field = $_[0];

    $ae->pack('forget');
    $ab1->pack('forget');
    $ab2->pack('forget');

    $qe->configure('-state' => 'normal');
    $tw->configure('-state' => 'normal');
    $qe->delete('0.0', 'end');
    $answer_text = "";
    $tw->delete('0.0', 'end');
    $tw->insert('0.0', $Question{$index}{$field});
    $qe->configure('-state' => 'disabled');
    $tw->configure('-state' => 'disabled');

    $defaultButton->configure('-state' => 'disabled');
    $db->configure('-state' => 'disabled');

}

#	Display the credits and wait for the user to say OK
#
sub finish_up {
    my @tw_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'top');
    my @but_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'left', '-expand' => 1);
    $finishwin = $mw->Toplevel;
    $finishwin->title("Finishing Up");
    $finishwin->Label(
        '-text' => "Configuration file has been saved.\n\nWhat would you like to do now?")->pack(@tw_opts);
    $finishwin->Button(
        '-text' => "Exit Without Changing System",
        '-command' => [sub {$finishwin->destroy; &show_credits; }])->pack(@but_opts);
    $finishwin->Button(
        '-text' => "Go Back and Change Configuration",
        '-command' => [sub {$mw->deiconify; $finishwin->destroy;}])->pack(@but_opts);
   $finishwin->Button(
        '-text' => "Apply Configuration to System",
        '-command' => [sub {$finishwin->destroy; &apply_config;}])->pack(@but_opts);
    $finishwin->grab;
}

###################################################################
#  &notFinished($module,$key);                                    #
#    This subroutine displays a window that tells the user that   #
#    they have not finished answering all questions and will have #
#    to before the back end will run.  Three options are given:    #
#    Finish answering question, Exit without saving, and save and #
#    exit.                                                        #
#                                                                 #
#   REQUIRES:  &Ask($key);                                        #
#   REQUIRES:  &partialSave                                       #
#                                                                 #
###################################################################
sub notFinished($$) {
    my ($module,$key) = @_;
    my @tw_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'top');
    my @but_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'left', '-expand' => 1);

    $unfinishedwin = $mw->Toplevel;
    $unfinishedwin->title("Warning");
    $unfinishedwin->Label(
        '-text' => "You have not answered all of the questions that pertain to your system.\n" . 
		   "In order for Bastille to apply changes you must answer all of the\n" . 
		   "questions that are relevant to your system.\n")->pack(@tw_opts);
    $unfinishedwin->Button(
        '-text' => "Exit Without Saving",
        '-command' => [sub { exit 0;}])->pack(@but_opts);
    $unfinishedwin->Button(
        '-text' => "Go Back and Finish Configuration",
        '-command' => [sub {$mw->deiconify; &Ask($key); $unfinishedwin->destroy;}])->pack(@but_opts);
    $unfinishedwin->Button(
        '-text' => "Save and Exit",
        '-command' => [sub { &partialSave; $unfinishedwin->destroy; exit 0;}])->pack(@but_opts);
    $unfinishedwin->grab;

}


###################################################################
#  &promptSave;                                                   #
#    This subroutine displays a window that prompts the user to   #
#    save the configuration file or exit without saving.          #
#                                                                 #
#   REQUIRES:  &Output_Config_Files;                              #
#                                                                 #
###################################################################
sub promptSave {

    my @tw_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'top');
    my @but_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'left', '-expand' => 1);

    my $promptSaveWin = $mw->Toplevel;
    $mw->withdraw;  # Hide the main window to help avoid confusion between windows
    $promptSaveWin->title("Save Configuration Changes?");
    $promptSaveWin->Label(
        '-text' => "Would you like to save the changes made to your Bastille configuration?\n" .
		   "Saving configuration changes will not apply the configuration to your system.\n" .
		   "If you do not save your configuration now, all changes made during this\n" . 
		   "session will be lost.\n")->pack(@tw_opts);
    $promptSaveWin->Button(
        '-text' => "Exit Without Saving",
        '-command' => [sub { $promptSaveWin->destroy; &show_credits; }])->pack(@but_opts);
    $promptSaveWin->Button(
        '-text' => "Go Back and Change Configuration",
        '-command' => [sub { $mw->deiconify;$promptSaveWin->destroy;}])->pack(@but_opts);
    $promptSaveWin->Button(
        '-text' => "Save Configuration",
        '-command' => [sub { &outputConfig; $promptSaveWin->destroy; &finish_up;}])->pack(@but_opts);
    $promptSaveWin->grab;

}    

sub apply_config {
    if (fork) { #If parent, exec the back end, if child, continue handling Tk interface
       exec &getGlobal('BFILE',"BastilleBackEnd");
    }
#   &Run_Bastille_with_Config; #commented out since now handling differently than Curses interface
                               #(curses and Tk used to share this InteractiveBastille function)
    &show_credits;
}

sub show_credits {
    
    # destroy the main Tk window
    $mw->destroy;
    # create a new window for the credits
    $credwin = MainWindow->new();
    $credwin->title("Credits");
    # read the credits file into a string variable
    open CREDITS, &getGlobal('BFILE',"credits");
    @creditsarray = <CREDITS>;
    close CREDITS;
    my $creditstext = " " . join(" ", @creditsarray);

    # Create a frame on the new window for the credits text
    my $textFrameOpts  = ['-side' => 'top', '-padx' => 5, '-pady' => 5, '-ipadx' => 5, '-ipady' => 5, '-fill' => 'x'];
    my $textFrame = $credwin->LabFrame('-label' => "Contributors", 
				       '-labelside' => "acrosstop")->pack(@$textFrameOpts);
    # create a scrolled text widget for the credits text on the text frame
    $credtxt = $textFrame->Scrolled('Text',
				    '-wrap' => 'word',
				    '-width'=> '80',
				    '-scrollbars' => 'e')->pack('-fill' => 'both','-expand' => 1);
    # add the credit text to the new text widget
    $credtxt->configure('-state' => 'normal');
    $credtxt->delete('0.0', 'end');
    $credtxt->insert('0.0', $creditstext);
    $credtxt->configure('-state' => 'disabled');

    # create a new frame on the credits window for the close button
    my $buttonFrameOpts  = ['-side' => 'top', '-padx' => 5, '-pady' => 5, '-ipadx' => 5, '-ipady' => 5, '-fill' => 'x'];
    my $buttonFrame = $credwin->LabFrame()->pack(@$buttonFrameOpts);
    # create the close button widget
    $buttonFrame->Button(
	    '-text' => "Close",
	    '-command' => [ sub { $credwin->destroy; exit 0; }])->pack(
	    '-expand' => 1,
	    '-side' => 'left');
    # put the newly created window into screen focus.
    $credwin->grab;
}


# This is the callback for the Back button
#
sub back_button {
    my $answer  = $answer_text;
    unless ($in_epilogue) {
	if ($Question{$index}{'answer'} ne $answer){
	    delete $GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index};

	    if ($Question{$index}{'toggle_yn'}) {
		if ($answer =~ /[Yy]/)  {
		    $Question{$index}{'answer'} = "Y";
		} 
	    elsif ($answer =~ /[Nn]/)  {
		$Question{$index}{'answer'} = "N";
	    } 
	    } 
	    else {
		$Question{$index}{'answer'} = $answer;
	    }
	}
    }

    if ($in_epilogue) {
        &Ask($index);
    } 
    else {
        &Ask($Question{$index}->{'proper_parent'});
    }
}

sub default_button {
    my $answer = $Question{$index}{'default_answer'};
    $Question{$index}{'answer'} = $answer;
    delete $GLOBAL_CONFIG{$Question{$index}{'shortModule'}}{$index};
    &Ask($index);
}

# This is the callback for the Details toggle button.
#

sub expn_button {
    my $explanation;

    if ($detail_toggle eq "low") {
        $detail_toggle = "high";
    } else {
        $detail_toggle = "low";
    }
    
    $db->configure('-text' => $detail_button{$detail_toggle});

    if ($in_epilogue) {
        return;
    }

    if (($detail_toggle eq "high") and ($Question{$index}{'long_exp'})) {
        $explanation = $Question{$index}{'long_exp'};
    } elsif ($Question{$index}{'short_exp'}) {
        $explanation = $Question{$index}{'short_exp'};
    } else  {
        $explanation = "";
    }
    
    $tw->configure('-state' => 'normal');
    $tw->delete('0.0', 'end');
    $tw->insert('0.0', $explanation);
    $tw->configure('-state' => 'disabled');
}

# This is the listbox callback
#
sub hlist_callback {
    my ($sel) = $list->info(selection);
    if($sel ne ""){
	$list->selectionClear('0', $reverse_module_index{"End"});
	$list->selectionSet($sel);
	$list->anchorClear;
	&Ask ($module_index[$sel]{'index'});
    } 
}


sub show_validateError {

    my $vRegExp = &getRegExp($index);
    my @tw_opts = ('-padx' => 5, '-pady' => 5, '-side' => 'top');
    my $exampleString = "";
    if(exists $Question{$index}{"expl_ans"} &&  $Question{$index}{"expl_ans"} ne "") {
	my $example = $Question{$index}{"expl_ans"};
	$exampleString = "An example of an acceptable answer is:\n\t$example\n\n";
    }
    elsif(exists $Question{$index}{"default_answer"} && $Question{$index}{"default_answer"} ne "") {
	my $example = $Question{$index}{"default_answer"};
	$exampleString = "An example of an acceptable answer is:\n\t$example\n\n";
    }

    my $vErrorWin = $mw->DialogBox;
    $vErrorWin->title("Input Error:");
    $vErrorWin->Label( '-text' =>
			  "\nThis question requires an answer with a specific\n" .
			  "format.\n\n" . $exampleString  .
		          "See the question explanation for more details.",
		       '-justify' => 'left' )->pack(@tw_opts);
    $vErrorWin->Show();
}


1;












