From 35dbc32f0a34ed43b9de8f64fffc263fc4da0826 Mon Sep 17 00:00:00 2001
From: nightfox <>
Date: Mon, 15 Feb 2016 00:19:32 +0000
Subject: [PATCH] Added the ability to display a custom header file above the
 area lists in the area choosers.  Added the configuration options
 areaChooserHdrFilenameBase and areaChooserHdrMaxLines to specify the filename
 (without the extension) and maximum number of lines from the header file to
 use.  These are still beta versions, but these should be ready for an
 official release soon.

---
 xtrn/DDAreaChoosers/DDFileAreaChooser.cfg |    2 +
 xtrn/DDAreaChoosers/DDFileAreaChooser.js  | 1115 ++++++++++++---------
 xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg  |    2 +
 xtrn/DDAreaChoosers/DDMsgAreaChooser.js   |  488 ++++++---
 xtrn/DDAreaChoosers/Read Me.txt           |   89 +-
 xtrn/DDAreaChoosers/Revision history.txt  |   59 ++
 6 files changed, 1118 insertions(+), 637 deletions(-)
 create mode 100644 xtrn/DDAreaChoosers/Revision history.txt

diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg b/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg
index 206bf56598..00a7aaeaec 100644
--- a/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg
+++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg
@@ -1,5 +1,7 @@
 [BEHAVIOR]
 useLightbarInterface=true
+areaChooserHdrFilenameBase=fileAreaChgHeader
+areaChooserHdrMaxLines=5
 
 [COLORS]
 ; Area number
diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.js b/xtrn/DDAreaChoosers/DDFileAreaChooser.js
index 9ab6ebf00a..63ee3f9449 100644
--- a/xtrn/DDAreaChoosers/DDFileAreaChooser.js
+++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.js
@@ -80,8 +80,8 @@ if (system.version_num < 31400)
 }
 
 // Version & date variables
-var DD_FILE_AREA_CHOOSER_VERSION = "1.09";
-var DD_FILE_AREA_CHOOSER_VER_DATE = "2016-01-17";
+var DD_FILE_AREA_CHOOSER_VERSION = "1.10 Beta 1";
+var DD_FILE_AREA_CHOOSER_VER_DATE = "2016-02-14";
 
 // Keyboard input key codes
 var CTRL_M = "\x0d";
@@ -97,6 +97,14 @@ var KEY_PAGE_DOWN = "\1PgDn";
 var UP_ARROW = ascii(24);
 var DOWN_ARROW = ascii(25);
 
+// Determine the script's startup directory.
+// This code is a trick that was created by Deuce, suggested by Rob Swindell
+// as a way to detect which directory the script was executed in.  I've
+// shortened the code a little.
+var gStartupPath = '.';
+try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
+gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
+
 // 1st command-line argument: Whether or not to choose a file library first (if
 // false, then only choose a directory within the user's current library).  This
 // can be true or false.
@@ -161,6 +169,10 @@ function DDFileAreaChooser()
 	this.areaNumLen = 4;
 	this.descFieldLen = 67; // Description field length
 
+	// Filename base of a header to display above the area list
+	this.areaChooserHdrFilenameBase = "fileAreaChgHeader";
+	this.areaChooserHdrMaxLines = 5;
+
 	// Set the function pointers for the object
 	this.ReadConfigFile = DDFileAreaChooser_ReadConfigFile;
 	this.SelectFileArea = DDFileAreaChooser_selectFileArea;
@@ -183,9 +195,10 @@ function DDFileAreaChooser()
 	this.ShowHelpScreen = DDFileAreaChooser_showHelpScreen;
 	// Misc. functions
 	this.NumFilesInDir = DDFileAreaChooser_NumFilesInDir;
-
 	// Function to build the directory printf information for a file lib
 	this.BuildFileDirPrintfInfoForLib = DDFileAreaChooser_buildFileDirPrintfInfoForLib;
+	// Function to display the header above the area list
+	this.DisplayAreaChgHdr = DDFileAreaChooser_DisplayAreaChgHdr;
 
 	// Read the settings from the config file.
 	this.ReadConfigFile();
@@ -268,6 +281,10 @@ function DDFileAreaChooser()
 	// created on the fly the first time the user lists directories for
 	// a file library.
 	this.fileDirListPrintfInfo = new Array();
+
+	// areaChangeHdrLines is an array of text lines to use as a header to display
+	// above the message area changer lists.
+	this.areaChangeHdrLines = loadTextFileIntoArray(this.areaChooserHdrFilenameBase, this.areaChooserHdrMaxLines);
 }
 
 // For the DDFileAreaChooser class: Lets the user choose a file area.
@@ -316,6 +333,9 @@ function DDFileAreaChooser_selectFileArea_Traditional(pChooseLib)
 			bbs.command_str = "";
 
 			console.clear("\1n");
+			this.DisplayAreaChgHdr(1);
+			if (this.areaChangeHdrLines.length > 0)
+				console.crlf();
 			this.ListFileLibs();
 			console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + +(bbs.curlib+1) + "\1n\1c]:\1h ");
 			// Accept Q (quit) or a file library number
@@ -352,47 +372,50 @@ function DDFileAreaChooser_selectFileArea_Traditional(pChooseLib)
 // Return value: Boolean - Whether or not the user chose a file area.
 function DDFileAreaChooser_selectDirWithinFileLib_Traditional(pLibNumber, pSelectedDir)
 {
-   var userChoseAnArea = false;
+	var userChoseAnArea = false;
 
-   // If the file library number is valid, then
-   // set it and let the user choose a file directory
-   // within the library.
-   if (pLibNumber > 0)
-   {
-      // Ensure that the file directory printf information is created for
-      // this file library.
-      this.BuildFileDirPrintfInfoForLib(pLibNumber-1);
+	// If the file library number is valid, then
+	// set it and let the user choose a file directory
+	// within the library.
+	if (pLibNumber > 0)
+	{
+		// Ensure that the file directory printf information is created for
+		// this file library.
+		this.BuildFileDirPrintfInfoForLib(pLibNumber-1);
 
-      // Set the default directory #: The current directory, or if the
-      // user chose a different file library, then this should be set
-      // to the first directory.
-      var defaultDir = bbs.curdir + 1;
-      if (pLibNumber-1 != bbs.curlib)
-         defaultDir = 1;
+		// Set the default directory #: The current directory, or if the
+		// user chose a different file library, then this should be set
+		// to the first directory.
+		var defaultDir = bbs.curdir + 1;
+		if (pLibNumber-1 != bbs.curlib)
+			defaultDir = 1;
 
-      console.clear("\1n");
-      this.ListDirsInFileLib(pLibNumber - 1, defaultDir - 1);
-      console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + defaultDir +
-                    "\1n\1c]: \1h");
-      // Accept Q (quit) or a file directory number
-      var selectedDir = console.getkeys("Q", file_area.lib_list[pLibNumber - 1].dir_list.length);
-
-      // If the user just pressed enter (selectedDir would be blank),
-      // default the selected directory.
-      if (selectedDir.toString() == "")
-         selectedDir = defaultDir;
-
-      // If the user chose a directory, then set bbs.curlib &
-      // bbs.curdir and quit the file library loop.
-      if ((pLibNumber.toString() != "Q") && (selectedDir > 0))
-      {
-         bbs.curlib = pLibNumber - 1;
-         bbs.curdir = selectedDir - 1;
-         userChoseAnArea = true;
-      }
-   }
+		console.clear("\1n");
+		this.DisplayAreaChgHdr(1);
+		if (this.areaChangeHdrLines.length > 0)
+			console.crlf();
+		this.ListDirsInFileLib(pLibNumber - 1, defaultDir - 1);
+		console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + defaultDir +
+		              "\1n\1c]: \1h");
+		// Accept Q (quit) or a file directory number
+		var selectedDir = console.getkeys("Q", file_area.lib_list[pLibNumber - 1].dir_list.length);
+
+		// If the user just pressed enter (selectedDir would be blank),
+		// default the selected directory.
+		if (selectedDir.toString() == "")
+			selectedDir = defaultDir;
+
+		// If the user chose a directory, then set bbs.curlib &
+		// bbs.curdir and quit the file library loop.
+		if ((pLibNumber.toString() != "Q") && (selectedDir > 0))
+		{
+			bbs.curlib = pLibNumber - 1;
+			bbs.curdir = selectedDir - 1;
+			userChoseAnArea = true;
+		}
+	}
 
-   return userChoseAnArea;
+	return userChoseAnArea;
 }
 
 // For the DDFileAreaChooser class: Traditional user interface for listing
@@ -566,7 +589,7 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 		if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number"))
 			selectedLibIndex = bbs.curlib;
 
-		var listStartRow = 2;      // The row on the screen where the list will start
+		var listStartRow = 2+this.areaChangeHdrLines.length; // The row on the screen where the list will start
 		var listEndRow = console.screen_rows - 1; // Row on screen where list will end
 		var topFileLibIndex = 0;    // The index of the message group at the top of the list
 
@@ -612,14 +635,15 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 			}
 		}
 
-		// Clear the screen, write the help line and group list header, and output
+		// Clear the screen, write the header, help line, and group list header, and output
 		// a screenful of message groups.
 		console.clear("\1n");
+		this.DisplayAreaChgHdr(1);
 		this.WriteKeyHelpLine();
 
 		var curpos = new Object();
 		curpos.x = 1;
-		curpos.y = 1;
+		curpos.y = 1+this.areaChangeHdrLines.length;
 		console.gotoxy(curpos);
 		this.WriteLibListHdrLine(numPages, pageNum);
 		this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, false, false);
@@ -729,7 +753,7 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 					{
 						// An area was not chosen, so we'll have to re-draw
 						// the header and list of message groups.
-						console.gotoxy(1, 1);
+						console.gotoxy(1, 1+this.areaChangeHdrLines.length);
 						this.WriteLibListHdrLine(numPages, pageNum);
 						this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, false, true);
 					}
