diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js index eda8baab21d35e31187e6497fca6029ca496039a..858f282867e922b79c30ab1736ac8e61c4cf96ea 100644 --- a/exec/load/dd_lightbar_menu.js +++ b/exec/load/dd_lightbar_menu.js @@ -198,6 +198,15 @@ To add additional key characters as quit keys (in addition to ESC), call AddAdditionalQuitKeys() with a string of characters. For example: lbMenu.AddAdditionalQuitKeys("qQ"); +To clear the additional quit keys: ClearAdditionalQuitKeys() +lbMenu.ClearAdditionalQuitKeys(); + +Similarly for additional PageUp keys: AddAdditionalPageUpKeys and ClearAdditionalPageUpKeys +For additional PageDown: AddAdditionalPageDownKeys and ClearAdditionalPageDownKeys +For additional first page (like HOME): AddAdditionalFirstPageKeys and ClearAdditionalFirstPageKeys +For additional last page (like END): AddAdditionalLastPageKeys and ClearAdditionalLastPageKeys + + To enable the border and set top and bottom border text: lbMenu.borderEnabled = true; lbMenu.topBorderText = "Options"; @@ -469,6 +478,10 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) this.numberedMode = false; this.itemNumLen = 0; // For the length of the item numbers in numbered mode this.additionalQuitKeys = ""; // A string of additional keys besides ESC to quit out of the menu + this.additionalPageUpKeys = ""; // A string of additional keys besides PageUp for page up + this.additionalPageDnKeys = ""; // A string of additional keys besides PageDown for page down + this.additionalFirstPageKeys = ""; // A string of additional keys besides HOME for going to the first page + this.additionalLastPageKeys = ""; // A string of additional keys besides END for going to the last page this.additionalSelectItemKeys = ""; // A string of additional keys to select any item this.topBorderText = ""; // Text to display in the top border this.bottomBorderText = ""; // Text to display in the bottom border @@ -553,6 +566,18 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) this.AddAdditionalQuitKeys = DDLightbarMenu_AddAdditionalQuitKeys; this.QuitKeysIncludes = DDLightbarMenu_QuitKeysIncludes; this.ClearAdditionalQuitKeys = DDLightbarMenu_ClearAdditionalQuitKeys; + this.AddAdditionalPageUpKeys = DDLightbarMenu_AddAdditionalPageUpKeys; + this.PageUpKeysIncludes = DDLightbarMenu_PageUpKeysIncludes; + this.ClearAdditionalPageUpKeys = DDLightbarMenu_ClearAdditionalPageUpKeys; + this.AddAdditionalPageDownKeys = DDLightbarMenu_AddAdditionalPageDownKeys; + this.PageDownKeysIncludes = DDLightbarMenu_PageDownKeysIncludes; + this.ClearAdditionalPageDownKeys = DDLightbarMenu_ClearAdditionalPageDownKeys; + this.AddAdditionalFirstPageKeys = DDLightbarMenu_AddAdditionalFirstPageKeys; + this.FirstPageKeysIncludes = DDLightbarMenu_FirstPageKeysIncludes; + this.ClearAdditionalFirstPageKeys = DDLightbarMenu_ClearAdditionalFirstPageKeys; + this.AddAdditionalLastPageKeys = DDLightbarMenu_AddAdditionalLastPageKeys; + this.LastPageKeysIncludes = DDLightbarMenu_LastPageKeysIncludes; + this.ClearAdditionalLastPageKeys = DDLightbarMenu_ClearAdditionalLastPageKeys; this.AddAdditionalSelectItemKeys = DDLightbarMenu_AddAdditionalSelectItemKeys; this.SelectItemKeysIncludes = DDLightbarMenu_SelectItemKeysIncludes; this.ClearAdditionalSelectItemKeys = DDLightbarMenu_ClearAdditionalSelectItemKeys; @@ -1031,7 +1056,10 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar, for (var i = 0; i < numMenuItems; ++i) { var showMultiSelectMark = (this.multiSelect && (typeof(pSelectedItemIndexes) == "object") && pSelectedItemIndexes.hasOwnProperty(idx)); - console.print(this.GetItemText(i, itemLen, false, showMultiSelectMark) + "\x01n"); + var itemText = this.GetItemText(i, itemLen, false, showMultiSelectMark); + // TODO: It seems the text must be shortened by 3 less than the console width or else + // it behaves like there's an extra CRLF + console.print(substrWithAttrCodes(itemText, 0, console.screen_columns-3) + "\x01n"); console.crlf(); } this.numberedMode = numberedModeBackup; @@ -1195,6 +1223,16 @@ function DDLightbarMenu_DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelected var height = pHeight; if (height > (this.size.height - pStartY + 1)) height = (this.size.height - pStartY + 1); + /* + // Temporary + if (user.is_sysop) + { + console.print("\x01n\r\n"); + printf("DrawPartial 1 - X, Y, width, height: %d, %d; %d, %d\r\n", pStartX, pStartY, width, height); + console.pause(); + } + // End Temporary + */ var selectedItemIndexes = { }; // For multi-select mode if (typeof(pSelectedItemIndexes) == "object") @@ -1311,6 +1349,16 @@ function DDLightbarMenu_DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelected // Write the menu items if (writeMenuItems) { + /* + // Temporary + if (user.is_sysop) + { + console.print("\x01n\r\n"); + console.print("itemTxtStartIdx: " + itemTxtStartIdx + ", itemLen: " + itemLen + "\r\n"); + console.pause(); + } + // End Temporary + */ var blankItemTextFormatStr = "\x01n%" + itemLen + "s"; for (var lineNum = pStartY + this.pos.y - 1; lineNum <= lastLineNum; ++lineNum) { @@ -1331,7 +1379,8 @@ function DDLightbarMenu_DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelected if (this.borderEnabled) --itemIdx; var highlightItem = itemIdx == this.selectedItemIdx; var itemText = this.GetItemText(itemIdx, null, highlightItem, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - var shortenedText = substrWithAttrCodes(itemText, itemTxtStartIdx, itemLen); + //var shortenedText = substrWithAttrCodes(itemText, itemTxtStartIdx, itemLen); + var shortenedText = substrWithAttrCodes(itemText, itemTxtStartIdx, itemLen, true); // If shortenedText is empty (perhaps there's no menu item for this line), // then make shortenedText consist of all spaces at the proper length if (shortenedText.length == 0) @@ -1362,6 +1411,16 @@ function DDLightbarMenu_DrawPartialAbs(pStartX, pStartY, pWidth, pHeight, pSelec var width = pWidth; var startX = pStartX - this.pos.x + 1; var startY = pStartY - this.pos.y + 1; + /* + // Temporary + if (user.is_sysop) + { + console.print("\x01n\r\n"); + printf("Here 1 - X, Y, width, height: %d, %d; %d, %d\r\n", startX, startY, width, height); + console.pause(); + } + // End Temporary + */ if (startX < 1) { var XDiff = 1 - startX; @@ -1374,6 +1433,16 @@ function DDLightbarMenu_DrawPartialAbs(pStartX, pStartY, pWidth, pHeight, pSelec startY += YDiff; height -= YDiff; } + /* + // Temporary + if (user.is_sysop) + { + console.print("\x01n\r\n"); + printf("Here 2 - X, Y, width, height: %d, %d; %d, %d\r\n", startX, startY, width, height); + console.pause(); + } + // End Temporary + */ this.DrawPartial(startX, startY, width, height, pSelectedItemIndexes); } @@ -1510,8 +1579,8 @@ function DDLightbarMenu_GetItemText(pIdx, pItemLen, pHighlight, pSelected) var currentTextLen = itemTextDisplayableLen(itemText, this.ampersandHotkeysInItems); if (currentTextLen < itemLen) itemText += format("%" + +(itemLen-currentTextLen) + "s", ""); // Append spaces to the end of itemText - // If in numbered mode, prepend the item number to the front of the item text. - if (this.numberedMode) + // If in numbered mode and the item is selectable, prepend the item number to the front of the item text. + if (this.numberedMode && menuItem.isSelectable) { if (this.itemNumLen == 0) this.itemNumLen = numItems.toString().length; @@ -1790,9 +1859,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) } } else // this.mouseEnabled is false - { this.lastUserInput = getKeyWithESCChars(inputMode, this.inputTimeoutMS); - } // If no further input processing needs to be done due to a mouse click // action, then continue to the next loop iteration. @@ -1800,7 +1867,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) continue; // Take the appropriate action based on the user's last input/keypress - if ((this.lastUserInput == KEY_ESC) || (this.QuitKeysIncludes(this.lastUserInput)) || console.aborted) + if ((this.lastUserInput == KEY_ESC) || this.QuitKeysIncludes(this.lastUserInput) || console.aborted) { // Only exit if there was not a no-action mouse click // TODO: Is this logic good and clean? @@ -1823,18 +1890,18 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) this.DoKeyUp(selectedItemIndexes, numItems); else if ((this.lastUserInput == KEY_DOWN) || (this.lastUserInput == KEY_RIGHT)) this.DoKeyDown(selectedItemIndexes, numItems); - else if (this.lastUserInput == KEY_PAGEUP) + else if (this.lastUserInput == KEY_PAGEUP || this.PageUpKeysIncludes(this.lastUserInput)) this.DoPageUp(selectedItemIndexes, numItems); - else if (this.lastUserInput == KEY_PAGEDN) + else if (this.lastUserInput == KEY_PAGEDN || this.PageDownKeysIncludes(this.lastUserInput)) this.DoPageDown(selectedItemIndexes, numItems); - else if (this.lastUserInput == KEY_HOME) + else if (this.lastUserInput == KEY_HOME || this.FirstPageKeysIncludes(this.lastUserInput)) { // Go to the first item in the list var firstSelectableItemIdx = this.FindSelectableItemForward(0, false); if (this.selectedItemIdx > firstSelectableItemIdx) this.NavMenuForNewSelectedItemTop(firstSelectableItemIdx, this.GetNumItemsPerPage(), numItems, selectedItemIndexes); } - else if (this.lastUserInput == KEY_END) + else if (this.lastUserInput == KEY_END || this.LastPageKeysIncludes(this.lastUserInput)) { // Go to the last item in the list var lastSelectableItem = this.FindSelectableItemBackward(numItems-1, false); @@ -2677,7 +2744,8 @@ function DDLightbarMenu_CalcPageForItemAndSetTopItemIdx(pNumItemsPerPage, pNumIt // pAdditionalQuitKeys: A string of key characters function DDLightbarMenu_AddAdditionalQuitKeys(pAdditionalQuitKeys) { - this.additionalQuitKeys += pAdditionalQuitKeys; + if (typeof(pAdditionalQuitKeys) === "string") + this.additionalQuitKeys += pAdditionalQuitKeys; } // Returns whether or not the additional quit keys array contains a given @@ -2699,6 +2767,70 @@ function DDLightbarMenu_ClearAdditionalQuitKeys() this.additionalQuitKeys = ""; } +function DDLightbarMenu_AddAdditionalPageUpKeys(pAdditionalKeys) +{ + if (typeof(pAdditionalKeys) === "string") + this.additionalPageUpKeys += pAdditionalKeys; +} + +function DDLightbarMenu_PageUpKeysIncludes(pKey) +{ + return (this.additionalPageUpKeys.indexOf(pKey) > -1); +} + +function DDLightbarMenu_ClearAdditionalPageUpKeys() +{ + this.additionalPageUpKeys = ""; +} + +function DDLightbarMenu_AddAdditionalPageDownKeys(pAdditionalKeys) +{ + if (typeof(pAdditionalKeys) === "string") + this.additionalPageDnKeys += pAdditionalKeys; +} + +function DDLightbarMenu_PageDownKeysIncludes(pKey) +{ + return (this.additionalPageDnKeys.indexOf(pKey) > -1); +} + +function DDLightbarMenu_ClearAdditionalPageDownKeys() +{ + this.additionalPageDnKeys = ""; +} + +function DDLightbarMenu_AddAdditionalFirstPageKeys(pAdditionalKeys) +{ + if (typeof(pAdditionalKeys) === "string") + this.additionalFirstPageKeys += pAdditionalKeys; +} + +function DDLightbarMenu_FirstPageKeysIncludes(pKey) +{ + return (this.additionalFirstPageKeys.indexOf(pKey) > -1); +} + +function DDLightbarMenu_ClearAdditionalFirstPageKeys() +{ + this.additionalFirstPageKeys = ""; +} + +function DDLightbarMenu_AddAdditionalLastPageKeys(pAdditionalKeys) +{ + if (typeof(pAdditionalKeys) === "string") + this.additionalLastPageKeys += pAdditionalKeys; +} + +function DDLightbarMenu_LastPageKeysIncludes(pKey) +{ + return (this.additionalLastPageKeys.indexOf(pKey) > -1); +} + +function DDLightbarMenu_ClearAdditionalLastPageKeys() +{ + this.additionalLastPageKeys = ""; +} + // Adds additional key characters to select any item. The keys will be case-sensitive. // // Parameters: @@ -3435,51 +3567,154 @@ function substrWithAttrCodes(pStr, pStartIdx, pLen) if (typeof(pStartIdx) === "number" && pStartIdx >= 0 && pStartIdx < screenLen) startIdx = pStartIdx; - // Find the real start index. If there are Synchronet attribute - startIdx = printedToRealIdxInStr(pStr, startIdx); - if (startIdx < 0) - return ""; - // Find the actual length of the string to get - var printableCharCount = 0; - var syncAttrCount = 0; - var syncAttrRegexWholeWord = /^\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i; - var i = startIdx; - while ((printableCharCount < pLen) && (i < pStr.length)) - { - if (syncAttrRegexWholeWord.test(pStr.substr(i, 2))) + // Find the actual start & end indexes, considering (not counting) attribute codes, + // and return the substring including any applicable attributes from the string + var actualStartIdx = findIdxConsideringAttrs(pStr, startIdx); + var actualEndIdx = findIdxConsideringAttrs(pStr, startIdx+len+1) + 1; // Initially tried just startIdx+len without the +1 + return getAttrsBeforeStrIdx(pStr, actualStartIdx) + pStr.substring(actualStartIdx, actualEndIdx); +} +// Helper for substrWithAttrCodes(): Maps a 'visual' character index in a string to its +// actual index within the string, considering any attribute codes in the string. +// +// Parameters: +// pStr: A string +// pIndex: The index of a character as displayed on the screen, to be mapped to actual string index +// +// Return value: The character index mapped to the actual index within the string, considering attribute codes +function findIdxConsideringAttrs(pStr, pIndex) +{ + if (typeof(pStr) !== "string" || pStr.length == 0) + return 0; + if (typeof(pIndex) !== "number" || pIndex < 0) + return 0; + + var printableLen = console.strlen(pStr); + var index = (pIndex >= printableLen ? printableLen - 1 : pIndex); + + var actualIdx = 0; + var numTimesUpdated = 0; + for (var i = 0; i < pStr.length && numTimesUpdated <= index; ++i) + { + // If this character is the attribute control character, skip it along with + // the next character + if (pStr.charAt(i) == "\x01") { - ++syncAttrCount; - i += 2; + ++i; + continue; } + actualIdx = i; + ++numTimesUpdated; + } + // Alternate implementation: + /* + var previousChar = ""; + var syncAttrLen = 0; + var numTimesUpdated = 0; + for (var i = 0; i < pStr.length && numTimesUpdated <= index; ++i) + { + // If this character is the attribute control character, skip it along with + // the next character + var currentChar = pStr.charAt(i); + if (currentChar == "\x01") + syncAttrLen = 1; + else if (previousChar == "\x01") + ++syncAttrLen; else + syncAttrLen = 0; + if (syncAttrLen == 0) { - ++printableCharCount; - ++i; + actualIdx = i; + ++numTimesUpdated; } + previousChar = currentChar; } - len += (syncAttrCount * 2); - var shortenedStr = pStr.substr(startIdx, len); - // Include any attribute codes that might appear before the start index - // in the string - var attrIdx = pStr.lastIndexOf("\x01", startIdx); - if (attrIdx >= 0) + */ + return actualIdx; +} +// Helper for substrWithAttrCodes(): Returns a string with any Synchronet color/attribute +// codes found in a string before a given index. +// +// Parameters: +// pStr: The string to search in +// pIdx: The index in the string to search before +// +// Return value: A string containing any Synchronet attribute codes found before +// the given index in the given string +function getAttrsBeforeStrIdx(pStr, pIdx) +{ + if (typeof(pStr) !== "string") + return ""; + if (typeof(pIdx) !== "number") + return ""; + if (pIdx < 0) + return ""; + + var idx = (pIdx < pStr.length ? pIdx : pStr.length-1); + var attrStartIdx = strIdxOfSyncAttrBefore(pStr, idx, true, false); + var attrEndIdx = strIdxOfSyncAttrBefore(pStr, idx, false, false); // Start of 2-character code + var attrsStr = ""; + if ((attrStartIdx > -1) && (attrEndIdx > -1)) + attrsStr = pStr.substring(attrStartIdx, attrEndIdx+2); + return attrsStr; +} + +// Returns the index of the first Synchronet attribute code before a given index +// in a string. +// +// Parameters: +// pStr: The string to search in +// pIdx: The index to search back from +// pSeriesOfAttrs: Optional boolean - Whether or not to look for a series of +// attributes. Defaults to false (look for just one attribute). +// pOnlyInWord: Optional boolean - Whether or not to look only in the current word +// (with words separated by whitespace). Defaults to false. +// +// Return value: The index of the first Synchronet attribute code before the given +// index in the string, or -1 if there is none or if the parameters +// are invalid +function strIdxOfSyncAttrBefore(pStr, pIdx, pSeriesOfAttrs, pOnlyInWord) +{ + if (typeof(pStr) != "string") + return -1; + if (typeof(pIdx) != "number") + return -1; + if ((pIdx < 0) || (pIdx >= pStr.length)) + return -1; + + var seriesOfAttrs = (typeof(pSeriesOfAttrs) == "boolean" ? pSeriesOfAttrs : false); + var onlyInWord = (typeof(pOnlyInWord) == "boolean" ? pOnlyInWord : false); + + var attrCodeIdx = pStr.lastIndexOf("\x01", pIdx-1); + if (attrCodeIdx > -1) { - var attrStartIdx = -1; - // Generate a string of all Synchronet attributes at the found location - for (var i = attrIdx; i >= 0; i -= 2) + // If we are to only check the current word, then continue only if + // there isn't a space between the attribute code and the given index. + if (onlyInWord) { - if (syncAttrRegexWholeWord.test(pStr.substr(i, 2))) - attrStartIdx = i; - else - break; + if (pStr.lastIndexOf(" ", pIdx-1) >= attrCodeIdx) + attrCodeIdx = -1; } - if (attrStartIdx > -1) + } + if (attrCodeIdx > -1) + { + var syncAttrRegexWholeWord = /^\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i; + if (syncAttrRegexWholeWord.test(pStr.substr(attrCodeIdx, 2))) { - var attrStr = pStr.substring(attrStartIdx, attrIdx+2); - shortenedStr = attrStr + shortenedStr; + if (seriesOfAttrs) + { + for (var i = attrCodeIdx - 2; i >= 0; i -= 2) + { + if (syncAttrRegexWholeWord.test(pStr.substr(i, 2))) + attrCodeIdx = i; + else + break; + } + } } + else + attrCodeIdx = -1; } - return shortenedStr; + return attrCodeIdx; } // Converts a 'printed' index in a string to its real index in the string