Skip to content
Snippets Groups Projects
DDMsgAreaChooser.js 88 KiB
Newer Older
            if (topSubIndex < topIndexForLastPage)
            {
               topSubIndex = topIndexForLastPage;
               pageNum = calcPageNum(topSubIndex, numItemsPerPage);
               bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
               this.updatePageNumInHeader(pageNum, numPages, false, false);
               this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow,
                                            listEndRow, false, true);
               selectedSubIndex = topIndexForLastPage;
            }
            break;
         case 'Q': // Quit
            continueChoosingSubBrd = 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.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum);
            console.cleartoeol("\1n");
            this.WriteKeyHelpLine();
            console.gotoxy(1, 2+this.areaChangeHdrLines.length);
            printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts",
                   "Latest date & time");
            this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, 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("\1cSub-board #: \1h");
               userInput = console.getnum(msg_area.grp_list[grpIndex].sub_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)
               {
                  continueChoosingSubBrd = false;
                  retObj.subBoardChosen = true;
                  retObj.subBoardIndex = 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.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum);
                  console.cleartoeol("\1n");
                  this.WriteKeyHelpLine();
                  console.gotoxy(1, 2);
                  printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts",
                         "Latest date & time");
                  this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow,
                                              listEndRow, false, true);
               }
            }
            break;
      }
   }

   return retObj;
}

// For the DDMsgAreaChooser class: Lets the user choose a message group and
// sub-board via numeric input, using a traditional user interface.
//
// Parameters:
//  pChooseGroup: Boolean - Whether or not to choose the message group.  If false,
//                then this will allow choosing a sub-board within the user's
//                current message group.  This is optional; defaults to true.
function DDMsgAreaChooser_selectMsgArea_Traditional(pChooseGroup)
	// If there are no message groups, then don't let the user
	// choose one.
	if (msg_area.grp_list.length == 0)
	{
		console.clear("\1n");
		console.print("\1y\1hThere are no message groups.\r\n\1p");
		return;
	}
	var chooseGroup = (typeof(pChooseGroup) == "boolean" ? pChooseGroup : true);
	if (chooseGroup)
	{
		// Show the message groups & sub-boards and let the user choose one.
		var selectedGrp = 0;      // The user's selected message group
		var selectedSubBoard = 0; // The user's selected sub-board
		var continueChoosingMsgArea = true;
		while (continueChoosingMsgArea)
		{
			// Clear the BBS command string to make sure there are no extra
			// commands in there that could cause weird things to happen.
			bbs.command_str = "";
			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");
			// Accept Q (quit) or a file library number
			selectedGrp = console.getkeys("Q", msg_area.grp_list.length);

			// If the user just pressed enter (selectedGrp would be blank),
			// default to the current group.
			if (selectedGrp.toString() == "")
				selectedGrp = bbs.curgrp + 1;

			if (selectedGrp.toString() == "Q")
				continueChoosingMsgArea = false;
			else
			{
				// If the user specified a message group number, then
				// set it and let the user choose a sub-board within
				// the group.
				if (selectedGrp > 0)
				{
					// Set the default sub-board #: The current sub-board, or if the
					// user chose a different group, then this should be set
					// to the first sub-board.
					var defaultSubBoard = bbs.cursub + 1;
					if (selectedGrp-1 != bbs.curgrp)
						defaultSubBoard = 1;

					console.clear("\1n");
					var selectSubRetVal = this.SelectSubBoard_Traditional(selectedGrp-1, defaultSubBoard-1);
					// If the user chose a directory, then set bbs.curlib &
					// bbs.curdir and quit the file library loop.
					if ((selectedGrp.toString() != "Q") && (selectSubRetVal.subBoardIndex > -1))
					{
						bbs.curgrp = selectedGrp - 1;
						bbs.cursub = selectSubRetVal.subBoardIndex;
						continueChoosingMsgArea = false;
					}
				}
			}
		}
	}
	else
	{
		// Don't choose a group, just a sub-board within the user's current group.
		var selectSubRetVal = this.SelectSubBoard_Traditional(bbs.curgrp, bbs.cursub);
		// If the user chose a directory, then set bbs.curlib &
		// bbs.curdir and quit the file library loop.
		if (selectSubRetVal.subBoardIndex > -1)
			bbs.cursub = selectSubRetVal.subBoardIndex;
	}
}