@@ -784,6 +808,7 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 						selectedLibIndex = topIndexForLastPage;
 					}
 					break;
+				case KEY_ESC: // Quit
 				case 'Q': // Quit
 					continueChoosingFileArea = false;
 					break;
@@ -791,8 +816,9 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 					this.ShowHelpScreen(true, true);
 					console.pause();
 					// Refresh the screen
+					this.DisplayAreaChgHdr(1);
 					this.WriteKeyHelpLine();
-					console.gotoxy(1, 1);
+					console.gotoxy(1, 1+this.areaChangeHdrLines.length);
 					this.WriteLibListHdrLine(numPages, pageNum);
 					this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, false, true);
 					break;
@@ -876,347 +902,348 @@ function DDFileAreaChooser_selectFileArea_Lightbar(pChooseLib)
 //                             Will be -1 if none chosen.
 function DDFileAreaChooser_selectDirWithinFileLib_Lightbar(pLibIndex, pHighlightIndex)
 {
-   // Create the return object.
-   var retObj = new Object();
-   retObj.fileDirChosen = false;
-   retObj.fileLibIndex = -1;
-
-   var libIndex = 0;
-   if (typeof(pLibIndex) == "number")
-      libIndex = pLibIndex;
-   else if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number"))
-      libIndex = bbs.curlib;
-   // Double-check libIndex
-   if (libIndex < 0)
-      libIndex = 0;
-   else if (libIndex >= file_area.lib_list.length)
-      libIndex = file_area.lib_list.length - 1;
-
-   var highlightIndex = 0;
-   if ((pHighlightIndex != null) && (typeof(pHighlightIndex) == "number"))
-      highlightIndex = pHighlightIndex;
-   else if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number") &&
-             (bbs.curlib == pLibIndex))
-   {
-      highlightIndex = bbs.curdir;
-   }
-   // Double-check highlightIndex
-   if (highlightIndex < 0)
-      highlightIndex = 0;
-   else if (highlightIndex >= file_area.lib_list[libIndex].dir_list.length)
-      highlightIndex = file_area.lib_list[libIndex].dir_list.length - 1;
-
-   // If there are no sub-boards in the given message group, then show
-   // an error and return.
-   if (file_area.lib_list[libIndex].dir_list.length == 0)
-   {
-      console.clear("\1n");
-      console.print("\1y\1hThere are no directories in the chosen library.\r\n\1p");
-      return retObj;
-   }
-   
-   // Ensure that the file directory printf information is created for
-   // this file library.
-   this.BuildFileDirPrintfInfoForLib(libIndex);
-
-   // Returns the index of the bottommost directory that can be displayed on
-   // the screen.
-   //
-   // Parameters:
-   //  pTopDirIndex: The index of the topmost directory displayed on screen
-   //  pNumItemsPerPage: The number of items per page
-   function getBottommostDirIndex(pTopDirIndex, pNumItemsPerPage)
-   {
-      var bottomDirIndex = pTopDirIndex + pNumItemsPerPage - 1;
-      // If bottomDirIndex is beyond the last index, then adjust it.
-      if (bottomDirIndex >= file_area.lib_list[libIndex].dir_list.length)
-         bottomDirIndex = file_area.lib_list[libIndex].dir_list.length - 1;
-      return bottomDirIndex;
-   }
+	// Create the return object.
+	var retObj = new Object();
+	retObj.fileDirChosen = false;
+	retObj.fileLibIndex = -1;
 
+	var libIndex = 0;
+	if (typeof(pLibIndex) == "number")
+		libIndex = pLibIndex;
+	else if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number"))
+		libIndex = bbs.curlib;
+	// Double-check libIndex
+	if (libIndex < 0)
+		libIndex = 0;
+	else if (libIndex >= file_area.lib_list.length)
+		libIndex = file_area.lib_list.length - 1;
+
+	var highlightIndex = 0;
+	if ((pHighlightIndex != null) && (typeof(pHighlightIndex) == "number"))
+		highlightIndex = pHighlightIndex;
+	else if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number") &&
+			(bbs.curlib == pLibIndex))
+	{
+		highlightIndex = bbs.curdir;
+	}
+	// Double-check highlightIndex
+	if (highlightIndex < 0)
+		highlightIndex = 0;
+	else if (highlightIndex >= file_area.lib_list[libIndex].dir_list.length)
+		highlightIndex = file_area.lib_list[libIndex].dir_list.length - 1;
+
+	// If there are no sub-boards in the given message group, then show
+	// an error and return.
+	if (file_area.lib_list[libIndex].dir_list.length == 0)
+	{
+		console.clear("\1n");
+		console.print("\1y\1hThere are no directories in the chosen library.\r\n\1p");
+		return retObj;
+	}
 
-   // Figure out the index of the user's currently-selected sub-board.
-   var selectedDirIndex = 0;
-   if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number"))
-   {
-      if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number") &&
-          (bbs.curlib == pLibIndex))
-      {
-         selectedDirIndex = bbs.curdir;
-      }
-   }
+	// Ensure that the file directory printf information is created for
+	// this file library.
+	this.BuildFileDirPrintfInfoForLib(libIndex);
 
-   var listStartRow = 3;      // The row on the screen where the list will start
-   var listEndRow = console.screen_rows - 1; // Row on screen where list will end
-   var topDirIndex = 0;      // The index of the message group at the top of the list
-   // Figure out the index of the last message group to appear on the screen.
-   var numItemsPerPage = listEndRow - listStartRow + 1;
-   var bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-   // Figure out how many pages are needed to list all the sub-boards.
-   var numPages = Math.ceil(file_area.lib_list[libIndex].dir_list.length / numItemsPerPage);
-   var pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-   // Figure out the top index for the last page.
-   var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage;
-
-   // If the highlighted row is beyond the current screen, then
-   // go to the appropriate page.
-   if (selectedDirIndex > bottomDirIndex)
-   {
-      var nextPageTopIndex = 0;
-      while (selectedDirIndex > bottomDirIndex)
-      {
-         nextPageTopIndex = topDirIndex + numItemsPerPage;
-         if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length)
-         {
-            // Adjust topDirIndex and bottomDirIndex, and
-            // refresh the list on the screen.
-            topDirIndex = nextPageTopIndex;
-            pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-            bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-         }
-         else
-            break;
-      }
+	// Returns the index of the bottommost directory that can be displayed on
+	// the screen.
+	//
+	// Parameters:
+	//  pTopDirIndex: The index of the topmost directory displayed on screen
+	//  pNumItemsPerPage: The number of items per page
+	function getBottommostDirIndex(pTopDirIndex, pNumItemsPerPage)
+	{
+		var bottomDirIndex = pTopDirIndex + pNumItemsPerPage - 1;
+		// If bottomDirIndex is beyond the last index, then adjust it.
+		if (bottomDirIndex >= file_area.lib_list[libIndex].dir_list.length)
+			bottomDirIndex = file_area.lib_list[libIndex].dir_list.length - 1;
+		return bottomDirIndex;
+	}
 
-      // If we didn't find the correct page for some reason, then set the
-      // variables to display page 1 and select the first message group.
-      var foundCorrectPage =
-          ((topDirIndex < file_area.lib_list[libIndex].dir_list.length) &&
-           (selectedDirIndex >= topDirIndex) && (selectedDirIndex <= bottomDirIndex));
-      if (!foundCorrectPage)
-      {
-         topDirIndex = 0;
-         pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-         bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-         selectedDirIndex = 0;
-      }
-   }
 
-   // Clear the screen, write the help line and group list header, and output
-   // a screenful of message groups.
-   console.clear("\1n");
-   this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
-   this.WriteKeyHelpLine();
-
-   var curpos = new Object();
-   curpos.x = 1;
-   curpos.y = 2;
-   console.gotoxy(curpos);
-   printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
-   this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, listEndRow,
-                               false, false);
-   // Start of the input loop.
-   var highlightScrenRow = 0; // The row on the screen for the highlighted group
-   var userInput = "";        // Will store a keypress from the user
-   var continueChoosingFileDir = true;
-   while (continueChoosingFileDir)
-   {
-      // Highlight the currently-selected message group
-      highlightScrenRow = listStartRow + (selectedDirIndex - topDirIndex);
-      curpos.y = highlightScrenRow;
-      if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows))
-      {
-         console.gotoxy(1, highlightScrenRow);
-         this.WriteFileLibDirLine(libIndex, selectedDirIndex, true);
-      }
+	// Figure out the index of the user's currently-selected sub-board.
+	var selectedDirIndex = 0;
+	if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number"))
+	{
+		if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number") && (bbs.curlib == pLibIndex))
+			selectedDirIndex = bbs.curdir;
+	}
 
