Skip to content
Snippets Groups Projects
DDMsgAreaChooser.js 121 KiB
Newer Older
			selectedSubIndex: srchObj.itemIdx
		};

		// For screen refresh optimization, don't redraw the whole
		// list if the result is on the same page
		if (srchObj.pageTopIdx != topSubIndex)
		{
			retObj.differentPage = true;
			chooserObj.updatePageNumInHeader(srchObj.pageNum, numPages, false, false);
			chooserObj.ListScreenfulOfSubBrds(grpIndex, srchObj.pageTopIdx, listStartRow, listEndRow, false, true, srchObj.itemIdx);
			retObj.selectedSubIndex = srchObj.itemIdx;
		}
		else
		{
			if (srchObj.itemIdx != selectedSubIndex)
			{
				var screenY = listStartRow + (selectedSubIndex - topSubIndex);
				console.gotoxy(1, screenY);
				chooserObj.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
				retObj.selectedSubIndex = srchObj.itemIdx;
				screenY = listStartRow + (retObj.selectedSubIndex - topSubIndex);
				console.gotoxy(1, screenY);
				chooserObj.WriteMsgSubBoardLine(grpIndex, retObj.selectedSubIndex, true);
			}
		}

		return retObj;
	}

	// Figure out the index of the user's currently-selected sub-board.
	var selectedSubIndex = 0;
	if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
		selectedSubIndex =  msg_area.sub[bbs.cursub_code].index;
	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.
	var numItemsPerPage = listEndRow - listStartRow + 1;
	var bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
	// Figure out how many pages are needed to list all the sub-boards.
	var numPages = Math.ceil(msg_area.grp_list[grpIndex].sub_list.length / 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 (selectedSubIndex > bottomSubIndex)
	{
		var nextPageTopIndex = 0;
		while (selectedSubIndex > bottomSubIndex)
		{
			nextPageTopIndex = topSubIndex + numItemsPerPage;
			if (nextPageTopIndex < msg_area.grp_list[grpIndex].sub_list.length)
			{
				// Adjust topSubIndex and bottomSubIndex, and
				// refresh the list on the screen.
				topSubIndex = nextPageTopIndex;
				bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
			}
			else
				break;
		}
		// 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 = ((topSubIndex < msg_area.grp_list[grpIndex].sub_list.length) &&
		                       (selectedSubIndex >= topSubIndex) && (selectedSubIndex <= bottomSubIndex));
		if (!foundCorrectPage)
		{
			topSubIndex = 0;
			bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
			selectedSubIndex = 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); // 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 = {
		x: 1,
		y: 2+this.areaChangeHdrLines.length
	};
	if (this.showDatesInSubBoardList)
		printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
	else
		printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts");
	this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, 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 lastSearchText = "";
	var lastSearchFoundIdx = -1;
	var continueChoosingSubBrd = true;
	while (continueChoosingSubBrd)
	{
		// Highlight the currently-selected message group
		highlightScrenRow = listStartRow + (selectedSubIndex - topSubIndex);
		curpos.y = highlightScrenRow;
		if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows))
		{
			console.gotoxy(1, highlightScrenRow);
			this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, 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 (selectedSubIndex > 0)
				{
					// If the previous group index is on the previous page, then
					// display the previous page.
					var previousSubIndex = selectedSubIndex - 1;
					if (previousSubIndex < topSubIndex)
					{
						// Adjust topSubIndex and bottomSubIndex, and
						// refresh the list on the screen.
						topSubIndex -= numItemsPerPage;
						bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
						pageNum = calcPageNum(topSubIndex, numItemsPerPage);
						this.updatePageNumInHeader(pageNum, numPages, false, false);
						this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
					}
					else
					{
						// Display the current line un-highlighted.
						console.gotoxy(1, curpos.y);
						this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
					}
					selectedSubIndex = previousSubIndex;
				}
				break;
			case KEY_DOWN: // Move down one message group in the list
				if (selectedSubIndex < msg_area.grp_list[grpIndex].sub_list.length - 1)
				{
					// If the next group index is on the next page, then display
					// the next page.
					var nextGrpIndex = selectedSubIndex + 1;
					if (nextGrpIndex > bottomSubIndex)
					{
						// Adjust topSubIndex and bottomSubIndex, and
						// refresh the list on the screen.
						topSubIndex += numItemsPerPage;
						bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
						pageNum = calcPageNum(topSubIndex, numItemsPerPage);
						this.updatePageNumInHeader(pageNum, numPages, false, false);
						this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
					}
					else
					{
						// Display the current line un-highlighted.
						console.gotoxy(1, curpos.y);
						this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
					}
					selectedSubIndex = nextGrpIndex;
				}
				break;
			case KEY_HOME: // Go to the top message group on the screen
				if (selectedSubIndex > topSubIndex)
				{
					// Display the current line un-highlighted, then adjust
					// selectedSubIndex.
					console.gotoxy(1, curpos.y);
					this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
					selectedSubIndex = topSubIndex;
					// 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 (selectedSubIndex < bottomSubIndex)
				{
					// Display the current line un-highlighted, then adjust
					// selectedSubIndex.
					console.gotoxy(1, curpos.y);
					this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
					selectedSubIndex = bottomSubIndex;
					// 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.
				continueChoosingSubBrd = false;
				retObj.subBoardChosen = true;
				retObj.subBoardIndex = selectedSubIndex;
				retObj.subBoardCode = msg_area.grp_list[grpIndex].sub_list[selectedSubIndex].code;
				break;
			case KEY_PAGE_DOWN: // Go to the next page
				var nextPageTopIndex = topSubIndex + numItemsPerPage;
				if (nextPageTopIndex < msg_area.grp_list[grpIndex].sub_list.length)
				{
					// Adjust topSubIndex and bottomSubIndex, and
					// refresh the list on the screen.
					topSubIndex = nextPageTopIndex;
					pageNum = calcPageNum(topSubIndex, numItemsPerPage);
					bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
					this.updatePageNumInHeader(pageNum, numPages, false, false);
					this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
					selectedSubIndex = topSubIndex;
				}
				break;
			case KEY_PAGE_UP: // Go to the previous page
				var prevPageTopIndex = topSubIndex - numItemsPerPage;
				if (prevPageTopIndex >= 0)
				{
					// Adjust topSubIndex and bottomSubIndex, and
					// refresh the list on the screen.
					topSubIndex = prevPageTopIndex;
					pageNum = calcPageNum(topSubIndex, numItemsPerPage);
					bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
					this.updatePageNumInHeader(pageNum, numPages, false, false);
					this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
					selectedSubIndex = topSubIndex;
				}
				break;
			case 'F': // Go to the first page
				if (topSubIndex > 0)
				{
					topSubIndex = 0;
					pageNum = calcPageNum(topSubIndex, numItemsPerPage);
					bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
					this.updatePageNumInHeader(pageNum, numPages, false, false);
					this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
					selectedSubIndex = 0;
				}
				break;
			case 'L': // Go to the last page
				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);
				if (this.showDatesInSubBoardList)
					printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
				else
					printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts");
				this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
				break;
			case '/': // Start of find (search)
			case CTRL_F: // Start of find
				console.gotoxy(1, console.screen_rows);
				console.cleartoeol("\1n");
				console.gotoxy(1, console.screen_rows);
				var promptText = "Search: ";
				console.print(promptText);
				var searchText = getStrWithTimeout(K_UPPER|K_NOCRLF|K_GETSTR|K_NOSPIN|K_LINE, console.screen_columns - promptText.length - 1, SEARCH_TIMEOUT_MS);
				// If the user entered text, then do the search, and if found,
				// found, go to the page and select the item indicated by the
				// search.
				if (searchText.length > 0)
				{
					var srchObj = getPageNumFromSearch(searchText, numItemsPerPage, true, 0, grpIndex);
					if (srchObj.pageNum > 0)
					{
						lastSearchText = searchText;
						lastSearchFoundIdx = srchObj.itemIdx;

						// For screen refresh optimization, don't redraw the whole
						// list if the result is on the same page
						if (srchObj.pageTopIdx != topSubIndex)
						{
							pageNum = srchObj.pageNum;
							topSubIndex = srchObj.pageTopIdx;
							bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
							this.updatePageNumInHeader(pageNum, numPages, false, false);
							this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, false, true);
							selectedSubIndex = srchObj.itemIdx;
						}
						else
						{
							if (srchObj.itemIdx != selectedSubIndex)
							{
								var screenY = listStartRow + (selectedSubIndex - topSubIndex);
								console.gotoxy(1, screenY);
								this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false);
								selectedSubIndex = srchObj.itemIdx;
								screenY = listStartRow + (selectedSubIndex - topSubIndex);
								console.gotoxy(1, screenY);
								this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, true);
							}
						}
					}
					else
						this.WriteLightbarKeyHelpErrorMsg("Not found", false);
				}
				this.WriteKeyHelpLine();
				break;
			case 'N': // Next search result (requires an existing search term)
				if ((lastSearchText.length > 0) && (lastSearchFoundIdx > -1))
				{
					// Do the search, and if found, go to the page and select the item
					// indicated by the search.
					var srchObj = getPageNumFromSearch(lastSearchText, numItemsPerPage, true, lastSearchFoundIdx+1, grpIndex);
					lastSearchFoundIdx = srchObj.itemIdx;
					if (srchObj.pageNum > 0)
					{
						var foundItemRetObj = nextSubSearchFoundItem(pGrpIndex, srchObj, numPages, listStartRow, listEndRow, selectedSubIndex, topSubIndex, this);
						if (foundItemRetObj.differentPage)
						{
							pageNum = srchObj.pageNum;
							topSubIndex = foundItemRetObj.pageTopIdx;
							bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
						}
						selectedSubIndex = foundItemRetObj.selectedSubIndex;
					}
					else
					{
						// Not found - Wrap around and start at 0 again
						var srchObj = getPageNumFromSearch(lastSearchText, numItemsPerPage, true, 0, grpIndex);
						lastSearchFoundIdx = srchObj.itemIdx;
						var foundItemRetObj = nextSubSearchFoundItem(pGrpIndex, srchObj, numPages, listStartRow, listEndRow, selectedSubIndex, topSubIndex, this);
						if (foundItemRetObj.selectedSubIndex != selectedSubIndex)
						{
							if (foundItemRetObj.differentPage)
							{
								pageNum = srchObj.pageNum;
								topSubIndex = foundItemRetObj.pageTopIdx;
								bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage);
							}
							selectedSubIndex = foundItemRetObj.selectedSubIndex;
						}
						else
							this.WriteLightbarKeyHelpErrorMsg("No others found", true);
					}
				}
				else
					this.WriteLightbarKeyHelpErrorMsg("There is no previous search", 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;
						retObj.subBoardCode = msg_area.grp_list[grpIndex].sub_list[retObj.subBoardIndex].code;
					}
					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);
						if (this.showDatesInSubBoardList)
							printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
						else
							printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts");
						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 usersCurrentIdxVals = getGrpAndSubIdxesFromCode(bbs.cursub_code, true);
		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();
			console.print("\1n\1b\1hþ \1n\1cWhich, \1hQ\1n\1cuit, \1hCTRL-F\1n\1c, \1h/\1n\1c, or [\1h" + +(usersCurrentIdxVals.grpIdx+1) + "\1n\1c]: \1h");
			// Accept Q (quit), / or CTRL_F (Search) or a file library number
			selectedGrp = console.getkeys("Q/" + CTRL_F, msg_area.grp_list.length);

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

			if (selectedGrp.toString() == "Q")
				continueChoosingMsgArea = false;
			else if ((selectedGrp.toString() == "/") || (selectedGrp.toString() == CTRL_F))
			{
				console.crlf();
				var searchPromptText = "\1n\1c\1hSearch\1g: \1n";
				console.print(searchPromptText);
				var searchText = console.getstr("", console.screen_columns-strip_ctrl(searchPromptText).length-1, K_UPPER|K_NOCRLF|K_GETSTR|K_NOSPIN|K_LINE);
				if (searchText.length > 0)
					grpSearchText = searchText;
			}
				// 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 = usersCurrentIdxVals.subIdx + 1;
					if (selectedGrp-1 != usersCurrentIdxVals.grpIdx)
						defaultSubBoard = 1;

					console.clear("\1n");
					var selectSubRetVal = this.SelectSubBoard_Traditional(selectedGrp-1, defaultSubBoard-1);
					// If the user chose a directory, then set the user's
					// message sub-board and quit the message group loop.
					if (selectSubRetVal.subBoardCode != "")
						bbs.cursub_code = selectSubRetVal.subBoardCode;
					}
				}
			}
		}
	}
	else
	{
		// Don't choose a group, just a sub-board within the user's current group.
		var idxVals = getGrpAndSubIdxesFromCode(bbs.cursub_code, true);
		var selectSubRetVal = this.SelectSubBoard_Traditional(idxVals.grpIdx, idxVals.subIdx);
		// If the user chose a directory, then set the user's sub-board.
		if (selectSubRetVal.subBoardCode != "")
			bbs.cursub_code = selectSubRetVal.subBoardCode;
	}
}