// For the DDMsgAreaChooser class: Allows the user to select a sub-board with the
// traditional user interface.
//
// Parameters:
//  pGrpIdx: The index of the message group to choose a sub-board for
//  pDefaultSubBoardIdx: The index of the default sub-board
//
// Return value: An object containing the following values:
//               subBoardChosen: Boolean - Whether or not a sub-board was chosen.
//               subBoardIndex: Numeric - The sub-board that was chosen (if any).
//                              Will be -1 if none chosen.
function DDMsgAreaChooser_selectSubBoard_Traditional(pGrpIdx, pDefaultSubBoardIdx)
{
	var retObj = new Object();
	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");
	// Accept Q (quit) or a sub-board number
	var selectedSubBoard = console.getkeys("Q", msg_area.grp_list[pGrpIdx].sub_list.length);

	// If the user just pressed enter (selectedSubBoard would be blank),
	// default the selected directory.
	var selectedSubBoardStr = selectedSubBoard.toString();
	if (selectedSubBoardStr == "")
		selectedSubBoard = pDefaultSubBoardIdx + 1; // Make this 1-based

	if (selectedSubBoard > 0)
	{
		retObj.subBoardChosen = true;
		retObj.subBoardIndex = selectedSubBoard - 1;
	}
	return retObj;
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
}

// For the DDMsgAreaChooser class: Lists all message groups (for the traditional
// user interface).
function DDMsgAreaChooser_listMsgGrps_Traditional()
{
   // Print the header
   this.WriteGrpListHdrLine();
   console.print("\1n");
   // List the message groups
   for (var i = 0; i < msg_area.grp_list.length; ++i)
   {
      console.crlf();
      this.WriteMsgGroupLine(i, false);
   }
}