-      // Get a key from the user (upper-case) and take action based upon it.
-      userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF);
-      switch (userInput)
-      {
-         case KEY_UP: // Move up one message group in the list
-            if (selectedDirIndex > 0)
-            {
-               // If the previous group index is on the previous page, then
-               // display the previous page.
-               var previousSubIndex = selectedDirIndex - 1;
-               if (previousSubIndex < topDirIndex)
-               {
-                  // Adjust topDirIndex and bottomDirIndex, and
-                  // refresh the list on the screen.
-                  topDirIndex -= numItemsPerPage;
-                  pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-                  bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-                  this.updatePageNumInHeader(pageNum, numPages, false, false);
-                  this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                              listEndRow, false, true);
-               }
-               else
-               {
-                  // Display the current line un-highlighted.
-                  console.gotoxy(1, curpos.y);
-                  this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
-               }
-               selectedDirIndex = previousSubIndex;
-            }
-            break;
-         case KEY_DOWN: // Move down one message group in the list
-            if (selectedDirIndex < file_area.lib_list[libIndex].dir_list.length - 1)
-            {
-               // If the next group index is on the next page, then display
-               // the next page.
-               var nextGrpIndex = selectedDirIndex + 1;
-               if (nextGrpIndex > bottomDirIndex)
-               {
-                  // Adjust topDirIndex and bottomDirIndex, and
-                  // refresh the list on the screen.
-                  topDirIndex += numItemsPerPage;
-                  pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-                  bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-                  this.updatePageNumInHeader(pageNum, numPages, false, false);
-                  this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                              listEndRow, false, true);
-               }
-               else
-               {
-                  // Display the current line un-highlighted.
-                  console.gotoxy(1, curpos.y);
-                  this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
-               }
-               selectedDirIndex = nextGrpIndex;
-            }
-            break;
-         case KEY_HOME: // Go to the top message group on the screen
-            if (selectedDirIndex > topDirIndex)
-            {
-               // Display the current line un-highlighted, then adjust
-               // selectedDirIndex.
-               console.gotoxy(1, curpos.y);
-               this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
-               selectedDirIndex = topDirIndex;
-               // Note: curpos.y is set at the start of the while loop.
-            }
-            break;
-         case KEY_END: // Go to the bottom message group on the screen
-            if (selectedDirIndex < bottomDirIndex)
-            {
-               // Display the current line un-highlighted, then adjust
-               // selectedDirIndex.
-               console.gotoxy(1, curpos.y);
-               this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
-               selectedDirIndex = bottomDirIndex;
-               // Note: curpos.y is set at the start of the while loop.
-            }
-            break;
-         case KEY_ENTER: // Select the currently-highlighted sub-board; and we're done.
-            continueChoosingFileDir = false;
-            retObj.fileDirChosen = true;
-            retObj.fileLibIndex = selectedDirIndex;
-            break;
-         case KEY_PAGE_DOWN: // Go to the next page
-            var nextPageTopIndex = topDirIndex + numItemsPerPage;
-            if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length)
-            {
-               // Adjust topDirIndex and bottomDirIndex, and
-               // refresh the list on the screen.
-               topDirIndex = nextPageTopIndex;
-               pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-               bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-               this.updatePageNumInHeader(pageNum, numPages, false, false);
-               this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                            listEndRow, false, true);
-               selectedDirIndex = topDirIndex;
-            }
-            break;
-         case KEY_PAGE_UP: // Go to the previous page
-            var prevPageTopIndex = topDirIndex - numItemsPerPage;
-            if (prevPageTopIndex >= 0)
-            {
-               // Adjust topDirIndex and bottomDirIndex, and
-               // refresh the list on the screen.
-               topDirIndex = prevPageTopIndex;
-               pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-               bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-               this.updatePageNumInHeader(pageNum, numPages, false, false);
-               this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                            listEndRow, false, true);
-               selectedDirIndex = topDirIndex;
-            }
-            break;
-         case 'F': // Go to the first page
-            if (topDirIndex > 0)
-            {
-               topDirIndex = 0;
-               pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-               bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-               this.updatePageNumInHeader(pageNum, numPages, false, false);
-               this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                            listEndRow, false, true);
-               selectedDirIndex = 0;
-            }
-            break;
-         case 'L': // Go to the last page
-            if (topDirIndex < topIndexForLastPage)
-            {
-               topDirIndex = topIndexForLastPage;
-               pageNum = calcPageNum(topDirIndex, numItemsPerPage);
-               bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
-               this.updatePageNumInHeader(pageNum, numPages, false, false);
-               this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                            listEndRow, false, true);
-               selectedDirIndex = topIndexForLastPage;
-            }
-            break;
-         case 'Q': // Quit
-            continueChoosingFileDir = false;
-            break;
-         case '?': // Show help
-            this.ShowHelpScreen(true, true);
-            console.pause();
-            // Refresh the screen
-            console.gotoxy(1, 1);
-            this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
-            console.cleartoeol("\1n");
-            this.WriteKeyHelpLine();
-            console.gotoxy(1, 2);
-            printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
-            this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                      listEndRow, false, true);
-            break;
-         default:
-            // If the user entered a numeric digit, then treat it as
-            // the start of the message group number.
-            if (userInput.match(/[0-9]/))
-            {
-               var originalCurpos = curpos;
-
-               // Put the user's input back in the input buffer to
-               // be used for getting the rest of the message number.
-               console.ungetstr(userInput);
-               // Move the cursor to the bottom of the screen and
-               // prompt the user for the message number.
-               console.gotoxy(1, console.screen_rows);
-               console.clearline("\1n");
-               console.print("\1cDir #: \1h");
-               userInput = console.getnum(file_area.lib_list[libIndex].dir_list.length);
-               // If the user made a selection, then set it in the
-               // return object and don't continue the input loop.
-               if (userInput > 0)
-               {
-                  continueChoosingFileDir = false;
-                  retObj.fileDirChosen = true;
-                  retObj.fileLibIndex = userInput - 1;
-               }
-               else
-               {
-                  // The user didn't enter a selection.  Now we need to
-                  // re-draw the screen due to everything being moved
-                  // up one line.
-                  console.gotoxy(1, 1);
-                  this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
-                  console.cleartoeol("\1n");
-                  this.WriteKeyHelpLine();
-                  console.gotoxy(1, 2);
-                  printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
-                  this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
-                                            listEndRow, false, true);
-               }
-            }
-            break;
-      }
-   }
+	var listStartRow = 3+this.areaChangeHdrLines.length;      // The row on the screen where the list will start
+	var listEndRow = console.screen_rows - 1; // Row on screen where list will end
+	var topDirIndex = 0;      // The index of the message group at the top of the list
+	// Figure out the index of the last message group to appear on the screen.
+	var numItemsPerPage = listEndRow - listStartRow + 1;
+	var bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+	// Figure out how many pages are needed to list all the sub-boards.
+	var numPages = Math.ceil(file_area.lib_list[libIndex].dir_list.length / numItemsPerPage);
+	var pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+	// Figure out the top index for the last page.
+	var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage;
+
+	// If the highlighted row is beyond the current screen, then
+	// go to the appropriate page.
+	if (selectedDirIndex > bottomDirIndex)
+	{
+		var nextPageTopIndex = 0;
+		while (selectedDirIndex > bottomDirIndex)
+		{
+			nextPageTopIndex = topDirIndex + numItemsPerPage;
+			if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length)
+			{
+				// Adjust topDirIndex and bottomDirIndex, and
+				// refresh the list on the screen.
+				topDirIndex = nextPageTopIndex;
+				pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+				bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+			}
+			else
+				break;
+		}
 
