#!/usr/bin/perl

# proc_files_lin.pl
# Processes files on Linux boxes for the Collective.
# Windows-OS processing is required before the files are moved to the Linux 
# PC (see proc_files_win.pl). This app only handles the Linux processing. 
# The app consists of a GUI (using tk) and processing functions (using perl).

# Various functions use private $statusFlag variables for error tracking; they 
# return a positive number on success and 0 on failure.

# Note: Functions have multiple returns. When a significant error is encountered,
# a function displays an error message and then returns at that point. This makes the
# code simpler, but when reading the code keep in mind that errors can cause early 
# returns from functions.

# For development on Windows instead of Linux, comment out ###Linux lines and
# uncomment ###Windows lines. Warning: ProcessProject() makes several system
# calls to Linux and will not work properly under Windows.

# Version History:
# 4 Nov. 02:
# Moved the call to ChangeOwnAndMode() to occur after RunGlimpse() (instead of
# before CreatePlatDir(), so that files and directories created during processing
# are all changed.

# Contents
# PART 1: The Premilinaries
# PART 1.1: Modules
# PART 1.2: Global Variables
# PART 2: The GUI
# PART 2.1: Widgets (Windows, Boxes, and their Contents)
            # Input Window
            # Output Window
            # Help Window
# PART 2.2: The Main Loop
# PART 2.3: GUI-Management Functions
            # OpenHelpWin()
            # OpenOutWin()
            # ShowError()
            # ShowQuery()
            # ScanWkgDir()
            # CheckStartDirState()
            # LookForSubDirs()
            # AdjustPlatState()
            # AdjustProjState()
# PART 3: Input Error Checking Functions
            # CheckForErrors()
            # CheckStartDir()
            # CheckDirContents()
            # CheckSrcDir()
            # CheckDocDir()
            # CheckPlatDir()
            # CheckProjDir()
            # CheckOwnGrp()
            # CheckProjTitle()
# PART 4: File Processing Functions
            # ProcessProject()
            # CreatePlatDir()
            # CreateProjDir()
            # MoveFiles()
            # RunLxr()
            # RunGlimpse()
            # ChangeOwnAndMode()
# PART 5: Help Data
            # GetHelpContents()


# PART 1: The Premilinaries
# PART 1.1: Modules
use strict;              # Catches errors during code.
use File::Find;          # For file operations.
use File::Path;          # For rmtree().
use Tk;                  # For the GUI.
use Tk::DialogBox;       # Dialog boxes for the GUI.
use Tk::Font;            # Font objects for the GUI.


# PART 1.2: Global Variables
# Global input variables. These hold the user input and important values
# derived from or pertaining to the input.
my $dirSep   = "/";                              # Directory separator. ###Linux
#my $dirSep  = "\\";                              # Directory separator. ###Windows
my $baseDir = "/inetpub/www";                    # Base path. ###Linux
                                                 ###TODO: Someday allow for the other Linux box, /var/www/.
#my $baseDir = "C:\\Astell-THQ";                  # Base path. ###Windows
my $wkgDir  = $baseDir . $dirSep . "wkg";        # Working directory (where dirs from Windows get dumped).
my $refDir  = $baseDir . $dirSep . "reference";  # Path to reference dir.
my $destDir;                           # Full destination path (refDir, platDir, projDir).
my $startDir       = "???";            # Starting project dir (dir to be processed).
my $startDirFlag   = 1;                # Flags whether (1, default) a dir from $wkgDir or (2) a user-input dir is to be used.
my $startDirState  = 1;                # Tracks whether or not to reset input dir items.
my $procDir        = "???";            # = $wkgDir . $dirSep . $startDir OR = $userDir
my $userDir        = "";               # For user to enter a project dir.
my $platDir        = "???";            # Platform or category dir for project.
my $projDir        = "???";            # Destination project dir.
my @foundDirs      = ();               # Dirs found in $wkgDir; possible project dirs.
my $numDirs        = 0;                # Number of dirs found in $wkgDir.
my $haveSrcFlag    = 0;                # Flags whether or not project has a src subdir.
my $haveDocFlag    = 0;                # Flags whether or not project has a doc subdir.
my $haveWarnFlag   = 0;                # Flags whether or not project displays a restricted usage warning.
my $overOld        = 0;                # Overwrite dest dir flag.
my $platDirValue   = "none";           # Holds platform radiobutton values.
my $userPlatDir    = "";               # Holds user-input platform dir.
my $projDirFlag    = -1;               # Flags which "project" dir is to be used in the reference dir.
my $userProjDir    = "";               # Holds user-input project dir.
my $projOwner      = "astell";         # Holds owner name for the files.
my $projGroup      = "inetadmin";      # Holds group name for the files.
my $projOwnGrp     = "";               # = $projOwner . ":" . $projGroup.
my $projTitle      = "???";            # Project title string (used on HTML pages, can't have spaces).
my $useLxrFlag     = 1;                # Flags whether or not source code is to be crossed referenced by lxr.
my $useGlimpseFlag = 1;                # Flags whether or not source code is to be crossed referenced by lxr.

# Predeclared variables (widget references) so that code can use them before they are defined.
my $inWin;
my $inTitleFr;
my $rescanBtn;
my $inBody;
my $inDirFr;
my $dirList;
my $userDirEn;
my $inPlatFr;
my( $platGbc, $platGba, $platGcn, $platPsx, $platPs2, $platXbox, $platPc, $platCpp, $platOther );
my $userPlatEn;
my( $projDefault, $projOther );
my $userProjEn;
my $projTitleEn;
my $showOutWinBn;
my $outWin;
my $outTitleFr;
my $outBody;
my $helpWin;
my $helpTitleFr;
my $helpBody;

# Other global variables.
my $temp;                # Used to refer to widgets when permanent reference is not needed.
my $tempStr = "";        # Temporary string.
my $width;               # Used in sizing windows.
my $height;              # Used in sizing windows.
my $widthEntry;          # Width of an entry box.
my @msgItem;             # Holds text-and-tag entries for error and query messages.
my $text;                # Holds the text to insert into a Text widget.
my $tag;                 # Holds the tag for the text being inserted into a Text widget.
my $scanFlag = 0;        # Flags whether or $wkgDir has been scanned for projects.


# PART 2: The GUI
# App can display these windows:
#   Input window (the Main Window) on left; is called up initially.
#   Output window (a Top Level) on right; is called up initially.
#   Help window (a Top Level) on right; user can call up.
# and these dialog boxes (Tk::DialogBox):
#   Error box; pops up when bad input is detected.
#   Query box; pops up to check that user actually wants to do some odd set of specifications.

# Canvases are used to create visual spacers (highlightthickness is set to 0 
# to turn off "border" outside canvas).

# $_toplevel_->protocol() is used to prevent inadvertent destruction of top level windows
# that are not the main window (e.g., the user clicks the X, which destroys
# the window. The protocol causes the window to be withdrawn instead. Cannot use:
#   $_toplevel_->bind( "", sub { $outWin->withdraw; } );
# as bind() detects the event but does not prevent it (so the window is 
# destroyed despite your best intentions.


# PART 2.1: Widgets (Windows, Boxes, and their Contents)
# Input Window
# Create the main window (the Input Window).
$inWin = new MainWindow;
$inWin->title( "Linux File Processing: Input" );
$inWin->configure( -background => "#EEEEEE" );
$inWin->focusFollowsMouse();

# When app loads, checks that processing will occur using Linux.
$inWin->bind( "", sub 
{
   if ( $^O eq "MSWin32" )
   {
#      return;   ###Windows - Uncomment this line (to abort the WIn32 check) when developing on Windows
      @msgItem = ( @msgItem, "ERROR: Wrong operating system!\n12b" );
      @msgItem = ( @msgItem, 
         "Processing must occur on a Linux box, but your OS says it is $^O.\n\n" .
         "The program will shut down.\n10n" );
      ShowError();
      exit;
   } elsif ( $^O ne "linux" )
   {
      @msgItem = ( @msgItem, "WARNING: Wrong operating system?\n12b" );
      @msgItem = ( @msgItem, 
         "This program was written for Linux, but your OS says it is $^O.\n\n" .
         "If your OS is some form of Unix, things may work OK, but who knows?\n10n" );
      ShowError();
   }
} );

# Ctrl-W closes the app (defined in main window but affects all windows).
# 'all' forces all widgets to recognize event, else widgets with focus
# would ignore this.
$inWin->bind( 'all', "", sub 
{
   $temp = $inWin->focusCurrent();
   if ( $temp->toplevel() == $inWin->toplevel() )
   {
      exit;                 # Ctrl-W was pressed in Input window; exit app.
   } elsif ( $temp->toplevel() == $outWin->toplevel() )
   {
      $outWin->withdraw;    # Ctrl-W was pressed in Output window; withdraw that window.
   } elsif ( $temp->toplevel() == $helpWin->toplevel() )
   {
      $helpWin->withdraw;   # Ctrl-W was pressed in Help window; withdraw that window.
   } else
   {
      return;               # Ctrl-W was pressed elsewhere (dialog box?); ignore.
   }
} );

# Adjust window per screen resolution.
$width  = int( .5 * .9 * $inWin->screenwidth() );
$height = int( .8 * $inWin->screenheight() );
$inWin->geometry( $width . "x" . $height . "+20+15" );

# Base width of entry widgets on screen size.
if ( $inWin->screenwidth() < 800 )
{
   $widthEntry = 30;
} elsif ( $inWin->screenwidth() < 1000 )
{
   $widthEntry = 40;
} else
{
   $widthEntry = 60;
}

# Fonts
my $helv8n = $inWin->Font(
   -family => "helvetica",
   -size   => 8,
   -weight => "normal" );