// For the DDMsgAreaChooser class: Lists the sub-boards in a message group,
// for the traditional user interface.
//
// Parameters:
//  pGrpIndex: The index of the message group (0-based)
//  pMarkIndex: An index of a message group to highlight.  This
//                   is optional; if left off, this will default to
//                   the current sub-board.
//  pSortType: Optional - A string describing how to sort the list (if desired):
//             "none": Default behavior - Sort by sub-board #
//             "dateAsc": Sort by date, ascending
//             "dateDesc": Sort by date, descending
//             "description": Sort by description
function DDMsgAreaChooser_listSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIndex, pSortType)
{
   // Default to the current message group & sub-board if pGrpIndex
   // and pMarkIndex aren't specified.
   var grpIndex = bbs.curgrp;
   if ((pGrpIndex != null) && (typeof(pGrpIndex) == "number"))
      grpIndex = pGrpIndex;
   var highlightIndex = bbs.cursub;
   if ((pMarkIndex != null) && (typeof(pMarkIndex) == "number"))
      highlightIndex = pMarkIndex;

   // Make sure grpIndex and highlightIndex are valid (they might not be for
   // brand-new users).
   if ((grpIndex == null) || (typeof(grpIndex) == "undefined"))
      grpIndex = 0;
   if ((highlightIndex == null) || (typeof(highlightIndex) == "undefined"))
      highlightIndex = 0;

   // Ensure that the sub-board printf information is created for
   // this message group.
   this.BuildSubBoardPrintfInfoForGrp(grpIndex);

   // Print the headers
   this.WriteSubBrdListHdr1Line(grpIndex);
   console.crlf();
   printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
   console.print("\1n");

   // List each sub-board in the message group.
   var subBoardArray = null;       // For sorting, if desired
   var newestDate = new Object(); // For storing the date of the newest post in a sub-board
   var msgBase = null;    // For opening the sub-boards with a MsgBase object
   var msgHeader = null;  // For getting the date & time of the newest post in a sub-board
   var subBoardNum = 0;   // 0-based sub-board number (because the array index is the number as a str)
   // If a sort type is specified, then add the sub-board information to
   // subBoardArray so that it can be sorted.
   if ((typeof(pSortType) == "string") && (pSortType != "") && (pSortType != "none"))
   {
      subBoardArray = new Array();
      var subBoardInfo = null;
      for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list)
      {
         // Open the current sub-board with the msgBase object.
         msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
         if (msgBase.open())
         {
            subBoardInfo = new MsgSubBoardInfo();
            subBoardInfo.subBoardNum = +(arrSubBoardNum);
            subBoardInfo.description = msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description;
            subBoardInfo.numPosts = msgBase.total_msgs;

            // Get the date & time when the last message was imported.
            if (msgBase.total_msgs > 0)
            {
               msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true);
               if (this.showImportDates)
                  subBoardInfo.newestPostDate = msgHeader.when_imported_time
               else
                  subBoardInfo.newestPostDate = msgHeader.when_written_time;
            }
         }
         msgBase.close();
         subBoardArray.push(subBoardInfo);
      }
      // Free some memory?
      delete msgBase;

      // Sort sub-board list.
      if (pSortType == "dateAsc")
      {
         subBoardArray.sort(function(pA, pB)
         {
            // Return -1, 0, or 1, depending on whether pA's date comes
            // before, is equal to, or comes after pB's date.
            var returnValue = 0;
            if (pA.newestPostDate < pB.newestPostDate)
               returnValue = -1;
            else if (pA.newestPostDate > pB.newestPostDate)
               returnValue = 1;
            return returnValue;
         });
      }
      else if (pSortType == "dateDesc")
      {
         subBoardArray.sort(function(pA, pB)
         {
            // Return -1, 0, or 1, depending on whether pA's date comes
            // after, is equal to, or comes before pB's date.
            var returnValue = 0;
            if (pA.newestPostDate > pB.newestPostDate)
               returnValue = -1;
            else if (pA.newestPostDate < pB.newestPostDate)
               returnValue = 1;
            return returnValue;
         });
      }
      else if (pSortType == "description")
      {
         // Binary safe string comparison  
         // 
         // version: 909.322
         // discuss at: http://phpjs.org/functions/strcmp    // +   original by: Waldo Malqui Silva
         // +      input by: Steve Hilder
         // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
         // +    revised by: gorthaur
         // *     example 1: strcmp( 'waldo', 'owald' );    // *     returns 1: 1
         // *     example 2: strcmp( 'owald', 'waldo' );
         // *     returns 2: -1
         subBoardArray.sort(function(pA, pB)
         {
            return ((pA.description == pB.description) ? 0 : ((pA.description > pB.description) ? 1 : -1));
         });
      }

      // Display the sub-board list.
      for (var i = 0; i < subBoardArray.length; ++i)
      {
         console.crlf();
         console.print((subBoardArray[i].subBoardNum == highlightIndex) ? "\1n" + this.colors.areaMark + "*" : " ");
         printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardArray[i].subBoardNum+1),
                subBoardArray[i].description.substr(0, this.subBoardNameLen),
                subBoardArray[i].numPosts, strftime("%Y-%m-%d", subBoardArray[i].newestPostDate),
                strftime("%H:%M:%S", subBoardArray[i].newestPostDate));
      }
   }
   // If no sort type is specified, then output the sub-board information in
   // order of sub-board number.
   else
   {
      for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list)
      {
         // Open the current sub-board with the msgBase object.
         msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
         if (msgBase.open())
         {
            // Get the date & time when the last message was imported.
            if (msgBase.total_msgs > 0)
            {
               msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true);
               // Construct the date & time strings of the latest post
               if (this.showImportDates)
               {
                  newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time);
                  newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time);
               }
               else
               {
                  newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time);
                  newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time);
               }
            }
            else
               newestDate.date = newestDate.time = "";

            // Print the sub-board information
            subBoardNum = +(arrSubBoardNum);
            console.crlf();
            console.print((subBoardNum == highlightIndex) ? "\1n" + this.colors.areaMark + "*" : " ");
            printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardNum+1),
                   msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen),
                   msgBase.total_msgs, newestDate.date, newestDate.time);
   
            msgBase.close();
         }
   
         // Free some memory?
         delete msgBase;
      }
   }
}