-   return retObj;
+		// If we didn't find the correct page for some reason, then set the
+		// variables to display page 1 and select the first message group.
+		var foundCorrectPage = ((topDirIndex < file_area.lib_list[libIndex].dir_list.length) &&
+		                        (selectedDirIndex >= topDirIndex) && (selectedDirIndex <= bottomDirIndex));
+		if (!foundCorrectPage)
+		{
+			topDirIndex = 0;
+			pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+			bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+			selectedDirIndex = 0;
+		}
+	}
+
+	// Clear the screen, write the header, help line, and group list header, and output
+	// a screenful of message groups.
+	console.clear("\1n");
+	this.DisplayAreaChgHdr(1);
+	if (this.areaChangeHdrLines.length > 0)
+		console.crlf();
+	this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
+	this.WriteKeyHelpLine();
+
+	var curpos = new Object();
+	curpos.x = 1;
+	curpos.y = 2+this.areaChangeHdrLines.length;
+	console.gotoxy(curpos);
+	printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
+	       this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, listEndRow,
+	       false, false);
+	// Start of the input loop.
+	var highlightScrenRow = 0; // The row on the screen for the highlighted group
+	var userInput = "";        // Will store a keypress from the user
+	var continueChoosingFileDir = true;
+	while (continueChoosingFileDir)
+	{
+		// Highlight the currently-selected message group
+		highlightScrenRow = listStartRow + (selectedDirIndex - topDirIndex);
+		curpos.y = highlightScrenRow;
+		if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows))
+		{
+			console.gotoxy(1, highlightScrenRow);
+			this.WriteFileLibDirLine(libIndex, selectedDirIndex, true);
+		}
+
+		// Get a key from the user (upper-case) and take action based upon it.
+		userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF);
+		switch (userInput)
+		{
+			case KEY_UP: // Move up one message group in the list
+				if (selectedDirIndex > 0)
+				{
+					// If the previous group index is on the previous page, then
+					// display the previous page.
+					var previousSubIndex = selectedDirIndex - 1;
+					if (previousSubIndex < topDirIndex)
+					{
+						// Adjust topDirIndex and bottomDirIndex, and
+						// refresh the list on the screen.
+						topDirIndex -= numItemsPerPage;
+						pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+						bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+						this.updatePageNumInHeader(pageNum, numPages, false, false);
+						this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+						                         listEndRow, false, true);
+					}
+					else
+					{
+						// Display the current line un-highlighted.
+						console.gotoxy(1, curpos.y);
+						this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
+					}
+					selectedDirIndex = previousSubIndex;
+				}
+				break;
+			case KEY_DOWN: // Move down one message group in the list
+				if (selectedDirIndex < file_area.lib_list[libIndex].dir_list.length - 1)
+				{
+					// If the next group index is on the next page, then display
+					// the next page.
+					var nextGrpIndex = selectedDirIndex + 1;
+					if (nextGrpIndex > bottomDirIndex)
+					{
+						// Adjust topDirIndex and bottomDirIndex, and
+						// refresh the list on the screen.
+						topDirIndex += numItemsPerPage;
+						pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+						bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+						this.updatePageNumInHeader(pageNum, numPages, false, false);
+						this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+						                         listEndRow, false, true);
+					}
+					else
+					{
+						// Display the current line un-highlighted.
+						console.gotoxy(1, curpos.y);
+						this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
+					}
+					selectedDirIndex = nextGrpIndex;
+				}
+				break;
+			case KEY_HOME: // Go to the top message group on the screen
+				if (selectedDirIndex > topDirIndex)
+				{
+					// Display the current line un-highlighted, then adjust
+					// selectedDirIndex.
+					console.gotoxy(1, curpos.y);
+					this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
+					selectedDirIndex = topDirIndex;
+					// Note: curpos.y is set at the start of the while loop.
+				}
+				break;
+			case KEY_END: // Go to the bottom message group on the screen
+				if (selectedDirIndex < bottomDirIndex)
+				{
+					// Display the current line un-highlighted, then adjust
+					// selectedDirIndex.
+					console.gotoxy(1, curpos.y);
+					this.WriteFileLibDirLine(libIndex, selectedDirIndex, false);
+					selectedDirIndex = bottomDirIndex;
+					// Note: curpos.y is set at the start of the while loop.
+				}
+				break;
+			case KEY_ENTER: // Select the currently-highlighted sub-board; and we're done.
+				continueChoosingFileDir = false;
+				retObj.fileDirChosen = true;
+				retObj.fileLibIndex = selectedDirIndex;
+				break;
+			case KEY_PAGE_DOWN: // Go to the next page
+				var nextPageTopIndex = topDirIndex + numItemsPerPage;
+				if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length)
+				{
+					// Adjust topDirIndex and bottomDirIndex, and
+					// refresh the list on the screen.
+					topDirIndex = nextPageTopIndex;
+					pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+					bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+					this.updatePageNumInHeader(pageNum, numPages, false, false);
+					this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+					                         listEndRow, false, true);
+					selectedDirIndex = topDirIndex;
+				}
+				break;
+			case KEY_PAGE_UP: // Go to the previous page
+				var prevPageTopIndex = topDirIndex - numItemsPerPage;
+				if (prevPageTopIndex >= 0)
+				{
+					// Adjust topDirIndex and bottomDirIndex, and
+					// refresh the list on the screen.
+					topDirIndex = prevPageTopIndex;
+					pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+					bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+					this.updatePageNumInHeader(pageNum, numPages, false, false);
+					this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+					                         listEndRow, false, true);
+					selectedDirIndex = topDirIndex;
+				}
+				break;
+			case 'F': // Go to the first page
+				if (topDirIndex > 0)
+				{
+					topDirIndex = 0;
+					pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+					bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+					this.updatePageNumInHeader(pageNum, numPages, false, false);
+					this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+					                         listEndRow, false, true);
+					selectedDirIndex = 0;
+				}
+				break;
+			case 'L': // Go to the last page
+				if (topDirIndex < topIndexForLastPage)
+				{
+					topDirIndex = topIndexForLastPage;
+					pageNum = calcPageNum(topDirIndex, numItemsPerPage);
+					bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage);
+					this.updatePageNumInHeader(pageNum, numPages, false, false);
+					this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+					                         listEndRow, false, true);
+					selectedDirIndex = topIndexForLastPage;
+				}
+				break;
+			case KEY_ESC: // Quit
+			case 'Q': // Quit
+				continueChoosingFileDir = false;
+				break;
+			case '?': // Show help
+				this.ShowHelpScreen(true, true);
+				console.pause();
+				// Refresh the screen
+				this.DisplayAreaChgHdr(1);
+				console.gotoxy(1, 1+this.areaChangeHdrLines.length);
+				this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
+				console.cleartoeol("\1n");
+				this.WriteKeyHelpLine();
+				console.gotoxy(1, 2+this.areaChangeHdrLines.length);
+				printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
+				this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+				listEndRow, false, true);
+				break;
+			default:
+				// If the user entered a numeric digit, then treat it as
+				// the start of the message group number.
+				if (userInput.match(/[0-9]/))
+				{
+					var originalCurpos = curpos;
+
+					// Put the user's input back in the input buffer to
+					// be used for getting the rest of the message number.
+					console.ungetstr(userInput);
+					// Move the cursor to the bottom of the screen and
+					// prompt the user for the message number.
+					console.gotoxy(1, console.screen_rows);
+					console.clearline("\1n");
+					console.print("\1cDir #: \1h");
+					userInput = console.getnum(file_area.lib_list[libIndex].dir_list.length);
+					// If the user made a selection, then set it in the
+					// return object and don't continue the input loop.
+					if (userInput > 0)
+					{
+						continueChoosingFileDir = false;
+						retObj.fileDirChosen = true;
+						retObj.fileLibIndex = userInput - 1;
+					}
+					else
+					{
+						// The user didn't enter a selection.  Now we need to
+						// re-draw the screen due to everything being moved
+						// up one line.
+						console.gotoxy(1, 1);
+						this.WriteDirListHdr1Line(libIndex, numPages, pageNum);
+						console.cleartoeol("\1n");
+						this.WriteKeyHelpLine();
+						console.gotoxy(1, 2);
+						printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files");
+						this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow,
+						                         listEndRow, false, true);
+					}
+				}
+				break;
+		}
+	}
+
+	return retObj;
 }
 
 // Displays a screenful of file libraries (for the lightbar interface).
@@ -1363,12 +1390,12 @@ function DDFileAreaChooser_updatePageNumInHeader(pPageNum, pNumPages, pFileLib,
 
   if (pFileLib)
   {
-    console.gotoxy(30, 1);
+    console.gotoxy(30, 1+this.areaChangeHdrLines.length);
     console.print("\1n" + this.colors.header + pPageNum + " of " + pNumPages + ")   ");
   }
   else
   {
-    console.gotoxy(67, 1);
+    console.gotoxy(67, 1+this.areaChangeHdrLines.length);
     console.print("\1n" + this.colors.fileAreaHdr + pPageNum + " of " + pNumPages + ")   ");
   }
 
@@ -1464,82 +1491,82 @@ function DDFileAreaChooser_writeKeyHelpLine()
 // For the DDFileAreaChooser class: Reads the configuration file.
 function DDFileAreaChooser_ReadConfigFile()
 {
-   // Determine the script's startup directory.
-   // This code is a trick that was created by Deuce, suggested by Rob Swindell
-   // as a way to detect which directory the script was executed in.  I've
-   // shortened the code a little.
-   var startup_path = '.';
-   try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
-   startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''));
-
-   // Open the configuration file
-   var cfgFile = new File(startup_path + "DDFileAreaChooser.cfg");
-   if (cfgFile.open("r"))
-   {
-      var settingsMode = "behavior";
-      var fileLine = null;     // A line read from the file
-      var equalsPos = 0;       // Position of a = in the line
-      var commentPos = 0;      // Position of the start of a comment
-      var setting = null;      // A setting name (string)
-      var settingUpper = null; // Upper-case setting name
-      var value = null;        // A value for a setting (string)
-      while (!cfgFile.eof)
-      {
-         // Read the next line from the config file.
-         fileLine = cfgFile.readln(2048);
+	// Open the configuration file
+	var cfgFile = new File(gStartupPath + "DDFileAreaChooser.cfg");
+	if (cfgFile.open("r"))
+	{
+		var settingsMode = "behavior";
+		var fileLine = null;     // A line read from the file
+		var equalsPos = 0;       // Position of a = in the line
+		var commentPos = 0;      // Position of the start of a comment
+		var setting = null;      // A setting name (string)
+		var settingUpper = null; // Upper-case setting name
+		var value = null;        // A value for a setting (string)
+		while (!cfgFile.eof)
+		{
+			// Read the next line from the config file.
+			fileLine = cfgFile.readln(2048);
 
-         // fileLine should be a string, but I've seen some cases
-         // where it isn't, so check its type.
-         if (typeof(fileLine) != "string")
-            continue;
+			// fileLine should be a string, but I've seen some cases
+			// where it isn't, so check its type.
+			if (typeof(fileLine) != "string")
+				continue;
 
-         // If the line starts with with a semicolon (the comment
-         // character) or is blank, then skip it.
-         if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-            continue;
+			// If the line starts with with a semicolon (the comment
+			// character) or is blank, then skip it.
+			if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
+				continue;
 
-         // If in the "behavior" section, then set the behavior-related variables.
-         if (fileLine.toUpperCase() == "[BEHAVIOR]")
-         {
-            settingsMode = "behavior";
-            continue;
-         }
-         else if (fileLine.toUpperCase() == "[COLORS]")
-         {
-            settingsMode = "colors";
-            continue;
-         }
+			// If in the "behavior" section, then set the behavior-related variables.
+			if (fileLine.toUpperCase() == "[BEHAVIOR]")
+			{
+				settingsMode = "behavior";
+				continue;
+			}
+			else if (fileLine.toUpperCase() == "[COLORS]")
+			{
+				settingsMode = "colors";
+				continue;
+			}
 
-         // If the line has a semicolon anywhere in it, then remove
-         // everything from the semicolon onward.
-         commentPos = fileLine.indexOf(";");
-         if (commentPos > -1)
-            fileLine = fileLine.substr(0, commentPos);
-
-         // Look for an equals sign, and if found, separate the line
-         // into the setting name (before the =) and the value (after the
-         // equals sign).
-         equalsPos = fileLine.indexOf("=");
-         if (equalsPos > 0)
-         {
-            // Read the setting & value, and trim leading & trailing spaces.
-            setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-            settingUpper = setting.toUpperCase();
-            value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
-
-            if (settingsMode == "behavior")
-            {
-               // Set the appropriate value in the settings object.
-               if (settingUpper == "USELIGHTBARINTERFACE")
-                  this.useLightbarInterface = (value.toUpperCase() == "TRUE");
-            }
-            else if (settingsMode == "colors")
-               this.colors[setting] = value;
-         }
-      }
-   
-      cfgFile.close();
-   }
+			// If the line has a semicolon anywhere in it, then remove
+			// everything from the semicolon onward.
+			commentPos = fileLine.indexOf(";");
+			if (commentPos > -1)
+				fileLine = fileLine.substr(0, commentPos);
+
+			// Look for an equals sign, and if found, separate the line
+			// into the setting name (before the =) and the value (after the
+			// equals sign).
+			equalsPos = fileLine.indexOf("=");
+			if (equalsPos > 0)
+			{
+				// Read the setting & value, and trim leading & trailing spaces.
+				setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
+				settingUpper = setting.toUpperCase();
+				value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
+
+				if (settingsMode == "behavior")
+				{
+					// Set the appropriate value in the settings object.
+					if (settingUpper == "USELIGHTBARINTERFACE")
+						this.useLightbarInterface = (value.toUpperCase() == "TRUE");
+					else if (settingUpper == "AREACHOOSERHDRFILENAMEBASE")
+						this.areaChooserHdrFilenameBase = value;
+					else if (settingUpper == "AREACHOOSERHDRMAXLINES")
+					{
+						var maxNumLines = +value;
+						if (maxNumLines > 0)
+							this.areaChooserHdrMaxLines = maxNumLines;
+					}
+				}
+				else if (settingsMode == "colors")
+					this.colors[setting] = value;
+			}
+		}
+
+		cfgFile.close();
+	}
 }
 
 // Misc. functions
