Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • dailybuild_linux-x64
  • dailybuild_win32
  • dd_msg_reader_use_dd_msg_area_chooser_and_area_sort_update
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

SlyEdit_DCTStuff.js

Blame
  • SlyEdit_DCTStuff.js 61.46 KiB
    /* This file contains DCTEdit-specific functions for SlyEdit.
     *
     * Author: Eric Oulashin (AKA Nightfox)
     * BBS: Digital Distortion
     * BBS address: digdist.bbsindex.com
     *
     * Date       User              Description
     * 2009-05-11 Eric Oulashin     Started development
     * 2009-08-09 Eric Oulashin     More development & testing
     * 2009-08-22 Eric Oulashin     Version 1.00
     *                              Initial public release
     * ... Removed comments ...
     * 2019-05-04 Eric Oulashin     Updated to use require() instead of load() if possible.
     * 2021-12-11 Eric Oulashin     Updated the quote window bottom border text
     * 2022-11-19 Eric Oulashin     Updated readColorConfig() to handle just attribute characters
     * 2023-05-15 Eric Oulashin     Refactored readColorConfig()
     */
    
    "use strict";
    
    if (typeof(require) === "function")
    {
    	require("sbbsdefs.js", "K_NOCRLF");
    	require(js.exec_dir + "SlyEdit_Misc.js", "CTRL_A");
    }
    else
    {
    	load("sbbsdefs.js");
    	load(js.exec_dir + "SlyEdit_Misc.js");
    }
    
    // DCTEdit menu item return values
    var DCTMENU_FILE_SAVE = 0;
    var DCTMENU_FILE_ABORT = 1;
    var DCTMENU_FILE_EDIT = 2;
    var DCTMENU_EDIT_INSERT_TOGGLE = 3;
    var DCTMENU_EDIT_FIND_TEXT = 4;
    var DCTMENU_EDIT_SPELL_CHECKER = 5;
    var DCTMENU_EDIT_SETTINGS = 6;
    var DCTMENU_SYSOP_IMPORT_FILE = 7;
    var DCTMENU_SYSOP_EXPORT_FILE = 11;
    var DCTMENU_HELP_COMMAND_LIST = 8;
    var DCTMENU_HELP_GENERAL = 9;
    var DCTMENU_HELP_PROGRAM_INFO = 10;
    var DCTMENU_CROSS_POST = 12;
    var DCTMENU_LIST_TXT_REPLACEMENTS = 13;
    
    // Read the color configuration file
    readColorConfig(gConfigSettings.DCTColors.ThemeFilename);
    
    ///////////////////////////////////////////////////////////////////////////////////
    // Functions
    
    // This function reads the color configuration for DCT style.
    //
    // Parameters:
    //  pFilename: The name of the color configuration file
    function readColorConfig(pFilename)
    {
    	var themeFile = new File(pFilename);
    	if (themeFile.open("r"))
    	{
    		var colorSettingsObj = themeFile.iniGetObject();
    		themeFile.close();
    
    		// DCT-specific colors
    		for (var prop in gConfigSettings.DCTColors)
    		{
    			if (colorSettingsObj.hasOwnProperty(prop))
    			{
    				// Using toString() to ensure the color attributes are strings (in case the value is just a number)
    				var value = colorSettingsObj[prop].toString();
    				// Remove any instances of specifying the control character
    				value = value.replace(/\\[xX]01/g, "").replace(/\\[xX]1/g, "").replace(/\\1/g, "");
    				// Add actual control characters in the color setting
    				gConfigSettings.DCTColors[prop] = attrCodeStr(value);
    			}
    		}
    		// General colors
    		for (var prop in gConfigSettings.genColors)
    		{
    			if (colorSettingsObj.hasOwnProperty(prop))
    			{
    				var value = colorSettingsObj[prop].toString();
    				// Remove any instances of specifying the control character
    				value = value.replace(/\\[xX]01/g, "").replace(/\\[xX]1/g, "").replace(/\\1/g, "");
    				// Add actual control characters in the color setting
    				gConfigSettings.genColors[prop] = attrCodeStr(value);
    			}
    		}
    	}
    }
    
    // Sets up any global screen-related variables needed for DCT style
    function globalScreenVarsSetup_DCTStyle()
    {
    	gSubjPos.x = 12;
    	gSubjPos.y = 4;
    	gSubjScreenLen = console.screen_columns - 15;
    }
    
    // Re-draws the screen, in the style of DCTEdit.
    //
    // Parameters:
    //  pEditLeft: The leftmost column of the edit area
    //  pEditRight: The rightmost column of the edit area
    //  pEditTop: The topmost row of the edit area
    //  pEditBottom: The bottommost row of the edit area
    //  pEditColor: The edit color
    //  pInsertMode: The insert mode ("INS" or "OVR")
    //  pUseQuotes: Whether or not message quoting is enabled
    //  pEditLinesIndex: The index of the message line at the top of the edit area
    //  pDisplayEditLines: The function that displays the edit lines
    function redrawScreen_DCTStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEditColor,
                                   pInsertMode, pUseQuotes, pEditLinesIndex, pDisplayEditLines)
    {
    	// Top header
    	// Generate & display the top border line (Note: Generate this
    	// border only once, for efficiency).
    	if (typeof(redrawScreen_DCTStyle.topBorder) == "undefined")
    	{
    		var innerWidth = console.screen_columns - 2;
    		redrawScreen_DCTStyle.topBorder = UPPER_LEFT_SINGLE;
    		for (var i = 0; i < innerWidth; ++i)
    			redrawScreen_DCTStyle.topBorder += HORIZONTAL_SINGLE;
    		redrawScreen_DCTStyle.topBorder += UPPER_RIGHT_SINGLE;
    		redrawScreen_DCTStyle.topBorder = randomTwoColorString(redrawScreen_DCTStyle.topBorder,
    		                                                       gConfigSettings.DCTColors.TopBorderColor1,
    		                                                       gConfigSettings.DCTColors.TopBorderColor2);
    	}
    	// Print the border line on the screen
    	console.clear();
    	console.print(redrawScreen_DCTStyle.topBorder);
    
    	// Next line
    	// From name
    	var lineNum = 2;
    	console.gotoxy(1, lineNum);
    	// Calculate the width of the from name field: 28 characters, based
    	// on an 80-column screen width.
    	var fieldWidth = (console.screen_columns * (28/80)).toFixed(0);
    	var screenText = gFromName.substr(0, fieldWidth);
    	console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.DCTColors.TopBorderColor1,
    	                                   gConfigSettings.DCTColors.TopBorderColor2) +
    				  " " + gConfigSettings.DCTColors.TopLabelColor + "From " +
    				  gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    				  gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    				  gConfigSettings.DCTColors.TopFromFillColor + DOT_CHAR + 
    				  gConfigSettings.DCTColors.TopFromColor + screenText +
    				  gConfigSettings.DCTColors.TopFromFillColor);
    	fieldWidth -= (screenText.length+1);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    	console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "]");
    
    	// Message area
    	fieldWidth = (console.screen_columns * (27/80)).toFixed(0);
    	screenText = gMsgArea.substr(0, fieldWidth);
    	var startX = console.screen_columns - fieldWidth - 9;
    	console.gotoxy(startX, lineNum);
    	console.print(gConfigSettings.DCTColors.TopLabelColor + "Area" +
    	              gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    	              gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    	              gConfigSettings.DCTColors.TopAreaFillColor + DOT_CHAR +
    	              gConfigSettings.DCTColors.TopAreaColor + screenText +
    	              gConfigSettings.DCTColors.TopAreaFillColor);
    	fieldWidth -= (screenText.length+1);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    	console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " +
    	              randomTwoColorString(VERTICAL_SINGLE,
    	                                   gConfigSettings.DCTColors.TopBorderColor1,
    	                                   gConfigSettings.DCTColors.TopBorderColor2));
    
    	// Next line: To, Time, time Left
    	++lineNum;
    	console.gotoxy(1, lineNum);
    	// To name
    	fieldWidth = (console.screen_columns * (28/80)).toFixed(0);
    	screenText = gToName.substr(0, fieldWidth);
    	console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.DCTColors.TopBorderColor1,
    	                                   gConfigSettings.DCTColors.TopBorderColor2) +
    				  " " + gConfigSettings.DCTColors.TopLabelColor + "To   " +
    				  gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    				  gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    				  gConfigSettings.DCTColors.TopToFillColor + DOT_CHAR +
    				  gConfigSettings.DCTColors.TopToColor + screenText +
    				  gConfigSettings.DCTColors.TopToFillColor);
    	fieldWidth -= (screenText.length+1);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    	console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "]");
    
    	// Current time
    	console.gotoxy(startX, lineNum);
    	console.print(gConfigSettings.DCTColors.TopLabelColor + "Time" +
    	              gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    	              gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    	              gConfigSettings.DCTColors.TopTimeFillColor + DOT_CHAR);
    	displayTime_DCTStyle();
    	console.print(gConfigSettings.DCTColors.TopTimeFillColor + DOT_CHAR +
    	              gConfigSettings.DCTColors.TopInfoBracketColor + "]");
    
    	// Time left
    	fieldWidth = (console.screen_columns * (7/80)).toFixed(0);
    	startX = console.screen_columns - fieldWidth - 9;
    	console.gotoxy(startX, lineNum);
    	var timeStr = Math.floor(bbs.time_left / 60).toString().substr(0, fieldWidth);
    	console.print(gConfigSettings.DCTColors.TopLabelColor + "Left" +
    	              gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    	              gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    	              gConfigSettings.DCTColors.TopTimeLeftFillColor + DOT_CHAR +
    	              gConfigSettings.DCTColors.TopTimeLeftColor + timeStr +
    	              gConfigSettings.DCTColors.TopTimeLeftFillColor);
    	fieldWidth -= (timeStr.length+1);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    	console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " +
    	              randomTwoColorString(VERTICAL_SINGLE,
    	              gConfigSettings.DCTColors.TopBorderColor1,
    	              gConfigSettings.DCTColors.TopBorderColor2));
    
    	// Line 3: Subject
    	++lineNum;
    	console.gotoxy(1, lineNum);
    	//fieldWidth = (console.screen_columns * (66/80)).toFixed(0);
    	fieldWidth = +(console.screen_columns - 15);
    	screenText = gMsgSubj.substr(0, fieldWidth);
    	console.print(randomTwoColorString(LOWER_LEFT_SINGLE, gConfigSettings.DCTColors.TopBorderColor1,
    	                                   gConfigSettings.DCTColors.TopBorderColor1) +
    	                                   " " + gConfigSettings.DCTColors.TopLabelColor + "Subj " +
    	                                   gConfigSettings.DCTColors.TopLabelColonColor + ": " +
    	                                   gConfigSettings.DCTColors.TopInfoBracketColor + "[" +
    	                                   gConfigSettings.DCTColors.TopSubjFillColor + DOT_CHAR +
    	                                   gConfigSettings.DCTColors.TopSubjColor + screenText +
    	                                   gConfigSettings.DCTColors.TopSubjFillColor);
    	fieldWidth -= (screenText.length);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    	console.print(DOT_CHAR);
    	console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " +
    	              randomTwoColorString(LOWER_RIGHT_SINGLE,
    	              gConfigSettings.DCTColors.TopBorderColor1,
    	              gConfigSettings.DCTColors.TopBorderColor2));
    	
    	// Line 4: Top border for message area
    	++lineNum;
    	displayTextAreaTopBorder_DCTStyle(lineNum, pEditLeft, pEditRight);
    
    	// Display the bottom message area border and help line
    	DisplayTextAreaBottomBorder_DCTStyle(pEditBottom+1, null, pEditLeft, pEditRight, pInsertMode);
    	DisplayBottomHelpLine_DCTStyle(console.screen_rows, pUseQuotes);
    	
    	// Go to the start of the edit area
    	console.gotoxy(pEditLeft, pEditTop);
    
    	// Write the message text that has been entered thus far.
    	pDisplayEditLines(pEditTop, pEditLinesIndex);
    	console.print(pEditColor);
    }
    
    function refreshSubjectOnScreen_DCTStyle(pX, pY, pLength, pText)
    {
    	console.print("\x01n" + gConfigSettings.DCTColors.TopSubjColor);
    	console.gotoxy(pX, pY);
    	//printf("%-" + pLength + "s", pText.substr(0, pLength));
    	// Ensure the text is no longer than the field width
    	var subj = pText.substr(0, pLength);
    	console.print(subj);
    	console.print("\x01n" + gConfigSettings.DCTColors.TopSubjFillColor);
    	var fieldWidth = pLength - subj.length;
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    }
    
    // Displays the top border of the message area, in the style of DCTEdit.
    //
    // Parameters:
    //  pLineNum: The line number on the screen at which to draw the border
    //  pEditLeft: The leftmost edit area column on the screen
    //  pEditRight: The rightmost edit area column on the screen
    function displayTextAreaTopBorder_DCTStyle(pLineNum, pEditLeft, pEditRight)
    {
    	// The border will use random bright/normal colors.  The colors
    	// should stay the same each time we draw it, so a "static"
    	// variable is used for the border text.  If that variable has
    	// not been defined yet, then build it.
    	if (typeof(displayTextAreaTopBorder_DCTStyle.border) == "undefined")
    	{
    		var numHorizontalChars = pEditRight - pEditLeft - 1;
    
    		displayTextAreaTopBorder_DCTStyle.border = UPPER_LEFT_SINGLE;
    		for (var i = 0; i < numHorizontalChars; ++i)
    			displayTextAreaTopBorder_DCTStyle.border += HORIZONTAL_SINGLE;
    		displayTextAreaTopBorder_DCTStyle.border += UPPER_RIGHT_SINGLE;
    		displayTextAreaTopBorder_DCTStyle.border =
    		randomTwoColorString(displayTextAreaTopBorder_DCTStyle.border,
    		gConfigSettings.DCTColors.EditAreaBorderColor1,
    		gConfigSettings.DCTColors.EditAreaBorderColor2);
    	}
    
    	// Draw the line on the screen
    	//console.gotoxy((console.screen_columns >= 82 ? pEditLeft-1 : pEditLeft), pLineNum);
    	console.gotoxy(pEditLeft, pLineNum);
    	console.print(displayTextAreaTopBorder_DCTStyle.border);
    }
    
    // Displays the bottom border of the message area, in the style of DCTEdit.
    //
    // Parameters:
    //  pLineNum: The line number on the screen at which to draw the border
    //  pUseQuotes: This is not used; this is only here so that the signatures of
    //              the IceEdit and DCTEdit versions match.
    //  pEditLeft: The leftmost edit area column on the screen
    //  pEditRight: The rightmost edit area column on the screen
    //  pInsertMode: The insert mode ("INS" or "OVR")
    //  pCanChgMsgColor: Whether or not changing the text color is allowed
    function DisplayTextAreaBottomBorder_DCTStyle(pLineNum, pUseQuotes, pEditLeft, pEditRight,
                                                   pInsertMode, pCanChgMsgColor)
    {
    	// The border will use random bright/normal colors.  The colors
    	// should stay the same each time we draw it, so a "static"
    	// variable is used for the border text.  If that variable has
    	// not been defined yet, then build it.
    	if (typeof(DisplayTextAreaBottomBorder_DCTStyle.border) == "undefined")
    	{
    		var innerWidth = pEditRight - pEditLeft - 1;
    
    		DisplayTextAreaBottomBorder_DCTStyle.border = LOWER_LEFT_SINGLE;
    
    		// This loop uses innerWidth-6 to make way for the insert mode
    		// text.
    		for (var i = 0; i < innerWidth-6; ++i)
    			DisplayTextAreaBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE;
    		DisplayTextAreaBottomBorder_DCTStyle.border =
    		randomTwoColorString(DisplayTextAreaBottomBorder_DCTStyle.border,
    		gConfigSettings.DCTColors.EditAreaBorderColor1,
    		gConfigSettings.DCTColors.EditAreaBorderColor2);
    		// Insert mode
    		DisplayTextAreaBottomBorder_DCTStyle.border += gConfigSettings.DCTColors.EditModeBrackets
    		                                            + "[" + gConfigSettings.DCTColors.EditMode
    		                                            + pInsertMode
    		                                            + gConfigSettings.DCTColors.EditModeBrackets
    		                                            + "]";
    		// The last 2 border characters
    		DisplayTextAreaBottomBorder_DCTStyle.border +=
    		randomTwoColorString(HORIZONTAL_SINGLE + LOWER_RIGHT_SINGLE,
    		gConfigSettings.DCTColors.EditAreaBorderColor1,
    		gConfigSettings.DCTColors.EditAreaBorderColor2);
    	}
    
    	// Draw the border line on the screen.
    	//console.gotoxy((console.screen_columns >= 82 ? pEditLeft-1 : pEditLeft), pLineNum);
    	console.gotoxy(pEditLeft, pLineNum);
    	console.print(DisplayTextAreaBottomBorder_DCTStyle.border);
    }
    
    // Displays the help line at the bottom of the screen, in the style
    // of DCTEdit.
    //
    // Parameters:
    //  pLineNum: The line number on the screen at which to draw the help line
    //  pUsingQuotes: Boolean - Whether or not message quoting is enabled.
    function DisplayBottomHelpLine_DCTStyle(pLineNum, pUsingQuotes)
    {
    	// For efficiency, define the help line variable only once.
    	if (typeof(DisplayBottomHelpLine_DCTStyle.helpText) == "undefined")
    	{
    		DisplayBottomHelpLine_DCTStyle.helpText = gConfigSettings.DCTColors.BottomHelpBrackets
    		                                        + "[" + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL"
    		                                        + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR
    		                                        + gConfigSettings.DCTColors.BottomHelpKeys + "Z"
    		                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "]\x01n "
    		                                        + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Save\x01n      "
    		                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "["
    		                                        + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL"
    		                                        + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR
    		                                        + gConfigSettings.DCTColors.BottomHelpKeys + "A"
    		                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "]\x01n "
    		                                        + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Abort";
    		// If we can allow message quoting, then add a text to show Ctrl-Q for
    		// quoting.
    		if (pUsingQuotes)
    		{
    			DisplayBottomHelpLine_DCTStyle.helpText += "\x01n      "
    			                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "["
    			                                        + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL"
    			                                        + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR
    			                                        + gConfigSettings.DCTColors.BottomHelpKeys + "Q"
    			                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "]\x01n "
    			                                        + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Quote";
    		}
    		DisplayBottomHelpLine_DCTStyle.helpText += "\x01n      "
    		                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "["
    		                                        + gConfigSettings.DCTColors.BottomHelpKeys + "ESC"
    		                                        + gConfigSettings.DCTColors.BottomHelpBrackets + "]\x01n "
    		                                        + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Menu";
    		// Center the text by padding it in the front with spaces.  This is done instead
    		// of using console.center() because console.center() will output a newline,
    		// which would not be good on the last line of the screen.
    		var numSpaces = (console.screen_columns/2).toFixed(0) - (strip_ctrl(DisplayBottomHelpLine_DCTStyle.helpText).length/2).toFixed(0);
    		for (var i = 0; i < numSpaces; ++i)
    			DisplayBottomHelpLine_DCTStyle.helpText = " " + DisplayBottomHelpLine_DCTStyle.helpText;
    	}
    
    	// Display the help line on the screen
    	var lineNum = console.screen_rows;
    	if ((typeof(pLineNum) != "undefined") && (pLineNum != null))
    		lineNum = pLineNum;
    	console.gotoxy(1, lineNum);
    	console.print(DisplayBottomHelpLine_DCTStyle.helpText);
    	console.print("\x01n");
    	console.cleartoeol();
    }
    
    // Updates the insert mode displayd on the screen, for DCT Edit style.
    //
    // Parameters:
    //  pEditRight: The rightmost column on the screen for the edit area
    //  pEditBottom: The bottommost row on the screen for the edit area
    //  pInsertMode: The insert mode ("INS" or "OVR")
    function updateInsertModeOnScreen_DCTStyle(pEditRight, pEditBottom, pInsertMode)
    {
       // If the number of columns on the screen is more than 80, the horizontal
       // position will be 1 more to the right due to the added vertical lines
       // around the edit area.
       if (console.screen_columns > 80)
          console.gotoxy(pEditRight-5, pEditBottom+1);
       else
          console.gotoxy(pEditRight-6, pEditBottom+1);
    	console.print(gConfigSettings.DCTColors.EditModeBrackets + "[" +
    	              gConfigSettings.DCTColors.EditMode + pInsertMode +
    	              gConfigSettings.DCTColors.EditModeBrackets + "]");
    }
    
    // Draws the top border of the quote window, DCT Edit style.
    //
    // Parameters:
    //  pQuoteWinHeight: The height of the quote window
    //  pEditLeft: The leftmost column on the screen for the edit window
    //  pEditRight: The rightmost column on the screen for the edit window
    function DrawQuoteWindowTopBorder_DCTStyle(pQuoteWinHeight, pEditLeft, pEditRight)
    {
       // Generate the top border vairable only once.
       if (typeof(DrawQuoteWindowTopBorder_DCTStyle.border) == "undefined")
       {
          DrawQuoteWindowTopBorder_DCTStyle.border = gConfigSettings.DCTColors.QuoteWinBorderColor
                                    + UPPER_LEFT_SINGLE + HORIZONTAL_SINGLE + " "
                                    + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                                    + "Quote Window " + gConfigSettings.DCTColors.QuoteWinBorderColor;
          var curLength = strip_ctrl(DrawQuoteWindowTopBorder_DCTStyle.border).length;
          var borderWidth = pEditRight - pEditLeft;
          for (var i = curLength; i < borderWidth; ++i)
             DrawQuoteWindowTopBorder_DCTStyle.border += HORIZONTAL_SINGLE;
          DrawQuoteWindowTopBorder_DCTStyle.border += UPPER_RIGHT_SINGLE;
       }
    
       // Draw the top border line
       var screenLine = console.screen_rows - pQuoteWinHeight + 1;
       console.gotoxy(pEditLeft, screenLine);
       console.print(DrawQuoteWindowTopBorder_DCTStyle.border);
    }
    
    // Draws the bottom border of the quote window, DCT Edit style.  Note:
    // The cursor should be placed at the start of the line (leftmost screen
    // column on the screen line where the border should be drawn).
    //
    // Parameters:
    //  pEditLeft: The leftmost column of the edit area
    //  pEditRight: The rightmost column of the edit area
    function DrawQuoteWindowBottomBorder_DCTStyle(pEditLeft, pEditRight)
    {
       // Generate the bottom border vairable only once.
       if (typeof(DrawQuoteWindowBottomBorder_DCTStyle.border) == "undefined")
       {
          // Create a string containing the quote help text.
          var quoteHelpText = gConfigSettings.DCTColors.QuoteWinBorderTextColor
                             + "[Enter] Accept" + gConfigSettings.DCTColors.QuoteWinBorderColor
                             + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                             + "[^Q/ESC] End" + gConfigSettings.DCTColors.QuoteWinBorderColor
                             + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                             + "[" + UP_ARROW + "/" + DOWN_ARROW + "/PgUp/PgDn/Home/End] Scroll"
                             + gConfigSettings.DCTColors.QuoteWinBorderColor + HORIZONTAL_SINGLE
                             + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor;
    						 /*
    						 + "[" + UP_ARROW + "/" + DOWN_ARROW + "/PgUp/PgDn] Scroll"
                             + gConfigSettings.DCTColors.QuoteWinBorderColor + HORIZONTAL_SINGLE
                             + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                             + "[F/L] First/last page";
    						 */
          var helpTextLen = strip_ctrl(quoteHelpText).length;
    
          // Figure out the starting horizontal position on the screen so that
          // the quote help text line can be centered.
          var helpTextStartX = Math.floor((console.screen_columns/2) - (helpTextLen/2));
    
          // Start creating DrawQuoteWindowBottomBorder_DCTStyle.border with the
          // bottom border lines, up until helpTextStartX.
          DrawQuoteWindowBottomBorder_DCTStyle.border = gConfigSettings.DCTColors.QuoteWinBorderColor
                                                      + LOWER_LEFT_SINGLE;
          for (var XPos = pEditLeft+2; XPos < helpTextStartX; ++XPos)
             DrawQuoteWindowBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE;
          // Add the help text, then display the rest of the bottom border characters.
          DrawQuoteWindowBottomBorder_DCTStyle.border += quoteHelpText
                                                       + gConfigSettings.DCTColors.QuoteWinBorderColor;
          for (var XPos = helpTextStartX + helpTextLen; XPos <= pEditRight-2; ++XPos)
             DrawQuoteWindowBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE;
          DrawQuoteWindowBottomBorder_DCTStyle.border += LOWER_RIGHT_SINGLE;
       }
    
       // Print the border text on the screen
       console.print(DrawQuoteWindowBottomBorder_DCTStyle.border);
    }
    
    // Prompts the user for a yes/no question, DCTEdit-style.
    //
    // Parameters:
    //  pQuestion: The question to ask, without the trailing "?"
    //  pBoxTitle: The text to appear in the top border of the question box
    //  pDefaultYes: Whether to default to "yes" (true/false).  If false, this
    //               will default to "no".
    //  pParamObj: An object containing the following members:
    //   displayMessageRectangle: The function used for re-drawing the portion of the
    //                            message on the screen when the yes/no box is no longer
    //                            needed
    //   editTop: The topmost row of the edit area
    //   editBottom: The bottom row of the edit area
    //   editWidth: The edit area width
    //   editHeight: The edit area height
    //   editLinesIndex: The current index into the edit lines array
    // pAlwaysEraseBox: Boolean: erase the box regardless of a Yes or No answer.
    function promptYesNo_DCTStyle(pQuestion, pBoxTitle, pDefaultYes, pParamObj, pAlwaysEraseBox)
    {
    	var userResponse = pDefaultYes;
    
    	// Get the current cursor position, so that we can place the cursor
    	// back there later.
    	const originalCurpos = console.getxy();
    
    	// Set up the abort confirmation box dimensions, and calculate
    	// where on the screen to display it.
    	//const boxWidth = 27;
    	const boxWidth = pQuestion.length + 14;
    	const boxHeight = 6;
    	const boxX = (console.screen_columns/2).toFixed(0) - (boxWidth/2).toFixed(0);
    	const boxY = +pParamObj.editTop + +(pParamObj.editHeight/2).toFixed(0) - +(boxHeight/2).toFixed(0);
    	const innerBoxWidth = boxWidth - 2;
    
    	// Display the question box
    	// Upper-left corner, 1 horizontal line, and "Abort" text
    	console.gotoxy(boxX, boxY);
    	console.print(gConfigSettings.DCTColors.TextBoxBorder + UPPER_LEFT_SINGLE +
    	              HORIZONTAL_SINGLE + " " + gConfigSettings.DCTColors.TextBoxBorderText +
    	              pBoxTitle + gConfigSettings.DCTColors.TextBoxBorder + " ");
    	// Remaining top box border
    	for (var i = pBoxTitle.length + 5; i < boxWidth; ++i)
    		console.print(HORIZONTAL_SINGLE);
    	console.print(UPPER_RIGHT_SINGLE);
    	// Inner box: Blank
    	var endScreenLine = boxY + boxHeight - 2;
    	for (var screenLine = boxY+1; screenLine < endScreenLine; ++screenLine)
    	{
    		console.gotoxy(boxX, screenLine);
    		console.print(VERTICAL_SINGLE);
    		printf("%" + innerBoxWidth + "s", "");
    		console.print(VERTICAL_SINGLE);
    	}
    	// Bottom box border
    	console.gotoxy(boxX, screenLine);
    	console.print(LOWER_LEFT_SINGLE);
    	for (var i = 0; i < innerBoxWidth; ++i)
    		console.print(HORIZONTAL_SINGLE);
    	console.print(LOWER_RIGHT_SINGLE);
    
    	// Prompt the user whether or not to abort: Move the cursor to
    	// the proper location on the screen, output the propmt text,
    	// and get user input.
    	console.gotoxy(boxX+3, boxY+2);
    	console.print(gConfigSettings.DCTColors.TextBoxInnerText + pQuestion + "?  " +
    	gConfigSettings.DCTColors.YesNoBoxBrackets + "[" +
    	gConfigSettings.DCTColors.YesNoBoxYesNoText);
    	// Default to yes/no, depending on the value of pDefaultYes.
    	if (pDefaultYes)
    	{
    		console.print("Yes");
    		userResponse = true;
    	}
    	else
    	{
    		console.print("No ");
    		userResponse = false;
    	}
    	console.print(gConfigSettings.DCTColors.YesNoBoxBrackets + "]");
    
    	// Input loop
    	var userInput = "";
    	var continueOn = true;
    	while (continueOn)
    	{
    		// Move the cursor where it needs to be to write the "Yes"
    		// or "No"
    		console.gotoxy(boxX+20, boxY+2);
    		// Get a key and take appropriate action.
    		userInput = getUserKey(K_UPPER|K_NOECHO|K_NOCRLF|K_NOSPIN, gConfigSettings);
    		if (userInput == KEY_ENTER)
    			continueOn = false;
    		else if (userInput == "Y")
    		{
    			userResponse = true;
    			continueOn = false;
    		}
    		else if ((userInput == "N") || (userInput == KEY_ESC))
    		{
    			userResponse = false;
    			continueOn = false;
    		}
    		// Arrow keys: Toggle back and forth
    		else if ((userInput == KEY_UP) || (userInput == KEY_DOWN) ||
    		         (userInput == KEY_LEFT) || (userInput == KEY_RIGHT))
    		{
    			// Toggle the userResponse variable
    			userResponse = !userResponse;
    			// Write "Yes" or "No", depending on the userResponse variable
    			if (userResponse)
    			{
    				console.print(gConfigSettings.DCTColors.YesNoBoxYesNoText + "Yes" +
    				gConfigSettings.DCTColors.YesNoBoxBrackets + "]");
    			}
    			else
    			{
    				console.print(gConfigSettings.DCTColors.YesNoBoxYesNoText + "No " +
    				gConfigSettings.DCTColors.YesNoBoxBrackets + "]");
    			}
    		}
    	}
    
    	// If the user chose no, then erase the confirmation box and put the
    	// cursor back where it originally was.
    	if (!userResponse || pAlwaysEraseBox)
    	{
    		// Calculate the difference in the edit lines index we'll need to use
    		// to erase the confirmation box.  This will be the difference between
    		// the cursor position between boxY and the current cursor row.
    		const editLinesIndexDiff = boxY - originalCurpos.y;
    		pParamObj.displayMessageRectangle(boxX, boxY, boxWidth, boxHeight,
    		                                  pParamObj.editLinesIndex + editLinesIndexDiff,
    		                                  true);
    		// Put the cursor back where it was
    		console.gotoxy(originalCurpos);
    	}
    
    	return userResponse;
    }
    
    // Displays the time on the screen.
    //
    // Parameters:
    //  pTimeStr: The time to display (optional).  If this is omitted,
    //            then this funtion will get the current time.
    function displayTime_DCTStyle(pTimeStr)
    {
    	// Calculate the horizontal location for the time string, using basically
    	// the formula used for calculating the horizontal location of the message
    	// area in redrawScreen_DCTStyle(), which the time lines up with.
    	var fieldWidth = (console.screen_columns * (27/80)).toFixed(0);
       //var curposX = console.screen_columns - fieldWidth - 9 + 8;
       var curposX = console.screen_columns - fieldWidth - 1;
       console.gotoxy(curposX, 3);
    
    	if (pTimeStr == null)
    		console.print("\x01n" + gConfigSettings.DCTColors.TopTimeColor + getCurrentTimeStr());
    	else
    		console.print("\x01n" + gConfigSettings.DCTColors.TopTimeColor + pTimeStr);
    }
    
    // Displays the number of minutes remaining on the screen.
    function displayTimeRemaining_DCTStyle()
    {
    	var fieldWidth = (console.screen_columns * (7/80)).toFixed(0);
    	var startX = console.screen_columns - fieldWidth - 1;
    	console.gotoxy(startX, 3);
    	var timeStr = Math.floor(bbs.time_left / 60).toString().substr(0, fieldWidth);
    	console.print(gConfigSettings.DCTColors.TopTimeLeftColor + timeStr +
    	              gConfigSettings.DCTColors.TopTimeLeftFillColor);
    	fieldWidth -= (timeStr.length+1);
    	for (var i = 0; i < fieldWidth; ++i)
    		console.print(DOT_CHAR);
    }
    
    // Displays & handles the input loop for the DCT style ESC menu.
    //
    // Parameters:
    //  pEditLeft: The leftmost column of the edit area
    //  pEditRight: The rightmost column of the edit area
    //  pEditTop: The topmost row of the edit area
    //  pDisplayMessageRectangle: The function for drawing part of the
    //                            edit text on the screen.  This is used
    //                            for refreshing the screen after a menu
    //                            box disappears.
    //  pEditLinesIndex: The current index into the edit lines array.  This
    //                   is is required to pass to the pDisplayMessageRectangle
    //                   function.
    //  pEditLineDiff: The difference between the current edit line and the top of
    //                 the edit area.
    //  pCanCrossPost: Whether or not cross-posting is allowed.
    //
    // Return value: The action code, based on the user's selection
    function doDCTESCMenu(pEditLeft, pEditRight, pEditTop, pDisplayMessageRectangle,
                          pEditLinesIndex, pEditLineDiff, pCanCrossPost)
    {
    	// This function displays the top menu options, with a given one highlighted.
    	//
    	// Parameters:
    	//  pItemPositions: An object containing the menu item positions.
    	//  pHighlightedItemNum: The index (0-based) of the menu item to be highlighted.
    	//  pMenus: An array containing the menus
    	//
    	// Return value: The user's last keypress during the menu's input loop.
    	function displayTopMenuItems(pItemPositions, pHighlightedItemNum, pMenus)
    	{
    		// File
    		console.gotoxy(pItemPositions.fileX, pItemPositions.mainMenuY);
    		if (pHighlightedItemNum == 0)
    		{
    			console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT +
    			              gConfigSettings.DCTColors.SelectedMenuLabelText + "File" +
    			              gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT);
    		}
    		else
    			console.print("\x01n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "File\x01n ");
    		// Edit
    		console.gotoxy(pItemPositions.editX, pItemPositions.mainMenuY);
    		if (pHighlightedItemNum == 1)
    		{
    			console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT +
    			              gConfigSettings.DCTColors.SelectedMenuLabelText + "Edit" +
    			              gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT);
    		}
    		else
    			console.print("\x01n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "Edit\x01n ");
    		// SysOp
    		if (user.is_sysop)
    		{
    			console.gotoxy(pItemPositions.sysopX, pItemPositions.mainMenuY);
    			if (pHighlightedItemNum == 2)
    			{
    				console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT +
    				              gConfigSettings.DCTColors.SelectedMenuLabelText + "SysOp" +
    				              gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT);
    			}
    			else
    				console.print("\x01n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "SysOp\x01n ");
    		}
    		// Help
    		console.gotoxy(pItemPositions.helpX, pItemPositions.mainMenuY);
    		if (pHighlightedItemNum == 3)
    		{
    			console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT +
    			              gConfigSettings.DCTColors.SelectedMenuLabelText + "Help" +
    			              gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT);
    		}
    		else
    			console.print("\x01n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "Help\x01n ");
    
    		// Display the menu (and capture the return object so that we can
    		// also return it here).
    		var returnObj = pMenus[pHighlightedItemNum].doInputLoop();
    
    		// Refresh the part of the edit text on the screen where the menu was.
    		pDisplayMessageRectangle(pMenus[pHighlightedItemNum].topLeftX,
    		                         pMenus[pHighlightedItemNum].topLeftY,
    		                         pMenus[pHighlightedItemNum].width,
    		                         pMenus[pHighlightedItemNum].height,
    		                         pEditLinesIndex-pEditLineDiff, true);
    
    		return returnObj;
    	}
    
    	// Set up an object containing the menu item positions.
    	// Only create this object once.
    	if (typeof(doDCTESCMenu.mainMenuItemPositions) == "undefined")
    	{
    		doDCTESCMenu.mainMenuItemPositions = {
    			// Vertical position on the screen for the main menu items
    			mainMenuY: pEditTop - 1,
    			// Horizontal position of the "File" text
    			fileX: gEditLeft + 5,
    			// Horizontal position of the "Edit" text
    			editX: 0,
    			// Horizontal position of the "SysOp" text
    			sysopX: 0,
    			helpX: 0
    		};
    		doDCTESCMenu.mainMenuItemPositions.editX = doDCTESCMenu.mainMenuItemPositions.fileX + 11;
    		doDCTESCMenu.mainMenuItemPositions.sysopX = doDCTESCMenu.mainMenuItemPositions.editX + 11;
    		// Horizontal position of of the "Help" text
    		if (user.is_sysop)
    			doDCTESCMenu.mainMenuItemPositions.helpX = doDCTESCMenu.mainMenuItemPositions.sysopX + 12;
    		else
    			doDCTESCMenu.mainMenuItemPositions.helpX = doDCTESCMenu.mainMenuItemPositions.sysopX;
    	}
    
    	// Variables for the menu numbers
    	const fileMenuNum = 0;
    	const editMenuNum = 1;
    	const sysopMenuNum = 2;
    	const helpMenuNum = 3;
    
    	// Set up the menu objects.  Only create these objects once.
    	if (typeof(doDCTESCMenu.allMenus) == "undefined")
    	{
    		doDCTESCMenu.allMenus = [];
    		// File menu
    		doDCTESCMenu.allMenus[fileMenuNum] = new DCTMenu(doDCTESCMenu.mainMenuItemPositions.fileX, doDCTESCMenu.mainMenuItemPositions.mainMenuY+1);
    		doDCTESCMenu.allMenus[fileMenuNum].addItem("&Save    Ctrl-Z", DCTMENU_FILE_SAVE);
    		doDCTESCMenu.allMenus[fileMenuNum].addItem("&Abort   Ctrl-A", DCTMENU_FILE_ABORT);
    		if (pCanCrossPost)
    			doDCTESCMenu.allMenus[fileMenuNum].addItem( "X-Post  Ctrl-&C", DCTMENU_CROSS_POST);
    		doDCTESCMenu.allMenus[fileMenuNum].addItem("&Edit       ESC", DCTMENU_FILE_EDIT);
    		doDCTESCMenu.allMenus[fileMenuNum].addExitLoopKey(CTRL_Z, DCTMENU_FILE_SAVE);
    		doDCTESCMenu.allMenus[fileMenuNum].addExitLoopKey(CTRL_A, DCTMENU_FILE_ABORT);
    		if (pCanCrossPost)
    			doDCTESCMenu.allMenus[fileMenuNum].addExitLoopKey(CTRL_C, DCTMENU_CROSS_POST);
    		doDCTESCMenu.allMenus[fileMenuNum].addExitLoopKey(KEY_ESC, DCTMENU_FILE_EDIT);
    
    		// Edit menu
    		doDCTESCMenu.allMenus[editMenuNum] = new DCTMenu(doDCTESCMenu.mainMenuItemPositions.editX, doDCTESCMenu.mainMenuItemPositions.mainMenuY+1);
    		doDCTESCMenu.allMenus[editMenuNum].addItem("&Insert Mode    Ctrl-I", DCTMENU_EDIT_INSERT_TOGGLE);
    		doDCTESCMenu.allMenus[editMenuNum].addItem("&Find Text      Ctrl-N", DCTMENU_EDIT_FIND_TEXT);
    		doDCTESCMenu.allMenus[editMenuNum].addItem("Spe&ll Checker  Ctrl-W", DCTMENU_EDIT_SPELL_CHECKER);
    		doDCTESCMenu.allMenus[editMenuNum].addItem("Setti&ngs       Ctrl-U", DCTMENU_EDIT_SETTINGS);
    		doDCTESCMenu.allMenus[editMenuNum].addExitLoopKey(CTRL_I, DCTMENU_EDIT_INSERT_TOGGLE);
    		doDCTESCMenu.allMenus[editMenuNum].addExitLoopKey(CTRL_N, DCTMENU_EDIT_FIND_TEXT);
    		doDCTESCMenu.allMenus[editMenuNum].addExitLoopKey(CTRL_U, DCTMENU_EDIT_SETTINGS);
    
    		// SysOp menu
    		doDCTESCMenu.allMenus[sysopMenuNum] = new DCTMenu(doDCTESCMenu.mainMenuItemPositions.sysopX, doDCTESCMenu.mainMenuItemPositions.mainMenuY+1);
    		doDCTESCMenu.allMenus[sysopMenuNum].addItem("&Import file      Ctrl-O", DCTMENU_SYSOP_IMPORT_FILE);
    		doDCTESCMenu.allMenus[sysopMenuNum].addItem("E&xport to file   Ctrl-X", DCTMENU_SYSOP_EXPORT_FILE);
    		doDCTESCMenu.allMenus[sysopMenuNum].addExitLoopKey(CTRL_O, DCTMENU_SYSOP_IMPORT_FILE);
    		doDCTESCMenu.allMenus[sysopMenuNum].addExitLoopKey(CTRL_X, DCTMENU_SYSOP_EXPORT_FILE);
    
    		// Help menu
    		doDCTESCMenu.allMenus[helpMenuNum] = new DCTMenu(doDCTESCMenu.mainMenuItemPositions.helpX, doDCTESCMenu.mainMenuItemPositions.mainMenuY+1);
    		doDCTESCMenu.allMenus[helpMenuNum].addItem("C&ommand List   Ctrl-P", DCTMENU_HELP_COMMAND_LIST);
    		doDCTESCMenu.allMenus[helpMenuNum].addItem("&General Help   Ctrl-G", DCTMENU_HELP_GENERAL);
    		doDCTESCMenu.allMenus[helpMenuNum].addItem("&Program Info   Ctrl-R", DCTMENU_HELP_PROGRAM_INFO);
    		if (gConfigSettings.enableTextReplacements)
    		{
    			doDCTESCMenu.allMenus[helpMenuNum].addItem("&Text replcmts  Ctrl-T", DCTMENU_LIST_TXT_REPLACEMENTS);
    			doDCTESCMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_T, DCTMENU_LIST_TXT_REPLACEMENTS);
    		}
    		doDCTESCMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_P, DCTMENU_HELP_COMMAND_LIST);
    		doDCTESCMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_G, DCTMENU_HELP_GENERAL);
    		doDCTESCMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_R, DCTMENU_HELP_PROGRAM_INFO);
    
    		// For each menu, add KEY_LEFT, KEY_RIGHT, and ESC as loop-exit keys;
    		// also, set the menu colors.
    		for (var i = 0; i < doDCTESCMenu.allMenus.length; ++i)
    		{
    			doDCTESCMenu.allMenus[i].addExitLoopKey(KEY_LEFT);
    			doDCTESCMenu.allMenus[i].addExitLoopKey(KEY_RIGHT);
    			doDCTESCMenu.allMenus[i].addExitLoopKey(KEY_ESC);
    
    			doDCTESCMenu.allMenus[i].colors.border = gConfigSettings.DCTColors.MenuBorders;
    			doDCTESCMenu.allMenus[i].colors.selected = gConfigSettings.DCTColors.MenuSelectedItems;
    			doDCTESCMenu.allMenus[i].colors.unselected = gConfigSettings.DCTColors.MenuUnselectedItems;
    			doDCTESCMenu.allMenus[i].colors.hotkey = gConfigSettings.DCTColors.MenuHotkeys;
    		}
    	}
    
    	// The chosen action will be returned from this function
    	var chosenAction = ESC_MENU_EDIT_MESSAGE;
    
    	// Boolean variables to keep track of which menus were displayed
    	// (for refresh purposes later)
    	var fileMenuDisplayed = false;
    	var editMenuDisplayed = false;
    	var sysopMenuDisplayed = false;
    	var helpMenuDisplayed = false;
    
    	// Display the top menu options with "File" highlighted.
    	var menuNum = fileMenuNum; // 0: File, ..., 3: Help
    	var subMenuItemNum = 0;
    	var menuRetObj = displayTopMenuItems(doDCTESCMenu.mainMenuItemPositions, menuNum,
    	                                     doDCTESCMenu.allMenus);
    	// Input loop
    	var userInput = "";
    	var matchedMenuRetval = false; // Whether one of the menu return values was matched
    	var continueOn = true;
    	while (continueOn)
    	{
    		matchedMenuRetval = valMatchesMenuCode(menuRetObj.returnVal);
    		// If a menu return value was matched, then set userInput to it.
    		if (matchedMenuRetval)
    			userInput = menuRetObj.returnVal;
    		// If the user's input from the last menu was ESC, left, right, or one of the
    		// characters from the menus, then set userInput to the last menu keypress.
    		else if (inputMatchesMenuSelection(menuRetObj.userInput))
    			userInput = menuRetObj.userInput;
    		// If nothing from the menu was matched, then get a key from the user.
    		else
    			userInput = getUserKey(K_UPPER|K_NOECHO|K_NOCRLF|K_NOSPIN, gConfigSettings);
    		menuRetObj.userInput = "";
    
    		// If a menu return code was matched or userInput is blank (the
    		// timeout was hit), then exit out of the loop.
    		if (matchedMenuRetval || (userInput == ""))
    			break;
    
    		// Take appropriate action based on the user's input.
    		switch (userInput)
    		{
    			case KEY_LEFT:
    				if (menuNum == 0)
    					menuNum = 3;
    				else
    					--menuNum;
    				// Don't allow the sysop menu for non-sysops.
    				if ((menuNum == sysopMenuNum) && !user.is_sysop)
    					--menuNum;
    				subMenuItemNum = 0;
    				menuRetObj = displayTopMenuItems(doDCTESCMenu.mainMenuItemPositions, menuNum, doDCTESCMenu.allMenus);
    				break;
    			case KEY_RIGHT:
    				if (menuNum == 3)
    					menuNum = 0;
    				else
    					++menuNum;
    				// Don't allow the sysop menu for non-sysops.
    				if ((menuNum == sysopMenuNum) && !user.is_sysop)
    					++menuNum;
    				subMenuItemNum = 0;
    				menuRetObj = displayTopMenuItems(doDCTESCMenu.mainMenuItemPositions, menuNum, doDCTESCMenu.allMenus);
    				break;
    			case KEY_UP:
    			case KEY_DOWN:
    				break;
    			case KEY_ENTER: // Selected an item from the menu
    				// Set userInput to the return code from the menu so that it will
    				// be returned from this function.
    				userInput = menuRetObj.returnVal;
    			case "S":       // Save
    			case CTRL_Z:    // Save
    			case "A":       // Abort
    			case CTRL_A:    // Abort
    			case "I":       // Import file for sysop, or Insert/Overwrite toggle for non-sysop
    			case CTRL_O:    // Import file (for sysop)
    			case "X":       // Export file (for sysop)
    			case CTRL_X:    // Export file (for sysop)
    			case CTRL_V:    // Insert/overwrite toggle
    			case "F":       // Find text
    			case CTRL_F:    // Find text
    			case "N":       // User settings
    			case CTRL_U:    // User settings
    			case "O":       // Command List
    			case "G":       // General help
    			case "P":       // Program info
    			case "E":       // Edit the message
    			case KEY_ESC:   // Edit the message
    				continueOn = false;
    				break;
    			case "C":       // Cross-post
    			case CTRL_C:    // Cross-post
    				if (pCanCrossPost)
    					continueOn = false;
    				break;
    			case "T": // List text replacements
    			case CTRL_T: // List text replacements
    				if (gConfigSettings.enableTextReplacements)
    					continueOn = false;
    				break;
    			case "L": // Spell checker
    			case CTRL_W: // Spell checker
    				continueOn = false;
    				break;
    			default:
    				break;
    		}
    	}
    
    	// We've exited the menu, so refresh the top menu border and the message text
    	// on the screen.
    	displayTextAreaTopBorder_DCTStyle(doDCTESCMenu.mainMenuItemPositions.mainMenuY, pEditLeft, pEditRight);
    
    	// Set chosenAction based on the user's input
    	// Save the message
    	if ((userInput == "S") || (userInput == CTRL_Z) || (userInput == DCTMENU_FILE_SAVE))
    		chosenAction = ESC_MENU_SAVE;
    	// Abort
    	else if ((userInput == "A") || (userInput == CTRL_A) || (userInput == DCTMENU_FILE_ABORT))
    		chosenAction = ESC_MENU_ABORT;
    	// Toggle insert/overwrite mode
    	else if ((userInput == CTRL_V) || (userInput == DCTMENU_EDIT_INSERT_TOGGLE))
    		chosenAction = ESC_MENU_INS_OVR_TOGGLE;
    	// Import file (sysop only)
    	else if (userInput == DCTMENU_SYSOP_IMPORT_FILE)
    	{
    		if (user.is_sysop)
    			chosenAction = ESC_MENU_SYSOP_IMPORT_FILE;
    	}
    	// Import file for sysop, or Insert/Overwrite toggle for non-sysop
    	else if (userInput == "I")
    	{
    		if (user.is_sysop)
    			chosenAction = ESC_MENU_SYSOP_IMPORT_FILE;
    		else
    			chosenAction = ESC_MENU_INS_OVR_TOGGLE;
    	}
    	// Find text
    	else if ((userInput == CTRL_F) || (userInput == "F") || (userInput == DCTMENU_EDIT_FIND_TEXT))
    		chosenAction = ESC_MENU_FIND_TEXT;
    	// Command List
    	else if ((userInput == "O") || (userInput == DCTMENU_HELP_COMMAND_LIST))
    		chosenAction = ESC_MENU_HELP_COMMAND_LIST;
    	// General help
    	else if ((userInput == "G") || (userInput == DCTMENU_HELP_GENERAL))
    		chosenAction = ESC_MENU_HELP_GENERAL;
    	// Program info
    	else if ((userInput == "P") || (userInput == DCTMENU_HELP_PROGRAM_INFO))
    		chosenAction = ESC_MENU_HELP_PROGRAM_INFO;
    	// Export the message
    	else if ((userInput == "X") || (userInput == DCTMENU_SYSOP_EXPORT_FILE))
    	{
    		if (user.is_sysop)
    			chosenAction = ESC_MENU_SYSOP_EXPORT_FILE;
    	}
    	// Edit the message
    	else if ((userInput == "E") || (userInput == KEY_ESC))
    		chosenAction = ESC_MENU_EDIT_MESSAGE;
    	// Cross-post
    	else if ((userInput == CTRL_C) || (userInput == "C") || (userInput == DCTMENU_CROSS_POST))
    	{
    		if (pCanCrossPost)
    			chosenAction = ESC_MENU_CROSS_POST_MESSAGE;
    	}
    	// List text replacements
    	else if ((userInput == CTRL_T) || (userInput == "T") || (userInput == DCTMENU_LIST_TXT_REPLACEMENTS))
    	{
    		if (gConfigSettings.enableTextReplacements)
    			chosenAction = ESC_MENU_LIST_TEXT_REPLACEMENTS;
    	}
    	// User settings
    	else if ((userInput == CTRL_U) || (userInput == "N") || (userInput == DCTMENU_EDIT_SETTINGS))
    		chosenAction = ESC_MENU_USER_SETTINGS;
    	// Spell checker
    	else if ((userInput == CTRL_W) || (userInput == "L") || (userInput == DCTMENU_EDIT_SPELL_CHECKER))
    		chosenAction = ESC_MENU_SPELL_CHECK;
    
    	return chosenAction;
    }
    
    // This function returns whether a value matches any of the DCT Edit menu return
    // values.  This is used in doDCTESCMenu()'s input loop;
    //
    // Parameters:
    //  pVal: The value to test
    //
    // Return: Boolean - Whether or not the value matches a DCT Edit menu return value.
    function valMatchesMenuCode(pVal)
    {
    	var valMatches = false;
    	valMatches = ((pVal == DCTMENU_FILE_SAVE) || (pVal == DCTMENU_FILE_ABORT) ||
    	              (pVal == DCTMENU_FILE_EDIT) || (pVal == DCTMENU_EDIT_INSERT_TOGGLE) ||
    	              (pVal == DCTMENU_EDIT_FIND_TEXT) || (pVal == DCTMENU_HELP_COMMAND_LIST) ||
    	              (pVal == DCTMENU_HELP_GENERAL) || (pVal == DCTMENU_HELP_PROGRAM_INFO) ||
    	              (pVal == DCTMENU_EDIT_SETTINGS) || (pVal == DCTMENU_EDIT_SPELL_CHECKER));
    	if (gConfigSettings.enableTextReplacements)
    		valMatches = (valMatches || (pVal == DCTMENU_LIST_TXT_REPLACEMENTS));
    	if (user.is_sysop)
    		valMatches = (valMatches || (pVal == DCTMENU_SYSOP_IMPORT_FILE) || (pVal == DCTMENU_SYSOP_EXPORT_FILE));
    	return valMatches;
    }
    
    // This function returns whether a user input matches a selection from one of the
    // menus.  This is used in doDCTESCMenu()'s input loop;
    //
    // Parameters:
    //  pInput: The user input to test
    function inputMatchesMenuSelection(pInput)
    {
       return((pInput == KEY_ESC) || (pInput == KEY_LEFT) ||
               (pInput == KEY_RIGHT) || (pInput == KEY_ENTER) ||
               (pInput == "S") || (pInput == "A") || (pInput == "E") ||
               (pInput == "I") || (user.is_sysop && (pInput == "X")) ||
               (pInput == "F") || (pInput == "C") || (pInput == "G") ||
               (pInput == "P") || (pInput == "T"));
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////
    // DCTMenu object functions
    
    // Constructs a menu item for a DCT menu
    function DCTMenuItem()
    {
       this.text = "";        // The item text
       this.hotkeyIndex = -1; // Index of the hotkey in the text (-1 for no hotkey).
       this.hotkey = "";      // The shortcut key for the item (blank for no hotkey).
       this.returnVal = 0;    // Return value for the item
    }
    
    // DCTMenu constructor: Constructs a DCTEdit-style menu.
    //
    // Parameters:
    //  pTopLeftX: The upper-left screen column
    //  pTopLeftY: The upper-left screen row
    function DCTMenu(pTopLeftX, pTopLeftY)
    {
       this.colors = {
    		// Unselected item colors
    		unselected: "\x01n\x01" + "7\x01k",
    		// Selected item colors
    		selected: "\x01n\x01w",
    		// Other colors
    		hotkey: "\x01h\x01w",
    		border: "\x01n\x01" + "+\x01k"
       };
    
       this.topLeftX = 1; // Upper-left screen column
       if ((pTopLeftX != null) && (pTopLeftX > 0) && (pTopLeftX <= console.screen_columns))
          this.topLeftX = pTopLeftX;
       this.topLeftY = 1; // Upper-left screen row
       if ((pTopLeftY != null) && (pTopLeftY > 0) && (pTopLeftY <= console.screen_rows))
          this.topLeftY = pTopLeftY;
       this.width = 0;
       this.height = 0;
       this.selectedItemIndex = 0;
    
       // this.menuItems will contain DCTMenuItem objects.
       this.menuItems = [];
       // hotkeyRetvals is an array, indexed by hotkey, that contains
       // the return values for each hotkey.
       this.hotkeyRetvals = [];
    
       // exitLoopKeys will contain keys that will exit the input loop
       // when pressed by the user, so that calling code can catch them.
       // It's indexed by the key, and the value is the return code that
       // should be returned for that key.
       this.exitLoopKeys = [];
    
       // Border style: "single" or "double"
       this.borderStyle = "single";
    
       // clearSpaceAroundMenu controls whether or not to clear one space
       // around the menu when it is drawn.
       this.clearSpaceAroundMenu = false;
       // clearSpaceColor controls which color to use when drawing the
       // clear space around the menu.
       this.clearSpaceColor = "\x01n";
       // clearSpaceTopText specifies text to display above the top of the
       // menu when clearing space around it.
       this.clearSpaceTopText = "";
    
       // Timeout (in milliseconds) for the input loop.  Default to 1 minute.
       this.timeoutMS = 60000;
    
       // Member functions
       this.addItem = DCTMenu_AddItem;
       this.addExitLoopKey = DCTMenu_AddExitLoopKey;
       this.displayItem = DCTMenu_DisplayItem;
       this.doInputLoop = DCTMenu_DoInputLoop;
       this.numItems = DCTMenu_NumItems;
       this.removeAllItems = DCTMenu_RemoveAllItems;
    }
    // Adds an item to the DCTMenu.
    //
    // Parameters:
    //  pText: The text of the menu item.  Note that a & precedes a hotkey.
    //  pReturnVal: The value to return upon selection of the item
    function DCTMenu_AddItem(pText, pReturnVal)
    {
       if (pText == "")
          return;
    
       var item = new DCTMenuItem();
       item.returnVal = pReturnVal;
       // Look for a & in pText, and if one is found, use the next character as
       // its hotkey.
       var ampersandIndex = pText.indexOf("&");
       if (ampersandIndex > -1)
       {
          // If pText has text after ampersandIndex, then set up
          // the next character as the hotkey in the item.
          if (pText.length > ampersandIndex+1)
          {
             item.hotkeyIndex = ampersandIndex;
             item.hotkey = pText.substr(ampersandIndex+1, 1);
             // Set the text of the item.  The text should not include
             // the ampersand.
             item.text = pText.substr(0, ampersandIndex) + pText.substr(ampersandIndex+1);
             // Add the hotkey & return value to this.hotkeyRetvals
             this.hotkeyRetvals[item.hotkey.toUpperCase()] = pReturnVal;
    
             // If the menu is not wide enough for this item's text, then
             // update this.width.
             if (this.width < item.text.length + 2)
                this.width = item.text.length + 2;
             // Also update this.height
             if (this.height == 0)
                this.height = 3;
             else
                ++this.height;
          }
          else
          {
             // pText does not have text after ampersandIndex.
             item.text = pText.substr(0, ampersandIndex);
          }
       }
       else
       {
          // No ampersand was found in pText.
          item.text = pText;
       }
    
       // Add the item to this.menuItems
       this.menuItems.push(item);
    }
    // Adds a key that will exit the input loop when pressed by the user.
    //
    // Parameters:
    //  pKey: The key that should cause the input loop to exit
    //  pReturnValue: The return value of the input loop when the key is pressed.
    //                If this is not specified, a default value of -1 will be used.
    function DCTMenu_AddExitLoopKey(pKey, pReturnValue)
    {
       var val = -1;
       if (typeof(pReturnValue) == "number")
          val = pReturnValue;
    
       this.exitLoopKeys[pKey] = val;
    }
    // Displays an item on the menu.
    //
    // Parameters:
    //  pItemIndex: The index of the item in the menuItems array
    //  pPrintBorders: Boolean - Whether or not to display the horizontal
    //                 borders on each side of the menu item text.
    function DCTMenu_DisplayItem(pItemIndex, pPrintBorders)
    {
       var printBorders = false;
       if (pPrintBorders != null)
          printBorders = pPrintBorders;
    
       // Determine whether to use the selected item color or unselected
       // item color.
       var itemColor = "";
       if (pItemIndex == this.selectedItemIndex)
          itemColor = this.colors.selected;
       else
          itemColor = this.colors.unselected;
    
       // Print the menu item text on the screen.
       if (printBorders)
       {
          console.gotoxy(this.topLeftX, this.topLeftY + pItemIndex + 1);
          if (this.borderStyle == "single")
             console.print(this.colors.border + VERTICAL_SINGLE);
          else if (this.borderStyle == "double")
             console.print(this.colors.border + VERTICAL_DOUBLE);
       }
       else
          console.gotoxy(this.topLeftX + 1, this.topLeftY + pItemIndex + 1);
       // If the menu item has a hotkey, then write the appropriate character
       // in the hotkey color.
       if (this.menuItems[pItemIndex].hotkeyIndex > -1)
       {
          console.print(itemColor +
                        this.menuItems[pItemIndex].text.substr(0, this.menuItems[pItemIndex].hotkeyIndex) +
                        this.colors.hotkey + this.menuItems[pItemIndex].hotkey + itemColor +
                        this.menuItems[pItemIndex].text.substr(this.menuItems[pItemIndex].hotkeyIndex + 1));
       }
       else
       {
          console.print(itemColor + this.menuItems[pItemIndex].text);
       }
       // If the item text isn't wide enough to fill the entire inner width, then
       // clear the line up until the right border.
       var innerWidth = this.width - 2;
       if (this.menuItems[pItemIndex].text.length < innerWidth)
       {
          for (var i = this.menuItems[pItemIndex].text.length; i < innerWidth; ++i)
             console.print(" ");
       }
       // Print the right border character if specified.
       if (printBorders)
       {
          if (this.borderStyle == "single")
             console.print(this.colors.border + VERTICAL_SINGLE);
          else if (this.borderStyle == "double")
             console.print(this.colors.border + VERTICAL_DOUBLE);
       }
    }
    // Displays the DCT menu and enters the input loop.
    //
    // Return value: An object containing the following properties:
    //  returnVal: The return code of the item selected, or -1 if no
    //             item was selected.
    //  userInput: The last user input
    function DCTMenu_DoInputLoop()
    {
    	var returnObj = {
    		returnVal: -1,
    		userInput: ""
    	};
    
    	// If clearSpaceAroundMenu is true, then draw a blank row
    	// above the menu.
    	if (this.clearSpaceAroundMenu && (this.topLeftY > 1))
    	{
    		// If there is room, output a space to the left, diagonal
    		// from the top-left corner of the menu.
    		if (this.topLeftX > 1)
    		{
    			console.gotoxy(this.topLeftX-1, this.topLeftY-1);
    			console.print(this.clearSpaceColor + " ");
    		}
    		else
    			console.gotoxy(this.topLeftX, this.topLeftY-1);
    
    		// Output this.clearSpaceTopText
    		console.print(this.clearSpaceTopText);
    		// Output the rest of the blank space
    		var textLen = strip_ctrl(this.clearSpaceTopText).length;
    		if (textLen < this.width)
    		{
    			var numSpaces = this.width - textLen;
    			if (this.topLeftX + this.width < console.screen_columns)
    				++numSpaces;
    			for (var i = 0; i < numSpaces; ++i)
    				console.print(this.clearSpaceColor + " ");
    		}
    	}
    
    	// Before drawing the top border, if clearSpaceAroundMenu is
    	// true, put space before the border.
    	if (this.clearSpaceAroundMenu && (this.topLeftY > 1))
    	{
    		console.gotoxy(this.topLeftX-1, this.topLeftY);
    		console.print(this.clearSpaceColor + " ");
    	}
    	else
    		console.gotoxy(this.topLeftX, this.topLeftY);
    	// Draw the top border
    	var innerWidth = this.width - 2;
    	if (this.borderStyle == "single")
    	{
    		console.print(this.colors.border + UPPER_LEFT_SINGLE);
    		for (var i = 0; i < innerWidth; ++i)
    			console.print(HORIZONTAL_SINGLE);
    		console.print(this.colors.border + UPPER_RIGHT_SINGLE);
    	}
    	else if (this.borderStyle == "double")
    	{
    		console.print(this.colors.border + UPPER_LEFT_DOUBLE);
    		for (var i = 0; i < innerWidth; ++i)
    			console.print(HORIZONTAL_DOUBLE);
    		console.print(this.colors.border + UPPER_RIGHT_DOUBLE);
    	}
    	// If clearSpaceAroundMenu is true, then put a space after the border.
    	if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns))
    		console.print(this.clearSpaceColor + " ");
    
    	// Print the menu items (and side spaces outside the menu if
    	// clearSpaceAroundMenu is true).
    	var itemColor = "";
    	for (var i = 0; i < this.menuItems.length; ++i)
    	{
    		if (this.clearSpaceAroundMenu && (this.topLeftX > 1))
    		{
    			console.gotoxy(this.topLeftX-1, this.topLeftY + i + 1);
    			console.print(this.clearSpaceColor + " ");
    		}
    
    		this.displayItem(i, true);
    
    		if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns))
    		{
    			console.gotoxy(this.topLeftX + this.width, this.topLeftY + i + 1);
    			console.print(this.clearSpaceColor + " ");
    		}
    	}
    
    	// Before drawing the bottom border, if clearSpaceAroundMenu is
    	// true, put space before the border.
    	if (this.clearSpaceAroundMenu && (this.topLeftY > 1))
    	{
    		console.gotoxy(this.topLeftX - 1, this.topLeftY + this.height - 1);
    		console.print(this.clearSpaceColor + " ");
    	}
    	else
    		console.gotoxy(this.topLeftX, this.topLeftY + this.height - 1);
    	// Draw the bottom border
    	if (this.borderStyle == "single")
    	{
    		console.print(this.colors.border + LOWER_LEFT_SINGLE);
    		for (var i = 0; i < innerWidth; ++i)
    			console.print(HORIZONTAL_SINGLE);
    		console.print(LOWER_RIGHT_SINGLE);
    	}
    	else if (this.borderStyle == "double")
    	{
    		console.print(this.colors.border + LOWER_LEFT_DOUBLE);
    		for (var i = 0; i < innerWidth; ++i)
    			console.print(HORIZONTAL_DOUBLE);
    		console.print(LOWER_RIGHT_DOUBLE);
    	}
    	// If clearSpaceAroundMenu is true, then put a space after the border.
    	if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns))
    		console.print(this.clearSpaceColor + " ");
    
    	// If clearSpaceAroundMenu is true, then draw a blank row
    	// below the menu.
    	if (this.clearSpaceAroundMenu && (this.topLeftY + this.height < console.screen_rows))
    	{
    		var numSpaces = this.width + 2;
    		if (this.topLeftX > 1)
    			console.gotoxy(this.topLeftX-1, this.topLeftY + this.height);
    		else
    		{
    			console.gotoxy(this.topLeftX, this.topLeftY + this.height);
    			--numSpaces;
    		}
    
    		if (this.topLeftX + this.width >= console.screen_columns)
    			--numSpaces;
    
    		for (var i = 0; i < numSpaces; ++i)
    			console.print(this.clearSpaceColor + " ");
    	}
    
    	// Place the cursor on the line of the selected item
    	console.gotoxy(this.topLeftX + 1, this.topLeftY + this.selectedItemIndex + 1);
    
    	// Keep track of the current cursor position
    	var curpos = {
    		x: this.topLeftX + 1,
    		y: this.topLeftY + this.selectedItemIndex + 1
    	};
    
    	// Input loop
    	const topItemLineNumber = this.topLeftY + 1;
    	const bottomItemLineNumber = this.topLeftY + this.height - 1;
    	var continueOn = true;
    	while (continueOn)
    	{
    		// Get a key, (time out after the selected time), and take appropriate action.
    		returnObj.userInput = getUserKey(K_UPPER|K_NOECHO|K_NOCRLF|K_NOSPIN, gConfigSettings);
    		// If the user input is blank, then the input timed out, and we should quit.
    		if (returnObj.userInput == "")
    		{
    			continueOn = false;
    			break;
    		}
    
    		// Take appropriate action, depending on the user's keypress.
    		switch (returnObj.userInput)
    		{
    			case KEY_ENTER:
    				// Set returnObj.returnVal to the currently-selected item's returnVal,
    				// and exit the input loop.
    				returnObj.returnVal = this.menuItems[this.selectedItemIndex].returnVal;
    				continueOn = false;
    				break;
    			case KEY_UP:
    				// Go up one item
    				if (this.menuItems.length > 1)
    				{
    					// If we're below the top menu item, then go up one item.  Otherwise,
    					// go to the last menu item.
    					var oldIndex = this.selectedItemIndex;
    					if ((curpos.y > topItemLineNumber) && (this.selectedItemIndex > 0))
    					{
    						--curpos.y;
    						--this.selectedItemIndex;
    					}
    					else
    					{
    						curpos.y = bottomItemLineNumber - 1;
    						this.selectedItemIndex = this.menuItems.length - 1;
    					}
    					// Refresh the items on the screen so that the item colors
    					// are updated.
    					this.displayItem(oldIndex, false);
    					this.displayItem(this.selectedItemIndex, false);
    				}
    				break;
    			case KEY_DOWN:
    				// Go down one item
    				if (this.menuItems.length > 1)
    				{
    					// If we're above the bottom menu item, then go down one item.  Otherwise,
    					// go to the first menu item.
    					var oldIndex = this.selectedItemIndex;
    					if ((curpos.y < bottomItemLineNumber) && (this.selectedItemIndex < this.menuItems.length - 1))
    					{
    						++curpos.y;
    						++this.selectedItemIndex;
    					}
    					else
    					{
    						curpos.y = this.topLeftY + 1;
    						this.selectedItemIndex = 0;
    					}
    					// Refresh the items on the screen so that the item colors
    					// are updated.
    					this.displayItem(oldIndex, false);
    					this.displayItem(this.selectedItemIndex, false);
    				}
    				break;
    			case KEY_ESC:
    				continueOn = false;
    				break;
    			default:
    				// If the user's input is one of the hotkeys, then stop the
    				// input loop and return with the return code for the hotkey.
    				if (typeof(this.hotkeyRetvals[returnObj.userInput]) != "undefined")
    				{
    					returnObj.returnVal = this.hotkeyRetvals[returnObj.userInput];
    					continueOn = false;
    				}
    				// If the user's input is one of the loop-exit keys, then stop
    				// the input loop.
    				else if (typeof(this.exitLoopKeys[returnObj.userInput]) != "undefined")
    				{
    					returnObj.returnVal = this.exitLoopKeys[returnObj.userInput];
    					continueOn = false;
    				}
    				break;
    		}
    	}
    
    	return returnObj;
    }
    
    // Returns the number of items in the menu.
    function DCTMenu_NumItems()
    {
       return this.menuItems.length;
    }
    // Removes all items from a DCTMenu.
    function DCTMenu_RemoveAllItems()
    {
       this.width = 0;
       this.height = 0;
       this.selectedItemIndex = 0;
       this.menuItems = [];
       this.hotkeyRetvals = [];
       this.exitLoopKeys = [];
    }