//////////////////////////////////////////////
// Message group list stuff (lightbar mode) //
//////////////////////////////////////////////

// Displays a screenful of message groups, for the lightbar interface.
//
// Parameters:
//  pStartIndex: The message group index to start at (0-based)
//  pStartScreenRow: The row on the screen to start at (1-based)
//  pEndScreenRow: The row on the screen to end at (1-based)
//  pClearScreenFirst: Boolean - Whether or not to clear the screen first
//  pBlankToEndRow: Boolean - Whether or not to write blank lines to the end
//                  screen row if there aren't enough message groups to fill
//                  the screen.
function DDMsgAreaChooser_listScreenfulOfMsgGrps(pStartIndex, pStartScreenRow,
                                                  pEndScreenRow, pClearScreenFirst,
                                                  pBlankToEndRow)
{
   // Check the parameters; If they're bad, then just return.
   if ((typeof(pStartIndex) != "number") ||
       (typeof(pStartScreenRow) != "number") ||
       (typeof(pEndScreenRow) != "number"))
   {
      return;
   }
   if ((pStartIndex < 0) || (pStartIndex >= msg_area.grp_list.length))
      return;
   if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows))
      return;
   if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows))
      return;

   // If pStartScreenRow is greather than pEndScreenRow, then swap them.
   if (pStartScreenRow > pEndScreenRow)
   {
      var temp = pStartScreenRow;
      pStartScreenRow = pEndScreenRow;
      pEndScreenRow = temp;
   }

   // Calculate the ending index to use for the message groups array.
   var endIndex = pStartIndex + (pEndScreenRow-pStartScreenRow);
   if (endIndex >= msg_area.grp_list.length)
      endIndex = msg_area.grp_list.length - 1;
   var onePastEndIndex = endIndex + 1;

   // Check to make sure bbs.curgrp is valid (it might not be for brand-new users).
   var curgrpValid = ((bbs.curgrp != null) && (typeof(bbs.curgrp) != "undefined"));

   // Clear the screen, go to the specified screen row, and display the message
   // group information.
   if (pClearScreenFirst)
      console.clear("\1n");
   console.gotoxy(1, pStartScreenRow);
   var grpIndex = pStartIndex;
   for (; grpIndex < onePastEndIndex; ++grpIndex)
   {
      this.WriteMsgGroupLine(grpIndex, false);
      if (grpIndex < endIndex)
         console.crlf();
   }

   // If pBlankToEndRow is true and we're not at the end row yet, then
   // write blank lines to the end row.
   if (pBlankToEndRow)
   {
      var screenRow = pStartScreenRow + (endIndex - pStartIndex) + 1;
      if (screenRow <= pEndScreenRow)
      {
         for (; screenRow <= pEndScreenRow; ++screenRow)
         {
            console.gotoxy(1, screenRow);
            console.clearline("\1n");
         }
      }
   }
}

// For the DDMsgAreaChooser class - Writes a message group information line.
//
// Parameters:
//  pGrpIndex: The index of the message group to write (assumed to be valid)
//  pHighlight: Boolean - Whether or not to write the line highlighted.
function DDMsgAreaChooser_writeMsgGroupLine(pGrpIndex, pHighlight)
{
   console.print("\1n");
   // Write the highlight background color if pHighlight is true.
   if (pHighlight)
      console.print(this.colors.bkgHighlight);

   // Write the message group information line
   console.print(((typeof(bbs.curgrp) == "number") && (pGrpIndex == bbs.curgrp)) ? this.colors.areaMark + "*" : " ");
   printf((pHighlight ? this.msgGrpListHilightPrintfStr : this.msgGrpListPrintfStr),
          +(pGrpIndex+1),
          msg_area.grp_list[pGrpIndex].description.substr(0, this.msgGrpDescLen),
          msg_area.grp_list[pGrpIndex].sub_list.length);
   console.cleartoeol("\1n");
}