@@ -1684,6 +1711,64 @@ function DDFileAreaChooser_buildFileDirPrintfInfoForLib(pLibIndex)
   }
 }
 
+// For the DDFileAreaChooser class: Displays the area chooser header
+//
+// Parameters:
+//  pStartScreenRow: The row on the screen at which to start displaying the
+//                   header information.  Will be used if the user's terminal
+//                   supports ANSI.
+//  pClearRowsFirst: Optional boolean - Whether or not to clear the rows first.
+//                   Defaults to true.  Only valid if the user's terminal supports
+//                   ANSI.
+function DDFileAreaChooser_DisplayAreaChgHdr(pStartScreenRow, pClearRowsFirst)
+{
+	if (this.areaChangeHdrLines == null)
+		return;
+	if (this.areaChangeHdrLines.length == 0)
+		return;
+
+	// If the user's terminal supports ANSI and pStartScreenRow is a number, then
+	// we can move the cursor and display the header where specified.
+	if (console.term_supports(USER_ANSI) && (typeof(pStartScreenRow) == "number"))
+	{
+		// If specified to clear the rows first, then do so.
+		var screenX = 1;
+		var screenY = pStartScreenRow;
+		var clearRowsFirst = (typeof(pClearRowsFirst) == "boolean" ? pClearRowsFirst : true);
+		if (clearRowsFirst)
+		{
+			console.print("\1n");
+			for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+			{
+				console.gotoxy(screenX, screenY++);
+				console.cleartoeol();
+			}
+		}
+		// Display the header starting on the first column and the given screen row.
+		screenX = 1;
+		screenY = pStartScreenRow;
+		for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+		{
+			console.gotoxy(screenX, screenY++);
+			console.print(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
+		}
+	}
+	else
+	{
+		// The user's terminal doesn't support ANSI or pStartScreenRow is not a
+		// number - So just output the header lines.
+		for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+		{
+			console.print(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
+			console.crlf();
+		}
+	}
+}
+
 // Removes multiple, leading, and/or trailing spaces
 // The search & replace regular expressions used in this
 // function came from the following URL:
@@ -1823,4 +1908,128 @@ function getKeyWithESCChars(pGetKeyMode)
    }
 
    return userInput;
+}
+
+// Loads a text file (an .ans or .asc) into an array.  This will first look for
+// an .ans version, and if exists, convert to Synchronet colors before loading
+// it.  If an .ans doesn't exist, this will look for an .asc version.
+//
+// Parameters:
+//  pFilenameBase: The filename without the extension
+//  pMaxNumLines: Optional - The maximum number of lines to load from the text file
+//
+// Return value: An array containing the lines from the text file
+function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
+{
+	if (typeof(pFilenameBase) != "string")
+		return new Array();
+
+	var maxNumLines = (typeof(pMaxNumLines) == "number" ? pMaxNumLines : -1);
+
+	var txtFileLines = new Array();
+	// See if there is a header file that is made for the user's terminal
+	// width (areaChgHeader-<width>.ans/asc).  If not, then just go with
+	// msgHeader.ans/asc.
+	var txtFileExists = true;
+	var txtFilenameFullPath = gStartupPath + pFilenameBase;
+	var txtFileFilename = "";
+	if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
+		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
+	else if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".asc"))
+		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".asc";
+	else if (file_exists(txtFilenameFullPath + ".ans"))
+		txtFileFilename = txtFilenameFullPath + ".ans";
+	else if (file_exists(txtFilenameFullPath + ".asc"))
+		txtFileFilename = txtFilenameFullPath + ".asc";
+	else
+		txtFileExists = false;
+	if (txtFileExists)
+	{
+		var syncConvertedHdrFilename = txtFileFilename;
+		// If the user's console doesn't support ANSI and the header file is ANSI,
+		// then convert it to Synchronet attribute codes and read that file instead.
+		if (!console.term_supports(USER_ANSI) && (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS"))
+		{
+			syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
+			if (!file_exists(syncConvertedHdrFilename))
+			{
+				if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
+				{
+					var filenameBase = txtFileFilename.substr(0, dotIdx);
+					var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+								+ syncConvertedHdrFilename + "\"";
+					// Note: Both system.exec(cmdLine) and
+					// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+					// execute the command, but system.exec() seems noticeably faster.
+					system.exec(cmdLine);
+				}
+				else
+					syncConvertedHdrFilename = txtFileFilename;
+			}
+		}
+		/*
+		// If the header file is ANSI, then convert it to Synchronet attribute
+		// codes and read that file instead.  This is done so that this script can
+		// accurately get the file line lengths using console.strlen().
+		var syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
+		if (!file_exists(syncConvertedHdrFilename))
+		{
+			if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
+			{
+				var filenameBase = txtFileFilename.substr(0, dotIdx);
+				var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+				            + syncConvertedHdrFilename + "\"";
+				// Note: Both system.exec(cmdLine) and
+				// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+				// execute the command, but system.exec() seems noticeably faster.
+				system.exec(cmdLine);
+			}
+			else
+				syncConvertedHdrFilename = txtFileFilename;
+		}
+		*/
+		// Read the header file into txtFileLines
+		var hdrFile = new File(syncConvertedHdrFilename);
+		if (hdrFile.open("r"))
+		{
+			var fileLine = null;
+			while (!hdrFile.eof)
+			{
+				// Read the next line from the header file.
+				fileLine = hdrFile.readln(2048);
+				// fileLine should be a string, but I've seen some cases
+				// where it isn't, so check its type.
+				if (typeof(fileLine) != "string")
+					continue;
+
+				// Make sure the line isn't longer than the user's terminal
+				//if (fileLine.length > console.screen_columns)
+				//   fileLine = fileLine.substr(0, console.screen_columns);
+				txtFileLines.push(fileLine);
+
+				// If the header array now has the maximum number of lines, then
+				// stop reading the header file.
+				if (txtFileLines.length == maxNumLines)
+					break;
+			}
+			hdrFile.close();
+		}
+	}
+	return txtFileLines;
+}
+
+// Returns the portion (if any) of a string after the period.
+//
+// Parameters:
+//  pStr: The string to extract from
+//
+// Return value: The portion of the string after the dot, if there is one.  If
+//               not, then an empty string will be returned.
+function getStrAfterPeriod(pStr)
+{
+	var strAfterPeriod = "";
+	var dotIdx = pStr.lastIndexOf(".");
+	if (dotIdx > -1)
+		strAfterPeriod = pStr.substr(dotIdx+1);
+	return strAfterPeriod;
 }
\ No newline at end of file
diff --git a/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg b/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg
index 7e5cd45fe0..1c510a59a5 100644
--- a/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg
+++ b/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg
@@ -4,6 +4,8 @@ useLightbarInterface=true
 ; will be the messaeg import date.  If false, the date will represent
 ; the timestamp in the message.
 showImportDates=true
+areaChooserHdrFilenameBase=msgAreaChgHeader
+areaChooserHdrMaxLines=5
 
 [COLORS]
 ; Area number
diff --git a/xtrn/DDAreaChoosers/DDMsgAreaChooser.js b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
index 0e194726dc..c40dd6a59a 100644
--- a/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
+++ b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
@@ -50,6 +50,8 @@
  *                                  line argument now specifies whether or not to
  *                                  allow choosing the message group, and it defaults
  *                                  to true.
+ * 2016-02-12 Eric Oulashin 1.10Beta Started working on adding the ability to display
+ *                                  a header ANSI/ASCII file above the list.
 */
 
 /* Command-line arguments:
@@ -78,8 +80,8 @@ if (system.version_num < 31400)
 }
 
 // Version & date variables
-var DD_MSG_AREA_CHOOSER_VERSION = "1.09";
-var DD_MSG_AREA_CHOOSER_VER_DATE = "2016-01-17";
+var DD_MSG_AREA_CHOOSER_VERSION = "1.10 Beta 2";
+var DD_MSG_AREA_CHOOSER_VER_DATE = "2016-02-14";
 
 // Keyboard input key codes
 var CTRL_M = "\x0d";
@@ -94,6 +96,14 @@ var KEY_PAGE_DOWN = "\1PgDn";
 var UP_ARROW = ascii(24);
 var DOWN_ARROW = ascii(25);
 
+// Determine the script's startup directory.
+// This code is a trick that was created by Deuce, suggested by Rob Swindell
+// as a way to detect which directory the script was executed in.  I've
+// shortened the code a little.
+var gStartupPath = '.';
+try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
+gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
+
 // 1st command-line argument: Whether or not to choose a message group first (if
 // false, then only choose a sub-board within the user's current group).  This
 // can be true or false.
@@ -175,6 +185,10 @@ function DDMsgAreaChooser()
 	this.msgGrpDescLen = console.screen_columns - this.areaNumLen -
 	this.numItemsLen - 5;
 
+	// Filename base of a header to display above the area list
+	this.areaChooserHdrFilenameBase = "msgAreaChgHeader";
+	this.areaChooserHdrMaxLines = 5;
+
 	// Set the function pointers for the object
 	this.ReadConfigFile = DDMsgAreaChooser_ReadConfigFile;
 	this.WriteKeyHelpLine = DDMsgAreaChooser_writeKeyHelpLine;
@@ -198,6 +212,7 @@ function DDMsgAreaChooser()
 	// Function to build the sub-board printf information for a message
 	// group
 	this.BuildSubBoardPrintfInfoForGrp = DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp;
+	this.DisplayAreaChgHdr = DDMsgAreaChooser_DisplayAreaChgHdr;
 
 	// Read the settings from the config file.
 	this.ReadConfigFile();
@@ -287,6 +302,10 @@ function DDMsgAreaChooser()
 	// on the fly the first time the user lists sub-boards for a message
 	// group.
 	this.subBoardListPrintfInfo = new Array();
+
+	// areaChangeHdrLines is an array of text lines to use as a header to display
+	// above the message area changer lists.
+	this.areaChangeHdrLines = loadTextFileIntoArray(this.areaChooserHdrFilenameBase, this.areaChooserHdrMaxLines);
 }
 
 // For the DDMsgAreaChooser class: Writes the line of key help at the bottom
@@ -399,7 +418,7 @@ function DDMsgAreaChooser_selectMsgArea_Lightbar(pChooseGroup)
 		if ((bbs.curgrp != null) && (typeof(bbs.curgrp) == "number"))
 			selectedGrpIndex = bbs.curgrp;
 
-		var listStartRow = 2;      // The row on the screen where the list will start
+		var listStartRow = 2 + this.areaChangeHdrLines.length; // The row on the screen where the list will start
 		var listEndRow = console.screen_rows - 1; // Row on screen where list will end
 		var topMsgGrpIndex = 0;    // The index of the message group at the top of the list
 
@@ -442,14 +461,15 @@ function DDMsgAreaChooser_selectMsgArea_Lightbar(pChooseGroup)
 			}
 		}
 
-		// Clear the screen, write the help line and group list header, and output
+		// Clear the screen, write the header, help line, and group list header, and output
 		// a screenful of message groups.
 		console.clear("\1n");
+		this.DisplayAreaChgHdr(1);
 		this.WriteKeyHelpLine();
 
 		var curpos = new Object();
 		curpos.x = 1;
-		curpos.y = 1;
+		curpos.y = 1 + this.areaChangeHdrLines.length;
 		console.gotoxy(curpos);
 		var pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
 		this.WriteGrpListHdrLine(numPages, pageNum);
@@ -557,7 +577,8 @@ function DDMsgAreaChooser_selectMsgArea_Lightbar(pChooseGroup)
 				{
 					// A sub-board was not chosen, so we'll have to re-draw
 					// the header and list of message groups.
-					console.gotoxy(1, 1);
+					this.DisplayAreaChgHdr(1);
+					console.gotoxy(1, 1+this.areaChangeHdrLines.length);
 					this.WriteGrpListHdrLine(numPages, pageNum);
 					this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, false, true);
 				}
@@ -620,7 +641,8 @@ function DDMsgAreaChooser_selectMsgArea_Lightbar(pChooseGroup)
 				console.pause();
 				// Refresh the screen
 				this.WriteKeyHelpLine();
-				console.gotoxy(1, 1);
+				this.DisplayAreaChgHdr(1);
+				console.gotoxy(1, 1+this.areaChangeHdrLines.length);
 				this.WriteGrpListHdrLine(numPages, pageNum);
 				this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, false, true);
 				break;
@@ -775,7 +797,7 @@ function DDMsgAreaChooser_selectSubBoard_Lightbar(pGrpIndex, pMarkIndex)
       }
    }
 
-   var listStartRow = 3;      // The row on the screen where the list will start
+   var listStartRow = 3+this.areaChangeHdrLines.length; // The row on the screen where the list will start
    var listEndRow = console.screen_rows - 1; // Row on screen where list will end
    var topSubIndex = 0;      // The index of the message group at the top of the list
    // Figure out the index of the last message group to appear on the screen.
@@ -818,16 +840,19 @@ function DDMsgAreaChooser_selectSubBoard_Lightbar(pGrpIndex, pMarkIndex)
       }
    }
 
-   // Clear the screen, write the help line and group list header, and output
+   // Clear the screen, write the header, help line, and group list header, and output
    // a screenful of message groups.
    console.clear("\1n");
+   this.DisplayAreaChgHdr(1); // Don't need to since it should already be drawn
+   if (this.areaChangeHdrLines.length > 0)
+	   console.crlf();
    var pageNum = calcPageNum(topSubIndex, numItemsPerPage);
    this.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum);
    this.WriteKeyHelpLine();
 
    var curpos = new Object();
    curpos.x = 1;
-   curpos.y = 2;
+   curpos.y = 2+this.areaChangeHdrLines.length;
    console.gotoxy(curpos);
    printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
    this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow,
@@ -991,11 +1016,12 @@ function DDMsgAreaChooser_selectSubBoard_Lightbar(pGrpIndex, pMarkIndex)
             this.ShowHelpScreen(true, true);
             console.pause();
             // Refresh the screen
-            console.gotoxy(1, 1);
+			this.DisplayAreaChgHdr(1);
+            console.gotoxy(1, 1+this.areaChangeHdrLines.length);
             this.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum);
             console.cleartoeol("\1n");
             this.WriteKeyHelpLine();
-            console.gotoxy(1, 2);
+            console.gotoxy(1, 2+this.areaChangeHdrLines.length);
             printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts",
                    "Latest date & time");
             this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow,
@@ -1080,6 +1106,9 @@ function DDMsgAreaChooser_selectMsgArea_Traditional(pChooseGroup)
 			bbs.command_str = "";
 
 			console.clear("\1n");
+			this.DisplayAreaChgHdr(1);
+			if (this.areaChangeHdrLines.length > 0)
+				console.crlf();
 			this.ListMsgGrps();
 			console.crlf();
 			console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + +(bbs.curgrp+1) + "\1n\1c]: \1h");
@@ -1149,6 +1178,9 @@ function DDMsgAreaChooser_selectSubBoard_Traditional(pGrpIdx, pDefaultSubBoardId
 	retObj.subBoardChosen = false;
 	retObj.subBoardIndex = -1;
 
+	this.DisplayAreaChgHdr(1);
+	if (this.areaChangeHdrLines.length > 0)
+		console.crlf();
 	this.ListSubBoardsInMsgGroup(pGrpIdx, pDefaultSubBoardIdx);
 	console.crlf();
 	console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + +(pDefaultSubBoardIdx+1) + "\1n\1c]: \1h");
@@ -1491,12 +1523,12 @@ function DDMsgAreaChooser_updatePageNumInHeader(pPageNum, pNumPages, pGroup, pRe
 
   if (pGroup)
   {
-    console.gotoxy(29, 1);
+    console.gotoxy(29, 1+this.areaChangeHdrLines.length);
     console.print("\1n" + this.colors.header + pPageNum + " of " + pNumPages + ")   ");
   }
   else
   {
-    console.gotoxy(51, 1);
+    console.gotoxy(51, 1+this.areaChangeHdrLines.length);
     console.print("\1n" + this.colors.subBoardHeader + pPageNum + " of " + pNumPages + ")   ");
   }
 
@@ -1647,84 +1679,92 @@ function DDMsgAreaChooser_writeMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
 // For the DDMsgAreaChooser class: Reads the configuration file.
 function DDMsgAreaChooser_ReadConfigFile()
 {
-   // Determine the script's startup directory.
-   // This code is a trick that was created by Deuce, suggested by Rob Swindell
-   // as a way to detect which directory the script was executed in.  I've
-   // shortened the code a little.
-   var startup_path = '.';
-   try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
-   startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''));
-
-   // Open the configuration file
-   var cfgFile = new File(startup_path + "DDMsgAreaChooser.cfg");
-   if (cfgFile.open("r"))
-   {
-      var settingsMode = "behavior";
-      var fileLine = null;     // A line read from the file
-      var equalsPos = 0;       // Position of a = in the line
-      var commentPos = 0;      // Position of the start of a comment
-      var setting = null;      // A setting name (string)
-      var settingUpper = null; // Upper-case setting name
-      var value = null;        // A value for a setting (string)
-      while (!cfgFile.eof)
-      {
-         // Read the next line from the config file.
-         fileLine = cfgFile.readln(2048);
+	// Determine the script's startup directory.
+	// This code is a trick that was created by Deuce, suggested by Rob Swindell
+	// as a way to detect which directory the script was executed in.  I've
+	// shortened the code a little.
+	var startup_path = '.';
+	try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
+	startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''));
+
+	// Open the configuration file
+	var cfgFile = new File(startup_path + "DDMsgAreaChooser.cfg");
+	if (cfgFile.open("r"))
+	{
+		var settingsMode = "behavior";
+		var fileLine = null;     // A line read from the file
+		var equalsPos = 0;       // Position of a = in the line
+		var commentPos = 0;      // Position of the start of a comment
+		var setting = null;      // A setting name (string)
+		var settingUpper = null; // Upper-case setting name
+		var value = null;        // A value for a setting (string)
+		while (!cfgFile.eof)
+		{
+			// Read the next line from the config file.
+			fileLine = cfgFile.readln(2048);
 
-         // fileLine should be a string, but I've seen some cases
-         // where it isn't, so check its type.
-         if (typeof(fileLine) != "string")
-            continue;
+			// fileLine should be a string, but I've seen some cases
+			// where it isn't, so check its type.
+			if (typeof(fileLine) != "string")
+				continue;
 
-         // If the line starts with with a semicolon (the comment
-         // character) or is blank, then skip it.
-         if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-            continue;
+			// If the line starts with with a semicolon (the comment
+			// character) or is blank, then skip it.
+			if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
+				continue;
 
-         // If in the "behavior" section, then set the behavior-related variables.
-         if (fileLine.toUpperCase() == "[BEHAVIOR]")
-         {
-            settingsMode = "behavior";
-            continue;
-         }
-         else if (fileLine.toUpperCase() == "[COLORS]")
-         {
-            settingsMode = "colors";
-            continue;
-         }
+			// If in the "behavior" section, then set the behavior-related variables.
+			if (fileLine.toUpperCase() == "[BEHAVIOR]")
+			{
+				settingsMode = "behavior";
+				continue;
+			}
+			else if (fileLine.toUpperCase() == "[COLORS]")
+			{
+				settingsMode = "colors";
+				continue;
+			}
 
-         // If the line has a semicolon anywhere in it, then remove
-         // everything from the semicolon onward.
-         commentPos = fileLine.indexOf(";");
-         if (commentPos > -1)
-            fileLine = fileLine.substr(0, commentPos);
-
-         // Look for an equals sign, and if found, separate the line
-         // into the setting name (before the =) and the value (after the
-         // equals sign).
-         equalsPos = fileLine.indexOf("=");
-         if (equalsPos > 0)
-         {
-            // Read the setting & value, and trim leading & trailing spaces.
-            setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-            settingUpper = setting.toUpperCase();
-            value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
+			// If the line has a semicolon anywhere in it, then remove
+			// everything from the semicolon onward.
+			commentPos = fileLine.indexOf(";");
+			if (commentPos > -1)
+				fileLine = fileLine.substr(0, commentPos);
+
+			// Look for an equals sign, and if found, separate the line
+			// into the setting name (before the =) and the value (after the
+			// equals sign).
+			equalsPos = fileLine.indexOf("=");
+			if (equalsPos > 0)
+			{
+				// Read the setting & value, and trim leading & trailing spaces.
+				setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
+				settingUpper = setting.toUpperCase();
+				value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
 
-            if (settingsMode == "behavior")
-            {
-               // Set the appropriate value in the settings object.
-               if (settingUpper == "USELIGHTBARINTERFACE")
-                  this.useLightbarInterface = (value.toUpperCase() == "TRUE");
-               else if (settingUpper == "SHOWIMPORTDATES")
-                  this.showImportDates = (value.toUpperCase() == "TRUE");
-            }
-            else if (settingsMode == "colors")
-               this.colors[setting] = value;
-         }
-      }
-   
-      cfgFile.close();
-   }
+				if (settingsMode == "behavior")
+				{
+					// Set the appropriate value in the settings object.
+					if (settingUpper == "USELIGHTBARINTERFACE")
+						this.useLightbarInterface = (value.toUpperCase() == "TRUE");
+					else if (settingUpper == "SHOWIMPORTDATES")
+						this.showImportDates = (value.toUpperCase() == "TRUE");
+					else if (settingUpper == "AREACHOOSERHDRFILENAMEBASE")
+						this.areaChooserHdrFilenameBase = value;
+					else if (settingUpper == "AREACHOOSERHDRMAXLINES")
+					{
+						var maxNumLines = +value;
+						if (maxNumLines > 0)
+							this.areaChooserHdrMaxLines = maxNumLines;
+					}
+				}
+				else if (settingsMode == "colors")
+					this.colors[setting] = value;
+			}
+		}
+
+		cfgFile.close();
+	}
 }
 
 // For the DDMsgAreaChooser class: Shows the help screen
@@ -1785,10 +1825,10 @@ function DDMsgAreaChooser_showHelpScreen(pLightbar, pClearScreen)
 	console.crlf();
 }
 
-// Builds sub-board printf format information for a message group.
-// The widths of the description & # messages columns are calculated
-// based on the greatest number of messages in a sub-board for the
-// message group.
+// For the DDMsgAreaChooser class: Builds sub-board printf format information
+// for a message group.  The widths of the description & # messages columns
+// are calculated based on the greatest number of messages in a sub-board for
+// the message group.
 //
 // Parameters:
 //  pGrpIndex: The index of the message group
@@ -1837,6 +1877,64 @@ function DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp(pGrpIndex)
    }
 }
 
+// For the DDMsgAreaChooser class: Displays the area chooser header
+//
+// Parameters:
+//  pStartScreenRow: The row on the screen at which to start displaying the
+//                   header information.  Will be used if the user's terminal
+//                   supports ANSI.
+//  pClearRowsFirst: Optional boolean - Whether or not to clear the rows first.
+//                   Defaults to true.  Only valid if the user's terminal supports
+//                   ANSI.
+function DDMsgAreaChooser_DisplayAreaChgHdr(pStartScreenRow, pClearRowsFirst)
+{
+	if (this.areaChangeHdrLines == null)
+		return;
+	if (this.areaChangeHdrLines.length == 0)
+		return;
+
+	// If the user's terminal supports ANSI and pStartScreenRow is a number, then
+	// we can move the cursor and display the header where specified.
+	if (console.term_supports(USER_ANSI) && (typeof(pStartScreenRow) == "number"))
+	{
+		// If specified to clear the rows first, then do so.
+		var screenX = 1;
+		var screenY = pStartScreenRow;
+		var clearRowsFirst = (typeof(pClearRowsFirst) == "boolean" ? pClearRowsFirst : true);
+		if (clearRowsFirst)
+		{
+			console.print("\1n");
+			for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+			{
+				console.gotoxy(screenX, screenY++);
+				console.cleartoeol();
+			}
+		}
+		// Display the header starting on the first column and the given screen row.
+		screenX = 1;
+		screenY = pStartScreenRow;
+		for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+		{
+			console.gotoxy(screenX, screenY++);
+			console.print(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
+		}
+	}
+	else
+	{
+		// The user's terminal doesn't support ANSI or pStartScreenRow is not a
+		// number - So just output the header lines.
+		for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
+		{
+			console.print(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
+			//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
+			console.crlf();
+		}
+	}
+}
+
 // Removes multiple, leading, and/or trailing spaces.
 // The search & replace regular expressions used in this
 // function came from the following URL:
@@ -1932,45 +2030,173 @@ function getGreatestNumMsgs(pGrpIndex)
 // Return value: The user's keypress
 function getKeyWithESCChars(pGetKeyMode)
 {
-   var getKeyMode = K_NONE;
-   if (typeof(pGetKeyMode) == "number")
-      getKeyMode = pGetKeyMode;
-
-   var userInput = console.getkey(getKeyMode);
-   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 = "\1F1";
-                 break;
-              case 'Q':
-                 userInput = "\1F2";
-                 break;
-              case 'R':
-                 userInput = "\1F3";
-                 break;
-              case 'S':
-                 userInput = "\1F4";
-                 break;
-              case 't':
-                 userInput = "\1F5";
-                 break;
-           }
-         default:
-           break;
-      }
-   }
+	var getKeyMode = K_NONE;
+	if (typeof(pGetKeyMode) == "number")
+		getKeyMode = pGetKeyMode;
+
+	var userInput = console.getkey(getKeyMode);
+	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 = "\1F1";
+						break;
+					case 'Q':
+						userInput = "\1F2";
+						break;
+					case 'R':
+						userInput = "\1F3";
+						break;
+					case 'S':
+						userInput = "\1F4";
+						break;
+					case 't':
+						userInput = "\1F5";
+						break;
+				}
+			default:
+				break;
+		}
+	}
+
+	return userInput;
+}
+
+// Loads a text file (an .ans or .asc) into an array.  This will first look for
+// an .ans version, and if exists, convert to Synchronet colors before loading
+// it.  If an .ans doesn't exist, this will look for an .asc version.
+//
+// Parameters:
+//  pFilenameBase: The filename without the extension
+//  pMaxNumLines: Optional - The maximum number of lines to load from the text file
+//
+// Return value: An array containing the lines from the text file
+function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
+{
+	if (typeof(pFilenameBase) != "string")
+		return new Array();
+
+	var maxNumLines = (typeof(pMaxNumLines) == "number" ? pMaxNumLines : -1);
+
+	var txtFileLines = new Array();
+	// See if there is a header file that is made for the user's terminal
+	// width (areaChgHeader-<width>.ans/asc).  If not, then just go with
+	// msgHeader.ans/asc.
+	var txtFileExists = true;
+	var txtFilenameFullPath = gStartupPath + pFilenameBase;
+	var txtFileFilename = "";
+	if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
+		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
+	else if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".asc"))
+		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".asc";
+	else if (file_exists(txtFilenameFullPath + ".ans"))
+		txtFileFilename = txtFilenameFullPath + ".ans";
+	else if (file_exists(txtFilenameFullPath + ".asc"))
+		txtFileFilename = txtFilenameFullPath + ".asc";
+	else
+		txtFileExists = false;
+	if (txtFileExists)
+	{
+		var syncConvertedHdrFilename = txtFileFilename;
+		// If the user's console doesn't support ANSI and the header file is ANSI,
+		// then convert it to Synchronet attribute codes and read that file instead.
+		if (!console.term_supports(USER_ANSI) && (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS"))
+		{
+			syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
+			if (!file_exists(syncConvertedHdrFilename))
+			{
+				if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
+				{
+					var filenameBase = txtFileFilename.substr(0, dotIdx);
+					var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+								+ syncConvertedHdrFilename + "\"";
+					// Note: Both system.exec(cmdLine) and
+					// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+					// execute the command, but system.exec() seems noticeably faster.
+					system.exec(cmdLine);
+				}
+				else
+					syncConvertedHdrFilename = txtFileFilename;
+			}
+		}
+		/*
+		// If the header file is ANSI, then convert it to Synchronet attribute
+		// codes and read that file instead.  This is done so that this script can
+		// accurately get the file line lengths using console.strlen().
+		var syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
+		if (!file_exists(syncConvertedHdrFilename))
+		{
+			if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
+			{
+				var filenameBase = txtFileFilename.substr(0, dotIdx);
+				var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+				            + syncConvertedHdrFilename + "\"";
+				// Note: Both system.exec(cmdLine) and
+				// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+				// execute the command, but system.exec() seems noticeably faster.
+				system.exec(cmdLine);
+			}
+			else
+				syncConvertedHdrFilename = txtFileFilename;
+		}
+		*/
+		// Read the header file into txtFileLines
+		var hdrFile = new File(syncConvertedHdrFilename);
+		if (hdrFile.open("r"))
+		{
+			var fileLine = null;
+			while (!hdrFile.eof)
+			{
+				// Read the next line from the header file.
+				fileLine = hdrFile.readln(2048);
+				// fileLine should be a string, but I've seen some cases
+				// where it isn't, so check its type.
+				if (typeof(fileLine) != "string")
+					continue;
+
+				// Make sure the line isn't longer than the user's terminal
+				//if (fileLine.length > console.screen_columns)
+				//   fileLine = fileLine.substr(0, console.screen_columns);
+				txtFileLines.push(fileLine);
+
+				// If the header array now has the maximum number of lines, then
+				// stop reading the header file.
+				if (txtFileLines.length == maxNumLines)
+					break;
+			}
+			hdrFile.close();
+		}
+	}
+	return txtFileLines;
+}
 