my $helv8b = $inWin->Font(
   -family => "helvetica",
   -size   => 8,
   -weight => "bold" );
my $helv10n = $inWin->Font(
   -family => "helvetica",
   -size   => 10,
   -weight => "normal" );
my $helv10b = $inWin->Font(
   -family => "helvetica",
   -size   => 10,
   -weight => "bold" );
my $helv10i = $inWin->Font(
   -family => "helvetica",
   -size   => 10,
   -slant  => "italic",
   -weight => "normal" );
my $helv12n = $inWin->Font(
   -family => "helvetica",
   -size   => 12,
   -weight => "normal" );
my $helv12b = $inWin->Font(
   -family => "helvetica",
   -size   => 12,
   -weight => "bold" );
my $helv16b = $inWin->Font(
   -family => "helvetica",
   -size   => 16,
   -weight => "bold" );

my $cour10n = $inWin->Font(
   -family => "courier",
   -size   => 10,
   -weight => "normal" );
my $cour10b = $inWin->Font(
   -family => "courier",
   -size   => 10,
   -weight => "bold" );


# Overall Title
$inWin->Label(
   -anchor     => 'w',
   -background => "#BBBBBB",
   -font       => "$helv16b",
   -text       => "Linux File Processing for the Collective" )
->pack(
   -fill => 'x',
   -side => 'top' );

# Input Title Frame
$inTitleFr = $inWin->Frame( -background  => "#EEEEEE" )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );

# Input Title
$inTitleFr->Label(
   -anchor     => 'w',
   -background => "#EEEEEE", 
   -font       => "$helv12b",
   -text       => "Input" )
->pack( -side => 'left' );

# Help Button
$inTitleFr->Button(
   -activebackground => "#F7DF8F",
   -background       => "#CCBB99",
   -command          => \&OpenHelpWin,
   -font             => "$helv8b",
   -text             => "Help" )
->pack(
   -padx => 10,
   -side => 'right' );

# Visual Separator
$inWin->Canvas(
   -background         => "#888888",
   -height             => 2,
   -highlightthickness => 0,
   -takefocus          => 0 )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );

# Input Area
$inBody = $inWin->Scrolled( "Text",
   -background => "#EEEEEE",
   -cursor     => 'left_ptr',
   -font       => "$helv10n",
   -relief     => 'flat',
   -scrollbars => 'osoe',
   -wrap       => 'word' )
->pack(
   -expand => 1,
   -fill   => 'both',
   -side   => 'top' );

$inBody->tagConfigure( '8n',  -font => "$helv8n" );
$inBody->tagConfigure( '8b',  -font => "$helv8b" );
$inBody->tagConfigure( '10n', -font => "$helv10n" );
$inBody->tagConfigure( '10b', -font => "$helv10b" );
$inBody->tagConfigure( '10i', -font => "$helv10i" );
$inBody->tagConfigure( '12n', -font => "$helv12n" );
$inBody->tagConfigure( '12b', -font => "$helv12b" );


# Input Directory
$inBody->insert( 'end', "Directory to Be Processed:\n", '10b' );

$inDirFr = $inBody->Frame( -background  => "#EEEEEE" )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );
$inBody->windowCreate( 'end', -window => $inDirFr );

# Use a Dir in $wkgDir Radiobutton
$temp = $inDirFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'nw',
   -background       => "#EEEEEE",
   -command          => sub
   {
      ScanWkgDir() if ( not $scanFlag );
      $rescanBtn->configure( -state => 'normal' ) if ( $rescanBtn->cget( -state ) eq "disabled" );
      CheckStartDirState();
   },
   -font             => "$helv10n",
   -justify          => 'left',
   -text             => "Choose a directory in $wkgDir:",
   -selectcolor      => "#FFFF00",
   -variable         => \$startDirFlag,
   -value            => "1" )
->grid(
   -column => 0,
   -row    => 0,
   -sticky => 'ne' );

# Directory Listing
$dirList = $inDirFr->Listbox(
   -background       => "#FFFFFF",
   -font             => "$helv10b",
   -height           => 0,
   -relief           => 'flat',
   -selectbackground => "#F7DF8F",
   -selectforeground => "#000000",
   -selectmode       => "single" )
->grid(
   -column => 1,
   -row    => 0,
   -sticky => 'ne' );
$dirList->insert( 'end', @foundDirs );
$dirList->bind( "", sub
{
   return unless ( $startDirFlag );
   if ( $numDirs == 0 )
   {
      @msgItem = ( @msgItem, "ERROR: No directories!\n12b" );
      @msgItem = ( @msgItem, 
         "No directories were found in $wkgDir.\n10n" );
      ShowError();
      return;
   }
   $startDir = $dirList->get( $dirList->curselection() );
   $procDir = $wkgDir . $dirSep . $startDir;
   CheckStartDir( $procDir );
   $projDir = $startDir if ( $projDirFlag == 1 );
   LookForSubDirs();
} );

# Rescan Button
$rescanBtn = $inDirFr->Button(
   -activebackground => "#F7DF8F",
   -anchor           => 'nw',
   -background       => "#CCBB99",
   -command          => \&ScanWkgDir,
   -font             => "$helv8b",
   -text             => "Rescan" )
->grid(
   -column => 2,
   -row    => 0,
   -sticky => 'ne' );
$rescanBtn->bind( "", sub
{
   if ( not $scanFlag )
   {
      if ( -d $wkgDir )
      {
         ScanWkgDir();
      } else
      {
         $startDirFlag = 0;
         $rescanBtn->configure( -state => 'disabled' );
         $userDirEn->configure( -background => "#FFFFFF", -state => 'normal' );
         $dirList->configure( -background => "#EEEEEE", -takefocus => '0' );
      }
   }
} );

$inBody->insert( 'end', "\n" );

# Use a User-Input Dir Radiobutton
$temp = $inBody->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -command          => sub
   {
      CheckStartDirState();
      $rescanBtn->configure( -state => 'disabled' ) if ( $rescanBtn->cget( -state ) eq "normal" );
      $userDir = "???";
   },
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Enter the full path to a directory: ",
   -variable         => \$startDirFlag,
   -value            => "0" );
$inBody->windowCreate( 'end', -window => $temp );

# User-Input Dir Entry
$userDirEn = $inBody->Entry(
   -background   => "#EEEEEE",
   -font         => "$helv10n",
   -state        => 'disabled',
   -textvariable => \$userDir,
   -width        => 40 );
$inBody->windowCreate( 'end', -window => $userDirEn );
$userDirEn->bind( "", sub
{
   return if ( $userDirEn->cget( -state ) eq 'disabled' );
   $userDirEn->selectionRange( 0, 'end' );
} );
$userDirEn->bind( "", sub
{
   return if ( $userDirEn->cget( -state ) eq 'disabled' );
   $userDir =~ s/\s/_/g;
   $userDir =~ s/\\/\//g;   # Make sure dir uses / separators. ###Linux
   #$userDir =~ s/\//\\/g;   # Make sure dir uses Windows \ separators. ###Windows
   $userDir = "???" if ( $userDir eq "" );
   chop $userDir while ( rindex( $userDir, $dirSep ) == (length( $userDir ) - 1) );
   $procDir = $userDir;
   if ( index( "$userDir", $dirSep ) != -1 )
   {
      $startDir = $projDir = substr( "$userDir", (rindex( "$userDir", "$dirSep" ) + 1) );
   } else
   {
      $startDir = $projDir = $userDir;
   }
   LookForSubDirs();
} );
$inBody->insert( 'end', "\n" );


# Directory Processing Options
$inBody->insert( 'end', "\n", '10n' );
$inBody->insert( 'end', "Processing Options for ", '10b' );
$temp = $inBody->Label(
   -anchor       => 'w',
   -background   => "#EEEEEE",
   -foreground   => "#FF0000",
   -font         => "$helv10b",
   -textvariable => \$procDir );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', ":" );
$inBody->insert( 'end', "\n" );


# Src Subdir Checkbutton
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Project's source code is in a src subdirectory",
   -variable         => \$haveSrcFlag );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

# Doc Subdir Checkbutton
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Project has a doc subdirectory",
   -variable         => \$haveDocFlag );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

# Warning Checkbutton
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Project needs restricted usage warning",
   -variable         => \$haveWarnFlag );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );


# Destination Directory
$tempStr = $refDir . $dirSep. "" . $dirSep . "";
$inBody->insert( 'end', "Move ", '10b' );
$temp = $inBody->Label(
   -anchor       => 'w',
   -background   => "#EEEEEE",
   -foreground   => "#FF0000",
   -font         => "$helv10b",
   -textvariable => \$procDir );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', " to $tempStr:\n", '10b' );

# Overwrite Checkbutton
$inBody->insert( 'end', "   ", '10b' );
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Overwrite  if it already exists",
   -variable         => \$overOld );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

# Destination "Platform" Directory
$inBody->insert( 'end', "   : Platform Directory: ", '10b' );
$temp = $inBody->Label(
   -anchor       => 'w',
   -background   => "#EEEEEE",
   -foreground   => "#FF0000",
   -font         => "$helv10b",
   -textvariable => \$platDir );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

# Platform Frame and Radiobuttons
$inBody->insert( 'end', "      " );
$inPlatFr = $inBody->Frame( -background  => "#EEEEEE" )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );
$inBody->windowCreate( 'end', -window => $inPlatFr );

$platGbc = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Game Boy Color (gbc)",
   -variable         => \$platDirValue,
   -value            => "gbc" )
->grid(
   -column => 1,
   -row    => 1,
   -sticky => 'nw' );

$platGba = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Game Boy Advance (gba)",
   -variable         => \$platDirValue,
   -value            => "gba" )
->grid(
   -column => 1,
   -row    => 2,
   -sticky => 'nw' );