//////////////////////////////////////////////////
// Message sub-board list stuff (lightbar mode) //
//////////////////////////////////////////////////

// Updates the page number text in the group list header line on the screen.
//
// Parameters:
//  pPageNum: The page number
//  pNumPages: The total number of pages
//  pGroup: Boolean - Whether or not this is for the group header.  If so,
//          then this will go to the right location for the group page text
//          and use this.colors.header for the text.  Otherwise, this will
//          go to the right place for the sub-board page text and use the
//          sub-board header color.
//  pRestoreCurPos: Optional - Boolean - If true, then move the cursor back
//                  to the position where it was before this function was called
function DDMsgAreaChooser_updatePageNumInHeader(pPageNum, pNumPages, pGroup, pRestoreCurPos)
{
  var originalCurPos = null;
  if (pRestoreCurPos)
    originalCurPos = console.getxy();

  if (pGroup)
  {
    console.gotoxy(29, 1+this.areaChangeHdrLines.length);
    console.print("\1n" + this.colors.header + pPageNum + " of " + pNumPages + ")   ");
  }
  else
  {
    console.gotoxy(51, 1+this.areaChangeHdrLines.length);
    console.print("\1n" + this.colors.subBoardHeader + pPageNum + " of " + pNumPages + ")   ");
  }

  if (pRestoreCurPos)
    console.gotoxy(originalCurPos);
}

// Displays a screenful of message sub-boards, for the lightbar interface.
//
// Parameters:
//  pGrpIndex: The index of the message group (0-based)
//  pStartSubIndex: The message sub-board index to start at (0-based)
//  pStartScreenRow: The row on the screen to start at (1-based)
//  pEndScreenRow: The row on the screen to end at (1-based)
//  pClearScreenFirst: Boolean - Whether or not to clear the screen first
//  pBlankToEndRow: Boolean - Whether or not to write blank lines to the end
//                  screen row if there aren't enough message groups to fill
//                  the screen.
function DDMsgAreaChooser_listScreenfulOfSubBrds(pGrpIndex, pStartSubIndex,
                                                  pStartScreenRow, pEndScreenRow,
                                                  pClearScreenFirst, pBlankToEndRow)
{
   // Check the parameters; If they're bad, then just return.
   if ((typeof(pGrpIndex) != "number") ||
       (typeof(pStartSubIndex) != "number") ||
       (typeof(pStartScreenRow) != "number") ||
       (typeof(pEndScreenRow) != "number"))
   {
      return;
   }
   if ((pGrpIndex < 0) || (pGrpIndex >= msg_area.grp_list.length))
      return;
   if ((pStartSubIndex < 0) ||
       (pStartSubIndex >= msg_area.grp_list[pGrpIndex].sub_list.length))
   {
      return;
   }
   if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows))
      return;
   if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows))
      return;
   // If pStartScreenRow is greather than pEndScreenRow, then swap them.
   if (pStartScreenRow > pEndScreenRow)
   {
      var temp = pStartScreenRow;
      pStartScreenRow = pEndScreenRow;
      pEndScreenRow = temp;
   }

   // Calculate the ending index to use for the sub-board array.
   var endIndex = pStartSubIndex + (pEndScreenRow-pStartScreenRow);
   if (endIndex >= msg_area.grp_list[pGrpIndex].sub_list.length)
      endIndex = msg_area.grp_list[pGrpIndex].sub_list.length - 1;
   var onePastEndIndex = endIndex + 1;

   // Clear the screen and go to the specified screen row.
   if (pClearScreenFirst)
      console.clear("\1n");
   console.gotoxy(1, pStartScreenRow);

   // Start listing the sub-boards.

   var subIndex = pStartSubIndex;
   for (; subIndex < onePastEndIndex; ++subIndex)
   {
      this.WriteMsgSubBoardLine(pGrpIndex, subIndex, false);
      if (subIndex < endIndex)
         console.crlf();
   }

   // If pBlankToEndRow is true and we're not at the end row yet, then
   // write blank lines to the end row.
   if (pBlankToEndRow)
   {
      var screenRow = pStartScreenRow + (endIndex - pStartSubIndex) + 1;
      if (screenRow <= pEndScreenRow)
      {
         for (; screenRow <= pEndScreenRow; ++screenRow)
         {
            console.gotoxy(1, screenRow);
            console.clearline("\1n");
         }
      }
   }
}