-   return userInput;
+// Returns the portion (if any) of a string after the period.
+//
+// Parameters:
+//  pStr: The string to extract from
+//
+// Return value: The portion of the string after the dot, if there is one.  If
+//               not, then an empty string will be returned.
+function getStrAfterPeriod(pStr)
+{
+	var strAfterPeriod = "";
+	var dotIdx = pStr.lastIndexOf(".");
+	if (dotIdx > -1)
+		strAfterPeriod = pStr.substr(dotIdx+1);
+	return strAfterPeriod;
 }
\ No newline at end of file
diff --git a/xtrn/DDAreaChoosers/Read Me.txt b/xtrn/DDAreaChoosers/Read Me.txt
index 74c647e9fe..42de386d79 100644
--- a/xtrn/DDAreaChoosers/Read Me.txt	
+++ b/xtrn/DDAreaChoosers/Read Me.txt	
@@ -1,6 +1,6 @@
                      Digital Distortion Area Choosers
-                              Version 1.09
-                        Release date: 2016-01-17
+                              Version 1.10
+                        Release date: 2016-??-??
 
                                   by
 
@@ -21,7 +21,6 @@ Contents
 4. Configuration file
 5. DDMsgAreaChooser class: Properties & methods
 6. DDFileAreaChooser class: Properties & methods
