diff --git a/docs/SlyEdit_ReadMe.txt b/docs/SlyEdit_ReadMe.txt index a47223f6ea8288845e476627bc080dd694b318c2..feace8dad71295637cf4da9bab201c34ea05bf4a 100644 --- a/docs/SlyEdit_ReadMe.txt +++ b/docs/SlyEdit_ReadMe.txt @@ -1,6 +1,6 @@ SlyEdit message editor - Version 1.73 - Release date: 2020-03-31 + Version 1.75 + Release date: 2021-12-11 by diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js index d74a58b19a9ceb1894dbf2461cbf8327e06f87ae..2e065e32e4765d515273c0295c6f527eb05ad5ad 100644 --- a/exec/SlyEdit.js +++ b/exec/SlyEdit.js @@ -128,6 +128,14 @@ * SlyEdit no longer thinks a 0x0 (sent with CTRL-Space on a Mac) is * a timeout. K_NUL was added on Jan. 21, 2021 by Rob Swindell: * https://gitlab.synchro.net/main/sbbs/-/commit/8b8ed2159c31057764d260c0860335c85e33d6d8 + * 2021-01-23 Eric Oulashin Version 1.75 Beta + * Started working on refactorin the cross-post + * area chooser to use DDLightbarMenu rather + * than the internal lightbar code. + * 2021-12-11 Eric Oulashin Version 1.75 + * Finished refactoring to use DDLightbarMenu + * for the cross-posting menus. Also used DDLightbarMenu + * for the quote selection window. */ /* Command-line arguments: @@ -224,8 +232,8 @@ if (console.screen_columns < 80) } // Constants -const EDITOR_VERSION = "1.74"; -const EDITOR_VER_DATE = "2021-01-23"; +const EDITOR_VERSION = "1.75"; +const EDITOR_VER_DATE = "2021-12-11"; // Program variables @@ -2789,7 +2797,7 @@ function doQuoteSelection(pCurpos, pCurrentWordLength) // quote lines if (typeof(doQuoteSelection.backupQuoteLines) == "undefined") { - doQuoteSelection.backupQuoteLines = new Array(); + doQuoteSelection.backupQuoteLines = []; // Array for (var i = 0; i < gQuoteLines.length; ++i) doQuoteSelection.backupQuoteLines.push(gQuoteLines[i]); } @@ -2849,259 +2857,88 @@ function doQuoteSelection(pCurpos, pCurrentWordLength) } // Set up some variables - var curpos = new Object(); - curpos.x = pCurpos.x; - curpos.y = pCurpos.y; + var curpos = { + x: pCurpos.x, + y: pCurpos.y + }; + // Make the quote window's height about 42% of the edit area. const quoteWinHeight = Math.floor(gEditHeight * 0.42) + 1; - // The first and last lines on the screen where quote lines are written + // The first line on the screen where quote lines are written const quoteTopScreenRow = console.screen_rows - quoteWinHeight + 2; - const quoteBottomScreenRow = console.screen_rows - 2; - // Quote window parameters - const quoteWinTopScreenRow = quoteTopScreenRow-1; - const quoteWinWidth = gEditRight - gEditLeft + 1; - - // For pageUp/pageDown functionality - Calculate the top quote line index - // for the last page. - var quoteWinInnerHeight = quoteBottomScreenRow - quoteTopScreenRow + 1; // # of quote lines in the quote window - var numPages = Math.ceil(gQuoteLines.length / quoteWinInnerHeight); - var topIndexForLastPage = (quoteWinInnerHeight * numPages) - quoteWinInnerHeight; // Display the top border of the quote window. fpDrawQuoteWindowTopBorder(quoteWinHeight, gEditLeft, gEditRight); + console.gotoxy(gEditLeft, gEditBottom+1); + fpDrawQuoteWindowBottomBorder(gEditLeft, gEditRight); + + var quoteLineMenu = createQuoteLineMenu(quoteTopScreenRow); + // Customize the menu's OnItemSelect function to add the selected quote + // line to the message. Note that the menu's exitOnItemSelect is set + // to false in createQuoteLineMenu() so that its input loop won't + // exit when the user selects a quote line. + quoteLineMenu.OnItemSelect = function(pQuoteLineIdx, pSelected) + { + if (!pSelected) return; // pSelected should always be true for this, but just in case.. - // Display the remainder of the quote window, with the quote lines in it. - displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, gQuoteLinesIndex); + // Keep gQuoteLinesIndex up to date + gQuoteLinesIndex = pQuoteLineIdx; - // Position the cursor at the currently-selected quote line. - var screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - console.gotoxy(gEditLeft, screenLine); + // numTimesToMoveDown specifies how many times to move the cursor + // down after inserting the quote line into the message. + var numTimesToMoveDown = 1; - // User input loop - var quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - retObj.timedOut = false; - var userInput = null; - var continueOn = true; - while (continueOn) - { - // Get a keypress from the user - userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN|K_NOECHO, gConfigSettings); - if (userInput == "") + // Insert the quote line into gEditLines after the current gEditLines index. + var quoteLine = getQuoteTextLine(pQuoteLineIdx, quoteLineMenu.size.width); + var insertedBelow = insertLineIntoMsg(gEditLinesIndex, quoteLine, true, true); + if (insertedBelow) { - // The input timeout was reached. Abort. - retObj.timedOut = true; - continueOn = false; - break; + // The cursor will need to be moved down 1 more line. + // So, increment numTimesToMoveDown, and set curpos.x + // and gTextLineIndex to the beginning of the line. + ++numTimesToMoveDown; + curpos.x = gEditLeft; + gTextLineIndex = 0; + retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex); } + else + retObj.currentWordLength = 0; - // If we got here, that means the user input didn't time out. - switch (userInput) - { - case KEY_UP: - // Go up 1 quote line - if (gQuoteLinesIndex > 0) - { - // If the cursor is at the topmost position, then - // we need to scroll up 1 line in gQuoteLines. - if (screenLine == quoteTopScreenRow) - { - --gQuoteLinesIndex; - --gQuoteLinesTopIndex; - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - // Redraw the quote lines in the quote window. - displayQuoteWindowLines(gQuoteLinesIndex, quoteWinHeight, quoteWinWidth, - true, gQuoteLinesIndex); - // Put the cursor back where it should be. - console.gotoxy(gEditLeft, screenLine); - } - // If the cursor is below the topmost position, then - // we can just go up 1 line. - else if (screenLine > quoteTopScreenRow) - { - // Write the current quote line using the normal color - // Note: This gets the quote line again using getQuoteTextLine() - // so that the color codes in the line will be correct. - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine); - - // Go up one line and display that quote line in the - // highlighted color. - --screenLine; - --gQuoteLinesIndex; - quoteLine = strip_ctrl(getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth)); - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine); - - // Make sure the cursor is where it should be. - console.gotoxy(gEditLeft, screenLine); - } - } - break; - case KEY_DOWN: - // Go down 1 line in the quote window. - var downRetObj = moveDownOneQuoteLine(gQuoteLinesIndex, screenLine, - quoteWinHeight, quoteWinWidth, - quoteBottomScreenRow); - gQuoteLinesIndex = downRetObj.quoteLinesIndex; - screenLine = downRetObj.screenLine; - quoteLine = downRetObj.quoteLine; - break; - case KEY_HOME: // Select the first quote line on the current page - if (gQuoteLinesIndex != gQuoteLinesTopIndex) - { - gQuoteLinesIndex = gQuoteLinesTopIndex; - // Write the current quote line with unhighlighted colors - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine); - // Calculate the new screen line and draw the new quote line with - // highlighted colors - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine); - console.gotoxy(gEditLeft, screenLine); - } - break; - case KEY_END: // Select the last quote line on the current page - var lastIndexForCurrentPage = gQuoteLinesTopIndex + quoteWinInnerHeight - 1; - if (gQuoteLinesIndex != lastIndexForCurrentPage) - { - gQuoteLinesIndex = lastIndexForCurrentPage; - // Write the current quote line with unhighlighted colors - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine); - // Calculate the new screen line and draw the new quote line with - // highlighted colors - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine); - console.gotoxy(gEditLeft, screenLine); - } - break; - case KEY_PAGE_UP: // Go up 1 page in the quote lines - // If the current top quote line index is greater than 0, then go to - // the previous page of quote lines and select the top index on that - // page as the current selected quote line. - if (gQuoteLinesTopIndex > 0) - { - gQuoteLinesTopIndex -= quoteWinInnerHeight; - if (gQuoteLinesTopIndex < 0) - gQuoteLinesTopIndex = 0; - gQuoteLinesIndex = gQuoteLinesTopIndex; - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, - gQuoteLinesIndex); - } - break; - case KEY_PAGE_DOWN: // Go down 1 page in the quote lines - // If the current top quote line index is below the top index for the - // last page, then go to the next page of quote lines and select the - // top index on that page as the current selected quote line. - if (gQuoteLinesTopIndex < topIndexForLastPage) - { - gQuoteLinesTopIndex += quoteWinInnerHeight; - if (gQuoteLinesTopIndex > topIndexForLastPage) - gQuoteLinesTopIndex = topIndexForLastPage; - gQuoteLinesIndex = gQuoteLinesTopIndex; - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, - gQuoteLinesIndex); - } - break; - case "F": // Go to the first page - if (gQuoteLinesTopIndex > 0) - { - gQuoteLinesTopIndex = 0; - gQuoteLinesIndex = gQuoteLinesTopIndex; - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, - gQuoteLinesIndex); - } - break; - case "L": // Go to the last page - if (gQuoteLinesTopIndex < topIndexForLastPage) - { - gQuoteLinesTopIndex = topIndexForLastPage; - gQuoteLinesIndex = gQuoteLinesTopIndex; - quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth); - screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex); - displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, - gQuoteLinesIndex); - } - break; - case KEY_ENTER: - // numTimesToMoveDown specifies how many times to move the cursor - // down after inserting the quote line into the message. - var numTimesToMoveDown = 1; + // Refresh the part of the message that needs to be refreshed on the + // screen (above the quote window). + if (curpos.y < quoteTopScreenRow-1) + displayEditLines(curpos.y, gEditLinesIndex, quoteTopScreenRow-2, false, true); - // Insert the quote line into gEditLines after the current gEditLines index. - var insertedBelow = insertLineIntoMsg(gEditLinesIndex, quoteLine, true, true); - if (insertedBelow) - { - // The cursor will need to be moved down 1 more line. - // So, increment numTimesToMoveDown, and set curpos.x - // and gTextLineIndex to the beginning of the line. - ++numTimesToMoveDown; - curpos.x = gEditLeft; - gTextLineIndex = 0; - retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex); - } - else - retObj.currentWordLength = 0; + gEditLinesIndex += numTimesToMoveDown; - // Refresh the part of the message that needs to be refreshed on the - // screen (above the quote window). - if (curpos.y < quoteTopScreenRow-1) - displayEditLines(curpos.y, gEditLinesIndex, quoteTopScreenRow-2, false, true); - - gEditLinesIndex += numTimesToMoveDown; - - // Go down one line in the quote window. - var tempReturnObj = moveDownOneQuoteLine(gQuoteLinesIndex, screenLine, - quoteWinHeight, quoteWinWidth, - quoteBottomScreenRow); - gQuoteLinesIndex = tempReturnObj.quoteLinesIndex; - screenLine = tempReturnObj.screenLine; - quoteLine = tempReturnObj.quoteLine; - - // Move the cursor down as specified by numTimesToMoveDown. If - // the cursor is at the bottom of the edit area, then refresh - // the message on the screen, scrolled down by one line. - for (var i = 0; i < numTimesToMoveDown; ++i) - { - if (curpos.y == gEditBottom) - { - // Refresh the message on the screen, scrolled down by - // one line, but only if this is the last time we're - // doing this (for efficiency). - if (i == numTimesToMoveDown-1) - { - displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), - quoteTopScreenRow-2, false, true); - } - } - else - ++curpos.y; - } - break; - // ESC or CTRL-Q: Stop quoting - case KEY_ESC: - case CTRL_Q: - // Quit out of the input loop (get out of quote mode). - continueOn = false; - break; + // Go down one line in the quote window + quoteLineMenu.DoKeyDown(); + + // Move the cursor down as specified by numTimesToMoveDown. If + // the cursor is at the bottom of the edit area, then refresh + // the message on the screen, scrolled down by one line. + for (var i = 0; i < numTimesToMoveDown; ++i) + { + if (curpos.y == gEditBottom) + { + // Refresh the message on the screen, scrolled down by + // one line, but only if this is the last time we're + // doing this (for efficiency). + if (i == numTimesToMoveDown-1) + displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), quoteTopScreenRow-2, false, true); + } + else + ++curpos.y; } } + // Now, display the quote line menu & run its input loop + quoteLineMenu.GetVal(); // We've exited quote mode. Refresh the message text on the screen. Note: // This will refresh only the quote window portion of the screen if the // cursor row is at or below the top of the quote window, and it will also // refresh the screen if the cursor row is above the quote window. + const quoteWinTopScreenRow = quoteTopScreenRow-1; displayEditLines(quoteWinTopScreenRow, gEditLinesIndex-(curpos.y-quoteWinTopScreenRow), gEditBottom, true, true); @@ -3123,141 +2960,54 @@ function doQuoteSelection(pCurpos, pCurrentWordLength) retObj.y = curpos.y; return retObj; } - -// Helper for doQuoteSelection(): This function moves the quote selection -// down one line and updates the quote window. -// -// Parameters: -// pQuoteLinesIndex: The index of the current line in gQuoteLines -// pScreenLine: The vertical position of the cursor on the screen -// pQuoteWinHeight: The height of the quote window -// pQuoteWinWidth: The width of the quote window -// pQuoteBottomScreenLine: The bottommost screen line where quote lines are displayed -function moveDownOneQuoteLine(pQuoteLinesIndex, pScreenLine, pQuoteWinHeight, - pQuoteWinWidth, pQuoteBottomScreenLine) -{ - // Create the return object - var returnObj = new Object(); - returnObj.quoteLinesIndex = pQuoteLinesIndex; - returnObj.screenLine = pScreenLine; - returnObj.quoteLine = ""; - - // If the current quote line is above the last one, then we can - // move down one quote line. - if (pQuoteLinesIndex < gQuoteLines.length-1) - { - // If the cursor is at the bottommost position, then - // we need to scroll up 1 line in gQuoteLines. - if (pScreenLine == pQuoteBottomScreenLine) - { - ++pQuoteLinesIndex; - ++gQuoteLinesTopIndex; - returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth); - // Redraw the quote lines in the quote window. - var topQuoteIndex = pQuoteLinesIndex - pQuoteWinHeight + 4; - displayQuoteWindowLines(topQuoteIndex, pQuoteWinHeight, pQuoteWinWidth, true, - pQuoteLinesIndex); - // Put the cursor back where it should be. - console.gotoxy(gEditLeft, pScreenLine); - } - // If the cursor is above the bottommost position, then - // we can just go down 1 line. - else if (pScreenLine < pQuoteBottomScreenLine) - { - // Write the current quote line using the normal color. - // Note: This gets the quote line again using getQuoteTextLine() - // so that the color codes in the line will be correct. - console.gotoxy(gEditLeft, pScreenLine); - returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth); - printf(gFormatStrWithAttr, gQuoteWinTextColor, returnObj.quoteLine); - - // Go down one line and display that quote line in the - // highlighted color. - ++pScreenLine; - ++pQuoteLinesIndex; - returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth); - console.gotoxy(gEditLeft, pScreenLine); - printf(gFormatStrWithAttr, gQuoteLineHighlightColor, returnObj.quoteLine); - - // Put the cursor back where it should be. - console.gotoxy(gEditLeft, pScreenLine); - } - } - else // This else case is for when we're already on the last quote line. - returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth); - - // Make sure the properties of returnObj have the correct - // values (except quoteLine, which is already set), and - // return the returnObj. - returnObj.quoteLinesIndex = pQuoteLinesIndex; - returnObj.screenLine = pScreenLine; - return returnObj; -} - -// Helper for doQuoteSelection(): This displays the quote window, except for its -// top border. +// Creates the DDLightbarMenu for selecting a quote line // // Parameters: -// pQuoteLinesIndex: The index into gQuoteLines to start at. The quote line -// at this index will be displayed at the top of the quote -// window. -// pQuoteWinHeight: The height of the quote window -// pQuoteWinWidth: The width of the quote window -// pDrawBottomBorder: Whether or not to draw the bottom border of the quote -// window. -// pHighlightIndex: Optional - An index of a quote line to highlight. -function displayQuoteWindowLines(pQuoteLinesIndex, pQuoteWinHeight, pQuoteWinWidth, pDrawBottomBorder, pHighlightIndex) +// pQuoteTopScreenRow: The first line on the screen where quote lines are written +function createQuoteLineMenu(pQuoteTopScreenRow) { - var quoteLinesIndex = pQuoteLinesIndex; - var quoteLine = ""; // A line of text from gQuoteLines - var screenLine = console.screen_rows - pQuoteWinHeight + 2; - if (gQuoteLines.length > 0) - { - var color = ""; // The color to use when writing the text - var lineLength = 0; // Length of a quote line - while ((quoteLinesIndex < gQuoteLines.length) && (screenLine < console.screen_rows-1)) - { - quoteLine = getQuoteTextLine(quoteLinesIndex, pQuoteWinWidth); - // Go to the line on screen and display the quote line text. - console.gotoxy(gEditLeft, screenLine); - // If pHighlightIndex is valid, and if quoteLinesIndex matches - // pHighlightIndex, then use the highlight color for this quote line. - if ((pHighlightIndex != null) && (pHighlightIndex >= 0) && (pHighlightIndex < gQuoteLines.length)) - { - if (quoteLinesIndex == pHighlightIndex) - { - color = gQuoteLineHighlightColor; - quoteLine = quoteLine; - } - else - color = gQuoteWinTextColor; - } - else - { - color = gQuoteWinTextColor; - quoteLine = quoteLine; - } - // Write the quote line, and fill the rest of the line with spaces. - printf(gFormatStrWithAttr, color, quoteLine); + // Quote window parameters + const quoteBottomScreenRow = console.screen_rows - 2; + const quoteWinTopScreenRow = pQuoteTopScreenRow-1; + const quoteWinWidth = gEditRight - gEditLeft + 1; + var quoteWinInnerHeight = quoteBottomScreenRow - pQuoteTopScreenRow + 1; // # of quote lines in the quote window + + // Create the quote line window + var quoteLineMenu = new DDLightbarMenu(1, pQuoteTopScreenRow, console.screen_columns, quoteWinInnerHeight); + quoteLineMenu.scrollbarEnabled = false; + quoteLineMenu.borderEnabled = false; + quoteLineMenu.multiSelect = false; + quoteLineMenu.ampersandHotkeysInItems = false; + quoteLineMenu.wrapNavigation = false; + // Have the menu persist on screen when items are selected (with the Enter key) + quoteLineMenu.exitOnItemSelect = false; + quoteLineMenu.colors.itemColor = gQuoteWinTextColor; + quoteLineMenu.colors.selectedItemColor = gQuoteLineHighlightColor; + + // Add additional keypresses for quitting the menu's input loop + quoteLineMenu.AddAdditionalQuitKeys(CTRL_Q); + + // Change the menu's NumItems() and GetItem() function to reference + // the message list in this object rather than add the menu items + // to the menu + quoteLineMenu.NumItems = function() { + return gQuoteLines.length; + }; + quoteLineMenu.GetItem = function(pQuoteLineIdx) { + var menuItemObj = this.MakeItemWithRetval(-1); + if (pQuoteLineIdx >= 0 && pQuoteLineIdx < gQuoteLines.length) + { + // Note: this refers to the quote line menu object (DDLightbarMenu) + menuItemObj.text = getQuoteTextLine(pQuoteLineIdx, this.size.width); + menuItemObj.retval = pQuoteLineIdx; + } + return menuItemObj; + }; - ++quoteLinesIndex; - ++screenLine; - } - } - // Fill the remainder of the quote window area - for (; screenLine < console.screen_rows-1; ++screenLine) - { - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, gQuoteWinTextColor, ""); - } + // Set the quote menu's currently selected index to the next one available + quoteLineMenu.SetSelectedItemIdx(gQuoteLinesIndex == 0 ? 0 : gQuoteLinesIndex+1); - // If pDrawBottomBorder is true, then display the bottom border of the - // quote window. - if (pDrawBottomBorder) - { - console.gotoxy(gEditLeft, screenLine); - fpDrawQuoteWindowBottomBorder(gEditLeft, gEditRight); - } + return quoteLineMenu; } // This function returns a line of text from gQuoteLines, with "> " @@ -3271,21 +3021,21 @@ function displayQuoteWindowLines(pQuoteLinesIndex, pQuoteWinHeight, pQuoteWinWid // Return value: The line of text from gQuoteLines function getQuoteTextLine(pIndex, pMaxWidth) { - var textLine = ""; - if ((pIndex >= 0) && (pIndex < gQuoteLines.length)) - { - if (gUserSettings.useQuoteLineInitials) - { - if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0)) - textLine = gQuoteLines[pIndex].substr(0, pMaxWidth-1); - } - else - { - if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0)) - textLine = quote_msg(gQuoteLines[pIndex], pMaxWidth-1, gQuotePrefix); - } - } - return textLine; + var textLine = ""; + if ((pIndex >= 0) && (pIndex < gQuoteLines.length)) + { + if (gUserSettings.useQuoteLineInitials) + { + if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0)) + textLine = gQuoteLines[pIndex].substr(0, pMaxWidth-1); + } + else + { + if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0)) + textLine = quote_msg(gQuoteLines[pIndex], pMaxWidth-1, gQuotePrefix); + } + } + return textLine; } // This function deletes the current edit line. This function is called @@ -3300,49 +3050,50 @@ function getQuoteTextLine(pIndex, pMaxWidth) // currentWordLength: The length of the current word function doDeleteLine(pCurpos) { - // Construct the object that we'll be returning - var retObj = new Object(); - retObj.x = pCurpos.x; - retObj.y = pCurpos.y; - retObj.currentWordLength = 0; - - // Remove the current line from gEditLines. If we're on the last line, - // then we'll need to add a blank line to gEditLines. We'll also need - // to refresh the edit lines on the screen. - if (gEditLinesIndex == gEditLines.length-1) - { - // We're on the last line. Remove it & replace it with a new line. - gEditLines.splice(gEditLinesIndex, 1, new TextLine()); - // Refresh (clear) the line on the screen - displayEditLines(pCurpos.y, gEditLinesIndex, pCurpos.y, true, true); - console.gotoxy(gEditLeft, pCurpos.y); - printf(gFormatStr, ""); - } - else - { - // We weren't on the last line. Remove the current line and get the - // word length, and then refresh the message on the screen. - gEditLines.splice(gEditLinesIndex, 1); - displayEditLines(pCurpos.y, gEditLinesIndex, gEditBottom, true, true); - // Update the current word length - retObj.currentWordLength = getWordLength(gEditLinesIndex, 0); - - // If there is a line above the current line, then set its hardNewlineEnd - // to true. This is for a scenario where the user deletes a line in the - // middle of their message - If the user goes back up to the previous line - // and starts typing in the middle and SlyEdit has to wrap it to the next - // line, SlyEdit would basically remove a line without this change. - if (gEditLinesIndex > 0) - gEditLines[gEditLinesIndex-1].hardNewlineEnd = true; - } + // Construct the object that we'll be returning + var retObj = { + x: pCurpos.x, + y: pCurpos.y, + currentWordLength: 0 + }; + + // Remove the current line from gEditLines. If we're on the last line, + // then we'll need to add a blank line to gEditLines. We'll also need + // to refresh the edit lines on the screen. + if (gEditLinesIndex == gEditLines.length-1) + { + // We're on the last line. Remove it & replace it with a new line. + gEditLines.splice(gEditLinesIndex, 1, new TextLine()); + // Refresh (clear) the line on the screen + displayEditLines(pCurpos.y, gEditLinesIndex, pCurpos.y, true, true); + console.gotoxy(gEditLeft, pCurpos.y); + printf(gFormatStr, ""); + } + else + { + // We weren't on the last line. Remove the current line and get the + // word length, and then refresh the message on the screen. + gEditLines.splice(gEditLinesIndex, 1); + displayEditLines(pCurpos.y, gEditLinesIndex, gEditBottom, true, true); + // Update the current word length + retObj.currentWordLength = getWordLength(gEditLinesIndex, 0); + + // If there is a line above the current line, then set its hardNewlineEnd + // to true. This is for a scenario where the user deletes a line in the + // middle of their message - If the user goes back up to the previous line + // and starts typing in the middle and SlyEdit has to wrap it to the next + // line, SlyEdit would basically remove a line without this change. + if (gEditLinesIndex > 0) + gEditLines[gEditLinesIndex-1].hardNewlineEnd = true; + } - // Adjust global message parameters, make sure the cursor position is - // correct in retObj, and place the cursor where it's supposed to be. - gTextLineIndex = 0; - retObj.x = gEditLeft; - console.gotoxy(retObj.x, retObj.y); + // Adjust global message parameters, make sure the cursor position is + // correct in retObj, and place the cursor where it's supposed to be. + gTextLineIndex = 0; + retObj.x = gEditLeft; + console.gotoxy(retObj.x, retObj.y); - return retObj; + return retObj; } // Toggles insert mode between insert and overwrite mode and updates it @@ -3354,11 +3105,11 @@ function doDeleteLine(pCurpos) // The cursor will be returned here when finished. function toggleInsertMode(pCurpos) { - // Change gInsertMode, and then refresh it on the screen. - gInsertMode = inInsertMode() ? "OVR" : "INS"; - fpUpdateInsertModeOnScreen(gEditRight, gEditBottom, gInsertMode); - if ((pCurpos != null) && (typeof(pCurpos) != "undefined")) - console.gotoxy(pCurpos); + // Change gInsertMode, and then refresh it on the screen. + gInsertMode = inInsertMode() ? "OVR" : "INS"; + fpUpdateInsertModeOnScreen(gEditRight, gEditBottom, gInsertMode); + if ((pCurpos != null) && (typeof(pCurpos) != "undefined")) + console.gotoxy(pCurpos); } // Displays the contents of the gEditLines array, starting at a given @@ -3382,81 +3133,80 @@ function toggleInsertMode(pCurpos) function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRemainingScreenRows, pIgnoreEditAreaBuffer) { - // Make sure the array has lines in it, the given array index is valid, and - // that the given line # is valid. If not, then just return. - if ((gEditLines.length == 0) || (pArrayIndex < 0) || (pStartScreenRow < 1) || (pStartScreenRow > gEditBottom)) - return; + // Make sure the array has lines in it, the given array index is valid, and + // that the given line # is valid. If not, then just return. + if ((gEditLines.length == 0) || (pArrayIndex < 0) || (pStartScreenRow < 1) || (pStartScreenRow > gEditBottom)) + return; - // Choose which ending screen row to use for displaying text, - // pEndScreenRow or gEditBottom. - var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom); + // Choose which ending screen row to use for displaying text, + // pEndScreenRow or gEditBottom. + var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom); - // Display the message lines - console.print("n" + gTextAttrs); - var screenLine = pStartScreenRow; - var arrayIndex = pArrayIndex; - while ((screenLine <= endScreenRow) && (arrayIndex < gEditLines.length)) - { - // Print the text from the current line in gEditLines. Note: Lines starting - // with " >" are assumed to be quote lines - Display those lines with cyan - // color and the normal lines with gTextAttrs. - var color = gTextAttrs; - // Note: gEditAreaBuffer is also used in clearMsgAreaToBottom(). - if ((gEditAreaBuffer[screenLine] != gEditLines[arrayIndex].text) || pIgnoreEditAreaBuffer) - { - // Choose the quote line color or the normal color for the line, then - // display the line on the screen. - color = (isQuoteLine(gEditLines, arrayIndex) ? gQuoteLineColor : gTextAttrs); - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStrWithAttr, color, gEditLines[arrayIndex].text); - gEditAreaBuffer[screenLine] = gEditLines[arrayIndex].text; - } + // Display the message lines + console.print("\1n" + gTextAttrs); + var screenLine = pStartScreenRow; + var arrayIndex = pArrayIndex; + while ((screenLine <= endScreenRow) && (arrayIndex < gEditLines.length)) + { + // Print the text from the current line in gEditLines. Note: Lines starting + // with " >" are assumed to be quote lines - Display those lines with cyan + // color and the normal lines with gTextAttrs. + var color = gTextAttrs; + // Note: gEditAreaBuffer is also used in clearMsgAreaToBottom(). + if ((gEditAreaBuffer[screenLine] != gEditLines[arrayIndex].text) || pIgnoreEditAreaBuffer) + { + // Choose the quote line color or the normal color for the line, then + // display the line on the screen. + color = (isQuoteLine(gEditLines, arrayIndex) ? gQuoteLineColor : gTextAttrs); + console.gotoxy(gEditLeft, screenLine); + printf(gFormatStrWithAttr, color, gEditLines[arrayIndex].text); + gEditAreaBuffer[screenLine] = gEditLines[arrayIndex].text; + } - ++screenLine; - ++arrayIndex; - } - if (arrayIndex > 0) - --arrayIndex; - // incrementLineBeforeClearRemaining stores whether or not we - // should increment screenLine before clearing the remaining - // lines in the edit area. - var incrementLineBeforeClearRemaining = true; - // If the array index is valid, and if the current line is shorter - // than the edit area width, then place the cursor after the last - // character in the line. - if ((arrayIndex >= 0) && (arrayIndex < gEditLines.length) && - (gEditLines[arrayIndex] != undefined) && (gEditLines[arrayIndex].text != undefined)) - { - var lineLength = gEditLines[arrayIndex].length(); - if (lineLength < gEditWidth) - { - --screenLine; - console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLine); - } - else if ((lineLength == gEditWidth) || (lineLength == 0)) - incrementLineBeforeClearRemaining = false; - } - else - incrementLineBeforeClearRemaining = false; + ++screenLine; + ++arrayIndex; + } + if (arrayIndex > 0) + --arrayIndex; + // incrementLineBeforeClearRemaining stores whether or not we + // should increment screenLine before clearing the remaining + // lines in the edit area. + var incrementLineBeforeClearRemaining = true; + // If the array index is valid, and if the current line is shorter + // than the edit area width, then place the cursor after the last + // character in the line. + if ((arrayIndex >= 0) && (arrayIndex < gEditLines.length) && + (gEditLines[arrayIndex] != undefined) && (gEditLines[arrayIndex].text != undefined)) + { + var lineLength = gEditLines[arrayIndex].length(); + if (lineLength < gEditWidth) + { + --screenLine; + console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLine); + } + else if ((lineLength == gEditWidth) || (lineLength == 0)) + incrementLineBeforeClearRemaining = false; + } + else + incrementLineBeforeClearRemaining = false; - // Edge case: If the current screen line is below the last line, then - // clear the lines up until that point. - var clearRemainingScreenLines = (pClearRemainingScreenRows != null ? pClearRemainingScreenRows : true); - if (clearRemainingScreenLines && (screenLine <= endScreenRow)) - { - console.print("n" + gTextAttrs); - var screenLineBackup = screenLine; // So we can move the cursor back - clearMsgAreaToBottom(incrementLineBeforeClearRemaining ? screenLine+1 : screenLine, - pIgnoreEditAreaBuffer); - // Move the cursor back to the end of the current text line. - if (typeof(gEditLines[arrayIndex]) != "undefined") - console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLineBackup); - else - console.gotoxy(gEditLeft, screenLineBackup); - } + // Edge case: If the current screen line is below the last line, then + // clear the lines up until that point. + var clearRemainingScreenLines = (pClearRemainingScreenRows != null ? pClearRemainingScreenRows : true); + if (clearRemainingScreenLines && (screenLine <= endScreenRow)) + { + console.print("\1n" + gTextAttrs); + var screenLineBackup = screenLine; // So we can move the cursor back + clearMsgAreaToBottom(incrementLineBeforeClearRemaining ? screenLine+1 : screenLine, pIgnoreEditAreaBuffer); + // Move the cursor back to the end of the current text line. + if (typeof(gEditLines[arrayIndex]) != "undefined") + console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLineBackup); + else + console.gotoxy(gEditLeft, screenLineBackup); + } - // Make sure the correct color is set for the current line. - console.print(chooseEditColor()); + // Make sure the correct color is set for the current line. + console.print(chooseEditColor()); } // Clears the lines in the message area from a given line to the bottom. @@ -3468,34 +3218,34 @@ function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRem // By default, gEditAreaBuffer is always checked. function clearMsgAreaToBottom(pStartLine, pIgnoreEditAreaBuffer) { - for (var screenLine = pStartLine; screenLine <= gEditBottom; ++screenLine) - { - // Note: gEditAreaBuffer is also used in displayEditLines(). - if ((gEditAreaBuffer[screenLine].length > 0) || pIgnoreEditAreaBuffer) - { - console.gotoxy(gEditLeft, screenLine); - printf(gFormatStr, ""); - gEditAreaBuffer[screenLine] = ""; - } - } + for (var screenLine = pStartLine; screenLine <= gEditBottom; ++screenLine) + { + // Note: gEditAreaBuffer is also used in displayEditLines(). + if ((gEditAreaBuffer[screenLine].length > 0) || pIgnoreEditAreaBuffer) + { + console.gotoxy(gEditLeft, screenLine); + printf(gFormatStr, ""); + gEditAreaBuffer[screenLine] = ""; + } + } } // Returns whether or not the message is empty (gEditLines may have lines in // it, and this tests to see if they are all empty). function messageIsEmpty() { - var msgEmpty = true; - - for (var i = 0; i < gEditLines.length; ++i) - { - if (gEditLines[i].length() > 0) - { - msgEmpty = false; - break; - } - } + var msgEmpty = true; - return msgEmpty; + for (var i = 0; i < gEditLines.length; ++i) + { + if (gEditLines[i].length() > 0) + { + msgEmpty = false; + break; + } + } + + return msgEmpty; } // Displays a part of the message text in a rectangle on the screen. This @@ -4785,15 +4535,15 @@ function drawInitialCrossPostSelBoxTopBorder(pTopLeft, pWidth, pBorderColor, pTe function drawInitialCrossPostSelBoxBottomBorder(pBottomLeft, pWidth, pBorderColor, pMsgSubs) { - console.gotoxy(pBottomLeft); - console.print(pBorderColor + LOWER_LEFT_SINGLE + RIGHT_T_SINGLE + - "nhcb, cb, cPgUpb, cPgDnb, cFy)birst, cLy)bast, cEntery=b" - + (pMsgSubs ? "Toggle" : "Select") + ", cCtrl-Cnc/hQy=bEnd, c?y=bHelpn" - + pBorderColor + LEFT_T_SINGLE); - len = pWidth - 71; - for (var i = 0; i < len; ++i) - console.print(HORIZONTAL_SINGLE); - console.print(LOWER_RIGHT_SINGLE); + console.gotoxy(pBottomLeft); + console.print(pBorderColor + LOWER_LEFT_SINGLE + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + RIGHT_T_SINGLE + + "\1n\1h\1c\1b, \1c\1b, \1cPgUp\1b, \1cPgDn\1b, \1cHome\1b, \1cEnd\1b, \1cEnter\1y=\1b" + + (pMsgSubs ? "Toggle" : "Select") + ", \1cCtrl-C\1n\1c/\1hQ\1y=\1bEnd, \1c?\1y=\1bHelp\1n" + + pBorderColor + LEFT_T_SINGLE); + len = pWidth - 71; + for (var i = 0; i < len; ++i) + console.print(HORIZONTAL_SINGLE); + console.print(LOWER_RIGHT_SINGLE); } // Displays help text for cross-posting, for use in cross-post selection mode. // @@ -4809,20 +4559,20 @@ function displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight) if (typeof(displayCrossPostHelp.helpLines) == "undefined") { displayCrossPostHelp.helpLines = new Array(); - displayCrossPostHelp.helpLines.push("ncCross-posing allows you to post a message in more than one message"); + displayCrossPostHelp.helpLines.push("\1n\1cCross-posing allows you to post a message in more than one message"); displayCrossPostHelp.helpLines.push("area. To select areas for cross-posting, do the following:"); - displayCrossPostHelp.helpLines.push(" h1. ncChoose a message group from the list with the Enter key."); + displayCrossPostHelp.helpLines.push(" \1h1. \1n\1cChoose a message group from the list with the Enter key."); displayCrossPostHelp.helpLines.push(" Alternately, you may type the number of the message group."); - displayCrossPostHelp.helpLines.push(" h2. ncIn the list of message sub-boards that appears, toggle individual"); + displayCrossPostHelp.helpLines.push(" \1h2. \1n\1cIn the list of message sub-boards that appears, toggle individual"); displayCrossPostHelp.helpLines.push(" sub-boards with the Enter key. Alternately, you may type the"); displayCrossPostHelp.helpLines.push(" number of the message sub-board."); displayCrossPostHelp.helpLines.push("Message sub-boards that are toggled for cross-posting will include a"); - displayCrossPostHelp.helpLines.push("check mark (" + gConfigSettings.genColors.crossPostChk + CHECK_CHAR + "nc) in the sub-board list. Initially, your current message"); + displayCrossPostHelp.helpLines.push("check mark (" + gConfigSettings.genColors.crossPostChk + CHECK_CHAR + "\1n\1c) in the sub-board list. Initially, your current message"); displayCrossPostHelp.helpLines.push("sub-board is enabled by default. Also, your current message group is"); - displayCrossPostHelp.helpLines.push("marked with an asterisk (" + gConfigSettings.genColors.crossPostMsgGrpMark + "*nc)."); - displayCrossPostHelp.helpLines.push("To navigate the list, you may use the up & down arrow keys, N to go to"); - displayCrossPostHelp.helpLines.push("the next page, P to go to the previous page, F to go to the first page,"); - displayCrossPostHelp.helpLines.push("and L to go to the last page. Ctrl-C, Q, and ESC end the selection."); + displayCrossPostHelp.helpLines.push("marked with an asterisk (" + gConfigSettings.genColors.crossPostMsgGrpMark + "*\1n\1c)."); + displayCrossPostHelp.helpLines.push("To navigate the list, you may use the up & down arrow keys, PageUp and"); + displayCrossPostHelp.helpLines.push("PageDown to go to the previous & next page, Home to go to the first"); + displayCrossPostHelp.helpLines.push("page, and End to go to the last page. To end: Ctrl-C, Q, or ESC."); } // Display the help text @@ -4850,7 +4600,7 @@ function displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight) if (screenRow < selBoxLowerRight.y) { var printfStr = "%-" + selBoxInnerWidth + "s"; - console.print("n"); + console.print("\1n"); for (; screenRow < selBoxLowerRight.y; ++screenRow) { console.gotoxy(selBoxUpperLeft.x+1, screenRow); @@ -4905,11 +4655,11 @@ function doCrossPosting(pOriginalCurpos) // Position the cursor after the "Cross-posting: " text in the border and // write the "Choose group" text console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y); - console.print("n" + gConfigSettings.genColors.listBoxBorderText + "Choose group"); + console.print("\1n" + gConfigSettings.genColors.listBoxBorderText + "Choose group"); // Re-write the border characters to overwrite the message group name grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25); // Write the updated border character(s) - console.print("n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE); + console.print("\1n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE); if (grpDesc.length > 3) { var numChars = grpDesc.length - 3; @@ -4990,1052 +4740,366 @@ function doCrossPosting(pOriginalCurpos) selBoxWidth, gConfigSettings.genColors.listBoxBorder, false); - // Write the message groups - var pageNum = 1; - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // Move the cursor to the inner upper-left corner of the selection box - var curpos = new Object(); // Current cursor position - curpos.x = selBoxUpperLeft.x+1; - curpos.y = selBoxUpperLeft.y+1; - console.gotoxy(curpos); + // Input loop - Prompting for sub-boards + // Note: promptUserForCrossPostSubBoardCodes() will add the user's + // selected sub-board codes to gCrossPostMsgSubs. + var promptRetObj = promptUserForCrossPostSubBoardCodes(selBoxUpperLeft, selBoxLowerRight); + + // We're done selecting message areas for cross-posting. + // Erase the message area selection rectangle by re-drawing the message text. + // Then, move the cursor back to where it was when we started the message + // area selection. + displayMessageRectangle(selBoxUpperLeft.x, selBoxUpperLeft.y, selBoxWidth, + selBoxHeight, editLineIndexAtSelBoxTopRow, true); + console.gotoxy(origStartingCurpos); +} +// Performs the input loop using DDLightbarMenu objects to get sub-board codes +// for cross-posting from the user. +// +// Parameters: +// pSelBoxUpperLeft: An object containing x & y coordinates for the upper-left of the selection box with borders +// pSelBoxLowerRight: An object containing x & y coordinates for the lower-right of the selection box with borders +// +// Return value: An object containing the following properties: +// lastUserInput: The last input from the user +function promptUserForCrossPostSubBoardCodes(pSelBoxUpperLeft, pSelBoxLowerRight) +{ + var retObj = { + lastUserInput: "" + }; - // User input loop - var userInput = null; - var continueChoosingMsgArea = true; - while (continueChoosingMsgArea) + // Set up an object containing the colors to use for the items in the group/sub-board lists + var menuListColors = { + areaMark: "\1g\1h", + areaNum: "\1n\1w\1h", + desc: "\1n\1c", + areaNumHighlight: "\1w\1h", + descHighlight: "\1c", + bkgHighlight: "\1" + "4" + }; + + // Calculate the selection box width & height, with borders + var selBoxWidth = pSelBoxLowerRight.x - pSelBoxUpperLeft.x + 1; + var selBoxHeight = pSelBoxLowerRight.y - pSelBoxUpperLeft.y + 1; + // Don't let the box's height be more than 17 characters. + if (selBoxHeight > 17) { - pageNum = calcPageNum(topMsgGrpIndex, selBoxInnerHeight); + selBoxLowerRight.y = selBoxUpperLeft.y + 16; // For a height of 17 characters + selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1; + } + // Inner size of the box (for the text menu items) + var selBoxInnerWidth = selBoxWidth - 2; + var selBoxInnerHeight = selBoxHeight - 2; - // Get a key from the user (upper-case) and take action based upon it. - userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings); - switch (userInput) + // The starting column & row for the text items in the menu + var listStartCol = pSelBoxUpperLeft.x+1; + var listStartRow = pSelBoxUpperLeft.y+1; + + // The number of milliseconds to pause after displaying the error message + // that the user isn't allowed to post in a sub-board (due to ARS). + var cantPostErrPauseMS = 2000; + + // Create & display the message group menu for the user to choose from + var msgGrpMenu = createCrossPostMsgGrpMenu(listStartCol, listStartRow, selBoxInnerWidth, selBoxInnerHeight, menuListColors); + var continueOn = true; + while (continueOn) + { + // Show the message group menu and get the chosen item from the user + var grpIdx = msgGrpMenu.GetVal(); + retObj.lastUserInput = msgGrpMenu.lastUserInput; + if (msgGrpMenu.lastUserInput == "?") { - case KEY_UP: // Move up one message group in the list - if (selectedGrpIndex > 0) - { - // If the previous group index is on the previous page, then - // display the previous page. - var previousGrpIndex = selectedGrpIndex - 1; - if (previousGrpIndex < topMsgGrpIndex) - { - // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgGrpIndex -= numItemsPerPage; - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - ListScreenfulOfMsgGrps(topMsgGrpIndex, previousGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // We'll want to move the cursor to the leftmost character - // of the selected line. - curpos.x = selBoxUpperLeft.x+1; - curpos.y = selBoxUpperLeft.y+selBoxInnerHeight; - } - else - { - // Display the current line un-highlighted - console.gotoxy(selBoxUpperLeft.x+1, curpos.y); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false); - // Display the previous line highlighted - curpos.x = selBoxUpperLeft.x+1; - --curpos.y; - console.gotoxy(curpos); - writeMsgGroupLine(previousGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true); - } - selectedGrpIndex = previousGrpIndex; - console.gotoxy(curpos); // Move the cursor into place where it should be - } - break; - case KEY_DOWN: // Move down one message group in the list - if (selectedGrpIndex < msg_area.grp_list.length - 1) - { - // If the next group index is on the next page, then display - // the next page. - var nextGrpIndex = selectedGrpIndex + 1; - if (nextGrpIndex > bottomMsgGrpIndex) - { - // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgGrpIndex += numItemsPerPage; - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - ListScreenfulOfMsgGrps(topMsgGrpIndex, nextGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // We'll want to move the cursor to the leftmost character - // of the selected line. - curpos.x = selBoxUpperLeft.x+1; - curpos.y = selBoxUpperLeft.y+1; - } - else - { - // Display the current line un-highlighted - console.gotoxy(selBoxUpperLeft.x+1, curpos.y); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false); - // Display the next line highlighted - curpos.x = selBoxUpperLeft.x+1; - ++curpos.y; - console.gotoxy(curpos); - writeMsgGroupLine(nextGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true); - } - selectedGrpIndex = nextGrpIndex; - console.gotoxy(curpos); // Move the cursor into place where it should be - } - break; - case KEY_HOME: // Go to the top message group on the screen - if (selectedGrpIndex > topMsgGrpIndex) - { - // Display the current line un-highlighted, adjust - // selectedGrpIndex, then display the new line - // highlighted. - console.gotoxy(selBoxUpperLeft.x+1, curpos.y); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false); - selectedGrpIndex = topMsgGrpIndex; - curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true); - console.gotoxy(curpos); - } - break; - case KEY_END: // Go to the bottom message group on the screen - if (selectedGrpIndex < bottomMsgGrpIndex) - { - // Display the current line un-highlighted, adjust - // selectedGrpIndex, then display the new line - // highlighted. - console.gotoxy(selBoxUpperLeft.x+1, curpos.y); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false); - selectedGrpIndex = bottomMsgGrpIndex; - curpos.x = selBoxUpperLeft.x + 1; - curpos.y = selBoxUpperLeft.y + (bottomMsgGrpIndex-topMsgGrpIndex+1); - console.gotoxy(curpos); - writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true); - console.gotoxy(curpos); - } - break; - case KEY_PAGE_DOWN: // Go to the next page - var nextPageTopIndex = topMsgGrpIndex + numItemsPerPage; - if (nextPageTopIndex < msg_area.grp_list.length) - { - // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgGrpIndex = nextPageTopIndex; - pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - selectedGrpIndex = topMsgGrpIndex; - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case KEY_PAGE_UP: // Go to the previous page - var prevPageTopIndex = topMsgGrpIndex - numItemsPerPage; - if (prevPageTopIndex >= 0) - { - // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgGrpIndex = prevPageTopIndex; - pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - selectedGrpIndex = topMsgGrpIndex; - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case 'F': // Go to the first page - if (topMsgGrpIndex > 0) - { - topMsgGrpIndex = 0; - pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - selectedGrpIndex = 0; - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case 'L': // Go to the last page - if (topMsgGrpIndex < topIndexForLastPage) - { - topMsgGrpIndex = topIndexForLastPage; - pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); - bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); - selectedGrpIndex = topIndexForLastPage; - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1, - msgGrpFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case CTRL_C: // Quit (Ctrl-C is the cross-post hotkey) - case KEY_ESC: // Quit - case "Q": // Quit - continueChoosingMsgArea = false; - break; - case KEY_ENTER: // Select the currently-highlighted message group - // Store the current cursor position for later, then show the - // sub-boards in the chosen message group and let the user - // toggle ones for cross-posting. - var selectCurrentGrp_originalCurpos = curpos; - var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(selectedGrpIndex, - selBoxUpperLeft, selBoxLowerRight, selBoxWidth, - selBoxHeight, selBoxInnerWidth, selBoxInnerHeight); - // If the user toggled some sub-boards... - if (selectMsgAreaRetObj.subBoardsToggled) - { - // TODO: Does anything need to be done here? - } - - // Update the Enter action text in the bottom border to say "Select" - // (instead of "Toggle"). - console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y); - console.print("\1n\1h\1bSelect"); - // Refresh the top border of the selection box, refresh the list of - // message groups in the box, and move the cursor back to its original - // position. - reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, selectedGrpIndex); - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, - selBoxLowerRight.x-1, msgGrpFieldLen, true); - console.gotoxy(selectCurrentGrp_originalCurpos); - break; - case '?': // Display cross-post help - displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight); - console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y-1); - console.pause(); - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, - selBoxLowerRight.x-1, msgGrpFieldLen, true); - console.gotoxy(curpos); - 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); - // We want to write the prompt text only if the first digit entered - // by the user is an ambiguous message group number (i.e., if - // the first digit is 2 and there's a message group # 2 and 20). - var writePromptText = (msg_area.grp_list.length >= +userInput * 10); - if (writePromptText) - { - console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y); - printf("\1n\1cChoose group #:%" + +(selBoxInnerWidth-15) + "s", ""); - console.gotoxy(selBoxUpperLeft.x+17, selBoxLowerRight.y); - console.print("\1h"); - } - else - console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y); - userInput = console.getnum(msg_area.grp_list.length); - - // Re-draw the bottom border of the selection box - if (writePromptText) - { - drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y }, - selBoxWidth, gConfigSettings.genColors.listBoxBorder, - false); - } - else - { - console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y); - console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE); - } - - // If the user made a selection, then let them choose a - // sub-board from the group. - if (userInput > 0) - { - // Show the sub-boards in the chosen message group and - // let the user toggle ones for cross-posting. - // userInput-1 is the group index - var chosenGrpIndex = userInput - 1; - var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(chosenGrpIndex, - selBoxUpperLeft, selBoxLowerRight, selBoxWidth, - selBoxHeight, selBoxInnerWidth, selBoxInnerHeight); - // If the user chose a sub-board, then set bbs.curgrp and - // bbs.cursub, and don't continue the input loop anymore. - if (selectMsgAreaRetObj.subBoardsToggled) - { - // TODO: Does anything need to be done here? - } - // Update the Enter action text in the bottom border to say "Select" - // (instead of "Toggle"). - console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y); - console.print("\1n\1h\1bSelect"); - // Refresh the top border of the selection box - reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, chosenGrpIndex); - } + displayCrossPostHelp(pSelBoxUpperLeft, pSelBoxLowerRight); + console.gotoxy(listStartCol, listStartRow); + consolePauseWithoutText(); + } + else if (msgGrpMenu.lastUserInput == "Q" || msgGrpMenu.lastUserInput == CTRL_C) + { + continueOn = false; + } + else if (typeof(grpIdx) == "number") + { + // Get an object of selected sub-board indexes that we can pass to + // the sub-board menu. + var selectedItemIndexes = {}; + for (var subIdxStr in msg_area.grp_list[grpIdx].sub_list) + { + var subIdx = +subIdxStr; + if (gCrossPostMsgSubs.subCodeExists(msg_area.grp_list[grpIdx].sub_list[subIdx].code)) + selectedItemIndexes[subIdx] = true; + } - // Refresh the list of message groups in the box and move the - // cursor back to its original position. - ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1, - selBoxUpperLeft.x+1, selBoxLowerRight.y-1, - selBoxLowerRight.x-1, msgGrpFieldLen, true); - console.gotoxy(originalCurpos); - } - break; + // Create the sub-board menu and let the user make a selection. + var subBoardMenu = createCrossPostSubBoardMenu(grpIdx, listStartCol, listStartRow, selBoxInnerWidth, selBoxInnerHeight, menuListColors); + // Get the selection of sub-boards from the user + var selectedSubBoards = subBoardMenu.GetVal(true, selectedItemIndexes); + retObj.lastUserInput = subBoardMenu.lastUserInput; + if (subBoardMenu.lastUserInput == "?") + { + displayCrossPostHelp(pSelBoxUpperLeft, pSelBoxLowerRight); + console.gotoxy(listStartCol, listStartRow); + consolePauseWithoutText(); + } + else if (subBoardMenu.lastUserInput == "Q" || subBoardMenu.lastUserInput == CTRL_C) + { + // Do nothing + } + // If the user selected some sub-boards, then add them to gCrossPostMsgSubs + if (selectedSubBoards != null && selectedSubBoards.length > 0) + { + // We want to ensure we only use the sub-boards the user selected. In case the + // user has gone into the same menu multiple times and selected and un-selected + // some, this loop goes through all sub-boards in the current message group and + // removes them from the selection, then adds the ones the user just selected. + for (var subIdx in msg_area.grp_list[grpIdx].sub_list) + gCrossPostMsgSubs.remove(msg_area.grp_list[grpIdx].sub_list[subIdx].code); + for (var idx in selectedSubBoards) + gCrossPostMsgSubs.add(selectedSubBoards[idx]); + } } + else + continueOn = false; } - // We're done selecting message areas for cross-posting. - // Erase the message area selection rectangle by re-drawing the message text. - // Then, move the cursor back to where it was when we started the message - // area selection. - displayMessageRectangle(selBoxUpperLeft.x, selBoxUpperLeft.y, selBoxWidth, - selBoxHeight, editLineIndexAtSelBoxTopRow, true); - console.gotoxy(origStartingCurpos); + return retObj; } -// Displays a screenful of message groups, for the cross-posting -// interface. +// Creates the DDLightbarMenu for use with choosing a message group for +// cross-posting. Does not use borders, so that the border display can be +// customized by the caller. // // Parameters: -// pStartIndex: The message group index to start at (0-based) -// pSelectedIndex: The index of the currently-selected message group -// pStartScreenRow: The row on the screen to start at (1-based) -// pStartScreenCol: The column on the screen to start at (1-based) -// pEndScreenRow: The row on the screen to end at (1-based) -// pEndScreenCol: The column on the screen to end at (1-based) -// pMsgGrpFieldLen: The length to use for the group number field -// 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 ListScreenfulOfMsgGrps(pStartIndex, pSelectedIndex, pStartScreenRow, - pStartScreenCol, pEndScreenRow, pEndScreenCol, - pMsgGrpFieldLen, pBlankToEndRow) +// pListStartCol: The column on the screen where the list thould start +// pListStartRow: The row on the screen where the list should start +// pListStartRow should be this.areaChangeHdrLines.length + 2 +// pListWidth: The width of the list on the screen (without borders; effectively the item text width) +// pListHeight: The width of the list on the screen (without borders) +// pColorObj: Optional - An object containing color/attribute codes for the various parts of +// the message group items in the menu. Needs the following properties: +// areaMark: For the selected group +// areaNum: For the group number +// desc: For the group description +// areaNumHighlight: For the group number for the selected item +// descHighlight: For the group descripton for the selected item +// bkgHighlight: The background color/attribute for the selected item +// +// Return value: A DDLightbarMenu object for choosing a message group +function createCrossPostMsgGrpMenu(pListStartCol, pListStartRow, pListWidth, pListHeight, pColorObj) { - // If the parameters are invalid, then just return. - if ((typeof(pStartIndex) != "number") || (typeof(pSelectedIndex) != "number") || - (typeof(pStartScreenRow) != "number") || (typeof(pStartScreenCol) != "number") || - (typeof(pEndScreenRow) != "number") || (typeof(pEndScreenCol) != "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 ((pStartScreenCol < 1) || (pStartScreenCol > console.screen_columns)) - return; - if ((pEndScreenCol < 1) || (pEndScreenCol > console.screen_columns)) - return; - - // If pStartScreenRow is greater than pEndScreenRow, then swap them. - // Do the same with pStartScreenCol and pEndScreenCol. - if (pStartScreenRow > pEndScreenRow) - { - var temp = pStartScreenRow; - pStartScreenRow = pEndScreenRow; - pEndScreenRow = temp; - } - if (pStartScreenCol > pEndScreenCol) + var numGrpsLength = msg_area.grp_list.length.toString().length; + + var msgGrpMenu = new DDLightbarMenu(pListStartCol, pListStartRow, pListWidth, pListHeight); + msgGrpMenu.scrollbarEnabled = true; + msgGrpMenu.borderEnabled = false; + msgGrpMenu.multiSelect = false; + msgGrpMenu.ampersandHotkeysInItems = false; + msgGrpMenu.wrapNavigation = false; + // Add additional keypresses for quitting the menu's input loop so we can + // respond to these keys + // ? = Help + // Q: Exit + // Ctrl-C: Exit + msgGrpMenu.AddAdditionalQuitKeys("?Qq" + CTRL_C); + + // Description length (for the color indexes & printf string) + var descLen = pListWidth - numGrpsLength - 2; // -2 for the possible * and the space + // If we actually need a scrollbar, then decrease descLen by 1 more + if (msg_area.grp_list.length > pListHeight) + --descLen; + // Colors + if (pColorObj != null) { - var temp = pStartScreenCol; - pStartScreenCol = pEndScreenCol; - pEndScreenCol = temp; + // Start & end indexes for the various items in each mssage group list row + // Selected mark, group#, description, # sub-boards + var msgGrpListIdxes = { + markCharStart: 0, + markCharEnd: 1, + grpNumStart: 1, + grpNumEnd: 2 + (+numGrpsLength) + }; + msgGrpListIdxes.descStart = msgGrpListIdxes.grpNumEnd; + msgGrpListIdxes.descEnd = msgGrpListIdxes.descStart + descLen; + + msgGrpMenu.SetColors({ + itemColor: [{start: msgGrpListIdxes.markCharStart, end: msgGrpListIdxes.markCharEnd, attrs: pColorObj.areaMark}, + {start: msgGrpListIdxes.grpNumStart, end: msgGrpListIdxes.grpNumEnd, attrs: pColorObj.areaNum}, + {start: msgGrpListIdxes.descStart, end: msgGrpListIdxes.descEnd, attrs: pColorObj.desc}], + selectedItemColor: [{start: msgGrpListIdxes.markCharStart, end: msgGrpListIdxes.markCharEnd, attrs: pColorObj.areaMark + pColorObj.bkgHighlight}, + {start: msgGrpListIdxes.grpNumStart, end: msgGrpListIdxes.grpNumEnd, attrs: pColorObj.areaNumHighlight + pColorObj.bkgHighlight}, + {start: msgGrpListIdxes.descStart, end: msgGrpListIdxes.descEnd, attrs: pColorObj.descHighlight + pColorObj.bkgHighlight}] + }); } - // 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; - - // Go to the specified screen row, and display the message group information. - var textWidth = pEndScreenCol - pStartScreenCol + 1; - var row = pStartScreenRow; - var grpIndex = pStartIndex; - for (; grpIndex < onePastEndIndex; ++grpIndex) - { - console.gotoxy(pStartScreenCol, row++); - // The 4th parameter to writeMsgGroupLine() is whether or not to - // write the message group with highlight colors. - writeMsgGroupLine(grpIndex, textWidth, pMsgGrpFieldLen, (grpIndex == pSelectedIndex)); - } + // Generate a printf string for the message group items + // *[# ] Description + var printfStr = "%s%" + +numGrpsLength + "d %-" + +descLen + "s"; - // 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) + // Change the menu's NumItems() and GetItem() function to reference + // the message list in this object rather than add the menu items + // to the menu + msgGrpMenu.NumItems = function() { + return msg_area.grp_list.length; + }; + msgGrpMenu.GetItem = function(pGrpIndex) { + var menuItemObj = this.MakeItemWithRetval(-1); + if ((pGrpIndex >= 0) && (pGrpIndex < msg_area.grp_list.length)) { - console.print("\1n"); - var areaWidth = pEndScreenCol - pStartScreenCol + 1; - var formatStr = "%-" + areaWidth + "s"; - for (; screenRow <= pEndScreenRow; ++screenRow) - { - console.gotoxy(pStartScreenCol, screenRow) - printf(formatStr, ""); - } + var markColText = (((typeof(bbs.curgrp) == "number") && (pGrpIndex == msg_area.sub[bbs.cursub_code].grp_index)) ? "*" : " "); + var grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, descLen); + menuItemObj.text = format(printfStr, markColText, +(pGrpIndex+1), grpDesc); + menuItemObj.text = strip_ctrl(menuItemObj.text); + menuItemObj.retval = pGrpIndex; } - } -} -// Writes a message group information line (for choosing a message group -// for cross-posing). -// -// Parameters: -// pGrpIndex: The index of the message group to write (assumed to be valid) -// pTextWidth: The maximum text width -// pMsgGrpFieldLen: The length to use for the group number field -// pHighlight: Boolean - Whether or not to write the line highlighted. -function writeMsgGroupLine(pGrpIndex, pTextWidth, pMsgGrpFieldLen, pHighlight) -{ - if ((typeof(pGrpIndex) != "number") || (typeof(pTextWidth) != "number")) - return; - // Build a printf format string - var grpDescLen = pTextWidth - pMsgGrpFieldLen - 2; - var printfStr = "\1n"; - if (pHighlight) - { - printfStr += gConfigSettings.genColors.crossPostMsgGrpMarkHighlight + "%1s" - + gConfigSettings.genColors.crossPostMsgAreaNumHighlight + "%" + pMsgGrpFieldLen - + "d " + gConfigSettings.genColors.crossPostMsgAreaDescHighlight + "%-" - + grpDescLen + "s"; - } - else - { - printfStr += gConfigSettings.genColors.crossPostMsgGrpMark + "%1s" - + gConfigSettings.genColors.crossPostMsgAreaNum + "%" + pMsgGrpFieldLen + "d " - + gConfigSettings.genColors.crossPostMsgAreaDesc + "%-" + grpDescLen + "s"; - } + return menuItemObj; + }; - // Write the message group information line - var markChar = (pGrpIndex == gMsgAreaInfo.grpIndex ? "*" : " "); - printf(printfStr, markChar, +(pGrpIndex+1), msg_area.grp_list[pGrpIndex].description.substr(0, grpDescLen)); + // Set the currently selected item to the current group + msgGrpMenu.SetSelectedItemIdx(msg_area.sub[bbs.cursub_code].grp_index); + + return msgGrpMenu; } -// For cross-posting: Lets the user choose a sub-board within a message group +// Creates the DDLightbarMenu for use with choosing a sub-board for +// cross-posting // // Parameters: -// pGrpIndex: The index of the message group to choose from. -// pSelBoxUpperLeft: An object containing the following values: -// x: The horizontal coordinate (column) of the selection box's -// upper-left corner -// y: The vertical coordinate (row) of the selection box's -// upper-left corner -// pSelBoxLowerRight: An object containing the following values: -// x: The horizontal coordinate (column) of the selection box's -// lower-right corner -// y: The vertical coordinate (row) of the selection box's -// lower-right corner -// pSelBoxWidth: The width of the selection box -// pSelBoxHeight: The height of the selection box -// pSelBoxInnerWidth: The inner width of the selection box (inside the borders) -// pSelBoxInnerHeight: The inner height of the selection box (inside the borders) +// pGrpIdx: The index of the message group +// pListStartCol: The column on the screen where the list thould start +// pListStartRow: The row on the screen where the list should start +// pListStartRow should be this.areaChangeHdrLines.length + 2 +// pListWidth: The width of the list on the screen (without borders; effectively the item text width) +// pListHeight: The width of the list on the screen (without borders) +// pColorObj: Optional - An object containing color/attribute codes for the various parts of +// the message sub-board items in the menu. Needs the following properties: +// areaMark: For the selected sub-board +// areaNum: For the sub-board number +// desc: For the sub-board description +// areaNumHighlight: For the sub-board number for the selected item +// descHighlight: For the sub-board descripton for the selected item +// bkgHighlight: The background color/attribute for the selected item // -// Return value: An object containing the following values: -// subBoardsToggled: Boolean - Whether or not any sub-boards were toggled -function crossPosting_selectSubBoardInGrp(pGrpIndex, pSelBoxUpperLeft, pSelBoxLowerRight, - pSelBoxWidth, pSelBoxHeight, pSelBoxInnerWidth, - pSelBoxInnerHeight) +// Return value: A DDLightbarMenu object for choosing a sub-board within +// the given message group +function createCrossPostSubBoardMenu(pGrpIdx, pListStartCol, pListStartRow, pListWidth, pListHeight, pColorObj) { - // Create the return object with default values - var retObj = { - subBoardsToggled: false - }; - - // Check the parameters and return if any are invalid - if ((typeof(pGrpIndex) != "number") || (typeof(pSelBoxWidth) != "number") || - (typeof(pSelBoxHeight) != "number") || (typeof(pSelBoxInnerWidth) != "number") || - (typeof(pSelBoxInnerHeight) != "number") || (typeof(pSelBoxUpperLeft) != "object") || - (typeof(pSelBoxLowerRight) != "object") || (typeof(pSelBoxUpperLeft.x) != "number") || - (typeof(pSelBoxUpperLeft.y) != "number") || (typeof(pSelBoxLowerRight.x) != "number") || - (typeof(pSelBoxLowerRight.y) != "number")) - { - return retObj; - } - - // Clear the text inside the selection box - console.print("\1n"); - var printfStr = "%" + pSelBoxInnerWidth + "s"; - for (var rowOffset = 1; rowOffset <= pSelBoxInnerHeight; ++rowOffset) + var numSubsLength = msg_area.grp_list[pGrpIdx].sub_list.length.toString().length; + + var subBoardMenu = new DDLightbarMenu(pListStartCol, pListStartRow, pListWidth, pListHeight); + subBoardMenu.scrollbarEnabled = true; + subBoardMenu.borderEnabled = false; + subBoardMenu.multiSelect = true; + subBoardMenu.ampersandHotkeysInItems = false; + subBoardMenu.wrapNavigation = false; + // Add additional keypresses for quitting the menu's input loop so we can + // respond to these keys + // ? = Help + subBoardMenu.AddAdditionalQuitKeys("?"); + // Add q, Q, and Ctrl-C as keys to quit out and select the sub-board(s) (instead of quit & abort) + subBoardMenu.AddAdditionalSelectItemKeys("qQ" + CTRL_C); + + // Description length (for the color indexes & printf string) + var descLen = pListWidth - numSubsLength - 2; // -2 for the possible * and the space + // If we actually need a scrollbar, then decrease descLen by 1 more + if (msg_area.grp_list.length > pListHeight) + --descLen; + // Colors + if (pColorObj != null) { - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+rowOffset); - printf(printfStr, ""); + // Start & end indexes for the various items in each mssage group list row + // Selected mark, group#, description, # sub-boards + var subBoardListIdxes = { + markCharStart: 0, + markCharEnd: 1, + subNumStart: 1, + subNumEnd: 2 + numSubsLength + }; + subBoardListIdxes.descStart = subBoardListIdxes.subNumEnd; + subBoardListIdxes.descEnd = subBoardListIdxes.descStart + descLen; + + subBoardMenu.SetColors({ + itemColor: [{start: subBoardListIdxes.markCharStart, end: subBoardListIdxes.markCharEnd, attrs: pColorObj.areaMark}, + {start: subBoardListIdxes.subNumStart, end: subBoardListIdxes.subNumEnd, attrs: pColorObj.areaNum}, + {start: subBoardListIdxes.descStart, end: subBoardListIdxes.descEnd, attrs: pColorObj.desc}], + selectedItemColor: [{start: subBoardListIdxes.markCharStart, end: subBoardListIdxes.markCharEnd, attrs: pColorObj.areaMark + pColorObj.bkgHighlight}, + {start: subBoardListIdxes.subNumStart, end: subBoardListIdxes.subNumEnd, attrs: pColorObj.areaNumHighlight + pColorObj.bkgHighlight}, + {start: subBoardListIdxes.descStart, end: subBoardListIdxes.descEnd, attrs: pColorObj.descHighlight + pColorObj.bkgHighlight}] + }); } - // If there are no sub-boards in the given message group, then show - // an error and return. - if (msg_area.grp_list[pGrpIndex].sub_list.length == 0) - { - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+1); - console.print("\1y\1hThere are no message areas in the chosen group."); - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+2); - console.pause(); - return retObj; - } - - // This function returns the index of the bottommost message sub-board that - // can be displayed on the screen. - // - // Parameters: - // pTopSubIndex: The index of the topmost message sub-board displayed on screen - // pNumItemsPerPage: The number of items per page - function getBottommostSubBoardIndex(pTopSubIndex, pNumItemsPerPage) - { - var bottomSubIndex = pTopSubIndex + pNumItemsPerPage - 1; - // If bottomSubIndex is beyond the last index, then adjust it. - if (bottomSubIndex >= msg_area.grp_list[pGrpIndex].sub_list.length) - bottomSubIndex = msg_area.grp_list[pGrpIndex].sub_list.length - 1; - return bottomSubIndex; - } + // Generate a printf string for the sub-boards + // *[# ] Description + var printfStr = "%s%" + +numSubsLength + "d %-" + +descLen + "s"; - // This function writes the error message that the user can't post in a - // message sub-board, pauses, then refreshes the bottom border of the - // selection box. - // - // Parameters: - // pX: The column of the lower-right corner of the selection box - // pY: The row of the lower-right corner of the selection box - // pSubBoardNum: The number of the sub-board (1-based) - // pSelBoxWidth: The width of the selection box - // pSelBoxInnerWidth: The width of the selection box inside the left & right borders - // pPauseMS: The number of millisecons to pause after displaying the error message - // pCurpos: The position of the cursor before calling this function - function writeCantPostErrMsg(pX, pY, pSubBoardNum, pSelBoxWidth, pSelBoxInnerWidth, pPauseMS, pCurpos) - { - var cantPostErrMsg = "You're not allowed to post in area " + pSubBoardNum + "."; - console.gotoxy(pX+1, pY); - printf("\1n\1h\1y%-" + pSelBoxInnerWidth + "s", cantPostErrMsg); - console.gotoxy(pX+cantPostErrMsg.length+1, pY); - mswait(pPauseMS); - // Refresh the bottom border of the selection box - drawInitialCrossPostSelBoxBottomBorder({ x: pX, y: pY }, pSelBoxWidth, - gConfigSettings.genColors.listBoxBorder, true); - console.gotoxy(pCurpos); - } - - // Update the text in the top border of the selection box to include - // the message area description - //�Cross-posting: Choose group - //�Cross-posting: Areas in - console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y); - var grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25); - console.print("\1n" + gConfigSettings.genColors.listBoxBorderText + "Areas in " + grpDesc); - // Write the updated border character(s) - console.print("\1n" + gConfigSettings.genColors.listBoxBorder); - // If the length of the group description is shorter than the remaining text - // the selection box border, then draw horizontal lines to fill in the gap. - if (grpDesc.length < 3) - { - - var numChars = 3 - grpDesc.length; - for (var i = 0; i < numChars; ++i) - console.print(HORIZONTAL_SINGLE); - } - console.print(LEFT_T_SINGLE); - - // Update the Enter action text in the bottom border to say "Toggle" - // (instead of "Select"). - console.gotoxy(pSelBoxUpperLeft.x+41, pSelBoxLowerRight.y); - console.print("\1n\1h\1bToggle"); - - // Variables for keeping track of the message group/area list - var topMsgSubIndex = 0; // The index of the message sub-board at the top of the list - // Figure out the index of the last message group to appear on the screen. - var bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, pSelBoxInnerHeight); - var numPages = Math.ceil(msg_area.grp_list[pGrpIndex].sub_list.length / pSelBoxInnerHeight); - var numItemsPerPage = pSelBoxInnerHeight; - var topIndexForLastPage = (pSelBoxInnerHeight * numPages) - pSelBoxInnerHeight; - var selectedMsgSubIndex = 0; // The currently-selected message sub-board index - // subNumFieldLen will store the length to use for the sub-board numbers in the list. - // It should be able to accommodate the highest message sub-board number in the - // group. - var subNumFieldLen = msg_area.grp_list[pGrpIndex].sub_list.length.toString().length; - // The number of milliseconds to pause after displaying the error message - // that the user isn't allowed to post in a sub-board (due to ARS). - var cantPostErrPauseMS = 2000; - - // Write the sub-boards - var pageNum = 1; - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, pSelBoxUpperLeft.y+1, - pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, pSelBoxLowerRight.x-1, - subNumFieldLen, true); - // Move the cursor to the inner upper-left corner of the selection box - var curpos = { // Current cursor position - x: pSelBoxUpperLeft.x+1, - y: pSelBoxUpperLeft.y+1 + // Change the menu's NumItems() and GetItem() function to reference + // the message list in this object rather than add the menu items + // to the menu + subBoardMenu.grpIdx = pGrpIdx; + subBoardMenu.NumItems = function() { + return msg_area.grp_list[this.grpIdx].sub_list.length; }; - console.gotoxy(curpos); - - // User input loop - var userInput = null; - var continueChoosingMsgSubBoard = true; - while (continueChoosingMsgSubBoard) - { - pageNum = calcPageNum(topMsgSubIndex, pSelBoxInnerHeight); - - // Get a key from the user (upper-case) and take action based upon it. - userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings); - switch (userInput) + subBoardMenu.GetItem = function(pSubIdx) { + var menuItemObj = this.MakeItemWithRetval(-1); + if ((pSubIdx >= 0) && (pSubIdx < msg_area.grp_list[this.grpIdx].sub_list.length)) { - case KEY_UP: // Move up one message sub-board in the list - if (selectedMsgSubIndex > 0) - { - // If the previous group index is on the previous page, then - // display the previous page. - var previousMsgSubIndex = selectedMsgSubIndex - 1; - if (previousMsgSubIndex < topMsgSubIndex) - { - // Adjust topMsgSubIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgSubIndex -= numItemsPerPage; - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, previousMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // We'll want to move the cursor to the leftmost character - // of the selected line. - curpos.x = pSelBoxUpperLeft.x+1; - curpos.y = pSelBoxUpperLeft.y+pSelBoxInnerHeight; - } - else - { - // Display the current line un-highlighted - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, false); - // Display the previous line highlighted - curpos.x = pSelBoxUpperLeft.x+1; - --curpos.y; - console.gotoxy(curpos); - writeMsgSubLine(pGrpIndex, previousMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, true); - } - selectedMsgSubIndex = previousMsgSubIndex; - console.gotoxy(curpos); // Move the cursor into place where it should be - } - break; - case KEY_DOWN: // Move down one message sub-board in the list - if (selectedMsgSubIndex < msg_area.grp_list[pGrpIndex].sub_list.length - 1) - { - // If the next sub-board index is on the next page, then display - // the next page. - var nextMsgSubIndex = selectedMsgSubIndex + 1; - if (nextMsgSubIndex > bottomMsgSubIndex) - { - // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and - // refresh the list on the screen. - topMsgSubIndex += numItemsPerPage; - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, nextMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // We'll want to move the cursor to the leftmost character - // of the selected line. - curpos.x = pSelBoxUpperLeft.x+1; - curpos.y = pSelBoxUpperLeft.y+1; - } - else - { - // Display the current line un-highlighted - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, false); - // Display the next line highlighted - curpos.x = pSelBoxUpperLeft.x+1; - ++curpos.y; - console.gotoxy(curpos); - writeMsgSubLine(pGrpIndex, nextMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, true); - } - selectedMsgSubIndex = nextMsgSubIndex; - console.gotoxy(curpos); // Move the cursor into place where it should be - } - break; - case KEY_HOME: // Go to the top message sub-board on the screen - if (selectedMsgSubIndex > topMsgSubIndex) - { - // Display the current line un-highlighted, adjust - // selectedMsgSubIndex, then display the new line - // highlighted. - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, false); - selectedMsgSubIndex = topMsgSubIndex; - curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, true); - console.gotoxy(curpos); - } - break; - case KEY_END: // Go to the bottom message sub-board on the screen - if (selectedMsgSubIndex < bottomMsgSubIndex) - { - // Display the current line un-highlighted, adjust - // selectedGrpIndex, then display the new line - // highlighted. - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, false); - selectedMsgSubIndex = bottomMsgSubIndex; - curpos.x = pSelBoxUpperLeft.x + 1; - curpos.y = pSelBoxUpperLeft.y + (bottomMsgSubIndex-topMsgSubIndex+1); - console.gotoxy(curpos); - writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth, - subNumFieldLen, true); - console.gotoxy(curpos); - } - break; - case KEY_PAGE_DOWN: // Go to the next page - var nextPageTopIndex = topMsgSubIndex + numItemsPerPage; - if (nextPageTopIndex < msg_area.grp_list[pGrpIndex].sub_list.length) - { - // Adjust the top and bottom indexes, and refresh the list on the - // screen. - topMsgSubIndex = nextPageTopIndex; - pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage); - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - selectedMsgSubIndex = topMsgSubIndex; - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case KEY_PAGE_UP: // Go to the previous page - var prevPageTopIndex = topMsgSubIndex - numItemsPerPage; - if (prevPageTopIndex >= 0) - { - // Adjust the top and bottom indexes, and refresh the list on the - // screen. - topMsgSubIndex = prevPageTopIndex; - pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage); - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - selectedMsgSubIndex = topMsgSubIndex; - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case 'F': // Go to the first page - if (topMsgSubIndex > 0) - { - topMsgSubIndex = 0; - pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage); - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - selectedMsgSubIndex = 0; - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case 'L': // Go to the last page - if (topMsgSubIndex < topIndexForLastPage) - { - topMsgSubIndex = topIndexForLastPage; - pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage); - bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage); - selectedMsgSubIndex = topIndexForLastPage; - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - // Put the cursor at the beginning of the topmost row of message groups - curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 }; - console.gotoxy(curpos); - } - break; - case CTRL_C: // Quit (Ctrl-C is the cross-post hotkey) - case "Q": // Quit - case KEY_ESC: // Quit - continueChoosingMsgSubBoard = false; - break; - case KEY_ENTER: // Select the currently-highlighted message sub-board - // If the sub-board code is toggled on, then toggle it off, and vice-versa. - var msgSubCode = msg_area.grp_list[pGrpIndex].sub_list[selectedMsgSubIndex].code; - if (gCrossPostMsgSubs.subCodeExists(msgSubCode)) - { - // Remove it from gCrossPostMsgSubs and refresh the line on the screen - gCrossPostMsgSubs.remove(msgSubCode); - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - // Write a blank space using highlight colors - console.print(gConfigSettings.genColors.crossPostChkHighlight + " "); - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - } - else - { - // If the user is allowed to post in the selected sub, then add it - // to gCrossPostMsgSubs and refresh the line on the screen; - // otherwise, show an error message. - if (msg_area.sub[msgSubCode].can_post) - { - gCrossPostMsgSubs.add(msgSubCode); - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - // Write a checkmark using highlight colors - console.print(gConfigSettings.genColors.crossPostChkHighlight + CHECK_CHAR); - console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y); - } - else - { - // Go to the bottom row of the selection box and display an error that - // the user can't post in the selected sub-board and pause for a moment. - writeCantPostErrMsg(pSelBoxUpperLeft.x, pSelBoxLowerRight.y, selectedMsgSubIndex+1, - pSelBoxWidth, pSelBoxInnerWidth, cantPostErrPauseMS, curpos); - } - } - break; - case '?': // Display cross-post help - displayCrossPostHelp(pSelBoxUpperLeft, pSelBoxLowerRight); - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1); - console.pause(); - ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, - pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, - pSelBoxLowerRight.x-1, subNumFieldLen, true); - console.gotoxy(curpos); - break; - default: - // If the user entered a numeric digit, then treat it as - // the start of the message sub-board 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); - // We want to write the prompt text only if the first digit entered - // by the user is an ambiguous message sub-board number (i.e., if - // the first digit is 2 and there's a message group # 2 and 20). - var writePromptText = (msg_area.grp_list[pGrpIndex].sub_list.length >= +userInput * 10); - if (writePromptText) - { - // Move the cursor to the bottom border of the selection box and - // prompt the user for the message number. - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y); - printf("ncToggle sub-board #:%" + +(pSelBoxInnerWidth-19) + "s", ""); - console.gotoxy(pSelBoxUpperLeft.x+21, pSelBoxLowerRight.y); - console.print("h"); - } - else - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y); - userInput = console.getnum(msg_area.grp_list[pGrpIndex].sub_list.length); - var chosenMsgSubIndex = userInput - 1; - - // Re-draw the bottom border of the selection box - if (writePromptText) - { - drawInitialCrossPostSelBoxBottomBorder({ x: pSelBoxUpperLeft.x, y: pSelBoxLowerRight.y }, - pSelBoxWidth, gConfigSettings.genColors.listBoxBorder, - true); - } - else - { - console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y); - console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE); - } - - // If the user made a selection, then toggle it on/off. - if (userInput > 0) - { - var msgSubCode = msg_area.grp_list[pGrpIndex].sub_list[chosenMsgSubIndex].code; - if (gCrossPostMsgSubs.subCodeExists(msgSubCode)) - { - // Remove it from gCrossPostMsgSubs and refresh the line on the screen - gCrossPostMsgSubs.remove(msgSubCode); - if ((chosenMsgSubIndex >= topMsgSubIndex) && (chosenMsgSubIndex <= bottomMsgSubIndex)) - { - var screenRow = pSelBoxUpperLeft.y + (chosenMsgSubIndex - topMsgSubIndex + 1); - console.gotoxy(pSelBoxUpperLeft.x+1, screenRow); - // Write a blank space - var color = (chosenMsgSubIndex == selectedMsgSubIndex ? - gConfigSettings.genColors.crossPostChkHighlight : - gConfigSettings.genColors.crossPostChk); - console.print(color + " "); - } - } - else - { - // If the user is allowed to post in the selected sub, then add it - // to gCrossPostMsgSubs and refresh the line on the screen; - // otherwise, show an error message. - if (msg_area.sub[msgSubCode].can_post) - { - gCrossPostMsgSubs.add(msgSubCode); - if ((chosenMsgSubIndex >= topMsgSubIndex) && (chosenMsgSubIndex <= bottomMsgSubIndex)) - { - var screenRow = pSelBoxUpperLeft.y + (chosenMsgSubIndex - topMsgSubIndex + 1); - console.gotoxy(pSelBoxUpperLeft.x+1, screenRow); - // Write a checkmark - var color = (chosenMsgSubIndex == selectedMsgSubIndex ? - gConfigSettings.genColors.crossPostChkHighlight : - gConfigSettings.genColors.crossPostChk); - console.print(color + CHECK_CHAR); - } - } - else - { - // Go to the bottom row of the selection box and display an error that - // the user can't post in the selected sub-board and pause for a moment. - writeCantPostErrMsg(pSelBoxUpperLeft.x, pSelBoxLowerRight.y, chosenMsgSubIndex+1, - pSelBoxWidth, pSelBoxInnerWidth, cantPostErrPauseMS, curpos); - } - } - } - - console.gotoxy(originalCurpos); - } - break; + var showSubBoardMark = false; + if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != "")) + showSubBoardMark = ((this.grpIdx == msg_area.sub[bbs.cursub_code].grp_index) && (pSubIdx == msg_area.sub[bbs.cursub_code].index)); + var markColText = showSubBoardMark ? "*" : " "; + var subDesc = msg_area.grp_list[this.grpIdx].sub_list[pSubIdx].description.substr(0, descLen); + menuItemObj.text = format(printfStr, markColText, +(pSubIdx+1), subDesc); + //menuItemObj.retval = pSubIdx; + // Have the selected item be the sub-board code + menuItemObj.retval = msg_area.grp_list[this.grpIdx].sub_list[pSubIdx].code; } - } - return retObj; -} -// Displays a screenful of message groups, for the cross-posting -// interface. -// -// Parameters: -// pGrpIndex: The message group index -// pStartIndex: The message group index to start at (0-based) -// pSelectedIndex: The index of the currently-selected message sub -// pStartScreenRow: The row on the screen to start at (1-based) -// pStartScreenCol: The column on the screen to start at (1-based) -// pEndScreenRow: The row on the screen to end at (1-based) -// pEndScreenCol: The column on the screen to end at (1-based) -// pSubNumFieldLen: The length to use for the sub-board number field -// 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 ListScreenfulOfMsgSubs(pGrpIndex, pStartIndex, pSelectedIndex, pStartScreenRow, - pStartScreenCol, pEndScreenRow, pEndScreenCol, - pSubNumFieldLen, pBlankToEndRow) -{ - // If the parameters are invalid, then just return. - if ((typeof(pGrpIndex) != "number") || (typeof(pStartIndex) != "number") || - (typeof(pSelectedIndex) != "number") || (typeof(pStartScreenRow) != "number") || - (typeof(pStartScreenCol) != "number") || (typeof(pEndScreenRow) != "number") || - (typeof(pEndScreenCol) != "number")) - { - return; - } - if ((pGrpIndex < 0) || (pGrpIndex >= msg_area.grp_list.length)) - return; - if ((pStartIndex < 0) || (pStartIndex >= 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 ((pStartScreenCol < 1) || (pStartScreenCol > console.screen_columns)) - return; - if ((pEndScreenCol < 1) || (pEndScreenCol > console.screen_columns)) - return; + return menuItemObj; + }; - // If pStartScreenRow is greater than pEndScreenRow, then swap them. - // Do the same with pStartScreenCol and pEndScreenCol. - if (pStartScreenRow > pEndScreenRow) - { - var temp = pStartScreenRow; - pStartScreenRow = pEndScreenRow; - pEndScreenRow = temp; - } - if (pStartScreenCol > pEndScreenCol) - { - var temp = pStartScreenCol; - pStartScreenCol = pEndScreenCol; - pEndScreenCol = temp; - } + // Set the currently selected item. If the current sub-board is in this list, + // then set the selected item to that; otherwise, the selected item should be + // the first sub-board. + if (msg_area.sub[bbs.cursub_code].grp_index == pGrpIdx) + subBoardMenu.SetSelectedItemIdx(msg_area.sub[bbs.cursub_code].index); + else + subBoardMenu.SetSelectedItemIdx(0); // This should also set its topItemIdx to 0 - // Calculate the ending index to use for the message groups array. - var endIndex = pStartIndex + (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; - - // Go to the specified screen row, and display the message group information. - var textWidth = pEndScreenCol - pStartScreenCol + 1; - var row = pStartScreenRow; - var msgSubIndex = pStartIndex; - for (; msgSubIndex < onePastEndIndex; ++msgSubIndex) - { - console.gotoxy(pStartScreenCol, row++); - // The 5th parameter to writeMsgSubLine() is whether or not to - // write the message sub information with highlight colors. - writeMsgSubLine(pGrpIndex, msgSubIndex, textWidth, pSubNumFieldLen, - (msgSubIndex == pSelectedIndex)); - } + // Sub-board selection validation function + subBoardMenu.ValidateSelectItem = function(pSubCode) { + if (msg_area.sub[pSubCode].can_post) + return true; + else + { + // Go to the bottom row of the selection box and display an error that + // the user can't post in the selected sub-board and pause for a moment. + writeCantPostErrMsg(pListStartCol-1, pListStartRow+pListHeight, subBoardMenu.selectedItemIdx+1, + pListWidth+2, pListWidth, 2000/*cantPostErrPauseMS*/, console.getxy()); + } + }; - // 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) - { - console.print("n"); - var areaWidth = pEndScreenCol - pStartScreenCol + 1; - var formatStr = "%-" + areaWidth + "s"; - for (; screenRow <= pEndScreenRow; ++screenRow) - { - console.gotoxy(pStartScreenCol, screenRow) - printf(formatStr, ""); - } - } - } + return subBoardMenu; } -// Writes a message sub-board information line (for choosing a message sub-board -// for cross-posing). +// This function writes the error message that the user can't post in a +// message sub-board, pauses, then refreshes the bottom border of the +// selection box. // // Parameters: -// pGrpIndex: The index of the message group (assumed to be valid) -// pSubIndex: The index of the message sub-board within the group (assumed to be valid) -// pSubCode: The sub-board code (assumed to be valid) -// pTextWidth: The maximum text width -// pSubNumFieldLen: The length to use for the sub-board number field -// pHighlight: Boolean - Whether or not to write the line highlighted. -function writeMsgSubLine(pGrpIndex, pSubIndex, pTextWidth, pSubNumFieldLen, pHighlight) +// pX: The column of the lower-right corner of the selection box +// pY: The row of the lower-right corner of the selection box +// pSubBoardNum: The number of the sub-board (1-based) +// pSelBoxWidth: The width of the selection box +// pSelBoxInnerWidth: The width of the selection box inside the left & right borders +// pPauseMS: The number of millisecons to pause after displaying the error message +// pCurpos: The position of the cursor before calling this function +function writeCantPostErrMsg(pX, pY, pSubBoardNum, pSelBoxWidth, pSelBoxInnerWidth, pPauseMS, pCurpos) { - if ((typeof(pGrpIndex) != "number") || (typeof(pSubIndex) != "number") || - (typeof(pTextWidth) != "number")) - { - return; - } - - // Put together the printf format string - var msgSubDescLen = pTextWidth - pSubNumFieldLen - 2; - var printfStr = "n"; - if (pHighlight) - { - printfStr += gConfigSettings.genColors.crossPostChkHighlight + "%1s" - + gConfigSettings.genColors.crossPostMsgAreaNumHighlight + "%" + pSubNumFieldLen + "d " - + gConfigSettings.genColors.crossPostMsgAreaDescHighlight + "%-" + msgSubDescLen + "s"; - } - else - { - printfStr += gConfigSettings.genColors.crossPostChk + "%1s" - + gConfigSettings.genColors.crossPostMsgAreaNum + "%" + pSubNumFieldLen + "d " - + gConfigSettings.genColors.crossPostMsgAreaDesc + "%-" + msgSubDescLen + "s"; - } - - // Write the message group information line - var subCode = msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code; - printf(printfStr, (gCrossPostMsgSubs.subCodeExists(subCode) ? CHECK_CHAR : " "), +(pSubIndex+1), - msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, msgSubDescLen)); + var cantPostErrMsg = "You're not allowed to post in area " + pSubBoardNum + "."; + console.gotoxy(pX+1, pY); + printf("\1n\1h\1y%-" + pSelBoxInnerWidth + "s", cantPostErrMsg); + console.gotoxy(pX+cantPostErrMsg.length+1, pY); + mswait(pPauseMS); + // Refresh the bottom border of the selection box + drawInitialCrossPostSelBoxBottomBorder({ x: pX, y: pY }, pSelBoxWidth, + gConfigSettings.genColors.listBoxBorder, true); + console.gotoxy(pCurpos); } // Writes a line in the edit lines array @@ -6656,10 +5720,7 @@ function doTaglineSelection() } // First page else if (lastUserInputUpper == "F") - { - taglineMenu.selectedItemIdx = 0; - taglineMenu.topItemIdx = 0; - } + tagLineMenu.SetSelectedItemIdx(0); // This should also set its topItemIdx to 0 // Last page else if (lastUserInputUpper == "L") { diff --git a/exec/SlyEdit_DCTStuff.js b/exec/SlyEdit_DCTStuff.js index 1645defa100989d7050b55404d6c7577dfc3fd7b..dcc4e9142494a0e9e733d9f3abfff7c74757cf57 100644 --- a/exec/SlyEdit_DCTStuff.js +++ b/exec/SlyEdit_DCTStuff.js @@ -1,4 +1,4 @@ -// $Id: SlyEdit_DCTStuff.js,v 1.26 2019/08/15 04:43:33 nightfox Exp $ +// $Id: SlyEdit_DCTStuff.js,v 1.21 2019/06/06 03:50:47 nightfox Exp $ /* This file contains DCTEdit-specific functions for SlyEdit. * @@ -13,6 +13,7 @@ * Initial public release * ... Removed comments ... * 2019-05-04 Eric Oulashin Updated to use require() instead of load() if possible. + * 2021-12-11 Eric Oulashin Updated the quote window bottom border text */ if (typeof(require) === "function") @@ -452,10 +453,15 @@ function DrawQuoteWindowBottomBorder_DCTStyle(pEditLeft, pEditRight) + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor + "[^Q/ESC] End" + gConfigSettings.DCTColors.QuoteWinBorderColor + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor - + "[" + UP_ARROW + "/" + DOWN_ARROW + "/PgUp/PgDn] Scroll" + + "[" + UP_ARROW + "/" + DOWN_ARROW + "/PgUp/PgDn/Home/End] Scroll" + + gConfigSettings.DCTColors.QuoteWinBorderColor + HORIZONTAL_SINGLE + + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor + /* + + "[" + UP_ARROW + "/" + DOWN_ARROW + "/PgUp/PgDn] Scroll" + gConfigSettings.DCTColors.QuoteWinBorderColor + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor + "[F/L] First/last page"; + */ var helpTextLen = strip_ctrl(quoteHelpText).length; // Figure out the starting horizontal position on the screen so that diff --git a/exec/SlyEdit_IceStuff.js b/exec/SlyEdit_IceStuff.js index 6a0c0a5986e15145210ea2fb7ab8e6cdb3395d9f..01af28ac7afabb7691add1f0f4a2888874fb8b89 100644 --- a/exec/SlyEdit_IceStuff.js +++ b/exec/SlyEdit_IceStuff.js @@ -1,4 +1,4 @@ -// $Id: SlyEdit_IceStuff.js,v 1.33 2019/08/15 04:43:33 nightfox Exp $ +// $Id: SlyEdit_IceStuff.js,v 1.29 2019/06/06 03:50:47 nightfox Exp $ /* This contains IceEdit-specific functions for SlyEdit. * @@ -13,6 +13,7 @@ * Initial public release * ... Removed comments ... * 2019-05-04 Eric Oulashin Updated to use require() instead of load() if possible. + * 2021-12-11 Eric Oulashin Updated the quote window bottom border text */ if (typeof(require) === "function") @@ -460,15 +461,19 @@ function DrawQuoteWindowBottomBorder_IceStyle(pEditLeft, pEditRight) + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT + gConfigSettings.iceColors.BorderColor1 + HORIZONTAL_DOUBLE + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.QuoteWinBorderTextColor + "Up/Down/PgUp/PgDn/Home/End=Scroll" + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT; + /* + gConfigSettings.iceColors.QuoteWinBorderTextColor + "Up/Down/PgUp/PgDn=Scroll" + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT + gConfigSettings.iceColors.BorderColor1 + HORIZONTAL_DOUBLE + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + gConfigSettings.iceColors.QuoteWinBorderTextColor + "F/L=First/Last pg" + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT; + */ // The border from here to the end of the line: Random high/low blue var screenText = ""; - for (var posX = pEditLeft + 73; posX <= pEditRight; ++posX) + for (var posX = pEditLeft + 62/*73*/; posX <= pEditRight; ++posX) screenText += HORIZONTAL_DOUBLE; screenText += LOWER_RIGHT_VSINGLE_HDOUBLE; DrawQuoteWindowBottomBorder_IceStyle.border += randomTwoColorString(screenText, diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js index 0ebb38b6eb3c235a2cb45232d6659b795a07c3ec..cbd783b2a41327dc0285fac73474522da884ffeb 100644 --- a/exec/SlyEdit_Misc.js +++ b/exec/SlyEdit_Misc.js @@ -1,4 +1,4 @@ -// $Id: SlyEdit_Misc.js,v 1.59 2020/03/04 20:59:50 nightfox Exp $ +// $Id: SlyEdit_Misc.js,v 1.51 2019/06/06 03:50:47 nightfox Exp $ /* This file declares some general helper functions and variables * that are used by SlyEdit. @@ -42,6 +42,7 @@ * the user can post in a sub-board by checking the can_post * property of the sub-board rather than checking the * ARS. The can_post property covers more cases. + * 2021-12-09 Eric Oulashin Added consolePauseWithoutText() */ if (typeof(require) === "function") @@ -4401,6 +4402,15 @@ function consolePauseWithESCChars(pCfgObj) getKeyWithESCChars(K_NOSPIN|K_NOCRLF|K_NOECHO, pCfgObj); } +// Sets the "pause" text to an empty string, does a console.pause(), then restores the pause text. +function consolePauseWithoutText() +{ + var originalPausePromptText = bbs.text(Pause); // 563: The "Press a key" text in text.dat + bbs.replace_text(Pause, ""); + console.pause(); + bbs.revert_text(Pause); +} + // Inputs a keypress from the user and handles some ESC-based // characters such as PageUp, PageDown, and ESC. If PageUp // or PageDown are pressed, this function will return the diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js index 38b20a0486bef374a79c6ef7d3e01e65239f6090..ecd41700906eff31f9da16fadf1bf5a388c7ec51 100644 --- a/exec/load/dd_lightbar_menu.js +++ b/exec/load/dd_lightbar_menu.js @@ -229,6 +229,55 @@ lbMenu.GetItem = function(pItemIndex) { menuItemObj.retval = itemRetval; return menuItemObj; // The DDLightbarMenu object will use this when displaying the menu }; + +If you want to set the currently selected item before calling GetVal() to allow user input, +you should call the SetSelectedItemIdx() function and pass the index to that. +lbMenu.SetSelectedItemIdx(5); + +For selecting an item, it may be desirable to validate whether a user should be allowed +to select the item. DDLightbarMenu has a member function it calls, ValidateSelectItem(), +to do just that. It takes the selected item's return value and returns a boolean to signify +whether the user can select it. By default, it just returns true (allowing the user to +select any item). When the user can't choose a value, your code should output why. +To change its behavior, you can overwrite it as follows (assuming lbMenu +is a DDLightbarMenu object): + +lbMenu.ValidateSelectItem = function(pItemRetval) { + // Should the user be able to select the item with the return val indicated + // by pItemRetval? + if (yourValidationCode(pItemRetval)) + return true; + else + { + console.print("* Can't choose " + pItemRetval + " because blah blah blah!\r\n\1p"); + return false; + } +} + +OnItemSelect is a function that is called when an item is selected, or toggled +if multi-select is enabled. + +Parameters: + pItemRetval: The return value of the item selected + pSelected: Boolean - Whether the item was selected or de-selected. De-selection + is possible when multi-select is enabled. +lbMenu.OnItemSelect = function(pItemRetval, pSelected) +{ + // Do something with pItemRetval. pSelected tells whether the item was selected, + // or de-selected if multi-select is enabled. +} + +The property exitOnItemSelect specifies whether or not to exit the input loop when an item is +selected/submitted (i.e. with ENTER; not for toggling with multi-select). This is true by +default. It can be desirable to set this to false in some situations, such as when you want a +menu with a custom OnItemSelect() function specified and you want the menu to continue to +be displayed allowing the user to select an item. +lbMenu.exitOnItemSelect = false; + +The 'key down' behavior can be called explicitly, if needed, by calling the DoKeyDown() function. +It takes 2 parameters: An object of selected item indexes (as passed to GetVal()) and, optionally, +the pre-calculated number of items. +lbMenu.DoKeyDown(pNumItems, pSelectedItemIndexes); */ if (typeof(require) === "function") @@ -244,12 +293,12 @@ else // Keyboard keys -var KEY_ESC = ascii(27); var KEY_ENTER = "\x0d"; // PageUp & PageDown keys - Synchronet 3.17 as of about December 18, 2017 // use CTRL-P and CTRL-N for PageUp and PageDown, respectively. key_defs.js // defines them as KEY_PAGEUP and KEY_PAGEDN (key_defs.js is loaded by // sbbsdefs.js). +//var KEY_ESC = ascii(27); // Box-drawing/border characters: Single-line var UPPER_LEFT_SINGLE = "\xDA"; @@ -374,6 +423,10 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) // /^\1[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i this.syncAttrRegex = /\1[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]/i; + // Whether or not to exit the input loop when an item is selected/submitted + // (i.e. with ENTER; not for toggling with multi-select) + this.exitOnItemSelect = true; + // Things for mouse support this.mouseTimeout = 0; // Timeout in ms. Currently using 0 for no timeout. this.mouseEnabled = false; // To pass to mouse_getkey @@ -401,6 +454,7 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) this.RemoveAllItemHotkeys = DDLightbarMenu_RemoveAllItemHotkeys; this.GetMouseClickRegion = DDLightbarMenu_GetMouseClickRegion; this.GetVal = DDLightbarMenu_GetVal; + this.DoKeyDown = DDLightbarMenu_DoKeyDown; this.SetBorderChars = DDLightbarMenu_SetBorderChars; this.SetColors = DDLightbarMenu_SetColors; this.GetNumItemsPerPage = DDLightbarMenu_GetNumItemsPerPage; @@ -423,6 +477,21 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) this.ItemUsesAltColors = DDLightbarMenu_ItemUsesAltColors; this.GetColorForItem = DDLightbarMenu_GetColorForItem; this.GetSelectedColorForItem = DDLightbarMenu_GetSelectedColorForItem; + this.SetSelectedItemIdx = DDLightbarMenu_SetSelectedItemIdx; + + // ValidateSelectItem is a function for validating that the user can select an item. + // It takes the selected item's return value and returns a boolean to signify whether + // the user can select it. + this.ValidateSelectItem = function(pItemRetval) { return true; } + + // OnItemSelect is a function that is called when an item is selected, or toggled + // if multi-select is enabled. + // + // Parameters: + // pItemRetval: The return value of the item selected + // pSelected: Boolean - Whether the item was selected or de-selected. De-selection + // is possible when multi-select is enabled. + this.OnItemSelect = function(pItemRetval, pSelected) { } // Set some things based on the parameters passed in if ((typeof(pX) == "number") && (typeof(pY) == "number")) @@ -869,6 +938,11 @@ function DDLightbarMenu_GetItemText(pIdx, pItemLen, pHighlight, pSelected) itemText = strip_ctrl(menuItem.text); if (itemTextDisplayableLen(itemText, this.ampersandHotkeysInItems) > itemLen) itemText = itemText.substr(0, itemLen); + // If the item text is empty, then fill it with spaces for the item length + // so that the line's colors/attributes will be applied for the whole line + // when written + if (strip_ctrl(itemText).length == 0) + itemText = format("%" + itemLen + "s", ""); // Add the item color to the item text itemText = addAttrsToString(itemText, itemColor); // If ampersandHotkeysInItems is true, see if there's an ampersand in @@ -1228,48 +1302,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) } else if ((this.lastUserInput == KEY_DOWN) || (this.lastUserInput == KEY_RIGHT)) { - if (this.selectedItemIdx < numItems-1) - { - // Draw the current item in regular colors - this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - ++this.selectedItemIdx; - // Draw the new current item in selected colors - // If the selected item is below the bottom of the menu, then we'll need to - // scroll the items up. - var numItemsPerPage = (this.borderEnabled ? this.size.height - 2 : this.size.height); - if (this.selectedItemIdx > this.topItemIdx+numItemsPerPage-1) - { - ++this.topItemIdx; - this.Draw(selectedItemIndexes); - } - else - { - // The selected item is not below the bottom of the menu, so we can - // just draw the selected item highlighted. - this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - } - } - else - { - // selectedItemIdx is the last item index. If wrap navigation is enabled, - // then go to the first item. - if (this.wrapNavigation) - { - // Draw the current item in regular colors - this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - // Go to the first item and scroll to the top if necessary - this.selectedItemIdx = 0; - var oldTopItemIdx = this.topItemIdx; - this.topItemIdx = 0; - if (this.topItemIdx != oldTopItemIdx) - this.Draw(selectedItemIndexes); - else - { - // Draw the new current item in selected colors - this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - } - } - } + this.DoKeyDown(selectedItemIndexes, numItems); } else if (this.lastUserInput == KEY_PAGEUP) { @@ -1447,65 +1480,94 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // Enter key or additional select-item key: Select the item & quit out of the input loop else if ((this.lastUserInput == KEY_ENTER) || (this.SelectItemKeysIncludes(this.lastUserInput))) { - // If multi-select is enabled and if the user hasn't made any choices, - // then add the current item to the user choices. Otherwise, choose - // the current item. Then exit. - if (this.multiSelect) + // Let the user select the item if ValidateSelectItem() returns true + var allowSelectItem = true; + if (typeof(this.ValidateSelectItem) === "function") + allowSelectItem = this.ValidateSelectItem(this.GetItem(this.selectedItemIdx).retval); + if (allowSelectItem) { - if (Object.keys(selectedItemIndexes).length == 0) - selectedItemIndexes[this.selectedItemIdx] = true; + // If multi-select is enabled and if the user hasn't made any choices, + // then add the current item to the user choices. Otherwise, choose + // the current item. Then exit. + if (this.multiSelect) + { + if (Object.keys(selectedItemIndexes).length == 0) + selectedItemIndexes[this.selectedItemIdx] = true; + } + else + retVal = this.GetItem(this.selectedItemIdx).retval; + + // Run the OnItemSelect event function + if (typeof(this.OnItemSelect) === "function") + this.OnItemSelect(retVal, true); + + // Exit the input loop if this.exitOnItemSelect is set to true + if (this.exitOnItemSelect) + continueOn = false; } - else - retVal = this.GetItem(this.selectedItemIdx).retval; - continueOn = false; } - else if (this.lastUserInput == " ") + else if (this.lastUserInput == " ") // Add the current item to multi-select { - // Select the current item + // Add the current item to multi-select if multi-select is enabled if (this.multiSelect) { - var added = false; // Will be true if added or false if deleted - if (selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)) - delete selectedItemIndexes[this.selectedItemIdx]; - else + // Only let the user select the item if ValidateSelectItem() returns true + var allowSelectItem = true; + if (typeof(this.ValidateSelectItem) === "function") + allowSelectItem = this.ValidateSelectItem(this.GetItem(this.selectedItemIdx).retval); + if (allowSelectItem) { - var addIt = true; - if (this.maxNumSelections > 0) - addIt = (Object.keys(selectedItemIndexes).length < this.maxNumSelections); - if (addIt) + var added = false; // Will be true if added or false if deleted + if (selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)) + delete selectedItemIndexes[this.selectedItemIdx]; + else { - selectedItemIndexes[this.selectedItemIdx] = true; - added = true; + var addIt = true; + if (this.maxNumSelections > 0) + addIt = (Object.keys(selectedItemIndexes).length < this.maxNumSelections); + if (addIt) + { + selectedItemIndexes[this.selectedItemIdx] = true; + added = true; + } } - } - // Draw a character next to the item if it's selected, or nothing if it's not selected - var XPos = this.pos.x + this.size.width - 2; - var YPos = this.pos.y+(this.selectedItemIdx-this.topItemIdx); - if (this.borderEnabled) - { - --XPos; - ++YPos; - } - if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow()) - --XPos; - console.gotoxy(XPos, YPos); - if (added) - { - // If the item color is an array, then default to a color string here - var itemColor = this.GetColorForItem(this.selectedItemIdx, true); - if (Array.isArray(itemColor)) + + // Run the OnItemSelect event function + if (typeof(this.OnItemSelect) === "function") { - var bkgColor = getBackgroundAttrAtIdx(itemColor, this.size.width-1); - itemColor = "\1n\1h\1g" + bkgColor; + //this.OnItemSelect = function(pItemRetval, pSelected) { } + this.OnItemSelect(this.GetItem(this.selectedItemIdx).retval, added); + } + + // Draw a character next to the item if it's selected, or nothing if it's not selected + var XPos = this.pos.x + this.size.width - 2; + var YPos = this.pos.y+(this.selectedItemIdx-this.topItemIdx); + if (this.borderEnabled) + { + --XPos; + ++YPos; + } + if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow()) + --XPos; + console.gotoxy(XPos, YPos); + if (added) + { + // If the item color is an array, then default to a color string here + var itemColor = this.GetColorForItem(this.selectedItemIdx, true); + if (Array.isArray(itemColor)) + { + var bkgColor = getBackgroundAttrAtIdx(itemColor, this.size.width-1); + itemColor = "\1n\1h\1g" + bkgColor; + } + console.print(itemColor + " " + this.multiSelectItemChar + "\1n"); + } + else + { + // Display the last 2 characters of the regular item text + var itemText = this.GetItemText(this.selectedItemIdx, null, true, false); + var textToPrint = substrWithAttrCodes(itemText, console.strlen(itemText)-2, 2); + console.print(textToPrint + "\1n"); } - console.print(itemColor + " " + this.multiSelectItemChar + "\1n"); - } - else - { - // Display the last 2 characters of the regular item text - var itemText = this.GetItemText(this.selectedItemIdx, null, true, false); - var textToPrint = substrWithAttrCodes(itemText, console.strlen(itemText)-2, 2); - console.print(textToPrint + "\1n"); } } } @@ -1617,6 +1679,61 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) return (this.multiSelect ? userChoices : retVal); } +// Performs the key-down behavior for showing the menu items +// +// Parameters: +// pSelectedItemIndexes: An object containing indexes of selected items. This is +// normally a temporary object created/used in GetVal(). +// pNumItems: The pre-calculated number of menu items. If this not given, this +// will be retrieved by calling NumItems(). +function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems) +{ + var selectedItemIndexes = (typeof(pSelectedItemIndexes) === "object" ? pSelectedItemIndexes : {}); + var numItems = (typeof(pNumItems) === "number" ? pNumItems : this.NumItems()); + + if (this.selectedItemIdx < numItems-1) + { + // Draw the current item in regular colors + this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + ++this.selectedItemIdx; + // Draw the new current item in selected colors + // If the selected item is below the bottom of the menu, then we'll need to + // scroll the items up. + var numItemsPerPage = (this.borderEnabled ? this.size.height - 2 : this.size.height); + if (this.selectedItemIdx > this.topItemIdx+numItemsPerPage-1) + { + ++this.topItemIdx; + this.Draw(selectedItemIndexes); + } + else + { + // The selected item is not below the bottom of the menu, so we can + // just draw the selected item highlighted. + this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + } + } + else + { + // selectedItemIdx is the last item index. If wrap navigation is enabled, + // then go to the first item. + if (this.wrapNavigation) + { + // Draw the current item in regular colors + this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + // Go to the first item and scroll to the top if necessary + this.selectedItemIdx = 0; + var oldTopItemIdx = this.topItemIdx; + this.topItemIdx = 0; + if (this.topItemIdx != oldTopItemIdx) + this.Draw(selectedItemIndexes); + else + { + // Draw the new current item in selected colors + this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + } + } + } +} // Sets the characters to use for drawing the border. Takes an object specifying // the values to set, but does not overwrite the whole borderChars object in the @@ -2047,12 +2164,35 @@ function DDLightbarMenu_GetColorForItem(pItemIndex, pSelected) // Return value: Either colors.selectedItemColor or colors.altSelectedItemColor function DDLightbarMenu_GetSelectedColorForItem(pItemIndex) { + if (typeof(pItemIndex) !== "number") + return; if ((pItemIndex < 0) || (pItemIndex >= this.NumItems())) return ""; return (this.GetItem(pItemIndex).useAltColors ? this.colors.altSelectedItemColor : this.colors.selectedItemColor); } +// Sets the selected item index for the menu, and sets anything else as appropriate +// (such as the index of the topmost menu item). +// +// Parameters: +// pSelectedItemIdx: The index of the selected item +function DDLightbarMenu_SetSelectedItemIdx(pSelectedItemIdx) +{ + if (typeof(pSelectedItemIdx) !== "number") + return; + if ((pSelectedItemIdx < 0) || (pSelectedItemIdx >= this.NumItems())) + return; + + this.selectedItemIdx = pSelectedItemIdx; + if (this.selectedItemIdx == 0) + this.topItemIdx = 0; + else if (this.selectedItemIdx >= this.topItemIdx+this.GetNumItemsPerPage()) + this.topItemIdx = this.selectedItemIdx - this.GetNumItemsPerPage() + 1; + else if (this.selectedItemIdx < this.topItemIdx) + this.topItemIdx = this.selectedItemIdx; +} + // Calculates the number of solid scrollbar blocks & non-solid scrollbar blocks // to use. Saves the information in this.scrollbarInfo.numSolidScrollBlocks and // this.scrollbarInfo.numNonSolidScrollBlocks.