diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js
index b24956ad4884efb8b41f009dcd211a103ead616a..91b641466bb3d5d8dffcf0f25a103e156c0fbf68 100644
--- a/exec/load/dd_lightbar_menu.js
+++ b/exec/load/dd_lightbar_menu.js
@@ -170,6 +170,11 @@ lbMenu.wrapNavigation = false;
 // long as it's not a space.  For instance, to highlight the "x" in "Exit":
 lbMenu.Add("E&xit", -1);
 
+// The last parameter to Add() is a boolean specifying whether the item text is
+// in UTF-8 format.
+// Adding an item where the text is known to be in UTF-8 format
+lbMenu.Add(someonesName, 1, null, true, true);
+
 To enable borders, set the borderEnabled property to true.  Example:
 lbMenu.borderEnabled = true;
 
@@ -242,6 +247,10 @@ lbMenu.GetItem = function(pItemIndex) {
 	// the return value to return from the menu:
 	menuItemObj.text = itemText;
 	menuItemObj.retval = itemRetval;
+
+	// And if the text in the item is UTF-8, you must specify so as follows:
+	menuItemObj.textIsUTF8 = true;
+
 	return menuItemObj; // The DDLightbarMenu object will use this when displaying the menu
 };
 
diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js
index 3ff6640591c0f4ba83a34228589e0a33f163b25a..cc7b5ccebe787e707332a45cedc95374bbfd469d 100644
--- a/xtrn/DDMsgReader/DDMsgReader.js
+++ b/xtrn/DDMsgReader/DDMsgReader.js
@@ -9838,7 +9838,13 @@ function DigDistMsgReader_DisplaySyncMsgHeader(pMsgHdr)
 				// message read prompt, this script now has to parse & replace some of
 				// the @-codes in the message header line, since Synchronet doesn't know
 				// that the user is reading a message.
-				console.putmsg(this.ParseMsgAtCodes(fileLine, pMsgHdr, null, dateTimeStr, false, true), printMode);
+				var hdrLine = this.ParseMsgAtCodes(fileLine, pMsgHdr, null, dateTimeStr, false, true);
+				// If the message is in UTF-8 and the user's terminal supports UTF-8, then we'll be printing
+				// with the P_UTF8 mode bit. We'll need to convert the display header line to UTF-8 so all
+				// of it will display properly.
+				if (hdrIsUTF8 && terminalSupportsUTF8)
+					hdrLine = utf8_encode(hdrLine);
+				console.putmsg(hdrLine, printMode);
 				console.crlf();
 			}
 			msgHdrFile.close();