-7. Revision History
 
 
 1. Disclaimer
@@ -214,6 +213,23 @@ showImportDates                       true/false: Whether or not to show the
                                       in the latest date & time column in the
                                       sub-board lists.
 
+areaChooserHdrFilenameBase            The filename to use (without the
+                                      extension) for a header to display above
+                                      the message area chooser list.  For
+                                      example, if areaChgHeader is specified,
+                                      then the chooser will look for
+                                      areaChgHeader.ans if it exists, and if
+                                      not, the chooser will look for
+                                      areaChgHeader.asc.  Additionally, you
+                                      can have multiple header files for
+                                      different terminal widths; fpr example,
+                                      areaChgHeader-80.ans for an 80-column
+                                      terminal, areaChgHeader-140.ans for a
+                                      140-column terminal, etc.
+
+areaChooserHdrMaxLines                The maximum number of lines to use from
+                                      the message area chooser header file
+
 Colors section: Message area chooser
 ------------------------------------
 Color setting                        Description
@@ -281,6 +297,23 @@ Setting                               Description
 useLightbarInterface                  true/false: Whether or not to use a
                                       lightbar user interface.
 
+areaChooserHdrFilenameBase            The filename to use (without the
+                                      extension) for a header to display above
+                                      the file area chooser list.  For example,
+                                      if areaChgHeader is specified, then the
+                                      chooser will look for areaChgHeader.ans
+                                      if it exists, and if not, the chooser
+                                      will look for areaChgHeader.asc.
+                                      Additionally, you can have multiple
+                                      header files for different terminal
+                                      widths; fpr example, areaChgHeader-80.ans
+                                      for an 80-column terminal,
+                                      areaChgHeader-140.ans for a 140-column
+                                      terminal, etc.
+
+areaChooserHdrMaxLines                The maximum number of lines to use from
+                                      the message area chooser header file
+
 Colors section: File area chooser
 ------------------------------------
 Color setting                        Description