$platGcn = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "GameCube (gcn)",
   -variable         => \$platDirValue,
   -value            => "gcn" )
->grid(
   -column => 1,
   -row    => 3,
   -sticky => 'nw' );

$platPsx = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "PlayStation (psx)",
   -variable         => \$platDirValue,
   -value            => "psx" )
->grid(
   -column => 1,
   -row    => 4,
   -sticky => 'nw' );

$platPs2 = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "PlayStation2 (ps2)",
   -variable         => \$platDirValue,
   -value            => "ps2" )
->grid(
   -column => 2,
   -row    => 1,
   -sticky => 'nw' );

$platXbox = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Xbox (xbox)",
   -variable         => \$platDirValue,
   -value            => "xbox" )
->grid(
   -column => 2,
   -row    => 2,
   -sticky => 'nw' );

$platPc = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "PC/Windows Game (pc)",
   -variable         => \$platDirValue,
   -value            => "pc" )
->grid(
   -column => 2,
   -row    => 3,
   -sticky => 'nw' );

$platCpp = $inPlatFr->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustPlatState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "C/C++ Tool or Library (cpp)",
   -variable         => \$platDirValue,
   -value            => "cpp" )
->grid(
   -column => 2,
   -row    => 4,
   -sticky => 'nw' );

$inBody->insert( 'end', "\n" );

# User-Input Platform Radiobutton
$inBody->insert( 'end', "      " );
$platOther = $inBody->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => sub
   {
      AdjustPlatState();
      $userPlatDir = "???";
   },
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Other: ",
   -variable         => \$platDirValue,
   -value            => "user" );
$inBody->windowCreate( 'end', -window => $platOther );

# User-Input Platform Entry
$userPlatEn = $inBody->Entry(
   -background   => "#EEEEEE",
   -font         => "$helv10n",
   -state        => 'disabled',
   -textvariable => \$userPlatDir,
   -width        => 30 );
$inBody->windowCreate( 'end', -window => $userPlatEn );
$userPlatEn->bind( "", sub
{
   return if ( $userPlatEn->cget( -state ) eq 'disabled' );
   $userPlatEn->selectionRange( 0, 'end' );
} );
$userPlatEn->bind( "", sub
{
   return if ( $userPlatEn->cget( -state ) eq 'disabled' );
   $userPlatDir =~ s/\s/_/g;
   $platDir = $userPlatDir;
} );
$inBody->insert( 'end', "\n" );

# Destination "Project" Directory
$inBody->insert( 'end', "   : Project Directory: ", '10b' );
$temp = $inBody->Label(
   -anchor       => 'w',
   -background   => "#EEEEEE",
   -foreground   => "#FF0000",
   -font         => "$helv10b",
   -textvariable => \$projDir );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

$inBody->insert( 'end', "      " );
$projDefault = $inBody->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => \&AdjustProjState,
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -textvariable     => \$startDir,
   -variable         => \$projDirFlag,
   -value            => "1" );
$inBody->windowCreate( 'end', -window => $projDefault );

$inBody->insert( 'end', "   " );
$projOther = $inBody->Radiobutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'w',
   -background       => "#EEEEEE",
   -command          => sub
   {
      AdjustProjState();
      $userProjDir = "???";
      $projDir = "???";
   },
   -font             => "$helv10n",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Other: ",
   -variable         => \$projDirFlag,
   -value            => "0" );
$inBody->windowCreate( 'end', -window => $projOther );

# User-Input Project Entry
$userProjEn = $inBody->Entry(
   -background   => "#EEEEEE",
   -font         => "$helv10n",
   -state        => 'disabled',
   -textvariable => \$userProjDir,
   -width        => 30 );
$inBody->windowCreate( 'end', -window => $userProjEn );
$userProjEn->bind( "", sub
{
   return if ( $userProjEn->cget( -state ) eq 'disabled' );
   $userProjEn->selectionRange( 0, 'end' );
} );
$userProjEn->bind( "", sub
{
   return if ( $userProjEn->cget( -state ) eq 'disabled' );
   $userProjDir =~ s/\s/_/g;
   $projDir = $userProjDir;
} );
$inBody->insert( 'end', "\n" );
$inBody->insert( 'end', "\n", '10n' );


# Owner and Group for the Files
$inBody->insert( 'end', "   Files' owner: ", '10b' );
$temp = $inBody->Entry(
   -background   => "#FFFFFF",
   -font         => "$helv10n",
   -state        => 'normal',
   -textvariable => \$projOwner,
   -width        => 20 );
$inBody->windowCreate( 'end', -window => $temp );

$inBody->insert( 'end', "   Files' group: ", '10b' );
$temp = $inBody->Entry(
   -background   => "#FFFFFF",
   -font         => "$helv10n",
   -state        => 'normal',
   -textvariable => \$projGroup,
   -width        => 20 );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n", '10n' );


# Project Title
$inBody->insert( 'end', "\n", '10n' );
$inBody->insert( 'end', "Project Title for Web Pages (no spaces!): ", '10b' );

$projTitleEn = $inBody->Entry(
   -background   => "#FFFFFF",
   -font         => "$helv10n",
   -textvariable => \$projTitle,
   -width        => 30 );
$inBody->windowCreate( 'end', -window => $projTitleEn );
$projTitleEn->bind( "", sub
{
   $projTitleEn->selectionRange( 0, 'end' );
} );
$projTitleEn->bind( "", sub
{
   return if ( $projTitleEn->cget( -state ) eq 'disabled' );
   $projTitle =~ s/\s/_/g;
} );
$inBody->insert( 'end', "\n" );
$inBody->insert( 'end', "Example: SomeGame(AGB)\n", '8n' );


# lxr Options
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Use lxr to link up the project",
   -variable         => \$useLxrFlag );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );


# glimpse Options
$temp = $inBody->Checkbutton(
   -activebackground => "#F7DF8F",
   -anchor           => 'e',
   -background       => "#EEEEEE",
   -font             => "$helv10b",
   -justify          => 'left',
   -selectcolor      => "#FFFF00",
   -text             => "Use glimpse to index the project (can be any code)",
   -variable         => \$useGlimpseFlag );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );


# Process Project Button
$temp = $inBody->Button(
   -activebackground => "#F7DF8F",
   -background       => "#CCBB99",
   -command          => sub
   {
      my $statusFlag = 1;
      $outBody->configure( -state => 'normal' );
      $statusFlag = CheckForErrors();
      ProcessProject() if ( $statusFlag );
      $outBody->configure( -state => 'disabled' );
   },
   -font             => "$helv8b",
   -text             => "Process the Project" );
$inBody->windowCreate( 'end', -window => $temp );

# Adds space between buttons
$inBody->insert( 'end', "   " );

# Quit Button
$temp = $inBody->Button(
   -activebackground => "#F7DF8F",
   -background       => "#CCBB99",
   -command          => sub { exit; },
   -font             => "$helv8b",
   -text             => "Quit" );
$inBody->windowCreate( 'end', -window => $temp );
$inBody->insert( 'end', "\n" );

# Output Window Button
$inBody->insert( 'end', "\n", '8n' );
$showOutWinBn = $inBody->Button(
   -activebackground   => "#F7DF8F",
   -background         => "#CCBB99",
   -disabledforeground => "#EEEEEE",
   -command            => \&OpenOutWin,
   -font               => "$helv8b",
   -text               => "Show Ouput Window" )
->pack(
   -padx => 10,
   -side => 'top' );
$inBody->windowCreate( 'end', -window => $showOutWinBn );
$inBody->insert( 'end', "\n" );

# Affects static elements but not inserted interactive widgets.
$inBody->configure( -state => 'disabled' );   # Disables user mucking with non-interactive widgets.


# Output Window
$outWin = $inWin->Toplevel();
$outWin->title( "Linux File Processing: Output" );
$outWin->configure( -background => "#DDDDDD" );

# Prevent inadvertent destruction of the window (withdraw it instead).
$outWin->protocol( 'WM_DELETE_WINDOW', sub 
{ 
   $outWin->withdraw; 
} );

# Adjust window per screen resolution.
$outWin->geometry( $width . "x" . $height . "+" . ( 27 + $width ) . "+15" );

# Output Title Frame
$outTitleFr = $outWin->Frame( -background  => "#DDDDDD" )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );

# Output Title
$outTitleFr->Label(
   -anchor     => 'w',
   -background => "#DDDDDD",
   -font       => "$helv12b",
   -text       => "Output" )
->pack( -side => 'left' );

# Clear Output Button
$outTitleFr->Button(
   -activebackground => "#F7DF8F",
   -background       => "#CCBB99",
   -font             => "$helv8b",
   -command          => sub
   {
      $outBody->configure( -state => 'normal' );
      $outBody->delete( "1.0", 'end' );
      $outBody->configure( -state => 'disabled' );
   },
   -text             => "Clear" )
->pack(
   -padx => 10,
   -side => 'right' );

# Visual Separator
$outWin->Canvas(
   -background         => "#888888",
   -height             => 2,
   -highlightthickness => 0,
   -takefocus          => 0 )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );

# Output Area
$outBody = $outWin->Scrolled( "Text",
   -background => "#DDDDDD",
   -cursor     => 'left_ptr',
   -font       => "$helv10n",
   -relief     => 'flat',
   -scrollbars => 'osoe',
   -wrap       => 'word' )
->pack(
   -expand => 1,
   -fill   => 'both',
   -side   => 'top' );

$outBody->tagConfigure( '10n',  -font => "$helv10n" );
$outBody->tagConfigure( '10b',  -font => "$helv10b" );
$outBody->tagConfigure( '12n',  -font => "$helv12n" );
$outBody->tagConfigure( '12b',  -font => "$helv12b" );
$outBody->tagConfigure( 'c10n', -font => "$cour10n" );
$outBody->tagConfigure( 'c10b', -font => "$cour10b" );