// For the DDMsgAreaChooser class: Writes a message sub-board information line.
//
// Parameters:
//  pGrpIndex: The index of the message group (assumed to be valid)
//  pSubIndex: The index of the sub-board within the message group to write (assumed to be valid)
//  pHighlight: Boolean - Whether or not to write the line highlighted.
function DDMsgAreaChooser_writeMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
{
   console.print("\1n");
   // Write the highlight background color if pHighlight is true.
   if (pHighlight)
      console.print(this.colors.bkgHighlight);

   // Determine if pGrpIndex and pSubIndex specify the user's
   // currently-selected group and sub-board.
   var currentSub = false;
   if ((typeof(bbs.curgrp) == "number") && (typeof(bbs.cursub) == "number"))
      currentSub = ((pGrpIndex == bbs.curgrp) && (pSubIndex == bbs.cursub));

   // Open the current sub-board with the msgBase object (so that we can get
   // the date & time of the last imporeted message).
   var msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code);
   if (msgBase.open())
   {
      var newestDate = new Object(); // For storing the date of the newest post
      // Get the date & time when the last message was imported.
      if (msgBase.total_msgs > 0)
      {
         msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true);
         // Construct the date & time strings of the latest post
         if (this.showImportDates)
         {
            newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time);
            newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time);
         }
         else
         {
            newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time);
            newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time);
         }
      }
      else
         newestDate.date = newestDate.time = "";

      // Print the sub-board information line.
      console.print(currentSub ? this.colors.areaMark + "*" : " ");
      printf((pHighlight ? this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr : this.subBoardListPrintfInfo[pGrpIndex].printfStr),
             +(pSubIndex+1),
             msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, this.subBoardListPrintfInfo[pGrpIndex].nameLen),
             msgBase.total_msgs, newestDate.date, newestDate.time);
      msgBase.close();

      // Free some memory?
      delete msgBase;
   }
}

///////////////////////////////////////////////
// Other functions for the msg. area chooser //
///////////////////////////////////////////////

// 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);
			// 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 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 (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
//
// Parameters:
//  pLightbar: Boolean - Whether or not to show lightbar help.  If
//             false, then this function will show regular help.
//  pClearScreen: Boolean - Whether or not to clear the screen first
function DDMsgAreaChooser_showHelpScreen(pLightbar, pClearScreen)
{
	if (pClearScreen)
		console.clear("\1n");
	else
		console.print("\1n");
	console.center("\1c\1hDigital Distortion Message Area Chooser");
	console.center("\1kÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ");
	console.center("\1n\1cVersion \1g" + DD_MSG_AREA_CHOOSER_VERSION +
	               " \1w\1h(\1b" + DD_MSG_AREA_CHOOSER_VER_DATE + "\1w)");
	console.crlf();
	console.print("\1n\1cFirst, a listing of message groups is displayed.  One can be chosen by typing");
	console.crlf();
	console.print("its number.  Then, a listing of sub-boards within that message group will be");
	console.crlf();
	console.print("shown, and one can be chosen by typing its number.");
	console.crlf();

	if (pLightbar)
	{
		console.crlf();
		console.print("\1n\1cThe lightbar interface also allows up & down navigation through the lists:");
		console.crlf();
		console.print("\1k\1hÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ");
		console.crlf();
		console.print("\1n\1c\1hUp arrow\1n\1c: Move the cursor up one line");
		console.crlf();
		console.print("\1hDown arrow\1n\1c: Move the cursor down one line");
		console.crlf();
		console.print("\1hENTER\1n\1c: Select the current group/sub-board");
		console.crlf();
		console.print("\1hHOME\1n\1c: Go to the first item on the screen");
		console.crlf();
		console.print("\1hEND\1n\1c: Go to the last item on the screen");
		console.crlf();
		console.print("\1hPageUp\1n\1c/\1hPageDown\1n\1c: Go to the previous/next page");
		console.crlf();
		console.print("\1hF\1n\1c/\1hL\1n\1c: Go to the first/last page");
		console.crlf();
	}

	console.crlf();
	console.print("Additional keyboard commands:");
	console.crlf();
	console.print("\1k\1hÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ");
	console.crlf();
	console.print("\1n\1c\1h?\1n\1c: Show this help screen");
	console.crlf();
	console.print("\1hQ\1n\1c: Quit");
	console.crlf();
}