@@ -414,53 +447,3 @@ ListDirsInFileLib(pLibIndex,          Lists the directories in the user's
                                       specify the index of the file library
                                       and the index of the directory to mark
                                       with the "chosen" character.
-7. Revision History
-===================
-Version  Date         Description
--------  ----         -----------
-1.09     2016-01-17   Added a command-line parameter to let the user choose a
-                      message sub-board only within their current message
-                      group, or file directory only within their current file
-                      library.
-1.08     2015-04-19   Added customizable color settings for the key help text
-                      line displayed at the bottom of the screen in lightbar
-                      mode.  Also, updated to allow the PageUp and PageDown
-                      keys to be used instead of the P and N keys to go to the
-                      previous & next pages in lightbar mode.
-1.07     2014-12-22   Message area chooser:
-                      Bug fix: Made this.colors.subBoardHeader apply to the
-                      whole line rather than just the page number.
-                      Bug fix: The initial display of the page number is now
-                      correct (previously, it would start out saying page 1,
-                      even if on another page).
-                      Documentation & example configuration files:
-                      Added the color options subBoardHeader (for the message
-                      area chooser) and fileAreaHdr (for the file area chooser)
-                      to the documentation and example configuration files.
-1.06     2014-09-14   Bug fix: Updated the lightbar highlight format string to
-                      include a normal attribute at the end to avoid the
-                      highlight color to be used when clearing the screen,
-                      etc.  Bug reported by Psi-Jack.
-1.05     2013-05-10   Bug fix in the file area chooser: When listing
-                      directories in a file group, it would sometimes
-                      crash due to an incorrect array index used, and
-                      the array was not set up.  Those have been fixed.
-1.04     2013-05-04   Updated to properly format message sub-boards and
-                      file directories with more than 9999 entries.  The
-                      formatting is now dynamically adjusted depending
-                      on the greatest number of entries in a sub-board
-                      for a message group or file directory in a file
-                      library (the descriptions will shrink as the
-                      text length of the greatest number of entries
-                      increases).
-1.03     2012-11-30   Bug fix: After leaving the help screen from the
-                      sub-board/directory list, the top line is now
-                      correctly written with the page information as "Page
-                      # of #".
-1.02     2012-10-06   For the lightbar interface, the current page number is
-                      now displayed at the top of the screen (along with the
-                      total number of pages) and is updated when going to a
-                      new page.
-1.01     2011-04-22   Fixed the wording when choosing a message sub-board and
-                      file library.
-1.00     2010-03-13   First public release
\ No newline at end of file
diff --git a/xtrn/DDAreaChoosers/Revision history.txt b/xtrn/DDAreaChoosers/Revision history.txt
new file mode 100644
index 0000000000..0b78364a29
--- /dev/null
+++ b/xtrn/DDAreaChoosers/Revision history.txt	
@@ -0,0 +1,59 @@
+This file lists all of the changes made for each release of the Digital
+Distortion Area Choosers.
+
+Revision History (change log)
+=============================
+Version  Date         Description
+-------  ----         -----------
+1.10     2016-??-??   Added the ability to display a custom header file above
+                      the area lists in the area choosers.  Added the
+                      configuration options areaChooserHdrFilenameBase and
+                      areaChooserHdrMaxLines to specify the filename (without
+                      the extension) and maximum number of lines from the header
+                      file to use.
+1.09     2016-01-17   Added a command-line parameter to let the user choose a
+                      message sub-board only within their current message
+                      group, or file directory only within their current file
+                      library.
+1.08     2015-04-19   Added customizable color settings for the key help text
+                      line displayed at the bottom of the screen in lightbar
+                      mode.  Also, updated to allow the PageUp and PageDown
+                      keys to be used instead of the P and N keys to go to the
+                      previous & next pages in lightbar mode.
+1.07     2014-12-22   Message area chooser:
+                      Bug fix: Made this.colors.subBoardHeader apply to the
+                      whole line rather than just the page number.
+                      Bug fix: The initial display of the page number is now
+                      correct (previously, it would start out saying page 1,
+                      even if on another page).
+                      Documentation & example configuration files:
+                      Added the color options subBoardHeader (for the message
+                      area chooser) and fileAreaHdr (for the file area chooser)
+                      to the documentation and example configuration files.
+1.06     2014-09-14   Bug fix: Updated the lightbar highlight format string to
+                      include a normal attribute at the end to avoid the
+                      highlight color to be used when clearing the screen,
+                      etc.  Bug reported by Psi-Jack.
+1.05     2013-05-10   Bug fix in the file area chooser: When listing
+                      directories in a file group, it would sometimes
+                      crash due to an incorrect array index used, and
+                      the array was not set up.  Those have been fixed.
+1.04     2013-05-04   Updated to properly format message sub-boards and
+                      file directories with more than 9999 entries.  The
+                      formatting is now dynamically adjusted depending
+                      on the greatest number of entries in a sub-board
+                      for a message group or file directory in a file
+                      library (the descriptions will shrink as the
+                      text length of the greatest number of entries
+                      increases).
+1.03     2012-11-30   Bug fix: After leaving the help screen from the
+                      sub-board/directory list, the top line is now
+                      correctly written with the page information as "Page
+                      # of #".
+1.02     2012-10-06   For the lightbar interface, the current page number is
+                      now displayed at the top of the screen (along with the
+                      total number of pages) and is updated when going to a
+                      new page.
+1.01     2011-04-22   Fixed the wording when choosing a message sub-board and
+                      file library.
+1.00     2010-03-13   First public release
\ No newline at end of file
-- 
GitLab