// 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.
//               subBoardCode: The internal code of the chosen sub-board (or "" if none chosen)
function DDMsgAreaChooser_selectSubBoard_Traditional(pGrpIdx, pDefaultSubBoardIdx)
{
	var retObj = {
		subBoardChosen: false,
		subBoardIndex: 1,
		subBoardCode: ""
	}
	var searchText = "";
	var defaultSubBoardIdx = pDefaultSubBoardIdx;
	var continueOn = false;
	do
	{
		this.DisplayAreaChgHdr(1);
		if (this.areaChangeHdrLines.length > 0)
			console.crlf();
		this.ListSubBoardsInMsgGroup(pGrpIdx, defaultSubBoardIdx, searchText);
		if (defaultSubBoardIdx >= 0)
			console.print("\1n\1b\1hþ \1n\1cWhich, \1hQ\1n\1cuit, \1hCTRL-F\1n\1c, \1h/\1n\1c, or [\1h" + +(defaultSubBoardIdx+1) + "\1n\1c]: \1h");
		else
			console.print("\1n\1b\1hþ \1n\1cWhich, \1hQ\1n\1cuit, \1hCTRL-F\1n\1c, \1h/\1n\1c: \1h");
		// Accept Q (quit) or a sub-board number
		var selectedSubBoard = console.getkeys("Q/" + CTRL_F, 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 == "")
		{
			if (defaultSubBoardIdx >= 0)
			{
				selectedSubBoard = defaultSubBoardIdx + 1; // Make this 1-based
				continueOn = false;
			}
		}
		else if ((selectedSubBoardStr == "/") || (selectedSubBoardStr == CTRL_F))
		{
			// Search
			console.crlf();
			var searchPromptText = "\1n\1c\1hSearch\1g: \1n";
			console.print(searchPromptText);
			searchText = console.getstr("", console.screen_columns-strip_ctrl(searchPromptText).length-1, K_UPPER|K_NOCRLF|K_GETSTR|K_NOSPIN|K_LINE);
			console.print("\1n");
			console.crlf();
			if (searchText.length > 0)
				defaultSubBoardIdx = -1;
			else
				defaultSubBoardIdx = pDefaultSubBoardIdx;
			continueOn = true;
		}
		else if (selectedSubBoardStr == "Q")
			continueOn = false;
		// If a sub-board was chosen, then select it.
		if (selectedSubBoard > 0)
		{
			retObj.subBoardChosen = true;
			retObj.subBoardIndex = selectedSubBoard - 1;
			retObj.subBoardCode = msg_area.grp_list[pGrpIdx].sub_list[retObj.subBoardIndex].code;
			continueOn = false;
		}
	} while (continueOn);
}

