diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js index 09e28af006de7ae219c186b50047514745d0f7d3..c406ef1ef6aabe2669d14958d890af9a0e188918 100644 --- a/exec/load/dd_lightbar_menu.js +++ b/exec/load/dd_lightbar_menu.js @@ -1007,7 +1007,7 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar, if (writeTheItem) { console.gotoxy(curPos.x, curPos.y++); - console.print("\x01n"); + console.attributes = "N"; if (this.numberedMode) printf(numberFormatStr, ""); var itemText = addAttrsToString(format(itemFormatStr, ""), this.colors.itemColor); @@ -1018,7 +1018,7 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar, } else { - // The user's terminal doesn't support ANSI + // ANSI mode disabled, or the user's terminal doesn't support ANSI var numberedModeBackup = this.numberedMode; this.numberedMode = true; var itemLen = this.size.width; @@ -1027,7 +1027,7 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar, this.itemNumLen = numMenuItems.toString().length; itemLen -= this.itemNumLen; --itemLen; // Have a space for separation between the numbers and items - console.print("\x01n"); + console.attributes = "N"; for (var i = 0; i < numMenuItems; ++i) { var showMultiSelectMark = (this.multiSelect && (typeof(pSelectedItemIndexes) == "object") && pSelectedItemIndexes.hasOwnProperty(idx)); @@ -1513,6 +1513,8 @@ function DDLightbarMenu_GetItemText(pIdx, pItemLen, pHighlight, pSelected) // If in numbered mode, prepend the item number to the front of the item text. if (this.numberedMode) { + if (this.itemNumLen == 0) + this.itemNumLen = numItems.toString().length; var numColor = "\x01n" + this.colors.itemNumColor; if (typeof(pHighlight) === "boolean") numColor = (pHighlight ? this.colors.highlightedItemNumColor : this.colors.itemNumColor); @@ -1528,7 +1530,7 @@ function DDLightbarMenu_GetItemText(pIdx, pItemLen, pHighlight, pSelected) function DDLightbarMenu_Erase() { var formatStr = "%" + this.size.width + "s"; // For use with printf() - console.print("\x01n"); + console.attributes = "N"; var curPos = { x: this.pos.x, y: this.pos.y }; for (var i = 0; i < this.size.height; ++i) { @@ -1665,13 +1667,13 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) if (this.callOnItemNavOnStartup && typeof(this.OnItemNav) === "function") this.OnItemNav(0, this.selectedItemIdx); + var selectedItemIndexes = { }; // For multi-select mode + if (typeof(pSelectedItemIndexes) == "object") + selectedItemIndexes = pSelectedItemIndexes; if (this.ANSISupported()) { // User input loop var userChoices = null; // For multi-select mode - var selectedItemIndexes = { }; // For multi-select mode - if (typeof(pSelectedItemIndexes) == "object") - selectedItemIndexes = pSelectedItemIndexes; var retVal = null; // For single-choice mode // mouseInputOnly_continue specifies whether to continue to the // next iteration if the mouse was clicked & there's no need to @@ -3416,26 +3418,31 @@ function getDefaultMenuItem() { // // Parameters: // pStr: The string to perform the substring on -// pLen: The length of the substring +// pLen: Optional: The length of the substring. If not specified, the rest of the string will be used. // // Return value: A substring of the string according to the parameters function substrWithAttrCodes(pStr, pStartIdx, pLen) { - if (typeof(pStr) != "string") - return ""; - if (typeof(pStartIdx) != "number") + if (typeof(pStr) !== "string") return ""; - if (typeof(pLen) != "number") + if (typeof(pStartIdx) !== "number") return ""; + var len = typeof(pLen) === "number" ? pLen : console.strlen(pStr)-pStartIdx; if ((pStartIdx <= 0) && (pLen >= console.strlen(pStr))) return pStr; + var startIdx = 0; + var screenLen = console.strlen(pStr); + if (typeof(pStartIdx) === "number" && pStartIdx >= 0 && pStartIdx < screenLen) + startIdx = pStartIdx; + var len = 0; + if (typeof(pLen) === "number" && pLen <= screenLen - startIdx) + len = pLen; // Find the real start index. If there are Synchronet attribute - var startIdx = printedToRealIdxInStr(pStr, pStartIdx); + startIdx = printedToRealIdxInStr(pStr, startIdx); if (startIdx < 0) return ""; // Find the actual length of the string to get - var len = pLen; var printableCharCount = 0; var syncAttrCount = 0; var syncAttrRegexWholeWord = /^\x01[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i; diff --git a/xtrn/ddfilelister/ddfilelister.cfg b/xtrn/ddfilelister/ddfilelister.cfg index 76cad4fdedb585a9695ba8f7dc38ac03ea37d55b..ebf0f7a51b90e40c780f3adaa93c2e0e5c475f4b 100644 --- a/xtrn/ddfilelister/ddfilelister.cfg +++ b/xtrn/ddfilelister/ddfilelister.cfg @@ -1,3 +1,12 @@ +; User interface style (lightbar or traditional) +interfaceStyle=lightbar + +; When using the traditional interface, use the Synchronet +; stock (don't use ddfilelister at all). Valid values are +; true or false. If this is false, ddfilelister's traditional +; interface will be used if interfaceStyle is traditional. +traditionalUseSyncStock=false + ; The sort order for the file list ; NATURAL: Natural sort order (same as DATE_A) ; NAME_AI: Filename ascending, case insensitive sort order diff --git a/xtrn/ddfilelister/ddfilelister.js b/xtrn/ddfilelister/ddfilelister.js index 1b0f6cd8895c7e6874811901d7c34c0cf478f063..292ee58f900b8d0f41171d0d5037ab071db8123f 100644 --- a/xtrn/ddfilelister/ddfilelister.js +++ b/xtrn/ddfilelister/ddfilelister.js @@ -72,49 +72,14 @@ * characters, without the control character. * * Future work: Actual support for a traditional/non-lightbar user interface + * 2023-07-29 Eric Oulashin Version 2.12 Beta + * Started working on implementing a traditional/non-lightbar UI + * 2023-08-12 Eric Oulashin Version 2.12 + * Releasing this version */ "use strict"; -if (typeof(require) === "function") -{ - require("sbbsdefs.js", "K_UPPER"); - require('key_defs.js', 'KEY_UP'); - require("text.js", "Email"); // Text string definitions (referencing text.dat) - require("dd_lightbar_menu.js", "DDLightbarMenu"); - require("frame.js", "Frame"); - require("scrollbar.js", "ScrollBar"); - require("mouse_getkey.js", "mouse_getkey"); - require("attr_conv.js", "convertAttrsToSyncPerSysCfg"); -} -else -{ - load("sbbsdefs.js"); - load('key_defs.js'); - load("text.js"); // Text string definitions (referencing text.dat) - load("dd_lightbar_menu.js"); - load("frame.js"); - load("scrollbar.js"); - load("mouse_getkey.js"); - load("attr_conv.js"); -} - - -/* -Configured in SCFG->System->Loadable Modules: -Scan Dirs: User scans one or more directories for (e.g. new) files -List Files: User lists files within a file directory -View File Info: User views detailed information on files in a directory - -This addresses/fixes feature request #521 for Nightfox - -Will need to document the mode argument bit values on the wiki, but -it's the usual suspects: FL_* for scandirs and listfiles and FI_* for -fileinfo. The scandirs_mod will be passed an extra bool (0/1) arg that -indicates whether or not the user is scanning *all* directories. -*/ - - // This script requires Synchronet version 3.19 or newer. // If the Synchronet version is below the minimum, then exit. if (system.version_num < 31900) @@ -133,9 +98,19 @@ if (system.version_num < 31900) exit(); } + +require("sbbsdefs.js", "K_UPPER"); +require('key_defs.js', 'KEY_UP'); +require("text.js", "Email"); // Text string definitions (referencing text.dat) +require("dd_lightbar_menu.js", "DDLightbarMenu"); +require("frame.js", "Frame"); +require("scrollbar.js", "ScrollBar"); +require("mouse_getkey.js", "mouse_getkey"); +require("attr_conv.js", "convertAttrsToSyncPerSysCfg"); + // Lister version information -var LISTER_VERSION = "2.11"; -var LISTER_DATE = "2023-05-14"; +var LISTER_VERSION = "2.12"; +var LISTER_DATE = "2023-08-12"; /////////////////////////////////////////////////////////////////////////////// @@ -192,7 +167,7 @@ var gColors = { confirmFileActionWindowBorder: "\x01r", confirmFileActionWindowWindowTitle: "\x01g", - fileAreaMenuBorder: "\x01b", + fileAreaMenuBorder: "\x01b", // File move menu for lightbar interface fileNormalBkg: "\x01" + "4", fileAreaNum: "\x01w", fileAreaDesc: "\x01w", @@ -201,7 +176,13 @@ var gColors = { fileAreaMenuHighlightBkg: "\x01" + "7", fileAreaNumHighlight: "\x01b", fileAreaDescHighlight: "\x01b", - fileAreaNumItemsHighlight: "\x01b" + fileAreaNumItemsHighlight: "\x01b", + + fileAreaMenuBorderTrad: "\x01b", // File move menu for traditional interface + fileNormalBkgTrad: "\x01n\x01w", + listNumTrad: "\x01g\x01h", + fileAreaDescTrad: "\x01c", + fileAreaNumItemsTrad: "\x01b\x01h" }; @@ -215,6 +196,11 @@ var QUIT = 6; var FILE_MOVE = 7; // Sysop action var FILE_DELETE = 8; // Sysop action +var NEXT_PAGE = 9; +var PREV_PAGE = 10; +var FIRST_PAGE = 11; +var LAST_PAGE = 12; + // Search/list modes var MODE_LIST_DIR = 1; var MODE_SEARCH_FILENAME = 2; @@ -254,6 +240,10 @@ var gPauseAfterViewingFile = true; // terminal supports ANSI) var gUseLightbarInterface = true; +// If using the traditional interface, whether to use Synchronet's stock +// file lister instead of ddfilelister +var gTraditionalUseSyncStock = false; + /////////////////////////////////////////////////////////////////////////////// // Script execution code @@ -274,10 +264,9 @@ readConfigFile(); // Parse command-line arguments (which sets program options) parseArgs(argv); -// If the user's terminal doesn't support ANSI, then just call the standard Synchronet -// file list function and exit now -// TODO: Add support in this script for a traditional/non-lightbar user interface -if (!console.term_supports(USER_ANSI) || !gUseLightbarInterface) +// If set to use the traditional (non-lightbar) UI and if set to use the Synchronet +// stock file lister, then do so instead of using ddfilelister's traditional UI +if ((!gUseLightbarInterface || !console.term_supports(USER_ANSI)) && gTraditionalUseSyncStock) { var exitCode = 0; if (gScriptMode == MODE_SEARCH_FILENAME || gScriptMode == MODE_SEARCH_DESCRIPTION || gScriptMode == MODE_NEW_FILE_SEARCH) @@ -302,7 +291,7 @@ if (listPopRetObj.exitNow) if (gFileList.length == 0) { console.crlf(); - console.print("\x01n\x01c"); + console.attributes = "NC"; if (gScriptMode == MODE_LIST_DIR) { if (gFilespec == "*" || gFilespec == "*.*") @@ -312,185 +301,408 @@ if (gFileList.length == 0) } else console.print("No files were found."); - console.print("\x01n"); + console.attributes = "N"; console.crlf(); console.pause(); exit(0); } - +// Construct and display the menu/command bar at the bottom of the screen +var fileMenuBar = new DDFileMenuBar({ x: 1, y: console.screen_rows }); // Clear the screen and display the header lines console.clear("\x01n"); if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) - displayFileLibAndDirHeader(); -// Construct and display the menu/command bar at the bottom of the screen -var fileMenuBar = new DDFileMenuBar({ x: 1, y: console.screen_rows }); -fileMenuBar.writePromptLine(); -// Create the file list menu + displayFileLibAndDirHeader(false, null, !gUseLightbarInterface || !console.term_supports(USER_ANSI)); +// Create the file list menu (must be done after displayFileLibAndDirHeader() when using ANSI and lightbar) var gFileListMenu = createFileListMenu(fileMenuBar.getAllActionKeysStr(true, true) + KEY_LEFT + KEY_RIGHT + KEY_DEL); -// In a loop, show the file list menu, allowing the user to scroll the file list, -// and respond to user input until the user decides to quit. -gFileListMenu.Draw({}); -// If using extended descriptions, write the first file's description on the screen -if (extendedDescEnabled()) - displayFileExtDescOnMainScreen(0); -var continueDoingFileList = true; -var drawFileListMenu = false; // For screen refresh optimization -while (continueDoingFileList) +if (gUseLightbarInterface && console.term_supports(USER_ANSI)) { - // Clear the menu's selected item indexes so it's 'fresh' for this round - for (var prop in gFileListMenu.selectedItemIndexes) - delete gFileListMenu.selectedItemIndexes[prop]; - var actionRetObj = null; - var currentActionVal = null; - var userChoice = gFileListMenu.GetVal(drawFileListMenu, gFileListMenu.selectedItemIndexes); - drawFileListMenu = false; // For screen refresh optimization - var lastUserInputUpper = gFileListMenu.lastUserInput != null ? gFileListMenu.lastUserInput.toUpperCase() : null; - if (lastUserInputUpper == null || lastUserInputUpper == "Q") - continueDoingFileList = false; - else if (lastUserInputUpper == KEY_LEFT) - fileMenuBar.decrementMenuItemAndRefresh(); - else if (lastUserInputUpper == KEY_RIGHT) - fileMenuBar.incrementMenuItemAndRefresh(); - else if (lastUserInputUpper == KEY_ENTER) - { - currentActionVal = fileMenuBar.getCurrentSelectedAction(); - fileMenuBar.setCurrentActionCode(currentActionVal); - actionRetObj = doAction_ANSI(currentActionVal, gFileList, gFileListMenu); - } - // Allow the delete key as a special key for sysops to delete the selected file(s). Also allow backspace - // due to some terminals returning backspace for delete. - else if (lastUserInputUpper == KEY_DEL || lastUserInputUpper == KEY_BACKSPACE) + fileMenuBar.writePromptLine(); + // In a loop, show the file list menu, allowing the user to scroll the file list, + // and respond to user input until the user decides to quit. + gFileListMenu.Draw({}); + // If using extended descriptions, write the first file's description on the screen + if (extendedDescEnabled()) + displayFileExtDescOnMainScreen(0); + var continueDoingFileList = true; + var drawFileListMenu = false; // For screen refresh optimization + while (continueDoingFileList) { - if (user.is_sysop) + // Clear the menu's selected item indexes so it's 'fresh' for this round + for (var prop in gFileListMenu.selectedItemIndexes) + delete gFileListMenu.selectedItemIndexes[prop]; + var actionRetObj = null; + var currentActionVal = null; + var userChoice = gFileListMenu.GetVal(drawFileListMenu, gFileListMenu.selectedItemIndexes); + drawFileListMenu = false; // For screen refresh optimization + var lastUserInputUpper = gFileListMenu.lastUserInput != null ? gFileListMenu.lastUserInput.toUpperCase() : null; + if (lastUserInputUpper == null || lastUserInputUpper == "Q") + continueDoingFileList = false; + else if (lastUserInputUpper == KEY_LEFT) + fileMenuBar.decrementMenuItemAndRefresh(); + else if (lastUserInputUpper == KEY_RIGHT) + fileMenuBar.incrementMenuItemAndRefresh(); + else if (lastUserInputUpper == KEY_ENTER) { - fileMenuBar.setCurrentActionCode(FILE_DELETE, true); - actionRetObj = doAction_ANSI(FILE_DELETE, gFileList, gFileListMenu); - currentActionVal = FILE_DELETE; + currentActionVal = fileMenuBar.getCurrentSelectedAction(); + fileMenuBar.setCurrentActionCode(currentActionVal); + actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); } - } - else - { - currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); - fileMenuBar.setCurrentActionCode(currentActionVal, true); - actionRetObj = doAction_ANSI(currentActionVal, gFileList, gFileListMenu); - } - // If an action was done (actionRetObj is not null), then look at actionRetObj and - // do what's needed. Note that quit (for the Q key) is already handled. - if (actionRetObj != null) - { - if (actionRetObj.exitNow) - continueDoingFileList = false; - else + // Allow the delete key as a special key for sysops to delete the selected file(s). Also allow backspace + // due to some terminals returning backspace for delete. + else if (lastUserInputUpper == KEY_DEL || lastUserInputUpper == KEY_BACKSPACE) { - if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) - { - if (actionRetObj.reDrawHeaderTextOnly) - { - console.print("\x01n"); - displayFileLibAndDirHeader(true); // Will move the cursor where it needs to be - } - else if (actionRetObj.reDrawListerHeader) - { - console.print("\x01n"); - console.gotoxy(1, 1); - displayFileLibAndDirHeader(); - } - } - if (actionRetObj.reDrawCmdBar) // Could call fileMenuBar.constructPromptText(); if needed - fileMenuBar.writePromptLine(); - var redrewPartOfFileListMenu = false; - // If we are to re-draw the main screen content, then - // enable the flag to draw the file list menu on the next - // GetVal(); also, if extended descriptions are being shown, - // write the current file's extended description too. - if (actionRetObj.reDrawMainScreenContent) + if (user.is_sysop) { - drawFileListMenu = true; - if (extendedDescEnabled()) - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); + fileMenuBar.setCurrentActionCode(FILE_DELETE, true); + actionRetObj = doAction(FILE_DELETE, gFileList, gFileListMenu); + currentActionVal = FILE_DELETE; } + } + else + { + currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); + fileMenuBar.setCurrentActionCode(currentActionVal, true); + actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); + } + // If an action was done (actionRetObj is not null), then look at actionRetObj and + // do what's needed. Note that quit (for the Q key) is already handled. + if (actionRetObj != null) + { + if (actionRetObj.exitNow) + continueDoingFileList = false; else { - // If there is partial redraw information available, then use it - // to re-draw that part of the main screen - if (actionRetObj.fileListPartialRedrawInfo != null) + if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) { - drawFileListMenu = false; - var startX = actionRetObj.fileListPartialRedrawInfo.absStartX; - var startY = actionRetObj.fileListPartialRedrawInfo.absStartY; - var width = actionRetObj.fileListPartialRedrawInfo.width; - var height = actionRetObj.fileListPartialRedrawInfo.height; - refreshScreenMainContent(startX, startY, width, height, true); - actionRetObj.refreshedSelectedFilesAlready = true; - redrewPartOfFileListMenu = true; + if (actionRetObj.reDrawHeaderTextOnly) + { + console.attributes = "N"; + displayFileLibAndDirHeader(true, null, gFileListMenu.numberedMode); // Will move the cursor where it needs to be + } + else if (actionRetObj.reDrawListerHeader) + { + console.attributes = "N"; + console.gotoxy(1, 1); + displayFileLibAndDirHeader(false, null, gFileListMenu.numberedMode); + } + } + if (actionRetObj.reDrawCmdBar) // Could call fileMenuBar.constructPromptText(); if needed + fileMenuBar.writePromptLine(); + var redrewPartOfFileListMenu = false; + // If we are to re-draw the main screen content, then + // enable the flag to draw the file list menu on the next + // GetVal(); also, if extended descriptions are being shown, + // write the current file's extended description too. + if (actionRetObj.reDrawMainScreenContent) + { + drawFileListMenu = true; + if (extendedDescEnabled()) + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); } else { - // Partial screen re-draw information was not returned. - continueDoingFileList = actionRetObj.continueFileLister; - drawFileListMenu = actionRetObj.reDrawMainScreenContent; - // If displaying extended descriptions and the user deleted some files, then - // refresh the file description area to erase the delete confirmation text - if (extendedDescEnabled()/* && currentActionVal == FILE_DELETE*/) + // If there is partial redraw information available, then use it + // to re-draw that part of the main screen + if (actionRetObj.fileListPartialRedrawInfo != null) { - if (actionRetObj.hasOwnProperty("filesDeleted") && actionRetObj.filesDeleted) - { - var numFiles = gFileListMenu.NumItems(); - if (numFiles > 0 && gFileListMenu.selectedItemIdx >= 0 && gFileListMenu.selectedItemIdx < numFiles) - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); - } - else + drawFileListMenu = false; + var startX = actionRetObj.fileListPartialRedrawInfo.absStartX; + var startY = actionRetObj.fileListPartialRedrawInfo.absStartY; + var width = actionRetObj.fileListPartialRedrawInfo.width; + var height = actionRetObj.fileListPartialRedrawInfo.height; + refreshScreenMainContent(startX, startY, width, height, true); + actionRetObj.refreshedSelectedFilesAlready = true; + redrewPartOfFileListMenu = true; + } + else + { + // Partial screen re-draw information was not returned. + continueDoingFileList = actionRetObj.continueFileLister; + drawFileListMenu = actionRetObj.reDrawMainScreenContent; + // If displaying extended descriptions and the user deleted some files, then + // refresh the file description area to erase the delete confirmation text + if (extendedDescEnabled()/* && currentActionVal == FILE_DELETE*/) { - var firstLine = startY + gFileListMenu.pos.y; - var lastLine = console.screen_rows - 1; - var width = console.screen_columns - gFileListMenu.size.width - 1; - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstLine, lastLine, width); + if (actionRetObj.hasOwnProperty("filesDeleted") && actionRetObj.filesDeleted) + { + var numFiles = gFileListMenu.NumItems(); + if (numFiles > 0 && gFileListMenu.selectedItemIdx >= 0 && gFileListMenu.selectedItemIdx < numFiles) + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); + } + else + { + var firstLine = startY + gFileListMenu.pos.y; + var lastLine = console.screen_rows - 1; + var width = console.screen_columns - gFileListMenu.size.width - 1; + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstLine, lastLine, width); + } } } } - } - // Remove checkmarks from any selected files in the file menu. - // For efficiency, we'd probably only do this if not re-drawing the wohle - // menu, but that's not working for now. - if (!actionRetObj.refreshedSelectedFilesAlready && /*!drawFileListMenu &&*/ gFileListMenu.numSelectedItemIndexes() > 0) - { - var bottomItemIdx = gFileListMenu.GetBottomItemIdx(); - var redrawTopY = -1; - var redrawBottomY = -1; - if (actionRetObj.fileListPartialRedrawInfo != null) - { - redrawTopY = actionRetObj.fileListPartialRedrawInfo.absStartY; - redrawBottomY = actionRetObj.fileListPartialRedrawInfo.height + height - 1; - } - for (var idx in gFileListMenu.selectedItemIndexes) + // Remove checkmarks from any selected files in the file menu. + // For efficiency, we'd probably only do this if not re-drawing the wohle + // menu, but that's not working for now. + if (!actionRetObj.refreshedSelectedFilesAlready && /*!drawFileListMenu &&*/ gFileListMenu.numSelectedItemIndexes() > 0) { - var idxNum = +idx; - if (idxNum >= gFileListMenu.topItemIdx && idxNum <= bottomItemIdx) + var bottomItemIdx = gFileListMenu.GetBottomItemIdx(); + var redrawTopY = -1; + var redrawBottomY = -1; + if (actionRetObj.fileListPartialRedrawInfo != null) { - var drawItem = true; - if (redrawTopY > -1 && redrawBottomY > redrawTopY) - { - var screenRowForItem = gFileListMenu.ScreenRowForItem(idxNum); - drawItem = (screenRowForItem < redrawTopY || screenRowForItem > redrawBottomY) - } - if (drawItem) + redrawTopY = actionRetObj.fileListPartialRedrawInfo.absStartY; + redrawBottomY = actionRetObj.fileListPartialRedrawInfo.height + height - 1; + } + for (var idx in gFileListMenu.selectedItemIndexes) + { + var idxNum = +idx; + if (idxNum >= gFileListMenu.topItemIdx && idxNum <= bottomItemIdx) { - var isSelected = (idxNum == gFileListMenu.selectedItemIdx); - gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); + var drawItem = true; + if (redrawTopY > -1 && redrawBottomY > redrawTopY) + { + var screenRowForItem = gFileListMenu.ScreenRowForItem(idxNum); + drawItem = (screenRowForItem < redrawTopY || screenRowForItem > redrawBottomY) + } + if (drawItem) + { + var isSelected = (idxNum == gFileListMenu.selectedItemIdx); + gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); + } + else + console.print("\x01n\r\nNot drawing idx " + idxNum + "\r\n\x01p"); } - else - console.print("\x01n\r\nNot drawing idx " + idxNum + "\r\n\x01p"); } } + // If part of the file list menu was re-drawn (partially, not completely), move the cursor + // to the lower-right corner of the screen so that it's out of the way + if (redrewPartOfFileListMenu) + console.gotoxy(console.screen_columns-1, console.screen_rows); } - // If part of the file list menu was re-drawn (partially, not completely), move the cursor - // to the lower-right corner of the screen so that it's out of the way - if (redrewPartOfFileListMenu) - console.gotoxy(console.screen_columns-1, console.screen_rows); } } } +else +{ + // Traditional UI + var exitCode = 0; + + console.crlf(); + + // An array containing text descriptions for all files, which may include + // multiple lines for files with an extended description (if enabled) + var allFileInfoLines = []; + for (var i = 0; i < gFileList.length; ++i) + allFileInfoLines = allFileInfoLines.concat(getFileInfoLineArrayForTraditionalUI(gFileList, i)); + + // Number of files per page, assuming 1-line descriptions; 3 lines for top + // header and 1 line for bottom key help line + var numLinesPerPage = console.screen_rows - 4; + var topItemIdx = 0; + var topItemIndexForLastPage = allFileInfoLines.length - numLinesPerPage; + + // Allowed keys for user input + var validOptionKeys = "IVBDMNPFLQ?" + KEY_PAGEDN + KEY_PAGEUP + KEY_HOME + KEY_END; + if (user.is_sysop) + validOptionKeys += KEY_DEL + KEY_BACKSPACE; + // If the user's terminal supports ANSI, then also allow left, right, and enter (option navigation & selection) + if (console.term_supports(USER_ANSI)) + validOptionKeys += KEY_LEFT + KEY_RIGHT + KEY_ENTER; + + // User input loop + var continueOn = true; + var drawDirHeaderLines = false; + var drawMenu = true; + var refreshWholePromptLine = true; + while (continueOn) + { + if (drawDirHeaderLines && (gListBehavior & FL_NO_HDR) != FL_NO_HDR) + { + if (console.term_supports(USER_ANSI)) + console.clear("\x01n"); + displayFileLibAndDirHeader(false, null, true); + console.crlf(); + } + if (drawMenu) + { + // Draw the current page of items + console.line_counter = 0; + var lastItemIdx = topItemIdx + numLinesPerPage - 1; + for (var i = topItemIdx; i <= lastItemIdx; ++i) + { + console.print(allFileInfoLines[i]); + console.crlf(); + } + } + + if (refreshWholePromptLine || drawMenu) + fileMenuBar.pos = console.getxy(); + if (refreshWholePromptLine) + fileMenuBar.writePromptLine(); + var userInput = console.getkeys(validOptionKeys, -1, K_UPPER|K_NOECHO|K_NOSPIN|K_NOCRLF).toString(); + // If the user pressed the enter key, change userInput to the key + // corresponding to what we'd expect for that option + if (userInput == KEY_ENTER) + { + //currentActionVal = fileMenuBar.getCurrentSelectedAction(); + switch (fileMenuBar.getCurrentSelectedAction()) + { + case NEXT_PAGE: + userInput = KEY_PAGEDN; + break; + case PREV_PAGE: + userInput = KEY_PAGEUP; + break; + case FIRST_PAGE: + userInput = "F"; + break; + case LAST_PAGE: + userInput = "L"; + break; + } + } + // Check action based on the user's last input + if (userInput == KEY_LEFT) + { + fileMenuBar.decrementMenuItemAndRefresh(); + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + else if (userInput == KEY_RIGHT) + { + fileMenuBar.incrementMenuItemAndRefresh(); + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + else if (userInput == KEY_ENTER) + { + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + currentActionVal = fileMenuBar.getCurrentSelectedAction(); + fileMenuBar.setCurrentActionCode(currentActionVal); + actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); + } + // Allow the delete key as a special key for sysops to delete the selected file(s). Also allow backspace + // due to some terminals returning backspace for delete. + else if (userInput == KEY_DEL || userInput == KEY_BACKSPACE) + { + if (user.is_sysop) + { + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + fileMenuBar.setCurrentActionCode(FILE_DELETE, true); + actionRetObj = doAction(FILE_DELETE, gFileList, gFileListMenu); + currentActionVal = FILE_DELETE; + } + else + { + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + } + else if (userInput == "Q") + continueOn = false; + else if (userInput == "N" || userInput == KEY_PAGEDN) + { + // Next page + if (topItemIdx < topItemIndexForLastPage) + { + topItemIdx += numLinesPerPage; + if (topItemIdx > topItemIndexForLastPage) + topItemIdx = topItemIndexForLastPage; + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + } + else + { + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + currentActionVal = NEXT_PAGE; + //fileMenuBar.setCurrentActionCode(NEXT_PAGE, !refreshWholePromptLine); + fileMenuBar.setCurrentActionCode(NEXT_PAGE, true); + } + else if (userInput == "P" || userInput == KEY_PAGEUP) + { + // Previous page + if (topItemIdx > 0) + { + topItemIdx -= numLinesPerPage; + if (topItemIdx < 0) + topItemIdx = 0; + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + } + else + { + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + currentActionVal = PREV_PAGE; + //fileMenuBar.setCurrentActionCode(PREV_PAGE, !refreshWholePromptLine); + fileMenuBar.setCurrentActionCode(PREV_PAGE, true); + } + else if (userInput == "F" || userInput == KEY_HOME) + { + // First page + if (topItemIdx > 0) + { + topItemIdx = 0; + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + } + else + { + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + currentActionVal = FIRST_PAGE; + //fileMenuBar.setCurrentActionCode(FIRST_PAGE, !refreshWholePromptLine); + fileMenuBar.setCurrentActionCode(FIRST_PAGE, true); + } + else if (userInput == "L" || userInput == KEY_END) + { + // Last page + if (topItemIdx < topItemIndexForLastPage) + { + topItemIdx = topItemIndexForLastPage; + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + } + else + { + drawDirHeaderLines = false; + drawMenu = false; + refreshWholePromptLine = false; + } + currentActionVal = LAST_PAGE; + //fileMenuBar.setCurrentActionCode(LAST_PAGE, !refreshWholePromptLine); + fileMenuBar.setCurrentActionCode(LAST_PAGE, true); + } + else + { + drawDirHeaderLines = true; + drawMenu = true; + refreshWholePromptLine = true; + currentActionVal = fileMenuBar.getActionFromChar(userInput, false); + fileMenuBar.setCurrentActionCode(currentActionVal, true); + actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); + } + } + exit(exitCode); +} @@ -498,7 +710,8 @@ while (continueDoingFileList) /////////////////////////////////////////////////////////////////////////////// // Functions: File actions -// Performs a specified file action based on an action code. For the ANSI user interface. +// Performs a specified file action based on an action code. +// This works for both the lightbar/ANSI and traditional/non-lightbar UI // // Parameters: // pActionCode: A code specifying an action to do. Must be one of the global @@ -508,25 +721,42 @@ while (continueDoingFileList) // // Return value: An object with values to indicate status & screen refresh actions; see // getDefaultActionRetObj() for details. -function doAction_ANSI(pActionCode, pFileList, pFileListMenu) +function doAction(pActionCode, pFileList, pFileListMenu) { if (typeof(pActionCode) !== "number") return getDefaultActionRetObj(); + if (pActionCode < 0) + return getDefaultActionRetObj(); - var fileMetadata = pFileList[pFileListMenu.selectedItemIdx]; + // If not using the ANSI interface, then prompt the user for the file number + // (for options that need one) + var useANSIInterface = gUseLightbarInterface && console.term_supports(USER_ANSI); + var fileIdx = pFileListMenu.selectedItemIdx; + if (!useANSIInterface && pActionCode != QUIT && pActionCode != HELP && pActionCode != NEXT_PAGE && pActionCode != PREV_PAGE) + { + var numMenuItems = pFileListMenu.NumItems(); + console.attributes = "N"; + console.crlf(); + console.print("\x01cFile # (\x01h1-" + numMenuItems + "\x01n\x01c)\x01h\x01g: \x01g"); + fileIdx = console.getnum(numMenuItems, 1) - 1; + pFileListMenu.selectedItemIdx = fileIdx; + console.attributes = "N"; + } + + var fileMetadata = pFileList[fileIdx]; var retObj = null; switch (pActionCode) { case FILE_VIEW_INFO: - retObj = showFileInfo_ANSI(fileMetadata); + retObj = useANSIInterface ? showFileInfo_ANSI(fileMetadata) : showFileInfo_noANSI(fileMetadata); break; case FILE_VIEW: - retObj = viewFile_ANSI(fileMetadata); + retObj = viewFile(fileMetadata); break; case FILE_ADD_TO_BATCH_DL: if (userCanDownloadFromFileArea_ShowErrorIfNot(fileMetadata.dirCode)) - retObj = addSelectedFilesToBatchDLQueue_ANSI(fileMetadata, pFileList); + retObj = addSelectedFilesToBatchDLQueue(fileMetadata, pFileList); else { retObj = getDefaultActionRetObj(); @@ -538,7 +768,7 @@ function doAction_ANSI(pActionCode, pFileList, pFileListMenu) break; case FILE_DOWNLOAD_SINGLE: if (userCanDownloadFromFileArea_ShowErrorIfNot(fileMetadata.dirCode) && pFileListMenu.selectedItemIdx >= 0 && pFileListMenu.selectedItemIdx < pFileListMenu.NumItems()) - retObj = letUserDownloadSelectedFile_ANSI(fileMetadata); + retObj = letUserDownloadSelectedFile(fileMetadata); else { retObj = getDefaultActionRetObj(); @@ -557,11 +787,11 @@ function doAction_ANSI(pActionCode, pFileList, pFileListMenu) break; case FILE_MOVE: // Sysop action if (user.is_sysop) - retObj = chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu); + retObj = chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu); break; case FILE_DELETE: // Sysop action if (user.is_sysop) - retObj = confirmAndRemoveFilesFromFilebase_ANSI(pFileList, pFileListMenu); + retObj = confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu); break; } @@ -603,7 +833,7 @@ function getDefaultActionRetObj() }; } -// Shows extended information about a file to the user. +// Shows extended information about a file to the user (ANSI version). // // Parameters: // pFileMetadata: The file metadata object for the file to view information about @@ -757,6 +987,128 @@ function showFileInfo_ANSI(pFileMetadata) return retObj; } +// Shows extended information about a file to the user (non-ANSI/traditional version). +// +// Parameters: +// pFileMetadata: The file metadata object for the file to view information about +// +// Return value: An object with values to indicate status & screen refresh actions; see +// getDefaultActionRetObj() for details. +function showFileInfo_noANSI(pFileMetadata) +{ + var retObj = getDefaultActionRetObj(); + + // pFileList[pFileListMenu.selectedItemIdx] has a file metadata object without + // extended information. Get a metadata object with extended information so we + // can display the extended description. + // The metadata object in pFileList should have a dirCode added by this script. + var dirCode = gDirCode; + if (pFileMetadata.hasOwnProperty("dirCode")) + dirCode = pFileMetadata.dirCode; + var fileMetadata = null; + if (extendedDescEnabled()) + fileMetadata = pFileMetadata; + else + fileMetadata = getFileInfoFromFilebase(dirCode, pFileMetadata.name, FileBase.DETAIL.EXTENDED); + + console.print("\x01n\x01wFilename:\r\n"); + console.print(gColors.filename + fileMetadata.name + "\x01n\x01w\r\n"); + console.print("Size: " + gColors.fileSize + getFileSizeStr(fileMetadata.size, 99999) + "\x01n\x01w\r\n"); + console.print("Timestamp: " + gColors.fileTimestamp + strftime("%Y-%m-%d %H:%M:%S", fileMetadata.time) + "\x01n\x01w\r\n"); + console.crlf(); + + // File library/directory information + var libIdx = file_area.dir[dirCode].lib_index; + var dirIdx = file_area.dir[dirCode].index; + var libDesc = file_area.lib_list[libIdx].description; + var dirDesc = file_area.dir[dirCode].description; + console.print("\x01c\x01hLib\x01g: \x01n\x01c" + libDesc + "\x01n\x01w\r\n"); + console.print("\x01c\x01hDir\x01g: \x01n\x01c" + dirDesc + "\x01n\x01w\r\n"); + console.crlf(); + + // fileMetadata should have extdDesc, but check just in case + var fileDesc = ""; + if (fileMetadata.hasOwnProperty("extdesc") && fileMetadata.extdesc.length > 0) + fileDesc = fileMetadata.extdesc; + else + fileDesc = fileMetadata.desc; + // It's possible for fileDesc to be undefined (due to extDesc or desc being undefined), + // so make sure it's a string. + // Also, if it's a string, reformat certain types of strings that don't look good in a + // Frame object + if (typeof(fileDesc) === "string") + { + // Check to see if it starts with a normal attribute and remove if so, + // since that seems to cause problems with displaying the description in a Frame object. This + // may be a kludge, and perhaps there's a better solution.. + fileDesc = fileDesc.replace(/^\x01[nN]/, ""); + // Fix line endings if necessary + fileDesc = lfexpand(fileDesc); + } + else + fileDesc = ""; + // This might be overkill, but just in case, convert any non-Synchronet + // attribute codes to Synchronet attribute codes in the description. + if (!fileMetadata.hasOwnProperty("attrsConverted")) + { + fileDesc = convertAttrsToSyncPerSysCfg(fileDesc); + fileMetadata.attrsConverted = true; + if (fileMetadata.hasOwnProperty("extdesc")) + fileMetadata.extdesc = fileDesc; + else + fileMetadata.desc = fileDesc; + } + + console.print(gColors.desc); + if (fileDesc.length > 0) + console.print("Description:\r\n" + fileDesc); // Don't want to use strip_ctrl(fileDesc) + else + console.print("No description available"); + console.crlf(); + // # of times downloaded and last downloaded date/time + var fieldFormatStr = "\x01n\x01c\x01h%s\x01g:\x01n\x01c %s\x01n\r\n"; + var timesDownloaded = fileMetadata.hasOwnProperty("times_downloaded") ? fileMetadata.times_downloaded : 0; + printf(fieldFormatStr, "Times downloaded", timesDownloaded); + if (fileMetadata.hasOwnProperty("last_downloaded")) + printf(fieldFormatStr, "Last downloaded", strftime("%Y-%m-%d %H:%M", fileMetadata.last_downloaded)); + // Some more fields for the sysop + if (user.is_sysop) + { + var sysopFields = [ "from", "cost", "added"]; + for (var sI = 0; sI < sysopFields.length; ++sI) + { + var prop = sysopFields[sI]; + if (fileMetadata.hasOwnProperty(prop)) + { + if (typeof(fileMetadata[prop]) === "string" && fileMetadata[prop].length == 0) + continue; + var propName = prop.charAt(0).toUpperCase() + prop.substr(1); + var infoValue = ""; + if (prop == "added") + infoValue = strftime("%Y-%m-%d %H:%M:%S", fileMetadata.added); + else + infoValue = fileMetadata[prop].toString(); + printf(fieldFormatStr, propName, infoValue); + console.attributes = "NW"; + } + } + } + console.attributes = "NW"; + console.crlf(); + + // Construct the file list redraw info. Note that the X and Y are relative + // to the file list menu, not absolute screen coordinates. + retObj.fileListPartialRedrawInfo = { + startX: 1, + startY: 1, + absStartX: 1, + absStartY: 1, + width: 0, + height: 0 + }; + + return retObj; +} // Splits a string on a given string and then re-combines the string with \r\n (carriage return & newline) // at the end of each line // @@ -785,14 +1137,15 @@ function splitStrAndCombineWithRN(pStr, pSplitStr) return newStr; } -// Lets the user view a file. +// Lets the user view a file. Note that this function works for both the ANSI/lightbar +// and traditional user interface. // // Parameters: // pFileMetadata: The file metadata object for the file to view // // Return value: An object with values to indicate status & screen refresh actions; see // getDefaultActionRetObj() for details. -function viewFile_ANSI(pFileMetadata) +function viewFile(pFileMetadata) { var retObj = getDefaultActionRetObj(); @@ -806,16 +1159,19 @@ function viewFile_ANSI(pFileMetadata) } else { - displayMsg("Failed to open the filebase!", true, true); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + displayMsg("Failed to open the filebase!", true, true); + else + console.print("\x01n" + gColors.errorMessage + "Failed to open the filebase!\x01n\r\n\x01p"); return retObj; } // View the file console.gotoxy(1, console.screen_rows); - console.print("\x01n"); + console.attributes = "N"; console.crlf(); var successfullyViewed = bbs.view_file(fullyPathedFilename); - console.print("\x01n"); + console.attributes = "N"; if (gPauseAfterViewingFile || !successfullyViewed) console.pause(); @@ -825,7 +1181,8 @@ function viewFile_ANSI(pFileMetadata) return retObj; } -// Allows the user to add their selected file to their batch downloaded queue +// Allows the user to add their selected file to their batch downloaded queue. Note that this can work +// with both the ANSI/lightbar interface or traditional interface. // // Parameters: // pFileMetadata: The file metadata object for the file @@ -833,7 +1190,7 @@ function viewFile_ANSI(pFileMetadata) // // Return value: An object with values to indicate status & screen refresh actions; see // getDefaultActionRetObj() for details. -function addSelectedFilesToBatchDLQueue_ANSI(pFileMetadata, pFileList) +function addSelectedFilesToBatchDLQueue(pFileMetadata, pFileList) { var retObj = getDefaultActionRetObj(); if (!userCanDownloadFromFileArea_ShowErrorIfNot(pFileMetadata.dirCode)) @@ -859,7 +1216,7 @@ function addSelectedFilesToBatchDLQueue_ANSI(pFileMetadata, pFileList) } // Note that confirmFileActionWithUser() will re-draw the parts of the file // list menu that are necessary. - var addFilesConfirmed = confirmFileActionWithUser(filenames, "Batch DL add", false); + var addFilesConfirmed = addFilesConfirmed = confirmFileActionWithUser(filenames, "Batch DL add", false); retObj.refreshedSelectedFilesAlready = true; if (addFilesConfirmed) { @@ -869,7 +1226,10 @@ function addSelectedFilesToBatchDLQueue_ANSI(pFileMetadata, pFileList) var batchDLFile = new File(batchDLFilename); if (batchDLFile.open(batchDLFile.exists ? "r+" : "w+")) { - displayMsg("Adding file(s) to batch DL queue..", false, false); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + displayMsg("Adding file(s) to batch DL queue..", false, false); + else + console.print("\x01n\x01cAdding file(s) to batch DL queue..\x01n\r\n"); for (var i = 0; i < metadataObjects.length; ++i) { // If the file isn't in the user's batch DL queue already, then add it. @@ -903,108 +1263,170 @@ function addSelectedFilesToBatchDLQueue_ANSI(pFileMetadata, pFileList) batchDLFile.close(); } - // Frame location & size for batch DL queue stats or filenames that failed - var frameUpperLeftX = gFileListMenu.pos.x + 2; - var frameUpperLeftY = gFileListMenu.pos.y + 2; - var frameWidth = console.screen_columns - 4; // Used to be gFileListMenu.size.width - 4; - var frameInnerWidth = frameWidth - 2; // Without borders - var frameHeight = 8; - - // If there were no failures, then show a success message & prompt the user if they - // want to download their batch queue. Otherwise, show the filenames that failed to - // get added. - if (filenamesFailed.length == 0) + // For ANSI/lightbar: Show a message box + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) { - displayMsg("Your batch DL queue was sucessfully updated", false, true); - // Prompt if the user wants to download their batch queue - if (bbs.batch_dnload_total > 0) + // Frame location & size for batch DL queue stats or filenames that failed + var frameUpperLeftX = gFileListMenu.pos.x + 2; + var frameUpperLeftY = gFileListMenu.pos.y + 2; + var frameWidth = console.screen_columns - 4; // Used to be gFileListMenu.size.width - 4; + var frameInnerWidth = frameWidth - 2; // Without borders + var frameHeight = 8; + + // If there were no failures, then show a success message & prompt the user if they + // want to download their batch queue. Otherwise, show the filenames that failed to + // get added. + if (filenamesFailed.length == 0) { - // Clear most of the screen area so the user has focus on the batch DL queue stats - var fullLineFormatStr = "%" + console.screen_columns + "s"; - var leftFormatStr = "%" + frameUpperLeftX + "s"; - var rightFormatStr = "%" + +(frameUpperLeftX+frameWidth-1) + "s"; - var lastFrameRow = frameUpperLeftY + frameHeight - 1; - var lastRow = console.screen_rows - 1; - console.print("\x01n"); - for (var screenRow = gNumHeaderLinesDisplayed+1; screenRow <= lastRow; ++screenRow) + displayMsg("Your batch DL queue was sucessfully updated", false, true); + // Prompt if the user wants to download their batch queue + if (bbs.batch_dnload_total > 0) { - console.gotoxy(1, screenRow); - if (screenRow < frameUpperLeftY || screenRow > lastFrameRow) - printf(fullLineFormatStr, ""); - else + // Clear most of the screen area so the user has focus on the batch DL queue stats + var fullLineFormatStr = "%" + console.screen_columns + "s"; + var leftFormatStr = "%" + frameUpperLeftX + "s"; + var rightFormatStr = "%" + +(frameUpperLeftX+frameWidth-1) + "s"; + var lastFrameRow = frameUpperLeftY + frameHeight - 1; + var lastRow = console.screen_rows - 1; + console.attributes = "N"; + for (var screenRow = gNumHeaderLinesDisplayed+1; screenRow <= lastRow; ++screenRow) { - printf(leftFormatStr, ""); - console.gotoxy(frameUpperLeftX+frameWidth, screenRow); - printf(rightFormatStr, ""); + console.gotoxy(1, screenRow); + if (screenRow < frameUpperLeftY || screenRow > lastFrameRow) + printf(fullLineFormatStr, ""); + else + { + printf(leftFormatStr, ""); + console.gotoxy(frameUpperLeftX+frameWidth, screenRow); + printf(rightFormatStr, ""); + } } - } - // Build a frame with batch DL queue stats and prompt the user if they want to - // download their batch DL queue - var frameTitle = "Download your batch queue (Y/N)?"; - // \x01cFiles: \x01h1 \x01n\x01c(\x01h100 \x01n\x01cMax) Credits: 0 Bytes: \x01h2,228,254 \x01n\x01c Time: 00:09:40 - // Note: The maximum number of allowed files in the batch download queue doesn't seem to - // be available to JavaScript. - var totalQueueSize = batchDLQueueStats.totalSize + pFileMetadata.size; - var totalQueueCost = batchDLQueueStats.totalCost + pFileMetadata.cost; - var queueStats = "\x01n\x01cFiles: \x01h" + batchDLQueueStats.numFilesInQueue + " \x01n\x01cCredits: \x01h" - + totalQueueCost + "\x01n\x01c Bytes: \x01h" + numWithCommas(totalQueueSize) + "\x01n\x01w\r\n"; - for (var i = 0; i < batchDLQueueStats.filenames.length; ++i) - { - queueStats += shortenFilename(batchDLQueueStats.filenames[i].filename, frameInnerWidth, false) + "\r\n"; - queueStats += batchDLQueueStats.filenames[i].desc.substr(0, frameInnerWidth) + "\r\n"; - if (i < batchDLQueueStats.filenames.length-1) - queueStats += "\r\n"; + // Build a frame with batch DL queue stats and prompt the user if they want to + // download their batch DL queue + var frameTitle = "Download your batch queue (Y/N)?"; + // \x01cFiles: \x01h1 \x01n\x01c(\x01h100 \x01n\x01cMax) Credits: 0 Bytes: \x01h2,228,254 \x01n\x01c Time: 00:09:40 + // Note: The maximum number of allowed files in the batch download queue doesn't seem to + // be available to JavaScript. + var totalQueueSize = batchDLQueueStats.totalSize + pFileMetadata.size; + var totalQueueCost = batchDLQueueStats.totalCost + pFileMetadata.cost; + var queueStats = "\x01n\x01cFiles: \x01h" + batchDLQueueStats.numFilesInQueue + " \x01n\x01cCredits: \x01h" + + totalQueueCost + "\x01n\x01c Bytes: \x01h" + numWithCommas(totalQueueSize) + "\x01n\x01w\r\n"; + for (var i = 0; i < batchDLQueueStats.filenames.length; ++i) + { + queueStats += shortenFilename(batchDLQueueStats.filenames[i].filename, frameInnerWidth, false) + "\r\n"; + queueStats += batchDLQueueStats.filenames[i].desc.substr(0, frameInnerWidth) + "\r\n"; + if (i < batchDLQueueStats.filenames.length-1) + queueStats += "\r\n"; + } + var additionalQuitKeys = "yYnN"; + var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, + frameHeight, gColors.batchDLInfoWindowBorder, + frameTitle, gColors.batchDLInfoWindowTitle, + queueStats, additionalQuitKeys); + // The main screen content (file list & extended description if applicable) + // will need to be redrawn after this. + retObj.reDrawMainScreenContent = true; + // If the user chose to download their file queue, then send it to the user. + // And the lister headers will need to be re-drawn as well. + if (lastUserInput.toUpperCase() == "Y") + { + retObj.reDrawListerHeader = true; + retObj.reDrawCmdBar = true; + console.attributes = "N"; + console.gotoxy(1, console.screen_rows); + console.crlf(); + bbs.batch_download(); + // If the user is still online (chose not to hang up after transfer), + // then pause so that the user can see the batch download status + if (bbs.online > 0) + console.pause(); + } } - var additionalQuitKeys = "yYnN"; + } + else + { + eraseMsgBoxScreenArea(); + // Build a frame object to show the names of the files that failed to be added to the + // user's batch DL queue + var frameTitle = "Failed to add these files to batch DL queue"; + var fileListStr = "\x01n\x01w"; + for (var i = 0; i < filenamesFailed.length; ++i) + fileListStr += shortenFilename(filenamesFailed[i], frameInnerWidth, false) + "\r\n"; var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.batchDLInfoWindowBorder, - frameTitle, gColors.batchDLInfoWindowTitle, - queueStats, additionalQuitKeys); - // The main screen content (file list & extended description if applicable) - // will need to be redrawn after this. - retObj.reDrawMainScreenContent = true; - // If the user chose to download their file queue, then send it to the user. - // And the lister headers will need to be re-drawn as well. - if (lastUserInput.toUpperCase() == "Y") - { - retObj.reDrawListerHeader = true; - retObj.reDrawCmdBar = true; - console.print("\x01n"); - console.gotoxy(1, console.screen_rows); - console.crlf(); - bbs.batch_download(); - // If the user is still online (chose not to hang up after transfer), - // then pause so that the user can see the batch download status - if (bbs.online > 0) - console.pause(); - } + frameHeight, gColors.batchDLInfoWindowBorder, + frameTitle, gColors.batchDLInfoWindowTitle, + fileListStr, ""); + // Add the file list redraw info. Note that the X and Y are relative + // to the file list menu, not absolute screen coordinates. + // To make the list refresh info to return to the main script loop + retObj.fileListPartialRedrawInfo = { + startX: 3, + startY: 3, + absStartX: gFileListMenu.pos.x + 3 - 1, // 1-based + absStartY: gFileListMenu.pos.y + 3 - 1, // 1-based + width: frameWidth + 1, + height: frameHeight + }; } } else { - eraseMsgBoxScreenArea(); - // Build a frame object to show the names of the files that failed to be added to the - // user's batch DL queue - var frameTitle = "Failed to add these files to batch DL queue"; - var fileListStr = "\x01n\x01w"; - for (var i = 0; i < filenamesFailed.length; ++i) - fileListStr += shortenFilename(filenamesFailed[i], frameInnerWidth, false) + "\r\n"; - var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.batchDLInfoWindowBorder, - frameTitle, gColors.batchDLInfoWindowTitle, - fileListStr, ""); - // Add the file list redraw info. Note that the X and Y are relative - // to the file list menu, not absolute screen coordinates. - // To make the list refresh info to return to the main script loop - retObj.fileListPartialRedrawInfo = { - startX: 3, - startY: 3, - absStartX: gFileListMenu.pos.x + 3 - 1, // 1-based - absStartY: gFileListMenu.pos.y + 3 - 1, // 1-based - width: frameWidth + 1, - height: frameHeight - }; + // Traditional UI + retObj.fileListPartialRedrawInfo = null; + // If there were no failures, then show a success message & prompt the user if they + // want to download their batch queue. Otherwise, show the filenames that failed to + // get added. + if (filenamesFailed.length == 0) + { + console.attributes = "N"; + console.print("Your batch DL queue was sucessfully updated"); + console.crlf(); + // Prompt if the user wants to download their batch queue + if (bbs.batch_dnload_total > 0) + { + // Output the user's file queue + // \x01cFiles: \x01h1 \x01n\x01c(\x01h100 \x01n\x01cMax) Credits: 0 Bytes: \x01h2,228,254 \x01n\x01c Time: 00:09:40 + // Note: The maximum number of allowed files in the batch download queue doesn't seem to + // be available to JavaScript. + var totalQueueSize = batchDLQueueStats.totalSize + pFileMetadata.size; + var totalQueueCost = batchDLQueueStats.totalCost + pFileMetadata.cost; + console.print("\x01cFiles: \x01h" + batchDLQueueStats.numFilesInQueue + " \x01n\x01cCredits: \x01h" + + totalQueueCost + "\x01n\x01c Bytes: \x01h" + numWithCommas(totalQueueSize) + "\x01n"); + console.crlf(); + for (var i = 0; i < batchDLQueueStats.filenames.length; ++i) + { + console.print(batchDLQueueStats.filenames[i].filename + "\r\n"); + console.print(batchDLQueueStats.filenames[i].desc + "\r\n"); + if (i < batchDLQueueStats.filenames.length-1) + console.crlf(); + } + // Prompt the user whether they want to download their file queue; send it to the user + // if they choose to do so. + if (console.yesno("Download your batch queue (Y/N)")) + { + retObj.reDrawListerHeader = true; + retObj.reDrawCmdBar = true; + console.attributes = "N"; + bbs.batch_download(); + // If the user is still online (chose not to hang up after transfer), + // then pause so that the user can see the batch download status + if (bbs.online > 0) + console.pause(); + } + } + } + else + { + // Show the names of the files that failed to be added to the user's batch DL queue + console.attributes = "N"; + console.print(gColors.errorMessage + "Failed to add these files to batch DL queue:\x01n\r\n"); + console.attributes = "NW"; + for (var i = 0; i < filenamesFailed.length; ++i) + console.print(filenamesFailed[i] + "\r\n"); + console.pause(); + console.line_counter = 0; + } } } @@ -1068,14 +1490,15 @@ function getUserDLQueueStats() return retObj; } -// Lets the user download the currently selected file on the file list menu +// Lets the user download the currently selected file on the file list. This works with both the +// ANSi/lightbar UI and traditional UI. // // Parameters: // pFileMetadata: The file metadata object for the file to download // // Return value: An object with values to indicate status & screen refresh actions; see // getDefaultActionRetObj() for details. -function letUserDownloadSelectedFile_ANSI(pFileMetadata) +function letUserDownloadSelectedFile(pFileMetadata) { var retObj = getDefaultActionRetObj(); console.attributes = "N"; @@ -1152,6 +1575,13 @@ function displayHelpScreen() printf(printfStr, "V", "View the file"); printf(printfStr, "B", "Flag the selected file(s) for batch download"); printf(printfStr, "D", "Download the highlighted (selected) file"); + if (!gUseLightbarInterface) + { + printf(printfStr, "N/PageDn", "Show the next page of files"); + printf(printfStr, "P/Pageup", "Show the previous page of files"); + printf(printfStr, "F/Home", "Show the first page of files"); + printf(printfStr, "L/End", "Show the last page of files"); + } if (user.is_sysop) { printf(printfStr, "M", "Move the file(s) to another directory"); @@ -1159,7 +1589,7 @@ function displayHelpScreen() } printf(printfStr, "?", "Show this help screen"); printf(printfStr, "Q", "Quit back to the BBS"); - console.print("\x01n"); + console.attributes = "N"; console.crlf(); //console.pause(); @@ -1177,7 +1607,7 @@ function displayHelpScreen() // // Return value: An object with values to indicate status & screen refresh actions; see // getDefaultActionRetObj() for details. -function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) +function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); @@ -1200,7 +1630,6 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) if (!moveFilesConfirmed) return retObj; - // Create a file library menu for the user to choose a file library (and then directory) var fileLibMenu = createFileLibMenu(); // For screen refresh purposes, construct the file list redraw info. Note that the X and Y are relative @@ -1214,8 +1643,14 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) width: fileLibMenu.size.width + 1, height: fileLibMenu.size.height + 1 // + 1 because of the label above the menu }; - console.gotoxy(fileLibMenu.pos.x, fileLibMenu.pos.y-1); - printf("\x01n\x01c\x01h|\x01n\x01c%-" + +(fileLibMenu.size.width-1) + "s\x01n", "Choose a destination area"); + var chooseAreaText = "Choose a destination area"; + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + console.gotoxy(fileLibMenu.pos.x, fileLibMenu.pos.y-1); + printf("\x01n\x01c\x01h|\x01n\x01c%-" + +(fileLibMenu.size.width-1) + "s\x01n", chooseAreaText); + } + else + printf("\x01n\x01c%-" + +(fileLibMenu.size.width-1) + "s\x01n\r\n", chooseAreaText); // Prompt the user which directory to move the file to var chosenDirCode = null; var continueOn = true; @@ -1227,6 +1662,8 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) // The file dir menu will be created at the same position & with the same size // as the file library menu var fileDirMenu = createFileDirMenu(chosenLibIdx); + if (!gUseLightbarInterface) + printf("\x01n%sDirectories of %s:\r\n", gColors.fileAreaDescTrad, file_area.lib_list[chosenLibIdx].description); chosenDirCode = fileDirMenu.GetVal(); if (typeof(chosenDirCode) === "string") { @@ -1242,6 +1679,7 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) else continueOn = false; } + // If the user chose a directory, then move the file there. if (typeof(chosenDirCode) === "string" && chosenDirCode.length > 0) { @@ -1344,15 +1782,11 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) } } // Display a success/fail message + //displayMsgs(pMsgArray, pIsError, pWaitAndErase) if (moveAllSucceeded) - { - var msg = "Successfully moved the file(s) to " + destLibAndDirDesc; - displayMsg(msg, false, true); - } + displayMsg("Successfully moved the file(s) to " + destLibAndDirDesc, false, true); else - { displayMsg("Failed to move the file(s)!", true, true); - } // After moving the files, if there are no more files (in the directory or otherwise), // say so and exit now. if (gScriptMode == MODE_LIST_DIR && file_area.dir[gDirCode].files == 0) @@ -1376,6 +1810,7 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) } // Allows the user to remove the selected file(s) from the filebase. Only for sysops! +// This works with both the ANSI/Lightbar UI and the traditional UI. // // Parameters: // pFileList: The list of file metadata objects from the file directory @@ -1386,7 +1821,7 @@ function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) // returned will have the following additional properties: // filesDeleted: Boolean - Whether or not files were actually deleted (after // confirmation) -function confirmAndRemoveFilesFromFilebase_ANSI(pFileList, pFileListMenu) +function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); retObj.filesDeleted = false; @@ -1607,6 +2042,14 @@ function DDFileMenuBar(pPos) //this.cmdArray.push(new DDFileMenuBarItem("Del", 0, FILE_DELETE)); this.cmdArray.push(new DDFileMenuBarItem("DEL", 0, FILE_DELETE, KEY_DEL)); } + //DDFileMenuBarItem(pItemText, pPos, pRetCode, pHotkeyOverride) + if (!gUseLightbarInterface || !console.term_supports(USER_ANSI)) + { + this.cmdArray.push(new DDFileMenuBarItem("Next", 0, NEXT_PAGE, KEY_PAGEDN)); + this.cmdArray.push(new DDFileMenuBarItem("Prev", 0, PREV_PAGE, KEY_PAGEUP)); + this.cmdArray.push(new DDFileMenuBarItem("First", 0, FIRST_PAGE)); + this.cmdArray.push(new DDFileMenuBarItem("Last", 0, LAST_PAGE)); + } this.cmdArray.push(new DDFileMenuBarItem("?", 0, HELP)); this.cmdArray.push(new DDFileMenuBarItem("Quit", 0, QUIT)); @@ -1668,7 +2111,8 @@ function DDFileMenuBar_getItemTextFromIdx(pIdx) function DDFileMenuBar_writePromptLine() { // Place the cursor at the defined location, then write the prompt text - console.gotoxy(this.pos.x, this.pos.y); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + console.gotoxy(this.pos.x, this.pos.y); console.print(this.promptText); } // For the DDFileMenuBar class: Refreshes 2 items in the command bar text line @@ -1685,13 +2129,16 @@ function DDFileMenuBar_refreshWithNewAction(pCmdIdx) // Refresh the prompt area for the previous index with regular colors // Re-draw the last item text with regular colors var itemText = this.getItemTextFromIdx(this.currentCommandIdx); - console.gotoxy(this.cmdArray[this.currentCommandIdx].pos, this.pos.y); - console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, false, false)); - // Draw the new item text with selected colors - itemText = this.getItemTextFromIdx(pCmdIdx); - console.gotoxy(this.cmdArray[pCmdIdx].pos, this.pos.y); - console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, true, false)); - console.gotoxy(this.pos.x+strip_ctrl(this.promptText).length-1, this.pos.y); + if (console.term_supports(USER_ANSI)) + { + console.gotoxy(this.cmdArray[this.currentCommandIdx].pos, this.pos.y); + console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, false, false)); + // Draw the new item text with selected colors + itemText = this.getItemTextFromIdx(pCmdIdx); + console.gotoxy(this.cmdArray[pCmdIdx].pos, this.pos.y); + console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, true, false)); + console.gotoxy(this.pos.x+strip_ctrl(this.promptText).length-1, this.pos.y); + } this.lastCommandIdx = this.currentCommandIdx; this.currentCommandIdx = pCmdIdx; @@ -2128,7 +2575,8 @@ function doFrameInputLoop(pFrame, pScrollbar, pFrameContentStr, pAdditionalQuitK // pTextOnly: Only draw the library & directory text (no decoration or other text). // This is optional & defaults to false. // pDirCodeOverride: Optional string: If this is valid, this will be used for the library & directory name -function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride) +// pNumberedMode: Boolean - Whether or not the menu/list has numbers in front of the file info items +function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride, pNumberedMode) { // If the behavior flags include no header, then just return immediately if ((gListBehavior & FL_NO_HDR) == FL_NO_HDR) @@ -2187,10 +2635,13 @@ function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride) // Library line if (textOnly) { - console.gotoxy(6, 1); - console.print("\x01n" + libText); - console.gotoxy(6, 2); - console.print("\x01n" + dirText); + if (console.term_supports(USER_ANSI)) + { + console.gotoxy(6, 1); + console.print("\x01n" + libText); + console.gotoxy(6, 2); + console.print("\x01n" + dirText); + } } else { @@ -2206,11 +2657,11 @@ function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride) console.print("\x01w" + THIN_RECTANGLE_RIGHT + "\x01k\x01h" + BLOCK4 + "\x01n\x01w" + THIN_RECTANGLE_LEFT + "\x01g\x01hLister \x01n\x01w"); console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); - console.print("\x01n"); + console.attributes = "N"; // List header console.crlf(); - displayListHdrLine(false); + displayListHdrLine(false, pNumberedMode); if (dispHdrFirstRun) { @@ -2223,14 +2674,21 @@ function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride) // // Parameters: // pMoveToLocationFirst: Boolean - Whether to move the cursor to the required location first. -function displayListHdrLine(pMoveToLocationFirst) +// pNumberedMode: Boolean - Whether or not the menu/list has numbers in front of the file info items +function displayListHdrLine(pMoveToLocationFirst, pNumberedMode) { if (pMoveToLocationFirst && console.term_supports(USER_ANSI)) console.gotoxy(1, 3); var filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; var fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; var descLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - var formatStr = "\x01n\x01w\x01h%-" + filenameLen + "s %" + fileSizeLen + "s %-" + var numItemsLen = gFileList.length.toString().length; + if (pNumberedMode) + descLen -= (numItemsLen+1); + var formatStr = "\x01n\x01w\x01h"; + if (pNumberedMode) + formatStr += format("%" + numItemsLen + "s ", "#"); + formatStr += "%-" + filenameLen + "s %" + fileSizeLen + "s %-" + +(descLen-7) + "s\x01n\x01w%5s\x01n"; var listHdrEndText = THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1; printf(formatStr, "Filename", "Size", "Description", listHdrEndText); @@ -2257,13 +2715,24 @@ function createFileListMenu(pQuitKeys) menuWidth = gListIdxes.fileSizeEnd + 1; var menuHeight = console.screen_rows - (startRow-1) - 1; var fileListMenu = new DDLightbarMenu(1, startRow, menuWidth, menuHeight); - // TODO: Add support for a traditional/non-lightbar user interface - //fileListMenu.allowANSI = gUseLightbarInterface; - fileListMenu.scrollbarEnabled = true; fileListMenu.borderEnabled = false; - fileListMenu.multiSelect = true; fileListMenu.ampersandHotkeysInItems = false; fileListMenu.wrapNavigation = false; + fileListMenu.colors.itemNumColor = gColors.listNumTrad; // For numbered mode, if non-lightbar + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + fileListMenu.allowANSI = true; + fileListMenu.scrollbarEnabled = true; + fileListMenu.multiSelect = true; + fileListMenu.numberedMode = false; + } + else + { + fileListMenu.allowANSI = false; + fileListMenu.scrollbarEnabled = false; + fileListMenu.multiSelect = false; + fileListMenu.numberedMode = true; + } fileListMenu.extdDescEnabled = extendedDescEnabled(); @@ -2328,7 +2797,7 @@ function createFileListMenu(pQuitKeys) if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) { var originalCurPos = console.getxy(); - displayFileLibAndDirHeader(true, gFileList[pIdx].dirCode); + displayFileLibAndDirHeader(true, gFileList[pIdx].dirCode, gFileListMenu.numberedMode); console.gotoxy(originalCurPos); } } @@ -2391,11 +2860,12 @@ function createFileLibMenu() // Create the menu object var startRow = gNumHeaderLinesDisplayed + 4; var fileLibMenu = new DDLightbarMenu(5, startRow, console.screen_columns - 10, console.screen_rows - startRow - 5); - fileLibMenu.scrollbarEnabled = true; + fileLibMenu.scrollbarEnabled = gUseLightbarInterface && console.term_supports(USER_ANSI); fileLibMenu.borderEnabled = true; fileLibMenu.multiSelect = false; fileLibMenu.ampersandHotkeysInItems = false; fileLibMenu.wrapNavigation = false; + fileLibMenu.allowANSI = gUseLightbarInterface && console.term_supports(USER_ANSI); // Add additional keypresses for quitting the menu's input loop. // Q: Quit @@ -2418,7 +2888,10 @@ function createFileLibMenu() --menuInnerWidth; // Allow 2 for spaces fileLibMenu.libDescLen = menuInnerWidth - fileLibMenu.libNumLen - fileLibMenu.numDirsLen - 2; - fileLibMenu.libFormatStr = "%" + fileLibMenu.libNumLen + "d %-" + fileLibMenu.libDescLen + "s %" + fileLibMenu.numDirsLen + "d"; + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + fileLibMenu.libFormatStr = "%" + fileLibMenu.libNumLen + "d %-" + fileLibMenu.libDescLen + "s %" + fileLibMenu.numDirsLen + "d"; + else + fileLibMenu.libFormatStr = "%-" + fileLibMenu.libDescLen + "s %" + fileLibMenu.numDirsLen + "d"; // Colors and their indexes fileLibMenu.borderColor = gColors.fileAreaMenuBorder; @@ -2428,23 +2901,50 @@ function createFileLibMenu() var descEnd = descStart + fileLibMenu.libDescLen; var numDirsStart = descEnd; //var numDirsEnd = numDirsStart + fileLibMenu.numDirsLen; + // Selected colors (for lightbar interface) fileLibMenu.SetColors({ - itemColor: [{start: libNumStart, end: libNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}], selectedItemColor: [{start: libNumStart, end: libNumEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumHighlight}, {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaDescHighlight}, {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumItemsHighlight}] }); + // Non-selected item colors + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + fileLibMenu.SetColors({ + itemColor: [{start: libNumStart, end: libNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, + {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, + {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}] + }); + } + else + { + fileLibMenu.colors.itemNumColor = gColors.listNumTrad; + descStart = 0; + descEnd = descStart + fileLibMenu.libDescLen; + numDirsStart = descEnd; + fileLibMenu.SetColors({ + itemColor: [{start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkgTrad + gColors.fileAreaDescTrad}, + {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkgTrad + gColors.fileAreaNumItemsTrad}] + }); + } fileLibMenu.topBorderText = "\x01y\x01hFile Libraries"; // Define the menu function for getting an item fileLibMenu.GetItem = function(pIdx) { var menuItemObj = this.MakeItemWithRetval(pIdx); - menuItemObj.text = format(this.libFormatStr, - pIdx + 1,//file_area.lib_list[pIdx].number + 1, - file_area.lib_list[pIdx].description.substr(0, this.libDescLen), - file_area.lib_list[pIdx].dir_list.length); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + menuItemObj.text = format(this.libFormatStr, + pIdx + 1,//file_area.lib_list[pIdx].number + 1, + file_area.lib_list[pIdx].description.substr(0, this.libDescLen), + file_area.lib_list[pIdx].dir_list.length); + } + else + { + menuItemObj.text = format(this.libFormatStr, + file_area.lib_list[pIdx].description.substr(0, this.libDescLen), + file_area.lib_list[pIdx].dir_list.length); + } return menuItemObj; } @@ -2487,11 +2987,12 @@ function createFileDirMenu(pLibIdx) //DDLightbarMenu(pX, pY, pWidth, pHeight) // Create the menu object var fileDirMenu = new DDLightbarMenu(5, startRow, console.screen_columns - 10, console.screen_rows - startRow - 5); - fileDirMenu.scrollbarEnabled = true; + fileDirMenu.scrollbarEnabled = gUseLightbarInterface && console.term_supports(USER_ANSI); fileDirMenu.borderEnabled = true; fileDirMenu.multiSelect = false; fileDirMenu.ampersandHotkeysInItems = false; fileDirMenu.wrapNavigation = false; + fileDirMenu.allowANSI = gUseLightbarInterface && console.term_supports(USER_ANSI); // Add additional keypresses for quitting the menu's input loop. // Q: Quit @@ -2515,7 +3016,10 @@ function createFileDirMenu(pLibIdx) --menuInnerWidth; // Allow 2 for spaces fileDirMenu.dirDescLen = menuInnerWidth - fileDirMenu.dirNumLen - fileDirMenu.numFilesLen - 2; - fileDirMenu.dirFormatStr = "%" + fileDirMenu.dirNumLen + "d %-" + fileDirMenu.dirDescLen + "s %" + fileDirMenu.numFilesLen + "d"; + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + fileDirMenu.dirFormatStr = "%" + fileDirMenu.dirNumLen + "d %-" + fileDirMenu.dirDescLen + "s %" + fileDirMenu.numFilesLen + "d"; + else + fileDirMenu.dirFormatStr = "%-" + fileDirMenu.dirDescLen + "s %" + fileDirMenu.numFilesLen + "d"; // Colors and their indexes fileDirMenu.borderColor = gColors.fileAreaMenuBorder; @@ -2525,24 +3029,52 @@ function createFileDirMenu(pLibIdx) var descEnd = descStart + fileDirMenu.dirDescLen; var numDirsStart = descEnd; //var numDirsEnd = numDirsStart + fileDirMenu.numDirsLen; + // Selected colors (for lightbar interface) fileDirMenu.SetColors({ - itemColor: [{start: dirNumStart, end: dirNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}], selectedItemColor: [{start: dirNumStart, end: dirNumEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumHighlight}, {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaDescHighlight}, {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumItemsHighlight}] }); + // Non-selected item colors + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + fileDirMenu.SetColors({ + itemColor: [{start: dirNumStart, end: dirNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, + {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, + {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}] + }); + } + else + { + fileDirMenu.colors.itemNumColor = gColors.listNumTrad; + descStart = 0; + descEnd = descStart + fileDirMenu.dirDescLen; + numDirsStart = descEnd; + fileDirMenu.SetColors({ + itemColor: [{start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkgTrad + gColors.fileAreaDescTrad}, + {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkgTrad + gColors.fileAreaNumItemsTrad}] + }); + } fileDirMenu.topBorderText = "\x01y\x01h" + ("File directories of " + file_area.lib_list[pLibIdx].description).substr(0, fileDirMenu.size.width-2); // Define the menu function for ggetting an item fileDirMenu.GetItem = function(pIdx) { // Return the internal code for the directory for the item var menuItemObj = this.MakeItemWithRetval(file_area.lib_list[this.libIdx].dir_list[pIdx].code); - menuItemObj.text = format(this.dirFormatStr, - pIdx + 1,//file_area.lib_list[this.libIdx].dir_list[pIdx].number + 1, - file_area.lib_list[this.libIdx].dir_list[pIdx].description.substr(0, this.dirDescLen), - file_area.lib_list[this.libIdx].dir_list[pIdx].files); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + menuItemObj.text = format(this.dirFormatStr, + pIdx + 1,//file_area.lib_list[this.libIdx].dir_list[pIdx].number + 1, + file_area.lib_list[this.libIdx].dir_list[pIdx].description.substr(0, this.dirDescLen), + file_area.lib_list[this.libIdx].dir_list[pIdx].files); + } + else + { + menuItemObj.text = format(this.dirFormatStr, + file_area.lib_list[this.libIdx].dir_list[pIdx].description.substr(0, this.dirDescLen), + file_area.lib_list[this.libIdx].dir_list[pIdx].files); + + } return menuItemObj; } @@ -2765,26 +3297,41 @@ function displayMsgs(pMsgArray, pIsError, pWaitAndErase) var waitAndErase = (typeof(pWaitAndErase) === "boolean" ? pWaitAndErase : true); - // Draw the box border, then write the messages - var title = pIsError ? "Error" : "Message"; - var titleColor = pIsError ? gColors.errorMessage : gColors.successMessage; - drawBorder(gErrorMsgBoxULX, gErrorMsgBoxULY, gErrorMsgBoxWidth, gErrorMsgBoxHeight, - gColors.errorBoxBorder, "single", title, titleColor, ""); - var msgColor = "\x01n" + (pIsError ? gColors.errorMessage : gColors.successMessage); - var innerWidth = gErrorMsgBoxWidth - 2; - var msgFormatStr = msgColor + "%-" + innerWidth + "s\x01n"; - for (var i = 0; i < pMsgArray.length; ++i) + if (gUseLightbarInterface) + { + // Draw the box border, then write the messages + var title = pIsError ? "Error" : "Message"; + var titleColor = pIsError ? gColors.errorMessage : gColors.successMessage; + drawBorder(gErrorMsgBoxULX, gErrorMsgBoxULY, gErrorMsgBoxWidth, gErrorMsgBoxHeight, + gColors.errorBoxBorder, "single", title, titleColor, ""); + var msgColor = "\x01n" + (pIsError ? gColors.errorMessage : gColors.successMessage); + var innerWidth = gErrorMsgBoxWidth - 2; + var msgFormatStr = msgColor + "%-" + innerWidth + "s\x01n"; + for (var i = 0; i < pMsgArray.length; ++i) + { + console.gotoxy(gErrorMsgBoxULX+1, gErrorMsgBoxULY+1); + printf(msgFormatStr, pMsgArray[i].substr(0, innerWidth)); + if (waitAndErase) + { + // Wait for the error wait duration + mswait(gErrorMsgWaitMS); + } + } + if (waitAndErase) + eraseMsgBoxScreenArea(); + } + else { - console.gotoxy(gErrorMsgBoxULX+1, gErrorMsgBoxULY+1); - printf(msgFormatStr, pMsgArray[i].substr(0, innerWidth)); + console.attributes = "N"; + var msgColor = "\x01n" + (pIsError ? gColors.errorMessage : gColors.successMessage); + for (var i = 0; i < pMsgArray.length; ++i) + console.print(msgColor + pMsgArray[i] + "\r\n"); if (waitAndErase) { // Wait for the error wait duration mswait(gErrorMsgWaitMS); } } - if (waitAndErase) - eraseMsgBoxScreenArea(); } function displayMsg(pMsg, pIsError, pWaitAndErase) { @@ -2797,7 +3344,7 @@ function eraseMsgBoxScreenArea() { // Refresh the list header line and have the file list menu refresh itself over // the error message window - displayListHdrLine(true); + displayListHdrLine(true, gFileListMenu.numberedMode); // This used to call gFileListMenu.DrawPartialAbs refreshScreenMainContent(gErrorMsgBoxULX, gErrorMsgBoxULY+1, gErrorMsgBoxWidth, gErrorMsgBoxHeight-2); } @@ -2928,10 +3475,11 @@ function drawSeparatorLine(pX, pY, pWidth) console.print("\x01n\x01g\x01h"); for (var i = 0; i < width; ++i) console.print(HORIZONTAL_SINGLE); - console.print("\x01n"); + console.attributes = "N"; } -// Confirms with the user to perform an action with a file or set of files +// Confirms with the user to perform an action with a file or set of files. Note that this +// can work with both the ANSI/lightbar or traditional UI. // // Parameters: // pFilenames: An array of filenames (as strings), or a string containing a filename @@ -2957,40 +3505,62 @@ function confirmFileActionWithUser(pFilenames, pActionName, pDefaultYes) else if (numFilenames == 1) { var filename = (typeof(pFilenames) === "string" ? pFilenames : pFilenames[0]); - drawSeparatorLine(1, console.screen_rows-2, console.screen_columns-1); - console.gotoxy(1, console.screen_rows-1); - console.cleartoeol("\x01n"); - console.gotoxy(1, console.screen_rows-1); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + drawSeparatorLine(1, console.screen_rows-2, console.screen_columns-1); + console.gotoxy(1, console.screen_rows-1); + console.cleartoeol("\x01n"); + console.gotoxy(1, console.screen_rows-1); + } var shortFilename = shortenFilename(filename, Math.floor(console.screen_columns/2), false); if (pDefaultYes) actionConfirmed = console.yesno(pActionName + " " + shortFilename); else actionConfirmed = !console.noyes(pActionName + " " + shortFilename); - // Refresh the main screen content, to erase the confirmation prompt - refreshScreenMainContent(1, console.screen_rows-2, console.screen_columns, 2, true); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + // Refresh the main screen content, to erase the confirmation prompt + refreshScreenMainContent(1, console.screen_rows-2, console.screen_columns, 2, true); + } } else { - // Construct & draw a frame with the file list & display the frame to confirm with the - // user to delete the files - var frameUpperLeftX = gFileListMenu.pos.x + 2; - var frameUpperLeftY = gFileListMenu.pos.y + 2; - //var frameWidth = gFileListMenu.size.width - 4; - var frameWidth = console.screen_columns - 4; - var frameHeight = 10; - var frameTitle = pActionName + " files? (Y/N)"; - var additionalQuitKeys = "yYnN"; - var frameInnerWidth = frameWidth - 2; // Without borders; for filename lengths - var fileListStr = "\x01n\x01w"; - for (var i = 0; i < pFilenames.length; ++i) - fileListStr += shortenFilename(pFilenames[i], frameInnerWidth, false) + "\r\n"; - var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.confirmFileActionWindowBorder, - frameTitle, gColors.confirmFileActionWindowWindowTitle, - fileListStr, additionalQuitKeys); - actionConfirmed = (lastUserInput.toUpperCase() == "Y"); - // Refresh the main screen content, to erase the confirmation window - refreshScreenMainContent(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, true); + if (gUseLightbarInterface && console.term_supports(USER_ANSI)) + { + // Construct & draw a frame with the file list & display the frame to confirm with the + // user + var frameUpperLeftX = gFileListMenu.pos.x + 2; + var frameUpperLeftY = gFileListMenu.pos.y + 2; + //var frameWidth = gFileListMenu.size.width - 4; + var frameWidth = console.screen_columns - 4; + var frameHeight = 10; + var frameTitle = pActionName + " files? (Y/N)"; + var additionalQuitKeys = "yYnN"; + var frameInnerWidth = frameWidth - 2; // Without borders; for filename lengths + var fileListStr = "\x01n\x01w"; + for (var i = 0; i < pFilenames.length; ++i) + fileListStr += shortenFilename(pFilenames[i], frameInnerWidth, false) + "\r\n"; + var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, + frameHeight, gColors.confirmFileActionWindowBorder, + frameTitle, gColors.confirmFileActionWindowWindowTitle, + fileListStr, additionalQuitKeys); + actionConfirmed = (lastUserInput.toUpperCase() == "Y"); + // Refresh the main screen content, to erase the confirmation window + refreshScreenMainContent(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, true); + } + else + { + // Traditional UI + // Write the file list + console.attributes = "NW"; + for (var i = 0; i < pFilenames.length; ++i) + console.print(pFilenames[i] + "\r\n"); + // Confirm with the user + if (pDefaultYes) + actionConfirmed = console.yesno(pActionName + " files"); + else + actionConfirmed = !console.noyes(pActionName + " files"); + } } return actionConfirmed; @@ -3024,51 +3594,72 @@ function readConfigFile() var settingsObj = cfgFile.iniGetObject(); cfgFile.close(); - if (typeof(settingsObj["sortOrder"]) === "string") + for (var prop in settingsObj) { - var valueUpper = settingsObj.sortOrder.toUpperCase(); - if (valueUpper == "NATURAL") - gFileSortOrder = FileBase.SORT.NATURAL; - else if (valueUpper == "NAME_AI") - gFileSortOrder = FileBase.SORT.NAME_AI; - else if (valueUpper == "NAME_DI") - gFileSortOrder = FileBase.SORT.NAME_DI; - else if (valueUpper == "NAME_AS") - gFileSortOrder = FileBase.SORT.NAME_AS; - else if (valueUpper == "NAME_DS") - gFileSortOrder = FileBase.SORT.NAME_DS; - else if (valueUpper == "DATE_A") - gFileSortOrder = FileBase.SORT.DATE_A; - else if (valueUpper == "DATE_D") - gFileSortOrder = FileBase.SORT.DATE_D; - else if (valueUpper == "ULTIME") - gFileSortOrder = SORT_FL_ULTIME; - else if (valueUpper == "DLTIME") - gFileSortOrder = SORT_FL_DLTIME; - else // Default - gFileSortOrder = FileBase.SORT.NATURAL; - } - if (typeof(settingsObj["pauseAfterViewingFile"]) === "boolean") - gPauseAfterViewingFile = settingsObj.pauseAfterViewingFile; - if (typeof(settingsObj["useLightbarInterface"]) === "boolean") - gUseLightbarInterface = settingsObj.useLightbarInterface; - if (typeof(settingsObj["themeFilename"]) === "string") - { - // First look for the theme config file in the sbbs/mods - // directory, then sbbs/ctrl, then the same directory as - // this script. - themeFilename = system.mods_dir + settingsObj.themeFilename; - if (!file_exists(themeFilename)) - themeFilename = system.ctrl_dir + settingsObj.themeFilename; - if (!file_exists(themeFilename)) - themeFilename = startupPath + settingsObj.themeFilename; + var propUpper = prop.toUpperCase(); + if (propUpper == "INTERFACESTYLE") + { + if (typeof(settingsObj[prop]) === "string") + gUseLightbarInterface = (settingsObj[prop].toUpperCase() == "LIGHTBAR"); + } + else if (propUpper == "TRADITIONALUSESYNCSTOCK") + { + typeof(settingsObj[prop]) === "boolean" + gTraditionalUseSyncStock = settingsObj[prop]; + } + else if (propUpper == "SORTORDER") + { + if (typeof(settingsObj[prop]) === "string") + { + var valueUpper = settingsObj[prop].toUpperCase(); + if (valueUpper == "NATURAL") + gFileSortOrder = FileBase.SORT.NATURAL; + else if (valueUpper == "NAME_AI") + gFileSortOrder = FileBase.SORT.NAME_AI; + else if (valueUpper == "NAME_DI") + gFileSortOrder = FileBase.SORT.NAME_DI; + else if (valueUpper == "NAME_AS") + gFileSortOrder = FileBase.SORT.NAME_AS; + else if (valueUpper == "NAME_DS") + gFileSortOrder = FileBase.SORT.NAME_DS; + else if (valueUpper == "DATE_A") + gFileSortOrder = FileBase.SORT.DATE_A; + else if (valueUpper == "DATE_D") + gFileSortOrder = FileBase.SORT.DATE_D; + else if (valueUpper == "ULTIME") + gFileSortOrder = SORT_FL_ULTIME; + else if (valueUpper == "DLTIME") + gFileSortOrder = SORT_FL_DLTIME; + else // Default + gFileSortOrder = FileBase.SORT.NATURAL; + } + } + else if (propUpper == "PAUSEAFTERVIEWINGFILE") + { + if (typeof(settingsObj[prop]) === "boolean") + gPauseAfterViewingFile = settingsObj[prop]; + } + else if (propUpper == "THEMEFILENAME") + { + if (typeof(settingsObj[prop]) === "string") + { + // First look for the theme config file in the sbbs/mods + // directory, then sbbs/ctrl, then the same directory as + // this script. + themeFilename = system.mods_dir + settingsObj[prop]; + if (!file_exists(themeFilename)) + themeFilename = system.ctrl_dir + settingsObj[prop]; + if (!file_exists(themeFilename)) + themeFilename = startupPath + settingsObj[prop]; + } + } } } else { // Was unable to read the configuration file. Output a warning to the user // that defaults will be used and to notify the sysop. - console.print("\x01n"); + console.attributes = "N"; console.crlf(); console.print("\x01w\x01hUnable to open the configuration file: \x01y" + cfgFilename); console.crlf(); @@ -3110,7 +3701,7 @@ function readConfigFile() { // Was unable to read the theme file. Output a warning to the user // that defaults will be used and to notify the sysop. - console.print("\x01n"); + console.attributes = "N"; console.crlf(); console.print("\x01w\x01hUnable to open the theme file: \x01y" + themeFilename); console.crlf(); @@ -3460,11 +4051,11 @@ function populateFileList(pSearchMode) userInputDLA = "A"; else { - console.print("\x01n"); + console.attributes = "N"; console.crlf(); //console.print("\r\n\x01c\x01hFind Text in File Descriptions (no wildcards)\x01n\r\n"); console.mnemonics(bbs.text(DirLibOrAll)); - console.print("\x01n"); + console.attributes = "N"; userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); } var searchDescription = ""; @@ -3523,12 +4114,12 @@ function populateFileList(pSearchMode) userInputDLA = "A"; else { - console.print("\x01n"); + console.attributes = "N"; console.crlf(); console.mnemonics(bbs.text(DirLibOrAll)); var validInputOptions = "DLA"; userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); - console.print("\x01n"); + console.attributes = "N"; console.crlf(); } if (userInputDLA == "D" || userInputDLA == "L" || userInputDLA == "A") @@ -3565,7 +4156,7 @@ function populateFileList(pSearchMode) console.print(dirErrors[i]); console.crlf(); } - console.print("\x01n"); + console.attributes = "N"; console.pause(); retObj.exitNow = true; retObj.exitCode = 1; @@ -3894,7 +4485,9 @@ function searchDirGroupOrAll(pSearchOption, pDirSearchFn) function extendedDescEnabled() { var userExtDescEnabled = ((user.settings & USER_EXTDESC) == USER_EXTDESC); - return userExtDescEnabled && console.screen_columns >= 80; + // TODO: If the traditional (non-lightbar) is enabled and/or the user's terminal doesn't support ANSI, or + // the user's terminal width is less than 80, this will cause the lister to not use extended file descriptions + return userExtDescEnabled && console.screen_columns >= 80 && gUseLightbarInterface && console.term_supports(USER_ANSI); } // Displays a file's extended description on the main screen, next to the @@ -3963,7 +4556,7 @@ function displayFileExtDescOnMainScreen(pFileIdx, pStartScreenRow, pEndScreenRow if (typeof(pEndScreenRow) === "number" && pEndScreenRow > firstScreenRow && pStartScreenRow <= lastScreenRow) lastScreenRow = pEndScreenRow; var fileDescArray = fileDesc.split("\r\n"); - console.print("\x01n"); + console.attributes = "N"; // screenRowNum is to keep track of the row on the screen where the // description line would be placed, in case the start row is after that var screenRowNum = firstScreenRow; @@ -3994,13 +4587,13 @@ function displayFileExtDescOnMainScreen(pFileIdx, pStartScreenRow, pEndScreenRow // If there is room, shoe the file date on the next line if (screenRowForPrinting <= lastScreenRow && fileMetadata.hasOwnProperty("time")) { - console.print("\x01n"); + console.attributes = "N"; console.gotoxy(startX, screenRowForPrinting++); var dateStr = "Date: " + strftime("%Y-%m-%d", fileMetadata.time); printf("%-" + maxDescLen + "s", dateStr.substr(0, maxDescLen)); } // Clear the rest of the lines to the bottom of the list area - console.print("\x01n"); + console.attributes = "N"; while (screenRowForPrinting <= lastScreenRow) { console.gotoxy(startX, screenRowForPrinting++); @@ -4168,4 +4761,119 @@ function attrCodeStr(pAttrCodeCharStr) str += "\x01" + currentChar; } return str; +} + +// Returns the number of lines to be displayed for all file information +// (accounting for multi-line descriptions if multi-line descriptions are enabled) +// +// Parameters: +// pFileList: An array of file metadata objects +// +// Return value: The total number of lines to be displayed for all files (accounting for +// possible multi-line descriptions if enabled) +function numFileInfoLines(pFileList) +{ + if ((user.settings & USER_EXTDESC) == USER_EXTDESC) + { + var totalNumLines = 0; + for (var i = 0; i < pFileList; ++i) + totalNumLines += getExtdFileDescArray(pFileList, i).length; + return totalNumLines; + } + else + return pFileList.length; +} + +// Gets an array of formatted strings with file information for the traditional UI, +// with a number before the file information (for a numbered list). If extended +// descriptions are not enabled, there will only be 1 string; if extended descriptions +// are enabled, there could be more than 1 string in the array. +// +// Parameters: +// pFileList: An array of file metadata objects +// pIdx: The index of the file information to display +// pCRAtEnd: Optional boolean - Whether or not to output a CRLF after displaying the file info line(s) +function getFileInfoLineArrayForTraditionalUI(pFileList, pIdx) +{ + if (!Array.isArray(pFileList) || typeof(pIdx) !== "number" || pIdx < 0 || pIdx > pFileList.length) + return []; + + var filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; + var fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; + var numItemsLen = gFileList.length.toString().length; + var descLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart - (numItemsLen+2); + var formatStr = "\x01n" + gColors.listNumTrad + "%" + numItemsLen + "d \x01n" + gColors.filename + "%-" + filenameLen + "s \x01n" + + gColors.fileSize + "%" + fileSizeLen + "s \x01n" + gColors.desc + "%-" + descLen + "s\x01n"; + var formatStrExtdDescLines = "\x01n" + charStr(" ", numItemsLen+filenameLen+fileSizeLen+3) + "%-" + descLen + "s\x01n"; + + var userExtDescEnabled = ((user.settings & USER_EXTDESC) == USER_EXTDESC); + var descLines; + if (userExtDescEnabled) + descLines = getExtdFileDescArray(pFileList, pIdx); + else + descLines = [ pFileList[pIdx].desc.replace(/\r$/, "").replace(/\n$/, "").replace(/\r\n$/, "") ]; + if (descLines.length == 0) + descLines.push(""); + + var filename = shortenFilename(pFileList[pIdx].name, filenameLen, true); + // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js + var fileInfoLines = []; + fileInfoLines.push(format(formatStr, pIdx+1, filename, getFileSizeStr(pFileList[pIdx].size, fileSizeLen), substrWithAttrCodes(descLines[0], 0, descLen))); + if (userExtDescEnabled) + { + for (var i = 1; i < descLines.length; ++i) + fileInfoLines.push(format(formatStrExtdDescLines, substrWithAttrCodes(descLines[i], 0, descLen))); + } + return fileInfoLines; +} + +// Gets an array of text lines with a file's extended description. To be used only if +// the user has extended file descriptions enabled. +// +// Parameters: +// pFileList: An array of file metadata objects +// pIdx: The index of the file information to display +// +// Return value: An array of strings containing the file's extended description, if available; +// if not available, the array will containin the non-extended description. +function getExtdFileDescArray(pFileList, pIdx) +{ + if (!Array.isArray(pFileList) || typeof(pIdx) !== "number" || pIdx < 0 || pIdx > pFileList.length) + return []; + + var extdDesc = ""; + if (pFileList[pIdx].hasOwnProperty("extdesc")) + extdDesc = pFileList[pIdx].extdesc; + else + { + var dirCode = (pFileList[pIdx].hasOwnProperty("dirCode") ? pFileList[pIdx].dirCode : gDirCode); + var fileMetadata = getFileInfoFromFilebase(dirCode, pFileList[pIdx].name, FileBase.DETAIL.EXTENDED); + if (fileMetadata.hasOwnProperty("extdesc")) + extdDesc = fileMetadata.extdesc; + } + if (extdDesc.length == 0) + extdDesc = pFileList[pIdx].desc; + var descLines = lfexpand(extdDesc).split("\r\n"); + // Splitting as above can result in an extra empty last line + if (descLines[descLines.length-1].length == 0) + descLines.pop(); + return descLines; +} + +// Returns a string with a character repeated a given number of times +// +// Parameters: +// pChar: The character to repeat in the string +// pNumTimes: The number of times to repeat the character +// +// Return value: A string with the given character repeated the given number of times +function charStr(pChar, pNumTimes) +{ + if (typeof(pChar) !== "string" || pChar.length == 0 || typeof(pNumTimes) !== "number" || pNumTimes < 1) + return ""; + + var str = ""; + for (var i = 0; i < pNumTimes; ++i) + str += pChar; + return str; } \ No newline at end of file diff --git a/xtrn/ddfilelister/defaultTheme.cfg b/xtrn/ddfilelister/defaultTheme.cfg index 218ea62f3b63d580c3fc772420922e65f1e5a980..8c8c9a77ff788a521c05a92f2fdaf17603a2d771 100644 --- a/xtrn/ddfilelister/defaultTheme.cfg +++ b/xtrn/ddfilelister/defaultTheme.cfg @@ -54,4 +54,17 @@ fileAreaNumHighlight=b ; The file library/directory description for 'highlight' colors (for moving a file) fileAreaDescHighlight=b ; The number of directories/files for 'highlight' colors (for moving a file) -fileAreaNumItemsHighlight=b \ No newline at end of file +fileAreaNumItemsHighlight=b + +; Traditional user interface for moving a file + +; The color of the file area menu border (for moving a file) +fileAreaMenuBorderTrad=b +; The file area entry background color for 'normal' colors (for moving a file) +fileNormalBkgTrad=n +; The color for file lists in the traditional interface +listNumTrad=gh +; The file library/directory description for 'normal' colors (for moving a file) +fileAreaDescTrad=c +; The number of directories/files for 'normal' colors (for moving a file) +fileAreaNumItemsTrad=bh diff --git a/xtrn/ddfilelister/readme.txt b/xtrn/ddfilelister/readme.txt index 8047e2a867776307e93b8e59388fc7888229493b..772c5dcb8767893db1e99b30d0e73b5200977e7e 100644 --- a/xtrn/ddfilelister/readme.txt +++ b/xtrn/ddfilelister/readme.txt @@ -1,6 +1,6 @@ Digital Distortion File Lister - Version 2.11 - Release date: 2023-05-14 + Version 2.12 + Release date: 2023-08-12 by @@ -209,6 +209,23 @@ Main configuration file (DDMsgReader.cfg) ----------------------------------------- Setting Description ------- ----------- +interfaceStyle The user interface style to use: lightbar + or traditional. Lightbar requires ANSI + support in the user's terminal; if the + user's terminal doesn't support ANSI, + ddfilelister will fall back to a + traditional user interface. + +traditionalUseSyncStock If using the traditional user interface, + whether or not to use Synchronet's stock + file lister instead of ddfilelister. Valid + values are true and false. If true, + ddfilelister won't be used at all, and + instead, Synchronet's stock file lister + will be used. If false and interfaceStyle + is traditional, ddfilelister's traditional + user interface will be used. + sortOrder String: The file sort order to use. Valid values are: NATURAL: Natural sort order (same as DATE_A) @@ -292,18 +309,35 @@ fileAreaDesc The file library/directory description for fileAreaNumItems The number of directories/files for 'normal' colors (for moving a file) -fileAreaMenuHighlightBkg The file area entry background color for - 'highlight' colors (for moving a file) +fileAreaMenuHighlightBkg The file area entry background color for + 'highlight' colors (for moving a file) -fileAreaNumHighlight The file library/directory number for - 'highlight' colors (for moving a file) +fileAreaNumHighlight The file library/directory number for + 'highlight' colors (for moving a file) -fileAreaDescHighlight The file library/directory description for - 'highlight' colors (for moving a file) +fileAreaDescHighlight The file library/directory description for + 'highlight' colors (for moving a file) fileAreaNumItemsHighlight The number of directories/files for 'highlight' colors (for moving a file) +Color settings for the traditional (non-lightbar) user interface: + +fileAreaMenuBorderTrad The color of the file area menu border (for + moving a file) + +fileNormalBkgTrad The file area entry background color for + 'normal' colors (for moving a file) + +listNumTrad The color for file lists in the traditional + interface + +fileAreaDescTrad The file library/directory description for + 'normal' colors (for moving a file) + +fileAreaNumItemsTrad The number of directories/files for + 'normal' colors (for moving a file) + 5. Strings used from text.dat ============================= diff --git a/xtrn/ddfilelister/revision_history.txt b/xtrn/ddfilelister/revision_history.txt index 0a39fbb34dd9b17addcc101fa5b120ad2456ab0a..06c7aab3c9a215aed811ac837ce01ffba85327c7 100644 --- a/xtrn/ddfilelister/revision_history.txt +++ b/xtrn/ddfilelister/revision_history.txt @@ -5,6 +5,10 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +2.12 2023-08-12 ddfilelister now has its own implementation of a + traditional (non-lightbar) user interface. It can still + optionally fall back on Synchronet's stock file lister + if the user's terminal doesn't support ANSI, if desired. 2.11 2023-05-14 The theme configuration file can now just contain the attribute characters, without the control character. Code: Refactored the function that reads the configuration