// 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
function DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp(pGrpIndex)
{
   // If the array of sub-board printf strings doesn't contain the printf
   // strings for this message group, then figure out the largest number
   // of messages in the message group and add the printf strings.
   if (typeof(this.subBoardListPrintfInfo[pGrpIndex]) == "undefined")
   {
      var greatestNumMsgs = getGreatestNumMsgs(pGrpIndex);

      this.subBoardListPrintfInfo[pGrpIndex] = new Object();
      this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen = greatestNumMsgs.toString().length;
      // Sub-board name length: With a # items length of 4, this should be
      // 47 for an 80-column display.
      this.subBoardListPrintfInfo[pGrpIndex].nameLen = console.screen_columns -
                                   this.areaNumLen -
                                   this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen -
                                   this.dateLen - this.timeLen - 7;
      // Create the printf strings
      this.subBoardListPrintfInfo[pGrpIndex].printfStr =
               " " + this.colors.areaNum
               + "%" + this.areaNumLen + "d "
               + this.colors.desc + "%-"
               + this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s "
               + this.colors.numItems + "%"
               + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d "
               + this.colors.latestDate + "%" + this.dateLen + "s "
               + this.colors.latestTime + "%" + this.timeLen + "s";
      this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr =
                              "\1n" + this.colors.bkgHighlight + " "
                              + "\1n" + this.colors.bkgHighlight
                              + this.colors.areaNumHighlight
                              + "%" + this.areaNumLen + "d \1n"
                              + this.colors.bkgHighlight
                              + this.colors.descHighlight + "%-"
                              + this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s \1n"
                              + this.colors.bkgHighlight
                              + this.colors.numItemsHighlight + "%"
                              + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d \1n"
                              + this.colors.bkgHighlight
                              + this.colors.dateHighlight + "%" + this.dateLen + "s \1n"
                              + this.colors.bkgHighlight
                              + this.colors.timeHighlight + "%" + this.timeLen + "s\1n";
   }
}

// 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:
//  http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces
//
// Parameters:
//  pString: The string to trim
//  pLeading: Whether or not to trim leading spaces (optional, defaults to true)
//  pMultiple: Whether or not to trim multiple spaces (optional, defaults to true)
//  pTrailing: Whether or not to trim trailing spaces (optional, defaults to true)
function trimSpaces(pString, pLeading, pMultiple, pTrailing)
{
	var leading = true;
	var multiple = true;
	var trailing = true;
	if(typeof(pLeading) != "undefined")
		leading = pLeading;
	if(typeof(pMultiple) != "undefined")
		multiple = pMultiple;
	if(typeof(pTrailing) != "undefined")
		trailing = pTrailing;
		
	// To remove both leading & trailing spaces:
	//pString = pString.replace(/(^\s*)|(\s*$)/gi,"");

	if (leading)
		pString = pString.replace(/(^\s*)/gi,"");
	if (multiple)
		pString = pString.replace(/[ ]{2,}/gi," ");
	if (trailing)
		pString = pString.replace(/(\s*$)/gi,"");

	return pString;
}

// 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);
}

// Returns the greatest number of messages of all sub-boards within
// a message group.
//
// Parameters:
//  pGrpIndex: The index of the message group
//
// Returns: The greatest number of messages of all sub-boards within
//          the message group
function getGreatestNumMsgs(pGrpIndex)
{
  // Sanity checking
  if (typeof(pGrpIndex) != "number")
    return 0;
  if (typeof(msg_area.grp_list[pGrpIndex]) == "undefined")
    return 0;