diff --git a/xtrn/DDMsgReader/dd_lightbar_menu.js b/xtrn/DDMsgReader/dd_lightbar_menu.js
deleted file mode 100644
index 91b641466bb3d5d8dffcf0f25a103e156c0fbf68..0000000000000000000000000000000000000000
--- a/xtrn/DDMsgReader/dd_lightbar_menu.js
+++ /dev/null
@@ -1,4017 +0,0 @@
-/* Digital Distortion Lightbar Menu library
- * Author: Eric Oulashin (AKA Nightfox)
- * BBS: Digital Distortion
- * Addresses: digitaldistortionbbs.com
- *            digdist.synchro.net
-
-This is a lightbar menu library.  This allows creating a scrollable menu of
-text items for the user to choose from.  The user can naviate the list using
-the up & down arrows, PageUp, PageDown, Home, and End keys.  The enter key
-selects an item.  The ESC key will exit the menu and return null.
-This menu library requires the use of an ANSI terminal.
-By default, this menu library does not display a border around the menu.
-If you want this library to draw a border around the menu, you can set the
-borderEnabled property to true.  Without a border, the menu gains 2
-characters of width and 2 lines of height.  If using a border, a title (text)
-can be displayed in the top border by setting the topBorderText property (it
-defaults to an empty string, for no title).
-
-This script provides an object, DDLightbarMenu.  Use the DDLightbarMenu
-constructor to create the object.  Some other notable methods:
-Add()
-SetItemHotkey()
-AddItemHotkey()
-SetPos()
-SetSize()
-GetVal()
-AddAdditionalSelectItemKeys()
-SetBorderChars()
-SetColors()
-
-To change the colors used for displaying the items, you can change the values
-in the colors object within the DDLightbarMenu object.  These are the current
-supported colors:
-itemColor: The color to use for non-selected items (current default is white
-           on blue).  This can be a string (with the color/attribute values)
-           or an array to specify colors for different sections of the item
-		   text to display in the menu.  See the note on item color arrays
-		   below.
-selectedItemColor: The color to use for selected items (current default is blue
-                   on white).  This can be a string (with the color/attribute values)
-                   or an array to specify colors for different sections of the item
-		           text to display in the menu.  See the note on item color arrays
-		           below.
-itemTextCharHighlightColor: The color of a highlighted non-space character in an
-                            item text (specified by having a & in the item text).
-							It's important not to specify a "\x01n" in here in case
-							the item text should have a background color.
-unselectableItemColor: The color to use for items that are not selectable
-borderColor: The color for the borders (if borders are enabled)
-You can also call SetColors() and pass in a JS object with any or all of the
-above properties to set the colors internally in the DDLightbarMenu object.
-
-Item color arrays: Currently, colors.itemColor and colors.seletedItemColor within
-a DDLightbarMenu object can be either a string (containing color/attribute codes)
-or an array with color/attribute codes for different sections of the item strings
-to display in the menu.  The array is to contain objects with the following
-properties:
-start: The index of the first character in the item string to apply the colors to
-end: One past the last character index in the string to apply the colors to
-attrs: The Synchronet attribute codes to apply to the section of the item string
-For the last item, the 'end' property can be -1, 0, or greater than the length
-of the item to apply the color/attribute codes to the rest of the string.
-
-
-By default, the menu selection will wrap around to the beginning/end when using
-the down/up arrows.  That behavior can be disabled by setting the wrapNavigation
-property to false.
-
-You can enable the display of a scrollbar by setting the scrollbarEnabled property
-to true.  By default, it is false.  For instance (assuming the menu object is lbMenu):
-lbMenu.scrollbarEnabled = true;
-The scrollbar can help to visually show how far the user is through the menu.  When
-enabled, the scrollbar will appear on the right side of the menu.  If borders are enabled,
-the scrollbar will appear just inside the right border. Also, if the scrollbar is
-enabled but all the items would fit in a single "page" in the menu, then the scrollbar
-won't be displayed.
-The scrollbar uses block characters to draw the scrollbar: ASCII character 176 for
-the background and ASCII 177 for the block that moves on the scrollbar.  If you want
-to change those characters, you can change the scrollbarInfo.BGChar and
-scrollbarInfo.blockChar properties in the menu object.
-By default, the scrollbar colors are high (bright) black for the background and high
-(bright) white for the moving block character.  If desired, those can be changed
-with the colors.scrollbarBGColor and colors.scrollbarScrollBlockColor properties in
-the menu object.
-
-This menu object supports adding multiple hotkeys to each menu item.  A hotkey
-can be specified in the Add() method a couple of different ways - By specifying
-a hotkey as the 3rd parameter and/or by putting a & in the menu item text just
-before a key you want to use as the hotkey.  For example, in the text "E&xit",
-"x" would be used as the hotkey for the item.  If you want to disable the use of
-ampersands for hotkeys in menu items (for instance, if you want an item to
-literally display an ampersand before a character), set the
-ampersandHotkeysInItems property to false.  For instance:
-lbMenu.ampersandHotkeysInItems = false;
-Note that ampersandHotkeysInItems must be set before adding menu items.
-
-You can call the SetItemHotkey() method to set a single hotkey to be used for
-a menu item or AddItemHotkey() to add an additional hotkey for an item in
-addition to any existing hotkeys it might already have.
-
-You can call AddAdditionalSelectItemKeys() to add additional keys that can be
-used to select any item (in addition to Enter).  That function takes a string
-of characters, and the keys are case-sensitive.  For example, to add the key E
-to select an item:
-lbMenu.AddAdditionalSelectItemKeys("E");
-To make a case-insensitive verison, both the uppercase and lowercase letter
-would need to be added, as in the following example for E:
-lbMenu.AddAdditionalSelectItemKeys("Ee");
-
-Also, after showing the menu & getting a value from the user (using the GetVal()
-function), the lastUserInput property will have the user's last keypress.
-
-This menu class also supports an optional "numbered mode", where each option is
-displayed with a number to the left (starting at 1), and the user is allowed to
-choose an option by typing the number of the item.  Numbered mode is disabled
-by default and can be enabled by setting the numberedMode property to true.
-For example:
-lbMenu.numberedMode = true;
-When numbered mode is enabled and the user starts typing a number, the menu will
-prompt the user for an item number.  Note that the prompt will be located on the
-line below the menu, so in addition to the menu's height, you'll also need an
-extra line on the screen to account for the item prompt.  In addition, when the
-user presses the enter key after the item number, a carriage return/line feed
-will be outputted, so in numbered mode, the menu's height should not go further
-than 2 lines below the console height.  Otherwise, the display of the menu will
-not be correct if the user decides not to enter a number.
-When numbered mode is enabled, you can specify the color used to display the
-item numbers.  For a non-selected item, set .colors.itemNumColor.  For selected items,
-set .colors.highlightedItemNumColor.  This is separate from the item color setting
-(.colors.itemColor).  For example:
-lbMenu.colors.itemNumColor = "\x01c"; // Use cyan for the item numbers for non-selected items.
-// For the selected item, use high cyan with a blue background for the item number
-lbMenu.colors.highlightedItemNumColor = "\x01" + "4\x01c\x01h";
-
-This menu also supports multiple options selected (by default, that is not enabled).
-To enable that, set the multiSelect property to true.  When enabled, the GetVal()
-method will return an array of the user's selections rather than a string (or null if
-the user aborted).  You can also set a limit on the number of items selected in
-multi-select mode by setting the maxNumSelections property.  The default value is -1,
-which means no limit (0 also means no limit).
-Example, with a limit:
-lbMenu.multiSelect = true;
-lbMenu.maxNumSelections = 5;
-
-Example usage:
-require("dd_lightbar_menu.js", "DDLightbarMenu");
-// Create a menu at position 1, 3 with width 45 and height of 10
-var lbMenu = new DDLightbarMenu(1, 3, 45, 10);
-// Add 12 items to the menu, each of which will return the text of the item
-for (var i = 0; i < 12; ++i)
-	lbMenu.Add("Item " + +(i+1), "Item " + +(i+1));
-// Set up the hotkey "s" to select the 2nd item
-lbMenu.SetItemHotkey(1, "s");
-// Show the menu and get the chosen item from the user
-var val = lbMenu.GetVal();
-// Output the chosen menu item
-console.print("\x01n\r\n");
-console.print("Value:" + val + ":, type: " + typeof(val) + "\r\n");
-console.pause();
-
-// Changing the normal item color to green & selected item color to bright green:
-lbMenu.colors.itemColor = "\x01n\x01g";
-lbMenu.colors.selectedItemColor = "\x01n\x01h\x01g";
-
-// Disabling the navigation wrap behavior:
-lbMenu.wrapNavigation = false;
-
-// If you want a particular character in an item's text highlighted with
-// a different color, you can put a & character immediately before it, as
-// long as it's not a space.  For instance, to highlight the "x" in "Exit":
-lbMenu.Add("E&xit", -1);
-
-// The last parameter to Add() is a boolean specifying whether the item text is
-// in UTF-8 format.
-// Adding an item where the text is known to be in UTF-8 format
-lbMenu.Add(someonesName, 1, null, true, true);
-
-To enable borders, set the borderEnabled property to true.  Example:
-lbMenu.borderEnabled = true;
-
-The menu object has an object called borderChars, which stores the characters used
-to draw the border.  you can change the characters used to draw the border by
-setting the following properties of the borderChars object:
-  upperLeft: The character to use for the upper-left corner
-  upperRight: The character to use for the upper-right corner
-  lowerLeft: The character to use for the lower-left corner
-  lowerRight: The character to use for the lower-right corner
-  top: The character to use for the top border
-  bottom: The character to use for the bottom border
-  left: The character to use for the left border
-  right: The character to use for the right border
-For example:
-lbMenu.borderChars.upperLeft = "\xDA"; // Single-line upper-left character
-Alternately, you can call the SetBorderChars() function and pass in a JS object
-with any or all of the above properties to set those values internally in the
-DDLightbarMenu object.
-
-If you want hotkeys to be case-sensitive, you can set the hotkeyCaseSensitive
-property to true (it is false by default).  For example:
-lbMenu.hotkeyCaseSensitive = true;
-
-To add additional key characters as quit keys (in addition to ESC), call
-AddAdditionalQuitKeys() with a string of characters.  For example:
-lbMenu.AddAdditionalQuitKeys("qQ");
-
-To clear the additional quit keys: ClearAdditionalQuitKeys()
-lbMenu.ClearAdditionalQuitKeys();
-
-Similarly for additional PageUp keys: AddAdditionalPageUpKeys and ClearAdditionalPageUpKeys
-For additional PageDown: AddAdditionalPageDownKeys and ClearAdditionalPageDownKeys
-For additional first page (like HOME): AddAdditionalFirstPageKeys and ClearAdditionalFirstPageKeys
-For additional last page (like END): AddAdditionalLastPageKeys and ClearAdditionalLastPageKeys
-
-
-To enable the border and set top and bottom border text:
-lbMenu.borderEnabled = true;
-lbMenu.topBorderText = "Options";
-lbMenu.bottomBorderText = "Enter = Select";
-
-
-For a more advanced usage, if you have another large list of items you want
-to use in the menu instead of the menu's own list of items, you can replace
-the NumItems and GetItem functions in the menu object and write your own
-versions that access a different list of items.  This can be useful, for instance,
-if you're working with a Synchronet messagebase (which may include a large number
-of messages), so you can avoid the time taken to add those items to a DDLightbarMenu.
-NumItems() needs to return the number of items in the list.  GetItem() takes an item
-index as a parameter and needs to return an item object that is compatible with
-DDLightbarMenu.  You can get a default item object by calling MakeItemWithRetval()
-or MakeItemWithTextAndRetval(), then change its text and retval properties as
-needed, then return the item object.  In the item object, the 'text' property
-is the text to display in the menu, and the 'retval' proprety is the value to return
-when the user chooses that item.
-An example (assuming the lightbar menu object is called lbMenu):
-lbMenu.NumItems = function() {
-	// Do your own thing to get the number of items in your list.
-	// ...
-	// Assuming myNumItems is the number of items in your list:
-	return myNumItems;
-};
-lbMenu.GetItem = function(pItemIndex) {
-	// Get a default item object from the menu with an initial return value of -1
-	var menuItemObj = this.MakeItemWithRetval(-1);
-	// Do your own thing to get the item text and return value for the menu.
-	// ...
-	// Assuming itemText is the text to display in the menu and itemRetval is
-	// the return value to return from the menu:
-	menuItemObj.text = itemText;
-	menuItemObj.retval = itemRetval;
-
-	// And if the text in the item is UTF-8, you must specify so as follows:
-	menuItemObj.textIsUTF8 = true;
-
-	return menuItemObj; // The DDLightbarMenu object will use this when displaying the menu
-};
-
-If you want to set the currently selected item before calling GetVal() to allow user input,
-you should call the SetSelectedItemIdx() function and pass the index to that.
-lbMenu.SetSelectedItemIdx(5);
-
-The property inputTimeoutMS sets the input timeout in milliseconds (defaults to 300000).
-lbMenu.inputTimeoutMS = 300000; // 300,000 milliseconds (5 minutes)
-
-The property mouseEnabled can be used to enable mouse support.  By default it is false.
-When mouse support is enabled, there can be problems inputting the ESC key from the user.
-lbMenu.mouseEnabled = true;
-
-For selecting an item, it may be desirable to validate whether a user should be allowed
-to select the item.  DDLightbarMenu has a member function it calls, ValidateSelectItem(),
-to do just that.  It takes the selected item's return value and returns a boolean to signify
-whether the user can select it.   By default, it just returns true (allowing the user to
-select any item).  When the user can't choose a value, your code should output why.
-To change its behavior, you can overwrite it as follows (assuming lbMenu
-is a DDLightbarMenu object):
-
-lbMenu.ValidateSelectItem = function(pItemRetval) {
-	// Should the user be able to select the item with the return val indicated
-	// by pItemRetval?
-	if (yourValidationCode(pItemRetval))
-		return true;
-	else
-	{
-		console.print("* Can't choose " + pItemRetval + " because blah blah blah!\r\n\x01p");
-		return false;
-	}
-}
-
-OnItemSelect is a function that is called when an item is selected, or toggled 
-if multi-select is enabled.
-
-Parameters:
- pItemRetval: The return value of the item selected
- pSelected: Boolean - Whether the item was selected or de-selected.  De-selection
-            is possible when multi-select is enabled.
-lbMenu.OnItemSelect = function(pItemRetval, pSelected)
-{
-	// Do something with pItemRetval.  pSelected tells whether the item was selected,
-	// or de-selected if multi-select is enabled.
-}
-
-The property exitOnItemSelect specifies whether or not to exit the input loop when an item is
-selected/submitted (i.e. with ENTER; not for toggling with multi-select).  This is true by
-default.  It can be desirable to set this to false in some situations, such as when you want a
-menu with a custom OnItemSelect() function specified and you want the menu to continue to
-be displayed allowing the user to select an item.
-lbMenu.exitOnItemSelect = false;
-
-OnItemNav is a function that is called when the user navigates to a new item (i.e., via
-the up or down arrow, PageUp, PageDown, Home, End, etc.).  Its parameters are the old
-item index and the new item index.
-lbMenu.OnItemNav = function(pOldItemIdx, pNewItemIdx) { }
-
-To have the menu object call OnItemNav() when it is first displayed to get the user's
-choice, set the callOnItemNavOnStartup property to true:
-lbMenu.callOnItemNavOnStartup = true;
-By default, it is false.
-
-The 'key down' behavior can be called explicitly, if needed, by calling the DoKeyDown() function.
-It takes 2 parameters: An object of selected item indexes (as passed to GetVal()) and, optionally,
-the pre-calculated number of items.
-lbMenu.DoKeyDown(pNumItems, pSelectedItemIndexes);
-
-
-For screen refreshing, DDLightbarMenu includes the function DrawPartial(), which can be used to
-redraw only a portion of the menu, specified by starting X & Y coordinates, width, and height.
-The starting X & Y coordinates are relative to the upper-left corner of the menu (not absolute
-screen coordinates) and start at (1, 1).  The function signature looks like this:
- DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelectedItemIndexes)
-The parameters:
- pStartX: The column of the character in the menu to start at
- pStartY: The row of the character in the menu to start at
- pWidth: The width of the content to draw
- pHeight: The height of the content to draw
- pSelectedItemIndexes: Optional - An object containing indexes of selected items
-
-Another function, DrawPartialAbs(), provies the same functionality but with absolute screen coordinates
-(also starting at (1, 1) in the upper-left corner):
- DrawPartialAbs(pStartX, pStartY, pWidth, pHeight, pSelectedItemIndexes)
-The parameters:
- pStartX: The column of the character in the menu to start at
-  pStartY: The row of the character in the menu to start at
- pWidth: The width of the content to draw
- pHeight: The height of the content to draw
- pSelectedItemIndexes: Optional - An object containing indexes of selected items
-
-
-Menu items can be marked not selectable by setting the isSelectable proprty of the item to false.
-Alternately, the menu function ToggleItemSelectable can be used for this purpose too:
-Parameters:
-- The index of the item to be toggled
-- Boolean: Whether or not the item should be selectable
-Example - Making the first item not selectable:
-lbMenu.ToggleItemSelectable(0, false);
-
-By default, DDLightbarMenu ignores the isSelectable attribute of items and considers all items
-selectable (for efficiency).  To enable usage of unselectable items, set the allowUnselectableItems
-property to true:
-lbMenu.allowUnselectableItems = true;
-*/
-
-"use strict";
-
-if (typeof(require) === "function")
-{
-	require("sbbsdefs.js", "K_UPPER");
-	require("mouse_getkey.js", "mouse_getkey");
-	require("userdefs.js", "USER_UTF8");
-	require("utf8_cp437.js", "utf8_cp437");
-}
-else
-{
-	load("sbbsdefs.js");
-	load("mouse_getkey.js");
-	load("userdefs.js");
-	load("utf8_cp437.js");
-}
-
-
-// Keyboard keys
-var KEY_ENTER = "\x0d";
-// PageUp & PageDown keys - Synchronet 3.17 as of about December 18, 2017
-// use CTRL-P and CTRL-N for PageUp and PageDown, respectively.  key_defs.js
-// defines them as KEY_PAGEUP and KEY_PAGEDN (key_defs.js is loaded by
-// sbbsdefs.js).
-//var KEY_ESC = ascii(27);
-
-// Box-drawing/border characters: Single-line
-var UPPER_LEFT_SINGLE = "\xDA";
-var HORIZONTAL_SINGLE = "\xC4";
-var UPPER_RIGHT_SINGLE = "\xBF";
-var VERTICAL_SINGLE = "\xB3";
-var LOWER_LEFT_SINGLE = "\xC0";
-var LOWER_RIGHT_SINGLE = "\xD9";
-var T_SINGLE = "\xC2";
-var LEFT_T_SINGLE = "\xC3";
-var RIGHT_T_SINGLE = "\xB4";
-var BOTTOM_T_SINGLE = "\xC1";
-var CROSS_SINGLE = "\xC5";
-// Box-drawing/border characters: Double-line
-var UPPER_LEFT_DOUBLE = "\xC9";
-var HORIZONTAL_DOUBLE = "\xCD";
-var UPPER_RIGHT_DOUBLE = "\xBB";
-var VERTICAL_DOUBLE = "\xBA";
-var LOWER_LEFT_DOUBLE = "\xC8";
-var LOWER_RIGHT_DOUBLE = "\xBC";
-var T_DOUBLE = "\xCB";
-var LEFT_T_DOUBLE = "\xCC";
-var RIGHT_T_DOUBLE = "\xB9";
-var BOTTOM_T_DOUBLE = "\xCA";
-var CROSS_DOUBLE = "\xCE";
-// Box-drawing/border characters: Vertical single-line with horizontal double-line
-var UPPER_LEFT_VSINGLE_HDOUBLE = "\xD5";
-var UPPER_RIGHT_VSINGLE_HDOUBLE = "\xB8";
-var LOWER_LEFT_VSINGLE_HDOUBLE = "\xD4";
-var LOWER_RIGHT_VSINGLE_HDOUBLE = "\xBE";
-// Other characters
-var CHECK_CHAR = "\xFB";
-var BLOCK1 = "\xB0"; // Dimmest block
-var BLOCK2 = "\xB1";
-var BLOCK3 = "\xB2";
-var BLOCK4 = "\xDB"; // Brightest block
-
-// Border types for a menu
-var BORDER_NONE = 0;
-var BORDER_SINGLE = 1;
-var BORDER_DOUBLE = 2;
-
-// DDLightbarMenu object contstructor
-//
-// Parameters:
-//  pX: Optional - The column (X) of the upper-left corner.  Defaults to 1.
-//  pY: Optional - The row (Y) of the upper-left corner.  Defaults to 1.
-//  pWidth: Optional - The width of the menu.  Defaults to 45.
-//  pHeight: Optional - The height of the menu.  Defaults to 10.
-function DDLightbarMenu(pX, pY, pWidth, pHeight)
-{
-	// Data members
-	this.items = [];
-	this.pos = {
-		x: 1,
-		y: 1
-	};
-	this.size = {
-		width: 45,
-		height: 10
-	};
-	this.scrollbarEnabled = false;
-	this.borderEnabled = false;
-	this.drawnAlready = false;
-	this.colors = {
-		itemColor: "\x01n\x01w\x01" + "4", // Can be either a string or an array specifying colors within the item
-		selectedItemColor: "\x01n\x01b\x01" + "7", // Can be either a string or an array specifying colors within the item
-		altItemColor: "\x01n\x01w\x01" + "4", // Alternate item color.  Can be either a string or an array specifying colors within the item
-		altSelectedItemColor: "\x01n\x01b\x01" + "7", // Alternate selected item color.  Can be either a string or an array specifying colors within the item
-		unselectableItemColor: "\x01n\x01b\x01h", // Can be either a string or an array specifying colors within the item
-		itemTextCharHighlightColor: "\x01y\x01h",
-		borderColor: "\x01n\x01b",
-		scrollbarScrollBlockColor: "\x01h\x01w",
-		scrollbarBGColor: "\x01h\x01k",
-		itemNumColor: "\x01n",
-		highlightedItemNumColor: "\x01n"
-	};
-	// Characters to use to draw the border
-	this.borderChars = {
-		upperLeft: UPPER_LEFT_DOUBLE,
-		upperRight: UPPER_RIGHT_DOUBLE,
-		lowerLeft: LOWER_LEFT_DOUBLE,
-		lowerRight: LOWER_RIGHT_DOUBLE,
-		top: HORIZONTAL_DOUBLE,
-		bottom: HORIZONTAL_DOUBLE,
-		left: VERTICAL_DOUBLE,
-		right: VERTICAL_DOUBLE
-	};
-	// Scrollbar information (characters, etc.)
-	this.scrollbarInfo = {
-		blockChar: BLOCK2,
-		BGChar: BLOCK1,
-		numSolidScrollBlocks: 0,
-		numNonSolidScrollBlocks: 0,
-		solidBlockLastStartRow: 0
-	};
-
-	this.selectedItemIdx = 0;
-	this.topItemIdx = 0;
-	this.wrapNavigation = true;
-	this.hotkeyCaseSensitive = false;
-	this.ampersandHotkeysInItems = true;
-	this.multiSelect = false;
-	this.maxNumSelections = -1; // -1 or 0 means no limit on the number of selections
-	this.multiSelectItemChar = CHECK_CHAR; // The character to display for a selected item in multi-select mode
-	this.numberedMode = false;
-	this.itemNumLen = 0; // For the length of the item numbers in numbered mode
-	this.additionalQuitKeys = ""; // A string of additional keys besides ESC to quit out of the menu
-	this.additionalPageUpKeys = ""; // A string of additional keys besides PageUp for page up
-	this.additionalPageDnKeys = ""; // A string of additional keys besides PageDown for page down
-	this.additionalFirstPageKeys = ""; // A string of additional keys besides HOME for going to the first page
-	this.additionalLastPageKeys = ""; // A string of additional keys besides END for going to the last page
-	this.additionalSelectItemKeys = ""; // A string of additional keys to select any item
-	this.topBorderText = ""; // Text to display in the top border
-	this.bottomBorderText = ""; // Text to display in the bottom border
-	this.lastUserInput = null; // The user's last keypress when the menu was shown/used
-	// nextDrawOnlyItemSubstr can be an object containing start & end properties, as
-	// indexes (end is one past last index) for drawing shortened versions of items on the
-	// next draw
-	this.nextDrawOnlyItemSubstr = null;
-	// nextDrawOnlyItems is an array specifying the indexes of the items to write
-	// on the next draw.  If this is empty, then all items on the page will be drawn.
-	this.nextDrawOnlyItems = [];
-
-	// This is a regex to do a case-insensitive test for Synchronet attribute
-	// codes in strings.
-	// For one that looks at the whole word having only Synchronet attribute
-	// codes, it would have ^ and $ around it, as in
-	// /^\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i
-	this.syncAttrRegex = /\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]/i;
-
-	// Whether or not to exit the input loop when an item is selected/submitted
-	// (i.e. with ENTER; not for toggling with multi-select)
-	this.exitOnItemSelect = true;
-
-	this.inputTimeoutMS = 300000; // Input timeout in ms
-	this.mouseEnabled = false;
-
-	// Whether or not to call OnItemNav() when the menu is first displayed
-	// to get the user's choice
-	this.callOnItemNavOnStartup = false;
-
-	// Whether or not to allow unselectable items (pay attention to the isSelectable attribute of items).
-	// Defaults to false, mainly for backwards compatibility.
-	this.allowUnselectableItems = false;
-
-	// Whether or not to allow ANSI behavior. Mainly for testing (this should be true).
-	this.allowANSI = true;
-
-	// Member functions
-	this.Add = DDLightbarMenu_Add;
-	this.Remove = DDLightbarMenu_Remove;
-	this.RemoveAllItems = DDLightbarMenu_RemoveAllItems;
-	this.NumItems = DDLightbarMenu_NumItems;
-	this.GetItem = DDLightbarMenu_GetItem;
-	this.ItemIsSelectable = DDLightbarMenu_ItemIsSelectable;
-	this.FindSelectableItemForward = DDLightbarMenu_FindSelectableItemForward;
-	this.FindSelectableItemBackward = DDLightbarMenu_FindSelectableItemBackward;
-	this.HasAnySelectableItems = DDLightbarMenu_HasAnySelectableItems;
-	this.ToggleItemSelectable = DDLightbarMenu_ToggleItemSelectable;
-	this.FirstSelectableItemIdx = DDLightbarMenu_FirstSelectableItemIdx;
-	this.LastSelectableItemIdx = DDLightbarMenu_LastSelectableItemIdx;
-	this.SetPos = DDLightbarMenu_SetPos;
-	this.SetSize = DDLightbarMenu_SetSize;
-	this.SetWidth = DDLightbarMenu_SetWidth;
-	this.SetHeight = DDLightbarMenu_SetHeight;
-	this.Draw = DDLightbarMenu_Draw;
-	this.DrawBorder = DDLightbarMenu_DrawBorder;
-	this.WriteItem = DDLightbarMenu_WriteItem;
-	this.WriteItemAtItsLocation = DDLightbarMenu_WriteItemAtItsLocation;
-	this.DrawPartial = DDLightbarMenu_DrawPartial;
-	this.DrawPartialAbs = DDLightbarMenu_DrawPartialAbs;
-	this.GetItemText = DDLightbarMenu_GetItemText;
-	this.ItemTextIsUTF8 = DDLightbarMenu_ItemTextIsUTF8;
-	this.Erase = DDLightbarMenu_Erase;
-	this.SetItemHotkey = DDLightbarMenu_SetItemHotkey;
-	this.AddItemHotkey = DDLightbarMenu_AddItemHotkey;
-	this.RemoveItemHotkey = DDLightbarMenu_RemoveItemHotkey;
-	this.RemoveItemHotkeys = DDLightbarMenu_RemoveItemHotkeys;
-	this.RemoveAllItemHotkeys = DDLightbarMenu_RemoveAllItemHotkeys;
-	this.GetMouseClickRegion = DDLightbarMenu_GetMouseClickRegion;
-	this.GetVal = DDLightbarMenu_GetVal;
-	this.DoKeyUp = DDLightbarMenu_DoKeyUp;
-	this.DoKeyDown = DDLightbarMenu_DoKeyDown;
-	this.DoPageUp = DDLightbarMenu_DoPageUp;
-	this.DoPageDown = DDLightbarMenu_DoPageDown;
-	this.NavMenuForNewSelectedItemTop = DDLightbarMenu_NavMenuForNewSelectedItemTop;
-	this.NavMenuForNewSelectedItemBottom = DDLightbarMenu_NavMenuForNewSelectedItemBottom;
-	this.SetBorderChars = DDLightbarMenu_SetBorderChars;
-	this.SetColors = DDLightbarMenu_SetColors;
-	this.GetNumItemsPerPage = DDLightbarMenu_GetNumItemsPerPage;
-	this.GetTopItemIdxOfLastPage = DDLightbarMenu_GetTopItemIdxOfLastPage;
-	this.CalcAndSetTopItemIdxToTopOfLastPage = DDLightbarMenu_CalcAndSetTopItemIdxToTopOfLastPage;
-	this.CalcPageForItemAndSetTopItemIdx = DDLightbarMenu_CalcPageForItemAndSetTopItemIdx;
-	this.AddAdditionalQuitKeys = DDLightbarMenu_AddAdditionalQuitKeys;
-	this.QuitKeysIncludes = DDLightbarMenu_QuitKeysIncludes;
-	this.ClearAdditionalQuitKeys = DDLightbarMenu_ClearAdditionalQuitKeys;
-	this.AddAdditionalPageUpKeys = DDLightbarMenu_AddAdditionalPageUpKeys;
-	this.PageUpKeysIncludes = DDLightbarMenu_PageUpKeysIncludes;
-	this.ClearAdditionalPageUpKeys = DDLightbarMenu_ClearAdditionalPageUpKeys;
-	this.AddAdditionalPageDownKeys = DDLightbarMenu_AddAdditionalPageDownKeys;
-	this.PageDownKeysIncludes = DDLightbarMenu_PageDownKeysIncludes;
-	this.ClearAdditionalPageDownKeys = DDLightbarMenu_ClearAdditionalPageDownKeys;
-	this.AddAdditionalFirstPageKeys = DDLightbarMenu_AddAdditionalFirstPageKeys;
-	this.FirstPageKeysIncludes = DDLightbarMenu_FirstPageKeysIncludes;
-	this.ClearAdditionalFirstPageKeys = DDLightbarMenu_ClearAdditionalFirstPageKeys;
-	this.AddAdditionalLastPageKeys = DDLightbarMenu_AddAdditionalLastPageKeys;
-	this.LastPageKeysIncludes = DDLightbarMenu_LastPageKeysIncludes;
-	this.ClearAdditionalLastPageKeys = DDLightbarMenu_ClearAdditionalLastPageKeys;
-	this.AddAdditionalSelectItemKeys = DDLightbarMenu_AddAdditionalSelectItemKeys;
-	this.SelectItemKeysIncludes = DDLightbarMenu_SelectItemKeysIncludes;
-	this.ClearAdditionalSelectItemKeys = DDLightbarMenu_ClearAdditionalSelectItemKeys;
-	this.DisplayInitialScrollbar = DDLightbarMenu_DisplayInitialScrollbar;
-	this.UpdateScrollbar = DDLightbarMenu_UpdateScrollbar;
-	this.CalcScrollbarBlocks = DDLightbarMenu_CalcScrollbarBlocks;
-	this.CalcScrollbarSolidBlockStartRow = DDLightbarMenu_CalcScrollbarSolidBlockStartRow;
-	this.UpdateScrollbarWithHighlightedItem = DDLightbarMenu_UpdateScrollbarWithHighlightedItem;
-	this.CanShowAllItemsInWindow = DDLightbarMenu_CanShowAllItemsInWindow;
-	this.MakeItemWithTextAndRetval = DDLightbarMenu_MakeItemWithTextAndRetval;
-	this.MakeItemWithRetval = DDLightbarMenu_MakeItemWithRetval;
-	this.ItemUsesAltColors = DDLightbarMenu_ItemUsesAltColors;
-	this.GetColorForItem = DDLightbarMenu_GetColorForItem;
-	this.GetSelectedColorForItem = DDLightbarMenu_GetSelectedColorForItem;
-	this.SetSelectedItemIdx = DDLightbarMenu_SetSelectedItemIdx;
-	this.GetBottomItemIdx = DDLightbarMenu_GetBottomItemIdx;
-	this.GetTopDisplayedItemPos = DDLightbarMenu_GetTopDisplayedItemPos;
-	this.GetBottomDisplayedItemPos = DDLightbarMenu_GetBottomDisplayedItemPos;
-	this.ScreenRowForItem = DDLightbarMenu_ScreenRowForItem;
-	this.ANSISupported = DDLightbarMenu_ANSISupported;
-
-	// ValidateSelectItem is a function for validating that the user can select an item.
-	// It takes the selected item's return value and returns a boolean to signify whether
-	// the user can select it.
-	this.ValidateSelectItem = function(pItemRetval) { return true; }
-
-	// OnItemSelect is a function that is called when an item is selected, or toggled 
-	// if multi-select is enabled.
-	//
-	// Parameters:
-	//  pItemRetval: The return value of the item selected
-	//  pSelected: Boolean - Whether the item was selected or de-selected.  De-selection
-	//             is possible when multi-select is enabled.
-	this.OnItemSelect = function(pItemRetval, pSelected) { }
-
-	// OnItemNav is a function that is called when the user navigates to
-	// new item (i.e., up/down arrow, pageUp, pageDown, home, end)
-	this.OnItemNav = function(pOldItemIdx, pNewItemIdx) { }
-
-	// Set some things based on the parameters passed in
-	if ((typeof(pX) == "number") && (typeof(pY) == "number"))
-		this.SetPos(pX, pY);
-	if (typeof(pWidth) == "number")
-		this.SetWidth(pWidth);
-	if (typeof(pHeight) == "number")
-		this.SetHeight(pHeight);
-}
-
-// Adds an item to the menu
-//
-// Parameters:
-//  pText: The text of the menu item
-//  pRetval: The value to return when the item is chosen.  Can be any type of value.
-//  pHotkey: Optional - A key to select the item when pressed by the user
-//  pSelectable: Optional - Whether or not the item is to be selectable. Defaults to true.
-//  pIsUTF8: Optional boolenan - Whether or not the text is UTF-8.  Defaults to false.
-function DDLightbarMenu_Add(pText, pRetval, pHotkey, pSelectable, pIsUTF8)
-{
-	var item = getDefaultMenuItem();
-	item.text = pText;
-	item.textIsUTF8 = (typeof(pIsUTF8) === "boolean" ? pIsUTF8 : false);
-	item.retval = (pRetval == undefined ? this.NumItems() : pRetval);
-	item.isSelectable = (typeof(pSelectable) === "boolean" ?  pSelectable : true);
-	// If pHotkey is defined, then use it as the hotkey.  Otherwise, if
-	// ampersandHotkeysInItems is true, look for the first & in the item text
-	// and if there's a non-space after it, then use that character as the
-	// hotkey.
-	if (typeof(pHotkey) == "string")
-		item.hotkeys += pHotkey;
-
-	if (this.ampersandHotkeysInItems)
-	{
-		var ampersandIndex = pText.indexOf("&");
-		if (ampersandIndex > -1)
-		{
-			// See if the next character is a space character.  If not, then
-			// don't count it in the length.
-			if (pText.length > ampersandIndex+1)
-			{
-				var nextChar = pText.substr(ampersandIndex+1, 1);
-				if (nextChar != " ")
-					item.hotkeys += nextChar;
-			}
-		}
-	}
-
-	this.items.push(item);
-}
-
-// Removes an item
-//
-// Parameters:
-//  pIdx: The index of the item to remove
-function DDLightbarMenu_Remove(pIdx)
-{
-	if ((typeof(pIdx) != "number") || (pIdx < 0) || (pIdx >= this.items.length))
-		return; // pIdx is invalid
-
-	this.items.splice(pIdx, 1);
-	if (this.items.length > 0)
-	{
-		if (this.selectedItemIdx >= this.items.length)
-			this.selectedItemIdx = this.items.length - 1;
-	}
-	else
-	{
-		this.selectedItemIdx = 0;
-		this.topItemIdx = 0;
-	}
-}
-
-// Removes all items
-function DDLightbarMenu_RemoveAllItems()
-{
-	this.items = [];
-	this.selectedItemIdx = 0;
-	this.topItemIdx = 0;
-}
-
-// Returns the number of items in the menu
-function DDLightbarMenu_NumItems()
-{
-	return this.items.length;
-}
-
-// Returns an item from the list
-//
-// Parameters:
-//  pItemIndex: The index of the item to get
-//
-// Return value: The item (or null if pItemIndex is invalid)
-function DDLightbarMenu_GetItem(pItemIndex)
-{
-	if ((pItemIndex < 0) || (pItemIndex >= this.NumItems()))
-		return null;
-	return this.items[pItemIndex];
-}
-
-// Returns whether an item is selectable
-//
-// Parameters:
-//  pItemIndex: The index of the item to check
-//
-// Return value: Boolean - Whether or not the item is selectable
-function DDLightbarMenu_ItemIsSelectable(pItemIndex)
-{
-	if ((pItemIndex < 0) || (pItemIndex >= this.NumItems()))
-		return false;
-
-	if (!this.allowUnselectableItems)
-		return true;
-
-	var item = this.GetItem(pItemIndex);
-	if (item == null || typeof(item) !== "object")
-		return false;
-	if (item.hasOwnProperty("isSelectable"))
-		return item.isSelectable;
-	else
-		return false;
-}
-
-// Finds a selectable menu item index going forward, starting at a given item index
-//
-// Parameters:
-//  pStartItemIdx: The index of the item to start at. This will be included in the search.
-//  pWrapAround: Boolean - Whether or not to wrap around. Defaults to false.
-//
-// Return value: The index of the next selectable item, or -1 if none is found.
-function DDLightbarMenu_FindSelectableItemForward(pStartItemIdx, pWrapAround)
-{
-	var numItems = this.NumItems();
-	if (typeof(pStartItemIdx) !== "number" || pStartItemIdx < 0 || pStartItemIdx >= numItems)
-		return -1;
-
-	if (!this.allowUnselectableItems)
-		return pStartItemIdx;
-
-	var wrapAround = (typeof(pWrapAround) === "boolean" ? pWrapAround : false);
-
-	var selectableItemIdx = -1;
-	var wrappedAround = false;
-	var onePastLastItemIdx = numItems;
-	for (var i = pStartItemIdx; i < onePastLastItemIdx && selectableItemIdx == -1; ++i)
-	{
-		var item = this.GetItem(i);
-		if (item.isSelectable)
-			selectableItemIdx = i;
-		else
-		{
-			if (i == pStartItemIdx - 1 && wrappedAround)
-				break;
-			else if (i == numItems-1 && wrapAround)
-			{
-				i = -1;
-				onePastLastItemIdx = pStartItemIdx;
-				wrappedAround = true;
-			}
-		}
-	}
-	return selectableItemIdx;
-}
-
-// Finds a selectable menu item index going backward, starting at a given item index
-//
-// Parameters:
-//  pStartItemIdx: The index of the item to start at. This will be included in the search.
-//  pWrapAround: Boolean - Whether or not to wrap around. Defaults to false.
-//
-// Return value: The index of the previous selectable item, or -1 if none is found.
-function DDLightbarMenu_FindSelectableItemBackward(pStartItemIdx, pWrapAround)
-{
-	var numItems = this.NumItems();
-	if (typeof(pStartItemIdx) !== "number" || pStartItemIdx < 0 || pStartItemIdx >= numItems)
-		return -1;
-
-	if (!this.allowUnselectableItems)
-		return pStartItemIdx;
-
-	var wrapAround = (typeof(pWrapAround) === "boolean" ? pWrapAround : false);
-
-	var selectableItemIdx = -1;
-	var wrappedAround = false;
-	for (var i = pStartItemIdx; i >= 0 && selectableItemIdx == -1; --i)
-	{
-		var item = this.GetItem(i);
-		if (item.isSelectable)
-			selectableItemIdx = i;
-		else
-		{
-			if (i == pStartItemIdx - 1 && wrappedAround)
-				break;
-			else if (i == numItems-1 && wrapAround)
-			{
-				i = this.NumItems() + 1;
-				onePastLastItemIdx = pStartItemIdx;
-				wrappedAround = true;
-			}
-		}
-	}
-	return selectableItemIdx;
-}
-
-// Returns whether there are any selectable items in the menu
-function DDLightbarMenu_HasAnySelectableItems(pNumItems)
-{
-	if (!this.allowUnselectableItems)
-		return true;
-
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	var anySelectable = false;
-	for (var i = 0; i < numItems && !anySelectable; ++i)
-		anySelectable = this.GetItem(i).isSelectable;
-	return anySelectable;
-}
-
-// Toggles whether an item is selectable
-//
-// Parameters:
-//  pItemIdx: The index of the item to toggle
-//  pSelectable: Boolean - Whether or not the item should be selectable
-function DDLightbarMenu_ToggleItemSelectable(pItemIdx, pSelectable)
-{
-	if (typeof(pItemIdx) !== "number" || pItemIdx < 0 || pItemIdx >= this.NumItems() || typeof(pSelectable) !== "boolean")
-		return;
-	this.GetItem(pItemIdx).isSelectable = false;
-}
-
-// Returns the index of the first electable item, or -1 if there is none.
-function DDLightbarMenu_FirstSelectableItemIdx(pNumItems)
-{
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	if (numItems == 0)
-		return -1;
-
-	if (!this.allowUnselectableItems)
-		return 0;
-
-	var selectableItemIdx = -1;
-	var anySelectable = false;
-	for (var i =0; i < numItems && selectableItemIdx == -1; ++i)
-	{
-		if (this.GetItem(i).isSelectable)
-			selectableItemIdx = i;
-	}
-	return selectableItemIdx;
-}
-
-// Returns the index of the last selectable item, or -1 if there is none.
-function DDLightbarMenu_LastSelectableItemIdx(pNumItems)
-{
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	if (numItems == 0)
-		return -1;
-
-	if (!this.allowUnselectableItems)
-		return numItems - 1;
-
-	var selectableItemIdx = -1;
-	var anySelectable = false;
-	for (var i = numItems-1; i >= 0 && selectableItemIdx == -1; --i)
-	{
-		if (this.GetItem(i).isSelectable)
-			selectableItemIdx = i;
-	}
-	return selectableItemIdx;
-}
-
-// Sets the menu's upper-left corner position
-//
-// Parameters:
-//  pX: The column (X) of the upper-left corner.
-//  pY: The row (Y) of the upper-left corner.
-function DDLightbarMenu_SetPos(pX, pY)
-{
-	if (typeof(pX) == "object")
-	{
-		if (pX.hasOwnProperty("x") && pX.hasOwnProperty("y"))
-		{
-			this.pos.x = pX.x;
-			this.pos.y = pX.y;
-		}
-		
-	}
-	else if ((typeof(pX) == "number") && (typeof(pY) == "number"))
-	{
-		this.pos.x = pX;
-		this.pos.y = pY;
-	}
-}
-
-// Sets the menu's size.
-//
-// Parameters:
-//  pSize: An object containing 'width' and 'height' members (numeric)
-function  DDLightbarMenu_SetSize(pSize)
-{
-	if (typeof(pSize) == "object")
-	{
-		if (pSize.hasOwnProperty("width") && pSize.hasOwnProperty("height") && (typeof(pSize.width) == "number") && (typeof(pSize.height) == "number"))
-		{
-			if ((pSize.width > 0) && (pSize.width <= console.screen_columns))
-				this.size.width = pSize.width;
-			if ((pSize.height > 0) && (pSize.height <= console.screen_rows))
-				this.size.height = pSize.height;
-		}
-	}
-}
-
-// Sets the menu's width
-//
-// Parameters:
-//  pWidth: The width of the menu
-function DDLightbarMenu_SetWidth(pWidth)
-{
-	if (typeof(pWidth) == "number")
-	{
-		if ((pWidth > 0) && (pWidth <= console.screen_columns))
-			this.size.width = pWidth;
-	}
-}
-
-// Sets the height of the menu
-//
-// Parameters:
-//  pHeight: The height of the menu
-function DDLightbarMenu_SetHeight(pHeight)
-{
-	if (typeof(pHeight) == "number")
-	{
-		if ((pHeight > 0) && (pHeight <= console.screen_rows))
-			this.size.height = pHeight;
-	}
-}
-
-// Draws the menu with all menu items.  The selected item will be highlighted.
-//
-// Parameters:
-//  pSelectedItemIndexes: An object that can contain multiple indexes of selected
-//                        items.  Only for multi-select mode.  These are used
-//                        for drawing a marking character in the item text.
-//  pDrawBorders: Optional boolean - Whether or not to draw the borders, if borders
-//                are enabled.  Defaults to true.
-//  pDrawScrollbar: Optional boolean - Whether or not to draw the scrollbar, if
-//                  the scrollbar is enabled.  Defaults to this.scrollbarEnabled, and the scrollbar
-//                  will only be drawn if not all items can be shown in a single page.
-//  pNumItems: Optional - A cached value for the number of menu items.  If not specified, this will
-//             call this.NumItems();
-function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar, pNumItems)
-{
-	var numMenuItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	if (this.ANSISupported())
-	{
-		var drawBorders = (typeof(pDrawBorders) == "boolean" ? pDrawBorders : true);
-		var drawScrollbar = (typeof(pDrawScrollbar) == "boolean" ? pDrawScrollbar : true);
-
-		var curPos = { x: this.pos.x, y: this.pos.y }; // For writing the menu items
-		var itemLen = this.size.width;
-		// If borders are enabled, then adjust the item length, starting x, and starting
-		// y accordingly, and draw the border.
-		if (this.borderEnabled)
-		{
-			itemLen -= 2;
-			++curPos.x;
-			++curPos.y;
-			if (drawBorders)
-				this.DrawBorder();
-		}
-		if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-			--itemLen; // Leave room for the scrollbar in the item lengths
-		// If the scrollbar is enabled & needed and we are to update it,
-		// then calculate the scrollbar blocks and update it on the screen.
-		if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow() && drawScrollbar)
-		{
-			this.CalcScrollbarBlocks();
-			if (!this.drawnAlready)
-				this.DisplayInitialScrollbar(this.pos.y);
-			else
-				this.UpdateScrollbarWithHighlightedItem(true);
-		}
-		// For numbered mode, we'll need to know the length of the longest item number
-		// so that we can use that space to display the item numbers.
-		if (this.numberedMode)
-		{
-			this.itemNumLen = numMenuItems.toString().length;
-			itemLen -= this.itemNumLen;
-			--itemLen; // Have a space for separation between the numbers and items
-		}
-
-		// Write the menu items, only up to the height of the menu
-		var numPossibleItems = (this.borderEnabled ? this.size.height - 2 : this.size.height);
-		var numItemsWritten = 0;
-		var writeTheItem = true;
-		for (var idx = this.topItemIdx; (idx < numMenuItems) && (numItemsWritten < numPossibleItems); ++idx)
-		{
-			writeTheItem = ((this.nextDrawOnlyItems.length == 0) || (this.nextDrawOnlyItems.indexOf(idx) > -1));
-			if (writeTheItem)
-			{
-				console.gotoxy(curPos.x, curPos.y);
-				var showMultiSelectMark = (this.multiSelect && (typeof(pSelectedItemIndexes) == "object") && pSelectedItemIndexes.hasOwnProperty(idx));
-				this.WriteItem(idx, itemLen, idx == this.selectedItemIdx, showMultiSelectMark, curPos.x, curPos.y);
-			}
-			++curPos.y;
-			++numItemsWritten;
-		}
-		// If there are fewer items than the height of the menu, then write blank lines to fill
-		// the rest of the height of the menu.
-		if (numItemsWritten < numPossibleItems)
-		{
-			var numberFormatStr = "%" + this.itemNumLen + "s ";
-			var itemFormatStr = "%-" + itemLen + "s";
-			for (; numItemsWritten < numPossibleItems; ++numItemsWritten)
-			{
-				writeTheItem = ((this.nextDrawOnlyItems.length == 0) || (this.nextDrawOnlyItems.indexOf(numItemsWritten) > -1));
-				if (writeTheItem)
-				{
-					console.gotoxy(curPos.x, curPos.y++);
-					console.attributes = "N";
-					if (this.numberedMode)
-						printf(numberFormatStr, "");
-					var itemText = addAttrsToString(format(itemFormatStr, ""), this.colors.itemColor);
-					console.print(itemText);
-				}
-			}
-		}
-	}
-	else
-	{
-		// ANSI mode disabled, or the user's terminal doesn't support ANSI
-		var numberedModeBackup = this.numberedMode;
-		this.numberedMode = true;
-		var itemLen = this.size.width;
-		// For numbered mode, we'll need to know the length of the longest item number
-		// so that we can use that space to display the item numbers.
-		this.itemNumLen = numMenuItems.toString().length;
-		itemLen -= this.itemNumLen;
-		--itemLen; // Have a space for separation between the numbers and items
-		console.attributes = "N";
-		for (var i = 0; i < numMenuItems; ++i)
-		{
-			var showMultiSelectMark = (this.multiSelect && (typeof(pSelectedItemIndexes) == "object") && pSelectedItemIndexes.hasOwnProperty(idx));
-			var itemText = this.GetItemText(i, itemLen, false, showMultiSelectMark);
-			// TODO: Once, it seemed the text must be shortened by 3 less than the console width or else
-			// it behaves like there's an extra CRLF, but 2 should be correct.
-			console.print(substrWithAttrCodes(itemText, 0, console.screen_columns-2) + "\x01n");
-			console.crlf();
-		}
-		this.numberedMode = numberedModeBackup;
-	}
-
-	this.drawnAlready = true;
-	this.nextDrawOnlyItemSubstr = null;
-	this.nextDrawOnlyItems = [];
-}
-
-// Draws the border around the menu items
-function DDLightbarMenu_DrawBorder()
-{
-	if (!this.borderEnabled)
-		return;
-
-	// Draw the border around the menu options
-	console.print("\x01n" + this.colors.borderColor);
-	// Upper border
-	console.gotoxy(this.pos.x, this.pos.y);
-	if (this.borderChars.hasOwnProperty("upperLeft") && (typeof(this.borderChars.upperLeft) == "string"))
-		console.print(this.borderChars.upperLeft);
-	else
-		console.print(" ");
-	var lineLen = this.size.width - 2;
-	if (this.borderChars.hasOwnProperty("top") && (typeof(this.borderChars.top) == "string"))
-	{
-		// Display the top border text (if any) in the top border.  Ensure the text
-		// length is no longer than the maximum possible length (lineLen).
-		var borderText = shortenStrWithAttrCodes(this.topBorderText, lineLen);
-		console.print("\x01n" + borderText + "\x01n" + this.colors.borderColor);
-		var remainingLineLen = lineLen - console.strlen(borderText);
-		for (var i = 0; i < remainingLineLen; ++i)
-			console.print(this.borderChars.top);
-	}
-	else
-	{
-		for (var i = 0; i < lineLen; ++i)
-			console.print(" ");
-	}
-	if (this.borderChars.hasOwnProperty("upperRight") && (typeof(this.borderChars.upperRight) == "string"))
-		console.print(this.borderChars.upperRight);
-	else
-		console.print(" ");
-	// Lower border
-	console.gotoxy(this.pos.x, this.pos.y+this.size.height-1);
-	if (this.borderChars.hasOwnProperty("lowerLeft") && (typeof(this.borderChars.lowerLeft) == "string"))
-		console.print(this.borderChars.lowerLeft);
-	else
-		console.print(" ");
-	var lineLen = this.size.width - 2;
-	if (this.borderChars.hasOwnProperty("bottom") && (typeof(this.borderChars.bottom) == "string"))
-	{
-		// Display the bottom border text (if any) in the bottom border.  Ensure the text
-		// length is no longer than the maximum possible length (lineLen).
-		var borderText = shortenStrWithAttrCodes(this.bottomBorderText, lineLen);
-		console.print("\x01n" + borderText + "\x01n" + this.colors.borderColor);
-		var remainingLineLen = lineLen - console.strlen(borderText);
-		for (var i = 0; i < remainingLineLen; ++i)
-			console.print(this.borderChars.bottom);
-	}
-	else
-	{
-		for (var i = 0; i < lineLen; ++i)
-			console.print(" ");
-	}
-	if (this.borderChars.hasOwnProperty("lowerRight") && (typeof(this.borderChars.lowerRight) == "string"))
-		console.print(this.borderChars.lowerRight);
-	else
-		console.print(" ");
-	// Side borders
-	var leftSideChar = " ";
-	var rightSideChar = " ";
-	if (this.borderChars.hasOwnProperty("left") && (typeof(this.borderChars.left) == "string"))
-		leftSideChar = this.borderChars.left;
-	if (this.borderChars.hasOwnProperty("right") && (typeof(this.borderChars.right) == "string"))
-		rightSideChar = this.borderChars.right;
-	lineLen = this.size.height - 2;
-	var lineNum = 1;
-	for (var lineNum = 1; lineNum <= lineLen; ++lineNum)
-	{
-		console.gotoxy(this.pos.x, this.pos.y+lineNum);
-		console.print(leftSideChar);
-		console.gotoxy(this.pos.x+this.size.width-1, this.pos.y+lineNum);
-		console.print(rightSideChar);
-	}
-}
-
-// Writes a single menu item
-//
-// Parameters:
-//  pIdx: The index of the item to write
-//  pItemLen: Optional - Calculated length of the item (in case the scrollbar is showing).
-//            If this is not given, then this will be calculated.
-//  pHighlight: Optional - Whether or not to highlight the item.  If this is not given,
-//              the item will be highlighted based on whether the current selected item
-//              matches the given index, pIdx.
-//  pSelected: Optional - Whether or not this item is selected (mainly intended for multi-select
-//             mode).  Defaults to false.  If true, then a mark character will be displayed
-//             at the end of the item's text.
-//  pScreenX: Optional - The horizontal screen coordinate of the start of the item
-//  pScreenY: Optional - The vertical screen coordinate of the start of the item
-function DDLightbarMenu_WriteItem(pIdx, pItemLen, pHighlight, pSelected, pScreenX, pScreenY)
-{
-	var itemText = this.GetItemText(pIdx, pItemLen, pHighlight, pSelected);
-	// If the text is UTF-8 and the user's terminal is UTF-8, then set the mode bit accordingly.
-	// If the text is UTF-8 and the user's terminal doesn't support UTF-8, convert the text to cp437.
-	var printModeBits = P_NONE;
-	if (this.ItemTextIsUTF8(pIdx))
-	{
-		if (console.term_supports(USER_UTF8))
-			printModeBits = P_UTF8;
-		else
-			itemText = utf8_cp437(itemText);
-	}
-	// If this.nextDrawOnlyItemSubstr is an object with start & end properties,
-	// then create a string that is shortened from itemText from those start & end
-	// indexes, and add color to it.
-	// Otherwise, just print the full item text.
-	if ((this.nextDrawOnlyItemSubstr != null) && (typeof(this.nextDrawOnlyItemSubstr) == "object") && this.nextDrawOnlyItemSubstr.hasOwnProperty("start") && this.nextDrawOnlyItemSubstr.hasOwnProperty("end") && (typeof(pScreenX) == "number") && (typeof(pScreenY) == "number"))
-	{
-		var len = this.nextDrawOnlyItemSubstr.end - this.nextDrawOnlyItemSubstr.start;
-		var shortenedText = substrWithAttrCodes(itemText, this.nextDrawOnlyItemSubstr.start, len);
-		console.gotoxy(pScreenX+this.nextDrawOnlyItemSubstr.start, pScreenY);
-		console.print(shortenedText + "\x01n", printModeBits);
-	}
-	else
-		console.print(itemText + "\x01n", printModeBits);
-}
-
-// Writes a menu item at its location on the menu.  This should only be called
-// if the item is on the current page.
-//
-// Parameters:
-//  pIdx: The index of the item to write
-//  pHighlight: Whether or not the item should be highlighted
-//  pSelected: Whether or not the item is selected
-function DDLightbarMenu_WriteItemAtItsLocation(pIdx, pHighlight, pSelected)
-{
-	if (this.borderEnabled)
-		console.gotoxy(this.pos.x+1, this.pos.y+pIdx-this.topItemIdx+1);
-	else
-		console.gotoxy(this.pos.x, this.pos.y+pIdx-this.topItemIdx);
-	this.WriteItem(pIdx, null, pHighlight, pSelected);
-}
-
-// Draws part of the menu, starting at a certain location within the menu and
-// with a given width & height (for screen refreshing).  The start X and Y location
-// are relative to the menu (not the screen), and they start at (1, 1) in the upper-left
-//
-// Parameters:
-//  pStartX: The column of the character in the menu to start at
-//  pStartY: The row of the character in the menu to start at
-//  pWidth: The width of the content to draw
-//  pHeight: The height of the content to draw
-//  pSelectedItemIndexes: Optional - An object containing indexes of selected items
-function DDLightbarMenu_DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelectedItemIndexes)
-{
-	// Sanity check the parameters
-	if (typeof(pStartX) !== "number" || typeof(pStartY) !== "number" || typeof(pWidth) !== "number" || typeof(pHeight) !== "number")
-		return;
-	if (pStartX < 1 || pStartX > this.size.width)
-		return;
-	if (pStartY < 1 || pStartY > this.size.height)
-		return;
-
-	// Fix the width & height if needed
-	var width = pWidth;
-	if (width > (this.size.width - pStartX + 1))
-		width = (this.size.width - pStartX + 1);
-	var height = pHeight;
-	if (height > (this.size.height - pStartY + 1))
-		height = (this.size.height - pStartY + 1);
-	/*
-	// Temporary
-	if (user.is_sysop)
-	{
-		console.print("\x01n\r\n");
-		printf("DrawPartial 1 - X, Y, width, height: %d, %d; %d, %d\r\n", pStartX, pStartY, width, height);
-		console.pause();
-	}
-	// End Temporary
-	*/
-
-	var selectedItemIndexes = { }; // For multi-select mode
-	if (typeof(pSelectedItemIndexes) == "object")
-		selectedItemIndexes = pSelectedItemIndexes;
-
-	// If borders are enabled, draw any border characters in the region first
-	// The X & Y locations are 1-based
-	var lastLineNum = (pStartY  + this.pos.y + height) - 1; // Last line # on the screen
-	if (lastLineNum > this.pos.y + this.size.height - 1)
-		lastLineNum = this.pos.y + this.size.height - 1;
-	if (this.borderEnabled)
-	{
-		var lastX = pStartX + width - 1;
-		for (var lineNum = pStartY + this.pos.y - 1; lineNum <= lastLineNum; ++lineNum)
-		{
-			// Top line
-			if (lineNum == this.pos.y)
-			{
-				console.print("\x01n" + this.colors.borderColor);
-				for (var posX = pStartX; posX <= lastX; ++posX)
-				{
-					console.gotoxy(posX, lineNum);
-					if (posX == this.pos.x)
-						console.print(this.borderChars.upperLeft);
-					else if (posX == this.pos.x + this.size.width - 1)
-						console.print(this.borderChars.upperRight);
-					else
-						console.print(this.borderChars.top);
-				}
-			}
-			// Bottom line
-			else if (lineNum == this.pos.y + this.size.height - 1)
-			{
-				console.print("\x01n" + this.colors.borderColor);
-				for (var posX = pStartX; posX <= lastX; ++posX)
-				{
-					console.gotoxy(posX, lineNum);
-					if (posX == this.pos.x)
-						console.print(this.borderChars.lowerLeft);
-					else if (posX == this.pos.x + this.size.width - 1)
-						console.print(this.borderChars.lowerRight);
-					else
-						console.print(this.borderChars.bottom);
-				}
-			}
-			// Somewhere between the top & bottom line
-			else
-			{
-				var printedBorderColor = false;
-				for (var posX = pStartX; posX <= lastX; ++posX)
-				{
-					console.gotoxy(posX, lineNum);
-					if (posX == this.pos.x)
-					{
-						if (!printedBorderColor)
-						{
-							console.print("\x01n" + this.colors.borderColor);
-							printedBorderColor = true;
-						}
-						console.print(this.borderChars.left);
-					}
-					else if (posX == this.pos.x + this.size.width - 1)
-					{
-						if (!printedBorderColor)
-						{
-							console.print("\x01n" + this.colors.borderColor);
-							printedBorderColor = true;
-						}
-						console.print(this.borderChars.right);
-					}
-				}
-			}
-		}
-	}
-	// Calculate the width and starting index of the menu items
-	// Note that pStartX is relative to the menu, not the screen
-	var itemLen = width;
-	var writeMenuItems = true; // Might not if the draw area only includes the scrollbar or border
-	var itemTxtStartIdx = pStartX - 1;
-	if (this.borderEnabled)
-	{
-		if (itemTxtStartIdx > 0)
-			--itemTxtStartIdx; // pStartX - 2
-		if (pStartX == 1)
-			--itemLen;
-		// Starts on 2 & width is 5: 2, 3, 4, 5, 6
-		var lastCol = this.pos.x + pStartX + width - 1;
-		if (this.pos.x + pStartX + width - 1 >= lastCol) // The last column drawn will contain the right border char
-			--itemLen;
-		if ((pStartX == 1 && width == 1) || pStartX == this.size.width)
-			writeMenuItems  = false;
-		else if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow() && pStartX == this.size.width-1)
-			writeMenuItems = false;
-	}
-	if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-	{
-		var scrollbarCol = this.borderEnabled ? this.pos.x + this.size.width - 2 : this.pos.x + this.size.width - 1;
-		// If the rightmost column is at or past the scrollbar column,
-		// then subtract from the item length so that we don't overwrite
-		// the scrollbar.
-		var rightmostCol = this.pos.x + pStartX + width - 2;
-		if (rightmostCol >= scrollbarCol)
-		{
-			var lenDiff = scrollbarCol - rightmostCol + 1; // The amount to subtract from the length
-			itemLen -= lenDiff;
-		}
-		if (!this.borderEnabled && pStartX == this.size.width)
-			writeMenuItems = false;
-		// Just draw the whole srollbar to ensure it's updated
-		this.DisplayInitialScrollbar(this.scrollbarInfo.solidBlockLastStartRow, this.scrollbarInfo.numSolidScrollBlocks);
-	}
-	if (itemTxtStartIdx < 0)
-		itemTxtStartIdx = 0;
-	// Write the menu items
-	if (writeMenuItems)
-	{
-		/*
-		// Temporary
-		if (user.is_sysop)
-		{
-			console.print("\x01n\r\n");
-			console.print("itemTxtStartIdx: " + itemTxtStartIdx + ", itemLen: " + itemLen + "\r\n");
-			console.pause();
-		}
-		// End Temporary
-		*/
-		var blankItemTextFormatStr = "\x01n%" + itemLen + "s";
-		for (var lineNum = pStartY + this.pos.y - 1; lineNum <= lastLineNum; ++lineNum)
-		{
-			var startX = pStartX;
-			// If borders are enabled, skip the top & bottom lines since borders were already drawn
-			if (this.borderEnabled)
-			{
-				if (lineNum == this.pos.y || lineNum == lastLineNum)
-					continue;
-				else
-				{
-					if (pStartX + this.pos.x - 1 == this.pos.x)
-						++startX;
-				}
-			}
-			// Write the menu item text
-			var itemIdx = this.topItemIdx + (lineNum - this.pos.y);
-			if (this.borderEnabled) --itemIdx;
-			var highlightItem = itemIdx == this.selectedItemIdx;
-			var itemText = this.GetItemText(itemIdx, null, highlightItem, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			//var shortenedText = substrWithAttrCodes(itemText, itemTxtStartIdx, itemLen);
-			var shortenedText = substrWithAttrCodes(itemText, itemTxtStartIdx, itemLen, true);
-			// If shortenedText is empty (perhaps there's no menu item for this line),
-			// then make shortenedText consist of all spaces at the proper length
-			if (shortenedText.length == 0)
-				shortenedText = format(blankItemTextFormatStr, "");
-			console.gotoxy(startX, lineNum);
-			console.print(shortenedText + "\x01n");
-		}
-	}
-}
-// Draws part of the menu, starting at a certain location within the menu and
-// with a given width & height (for screen refreshing).  For this version, the start X
-// and Y location are absolute on the screen.  They start at (1, 1) in the upper-left.
-//
-// Parameters:
-//  pStartX: The column of the character in the menu to start at
-//  pStartY: The row of the character in the menu to start at
-//  pWidth: The width of the content to draw
-//  pHeight: The height of the content to draw
-//  pSelectedItemIndexes: Optional - An object containing indexes of selected items
-function DDLightbarMenu_DrawPartialAbs(pStartX, pStartY, pWidth, pHeight, pSelectedItemIndexes)
-{
-	if (typeof(pStartX) !== "number" || typeof(pStartY) !== "number" || typeof(pWidth) !== "number" || typeof(pHeight) !== "number")
-		return;
-
-	// Calculate the start X & Y coordinates relative to the menu (1-based), and adjust height &
-	// width if necessary.  Then draw partial.
-	var height = pHeight;
-	var width = pWidth;
-	var startX = pStartX - this.pos.x + 1;
-	var startY = pStartY - this.pos.y + 1;
-	/*
-	// Temporary
-	if (user.is_sysop)
-	{
-		console.print("\x01n\r\n");
-		printf("Here 1 - X, Y, width, height: %d, %d; %d, %d\r\n", startX, startY, width, height);
-		console.pause();
-	}
-	// End Temporary
-	*/
-	if (startX < 1)
-	{
-		var XDiff = 1 - startX;
-		startX += XDiff;
-		width -= XDiff;
-	}
-	if (startY < 1)
-	{
-		var YDiff = 1 - startY;
-		startY += YDiff;
-		height -= YDiff;
-	}
-	/*
-	// Temporary
-	if (user.is_sysop)
-	{
-		console.print("\x01n\r\n");
-		printf("Here 2 - X, Y, width, height: %d, %d; %d, %d\r\n", startX, startY, width, height);
-		console.pause();
-	}
-	// End Temporary
-	*/
-	this.DrawPartial(startX, startY, width, height, pSelectedItemIndexes);
-}
-
-
-// Gets the text of a menu item with colors applied
-//
-// Parameters:
-//  pIdx: The index of the item to get
-//  pItemLen: Optional - Calculated length of the item (in case the scrollbar is showing).
-//            If this is not given, then this will be calculated.
-//  pHighlight: Optional - Whether or not to highlight the item.  If this is not given,
-//              the item will be highlighted based on whether the current selected item
-//              matches the given index, pIdx.
-//  pSelected: Optional - Whether or not this item is selected (mainly intended for multi-select
-//             mode).  Defaults to false.  If true, then a mark character will be displayed
-//             at the end of the item's text.
-function DDLightbarMenu_GetItemText(pIdx, pItemLen, pHighlight, pSelected)
-{
-	var itemText = "";
-	var numItems = this.NumItems();
-	if ((pIdx >= 0) && (pIdx < numItems))
-	{
-		var itemLen = 0;
-		if (typeof(pItemLen) === "number")
-			itemLen = pItemLen;
-		else
-		{
-			itemLen = this.size.width;
-			// If the scrollbar is enabled & we can't show all items in the window,
-			// then subtract 1 from itemLen to make room for the srollbar.
-			if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-				--itemLen;
-			// If borders are enabled, then subtract another 2 from itemLen to make
-			// room for the left & right borders
-			if (this.borderEnabled)
-				itemLen -= 2;
-			// For numbered mode, we'll need to know the length of the longest item number
-			// so that we can use that space to display the item numbers.
-			if (this.numberedMode)
-			{
-				this.itemNumLen = numItems.toString().length;
-				itemLen -= this.itemNumLen;
-				--itemLen; // Have a space for separation between the numbers and items
-			}
-		}
-
-		// Decide which color(s) to use for the item text
-		var menuItem = this.GetItem(pIdx);
-		var normalItemColor;
-		var selectedItemColor;
-		if (menuItem.itemColor != null)
-			normalItemColor = menuItem.itemColor;
-		else
-			normalItemColor = (menuItem.useAltColors ? this.colors.altItemColor : this.colors.itemColor);
-		if (menuItem.itemSelectedColor != null)
-			selectedItemColor = menuItem.itemSelectedColor;
-		else
-			selectedItemColor = (menuItem.useAltColors ? this.colors.altSelectedItemColor : this.colors.selectedItemColor);
-		var itemColor = "";
-		if (this.allowUnselectableItems && !menuItem.isSelectable)
-			itemColor = this.colors.unselectableItemColor;
-		else if (typeof(pHighlight) === "boolean")
-			itemColor = (pHighlight ? selectedItemColor : normalItemColor);
-		else
-			itemColor = (pIdx == this.selectedItemIdx ? selectedItemColor : normalItemColor);
-		var selected = (typeof(pSelected) == "boolean" ? pSelected : false);
-
-		// Get the item text
-		if ((typeof(itemColor) === "string" || Array.isArray(itemColor)) && itemColor.length > 0)
-		{
-			// Use strip_ctrl to ensure there are no attribute codes, since we will
-			// apply our own.  This might be only a temporary item returned by a
-			// replaced GetItem(), so we just have to strip_ctrl() it here.
-			itemText = strip_ctrl(menuItem.text);
-		}
-		else // Allow other colors in the text to be specified if the configured item color is empty
-			itemText = menuItem.text;
-		// Truncate the item text to the displayable item width
-		if (itemTextDisplayableLen(itemText, this.ampersandHotkeysInItems) > itemLen)
-			itemText = substrWithAttrCodes(itemText, 0, itemLen); //itemText = itemText.substr(0, itemLen);
-		// If the item text is empty, then fill it with spaces for the item length
-		// so that the line's colors/attributes will be applied for the whole line
-		// when written
-		if (strip_ctrl(itemText).length == 0)
-			itemText = format("%" + itemLen + "s", "");
-		// Add the item color to the item text
-		itemText = addAttrsToString(itemText, itemColor);
-		// If ampersandHotkeysInItems is true, see if there's an ampersand in
-		// the item text.  If so, we'll want to highlight the next character
-		// with a different color.
-		if (this.ampersandHotkeysInItems)
-		{
-			var ampersandIndex = itemText.indexOf("&");
-			if (ampersandIndex > -1)
-			{
-				// See if the next character is a space character.  If not, then remove
-				// the ampersand and highlight the next character in the text.
-				if (itemText.length > ampersandIndex+1)
-				{
-					var nextChar = itemText.substr(ampersandIndex+1, 1);
-					if (nextChar != " ")
-					{
-						itemText = itemText.substr(0, ampersandIndex) + this.colors.itemTextCharHighlightColor
-								 + nextChar + "\x01n" + itemColor + itemText.substr(ampersandIndex+2);
-					}
-				}
-			}
-		}
-		// If the item is selected, then display a check mark at the end of the item text.
-		if (selected)
-		{
-			var itemTextLen = itemTextDisplayableLen(itemText, this.ampersandHotkeysInItems);
-			if (itemTextLen < this.size.width)
-			{
-				var numSpaces = itemLen - itemTextLen - 2;
-				// Kludge? If numSpaces is positive, append a space and then the selected
-				// character,  Otherwise, we'll need to replace the last character in
-				// itemText with the selected character.
-				if (numSpaces > 0)
-					itemText += format("%" + numSpaces + "s %s", "", this.multiSelectItemChar);
-				else
-					itemText = itemText.substr(0, itemText.length-1) + this.multiSelectItemChar;
-			}
-			else
-			{
-				// itemText should already be shortened to only the menu width, so
-				// take 2 characters off the end and add a space and mark character
-				itemText = itemText.substr(0, itemText.length-2) + " " + this.multiSelectItemChar;
-			}
-		}
-		// Ensure the item text fills the width of the menu (in case there's a
-		// background color, it should be used for the entire width of the item
-		// text).  Then write the item.
-		var currentTextLen = itemTextDisplayableLen(itemText, this.ampersandHotkeysInItems);
-		if (currentTextLen < itemLen)
-			itemText += format("%" + +(itemLen-currentTextLen) + "s", ""); // Append spaces to the end of itemText
-		// If in numbered mode and the item is selectable, prepend the item number to the front of the item text.
-		if (this.numberedMode && menuItem.isSelectable)
-		{
-			if (this.itemNumLen == 0)
-				this.itemNumLen = numItems.toString().length;
-			var numColor = "\x01n" + this.colors.itemNumColor;
-			if (typeof(pHighlight) === "boolean")
-				numColor = (pHighlight ? this.colors.highlightedItemNumColor : this.colors.itemNumColor);
-			else
-				numColor = (pIdx == this.selectedItemIdx ? this.colors.highlightedItemNumColor : this.colors.itemNumColor);
-			itemText = format("\x01n" + numColor + "%" + this.itemNumLen + "d \x01n", pIdx+1) + itemText;
-		}
-	}
-	return itemText;
-}
-
-// Returns whether or not an item's text is UTF-8, as specified in the item.
-//
-// Parameters:
-//  pIdx: The index of the item
-//
-// Return value: Whether or not the item's text is UTF-8
-function DDLightbarMenu_ItemTextIsUTF8(pIdx)
-{
-	if (typeof(pIdx) !== "number")
-		return false;
-	if (pIdx < 0 || pIdx >= this.NumItems())
-		return false;
-
-	return this.GetItem(pIdx).textIsUTF8;
-}
-
-// Erases the menu - Draws black (normal color) where the menu was
-function DDLightbarMenu_Erase()
-{
-	var formatStr = "%" + this.size.width + "s"; // For use with printf()
-	console.attributes = "N";
-	var curPos = { x: this.pos.x, y: this.pos.y };
-	for (var i = 0; i < this.size.height; ++i)
-	{
-		console.gotoxy(curPos.x, curPos.y++);
-		printf(formatStr, "");
-	}
-	console.gotoxy(curPos);
-}
-
-// Sets a hotkey for a menu item
-//
-// Parameters:
-//  pIdx: The index of the menu item
-//  pHotkey: The hotkey to set for the menu item
-function DDLightbarMenu_SetItemHotkey(pIdx, pHotkey)
-{
-	if ((typeof(pIdx) == "number") && (pIdx >= 0) && (pIdx < this.items.length) && (typeof(pHotkey) == "string"))
-		this.items[pIdx].hotkeys = pHotkey;
-}
-
-// Adds a hotkey for a menu item (in addition to the item's other hotkeys)
-//
-// Parameters:
-//  pIdx: The index of the menu item
-//  pHotkey: The hotkey to add for the menu item
-function DDLightbarMenu_AddItemHotkey(pIdx, pHotkey)
-{
-	if ((typeof(pIdx) == "number") && (pIdx >= 0) && (pIdx < this.items.length) && (typeof(pHotkey) == "string") && (this.items[pIdx].hotkeys.indexOf(pHotkey) == -1))
-		this.items[pIdx].hotkeys += pHotkey;
-}
-
-// Removes a specific hotkey from an item.
-//
-// Parameters:
-//  pIdx: The index of the item
-//  pHotkey: The hotkey to remove from the item
-function DDLightbarMenu_RemoveItemHotkey(pIdx, pHotkey)
-{
-	if ((typeof(pIdx) == "number") && (pIdx >= 0) && (pIdx < this.items.length))
-	{
-		var hotkeyIdx = this.items[pIdx].hotkeys.indexOf(pHotkey);
-		while (hotkeyIdx > -1)
-		{
-			this.items[pIdx].hotkeys = this.items[pIdx].hotkeys.substr(0, hotkeyIdx) + this.items[pIdx].hotkeys.substr(hotkeyIdx+1);
-			hotkeyIdx = this.items[pIdx].hotkeys.indexOf(pHotkey);
-		}
-	}
-}
-
-// Removes all hotkeys for an item
-//
-// Parameters:
-//  pIdx: The index of the item
-function  DDLightbarMenu_RemoveItemHotkeys(pIdx)
-{
-	if ((typeof(pIdx) == "number") && (pIdx >= 0) && (pIdx < this.items.length))
-		this.items[pIdx].hotkeys = "";
-}
-
-// Removes the hotkeys from all items
-function DDLightbarMenu_RemoveAllItemHotkeys()
-{
-	for (var i = 0; i < this.items.length; ++i)
-		this.items[i].hotkeys = "";
-}
-
-// Returns an object specifying the mouse valid click region for the menu,
-// with properties left, right, top, and bottom.
-function DDLightbarMenu_GetMouseClickRegion()
-{
-	var clickRegion = {
-		left: this.pos.x,
-		right: this.pos.x + this.size.width - 1,
-		top: this.pos.y,
-		bottom: this.pos.y + this.size.height - 1
-	};
-	if (this.borderEnabled)
-	{
-		++clickRegion.left;
-		++clickRegion.top;
-		--clickRegion.right;
-		--clickRegion.bottom;
-	}
-	return clickRegion;
-}
-
-// Waits for user input, optionally drawing the menu first.
-//
-// Parameters:
-//  pDraw: Optional - Whether or not to draw the menu first.  By default, the
-//         menu will be drawn first.
-//  pSelectedItemIndexes: Optional - An object containing indexes of selected items
-function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
-{
-	this.lastUserInput = null;
-
-	var numItems = this.NumItems();
-	if (numItems == 0)
-		return null;
-
-	// If allowing unselectable items, then make sure there are selectable items before
-	// doing the input loop (and if not, return null).  If there are selectable items,
-	// make sure the current selected item is selectable (if not, go to the next one).
-	if (this.allowUnselectableItems)
-	{
-		if (this.HasAnySelectableItems())
-		{
-			if (!this.ItemIsSelectable(this.selectedItemIdx))
-			{
-				var nextSelectableItemIdx = this.FindSelectableItemForward(this.selectedItemIdx+1, true);
-				// nextSelectableItemIdx should be valid since we know there are selectable items
-				if (nextSelectableItemIdx > -1 && nextSelectableItemIdx != this.selectedItemIdx)
-				{
-					this.selectedItemIdx = nextSelectableItemIdx;
-					this.CalcPageForItemAndSetTopItemIdx(this.GetNumItemsPerPage(), numItems);
-				}
-			}
-		}
-		else // No selectable items
-			return null;
-	}
-
-	if (typeof(this.lastMouseClickTime) == "undefined")
-		this.lastMouseClickTime = -1;
-
-	var draw = (typeof(pDraw) == "boolean" ? pDraw : true);
-	if (draw)
-	{
-		this.Draw(pSelectedItemIndexes, null, null, numItems);
-		if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-			this.DisplayInitialScrollbar(this.scrollbarInfo.solidBlockLastStartRow);
-	}
-
-	if (this.callOnItemNavOnStartup && typeof(this.OnItemNav) === "function")
-		this.OnItemNav(0, this.selectedItemIdx);
-
-	var selectedItemIndexes = { }; // For multi-select mode
-	if (typeof(pSelectedItemIndexes) == "object")
-		selectedItemIndexes = pSelectedItemIndexes;
-	if (this.ANSISupported())
-	{
-		// User input loop
-		var userChoices = null; // For multi-select mode
-		var retVal = null; // For single-choice mode
-		// mouseInputOnly_continue specifies whether to continue to the
-		// next iteration if the mouse was clicked & there's no need to
-		// process user input further
-		var mouseInputOnly_continue = false;
-		var continueOn = true;
-		while (continueOn)
-		{
-			if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-				this.UpdateScrollbarWithHighlightedItem();
-
-			mouseInputOnly_continue = false;
-
-			// TODO: With mouse_getkey(), it seems you need to press ESC twice
-			// to get the ESC key and exit the menu
-			var inputMode = K_NOECHO|K_NOSPIN|K_NOCRLF;
-			var mk = null; // Will be used for mouse support
-			var mouseNoAction = false;
-			if (this.mouseEnabled)
-			{
-				mk = mouse_getkey(inputMode, this.inputTimeoutMS > 1 ? this.inputTimeoutMS : undefined, this.mouseEnabled);
-				if (mk.mouse !== null)
-				{
-					// See if the user clicked anywhere in the region where items are
-					// listed or the scrollbar
-					var clickRegion = this.GetMouseClickRegion();
-					// Button 0 is the left/main mouse button
-					if (mk.mouse.press && (mk.mouse.button == 0) && (mk.mouse.motion == 0) &&
-						(mk.mouse.x >= clickRegion.left) && (mk.mouse.x <= clickRegion.right) &&
-						(mk.mouse.y >= clickRegion.top) && (mk.mouse.y <= clickRegion.bottom))
-					{
-						var isDoubleClick = ((this.lastMouseClickTime > -1) && (system.timer - this.lastMouseClickTime <= 0.4));
-
-						// If the scrollbar is enabled, then see if the mouse click was
-						// in the scrollbar region.  If below the scrollbar bright blocks,
-						// then we'll want to do a PageDown.  If above the scrollbar bright
-						// blocks, then we'll want to do a PageUp.
-						var scrollbarX = this.pos.x + this.size.width - 1;
-						if (this.borderEnabled)
-							--scrollbarX;
-						if ((mk.mouse.x == scrollbarX) && this.scrollbarEnabled)
-						{
-							var scrollbarSolidBlockEndRow = this.scrollbarInfo.solidBlockLastStartRow + this.scrollbarInfo.numSolidScrollBlocks - 1;
-							if (mk.mouse.y < this.scrollbarInfo.solidBlockLastStartRow)
-								this.lastUserInput = KEY_PAGEUP;
-							else if (mk.mouse.y > scrollbarSolidBlockEndRow)
-								this.lastUserInput = KEY_PAGEDN;
-							else
-							{
-								// Mouse click no-action
-								// TODO: Can we detect if they're holding the mouse down
-								// and scroll while the user holds the mouse & scrolls on
-								// the scrollbar?
-								this.lastUserInput = "";
-								mouseNoAction = true;
-								mouseInputOnly_continue = true;
-							}
-						}
-						else
-						{
-							// The user didn't click on the scrollbar or the scrollbar
-							// isn't enabled.
-							// For a double-click, if multi-select is enabled, set the
-							// last user input to a space to select/de-select the item.
-							if (isDoubleClick)
-							{
-								if (this.multiSelect)
-									this.lastUserInput = " ";
-								else
-								{
-									// No mouse action
-									this.lastUserInput = "";
-									mouseNoAction = true;
-									mouseInputOnly_continue = true;
-								}
-							}
-							else
-							{
-								// Make the clicked-on item the currently highlighted
-								// item.  Only select the item if the index is valid.
-								var topItemY = (this.borderEnabled ? this.pos.y + 1 : this.pos.y);
-								var distFromTopY = mk.mouse.y - topItemY;
-								var itemIdx = this.topItemIdx + distFromTopY;
-								if ((itemIdx >= 0) && (itemIdx < this.NumItems()))
-								{
-									this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-									this.selectedItemIdx = itemIdx;
-									this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-								}
-								// Don't have the later code do anything
-								this.lastUserInput = "";
-								mouseNoAction = true;
-								mouseInputOnly_continue = true;
-							}
-						}
-
-						this.lastMouseClickTime = system.timer;
-					}
-					else
-					{
-						// The mouse click is outside the click region.  Set the appropriate
-						// variables for mouse no-action.
-						// TODO: Perhaps this may also need to be done in some places above
-						// where no action needs to be taken
-						this.lastUserInput = "";
-						mouseNoAction = true;
-						mouseInputOnly_continue = true;
-					}
-				}
-				else
-				{
-					// mouse is null, so a keybaord key must have been pressed
-					this.lastUserInput = mk.key;
-				}
-			}
-			else // this.mouseEnabled is false
-				this.lastUserInput = getKeyWithESCChars(inputMode, this.inputTimeoutMS);
-
-			// If no further input processing needs to be done due to a mouse click
-			// action, then continue to the next loop iteration.
-			if (mouseInputOnly_continue)
-				continue;
-
-			// Take the appropriate action based on the user's last input/keypress
-			if ((this.lastUserInput == KEY_ESC) || this.QuitKeysIncludes(this.lastUserInput) || console.aborted)
-			{
-				// Only exit if there was not a no-action mouse click
-				// TODO: Is this logic good and clean?
-				var goAheadAndExit = true;
-				if (mk !== null && mk.mouse !== null)
-				{
-					goAheadAndExit = !mouseNoAction; // Only really needed with an input timer?
-				}
-				if (goAheadAndExit)
-				{
-					continueOn = false;
-					// Ensure any returned choice objects are null/empty to signal
-					// that the user aborted
-					userChoices = null; // For multi-select mode
-					selectedItemIndexes = { }; // For multi-select mode
-					retVal = null; // For single-choice mode
-				}
-			}
-			else if ((this.lastUserInput == KEY_UP) || (this.lastUserInput == KEY_LEFT))
-				this.DoKeyUp(selectedItemIndexes, numItems);
-			else if ((this.lastUserInput == KEY_DOWN) || (this.lastUserInput == KEY_RIGHT))
-				this.DoKeyDown(selectedItemIndexes, numItems);
-			else if (this.lastUserInput == KEY_PAGEUP || this.PageUpKeysIncludes(this.lastUserInput))
-				this.DoPageUp(selectedItemIndexes, numItems);
-			else if (this.lastUserInput == KEY_PAGEDN || this.PageDownKeysIncludes(this.lastUserInput))
-				this.DoPageDown(selectedItemIndexes, numItems);
-			else if (this.lastUserInput == KEY_HOME || this.FirstPageKeysIncludes(this.lastUserInput))
-			{
-				// Go to the first item in the list
-				var firstSelectableItemIdx = this.FindSelectableItemForward(0, false);
-				if (this.selectedItemIdx > firstSelectableItemIdx)
-					this.NavMenuForNewSelectedItemTop(firstSelectableItemIdx, this.GetNumItemsPerPage(), numItems, selectedItemIndexes);
-			}
-			else if (this.lastUserInput == KEY_END || this.LastPageKeysIncludes(this.lastUserInput))
-			{
-				// Go to the last item in the list
-				var lastSelectableItem = this.FindSelectableItemBackward(numItems-1, false);
-				if (this.selectedItemIdx < lastSelectableItem)
-					this.NavMenuForNewSelectedItemBottom(lastSelectableItem, this.GetNumItemsPerPage(), numItems, selectedItemIndexes, true);
-			}
-			// Enter key or additional select-item key: Select the item & quit out of the input loop
-			else if ((this.lastUserInput == KEY_ENTER) || (this.SelectItemKeysIncludes(this.lastUserInput)))
-			{
-				// Let the user select the item if ValidateSelectItem() returns true
-				var allowSelectItem = true;
-				if (typeof(this.ValidateSelectItem) === "function")
-					allowSelectItem = this.ValidateSelectItem(this.GetItem(this.selectedItemIdx).retval);
-				if (allowSelectItem)
-				{
-					// If multi-select is enabled and if the user hasn't made any choices,
-					// then add the current item to the user choices.  Otherwise, choose
-					// the current item.  Then exit.
-					if (this.multiSelect)
-					{
-						if (Object.keys(selectedItemIndexes).length == 0)
-							selectedItemIndexes[+(this.selectedItemIdx)] = true;
-					}
-					else
-						retVal = this.GetItem(this.selectedItemIdx).retval;
-
-					// Run the OnItemSelect event function
-					if (typeof(this.OnItemSelect) === "function")
-						this.OnItemSelect(retVal, true);
-
-					// Exit the input loop if this.exitOnItemSelect is set to true
-					if (this.exitOnItemSelect)
-						continueOn = false;
-				}
-			}
-			else if (this.lastUserInput == " ") // Add the current item to multi-select
-			{
-				// Add the current item to multi-select if multi-select is enabled
-				if (this.multiSelect)
-				{
-					// Only let the user select the item if ValidateSelectItem() returns true
-					var allowSelectItem = true;
-					if (typeof(this.ValidateSelectItem) === "function")
-						allowSelectItem = this.ValidateSelectItem(this.GetItem(this.selectedItemIdx).retval);
-					if (allowSelectItem)
-					{
-						var added = false; // Will be true if added or false if deleted
-						if (selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)))
-							delete selectedItemIndexes[+(this.selectedItemIdx)];
-						else
-						{
-							var addIt = true;
-							if (this.maxNumSelections > 0)
-								addIt = (Object.keys(selectedItemIndexes).length < this.maxNumSelections);
-							if (addIt)
-							{
-								selectedItemIndexes[+(this.selectedItemIdx)] = true;
-								added = true;
-							}
-						}
-
-						// Run the OnItemSelect event function
-						if (typeof(this.OnItemSelect) === "function")
-						{
-							//this.OnItemSelect = function(pItemRetval, pSelected) { }
-							this.OnItemSelect(this.GetItem(this.selectedItemIdx).retval, added);
-						}
-
-						// Draw a character next to the item if it's selected, or nothing if it's not selected
-						var XPos = this.pos.x + this.size.width - 2;
-						var YPos = this.pos.y+(this.selectedItemIdx-this.topItemIdx);
-						if (this.borderEnabled)
-						{
-							--XPos;
-							++YPos;
-						}
-						if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
-							--XPos;
-						console.gotoxy(XPos, YPos);
-						if (added)
-						{
-							// If the item color is an array, then default to a color string here
-							var itemColor = this.GetColorForItem(this.selectedItemIdx, true);
-							if (Array.isArray(itemColor))
-							{
-								var bkgColor = getBackgroundAttrAtIdx(itemColor, this.size.width-1);
-								itemColor = "\x01n\x01h\x01g" + bkgColor;
-							}
-							console.print(itemColor + " " + this.multiSelectItemChar + "\x01n");
-						}
-						else
-						{
-							// Display the last 2 characters of the regular item text
-							var itemText = this.GetItemText(this.selectedItemIdx, null, true, false);
-							var textToPrint = substrWithAttrCodes(itemText, console.strlen(itemText)-2, 2);
-							console.print(textToPrint + "\x01n");
-						}
-					}
-				}
-			}
-			// For numbered mode, if the user enters a number, allow the user to
-			// choose an item by typing its number.
-			else if (/[0-9]/.test(this.lastUserInput) && this.numberedMode)
-			{
-				var originalCurpos = console.getxy();
-
-				// Put the user's input back in the input buffer to
-				// be used for getting the rest of the message number.
-				console.ungetstr(this.lastUserInput);
-				// Move the cursor to the bottom of the screen and
-				// prompt the user for the message number.
-				var promptX = this.pos.x;
-				var promptY = this.pos.y+this.size.height;
-				console.gotoxy(promptX, promptY);
-				printf("\x01n%" + this.size.width + "s", ""); // Blank out what might be on the screen already
-				console.gotoxy(promptX, promptY);
-				console.print("\x01cItem #: \x01h");
-				var userEnteredItemNum = console.getnum(numItems);
-				// Blank out the input prompt
-				console.gotoxy(promptX, promptY);
-				printf("\x01n%" + this.size.width + "s", "");
-				// If the user entered a number, then get that item's return value
-				// and stop the input loop.
-				if (userEnteredItemNum > 0)
-				{
-					var oldSelectedItemIdx = this.selectedItemIdx;
-					this.selectedItemIdx = userEnteredItemNum-1;
-					if (this.multiSelect)
-					{
-						if (selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)))
-							delete selectedItemIndexes[+(this.selectedItemIdx)];
-						else
-						{
-							var addIt = true;
-							if (this.maxNumSelections > 0)
-								addIt = (Object.keys(selectedItemIndexes).length < this.maxNumSelections);
-							if (addIt)
-								selectedItemIndexes[+(this.selectedItemIdx)] = true;
-						}
-					}
-					else
-					{
-						retVal = this.GetItem(this.selectedItemIdx).retval;
-						continueOn = false;
-					}
-					// If the item typed by the user is different than the current selected item
-					// index, then refresh the selected item on the menu (if they're visible).
-					// If multi-select mode is enabled, also toggle the checkmark in the item text.
-					if (this.selectedItemIdx != oldSelectedItemIdx)
-					{
-						if (this.ScreenRowForItem(oldSelectedItemIdx) > -1)
-						{
-							var oldIsSelected = selectedItemIndexes.hasOwnProperty(oldSelectedItemIdx);
-							this.WriteItemAtItsLocation(oldSelectedItemIdx, false, oldIsSelected);
-						}
-						if (this.ScreenRowForItem(this.selectedItemIdx) > -1)
-						{
-							var newIsSelected = selectedItemIndexes.hasOwnProperty(this.selectedItemIdx);
-							this.WriteItemAtItsLocation(this.selectedItemIdx, true, newIsSelected);
-						}
-					}
-
-					if (typeof(this.OnItemNav) === "function")
-						this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-				}
-				else
-					console.gotoxy(originalCurpos); // Move the cursor back where it was
-			}
-			else
-			{
-				// See if the user pressed a hotkey set for one of the items.  If so,
-				// then choose that item.
-				for (var i = 0; i < numItems; ++i)
-				{
-					var theItem = this.GetItem(i);
-					for (var h = 0; h < theItem.hotkeys.length; ++h)
-					{
-						var userPressedHotkey = false;
-						if (this.hotkeyCaseSensitive)
-							userPressedHotkey = (this.lastUserInput == theItem.hotkeys.charAt(h));
-						else
-							userPressedHotkey = (this.lastUserInput.toUpperCase() == theItem.hotkeys.charAt(h).toUpperCase());
-						if (userPressedHotkey)
-						{
-							if (this.multiSelect)
-							{
-								if (selectedItemIndexes.hasOwnProperty(i))
-									delete selectedItemIndexes[i];
-								else
-								{
-									var addIt = true;
-									if (this.maxNumSelections > 0)
-										addIt = (Object.keys(selectedItemIndexes).length < this.maxNumSelections);
-									if (addIt)
-										selectedItemIndexes[i] = true;
-								}
-								// TODO: Screen refresh?
-							}
-							else
-							{
-								retVal = theItem.retval;
-								var oldSelectedItemIdx = this.selectedItemIdx;
-								this.selectedItemIdx = i;
-								continueOn = false;
-								if (typeof(this.OnItemNav) === "function")
-									this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-							}
-							break;
-						}
-					}
-				}
-			}
-		}
-	}
-	else
-	{
-		// The user's terminal doesn't support ANSI
-		var userAnswerIsValid = false;
-		do
-		{
-			console.print("\x01n\x01c\x01hY\x01n\x01cour \x01hC\x01n\x01choice\x01h\x01g: \x01c");
-			console.attributes = "N";
-			var userEnteredItemNum = console.getnum(numItems);
-			this.lastUserInput = userEnteredItemNum.toString();
-			if (!console.aborted && userEnteredItemNum > 0)
-			{
-				if (this.ItemIsSelectable(userEnteredItemNum-1))
-				{
-					var chosenItem = this.GetItem(userEnteredItemNum-1);
-					if (typeof(chosenItem) === "object" && chosenItem.hasOwnProperty("retval"))
-						retVal = chosenItem.retval;
-					userAnswerIsValid = true;
-				}
-			}
-			else
-			{
-				this.lastUserInput = "Q"; // To signify quitting
-				userAnswerIsValid = true;
-			}
-		} while (!userAnswerIsValid);
-	}
-
-	// Set the screen color back to normal so that text written to the screen
-	// after this looks good.
-	console.attributes = "N";
-	
-	// If in multi-select mode, populate userChoices with the choices
-	// that the user selected.
-	if (this.multiSelect && (Object.keys(selectedItemIndexes).length > 0))
-	{
-		userChoices = [];
-		for (var prop in selectedItemIndexes)
-			userChoices.push(this.GetItem(prop).retval);
-	}
-
-	return (this.multiSelect ? userChoices : retVal);
-}
-// Performs the key-up behavior for showing the menu items
-//
-// Parameters:
-//  pSelectedItemIndexes: An object containing indexes of selected items.  This is
-//                        normally a temporary object created/used in GetVal().
-//  pNumItems: The pre-calculated number of menu items.  If this not given, this
-//             will be retrieved by calling NumItems().
-function DDLightbarMenu_DoKeyUp(pSelectedItemIndexes, pNumItems)
-{
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	if (this.selectedItemIdx > this.FirstSelectableItemIdx(numItems))
-	{
-		var prevSelectableItemIdx = this.FindSelectableItemBackward(this.selectedItemIdx-1, false);
-		if (prevSelectableItemIdx < this.selectedItemIdx && prevSelectableItemIdx > -1)
-		{
-			// Draw the current item in regular colors
-			this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.selectedItemIdx = prevSelectableItemIdx;
-			var numItemsDiff = oldSelectedItemIdx - prevSelectableItemIdx;
-			// Draw the new current item in selected colors
-			// If the selected item is above the top of the menu, then we'll need to
-			// scroll the items down.
-			if (this.selectedItemIdx < this.topItemIdx)
-			{
-				this.topItemIdx -= numItemsDiff;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-			{
-				// The selected item is not above the top of the menu, so we can
-				// just draw the selected item highlighted.
-				this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			}
-			if (typeof(this.OnItemNav) === "function")
-				this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-		}
-	}
-	else
-	{
-		// selectedItemIdx is 0.  If wrap navigation is enabled, then go to the
-		// last item.
-		// If there are unselectable items above the current one, then scroll the item list up before
-		// wrapping down to the last selectable item
-		/*
-		var canWrapNav = false;
-		if (this.allowUnselectableItems && this.selectedItemIdx > 0)
-		{
-			if (this.topItemIdx > 0)
-			{
-				--this.topItemIdx;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-				canWrapNav = true;
-		}
-		*/
-		var canWrapNav = true;
-		if (this.allowUnselectableItems)
-		{
-			canWrapNav = false;
-			if (this.selectedItemIdx > 0)
-			{
-				if (this.topItemIdx > 0)
-				{
-					--this.topItemIdx;
-					this.Draw(selectedItemIndexes);
-				}
-				else
-					canWrapNav = true;
-			}
-		}
-		if (canWrapNav && this.wrapNavigation)
-		{
-			// If there are more items than can fit on the menu, then ideally, the top
-			// item index would be the one at the top of the page where the rest of the items
-			// fill the menu.
-			var prevSelectableItemIdx = this.FindSelectableItemBackward(numItems-1, false);
-			if (prevSelectableItemIdx > this.selectedItemIdx && prevSelectableItemIdx > -1)
-			{
-				// Draw the current item in regular colors
-				this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-				// Set the new selected item index, and figure out what page it's on
-				var oldSelectedItemIdx = this.selectedItemIdx;
-				this.selectedItemIdx = prevSelectableItemIdx;
-				// Calculate the top index for the page of the new selected item.  If the page
-				// is different, go to that page.
-				if (this.CalcPageForItemAndSetTopItemIdx(this.GetNumItemsPerPage(), numItems))
-					this.Draw(selectedItemIndexes);
-				else // The selected item is on the current page
-					this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-
-				if (typeof(this.OnItemNav) === "function")
-					this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-			}
-		}
-	}
-}
-// Performs the key-down behavior for showing the menu items
-//
-// Parameters:
-//  pSelectedItemIndexes: An object containing indexes of selected items.  This is
-//                        normally a temporary object created/used in GetVal().
-//  pNumItems: The pre-calculated number of menu items.  If this not given, this
-//             will be retrieved by calling NumItems().
-function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems)
-{
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-
-	if (this.selectedItemIdx < this.LastSelectableItemIdx(numItems))
-	{
-		var nextSelectableItemIdx = this.FindSelectableItemForward(this.selectedItemIdx+1, false);
-		if (nextSelectableItemIdx > this.selectedItemIdx)
-		{
-			// Draw the current item in regular colors
-			this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)));
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.selectedItemIdx = nextSelectableItemIdx;
-			var numItemsDiff = nextSelectableItemIdx - oldSelectedItemIdx;
-			// Draw the new current item in selected colors
-			// If the selected item is below the bottom of the menu, then we'll need to
-			// scroll the items up.
-			var numItemsPerPage = this.GetNumItemsPerPage();
-			if (this.selectedItemIdx > this.topItemIdx + numItemsPerPage-1)
-			{
-				this.topItemIdx += numItemsDiff;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-			{
-				// The selected item is not below the bottom of the menu, so we can
-				// just draw the selected item highlighted.
-				this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)));
-			}
-			if (typeof(this.OnItemNav) === "function")
-				this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-		}
-	}
-	else
-	{
-		// selectedItemIdx is the last item index.  If wrap navigation is enabled,
-		// then go to the first item.
-		// If there are unselectable items below the current one, then scroll the item down up before
-		// wrapping up to the first selectable item
-		/*
-		var canWrapNav = false;
-		if (this.allowUnselectableItems && this.selectedItemIdx > 0)
-		{
-			var topIndexForLastPage = numItems - this.GetNumItemsPerPage();
-			if (topIndexForLastPage < 0)
-				topIndexForLastPage = 0;
-			else if (topIndexForLastPage >= numItems)
-				topIndexForLastPage = numItems - 1;
-			if (this.topItemIdx < topIndexForLastPage)
-			{
-				++this.topItemIdx;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-				canWrapNav = true;
-		}
-		*/
-		var canWrapNav = true;
-		if (this.allowUnselectableItems)
-		{
-			canWrapNav = false;
-			if (this.selectedItemIdx > 0)
-			{
-				var topIndexForLastPage = numItems - this.GetNumItemsPerPage();
-				if (topIndexForLastPage < 0)
-					topIndexForLastPage = 0;
-				else if (topIndexForLastPage >= numItems)
-					topIndexForLastPage = numItems - 1;
-				if (this.topItemIdx < topIndexForLastPage)
-				{
-					++this.topItemIdx;
-					this.Draw(selectedItemIndexes);
-				}
-				else
-					canWrapNav = true;
-			}
-		}
-		if (canWrapNav && this.wrapNavigation)
-		{
-			// If there are more items than can fit on the menu, then ideally, the top
-			// item index would be the one at the top of the page where the rest of the items
-			// fill the menu.
-			//var nextSelectableItemIdx = this.FindSelectableItemForward(0, false);
-			var nextSelectableItemIdx = this.FirstSelectableItemIdx(numItems);
-			if (nextSelectableItemIdx < this.selectedItemIdx && nextSelectableItemIdx > -1)
-			{
-				// Draw the current item in regular colors
-				this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-				// Set the new selected item index, and figure out what page it's on
-				var oldSelectedItemIdx = this.selectedItemIdx;
-				this.selectedItemIdx = nextSelectableItemIdx;
-				// Calculate the top index for the page of the new selected item.  If the page
-				// is different, go to that page.
-				if (this.CalcPageForItemAndSetTopItemIdx(this.GetNumItemsPerPage(), numItems))
-					this.Draw(selectedItemIndexes);
-				else // The selected item is on the current page
-					this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-
-				if (typeof(this.OnItemNav) === "function")
-					this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-			}
-
-			// Older, before non-selectable items:
-			/*
-			// Draw the current item in regular colors
-			this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)));
-			// Go to the first item and scroll to the top if necessary
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.selectedItemIdx = 0;
-			var oldTopItemIdx = this.topItemIdx;
-			this.topItemIdx = 0;
-			if (this.topItemIdx != oldTopItemIdx)
-				this.Draw(selectedItemIndexes);
-			else
-			{
-				// Draw the new current item in selected colors
-				this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx)));
-			}
-			if (typeof(this.OnItemNav) === "function")
-				this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-			*/
-		}
-	}
-}
-// Performs the page-up behavior for showing the menu items
-//
-// Parameters:
-//  pSelectedItemIndexes: An object containing indexes of selected items.  This is
-//                        normally a temporary object created/used in GetVal().
-//  pNumItems: The pre-calculated number of menu items.  If this not given, this
-//             will be retrieved by calling NumItems().
-function DDLightbarMenu_DoPageUp(pSelectedItemIndexes, pNumItems)
-{
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	var numItemsPerPage = this.GetNumItemsPerPage();
-
-	var prevSelectableItemIdx = 0;
-	var currentPageNum = findPageNumOfItemNum(this.selectedItemIdx+1, numItemsPerPage, numItems, false);
-	if (currentPageNum > 1)
-	{
-		var startIdxToCheck = this.selectedItemIdx - numItemsPerPage;
-		if (startIdxToCheck < 0)
-		{
-			//startIdxToCheck = 0;
-			startIdxToCheck = (this.selectedItemIdx > 0 ? this.selectedItemIdx - 1 : 0);
-		}
-		prevSelectableItemIdx = this.FindSelectableItemBackward(startIdxToCheck, this.wrapNavigation);
-		//this.NavMenuForNewSelectedItemTop(prevSelectableItemIdx, numItemsPerPage, numItems, selectedItemIndexes);
-	}
-	else
-		prevSelectableItemIdx = this.FindSelectableItemForward(0, this.wrapNavigation);
-	this.NavMenuForNewSelectedItemTop(prevSelectableItemIdx, numItemsPerPage, numItems, selectedItemIndexes);
-
-	// Older, before un-selectable items:
-	/*
-	// Only do this if we're not already at the top of the list
-	if (this.topItemIdx > 0)
-	{
-		var oldSelectedItemIdx = this.selectedItemIdx;
-		var numItemsPerPage = this.GetNumItemsPerPage();
-		var newTopItemIdx = this.topItemIdx - numItemsPerPage;
-		if (newTopItemIdx < 0)
-			newTopItemIdx = 0;
-		if (newTopItemIdx != this.topItemIdx)
-		{
-			this.topItemIdx = newTopItemIdx;
-			this.selectedItemIdx -= numItemsPerPage;
-			if (this.selectedItemIdx < 0)
-				this.selectedItemIdx = 0;
-			this.Draw(selectedItemIndexes);
-		}
-		else
-		{
-			// The top index is the top index for the last page.
-			// If wrapping is enabled, then go back to the first page.
-			if (this.wrapNavigation)
-			{
-				var topIndexForLastPage = numItems - numItemsPerPage;
-				if (topIndexForLastPage < 0)
-					topIndexForLastPage = 0;
-				else if (topIndexForLastPage >= numItems)
-					topIndexForLastPage = numItems - 1;
-
-				this.topItemIdx = topIndexForLastPage;
-				this.selectedItemIdx = topIndexForLastPage;
-				this.Draw(selectedItemIndexes);
-			}
-		}
-		if (typeof(this.OnItemNav) === "function")
-			this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-	}
-	else
-	{
-		// We're already showing the first page of items.
-		// If the currently selected item is not the first
-		// item, then make it so.
-		if (this.selectedItemIdx > 0)
-		{
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			this.selectedItemIdx = 0;
-			this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			if (typeof(this.OnItemNav) === "function")
-				this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-		}
-	}
-	*/
-}
-// Performs the page-down behavior for showing the menu items
-//
-// Parameters:
-//  pSelectedItemIndexes: An object containing indexes of selected items.  This is
-//                        normally a temporary object created/used in GetVal().
-//  pNumItems: The pre-calculated number of menu items.  If this not given, this
-//             will be retrieved by calling NumItems().
-function DDLightbarMenu_DoPageDown(pSelectedItemIndexes, pNumItems)
-{
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-
-	var numItemsPerPage = this.GetNumItemsPerPage();
-	var startIdxToCheck = this.selectedItemIdx + numItemsPerPage;
-	if (startIdxToCheck >= numItems)
-		startIdxToCheck = numItems - 1;
-	var nextSelectableItemIdx = this.FindSelectableItemForward(startIdxToCheck, this.wrapNavigation);
-	this.NavMenuForNewSelectedItemBottom(nextSelectableItemIdx, numItemsPerPage, numItems, selectedItemIndexes, true);
-
-	// Older, before un-selectable items:
-	/*
-	// Only do the pageDown if we're not showing the last item already
-	var lastItemIdx = numItems - 1;
-	if (lastItemIdx > this.topItemIdx+numItemsPerPage-1)
-	{
-		var oldSelectedItemIdx = this.selectedItemIdx;
-		// Figure out the top index for the last page.
-		var topIndexForLastPage = numItems - numItemsPerPage;
-		if (topIndexForLastPage < 0)
-			topIndexForLastPage = 0;
-		else if (topIndexForLastPage >= numItems)
-			topIndexForLastPage = numItems - 1;
-		if (topIndexForLastPage != this.topItemIdx)
-		{
-			// Update the selected & top item indexes
-			this.selectedItemIdx += numItemsPerPage;
-			this.topItemIdx += numItemsPerPage;
-			if (this.selectedItemIdx >= topIndexForLastPage)
-				this.selectedItemIdx = topIndexForLastPage;
-			if (this.topItemIdx > topIndexForLastPage)
-				this.topItemIdx = topIndexForLastPage;
-			this.Draw(selectedItemIndexes);
-		}
-		else
-		{
-			// The top index is the top index for the last page.
-			// If wrapping is enabled, then go back to the first page.
-			if (this.wrapNavigation)
-			{
-				this.topItemIdx = 0;
-				this.selectedItemIdx = 0;
-			}
-			this.Draw(selectedItemIndexes);
-		}
-		if (typeof(this.OnItemNav) === "function")
-			this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-	}
-	else
-	{
-		// We're already showing the last page of items.
-		// If the currently selected item is not the last
-		// item, then make it so.
-		if (this.selectedItemIdx < lastItemIdx)
-		{
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			this.selectedItemIdx = lastItemIdx;
-			this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			if (typeof(this.OnItemNav) === "function")
-				this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-		}
-	}
-	*/
-}
-
-function DDLightbarMenu_NavMenuForNewSelectedItemTop(pNewSelectedItemIdx, pNumItemsPerPage, pNumItems, pSelectedItemIndexes)
-{
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	if (pNewSelectedItemIdx > -1 && pNewSelectedItemIdx != this.selectedItemIdx)
-	{
-		var indexDiff = 0;
-		if (pNewSelectedItemIdx < this.selectedItemIdx)
-			indexDiff = this.selectedItemIdx - pNewSelectedItemIdx;
-		else if (pNewSelectedItemIdx > this.selectedItemIdx)
-			indexDiff = pNewSelectedItemIdx - this.selectedItemIdx;
-		var oldSelectedItemIdx = this.selectedItemIdx;
-		this.selectedItemIdx = pNewSelectedItemIdx;
-		var pageNum = findPageNumOfItemNum(this.selectedItemIdx + 1, pNumItemsPerPage, numItems, false);
-		if (pageNum > 0)
-		{
-			var newTopItemIdx = pNumItemsPerPage * (pageNum-1);
-			if (newTopItemIdx != this.topItemIdx)
-			{
-				this.topItemIdx = newTopItemIdx;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-			{
-				// We're already showing the first page of items.
-				// Re-draw the old & new selected items with the proper highlighting
-				this.WriteItemAtItsLocation(oldSelectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-				this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			}
-		}
-		if (typeof(this.OnItemNav) === "function")
-			this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-	}
-}
-
-function DDLightbarMenu_NavMenuForNewSelectedItemBottom(pNewSelectedItemIdx, pNumItemsPerPage, pNumItems, pSelectedItemIndexes, pLastItemAtBottom)
-{
-	var numItemsPerPage = (typeof(pNumItemsPerPage) === "number" ? pNumItemsPerPage : this.GetNumItemsPerPage());
-	var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {});
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	var lastItemAtBottom = (typeof(pLastItemAtBottom) === "boolean" ? pLastItemAtBottom : false);
-	if (pNewSelectedItemIdx > -1 && pNewSelectedItemIdx != this.selectedItemIdx)
-	{
-		if (lastItemAtBottom)
-		{
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.selectedItemIdx = pNewSelectedItemIdx;
-			var newTopItemIdx = pNewSelectedItemIdx - numItemsPerPage + 1;
-			if (newTopItemIdx < 0)
-				newTopItemIdx = 0;
-			if (newTopItemIdx != this.topItemIdx)
-			{
-				this.topItemIdx = newTopItemIdx;
-				this.Draw(selectedItemIndexes);
-			}
-			else
-			{
-				// We're already showing the page with the calculated top index
-				// Re-draw the old & new selected items with the proper highlighting
-				this.WriteItemAtItsLocation(oldSelectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-				this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-			}
-		}
-		else
-		{
-			var indexDiff = 0;
-			if (pNewSelectedItemIdx < this.selectedItemIdx)
-				indexDiff = this.selectedItemIdx - pNewSelectedItemIdx;
-			else if (pNewSelectedItemIdx > this.selectedItemIdx)
-				indexDiff = pNewSelectedItemIdx - this.selectedItemIdx;
-			var oldSelectedItemIdx = this.selectedItemIdx;
-			this.selectedItemIdx = pNewSelectedItemIdx;
-			var pageNum = findPageNumOfItemNum(this.selectedItemIdx + 1, numItemsPerPage, numItems, false);
-			if (pageNum > 0)
-			{
-				var newTopItemIdx = numItemsPerPage * (pageNum-1);
-				// Figure out the top index for the last page.
-				var topIndexForLastPage = numItems - numItemsPerPage;
-				if (topIndexForLastPage < 0)
-					topIndexForLastPage = 0;
-				else if (topIndexForLastPage >= numItems)
-					topIndexForLastPage = numItems - 1;
-				if (newTopItemIdx != topIndexForLastPage)
-				{
-					this.topItemIdx = newTopItemIdx;
-					this.Draw(selectedItemIndexes);
-				}
-				else
-				{
-					// We're already showing the last page of items.
-					// Re-draw the old & new selected items with the proper highlighting
-					this.WriteItemAtItsLocation(oldSelectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-					this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-				}
-			}
-		}
-		if (typeof(this.OnItemNav) === "function")
-			this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx);
-	}
-}
-
-// Sets the characters to use for drawing the border.  Takes an object specifying
-// the values to set, but does not overwrite the whole borderChars object in the
-// menu object.
-//
-// Parameters:
-//  pBorderChars: An object with the following properties:
-//                upperLeft: The character to use for the upper-left corner
-//                upperRight: The character to use for the upper-right corner
-//                lowerLeft: The character to use for the lower-left corner
-//                lowerRight: The character to use for the lower-right corner
-//                top: The character to use for the top border
-//                bottom: The character to use for the bottom border
-//                left: The character to use for the left border
-//                right: The character to use for the right border
-function DDLightbarMenu_SetBorderChars(pBorderChars)
-{
-	if (typeof(pBorderChars) !== "object")
-		return;
-
-	var borderPropNames = [ "upperLeft", "upperRight", "lowerLeft", "lowerRight",
-	                        "top", "bottom", "left", "right" ];
-	for (var i = 0; i < borderPropNames.length; ++i)
-	{
-		if (pBorderChars.hasOwnProperty(borderPropNames[i]))
-			this.borderChars[borderPropNames[i]] = pBorderChars[borderPropNames[i]];
-	}
-}
-
-// Sets the colors to use with the menu.  Takes an object specifying the values
-// to set, but does not overwrite the whole colors object in the menu object.
-//
-// Parameters:
-//  pColors: An object with the following properties:
-//           itemColor: The color to use for non-highlighted items
-//           selectedItemColor: The color to use for selected items
-//           itemTextCharHighlightColor: The color to use for a highlighted
-//                                       non-space character in an item text
-//                                       (specified by having a & in the item
-//                                       text).
-//                                       It's important not to specify a "\x01n"
-//                                       in here in case the item text should
-//                                       have a background color.
-//           borderColor: The color to use for the border
-//           scrollbarScrollBlockColor: The color to use for the scrollbar block
-//           scrollbarBGColor: The color to use for the scrollbar background
-function DDLightbarMenu_SetColors(pColors)
-{
-	if (typeof(pColors) != "object")
-		return;
-
-	var colorPropNames = ["itemColor", "selectedItemColor", "altItemColor", "altSelectedItemColor",
-	                      "itemTextCharHighlightColor", "borderColor", "scrollbarScrollBlockColor",
-	                      "scrollbarBGColor", "unselectableItemColor"];
-	for (var i = 0; i < colorPropNames.length; ++i)
-	{
-		if (pColors.hasOwnProperty(colorPropNames[i]))
-			this.colors[colorPropNames[i]] = pColors[colorPropNames[i]];
-	}
-}
-
-// Returns the number of (possible) items per page
-function DDLightbarMenu_GetNumItemsPerPage()
-{
-	var numItemsPerPage = this.size.height;
-	if (this.borderEnabled)
-		numItemsPerPage -= 2;
-	return numItemsPerPage;
-}
-
-// Gets the top item index of the last page of items
-function DDLightbarMenu_GetTopItemIdxOfLastPage()
-{
-	var numItemsPerPage = this.size.height;
-	if (this.borderEnabled)
-		numItemsPerPage -= 2;
-	var topItemIndex = this.NumItems() - numItemsPerPage;
-	if (topItemIndex < 0)
-		topItemIndex = 0;
-	return topItemIndex;
-}
-
-// Calculates & sets the top item index to the top item of the last page of items
-function DDLightbarMenu_CalcAndSetTopItemIdxToTopOfLastPage(pNumItems)
-{
-	var numItemsPerPage = this.size.height;
-	if (this.borderEnabled)
-		numItemsPerPage -= 2;
-
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-	this.topItemIdx = numItems - numItemsPerPage;
-	if (this.topItemIdx < 0)
-		this.topItemIdx = 0;
-}
-
-// Calculates the page for an item (by its index) and sets the top index for the menu
-// based on that page.
-//
-// Parameters:
-//  pNumItemsPerPage: Optional - The number of items per page, if already calculated
-//  pNumItems: Optional - The number of items in the menu, if already known
-//
-//
-// Return value: Boolean - Whether or not the top index of the menu changed
-function DDLightbarMenu_CalcPageForItemAndSetTopItemIdx(pNumItemsPerPage, pNumItems)
-{
-	var numItemsPerPage = (typeof(pNumItemsPerPage) === "number" ? pNumItemsPerPage : this.GetNumItemsPerPage());
-	var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems());
-
-	var topItemIdxChanged = false;
-	var pageNum = findPageNumOfItemNum(this.selectedItemIdx+1, numItemsPerPage, numItems, false);
-	if (pageNum > 0)
-	{
-		var topItemIdxOnNewPage = numItemsPerPage * (pageNum-1);
-		if (topItemIdxOnNewPage + numItemsPerPage >= numItems)
-			topItemIdxOnNewPage = numItems - numItemsPerPage;
-		if (topItemIdxOnNewPage < 0)
-			topItemIdxOnNewPage = 0;
-		if (topItemIdxOnNewPage != this.topItemIdx)
-		{
-			this.topItemIdx = topItemIdxOnNewPage;
-			topItemIdxChanged = true;
-		}
-	}
-	return topItemIdxChanged;
-}
-
-// Adds additional key characters to cause quitting out of the menu
-// in addition to ESC.  The keys will be case-sensitive.
-//
-// Parameters:
-//  pAdditionalQuitKeys: A string of key characters
-function DDLightbarMenu_AddAdditionalQuitKeys(pAdditionalQuitKeys)
-{
-	if (typeof(pAdditionalQuitKeys) === "string")
-		this.additionalQuitKeys += pAdditionalQuitKeys;
-}
-
-// Returns whether or not the additional quit keys array contains a given
-// key character.
-//
-// Parameters:
-//  pKey: The key to look for in the additional quit keys
-//
-// Return value: Boolean - Whether or not the additional quit keys includes
-//               pKey
-function DDLightbarMenu_QuitKeysIncludes(pKey)
-{
-	return (this.additionalQuitKeys.indexOf(pKey) > -1);
-}
-
-// Clears the string of additional key characters to quit out of the menu
-function DDLightbarMenu_ClearAdditionalQuitKeys()
-{
-	this.additionalQuitKeys = "";
-}
-
-function DDLightbarMenu_AddAdditionalPageUpKeys(pAdditionalKeys)
-{
-	if (typeof(pAdditionalKeys) === "string")
-		this.additionalPageUpKeys += pAdditionalKeys;
-}
-
-function DDLightbarMenu_PageUpKeysIncludes(pKey)
-{
-	return (this.additionalPageUpKeys.indexOf(pKey) > -1);
-}
-
-function DDLightbarMenu_ClearAdditionalPageUpKeys()
-{
-	this.additionalPageUpKeys = "";
-}
-
-function DDLightbarMenu_AddAdditionalPageDownKeys(pAdditionalKeys)
-{
-	if (typeof(pAdditionalKeys) === "string")
-		this.additionalPageDnKeys += pAdditionalKeys;
-}
-
-function DDLightbarMenu_PageDownKeysIncludes(pKey)
-{
-	return (this.additionalPageDnKeys.indexOf(pKey) > -1);
-}
-
-function DDLightbarMenu_ClearAdditionalPageDownKeys()
-{
-	this.additionalPageDnKeys = "";
-}
-
-function DDLightbarMenu_AddAdditionalFirstPageKeys(pAdditionalKeys)
-{
-	if (typeof(pAdditionalKeys) === "string")
-		this.additionalFirstPageKeys += pAdditionalKeys;
-}
-
-function DDLightbarMenu_FirstPageKeysIncludes(pKey)
-{
-	return (this.additionalFirstPageKeys.indexOf(pKey) > -1);
-}
-
-function DDLightbarMenu_ClearAdditionalFirstPageKeys()
-{
-	this.additionalFirstPageKeys = "";
-}
-
-function DDLightbarMenu_AddAdditionalLastPageKeys(pAdditionalKeys)
-{
-	if (typeof(pAdditionalKeys) === "string")
-		this.additionalLastPageKeys += pAdditionalKeys;
-}
-
-function DDLightbarMenu_LastPageKeysIncludes(pKey)
-{
-	return (this.additionalLastPageKeys.indexOf(pKey) > -1);
-}
-
-function DDLightbarMenu_ClearAdditionalLastPageKeys()
-{
-	this.additionalLastPageKeys = "";
-}
-
-// Adds additional key characters to select any item.  The keys will be case-sensitive.
-//
-// Parameters:
-//  pAdditionalAddItemKeys: A string containing key characters
-function DDLightbarMenu_AddAdditionalSelectItemKeys(pAdditionalAddItemKeys)
-{
-	this.additionalSelectItemKeys += pAdditionalAddItemKeys;
-}
-
-// Returns whether or not the additional select-item keys array contains a given
-// key character.
-//
-// Parameters:
-//  pKey: The key to look for in the additional select-itemkeys
-//
-// Return value: Boolean - Whether or not the additional select-item keys includes
-//               pKey
-function DDLightbarMenu_SelectItemKeysIncludes(pKey)
-{
-	return (this.additionalSelectItemKeys.indexOf(pKey) > -1);
-}
-
-// Clears the string of additional key characters to select any item
-function DDLightbarMenu_ClearAdditionalSelectItemKeys()
-{
-	this.additionalSelectItemKeys = "";
-}
-
-// Displays an initial scrollbar
-//
-// Parameters:
-//  pSolidBlockStartRow: The starting row for the solid/bright blocks
-//  pNumSolidBlocks: The number of solid/bright blocks to write.  If this
-//                   is omitted, this.scrollbarInfo.numSolidScrollBlocks
-//                   will be used.
-function DDLightbarMenu_DisplayInitialScrollbar(pSolidBlockStartRow, pNumSolidBlocks)
-{
-	var numSolidBlocks = (typeof(pNumSolidBlocks) == "number" ? pNumSolidBlocks : this.scrollbarInfo.numSolidScrollBlocks);
-
-	var numSolidBlocksWritten = 0;
-	var wroteBrightBlockColor = false;
-	var wroteDimBlockColor = false;
-	var startY = this.pos.y;
-	var screenBottomRow = this.pos.y + this.size.height - 1;
-	var scrollbarCol = this.pos.x + this.size.width - 1;
-	if (this.borderEnabled)
-	{
-		++startY;
-		--screenBottomRow;
-		--scrollbarCol;
-	}
-	this.scrollbarInfo.solidBlockLastStartRow = startY;
-	for (var screenY = startY; screenY <= screenBottomRow; ++screenY)
-	{
-		console.gotoxy(scrollbarCol, screenY);
-		if ((screenY >= pSolidBlockStartRow) && (numSolidBlocksWritten < numSolidBlocks))
-		{
-			if (!wroteBrightBlockColor)
-			{
-				console.print("\x01n" + this.colors.scrollbarScrollBlockColor);
-				wroteBrightBlockColor = true;
-				wroteDimBlockColor = false;
-			}
-			console.print(this.scrollbarInfo.blockChar);
-			++numSolidBlocksWritten;
-		}
-		else
-		{
-			if (!wroteDimBlockColor)
-			{
-				console.print("\x01n" + this.colors.scrollbarBGColor);
-				wroteDimBlockColor = true;
-			}
-			console.print(this.scrollbarInfo.BGChar);
-		}
-	}
-}
-
-// For the DigDistMsgReader class: Updates the scrollbar for a message, for use
-// in enhanced reader mode.  This does only the necessary character updates to
-// minimize the number of characters that need to be updated on the screen.
-//
-// Parameters:
-//  pNewStartRow: The new (current) start row for solid/bright blocks
-//  pOldStartRow: The old start row for solid/bright blocks
-//  pNumSolidBlocks: The number of solid/bright blocks.  If this is omitted,
-//                   this.scrollbarInfo.numSolidScrollBlocks will be used.
-function DDLightbarMenu_UpdateScrollbar(pNewStartRow, pOldStartRow, pNumSolidBlocks)
-{
-	var numSolidBlocks = (typeof(pNumSolidBlocks) == "number" ? pNumSolidBlocks : this.scrollbarInfo.numSolidScrollBlocks);
-
-	var startY = this.pos.y;
-	var screenBottomRow = this.pos.y + this.size.height - 1;
-	var scrollbarCol = this.pos.x + this.size.width - 1;
-	if (this.borderEnabled)
-	{
-		++startY;
-		--screenBottomRow;
-		--scrollbarCol;
-	}
-
-	// Calculate the difference in the start row.  If the difference is positive,
-	// then the solid block section has moved down; if the diff is negative, the
-	// solid block section has moved up.
-	var solidBlockStartRowDiff = pNewStartRow - pOldStartRow;
-	var oldLastRow = pOldStartRow + numSolidBlocks - 1;
-	var newLastRow = pNewStartRow + numSolidBlocks - 1;
-	if (solidBlockStartRowDiff > 0)
-	{
-		// The solid block section has moved down
-		if (pNewStartRow > oldLastRow)
-		{
-			// No overlap
-			// Write dim blocks over the old solid block section
-			console.print("\x01n" + this.colors.scrollbarBGColor);
-			for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.BGChar);
-			}
-			// Write solid blocks in the new locations
-			console.print("\x01n" + this.colors.scrollbarScrollBlockColor);
-			for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.blockChar);
-			}
-		}
-		else
-		{
-			// There is some overlap
-			// Write dim blocks on top
-			console.print("\x01n" + this.colors.scrollbarBGColor);
-			for (var screenY = pOldStartRow; screenY < pNewStartRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.BGChar);
-			}
-			// Write bright blocks on the bottom
-			console.print("\x01n" + this.colors.scrollbarScrollBlockColor);
-			for (var screenY = oldLastRow+1; screenY <= newLastRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.blockChar);
-			}
-		}
-	}
-	else if (solidBlockStartRowDiff < 0)
-	{
-		// The solid block section has moved up
-		if (pOldStartRow > newLastRow)
-		{
-			// No overlap
-			// Write dim blocks over the old solid block section
-			console.print("\x01n" + this.colors.scrollbarBGColor);
-			for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.BGChar);
-			}
-			// Write solid blocks in the new locations
-			console.print("\x01n" + this.colors.scrollbarScrollBlockColor);
-			for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.blockChar);
-			}
-		}
-		else
-		{
-			// There is some overlap
-			// Write bright blocks on top
-			console.print("\x01n" + this.colors.scrollbarScrollBlockColor);
-			var endRow = pOldStartRow;
-			for (var screenY = pNewStartRow; screenY < endRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.blockChar);
-			}
-			// Write dim blocks on the bottom
-			console.print("\x01n" + this.colors.scrollbarBGColor);
-			endRow = pOldStartRow + numSolidBlocks;
-			for (var screenY = pNewStartRow+numSolidBlocks; screenY < endRow; ++screenY)
-			{
-				console.gotoxy(scrollbarCol, screenY);
-				console.print(this.scrollbarInfo.BGChar);
-			}
-		}
-	}
-}
-
-// Calculates the starting row for the solid blocks on the scrollbar
-//
-// Return value: The starting row for the solid blocks on the scrollbar
-function DDLightbarMenu_CalcScrollbarSolidBlockStartRow()
-{
-	var scrollbarStartY = this.pos.y;
-	var scrollbarHeight = this.size.height;
-	if (this.borderEnabled)
-	{
-		++scrollbarStartY;
-		scrollbarHeight -= 2;
-	}
-	var scrollbarBottomY = scrollbarStartY + scrollbarHeight - 1;
-	var solidBlockStartRow = scrollbarStartY;
-	var numMenuItems = this.NumItems();
-	if (numMenuItems > 0)
-	{
-		var scrollbarFraction = this.selectedItemIdx / numMenuItems;
-		var scrollbarStartRow = scrollbarStartY + Math.floor(scrollbarHeight * scrollbarFraction);
-		solidBlockStartRow = scrollbarStartRow - Math.floor(this.scrollbarInfo.numSolidScrollBlocks / 2);
-		// Don't let the solid blocks go above the starting screen row or below the ending
-		// screen row of the scrollbar
-		if (solidBlockStartRow < scrollbarStartY)
-			solidBlockStartRow = scrollbarStartY;
-		else if (solidBlockStartRow + this.scrollbarInfo.numSolidScrollBlocks > scrollbarBottomY)
-			solidBlockStartRow = scrollbarBottomY - this.scrollbarInfo.numSolidScrollBlocks + 1;
-	}
-	return solidBlockStartRow;
-}
-
-// Updates the scrollbar position based on the currently-selected
-// item index, this.selectedItemIdx.
-//
-// Parameters:
-//  pForceUpdate: Boolean - Whether or not to force the redraw regardless of block location.
-//                Defaults to false.
-function DDLightbarMenu_UpdateScrollbarWithHighlightedItem(pForceUpdate)
-{
-	var forceUpdate = (typeof(pForceUpdate) === "boolean" ? pForceUpdate : false);
-	var solidBlockStartRow = this.CalcScrollbarSolidBlockStartRow();
-	if (forceUpdate || (solidBlockStartRow != this.scrollbarInfo.solidBlockLastStartRow))
-		this.UpdateScrollbar(solidBlockStartRow, this.scrollbarInfo.solidBlockLastStartRow, this.scrollbarInfo.numSolidScrollBlocks);
-	this.scrollbarInfo.solidBlockLastStartRow = solidBlockStartRow;
-}
-
-function DDLightbarMenu_CanShowAllItemsInWindow()
-{
-	var pageHeight = (this.borderEnabled ? this.size.height - 2 : this.size.height);
-	return (this.NumItems() <= pageHeight);
-}
-
-// Makes an item object that is compatible with DDLightbarMenu, with a given
-// item text and return value.
-//
-// Parameters:
-//  pText: The text to show in the menu for the item
-//  pRetval: The return value of the item when the user selects it from the menu
-//
-// Return value: An object with the given text & return value compatible with DDLightbarMenu
-function DDLightbarMenu_MakeItemWithTextAndRetval(pText, pRetval)
-{
-	var item = getDefaultMenuItem();
-	item.text = pText;
-	item.retval = pRetval;
-	return item;
-}
-
-// Makes an item object that is compatible with DDLightbarMenu, with a given
-// return value.
-//
-// Parameters:
-//  pRetval: The return value of the item when the user selects it from the menu
-//
-// Return value: An object with the given return value compatible with DDLightbarMenu
-function DDLightbarMenu_MakeItemWithRetval(pRetval)
-{
-	var item = getDefaultMenuItem();
-	item.retval = pRetval;
-	return item;
-}
-
-// Returns whether an item is set to use the alternate item colors
-//
-// Parameters:
-//  pItemIndex: The index of the item
-//
-// Return value: Boolean - Whether or not an item is configured to use alternate item colors
-function DDLightbarMenu_ItemUsesAltColors(pItemIndex)
-{
-	if ((pItemIndex < 0) || (pItemIndex >= this.NumItems()))
-		return false;
-
-	return this.GetItem(pItemIndex).useAltColors;
-}
-
-// Returns either the normal or alternate color for an item
-//
-// Parameters:
-//  pItemIndex: The index of the item
-//  pSelected: Whether or not to use selected item colors.  Defaults to false.
-//
-// Return value: Either colors.itemColor or colors.altItemColor
-function DDLightbarMenu_GetColorForItem(pItemIndex, pSelected)
-{
-	if ((pItemIndex < 0) || (pItemIndex >= this.NumItems()))
-		return "";
-
-	var selected = (typeof(pSelected) == "boolean" ? pSelected : false);
-	if (selected)
-		return (this.GetItem(pItemIndex).useAltColors ? this.colors.altSelectedItemColor : this.colors.selectedItemColor);
-	else
-		return (this.GetItem(pItemIndex).useAltColors ? this.colors.altItemColor : this.colors.itemColor);
-}
-
-// Returns either the selected or alternate selected color for an item
-//
-// Parameters:
-//  pItemIndex: The index of the item
-//
-// Return value: Either colors.selectedItemColor or colors.altSelectedItemColor
-function DDLightbarMenu_GetSelectedColorForItem(pItemIndex)
-{
-	if (typeof(pItemIndex) !== "number")
-		return;
-	if ((pItemIndex < 0) || (pItemIndex >= this.NumItems()))
-		return "";
-
-	return (this.GetItem(pItemIndex).useAltColors ? this.colors.altSelectedItemColor : this.colors.selectedItemColor);
-}
-
-// Sets the selected item index for the menu, and sets anything else as appropriate
-// (such as the index of the topmost menu item).
-//
-// Parameters:
-//  pSelectedItemIdx: The index of the selected item
-function DDLightbarMenu_SetSelectedItemIdx(pSelectedItemIdx)
-{
-	if (typeof(pSelectedItemIdx) !== "number")
-		return;
-	if ((pSelectedItemIdx < 0) || (pSelectedItemIdx >= this.NumItems()))
-		return;
-
-	this.selectedItemIdx = pSelectedItemIdx;
-	if (this.selectedItemIdx == 0)
-		this.topItemIdx = 0;
-	else if (this.selectedItemIdx >= this.topItemIdx+this.GetNumItemsPerPage())
-		this.topItemIdx = this.selectedItemIdx - this.GetNumItemsPerPage() + 1;
-	else if (this.selectedItemIdx < this.topItemIdx)
-		this.topItemIdx = this.selectedItemIdx;
-}
-
-// Gets the index of the bottommost item on the menu
-function DDLightbarMenu_GetBottomItemIdx()
-{
-	var bottomItemIdx = this.topItemIdx + this.size.height - 1;
-	if (this.borderEnabled)
-		bottomItemIdx -= 2;
-	return bottomItemIdx;
-}
-
-// Returns the absolute screen position (x, y) of the topmost displayed item on the menu
-//
-// Return value: An object with the following properties:
-//               x: The horizontal screen location of the top item (1-based)
-//               y: The vertical screen location of the top item (1-based)
-function DDLightbarMenu_GetTopDisplayedItemPos()
-{
-	var itemPos = {
-		x: this.pos.x,
-		y: this.pos.y
-	};
-	if (this.borderEnabled)
-	{
-		++itemPos.x;
-		++itemPos.y;
-	}
-	return itemPos;
-}
-
-// Returns the absolute screen position (x, y) of the bottommost displayed item on the menu
-//
-// Return value: An object with the following properties:
-//               x: The horizontal screen location of the top item (1-based)
-//               y: The vertical screen location of the top item (1-based)
-function DDLightbarMenu_GetBottomDisplayedItemPos()
-{
-	var itemPos = {
-		x: this.pos.x,
-		y: this.pos.y + this.size.height - 1
-	};
-	if (this.borderEnabled)
-	{
-		++itemPos.x;
-		--itemPos.y;
-	}
-	return itemPos;
-}
-
-// Returns the absolute screen row number for an item index, if it is visible
-// on the menu.  If the item is not visible on the menu, this will return -1.
-//
-// Parameters:
-//  pItemIdx: The index of the menu item to check
-//
-// Return value: The absolute row number on the screen where the item is, if it is
-//               visible on the menu, or -1 if the item is not visible on the menu.
-function DDLightbarMenu_ScreenRowForItem(pItemIdx)
-{
-	if (typeof(pItemIdx) !== "number")
-		return -1;
-	if (pItemIdx < 0 || pItemIdx >= this.NumItems())
-		return -1;
-
-	var screenRow = -1;
-	if (pItemIdx >= this.topItemIdx && pItemIdx <= this.GetBottomItemIdx())
-	{
-		if (this.borderEnabled)
-			screenRow = this.pos.y + pItemIdx - this.topItemIdx + 1;
-		else
-			screenRow = this.pos.y + pItemIdx - this.topItemIdx;
-	}
-	return screenRow;
-}
-
-// Returns whether ANSI is supported by the user's terminal. Also checks this.allowANSI
-function DDLightbarMenu_ANSISupported()
-{
-	return (console.term_supports(USER_ANSI) && this.allowANSI);
-}
-
-// Calculates the number of solid scrollbar blocks & non-solid scrollbar blocks
-// to use.  Saves the information in this.scrollbarInfo.numSolidScrollBlocks and
-// this.scrollbarInfo.numNonSolidScrollBlocks.
-function DDLightbarMenu_CalcScrollbarBlocks()
-{
-	var menuDisplayHeight = this.size.height;
-	if (this.borderEnabled)
-		menuDisplayHeight -= 2;
-	var numMenuItems = this.NumItems();
-	if (numMenuItems > 0)
-	{
-		var menuListFractionShown = menuDisplayHeight / numMenuItems;
-		if (menuListFractionShown > 1)
-			menuListFractionShown = 1.0;
-		this.scrollbarInfo.numSolidScrollBlocks = Math.floor(menuDisplayHeight * menuListFractionShown);
-		if (this.scrollbarInfo.numSolidScrollBlocks <= 0)
-			this.scrollbarInfo.numSolidScrollBlocks = 1;
-		else if (this.scrollbarInfo.numSolidScrollBlocks > menuDisplayHeight)
-			this.scrollbarInfo.numSolidScrollBlocks = menuDisplayHeight;
-		this.scrollbarInfo.numNonSolidScrollBlocks = menuDisplayHeight - this.scrollbarInfo.numSolidScrollBlocks;
-	}
-	else
-	{
-		this.scrollbarInfo.numSolidScrollBlocks = menuDisplayHeight;
-		this.scrollbarInfo.numNonSolidScrollBlocks = 0;
-	}
-}
-
-
-
-
-//////////////////////////////////////////////////////////
-// Helper functions, not part of the DDLightbarMenu class
-
-// Returns the length of an item's text, not counting non-displayable
-// characters (such as Synchronet color attributes and an ampersand
-// immediately before a non-space)
-//
-// Parameters:
-//  pText: The text to test
-//  pAmpersandHotkeysInItems: Boolean - Whether or not ampersand hotkeys are enabled for the item text
-function itemTextDisplayableLen(pText, pAmpersandHotkeysInItems)
-{
-	var textLen = console.strlen(pText);
-	// If pAmpersandHotkeysInItems is true, look for ampersands immediately
-	// before a non-space and if found, don't count those.
-	if (pAmpersandHotkeysInItems)
-	{
-		var startIdx = 0;
-		var ampersandIndex = pText.indexOf("&", startIdx);
-		while (ampersandIndex > -1)
-		{
-			// See if the next character is a space character.  If not, then
-			// don't count it in the length.
-			if (pText.length > ampersandIndex+1)
-			{
-				var nextChar = pText.substr(ampersandIndex+1, 1);
-				if (nextChar != " ")
-					--textLen;
-			}
-			startIdx = ampersandIndex+1;
-			ampersandIndex = pText.indexOf("&", startIdx);
-		}
-	}
-	return textLen;
-}
-
-// Shortens a string, accounting for control/attribute codes.  Returns a new
-// (shortened) copy of the string.
-//
-// Parameters:
-//  pStr: The string to shorten
-//  pNewLength: The new (shorter) length of the string
-//  pFromLeft: Optional boolean - Whether to start from the left (default) or
-//             from the right.  Defaults to true.
-//
-// Return value: The shortened version of the string
-function shortenStrWithAttrCodes(pStr, pNewLength, pFromLeft)
-{
-	if (typeof(pStr) != "string")
-		return "";
-	if (typeof(pNewLength) != "number")
-		return pStr;
-	if (pNewLength >= console.strlen(pStr))
-		return pStr;
-
-	var fromLeft = (typeof(pFromLeft) == "boolean" ? pFromLeft : true);
-	var strCopy = "";
-	var tmpStr = "";
-	var strIdx = 0;
-	var lengthGood = true;
-	if (fromLeft)
-	{
-		while (lengthGood && (strIdx < pStr.length))
-		{
-			tmpStr = strCopy + pStr.charAt(strIdx++);
-			if (console.strlen(tmpStr) <= pNewLength)
-				strCopy = tmpStr;
-			else
-				lengthGood = false;
-		}
-	}
-	else
-	{
-		strIdx = pStr.length - 1;
-		while (lengthGood && (strIdx >= 0))
-		{
-			tmpStr = pStr.charAt(strIdx--) + strCopy;
-			if (console.strlen(tmpStr) <= pNewLength)
-				strCopy = tmpStr;
-			else
-				lengthGood = false;
-		}
-	}
-	return strCopy;
-}
-
-// Returns whether or not all string attribute objects in an array have the
-// expected properties, and that the property types are correct, for menu item
-// string color definitions.
-//
-// Parameters:
-//  pAttrsArray: An array of objects which are expected to containg the
-//               following properties: start, end, attrs
-//
-// Return value: Boolean - Whether or not all elements in the array
-//               have all the expected properties
-function attrsArrayElementsHaveAllCorrectProps(pAttrsArray)
-{
-	var allElementsHaveCorrectProps = true;
-	for (var i = 0; (i < pAttrsArray.length) && allElementsHaveCorrectProps; ++i)
-	{
-		allElementsHaveCorrectProps = ((typeof(pAttrsArray[i]) == "object") &&
-		                               pAttrsArray[i].hasOwnProperty("start") &&
-		                               pAttrsArray[i].hasOwnProperty("end") &&
-		                               pAttrsArray[i].hasOwnProperty("attrs") &&
-		                               (typeof(pAttrsArray[i].start) == "number") &&
-		                               (typeof(pAttrsArray[i].end) == "number") &&
-		                               (typeof(pAttrsArray[i].attrs) == "string"));
-	}
-	return allElementsHaveCorrectProps;
-}
-
-// Adds color/attribute codes to a string.
-//
-// Parameters:
-//  pStr: The string to add attribute codes to
-//  pAttrs: This can be either a string containing attribute codes or an array
-//          of objects with start, end, and color properties, for applying attribute
-//          codes to different parts of the string.  These are the properties of
-//          each object in the string (note: for the last one, end can be 0 or -1
-//          to apply the attributes to the rest of the string):
-//           start: The start index in the string to apply the attributes to
-//           end: One past the last index in the part of the string to apply the attributes to
-//           attrs: The attributes to apply to that part of the string
-//
-// Return value: A copy of the string with attributes applied
-function addAttrsToString(pStr, pAttrs)
-{
-	if (typeof(pStr) != "string")
-		return "";
-	else if (pStr.length == 0)
-		return "";
-
-	var str;
-	if (Array.isArray(pAttrs))
-	{
-		// To use the attributes array, the array must have some objects and
-		// each element of the array must have start, end, and attrs properties
-		if ((pAttrs.length > 0) && attrsArrayElementsHaveAllCorrectProps(pAttrs))
-		{
-			// Colorize the string with the object in pAttrs.
-			// Don't do the last object in this loop, because for the last object,
-			// we'll want to check if its end index is valid.
-			str = "";
-			var lastEnd = -1;
-			for (var i = 0; i < pAttrs.length; ++i)
-			{
-				// If the current object's start is more than 1 character after
-				// the last's end, then append the gap in the string with the
-				// normal attribute
-				if ((i > 0) && (pAttrs[i].start > pAttrs[i-1].end))
-					str += "\x01n" + pStr.substring(pAttrs[i-1].end, pAttrs[i].start);
-				// If the properties for the current attrib object are all valid, append
-				// the current part of the string with the given attributes
-				if ((pAttrs[i].start >= lastEnd) && (pAttrs[i].start >= 0) && (pAttrs[i].start < pStr.length) && (pAttrs[i].end > pAttrs[i].start) && (pAttrs[i].end <= pStr.length))
-					str += "\x01n" + pAttrs[i].attrs + pStr.substring(pAttrs[i].start, pAttrs[i].end);
-				// For the last attribute object, allow the end index to be <= 0 or
-				// more than the length of the string to apply the attributes to the
-				// rest of the string.
-				//else if ((i == pAttrs.length-1) && (pAttrs[i].start >= lastEnd) && (pAttrs[i].start >= 0) && (pAttrs[i].start < pStr.length) && (pAttrs[i].end <= 0))
-				else if ((i == pAttrs.length-1) && (pAttrs[i].start >= lastEnd) && (pAttrs[i].start >= 0) && (pAttrs[i].start < pStr.length) && ((pAttrs[i].end <= 0) || (pAttrs[i].end > pStr.length)))
-					str += "\x01n" + pAttrs[i].attrs + pStr.substring(pAttrs[i].start);
-				lastEnd = pAttrs[i].end;
-			}
-
-			// If str is shorter than the passed-in string, then append the rest of the string
-			// with the normal attribute.
-			var theStrLen = console.strlen(str);
-			if (theStrLen < pStr.length)
-				str += "\x01n" + pStr.substring(theStrLen);
-		}
-		else
-			str = pStr;
-	}
-	else if (typeof(pAttrs) == "string")
-		str = "\x01n" + pAttrs + pStr;
-	else
-		str = pStr;
-	return str;
-}
-
-function getBackgroundAttrAtIdx(pAttrs, pIdx)
-{
-	if (typeof(pIdx) != "number")
-		return "";
-	if (pIdx < 0)
-		return "";
-
-	// Synchronet background color codes:
-	// Black: 0
-	// Red: 1
-	// Green: 2
-	// Yellow/brown: 3
-	// Blue: 4
-	// Magenta: 5
-	// Cyan: 6
-	// White/grey: 7
-	var syncBkgAttrRegex = /\x01[01234567]/;
-	var bkgAttr = "";
-	if (Array.isArray(pAttrs))
-	{
-		if ((pAttrs.length > 0) && attrsArrayElementsHaveAllCorrectProps(pAttrs))
-		{
-			// Go through the array, and if a start & end is found where pIdx
-			// falls between, check that objects attrs property for its last
-			// background attribute, if there is one
-			for (var i = 0; i < pAttrs.length; ++i)
-			{
-				if ((pIdx >= pAttrs[i].start) && ((pIdx < pAttrs[i].end) || (pAttrs[i].end == 0)))
-				{
-					// Check the attrs for the last background attribute, starting
-					// from the end
-					if (pAttrs[i].attrs.length >= 2)
-					{
-						for (var attrIdx = pAttrs[i].attrs.length - 2; attrIdx >= 0; attrIdx -= 2)
-						{
-							var currentTwo = pAttrs[i].attrs.substr(attrIdx, 2);
-							if (syncBkgAttrRegex.test(currentTwo))
-							{
-								bkgAttr = currentTwo;
-								break;
-							}
-						}
-					}
-					break;
-				}
-			}
-		}
-	}
-	else if (typeof(pAttrs) == "string")
-	{
-		if ((pIdx >= 0) || (pIdx < pAttrs.length))
-		{
-			// Starting from pIdx, go backwards through pAttrs, and if a Synchronet
-			// background attribute code is found, then use it.
-			for (var i = pIdx - 2; i >= 0; i -= 2)
-			{
-				var currentTwo = pAttrs.substr(i, 2);
-				if (syncBkgAttrRegex.test(currentTwo))
-				{
-					bkgAttr = currentTwo;
-					break;
-				}
-			}
-		}
-	}
-	return bkgAttr;
-}
-
-// Returns a default item object for a DDLightbarMenu
-function getDefaultMenuItem() {
-	return {
-		text: "",
-		textIsUTF8: false,
-		retval: null,
-		hotkeys: "",
-		useAltColors: false,
-		itemColor: null,
-		itemSelectedColor: null,
-		isSelectable: true
-	};
-}
-
-// Returns a substring of a string, accounting for Synchronet attribute
-// codes (not including the attribute codes in the start index or length)
-//
-// Parameters:
-//  pStr: The string to perform the substring on
-//  pLen: Optional: The length of the substring. If not specified, the rest of the string will be used.
-//
-// Return value: A substring of the string according to the parameters
-function substrWithAttrCodes(pStr, pStartIdx, pLen)
-{
-	if (typeof(pStr) !== "string")
-		return "";
-	if (typeof(pStartIdx) !== "number")
-		return "";
-	var len = typeof(pLen) === "number" ? pLen : console.strlen(pStr)-pStartIdx;
-	if (len <= 0)
-		return "";
-	if ((pStartIdx <= 0) && (pLen >= console.strlen(pStr)))
-		return pStr;
-	var startIdx = 0;
-	var screenLen = console.strlen(pStr);
-	if (typeof(pStartIdx) === "number" && pStartIdx >= 0 && pStartIdx < screenLen)
-		startIdx = pStartIdx;
-
-	// Find the actual start & end indexes, considering (not counting) attribute codes,
-	// and return the substring including any applicable attributes from the string
-	var actualStartIdx = findIdxConsideringAttrs(pStr, startIdx);
-	var actualEndIdx = findIdxConsideringAttrs(pStr, startIdx+len+1);
-	// With the actual start & end indexes, make sure we'll get the string
-	// length desired; if not, adjust actualEndIdx;
-	var lenWithActualIndexes = actualEndIdx - actualStartIdx;
-	if (actualEndIdx-actualStartIdx < len)
-		actualEndIdx += len - lenWithActualIndexes;
-	return getAttrsBeforeStrIdx(pStr, actualStartIdx) + pStr.substring(actualStartIdx, actualEndIdx);
-}
-// Helper for substrWithAttrCodes(): Maps a 'visual' character index in a string to its
-// actual index within the string, considering any attribute codes in the string.
-//
-// Parameters:
-//  pStr: A string
-//  pIndex: The index of a character as displayed on the screen, to be mapped to actual string index
-//
-// Return value: The character index mapped to the actual index within the string, considering attribute codes
-function findIdxConsideringAttrs(pStr, pIndex)
-{
-	if (typeof(pStr) !== "string" || pStr.length == 0)
-		return 0;
-	if (typeof(pIndex) !== "number" || pIndex < 0)
-		return 0;
-
-	var printableLen = console.strlen(pStr);
-	var index = (pIndex >= printableLen ? printableLen - 1 : pIndex);
-
-	var actualIdx = 0;
-	var numTimesUpdated = 0;
-	for (var i = 0; i < pStr.length && numTimesUpdated <= index; ++i)
-	{
-		// If this character is the attribute control character, skip it along with
-		// the next character
-		if (pStr.charAt(i) == "\x01")
-		{
-			++i;
-			continue;
-		}
-		actualIdx = i;
-		++numTimesUpdated;
-	}
-	// Alternate implementation:
-	/*
-	var previousChar = "";
-	var syncAttrLen = 0;
-	var numTimesUpdated = 0;
-	for (var i = 0; i < pStr.length && numTimesUpdated <= index; ++i)
-	{
-		// If this character is the attribute control character, skip it along with
-		// the next character
-		var currentChar = pStr.charAt(i);
-		if (currentChar == "\x01")
-			syncAttrLen = 1;
-		else if (previousChar == "\x01")
-			++syncAttrLen;
-		else
-			syncAttrLen = 0;
-		if (syncAttrLen == 0)
-		{
-			actualIdx = i;
-			++numTimesUpdated;
-		}
-		previousChar = currentChar;
-	}
-	*/
-	return actualIdx;
-}
-// Helper for substrWithAttrCodes(): Returns a string with any Synchronet color/attribute
-// codes found in a string before a given index.
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The index in the string to search before
-//
-// Return value: A string containing any Synchronet attribute codes found before
-//               the given index in the given string
-function getAttrsBeforeStrIdx(pStr, pIdx)
-{
-	if (typeof(pStr) !== "string")
-		return "";
-	if (typeof(pIdx) !== "number")
-		return "";
-	if (pIdx < 0)
-		return "";
-
-	var idx = (pIdx < pStr.length ? pIdx : pStr.length-1);
-	var attrStartIdx = strIdxOfSyncAttrBefore(pStr, idx, true, false);
-	var attrEndIdx = strIdxOfSyncAttrBefore(pStr, idx, false, false); // Start of 2-character code
-	var attrsStr = "";
-	if ((attrStartIdx > -1) && (attrEndIdx > -1))
-		attrsStr = pStr.substring(attrStartIdx, attrEndIdx+2);
-	return attrsStr;
-}
-
-// Returns the index of the first Synchronet attribute code before a given index
-// in a string.
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The index to search back from
-//  pSeriesOfAttrs: Optional boolean - Whether or not to look for a series of
-//                  attributes.  Defaults to false (look for just one attribute).
-//  pOnlyInWord: Optional boolean - Whether or not to look only in the current word
-//               (with words separated by whitespace).  Defaults to false.
-//
-// Return value: The index of the first Synchronet attribute code before the given
-//               index in the string, or -1 if there is none or if the parameters
-//               are invalid
-function strIdxOfSyncAttrBefore(pStr, pIdx, pSeriesOfAttrs, pOnlyInWord)
-{
-	if (typeof(pStr) != "string")
-		return -1;
-	if (typeof(pIdx) != "number")
-		return -1;
-	if ((pIdx < 0) || (pIdx >= pStr.length))
-		return -1;
-
-	var seriesOfAttrs = (typeof(pSeriesOfAttrs) == "boolean" ? pSeriesOfAttrs : false);
-	var onlyInWord = (typeof(pOnlyInWord) == "boolean" ? pOnlyInWord : false);
-
-	var attrCodeIdx = pStr.lastIndexOf("\x01", pIdx-1);
-	if (attrCodeIdx > -1)
-	{
-		// If we are to only check the current word, then continue only if
-		// there isn't a space between the attribute code and the given index.
-		if (onlyInWord)
-		{
-			if (pStr.lastIndexOf(" ", pIdx-1) >= attrCodeIdx)
-				attrCodeIdx = -1;
-		}
-	}
-	if (attrCodeIdx > -1)
-	{
-		var syncAttrRegexWholeWord = /^\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i;
-		if (syncAttrRegexWholeWord.test(pStr.substr(attrCodeIdx, 2)))
-		{
-			if (seriesOfAttrs)
-			{
-				for (var i = attrCodeIdx - 2; i >= 0; i -= 2)
-				{
-					if (syncAttrRegexWholeWord.test(pStr.substr(i, 2)))
-						attrCodeIdx = i;
-					else
-						break;
-				}
-			}
-		}
-		else
-			attrCodeIdx = -1;
-	}
-	return attrCodeIdx;
-}
-
-// Converts a 'printed' index in a string to its real index in the string
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The printed index in the string
-//
-// Return value: The actual index in the string object, or -1 on error
-function printedToRealIdxInStr(pStr, pIdx)
-{
-	if (typeof(pStr) != "string")
-		return -1;
-	if ((pIdx < 0) || (pIdx >= pStr.length))
-		return -1;
-
-	// Store the character at the given index if the string didn't have attribute codes.
-	// Also, to help ensure this returns the correct index, get a substring with several
-	// characters starting at the given index to match a word within the string
-	var strWithoutAttrCodes = strip_ctrl(pStr);
-	var substr_len = 5;
-	var substrWithoutAttrCodes = strWithoutAttrCodes.substr(pIdx, substr_len);
-	var printableCharAtIdx = strWithoutAttrCodes.charAt(pIdx);
-	// Iterate through pStr until we find that character and return that index.
-	var realIdx = 0;
-	for (var i = 0; i < pStr.length; ++i)
-	{
-		// tempStr is the string to compare with substrWithoutAttrCodes
-		var tempStr = strip_ctrl(pStr.substr(i)).substr(0, substr_len);
-		if ((pStr.charAt(i) == printableCharAtIdx) && (tempStr == substrWithoutAttrCodes))
-		{
-			realIdx = i;
-			break;
-		}
-	}
-	return realIdx;
-}
-
-// Inputs a keypress from the user and handles some ESC-based
-// characters such as PageUp, PageDown, and ESC.  If PageUp
-// or PageDown are pressed, this function will return the
-// string defined by KEY_PAGE_UP or EY_PAGE_DOWN,
-// respectively.  Also, F1-F5 will be returned as "\x01F1"
-// through "\x01F5", respectively.
-// Thanks goes to Psi-Jack for the original impementation
-// of this function.
-//
-// Parameters:
-//  pGetKeyMode: Optional - The mode bits for console.getkey().
-//               If not specified, K_NONE will be used.
-//  pInputTimeoutMS: The input timeout in milliseconds (defaults to 300000).
-//                   If the user is a sysop, this will use a timeout of 0 for no timeout.
-//
-// Return value: The user's keypress
-function getKeyWithESCChars(pGetKeyMode, pInputTimeoutMS)
-{
-	var getKeyMode = (typeof(pGetKeyMode) === "number" ? pGetKeyMode : K_NONE);
-	var inputTimeoutMS = (typeof(pInputTimeoutMS) === "number" ? pInputTimeoutMS : 300000);
-	if (inputTimeoutMS == 0)
-		inputTimeoutMS = 300000;
-	// Input a key from the user and take action based on the user's input.  If
-	// the user is a sysop, don't use an input timeout.
-	var userInput = "";
-	if (user.compare_ars("SYSOP"))
-		userInput = console.getkey(getKeyMode);
-	else
-		userInput = console.inkey(getKeyMode, inputTimeoutMS);
-	if (userInput == KEY_ESC)
-	{
-		switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
-		{
-			case '[':
-				switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
-				{
-					case 'V':
-						userInput = KEY_PAGE_UP;
-						break;
-					case 'U':
-						userInput = KEY_PAGE_DOWN;
-						break;
-				}
-				break;
-			case 'O':
-				switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
-				{
-					case 'P':
-						userInput = KEY_F1;
-						break;
-					case 'Q':
-						userInput = KEY_F2;
-						break;
-					case 'R':
-						userInput = KEY_F3;
-						break;
-					case 'S':
-						userInput = KEY_F4;
-						break;
-					case 't':
-						userInput = KEY_F5;
-						break;
-				}
-			default:
-				break;
-		}
-	}
-
-	return userInput;
-}
-
-// Calculates & returns a page number.
-//
-// Parameters:
-//  pTopIndex: The index (0-based) of the topmost item on the page
-//  pNumPerPage: The number of items per page
-//
-// Return value: The page number
-function calcPageNum(pTopIndex, pNumPerPage)
-{
-  return ((pTopIndex / pNumPerPage) + 1);
-}
-
-// Finds the (1-based) page number of an item by number (1-based).  If no page
-// is found, then the return value will be 0.
-//
-// Parameters:
-//  pItemNum: The item number (1-based)
-//  pNumPerPage: The number of items per page
-//  pTotoalNum: The total number of items in the list
-//  pReverseOrder: Boolean - Whether or not the list is in reverse order.  If not specified,
-//                 this will default to false.
-//
-// Return value: The page number (1-based) of the item number.  If no page is found,
-//               the return value will be 0.
-function findPageNumOfItemNum(pItemNum, pNumPerPage, pTotalNum, pReverseOrder)
-{
-	if ((typeof(pItemNum) !== "number") || (typeof(pNumPerPage) !== "number") || (typeof(pTotalNum) !== "number"))
-		return 0;
-	if ((pItemNum < 1) || (pItemNum > pTotalNum))
-		return 0;
-
-	var reverseOrder = (typeof(pReverseOrder) == "boolean" ? pReverseOrder : false);
-	var itemPageNum = 0;
-	if (reverseOrder)
-	{
-		var pageNum = 1;
-		for (var topNum = pTotalNum; ((topNum > 0) && (itemPageNum == 0)); topNum -= pNumPerPage)
-		{
-			if ((pItemNum <= topNum) && (pItemNum >= topNum-pNumPerPage+1))
-				itemPageNum = pageNum;
-			++pageNum;
-		}
-	}
-	else // Forward order
-		itemPageNum = Math.ceil(pItemNum / pNumPerPage);
-
-	return itemPageNum;
-}
-
-
-
-
-function logStackTrace(levels) {
-    var callstack = [];
-    var isCallstackPopulated = false;
-    try {
-        i.dont.exist += 0; //doesn't exist- that's the point
-    } catch (e) {
-        if (e.stack) { //Firefox / chrome
-            var lines = e.stack.split('\n');
-            for (var i = 0, len = lines.length; i < len; i++) {
-                    callstack.push(lines[i]);
-            }
-            //Remove call to logStackTrace()
-            callstack.shift();
-            isCallstackPopulated = true;
-        }
-        else if (window.opera && e.message) { //Opera
-            var lines = e.message.split('\n');
-            for (var i = 0, len = lines.length; i < len; i++) {
-                if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
-                    var entry = lines[i];
-                    //Append next line also since it has the file info
-                    if (lines[i + 1]) {
-                        entry += " at " + lines[i + 1];
-                        i++;
-                    }
-                    callstack.push(entry);
-                }
-            }
-            //Remove call to logStackTrace()
-            callstack.shift();
-            isCallstackPopulated = true;
-        }
-    }
-    if (!isCallstackPopulated) { //IE and Safari
-        var currentFunction = arguments.callee.caller;
-        while (currentFunction) {
-            var fn = currentFunction.toString();
-            var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
-            callstack.push(fname);
-            currentFunction = currentFunction.caller;
-        }
-    }
-    if (levels) {
-        console.print(callstack.slice(0, levels).join("\r\n"));
-    }
-    else {
-        console.print(callstack.join("\r\n"));
-    }
-}