# Screen size warning
if ( $inWin->screenwidth() < 800 )
{
   $outBody->insert( 'end', "Screen Size\n", '12b' );
   $outBody->insert( 'end', "WARNING: ", '10b' );
   $outBody->insert( 'end', "Screen size is small! " .
      "You may have problems using this program at this resolution.\n", '10n' );
}

# Initial Message
$outBody->insert( 'end', "Output messages will appear here.\n" );

# User cannot change output contents.
$outBody->configure( -state => 'disabled' );


# Binding to toggle Show Output Window button (in Input Window) on and off.
# Binding is mapped to a specific widget in $outWin rather than $outWin itself
# (as otherwise the binding is triggered as each child in $outWin is mapped
# and unmapped).
$outBody->bind( "", sub
{
   $showOutWinBn->configure( 
      -activebackground => "#EEEEEE",
      -background       => "#EEEEEE",
      -relief           => 'flat',
      -state            => 'disabled',
      -takefocus        => 0 );
} );

$outBody->bind( "", sub
{
   $showOutWinBn->configure( 
      -activebackground => "#F7DF8F",
      -background       => "#CCBB99",
      -relief           => 'raised',
      -state            => 'normal',
      -takefocus        => 'undef' );
} );


# Help Window
$helpWin = $inWin->Toplevel();
$helpWin->withdraw;
$helpWin->title( "Input Help" );
$helpWin->configure( -background => "#CCCCCC" );

# Prevent inadvertent destruction of the window (withdraw it instead).
$helpWin->protocol( 'WM_DELETE_WINDOW', sub 
{ 
   $helpWin->withdraw; 
} );

# Adjust window per screen resolution.
$helpWin->geometry( $width . "x" . $height . "+" . ( 20 + $width ) . "+35" );

# Help Title Frame
$helpTitleFr = $helpWin->Frame( -background => "#CCCCCC" )
->pack(
   -fill => 'x',
   -pady => 2,
   -side => 'top' );

# Help Title
$helpTitleFr->Label(
   -anchor     => 'w',
   -background => "#CCCCCC",
   -font       => "$helv12b",
   -text       => "Input Help" )
->pack( -side => 'left' );

# Close Button
$helpTitleFr->Button(
   -activebackground => "#F7DF8F",
   -background       => "#CCBB99",
   -command          => sub { $helpWin->withdraw; },
   -font             => "$helv8b",
   -text             => "Close" )
->pack( -side => 'right' );

# Help Area
$helpBody = $helpWin->Scrolled( "Text",
   -background => "#EEEEEE",
   -cursor     => 'left_ptr',
   -font       => "$helv10n",
   -relief     => 'flat',
   -scrollbars => 'osoe',
   -wrap       => 'word' )
->pack(
   -expand => 1,
   -fill   => 'both',
   -side   => 'top' );

$helpBody->tagConfigure( '10n', -font => "$helv10n" );
$helpBody->tagConfigure( '10b', -font => "$helv10b" );
$helpBody->tagConfigure( '12n', -font => "$helv12n" );
$helpBody->tagConfigure( '12b', -font => "$helv12b" );

# Get help contents -- stored as data in Part 5
$helpBody->configure( -state => 'normal' );
$helpBody->delete( "1.0", 'end' );
GetHelpContents();


# Final Bindings
# Place just before MainLoop to bind to all appropriate widgets.
foreach ( (qw(Tk::Button Tk::Checkbutton Tk::Radiobutton Tk::Entry)) )
{
   # Clicking Mouse Button 1 on interactive widgets causes the focus to shift to 
   # that widget (invoke() causes the regular effect of clicking).
   $inWin->bind( $_, "", sub
   {
      $temp = $_[0];                                          # First argument in @_ is the calling widget.
      $temp->invoke() unless ( $temp->class() eq "Entry" );   # Entry widgets do not have invoke() methods.
      $temp->focus();
   } );
}


# PART 2.2: The Main Loop
MainLoop;


# PART 2.3: GUI-Management Functions

# OpenHelpWin()
# Opens the Help window.
sub OpenHelpWin
{
   my $state = $helpWin->state();

   if ( $state eq "normal" )
   {
      # Apparenly user can't see window -- under another?
      $helpBody->focus();
   } elsif ( $state eq "iconic" )
   {
      $helpWin->deiconify();
      $helpBody->focus();
   } elsif ( $state eq "withdrawn" )
   {
      $helpWin->raise();
      $helpWin->deiconify();
      $helpBody->focus();
   } else
   {
      @msgItem = ( @msgItem, "ERROR: Could not find Help window!\n12b" );
      @msgItem = ( @msgItem, 
         "An invalid state was detected for the Help window. The window could not be " .
         "displayed. Something is very wrong.\n10n" );
      ShowError();
   }
}


# OpenOutWin()
# Checks the Output window. If it closed, reopen it.
sub OpenOutWin
{
   my $state = $outWin->state();

   if ( $state eq "normal" )
   {
      # Apparenly user can't see window -- under another?
      my $curFocus = $inWin->focusCurrent();
      $outBody->focus();
   } elsif ( $state eq "iconic" )
   {
      $outWin->deiconify();
      $outBody->focus();
   } elsif ( $state eq "withdrawn" )
   {
      $outWin->raise();
      $outWin->deiconify();
      $outBody->focus();
   } else
   {
      @msgItem = ( @msgItem, "ERROR: Could not find Output window!\n12b" );
      @msgItem = ( @msgItem, 
         "An invalid state was detected for the Output window. The window could not be " .
         "displayed. Something is very wrong.\n10n" );
      ShowError();
   }
}