// For the DDMsgAreaChooser class: Lists all message groups (for the traditional
// user interface).
//
// Parameters:
//  pSearchText: Optional - Search text for the message groups
function DDMsgAreaChooser_listMsgGrps_Traditional(pSearchText)
	// Print the header
	this.WriteGrpListHdrLine();
	console.print("\1n");

	var searchText = (typeof(pSearchText) == "string" ? pSearchText.toUpperCase() : "");

	for (var i = 0; i < msg_area.grp_list.length; ++i)
	{
		if (searchText.length > 0)
			printIt = ((msg_area.grp_list[i].name.toUpperCase().indexOf(searchText) >= 0) || (msg_area.grp_list[i].description.toUpperCase().indexOf(searchText) >= 0));
		else
			printIt = true;

		if (printIt)
		{
			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.
//  pSearchText: Optional - Search text for the sub-boards
//  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, pSearchText, pSortType)
	// Default to the current message group & sub-board if pGrpIndex
	// and pMarkIndex aren't specified.
	var grpIndex = 0;
	if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
		grpIndex = msg_area.sub[bbs.cursub_code].grp_index;
	if ((pGrpIndex != null) && (typeof(pGrpIndex) == "number"))
		grpIndex = pGrpIndex;
	var highlightIndex = 0;
	if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
		highlightIndex = (pGrpIndex == msg_area.sub[bbs.cursub_code].index);
	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();
	if (this.showDatesInSubBoardList)
		printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
	else
		printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts");
	// Make the search text uppercase for case-insensitive matching
	var searchTextUpper = (typeof(pSearchText) == "string" ? pSearchText.toUpperCase() : "");

	// 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.
			// If the search text is set, then use it to filter the sub-boards.
			addSubBoard = true;
			if (searchTextUpper.length > 0)
			{
				addSubBoard = ((msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].name.indexOf(searchTextUpper) >= 0) ||
				               (msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.indexOf(searchTextUpper) >= 0));
			}
			if (addSubBoard)
				msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
				if (msgBase.open())
					subBoardInfo = new MsgSubBoardInfo();
					subBoardInfo.subBoardNum = +(arrSubBoardNum);
					subBoardInfo.subBoardIdx = msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].index;
					subBoardInfo.description = msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description;
					//subBoardInfo.numPosts = numReadableMsgs(msgBase, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
					subBoardInfo.numPosts = msgBase.total_msgs;

					// Get the date & time when the last message was imported.
					if (this.showDatesInSubBoardList && (subBoardInfo.numPosts > 0))
						//var msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true);
						var msgHeader = getLatestMsgHdr(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
						if (msgHeader === null)
							msgHeader = getBogusMsgHdr();
						if (this.showImportDates)
							subBoardInfo.newestPostDate = msgHeader.when_imported_time
						{
							var msgWrittenLocalBBSTime = msgWrittenTimeToLocalBBSTime(msgHeader);
							if (msgWrittenLocalBBSTime != -1)
								subBoardInfo.newestPostDate = msgWrittenLocalBBSTime;
							else
								subBoardInfo.newestPostDate = msgHeader.when_written_time;
						}
				msgBase.close();
				subBoardArray.push(subBoardInfo);
				delete msgBase; // Free some memory?
		// 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();
			var showSubBoardMark = false;
			if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
			{
				if (subBoardArray[i].subBoardNum == highlightIndex)
					showSubBoardMark = ((grpIndex == msg_area.sub[bbs.cursub_code].grp_index) && (highlightIndex == subBoardArray[i].subBoardIdx));
			}
			console.print(showSubBoardMark ? "\1n" + this.colors.areaMark + "*" : " ");
			if (this.showDatesInSubBoardList)
			{
				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));
			}
			else
			{
				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));
			}
		}
	}
	// 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)
		{
			// If the search text is set, then use it to filter the sub-board list.
			includeSubBoard = true;
			if (searchTextUpper.length > 0)
			{
				includeSubBoard = ((msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].name.toUpperCase().indexOf(searchTextUpper) >= 0) ||
				                   (msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.toUpperCase().indexOf(searchTextUpper) >= 0));
			}
			if (includeSubBoard)
				// 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.
					//var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
					var numMsgs = msgBase.total_msgs;
					if (numMsgs > 0)
						//var msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true);
						var msgHeader = getLatestMsgHdr(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
						if (msgHeader === null)
							msgHeader = getBogusMsgHdr();
						// 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);
							var msgWrittenLocalBBSTime = msgWrittenTimeToLocalBBSTime(msgHeader);
							if (msgWrittenLocalBBSTime != -1)
							{
								newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalBBSTime);
								newestDate.time = strftime("%H:%M:%S", msgWrittenLocalBBSTime);
							}
							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 + "*" : " ");
					if (this.showDatesInSubBoardList)
					{
						printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardNum+1),
							   msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen),
							   numMsgs, newestDate.date, newestDate.time);
					}
					else
					{
						printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardNum+1),
							   msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen),
							   numMsgs);
					}

					msgBase.close();
}

//////////////////////////////////////////////
// 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.
//  pHighlightIndex: Optional - The index of an item to highlight
function DDMsgAreaChooser_listScreenfulOfMsgGrps(pStartIndex, pStartScreenRow,
	// 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;

	// Clear the screen, go to the specified screen row, and display the message
	// group information.
	var highlightIdx = (typeof(pHighlightIndex) == "number" ? pHighlightIndex : -1);
	if (pClearScreenFirst)
		console.clear("\1n");
	console.gotoxy(1, pStartScreenRow);
	var grpIndex = pStartIndex;
	for (; grpIndex < onePastEndIndex; ++grpIndex)
	{
		highlight = (grpIndex == highlightIdx);
		this.WriteMsgGroupLine(grpIndex, highlight);
		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
	var grpIsSelected = false;
	if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
		grpIsSelected = (pGrpIndex == msg_area.sub[bbs.cursub_code].grp_index);
	console.print(grpIsSelected ? 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)