# ShowError()
# Displays the Error dialog box.
sub ShowError
{
   my $errBox = $inWin->DialogBox(
      -buttons        => [ "OK" ],
      -default_button => "OK",
      -title          => "Error" );

   my $errMsg = $errBox->add( "Text",
      -background => "#FFFFCC",
      -cursor     => 'left_ptr',
      -font       => "$helv10n",
      -relief     => 'flat',
      -wrap       => 'word' )
   ->pack;
   $errMsg->tagConfigure( '10n', -font => "$helv10n" );
   $errMsg->tagConfigure( '10b', -font => "$helv10b" );
   $errMsg->tagConfigure( '12n', -font => "$helv12n" );
   $errMsg->tagConfigure( '12b', -font => "$helv12b" );

   foreach ( @msgItem )
   {
      # Assumes text and tags are present and correct; no error checking done!
      ($text, $tag) = split( //, $_ );
      $errMsg->insert( 'end', $text, $tag );
   }

   # Clear the array for new input.
   @msgItem = ();

   $errBox->Show ( -global );
}


# ShowQuery()
# Displays the Query dialog box.
sub ShowQuery
{
   my $queryBox = $inWin->DialogBox(
      -buttons        => [ "YES", "NO" ],
      -default_button => "Yes",
      -title          => "Query" );

   my $queryMsg = $queryBox->add( "Text",
      -background => "#FFFFCC",
      -cursor     => 'left_ptr',
      -font       => "$helv10n",
      -relief     => 'flat',
      -wrap       => 'word' )
   ->pack;
   $queryMsg->tagConfigure( '10n', -font => "$helv10n" );
   $queryMsg->tagConfigure( '10b', -font => "$helv10b" );
   $queryMsg->tagConfigure( '12n', -font => "$helv12n" );
   $queryMsg->tagConfigure( '12b', -font => "$helv12b" );

   foreach ( @msgItem )
   {
      # Assumes text and tags are present and correct; no error checking done!
      ($text, $tag) = split( //, $_ );
      $queryMsg->insert( 'end', $text, $tag );
   }

   # Clear the array for new input.
   @msgItem = ();

   $queryBox->Show ( -global );
}


# ScanWkgDir()
# Scans $wkgDir for possible input directories
sub ScanWkgDir
{
   my $statusFlag = 1;

   if ( not $statusFlag = chdir( "$wkgDir" ))
   {
      @msgItem = ( @msgItem, "ERROR: Could not find $wkgDir\n10b" );
      @msgItem = ( @msgItem, "Could not find $wkgDir: $!\n" .
         "If this is supposed to exist, find out what is wrong!10n" );
      ShowError();
      return $statusFlag;
   }

   if ( not $statusFlag = opendir( WKG, "$wkgDir" ))
   {
      @msgItem = ( @msgItem, "ERROR: Could not open $wkgDir\n10b" );
      @msgItem = ( @msgItem, "Could not open $wkgDir: $!\n" .
         "The directory does exist; find out what is wrong!10n" );
      ShowError();
      return $statusFlag;
   }

   @foundDirs = ();
   foreach $tempStr ( sort readdir( WKG ))
   {
      next if ( $tempStr eq "." or $tempStr eq ".." );
      @foundDirs = ( @foundDirs, $tempStr ) if ( -d $tempStr );
   }

   if ( not $statusFlag = closedir( WKG ))
   {
      @msgItem = ( @msgItem, "WARNING: Could not close $wkgDir10b" );
      @msgItem = ( @msgItem, "Could not close $wkgDir: $!\n" .
         "This is NOT a fatal error, but you should find out what is wrong.10n" );
      ShowError();
      $statusFlag = 1;   # To make it fatal, change message, change flag to = 0, and add a return $statusFlag.
   }

   $numDirs = @foundDirs;   # Get number of entries in @foundDirs.
   $foundDirs[0] = "NONE!" if ( $numDirs == 0 );
   $scanFlag = 1;

   $dirList->delete( 0, 'end' );
   $dirList->insert( 'end', @foundDirs );

   return $statusFlag;
}


# CheckStartDirState()
# Sets values, etc., depending on whether user is choosing a dir from the list
# or is entering a dir.
sub CheckStartDirState
{
   if ( $startDirState and not $startDirFlag )
   {
      $userDirEn->configure( -background => "#FFFFFF", -state => 'normal' );
      $dirList->configure( -background => "#EEEEEE", -takefocus => '0' );
      $dirList->delete( 0, 'end' );
      $rescanBtn->configure( -state => 'disabled' );
      $startDirState = 0;
      $procDir       = "???";
   } elsif ( not $startDirState and $startDirFlag )
   {
      $userDirEn->configure( -background => "#EEEEEE", -state => 'disabled' );
      $dirList->configure( -background => "#FFFFFF", -takefocus => 'undef' );
      $dirList->insert( 'end', @foundDirs );
      $rescanBtn->configure( -state => 'normal' );
      $startDirState = 1;
      $procDir       = "???";
      $projDir       = "???";
      $startDir      = "???";
      $userDir       = "";
   }
}


# LookForSubDirs()
# Scans $procDir for doc and src subdirs
sub LookForSubDirs
{
   my $testDir = $procDir . $dirSep . "src";

   if ( -d $testDir )
   {
      if ( CheckDirContents( $testDir ))
      {
         $haveSrcFlag = 1;
      } else
      {
         $haveSrcFlag = 0;
      }
   } else
   {
      $haveSrcFlag = 0;
   }

   $testDir = $procDir . $dirSep . "doc";
   if ( -d $testDir )
   {
      if ( CheckDirContents( $testDir ))
      {
         $haveDocFlag = 1;
      } else
      {
         $haveDocFlag = 0;
      }
   } else
   {
      $haveDocFlag = 0;
   }
}


# AdjustPlatState()
# Assigns $procDir and disables/enables the "other platform" entry.
sub AdjustPlatState
{
   if ( $platDirValue ne "user" )
   {
      $userPlatDir = "";
      $userPlatEn->configure( -background => "#EEEEEE", -state => 'disabled' );
      $platDir = $platDirValue;
   } else
   {
      $userPlatEn->configure( -background => "#FFFFFF", -state => 'normal' );
      $platDir = $userPlatDir;
   }
}


# AdjustProjState()
# Assigns $projDir and disables/enables the "other project" entry.
sub AdjustProjState
{
   if ( $projDirFlag == 1 )
   {
      $userProjDir = "";
      $userProjEn->configure( -background => "#EEEEEE", -state => 'disabled' );
      $projDir = $startDir;
   } elsif ( $projDirFlag == 0 )
   {
      $userProjEn->configure( -background => "#FFFFFF", -state => 'normal' );
      $projDir = $userProjDir;
   }
}


# PART 3: Input Error Checking Functions

# CheckForErrors()
# Called when Process the Project button is pressed. Does extensive error checking 
# beofre trying to process files. Structured to end when first error is encountered.
sub CheckForErrors
{
   my $statusFlag = 1;

   $statusFlag = CheckStartDir( $procDir );
   $statusFlag = CheckSrcDir()    if ( $statusFlag and $haveSrcFlag );
   $statusFlag = CheckDocDir()    if ( $statusFlag and $haveDocFlag );
   $statusFlag = CheckPlatDir()   if ( $statusFlag );
   $statusFlag = CheckProjDir()   if ( $statusFlag );
   $statusFlag = CheckOwnGrp()    if ( $statusFlag );
   $statusFlag = CheckProjTitle() if ( $statusFlag );

   if ( not $statusFlag)
   {
      $outBody->insert( 'end', "\n", '8n' );
      $outBody->insert( 'end', "Processing Aborted!\n", '12b' );
      $outBody->insert( 'end', 
         "Errors were encounted before processing could begin. Please correct them and " .
         "try again.\n", '10n' );
   }

   return $statusFlag;
}


# CheckStartDir()
# Checks dir to be processed.
sub CheckStartDir
{
   my $statusFlag = 1;
   my $testDir    = $_[0];

   if ( not $statusFlag = chdir( $testDir ))
   {
      @msgItem = ( @msgItem, "ERROR: Directory cannot be found!\n12b" );
      @msgItem = ( @msgItem, 
         "$testDir does not seem to exist. " .
         "Find out what is wrong.\n10n" );
      ShowError();
      return $statusFlag;
   }
   
   if ( not $statusFlag = CheckDirContents( $testDir ))
   {  
      # Reject if directory is empty.
      @msgItem = ( @msgItem, "ERROR: Directory is empty!\n12b" );
      @msgItem = ( @msgItem, 
         "$testDir does not seem to contain anything. " .
         "If this is supposed to be a project, find out why it is empty.\n10n" );
      ShowError();
      return $statusFlag;
   }

   return $statusFlag;
}


# CheckDirContents()
# Checks if input dir actually has anything in it.
sub CheckDirContents
{
   my $statusFlag = 1;
   my $testDir    = $_[0];
   my $numFiles   = 0;

   finddepth sub 
   {
      $numFiles += 1;
   }, $testDir;

   $statusFlag = 0 if ( $numFiles <= 1 );   # $numFiles = 1 means dir exists but has nothing in it.

   return $statusFlag;
}


# CheckSrcDir()
# Checks if src dir actually exists and has something in it.
sub CheckSrcDir
{
   my $statusFlag = 1;
   my $testDir    = $procDir . $dirSep . "src";

   if ( not $statusFlag = -d $testDir )
   {
      @msgItem = ( @msgItem, "ERROR: src subdirectory cannot be found!\n12b" );
      @msgItem = ( @msgItem, 
         "You said you had a src subdir, but $testDir does not seem to exist. " .
            "Either turn off the \"src subdirectory\" option or "               .
            "find out why src is missing.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag;
   }

   if ( not $statusFlag = CheckDirContents( $testDir ))
   {
      @msgItem = ( @msgItem, "ERROR: src subdirectory is empty!\n12b" );
      @msgItem = ( @msgItem, 
         "You said you had a src subdir, but $testDir is empty. " .
         "Either turn off the \"src subdirectory\" option or "    .
         "find out why src is empty.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag;
   }

   return $statusFlag;
}


# CheckDocDir()
# Checks if doc dir actually exists and has something in it.
sub CheckDocDir
{
   my $statusFlag = 1;
   my $testDir    = $procDir . $dirSep . "doc";

   if ( not $statusFlag = -d $testDir )
   {
      @msgItem = ( @msgItem, "ERROR: doc subdirectory cannot be found!\n12b" );
      @msgItem = ( @msgItem, 
         "You said you had a doc subdir, but $testDir does not seem to exist. " .
            "Either turn off the \"doc subdirectory\" option or "               .
            "find out why doc is missing.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag;
   }

   if ( not $statusFlag = CheckDirContents( $testDir ))
   {
      @msgItem = ( @msgItem, "ERROR: doc subdirectory is empty!\n12b" );
      @msgItem = ( @msgItem, 
         "You said you had a doc subdir, but $testDir is empty. " .
         "Either turn off the \"doc subdirectory\" option or "    .
         "find out why doc is empty.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag;
   }

   return $statusFlag;
}


# CheckPlatDir()
# Checks that $platDir is specified, if user input does not have illegal characters, and does 
# not have other known problems
sub CheckPlatDir
{
   my $statusFlag = 1;
   my $testDir;

   if ( $platDirValue eq "none" )
   {
      @msgItem = ( @msgItem, "ERROR: No platform directory!\n12b" );
      @msgItem = ( @msgItem, 
         "You have not specified any platform directory.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   if ( $platDirValue eq "user" and $platDir =~ /[\*\?":]/ )
   {
      @msgItem = ( @msgItem, "ERROR: Incorrect characters in directory name!\n12b" );
      @msgItem = ( @msgItem, 
         "You specified $platDir for the platform directory, but " .
         "you cannot have : * ? or \" in directory names.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   $testDir = $refDir . $dirSep . $platDir;
   if ( -e $testDir and not -d $testDir )
   {
      @msgItem = ( @msgItem, "ERROR: Directory-file mismatch\n12b" );
      @msgItem = ( @msgItem, "$platDir exists but is a file, not a directory! Find out what is wrong.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   return $statusFlag;
}


# CheckProjDir()
# Checks that $projDir is specified, does not have illegal characters, and is not 
# the same as the dir being processed.
sub CheckProjDir
{
   my $statusFlag = 1;
   my $testDir;

   if ( $projDirFlag == -1 )
   {
      @msgItem = ( @msgItem, "ERROR: You must specify a project directory!\n12b" );
      @msgItem = ( @msgItem, 
         "You have not specified any project directory.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   if ( $projDirFlag == 0 and $projDir =~ /[\*\?":]/ )
   {
      @msgItem = ( @msgItem, "ERROR: Incorrect characters in directory name!\n12b" );
      @msgItem = ( @msgItem, 
         "You specified $projDir for the project directory, but " .
         "you cannot have : * ? or \" in directory names.\n10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   $testDir = $refDir . $dirSep . $platDir . $dirSep . $projDir;

   if ( -e $testDir and not -d $testDir )
   {
      @msgItem = ( @msgItem, "ERROR: Directory-file mismatch!\n12b" );
      @msgItem = ( @msgItem, "$platDir exists but is a file, not a directory! Find out what is wrong.\n", '10n' );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }
   
   if ( not $overOld and -d $testDir )
   {
      @msgItem = ( @msgItem, "ERROR: $projDir already exists!\n12b" );
      @msgItem = ( @msgItem, 
         "You specified not to overwrite $projDir if it already exists. Well, it does.10n" );
      @msgItem = ( @msgItem, "Processing aborted.\n10b" );
      ShowError();
      return $statusFlag = 0;
   }

   if ( $testDir eq $procDir )
   {
      @msgItem = ( @msgItem, "QUERY: Starting and ending directories are the same?\n12b" );
      @msgItem = ( @msgItem, "You said to take $procDir and move it to $testDir, the "      .
         "same directory. This CAN be done: the starting dir will be renamed, any old lxr " .
         "and glimpe files will be trashed, and a new ending directory will be created.\n"  .
         "Is this what you want to do?\n"                                                   .
         "(YES accepts and continues processing; NO aborts processing.)\n10n" );
      $tempStr = ShowQuery();
      return $statusFlag = 0 if ( $tempStr eq "NO" );
   }

   return $statusFlag;
}


# CheckOwnGrp()
# Checks that owner and group names are valid
sub CheckOwnGrp
{
   my $statusFlag = 1;
   my $result;

   # Check $projOwner
   if ( $projOwner eq "root" )
   {
      @msgItem = ( @msgItem, "QUERY: Use \"root\" as owner?\n12b" );
      @msgItem = ( @msgItem, "It usually best not to use root as the owner, " .
         "but you can if you know what you're doing.\n"                       .
         "Is this what you want to do?\n"                                     .
         "(YES accepts and continues processing; NO aborts processing.)\n10n" );
      $tempStr = ShowQuery();
      return $statusFlag = 0 if ( $tempStr eq "NO" );
   }

   my @badOwners = qw( daemon, bin, sys, sync, man, lp, mail, news, uucp, proxy, majordom, 
      post-gres, www-data, backup, msql, operator, list, irc, gnats, alias, nobody );
   foreach ( @badOwners )
   {
      if ( $projOwner eq $_ )
      {
         @msgItem = ( @msgItem, "ERROR: Bad owner name\n12b" );
         @msgItem = ( @msgItem, "You cannot use $projOwner as the owner of " .
            "the project's files. This name is not for general use. Please " .
            "choose a different one.\n10n" );
         ShowError();
         return $statusFlag = 0;
      }
   }

   $result = getpwnam( "$projOwner" );
   if ( not defined( $result ))
   {
      @msgItem = ( @msgItem, "ERROR: Unknown owner name\n12b" );
      @msgItem = ( @msgItem, "$projOwner seems to be an invalid user name. " .
         "Find out what is wrong.\n10n" );
      ShowError();
      return $statusFlag = 0;
   }

   # Check $projGroup
   if ( $projGroup eq "root" )
   {
      @msgItem = ( @msgItem, "QUERY: Use \"root\" as group?\n12b" );
      @msgItem = ( @msgItem, "It usually best not to use root for the group, " .
         "but you can if you know what you're doing.\n"                        .
         "Is this what you want to do?\n"                                      .
         "(YES accepts and continues processing; NO aborts processing.)\n10n" );
      $tempStr = ShowQuery();
      return $statusFlag = 0 if ( $tempStr eq "NO" );
   }

   my @badGroups = qw( daemon, bin, sys, tty, disk, lp, mail, news, uucp, proxy, kmem, dialout, 
      fax, voice, cdrom, floppy, tape, sudo, audio, dip, majordom, post-gres, www-data, backup, 
      msql, operator, list, irc, src, gnats, shadow, nogroup );
   foreach ( @badGroups )
   {
      if ( $projGroup eq $_ )
      {
         @msgItem = ( @msgItem, "ERROR: Bad group name\n12b" );
         @msgItem = ( @msgItem, "You cannot use $projGroup as the group for " .
         "the project's files. This name is not for general use. Please "    .
         "choose a different one.\n10n" );
         ShowError();
         return $statusFlag = 0;
      }
   }

   $result = getgrnam( "$projGroup" );
   if ( not defined( $result ))
   {
      @msgItem = ( @msgItem, "ERROR: Unknown group name\n12b" );
      @msgItem = ( @msgItem, "$projGroup seems to be an invalid group name. " .
         "Find out what is wrong.\n10n" );
      ShowError();
      return $statusFlag = 0;
   }

   return $statusFlag;
}


# CheckProjTitle()
# Checks $projTitle
sub CheckProjTitle
{
   my $statusFlag = 1;

   if ( $projTitle eq "???" or $projTitle eq "" )
   {
      my $tempName = $platDir;
      $tempName =~ tr/a-z/A-Z/;
      $tempName = "C++" if ( $tempName eq "CPP" );
      my $guessTitle = $projDir . "(" . $tempName .")";
      @msgItem = ( @msgItem, "ERROR: Project title not specified!\n12b" );
      @msgItem = ( @msgItem, 
         "The HTML pages need a project title, but \"$projTitle\" is " .
         "not particularly descriptive. How about $guessTitle?\n"      .
         "(YES accepts and continues processing; NO aborts processing.)\n10n" );
      $tempStr = ShowQuery();
      if ( $tempStr eq "YES" )
      {
         $projTitle = $guessTitle;
      } else
      {
         $statusFlag = 0;
      }
   }

   return $statusFlag;
}


# PART 4: File Processing Functions
# Note: These are not GUI-independent. In particular, they format their output for 
# the Output window and the error and query dialog boxes. Also, they assume processing 
# errors put the user back in the Input Window.

# ProcessProject()
# Called by the Process the Project button if the error checking pass finds no significant 
# errors. Calls the various individual processing functions.
sub ProcessProject
{
   my $statusFlag = 1;

   OpenOutWin();
   $outBody->delete( "1.0", 'end' );
   $outBody->insert( 'end', "Processing Begun!\n", '12b' );
   $outBody->insert( 'end', "Note: ", '10b' );
   $outBody->insert( 'end', 
      "Large projects can take several minutes to process. " .
      "Please wait until the results appear.\n", '10n' );
   $outBody->update();   # Displays the above output now, rather than after function finishes.

   $projOwnGrp = $projOwner . ":" . $projGroup;   # 4 Nov. 02: Moved here from ChangeOwnGrp(),
                                                  # as ChangeOwnGrp() now occurs later.
   $statusFlag = CreatePlatDir();
   $statusFlag = CreateProjDir()    if ( $statusFlag );
   $statusFlag = MoveFiles()        if ( $statusFlag );
   $statusFlag = RunLxr()           if ( $statusFlag and $useLxrFlag );
   $statusFlag = RunGlimpse()       if ( $statusFlag and $useGlimpseFlag );
   $statusFlag = ChangeOwnAndMode() if ( $statusFlag );   # 4 Nov. 02: ChangeOwnGrp() now comes last.

   if ( $statusFlag )
   {
      $outBody->insert( 'end', "\n", '8n' );
      $outBody->insert( 'end', "Processing Done!\n", '12b' );
      $outBody->insert( 'end', 
         "You are ready to test the project using a browser and to link the " .
         "project into your site.\n", '1on' );
   }

   return $statusFlag;
}


# CreatePlatDir()
# Tries to create the platform directory if it does not exist.
sub CreatePlatDir
{
   my $statusFlag = 1;
   my $platPath = $refDir . $dirSep . $platDir;

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Platform Directory\n", '12b' );
   $outBody->insert( 'end', "Checking platform directory: ", '10b' );

   if ( not -e $platPath )
   {
      # The dir doesn't exist -- try to make it.
      $statusFlag = mkdir( $platPath, 0775 );   # 4 Nov. 02: mkdir ignores 775/0775? system call below fixes permissions.
      if ( $statusFlag )
      {
         $outBody->insert( 'end', "$platPath has been created.\n", '10n' );
      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', "$platPath does not exist and could not be created: $!\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag = 0;
      }
      system( "chmod 775 $platPath" );           # 4 Nov. 02: Not error checked!
      system( "chown $projOwnGrp $platPath" );   # 4 Nov. 02: Added. Not error checked!

   } elsif ( -d $platPath )
   {
      $outBody->insert( 'end', "$platPath exists!\n", '10n' );
   }

   return $statusFlag;
}


# CreateProjDir()
# Sets up the project directory. Notes: 1) Overwrite error checking already done, so if
# $overOld says overwrite, just do it! 2) Mucking around with the dummyshell dir
# follows the "by hand" procedure to avoid confusing the script maintainer (even though
# the script could be simpler if it had its own procedure).
sub CreateProjDir
{
   my $statusFlag = 1;
   my $result;             # Used to hold return values of system calls that
                           # that do NOT return 1 for success and 0 for failure.

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Project Directory\n", '12b' );
   $outBody->insert( 'end', "Setting up project directory: ", '10b' );

   $destDir = $refDir . $dirSep . $platDir . $dirSep . $projDir;

   if ( $overOld and $procDir eq $destDir )
   {
      # $procDir and $destDir are the same. Since the user had to agree earlier
      # that this is what is wanted, we just rename and clean up $procDir and then 
      # proceed as normal.
      my $ver = 1;
      $tempStr = $procDir . "OLD" . $ver;

      while ( -e $tempStr )
      {
         # Guarantees that $procDir is renamed uniquely. Possibly could have
         # upper limit on number of tries and abort if limit is reached.
         $ver += 1;
         $tempStr = $procDir . "OLD" . $ver;
      }

      $statusFlag = rename( $procDir, $tempStr );
      if ( $statusFlag )
      {
         $outBody->insert( 'end', "Starting and ending directories are the same; so, " .
            "$procDir was renamed as $tempStr.\n", '10n' );
         $procDir = $tempStr;

         # Get rid of old lxr and glimpse stuff
         $statusFlag = opendir( PROCDIR, "$procDir" );
         if ( not $statusFlag )
         {
            $outBody->insert( 'end', "ERROR: ", '10b' );
            $outBody->insert( 'end', 
               "Could not open $procDir (to remove any old lxr and glimpse files): $!\n", '10n' );
            $outBody->insert( 'end', "Processing aborted.\n", '10b' );
            return $statusFlag;
         }

         foreach ( readdir( PROCDIR ))
         {
            if ( /^fileidx$/ or /^xref$/ or /^\.glimpse.+/ )
            {
               $statusFlag = unlink( "$_" );
               if ( not $statusFlag )
               {
                  $outBody->insert( 'end', "ERROR: ", '10b' );
                  $outBody->insert( 'end', 
                     "Could not delete file $_ when cleaning up $procDir: $!\n", '10n' );
                  $outBody->insert( 'end', "Processing aborted.\n", '10b' );
                  return $statusFlag;
               }
            } elsif ( /^bin$/ or /^http$/ )
            {
               $statusFlag = rmtree( "$_", 0, 1 ) if ( -d "$_" );
               if ( not $statusFlag )
               {
                  $outBody->insert( 'end', "ERROR: ", '10b' );
                  $outBody->insert( 'end', 
                     "Could not remove directory $_ when cleaning up $procDir: $!\n", '10n' );
                  $outBody->insert( 'end', "Processing aborted.\n", '10b' );
                  return $statusFlag;
               }
            }
         }

         $statusFlag = closedir( PROCDIR );
         if ( not $statusFlag )
         {
            $outBody->insert( 'end', "WARNING: ", '10b' );
            $outBody->insert( 'end', 
               "Could not close $procDir (after cleaning it up): $!\n" .
               "This may not be a fatal error; processing continues...\n", '10n' );
            $statusFlag = 1;
         }

      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', "Could not rename $procDir: $!\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag;
      }
   }

   if ( $overOld and -d $destDir and not $statusFlag = rmtree( "$destDir", 0, 1 ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end',
         "You said overwrite $destDir if it existed. Well, " .
         "it exists but could not be deleted.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   $tempStr = $refDir . $dirSep . "dummyshell";
   $result = system( "cp -r $tempStr $destDir" );
   if ( $result != 0 )
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not copy $tempStr while setting up $destDir.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   $tempStr = $destDir . $dirSep . "src";
   if ( not $statusFlag = rmtree( "$tempStr", 0, 1 ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not delete dummy $tempStr while setting up $destDir.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   my( $srcHead, $srcTail );
   my $dstHead = $destDir . $dirSep . "http/template-head";
   my $dstTail = $destDir . $dirSep . "http/template-tail";

   if ( $haveDocFlag == 0 and $haveWarnFlag == 0 )
   {
      $srcHead = $srcTail = "";
   } elsif ( $haveDocFlag == 1 and $haveWarnFlag == 0 )
   {
      $srcHead = $destDir . $dirSep . "http/tpl-doc/template-head";
      $srcTail = $destDir . $dirSep . "http/tpl-doc/template-tail";
   } elsif ( $haveDocFlag == 1 and $haveWarnFlag == 1 )
   {
      $srcHead = $destDir . $dirSep . "http/tpl-doc-warn/template-head";
      $srcTail = $destDir . $dirSep . "http/tpl-doc-warn/template-tail";
   } elsif ( $haveDocFlag == 0 and $haveWarnFlag == 1 )
   {
      $srcHead = $destDir . $dirSep . "http/tpl-warn/template-head";
      $srcTail = $destDir . $dirSep . "http/tpl-warn/template-tail";
   }

   # Move the templates if needed.
   $result = system( "mv -f $srcHead $dstHead" ) if ( $srcHead ne "" );
   if ( $result != 0 )
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not set up correct template-head file.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   $result = system( "mv -f $srcTail $dstTail" ) if ( $srcTail ne "" );
   if ( $result != 0 )
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not set up correct template-tail file.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   # Now clean up stuff. This is not error checked because nothing bad happens
   # if it fails.
   $tempStr = $destDir . $dirSep . "http/tpl-doc";
   rmtree( "$tempStr", 0, 1 );
   $tempStr = $destDir . $dirSep . "http/tpl-doc-warn";
   rmtree( "$tempStr", 0, 1 );
   $tempStr = $destDir . $dirSep . "http/tpl-warn";
   rmtree( "$tempStr", 0, 1 );
   $tempStr = $destDir . $dirSep . "http/templates.README";
   unlink( "$tempStr" );

   $outBody->insert( 'end', "$destDir set up.\n", '10' );

   return $statusFlag;
}


# MoveFiles()
# Moves files into destination.
sub MoveFiles
{
   my $statusFlag = 1;
   my $result;             # Used to hold return values of system calls that
                           # that do NOT return 1 for success and 0 for failure.
   my( $src, $dst );

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Moving Files\n", '12b' );
   $outBody->insert( 'end', "Moving files from $procDir to $destDir.\n", '10n' );

   # Move any zip files in $procDir to $destDir.
   if ( not $statusFlag = opendir( PROCDIR, "$procDir" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', "Could not open $procDir: $!\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   foreach ( readdir( PROCDIR ))
   {
      $result = system( "mv -f $_ $destDir" ) if ( /\.zip$/ );
      if ( $result != 0 )
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', 
            "Could not move one or more zip files in $procDir to $destDir.\n", '10n' );
         $outBody->insert( 'end', "WARNING: ", '10b' );
         $outBody->insert( 'end', 
            "Some files may have been moved to $destDir and are no longer in $procDir .\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag = 0;
      }
   }

   $outBody->insert( 'end', "Zip file(s) placed in $destDir\n", '10n' );

   if ( not $statusFlag = closedir( PROCDIR ))
   {
      $outBody->insert( 'end', "WARNING: ", '10b' );
      $outBody->insert( 'end', "Could not close $procDir. " .
         "This is NOT a fatal error, but you should find out what is wrong.\n", '10n' );
      return $statusFlag = 0;
   }

   # Move doc dir if flag says so.
   if ( $haveDocFlag )
   {
      $src = $procDir . $dirSep . "doc";
      $dst = $destDir . $dirSep . "doc";

      $result = system( "mv -f $src $dst" );
      if ( $result == 0 )
      {
         $outBody->insert( 'end', "Moved $src to $dst.\n", '10n' );
      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', "Could not copy $src to $dst.\n", '10n' );
         $outBody->insert( 'end', "WARNING: ", '10b' );
         $outBody->insert( 'end', 
            "Some files may have been moved to $destDir and are no longer in $procDir .\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag = 0;
      }
   }

   # Move source and any remaining files. (Any .zip and doc have already been moved.) 
   # If there is a src subdir in $procDir, then it is moved to become the src subdir of 
   # $destDir, and any other files left in $procDir are moved to $destDir (presumably 
   # these are non-source files and will be handled specially by the user).
   # If there is not a src subdir in $procDir, then all the files in $procDir are assumed 
   # to be source code, and $prodDir itself is moved to become the src subdir of $destDir.
   if ( $haveSrcFlag )
   {
      # First move the src dir.
      $src = $procDir . $dirSep . "src";
      $dst = $destDir . $dirSep . "src";

      $result = system( "mv -f $src $dst" );
      if ( $result == 0 )
      {
         $outBody->insert( 'end', "Moved $src to $dst.\n", '10n' );
      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', 
            "Could not copy $src to $dst.\n", '10n' );
         $outBody->insert( 'end', "WARNING: ", '10b' );
         $outBody->insert( 'end', 
            "Some files may have been moved to $destDir and are no longer in $procDir .\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag = 0;
      }

      # Next move any remaining contents.
      if ( $statusFlag = opendir( PROCDIR, "$procDir" ))
      {
         foreach ( readdir( PROCDIR ))
         {
            next if ( $_ eq "." or $_ eq ".." );
            $result = system( "mv -f $_ $destDir" );
            if ( $result != 0 )
            {
               $outBody->insert( 'end', "ERROR: ", '10b' );
               $outBody->insert( 'end', 
                  "Could not move one or more files in $procDir to $destDir.\n", '10n' );
               $outBody->insert( 'end', "WARNING: ", '10b' );
               $outBody->insert( 'end', 
                  "Some files may have been moved to $destDir and are no longer in $procDir .\n", '10n' );
               $outBody->insert( 'end', "Processing aborted.\n", '10b' );
               return $statusFlag = 0;
            }
         }
         $outBody->insert( 'end', "All files are now in $destDir\n", '10n' );
      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', "Could not open $procDir: $!\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag;
      }

      if ( not $statusFlag = closedir( PROCDIR ))
      {
         $outBody->insert( 'end', "WARNING: ", '10b' );
         $outBody->insert( 'end', "Could not close $procDir. " .
            "This is NOT a fatal error, but you should find out what is wrong.\n", '10n' );
         $statusFlag = 1;   # To make fatal, change msg, make flag = 0, and return.
      }
   } else
   {
      $src = $procDir;
      $dst = $destDir . $dirSep . "src";

      $result = system( "mv -f $src $dst" );
      if ( $result == 0 )
      {
         $outBody->insert( 'end', "Moved $src to $dst.\n", '10n' );
      } else
      {
         $outBody->insert( 'end', "ERROR: ", '10b' );
         $outBody->insert( 'end', 
            "Could not copy $src to $dst.\n", '10n' );
         $outBody->insert( 'end', "WARNING: ", '10b' );
         $outBody->insert( 'end', 
            "Some files may have been moved to $destDir and are no longer in $procDir .\n", '10n' );
         $outBody->insert( 'end', "Processing aborted.\n", '10b' );
         return $statusFlag = 0;
      }
   }

   # Remove $procDir. Should be empty by now, rmdir only works on empty dirs.
   if ( -e $procDir and not $statusFlag = rmdir( "$procDir" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "$procDir has been processed, but it could not be deleted. Possible errors:\n" .
         "1. The dir still contains something. If so, check out what went wrong.\n"     .
         "2. Perl's rmdir() failed for some reason. If so, this may not be a fatal "    .
         "problem, but you should check it out anyway", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   # Rescan wkg dir; $procDir should be gone by this point.
   ScanWkgDir();   

   return $statusFlag;
}


# RunLxr()
# Sets up lxr.conf and then runs genxref on src subdir.
sub RunLxr
{
   my $statusFlag = 1;
   my $result;             # Used to hold return values of system calls that
                           # that do NOT return 1 for success and 0 for failure.
   my $lxrConf  = $destDir . $dirSep . "http" . $dirSep . "lxr.conf";
   my $tempConf = $destDir . $dirSep . "http" . $dirSep . "lxr.conf.temp";

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Using lxr\n", '12b' );

   # Set up lxr.conf
   if ( not $statusFlag = open( CONF_IN, "$lxrConf" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not open $lxrConf to configure lxr.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   if ( not $statusFlag = open( CONF_OUT, ">$tempConf" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not create $tempConf to configure lxr.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   while (  )
   {
      chomp;
      if ( /variable: fmt, Format, xxx/ )
      {
         $tempStr = "variable: fmt, Format, " . $platDir . "\n";
         print CONF_OUT $tempStr;
      } elsif ( /variable: name, Name, xxx/ )
      {
         $tempStr = "variable: name, Name, " . $projDir . "\n";
         print CONF_OUT $tempStr;
      } elsif ( /srcrootname: \"xxx\(xxx\)\"/ )
      {
         $tempStr = "srcrootname: " . $projTitle . "\n";
         print CONF_OUT $tempStr;
      } else
      {
         $tempStr = $_ . "\n";
         print CONF_OUT $tempStr;
      }
   }

   if ( not $statusFlag = close( CONF_OUT ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not close $tempConf while configuring lxr.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   if ( not $statusFlag = close( CONF_IN ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not close $lxrConf while configuring lxr.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   if ( not $statusFlag = unlink( "$lxrConf" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not delete old $lxrConf while configuring lxr. " .
         "(This is necessary to replace it with $tempConf.)\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   if (not $statusFlag = rename( "$tempConf", "$lxrConf" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not rename $tempConf as $lxrConf while configuring lxr.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   $outBody->insert( 'end', "Step 1: lxr configured for the project.\n", '10n' );

   # Run genxref to have lxr cross-reference the code.
   if ( not $statusFlag = chdir( "$destDir" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not find $destDir when trying to run lxr. Something is wrong!.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   $result = system( "bin/genxref src" );
   if ( $result == 0)
   {
      $outBody->insert( 'end', "Step 2: lxr has cross-referenced the source code.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "lxr could not cross-reference the source code. " .
         "(Something went wrong with bin/genxref.)\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   # These aren't error-checked.
   system( "chown $projOwnGrp fileidx" );
   system( "chown $projOwnGrp xref" );

   $result = system( "chmod o+r fileidx" );   # 4 Nov. 02: This is no longer necessary, as 
                                              # ChangeOwnerAndMod() now occurs after this and
                                              # affects this file. Still, it doesn't hurt to keep it.
   if ( $result == 0 )
   {
      $outBody->insert( 'end', "Step 3: lxr's fileidx permissions were changed.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Permissions were not changed for lxr's fileidx! This makes the project unbrowseable by " .
         "most users. Find out what went wrong.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   return $statusFlag;
}


# RunGlimpse()
# Uses glimpseindex to index the src subdir (for free text searches).
sub RunGlimpse
{
   my $statusFlag = 1;
   my $result;             # Used to hold return values of system calls that
                           # that do NOT return 1 for success and 0 for failure.

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Using glimpse\n", '12b' );

   if ( not $statusFlag = chdir( "$destDir" ))
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Could not find $destDir when trying to run glimpseindex. Something is wrong!.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag;
   }

   $result = system( "glimpseindex -H . src" );
   if ( $result == 0 )
   {
      $outBody->insert( 'end', "Step 1: glimpse has indexed the source code.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "glimpse could not index the source code. " .
         "(Something went wrong with glimpseindex?)\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   # This isn't error-checked.
   system( "chown $projOwnGrp .glimpse*" );

   $result = system( "chmod g+r,o+r .glimpse*" );   # 4 Nov. 02: This is no longer necessary, as 
                                                    # ChangeOwnerAndMod() now occurs after this and 
                                                    # affects this file. Still, it doesn't hurt to keep it.
   if ( $result == 0 )
   {
      $outBody->insert( 'end', "Step 2: .glimpse* permissions were changed.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', 
         "Permissions were not changed for .glimpse* files! This makes the project unsearchable by " .
         "most users. Find out what went wrong.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   return $statusFlag;
}


# ChangeOwnAndMode()
# Changes owernship and permissions of the project files.
# Note: Uses system calls to Linux; chown requires script to be run 
# by root (superuser) or whoever owns the files placed on the Linux box.
sub ChangeOwnAndMode
{
   my $statusFlag = 1;
   my $result;             # Used to hold return values of system calls that
                           # that do NOT return 1 for success and 0 for failure.

   $outBody->insert( 'end', "\n", '8n' );
   $outBody->insert( 'end', "Changing Ownership and Permissions\n", '12b' );

   $result = system( "chown -R $projOwnGrp $destDir" );
   if ( $result == 0 )
   {
      $outBody->insert( 'end', "Ownership changed.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', "Ownership was not changed! Find out what went wrong.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   # 4 Nov. 02: Note that the chmod is a system call to Linux so 775 is OK; it 
   # is NOT a call to Perl's own chmod, so 0775 is not necessary (and may be incorrect).
   $result = system( "chmod -R 775 $destDir" ) if ( $statusFlag );
   if ( $result == 0 )
   {
      $outBody->insert( 'end', "Permissions changed.\n", '10n' );
   } else
   {
      $outBody->insert( 'end', "ERROR: ", '10b' );
      $outBody->insert( 'end', "Permissions were not changed! Find out what went wrong.\n", '10n' );
      $outBody->insert( 'end', "Processing aborted.\n", '10b' );
      return $statusFlag = 0;
   }

   return $statusFlag;
}


# PART 5: Help Data
# GetHelpContents()
# This originally was a separate file that was read in, but it was incorporated into the script
# so that only one file needed to be tracked. It is organized to be easily split out again
# should someone's mind change once again.
sub GetHelpContents
{
   my @helpContent;
   $helpContent[0] = 
      "Directory to be Processed\n12b";
   $helpContent[1] = 
      "Choose the directory to be processed by selecting one found in the working directory " .
      "or by entering the full path (starting with /) to one.\n10n";
   $helpContent[2] = 
      "\n10n";
   $helpContent[3] = 
      "Processing Options\n12b";
   $helpContent[4] = 
      "Project's source is in a src subdirectory: 10b";
   $helpContent[5] = 
      "Use this option if the source code for the project is in a src subdirectory directly " .
      "under the directory to be processed. If this option is not used, the program assumes " .
      "the directory to be processed itself contains the source code (and if this is "        .
      "incorrect the project may not be processed correctly).\n10n";
   $helpContent[6] = 
      "\n10n";
   $helpContent[7] = 
      "Project has a doc subdirectory: 10b";
   $helpContent[8] = 
      "Use this option if the directory to be processed has a doc subdirectory (containing project " .
      "documents) directly under the directory to be processed. The directory will be linked up so " .
      "that it is accessible from the HTML pages.\n10n";
   $helpContent[9] = 
      "\n10n";
   $helpContent[10] = 
      "Project needs restricted usage warning: 10b";
   $helpContent[11] = 
      "Use this option if the project comes from an outside developer. This warns users that they can " .
      "examine the code but should check its contractural status before copying verbatum in their own code.\n10n";
   $helpContent[12] = 
      "\n10n";
   $helpContent[13] = 
      "Move to: 10b";
   $helpContent[14] = 
      "The options here specify where to move the directory to processed on the Linux box -- this " .
      "will be in a web-accessible directory named reference//. You can choose "  .
      "pre-set names or enter your own names for  and . No spaces or illegal "    .
      "directory name characters, please (spaces will be converted to underscores).\n10n";
   $helpContent[15] = 
      "Note: 10b";
   $helpContent[16] = 
      "If the directory to be processed is the same as the \"move to\" directory, processing will " .
      "still occur. (The starting directory will be renamed and a new \"move to\" directory will be built).\n10n";
   $helpContent[17] = 
      "\n10n";
   $helpContent[18] = 
      "Overwrite: 10b";
   $helpContent[19] = 
      "The options here specify where to move the directory to processed on the Linux box -- " .
      "this will be in a web-accessible directory named reference//. You "   .
      "can choose pre-set names or enter your own names for  and . No "      .
      "spaces or illegal directory name characters, please (spaces will be converted to underscores).\n10n";
   $helpContent[20] = 
      "\n10n";
   $helpContent[21] = 
      "Project title: 10b";
   $helpContent[22] = 
      "Specify a title for the project; the title will appear on each HTML page. Sugggested format " .
      "is: ProjectName(Platform), such as MyGame(AGB). Spaces are problematic and will be "          .
      "converted to underscores.\n10n";
   $helpContent[23] = 
      "\n10n";
   $helpContent[24] = 
      "Use lxr: 10b";
   $helpContent[25] = 
      "This option specifies that lxr will link up the project, enabling it to be viewed in a browser. " .
      "lxr also will cross-reference C or C++ source code (lxr only recognizes C and C++ code, well, "   .
      "files with extensions of .c, .cpp, .h, and .hpp).\n10n";
   $helpContent[26] = 
      "\n10n";
   $helpContent[27] = 
      "Use glimpse: 10b";
   $helpContent[28] = 
      "This option specifies that glimpse will index the source code (allowing for text and file " .
      "searches). glimpse works with any code, not just C/C++.\n10n";
   $helpContent[29] = 
      "\n10n";

   foreach ( @helpContent )
   {
      ($text, $tag) = split( //, $_ );
      $helpBody->insert( 'end', $text, $tag );
   }
}