Select Git revision
-
nightfox authored
Reverting SlyEdit back to version 1.54, since there seem to still be problems with the new versions that allow text color selection.
nightfox authoredReverting SlyEdit back to version 1.54, since there seem to still be problems with the new versions that allow text color selection.
SlyEdit.js 242.52 KiB
// $Id$
/* This is a text editor for Synchronet designed to mimic the look & feel of
* DCTEdit and IceEdit, since neither of those editors have been developed
* for quite a while and still exist only as 16-bit DOS applications.
*
* Author: Eric Oulashin (AKA Nightfox)
* BBS: Digital Distortion
* BBS address: digdist.bbsindex.com
*
* Date Author Description
* 2009-05-11 Eric Oulashin Started development
* 2009-06-11 Eric Oulashin Taking a break from development
* 2009-08-09 Eric Oulashin Started more development & testing
* 2009-08-22 Eric Oulashin Version 1.00
* Initial public release
* ....Removed some comments...
* 2017-12-16 Eric Oulashin Version 1.52 beta
* Added the ability for the sysop to toggle whether or
* not to allow users to edit quote lines, using the
* configuration option allowEditQuoteLines.
* 2017-12-17 Eric Oulashin Version 1.52
* Releasing this version
* 2017-12-19 Eric Oulashin Version 1.53
* Updated the PageUp and PageDown keys to ensure they
* match what's in sbbsdefs.js, since Synchronet added
* key codes for those keys on December 17, 2018. SlyEdit
* should still work with older and newer builds of
* Synchronet, with or without the updated sbbsdefs.js.
* 2017-12-26 Eric Oulashin Version 1.54
* Improved quoting with author initials when a >
* character exists in the quote lines: Not mistaking
* the preceding text as a quote prefix if it has 3
* or more non-space characters before the >. Also
* fixed an issue where wrapped quote lines were
* sometimes missing the quote line prefix.
*/
/* Command-line arguments:
1 (argv[0]): Filename to read/edit
2 (argv[1]): Editor mode ("DCT", "ICE", or "RANDOM")
*/
// Determine the location of this script (its startup directory).
// The code for figuring this out is a trick that was created by Deuce,
// suggested by Rob Swindell. I've shortened the code a little.
var gStartupPath = '.';
try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
// EDITOR_STYLE: Can be changed to mimic the look of DCT Edit or IceEdit.
// The following are supported:
// "DCT": DCT Edit style
// "ICE": IceEdit style
// "RANDOM": Randomly choose a style
var EDITOR_STYLE = "DCT";
// The second command-line argument (argv[1]) can change this.
if (typeof(argv[1]) != "undefined")
{
var styleUpper = argv[1].toUpperCase();
// Make sure styleUpper is valid before setting EDITOR_STYLE.
if (styleUpper == "DCT")
EDITOR_STYLE = "DCT";
else if (styleUpper == "ICE")
EDITOR_STYLE = "ICE";
else if (styleUpper == "RANDOM")
EDITOR_STYLE = (Math.floor(Math.random()*2) == 0) ? "DCT" : "ICE";
}
// Load sbbsdefs.js and SlyEdit's misc. defs first
load("sbbsdefs.js");
load(gStartupPath + "SlyEdit_Misc.js");
// Determine whether the user settings file exists
const userSettingsFileExistedOnStartup = file_exists(gUserSettingsFilename);
// Load program settings from SlyEdit.cfg, and load the user configuratio nsettings
var gConfigSettings = ReadSlyEditConfigFile();
var gUserSettings = ReadUserSettingsFile(gConfigSettings);
// Load any specified 3rd-party startup scripts
for (var i = 0; i < gConfigSettings.thirdPartyLoadOnStart.length; ++i)
load(gConfigSettings.thirdPartyLoadOnStart[i]);
// Execute any provided startup JavaScript commands
for (var i = 0; i < gConfigSettings.runJSOnStart.length; ++i)
eval(gConfigSettings.runJSOnStart[i]);
const EDITOR_PROGRAM_NAME = "SlyEdit";
const ERRORMSG_PAUSE_MS = 1500;
// This script requires Synchronet version 3.14 or higher.
// Exit if the Synchronet version is below the minimum.
if (system.version_num < 31400)
{
console.print("\1n");
console.crlf();
console.print("\1n\1h\1y\1i* Warning:\1n\1h\1w " + EDITOR_PROGRAM_NAME);
console.print(" " + "requires version \1g3.14\1w or");
console.crlf();
console.print("higher of Synchronet. This BBS is using version \1g");
console.print(system.version + "\1w. Please notify the sysop.");
console.crlf();
console.pause();
exit(1); // 1: Aborted
}
// If the user's terminal doesn't support ANSI, then exit.
if (!console.term_supports(USER_ANSI))
{
console.print("\1n\r\n\1h\1yERROR: \1w" + EDITOR_PROGRAM_NAME +
" requires an ANSI terminal.\1n\r\n\1p");
exit(1); // 1: Aborted
}
// Constants
const EDITOR_VERSION = "1.54";
const EDITOR_VER_DATE = "2017-12-26";
// Program variables
var gEditTop = 6; // The top line of the edit area
var gEditBottom = console.screen_rows-2; // The last line of the edit area
// gEditLeft and gEditRight are the rightmost and leftmost columns of the edit
// area, respectively. They default to an edit area 80 characters wide
// in the center of the screen, but for IceEdit mode, the edit area will
// be on the left side of the screen to match up with the screen header.
// gEditLeft and gEditRight are 1-based.
var gEditLeft = (console.screen_columns/2).toFixed(0) - 40 + 1;
var gEditRight = gEditLeft + 79; // Based on gEditLeft being 1-based
// If the screen has less than 80 columns, then use the whole screen.
if (console.screen_columns < 80)
{
gEditLeft = 1;
gEditRight = console.screen_columns;
}
// Colors
var gQuoteWinTextColor = "\1n\1" + "7\1k"; // Normal text color for the quote window (DCT default)
var gQuoteLineHighlightColor = "\1n\1w"; // Highlighted text color for the quote window (DCT default)
var gTextAttrs = "\1n\1w"; // The text color for edit mode
var gQuoteLineColor = "\1n\1c"; // The text color for quote lines
var gUseTextAttribs = false; // Will be set to true if text colors start to be used
// gQuotePrefix contains the text to prepend to quote lines.
// gQuotePrefix will later be updated to include the "To" user's
// initials or first 2 letters of their username.
var gQuotePrefix = " > ";
// gCrossPostMsgSubs will contain sub-boards for which the user wants to
// cross-post their message into. Sub-board codes will be contained in
// objects whose name is the index to the message group in msg_area.grp_list
// to which the sub-board codes belong.
var gCrossPostMsgSubs = new Object();
// This function returns whether or not a property of the gCrossPostMsgSubs
// object is one of its member functions (i.e., something to skip when looking
// only for the message groups).
//
// Parameters:
// pPropName: The name of a property
//
// Return value: Boolean - Whether or not the property is the name of one of the
// functions in the gCrossPostMsgSubs object
gCrossPostMsgSubs.propIsFuncName = function(pPropName) {
return((pPropName == "propIsFuncName") || (pPropName == "add") || (pPropName == "remove") ||
(pPropName == "subCodeExists") || (pPropName == "numMsgGrps") ||
(pPropName == "numSubBoards"));
};
// This function returns whether or not the a message sub-coard code exists in
// gCrossPostMsgSubs.
//
// Parameters:
// pSubCode: The sub-code to look for
gCrossPostMsgSubs.subCodeExists = function(pSubCode) {
if (typeof(pSubCode) != "string")
return false;
var grpIndex = msg_area.sub[pSubCode].grp_index;
var foundIt = false;
if (this.hasOwnProperty(grpIndex))
foundIt = this[grpIndex].hasOwnProperty(pSubCode);
return foundIt;
};
// This function adds a sub-board code to gCrossPostMsgSubs.
//
// Parameters:
// pSubCode: The sub-code to add
gCrossPostMsgSubs.add = function(pSubCode) {
if (typeof(pSubCode) != "string")
return;
if (this.subCodeExists(pSubCode))
return;
var grpIndex = msg_area.sub[pSubCode].grp_index;
if (!this.hasOwnProperty(grpIndex))
this[grpIndex] = new Object();
this[grpIndex][pSubCode] = true;
};
// This function removes a sub-board code from gCrossPostMsgSubs.
//
// Parameters:
// pSubCode: The sub-code to remove
gCrossPostMsgSubs.remove = function(pSubCode) {
if (typeof(pSubCode) != "string")
return;
var grpIndex = msg_area.sub[pSubCode].grp_index;
if (this.hasOwnProperty(grpIndex))
{
delete this[grpIndex][pSubCode];
if (numObjProperties(this[grpIndex]) == 0)
delete this[grpIndex];
}
};
// This function returns the number of message groups in
// gCrossPostMsgSubs.
gCrossPostMsgSubs.numMsgGrps = function() {
var msgGrpCount = 0;
for (var prop in this)
{
if (!this.propIsFuncName(prop))
++msgGrpCount;
}
return msgGrpCount;
};
// This function returns the number of sub-boards the user has chosen to post
// the message into.
gCrossPostMsgSubs.numSubBoards = function () {
var numMsgSubs = 0;
for (var grpIndex in this)
{
if (!this.propIsFuncName(grpIndex))
{
for (var subCode in gCrossPostMsgSubs[grpIndex])
++numMsgSubs;
}
}
return numMsgSubs;
}
// Set variables properly for the different editor styles. Also, set up
// function pointers for functionality common to the IceEdit & DCTedit styles.
var fpDrawQuoteWindowTopBorder = null;
var fpDisplayTextAreaBottomBorder = null;
var fpDrawQuoteWindowBottomBorder = null;
var fpRedrawScreen = null;
var fpUpdateInsertModeOnScreen = null;
var fpDisplayBottomHelpLine = null;
var fpHandleESCMenu = null;
var fpDisplayTime = null;
var fpDisplayTimeRemaining = null;
if (EDITOR_STYLE == "DCT")
{
load(gStartupPath + "SlyEdit_DCTStuff.js");
gEditTop = 6;
gQuoteWinTextColor = gConfigSettings.DCTColors.QuoteWinText;
gQuoteLineHighlightColor = gConfigSettings.DCTColors.QuoteLineHighlightColor;
gTextAttrs = gConfigSettings.DCTColors.TextEditColor;
gQuoteLineColor = gConfigSettings.DCTColors.QuoteLineColor;
// Function pointers for the DCTEdit-style screen update functions
fpDrawQuoteWindowTopBorder = DrawQuoteWindowTopBorder_DCTStyle;
fpDisplayTextAreaBottomBorder = DisplayTextAreaBottomBorder_DCTStyle;
fpDrawQuoteWindowBottomBorder = DrawQuoteWindowBottomBorder_DCTStyle;
fpRedrawScreen = redrawScreen_DCTStyle;
fpUpdateInsertModeOnScreen = updateInsertModeOnScreen_DCTStyle;
fpDisplayBottomHelpLine = DisplayBottomHelpLine_DCTStyle;
fpHandleESCMenu = handleDCTESCMenu;
fpDisplayTime = displayTime_DCTStyle;
fpDisplayTimeRemaining = displayTimeRemaining_DCTStyle;
}
else if (EDITOR_STYLE == "ICE")
{
load(gStartupPath + "SlyEdit_IceStuff.js");
gEditTop = 5;
gQuoteWinTextColor = gConfigSettings.iceColors.QuoteWinText;
gQuoteLineHighlightColor = gConfigSettings.iceColors.QuoteLineHighlightColor;
gTextAttrs = gConfigSettings.iceColors.TextEditColor;
gQuoteLineColor = gConfigSettings.iceColors.QuoteLineColor;
// Function pointers for the IceEdit-style screen update functions
fpDrawQuoteWindowTopBorder = DrawQuoteWindowTopBorder_IceStyle;
fpDisplayTextAreaBottomBorder = DisplayTextAreaBottomBorder_IceStyle;
fpDrawQuoteWindowBottomBorder = DrawQuoteWindowBottomBorder_IceStyle;
fpRedrawScreen = redrawScreen_IceStyle;
fpUpdateInsertModeOnScreen = updateInsertModeOnScreen_IceStyle;
fpDisplayBottomHelpLine = DisplayBottomHelpLine_IceStyle;
fpHandleESCMenu = handleIceESCMenu;
fpDisplayTime = displayTime_IceStyle;
fpDisplayTimeRemaining = displayTimeRemaining_IceStyle;
}
// Temporary (for testing): Make the edit area small
//gEditLeft = 25;
//gEditRight = 45;
//gEditBottom = gEditTop + 1;
// End Temporary
// Calculate the edit area width & height
const gEditWidth = gEditRight - gEditLeft + 1;
const gEditHeight = gEditBottom - gEditTop + 1;
// Message display & edit variables
var gInsertMode = "INS"; // Insert (INS) or overwrite (OVR) mode
var gQuoteLines = new Array(); // Array of quote lines loaded from file, if in quote mode
var gUserHasOpenedQuoteWindow = false; // Whether or not the user has opened the quote line selection window
var gQuoteLinesTopIndex = 0; // Index of the first displayed quote line
var gQuoteLinesIndex = 0; // Index of the current quote line
// The gEditLines array will contain TextLine objects storing the line
// information.
var gEditLines = new Array();
var gEditLinesIndex = 0; // Index into gEditLines for the line being edited
var gTextLineIndex = 0; // Index into the current text line being edited
// Format strings used for printf() to display text in the edit area
const gFormatStr = "%-" + gEditWidth + "s";
const gFormatStrWithAttr = "%s%-" + gEditWidth + "s";
// gEditAreaBuffer will be an array of strings for the edit area, which
// will be checked by displayEditLines() before outputting text lines
// to optimize the update of message text on the screen. displayEditLines()
// will also update this array after writing a line of text to the screen.
// The indexes in this array are the absolute screen lines.
var gEditAreaBuffer = new Array();
function clearEditAreaBuffer()
{
for (var lineNum = gEditTop; lineNum <= gEditBottom; ++lineNum)
gEditAreaBuffer[lineNum] = "";
}
clearEditAreaBuffer();
// gTxtreplacements will be an associative array that stores words (in upper
// case) to be replaced with other words.
var gNumTxtReplacements = 0;
var gTxtReplacements = new Object();
if (gConfigSettings.enableTextReplacements)
gNumTxtReplacements = populateTxtReplacements(gTxtReplacements, gConfigSettings.textReplacementsUseRegex);
// Set some stuff up for message editing
var gUseQuotes = true;
var gInputFilename = file_getcase(system.node_dir + "QUOTES.TXT");
if (gInputFilename == undefined)
{
gUseQuotes = false;
if ((argc > 0) && (gInputFilename == undefined))
gInputFilename = argv[0];
}
else
{
var all_files = directory(system.node_dir + "*");
var newest_filedate = -Infinity;
var newest_filename;
for (var file in all_files)
{
if (all_files[file].search(/quotes.txt$/i) != -1)
{
var this_date = file_date(all_files[file]);
if (this_date > newest_filedate)
{
newest_filename = all_files[file];
newest_filedate = this_date;
}
}
}
if (newest_filename != undefined)
gInputFilename = newest_filename;
}
var gOldStatus = bbs.sys_status;
bbs.sys_status &=~SS_PAUSEON;
bbs.sys_status |= SS_PAUSEOFF;
var gOldPassthru = console.ctrlkey_passthru;
console.ctrlkey_passthru = "+ACGKLOPQRTUVWXYZ_";
// Set some on-exit code to ensure that the original ctrl key passthru & system
// status settings are restored upon script exit, even if there is a runtime error.
js.on_exit("console.ctrlkey_passthru = gOldPassthru; bbs.sys_status = gOldStatus;");
// Enable delete line in SyncTERM (Disabling ANSI Music in the process)
console.write("\033[=1M");
console.clear();
// Read the message from name, to name, and subject from the drop file
// (msginf in the node directory).
var gMsgAreaInfo = null; // Will store the value returned by getCurMsgInfo().
var setMsgAreaInfoObj = false;
var gMsgSubj = "";
var gFromName = user.alias;
var gToName = gInputFilename;
var gMsgArea = "";
var gTaglineFile = "";
var dropFileTime = -Infinity;
var dropFileName = file_getcase(system.node_dir + "msginf");
if (dropFileName != undefined)
{
if (file_date(dropFileName) >= dropFileTime)
{
var dropFile = new File(dropFileName);
if (dropFile.exists && dropFile.open("r"))
{
dropFileTime = dropFile.date;
info = dropFile.readAll();
dropFile.close();
gFromName = info[0];
gToName = info[1];
gMsgSubj = info[2];
gMsgArea = info[4];
// Now that we know the name of the message area
// that the message is being posted in, call
// getCurMsgInfo() to set gMsgAreaInfo.
gMsgAreaInfo = getCurMsgInfo(gMsgArea);
setMsgAreaInfoObj = true;
// If the msginf file has 7 lines, then the 7th line is the full
// path & filename of the tagline file, where we can write the
// user's chosen tag line (if the user has that option enabled)
// for the BBS software to read the tagline & insert it at the
// proper place in the message.
if (info.length >= 7)
gTaglineFile = info[6];
}
}
file_remove(dropFileName);
}
// If gMsgAreaInfo hasn't been set yet, then set it.
if (!setMsgAreaInfoObj)
{
gMsgAreaInfo = getCurMsgInfo(gMsgArea);
setMsgAreaInfoObj = true;
}
// Set a variable to store whether or not cross-posting can be done.
var gCanCrossPost = (gConfigSettings.allowCrossPosting && postingInMsgSubBoard(gMsgArea));
// If the user is posting in a message sub-board, then add its information
// to gCrossPostMsgSubs.
if (postingInMsgSubBoard(gMsgArea))
gCrossPostMsgSubs.add(gMsgAreaInfo.subBoardCode);
// Open the quote file / message file
readQuoteOrMessageFile();
// If the subject is blank, set it to something.
if (gMsgSubj == "")
gMsgSubj = gToName.replace(/^.*[\\\/]/,'');
// Store a copy of the current subject (possibly allowing the user to
// change the subject in the future)
var gOldSubj = gMsgSubj;
// Now it's edit time.
var exitCode = doEditLoop();
// Remove any extra blank lines that may be at the end of
// the message (in gEditLines).
if ((exitCode == 0) && (gEditLines.length > 0))
{
var lineIndex = gEditLines.length - 1;
while ((lineIndex > 0) && (lineIndex < gEditLines.length) &&
(gEditLines[lineIndex].length() == 0))
{
gEditLines.splice(lineIndex, 1);
--lineIndex;
}
}
// Clear the screen and display the end-of-program information (if the setting
// is enabled).
console.clear("\1n");
if (gConfigSettings.displayEndInfoScreen)
{
displayProgramExitInfo(false);
console.crlf();
}
// If the user wrote & saved a message, then output the message
// lines to a file with the passed-in input filename.
var savedTheMessage = false;
if ((exitCode == 0) && (gEditLines.length > 0))
{
// Store whether the user is still posting the message in the original sub-board
// and whether that's the only sub-board they're posting in.
var postingInOriginalSubBoard = gCrossPostMsgSubs.subCodeExists(gMsgAreaInfo.subBoardCode);
var postingOnlyInOriginalSubBoard = (postingInOriginalSubBoard && (gCrossPostMsgSubs.numSubBoards() == 1));
// If some message areas have been selected for cross-posting, then otuput
// which areas will be cross-posted into, and do the cross-posting.
var crossPosted = false;
if (gCrossPostMsgSubs.numMsgGrps() > 0)
{
// If the will cross-post into other sub-boards, then create a string containing
// the user's entire message.
var msgContents = "";
if (!postingOnlyInOriginalSubBoard)
{
// Append each line to msgContents. Then,
// - If using Synchronet 3.15 or higher:
// Depending on whether the line has a hard newline
// or a soft newline, append a "\r\n" or a " \n", as
// per Synchronet's standard as of 3.15.
// - Otherwise (Synchronet 3.14 and below):
// Just append a "\r\n" to the line
if (system.version_num >= 31500)
{
var useHardNewline = false;
for (var i = 0; i < gEditLines.length; ++i)
{
// Use a hard newline if the current edit line has one or if this is
// the last line of the message.
useHardNewline = (gEditLines[i].hardNewlineEnd || (i == gEditLines.length-1));
msgContents += gEditLines[i].text + (useHardNewline ? "\r\n" : " \n");
}
}
else // Synchronet 3.14 and below
{
for (var i = 0; i < gEditLines.length; ++i)
msgContents += gEditLines[i].text + "\r\n";
}
// Read the user's signature, in case they have one
var msgSigInfo = readUserSigFile();
// If the user has not chosen to auto-sign messages, then also append their
// signature to the message now.
if (!gUserSettings.autoSignMessages)
{
// Append a blank line to separate the message & signature.
// Note: msgContents already has a newline at the end, so we don't have
// to append one here; just append the signature.
if (msgSigInfo.sigContents.length > 0)
msgContents += msgSigInfo.sigContents + "\r\n";
}
}
console.print("\1n");
console.crlf();
console.print("\1n" + gConfigSettings.genColors.msgWillBePostedHdr + "Your message will be posted into the following area(s):");
console.crlf();
// If the user is posting in the originally-chosen sub-board and other sub-boards,
// then make a log in the BBS log that the user is posting a message (for
// cross-post logging).
if (postingInOriginalSubBoard && !postingOnlyInOriginalSubBoard)
{
log(LOG_INFO, "SlyEdit: " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaInfo.subBoardCode].grp_name +
" " + msg_area.sub[gMsgAreaInfo.subBoardCode].description + " (" + gMsgSubj + ")");
bbs.log_str("SlyEdit: " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaInfo.subBoardCode].grp_name +
" " + msg_area.sub[gMsgAreaInfo.subBoardCode].description + " (" + gMsgSubj + ")");
}
var postMsgErrStr = ""; // For storing errors related to saving the message
for (var grpIndex in gCrossPostMsgSubs)
{
// Skip the function names (we only want the group indexes)
if (gCrossPostMsgSubs.propIsFuncName(grpIndex))
continue;
console.print("\1n" + gConfigSettings.genColors.msgPostedGrpHdr + msg_area.grp_list[grpIndex].description + ":");
console.crlf();
for (var subCode in gCrossPostMsgSubs[grpIndex])
{
if (subCode == gMsgAreaInfo.subBoardCode)
{
printf("\1n " + gConfigSettings.genColors.msgPostedSubBoardName + "%-48s", msg_area.sub[subCode].description.substr(0, 48));
console.print("\1n " + gConfigSettings.genColors.msgPostedOriginalAreaText + "(original message area)");
}
// If subCode is not the user's current sub, then if the user is allowed
// to post in that sub, then post the message there.
else
{
// Write a log in the BBS log about which message area the user is
// cross-posting into.
log(LOG_INFO, "SlyEdit: " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
" " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
bbs.log_str("SlyEdit: " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
" " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
// Write the cross-posting message area on the user's screen.
printf("\1n " + gConfigSettings.genColors.msgPostedSubBoardName + "%-73s", msg_area.sub[subCode].description.substr(0, 73));
if (user.compare_ars(msg_area.sub[subCode].post_ars))
{
// If the user's auto-sign setting is enabled, then auto-sign
// the message and append their signature afterward. Otherwise,
// don't auto-sign, and their signature has already been appended.
if (gUserSettings.autoSignMessages)
{
var msgContents2 = msgContents + "\r\n";
msgContents2 += getSignName(subCode, gUserSettings.autoSignRealNameOnlyFirst, gUserSettings.autoSignEmailsRealName);
msgContents2 += "\r\n\r\n";
if (msgSigInfo.sigContents.length > 0)
msgContents2 += msgSigInfo.sigContents + "\r\n";
postMsgErrStr = postMsgToSubBoard(subCode, gToName, gMsgSubj, msgContents2, user.number);
}
else
postMsgErrStr = postMsgToSubBoard(subCode, gToName, gMsgSubj, msgContents, user.number);
if (postMsgErrStr.length == 0)
{
savedTheMessage = true;
crossPosted = true;
console.print("\1n\1h\1b[\1n\1g" + CHECK_CHAR + "\1n\1h\1b]\1n");
}
else
{
console.print("\1n\1h\1b[\1rX\1b]\1n");
console.crlf();
console.print(" \1n\1h\1r*\1n " + postMsgErrStr);
console.crlf();
}
}
else
{
// The user isn't allowed to post in the sub. Output a message
// saying so.
console.print("\1n\1h\1b[\1rX\1b]\1n");
console.crlf();
console.print(" \1n\1h\1r*\1n You're not allowed to post in this area.");
console.crlf();
}
}
console.crlf();
}
}
console.print("\1n");
console.crlf();
}
// Determine whether or not to save the message to INPUT.MSG. We want to
// save it by default, but if the user is posting in a sub-board, whether we
// want to save it will be determined by whether the user's current sub-board
// code is in the list of cross-post areas.
var saveMsgFile = true;
if (postingInMsgSubBoard(gMsgArea))
{
if (!gCrossPostMsgSubs.subCodeExists(gMsgAreaInfo.subBoardCode))
{
saveMsgFile = false;
// If the message was cross-posted to other message areas and not in the
// user's current message area, output a message to say that Synchronet
// will say the message was aborted, and that's normal.
if (crossPosted)
{
console.print("\1n\1c* Note: Your message has been cross-posted in areas other than your currently-");
console.crlf();
console.print("selected message area. Because your message was not saved for your currently-");
console.crlf();
console.print("selected message area, the BBS will say the message was aborted, even");
console.crlf();
console.print("though it was posted in those other areas. This is normal.n");
console.crlf();
console.crlf();
}
}
}
if (saveMsgFile)
{
// Open the output filename. If no arguments were passed, then use
// INPUT.MSG in the node's temporary directory; otherwise, use the
// first program argument.
var msgFile = new File((argc == 0 ? system.temp_dir + "INPUT.MSG" : argv[0]));
if (msgFile.open("w"))
{
// Write each line of the message to the file. Note: The
// "Expand Line Feeds to CRLF" option should be turned on
// in SCFG for this to work properly for all platforms.
for (var i = 0; i < gEditLines.length; ++i)
msgFile.writeln(gEditLines[i].text);
// Auto-sign the message if the user's setting to do so is enabled
if (gUserSettings.autoSignMessages)
{
msgFile.writeln("");
var subCode = (postingInMsgSubBoard(gMsgArea) ? gMsgAreaInfo.subBoardCode : "mail");
msgFile.writeln(getSignName(subCode, gUserSettings.autoSignRealNameOnlyFirst, gUserSettings.autoSignEmailsRealName));
}
msgFile.close();
savedTheMessage = true;
}
else
console.print("nrh* Unable to save the message!n\r\n");
}
}
/*
// Note: If we were using WWIV editor.inf/result.ed drop files, we
// could allow the user to change the subject and write the new
// subject in result.ed..
if (savedTheMessage)
{
gMsgSubj = "New subject";
if (gMsgSubj != gOldSubj)
{
var dropFile = new File(system.node_dir + "result.ed");
if (dropFile.open("w"))
{
dropFile.writeln("0");
dropFile.writeln(gMsgSubj);
dropFile.close();
}
}
}
*/
// Set the original ctrlkey_passthru and sys_status settins back.
console.ctrlkey_passthru = gOldPassthru;
bbs.sys_status = gOldStatus;
// Set the end-of-program status message.
var endStatusMessage = "";
if (exitCode == 1)
endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
else if (exitCode == 0)
{
if (gEditLines.length > 0)
{
if (savedTheMessage)
endStatusMessage = gConfigSettings.genColors.msgHasBeenSavedText + "The message has been saved.";
else
endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
}
else
endStatusMessage = gConfigSettings.genColors.emptyMsgNotSentText + "Empty message not sent.";
}
// We shouldn't hit this else case, but it's here just to be safe.
else
endStatusMessage = gConfigSettings.genColors.genMsgErrorText + "Possible message error.";
console.print(endStatusMessage);
console.crlf();
// If the user's setting to pause after every screenful is disabled, then
// pause here so that they can see the exit information.
if (user.settings & USER_PAUSE == 0)
mswait(1000);
// Load any specified 3rd-party exit scripts and execute any provided exit
// JavaScript commands.
for (var i = 0; i < gConfigSettings.thirdPartyLoadOnExit.length; ++i)
load(gConfigSettings.thirdPartyLoadOnExit[i]);
for (var i = 0; i < gConfigSettings.runJSOnExit.length; ++i)
eval(gConfigSettings.runJSOnExit[i]);
exit(exitCode);
// End of script execution
///////////////////////////////////////////////////////////////////////////////////
// Functions
// Reads the quote/message file (must be done on startup)
function readQuoteOrMessageFile()
{
var inputFile = new File(gInputFilename);
if (inputFile.open("r", false))
{
// Read into the gQuoteLines or gEditLines array, depending on the value
// of gUseQuotes. Use a buffer size that should be long enough.
if (gUseQuotes)
{
var textLine = null; // Line of text read from the quotes file
while (!inputFile.eof)
{
textLine = inputFile.readln(2048);
// Only use textLine if it's actually a string.
if (typeof(textLine) == "string")
{
textLine = strip_ctrl(textLine);
// If the line has only whitespace and/or > characters,
// then make the line blank before putting it into
// gQuoteLines.
if (/^[\s>]+$/.test(textLine))
textLine = "";
gQuoteLines.push(textLine);
}
}
}
else
{
var textLine = null;
while (!inputFile.eof)
{
textLine = new TextLine();
textLine.text = inputFile.readln(2048);
if (typeof(textLine.text) == "string")
textLine.text = strip_ctrl(textLine.text);
else
textLine.text = "";
textLine.hardNewlineEnd = true;
// If there would still be room on the line for at least
// 1 more character, then add a space to the end of the
// line.
if (textLine.text.length < console.screen_columns-1)
textLine.text += " ";
gEditLines.push(textLine);
}
// If the last edit line is undefined (which is possible after reading the end
// of the quotes file), then remove it from gEditLines.
if (gEditLines.length > 0)
{
if (gEditLines.length > 0)
{
var lastQuoteLineIndex = gEditLines.length - 1;
if (gEditLines[lastQuoteLineIndex].text == undefined)
gEditLines.splice(lastQuoteLineIndex, 1);
}
}
}
inputFile.close();
}
}
// Edit mode & input loop
function doEditLoop()
{
// Return codes:
// 0: Success
// 1: Aborted
var returnCode = 0;
// Set the shortcut keys. Note - Avoid the following:
// CTRL_B: Home key
// CTRL_E: End key
// CTRL_F: Right arrow key
// CTRL_H: Backspace
// CTRL_J: Down arrow key
// CTRL_M: Enter key
// CTRL_N: PageDown
// CTRL_P: PageUp
// CTRL_V: Insert key
const ABORT_KEY = CTRL_A;
const CROSSPOST_KEY = CTRL_C;
const DELETE_LINE_KEY = CTRL_D;
const GENERAL_HELP_KEY = CTRL_G;
const TOGGLE_INSERT_KEY = CTRL_I;
const CHANGE_COLOR_KEY = CTRL_K;
const CMDLIST_HELP_KEY = CTRL_L;
const CMDLIST_HELP_KEY_2 = KEY_F1;
const IMPORT_FILE_KEY = CTRL_O;
const QUOTE_KEY = CTRL_Q;
const PROGRAM_INFO_HELP_KEY = CTRL_R;
const SEARCH_TEXT_KEY = CTRL_S;
const LIST_TXT_REPLACEMENTS_KEY = CTRL_T;
const USER_SETTINGS_KEY = CTRL_U;
const EXPORT_TO_FILE_KEY = CTRL_X;
const SAVE_KEY = CTRL_Z;
// Draw the screen.
// Note: This is purposefully drawing the top of the message. We
// want to place the cursor at the first character on the top line,
// too. This is for the case where we're editing an existing message -
// we want to start editigng it at the top.
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs, gInsertMode, gUseQuotes, 0, displayEditLines);
var curpos = new Object();
curpos.x = gEditLeft;
curpos.y = gEditTop;
console.gotoxy(curpos);
// initialTimeLeft and updateTimeLeft will be used to keep track of the user's
// time remaining so that we can update the user's time left on the screen.
var initialTimeLeft = bbs.time_left;
var updateTimeLeft = false;
// Input loop
var userInput = "";
var currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
var numKeysPressed = 0; // Used only to determine when to call updateTime()
var continueOn = true;
while (continueOn)
{
userInput = getKeyWithESCChars(K_NOCRLF|K_NOSPIN, gConfigSettings);
// If userInput is blank, then the input timeout was probably
// reached, so abort.
if (userInput == "")
{
returnCode = 1; // Aborted
continueOn = false;
console.crlf();
console.print("\1n\1h\1r" + EDITOR_PROGRAM_NAME + ": Input timeout reached.");
continue;
}
// If we get here, that means the timeout wasn't reached.
++numKeysPressed;
// If gEditLines currently has 1 less line than we need,
// then add a new line to gEditLines.
if (gEditLines.length == gEditLinesIndex)
gEditLines.push(new TextLine());
// Take the appropriate action for the key pressed.
switch (userInput)
{
case ABORT_KEY:
// Before aborting, ask they user if they really want to abort.
if (promptYesNo("Abort message", false, "Abort", false))
{
returnCode = 1; // Aborted
continueOn = false;
}
else
{
// Make sure the edit color attribute is set.
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
}
break;
case SAVE_KEY:
returnCode = 0; // Save
continueOn = false;
break;
case CMDLIST_HELP_KEY:
case CMDLIST_HELP_KEY_2:
displayCommandList(true, true, true, gCanCrossPost, gConfigSettings.userIsSysop,
gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
displayEditLines);
break;
case GENERAL_HELP_KEY:
displayGeneralHelp(true, true, true);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
displayEditLines);
break;
case PROGRAM_INFO_HELP_KEY:
displayProgramInfo(true, true);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
displayEditLines);
break;
case QUOTE_KEY:
// Let the user choose & insert quote lines into the message.
if (gUseQuotes)
{
var retObject = doQuoteSelection(curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
// If user input timed out, then abort.
if (retObject.timedOut)
{
returnCode = 1; // Aborted
continueOn = false;
console.crlf();
console.print("\1n\1h\1r" + EDITOR_PROGRAM_NAME + ": Input timeout reached.");
continue;
}
}
break;
case CHANGE_COLOR_KEY:
// Let the user change the text color.
/*if (gConfigSettings.allowColorSelection)
{
var retObj = doColorSelection(gTextAttrs, curpos, currentWordLength);
if (!retObj.timedOut)
{
// Note: DoColorSelection() will prefix the color with the normal
// attribute.
gTextAttrs = retObj.txtAttrs;
console.print(gTextAttrs);
curpos.x = retObj.x;
curpos.y = retObj.y;
currentWordLength = retObj.currentWordLength;
}
else
{
// User input timed out, so abort.
returnCode = 1; // Aborted
continueOn = false;
console.crlf();
console.print("\1n\1h\1r" + EDITOR_PROGRAM_NAME + ": Input timeout reached.");
continue;
}
}*/
break;
case KEY_UP:
// Move the cursor up one line.
if (gEditLinesIndex > 0)
{
--gEditLinesIndex;
// gTextLineIndex should containg the index in the text
// line where the cursor would add text. If the previous
// line is shorter than the one we just left, then
// gTextLineIndex and curpos.x need to be adjusted.
if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
{
gTextLineIndex = gEditLines[gEditLinesIndex].length();
curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
}
// Figure out the vertical coordinate of where the
// cursor should be.
// If the cursor is at the top of the edit area,
// then scroll up through the message by 1 line.
if (curpos.y == gEditTop)
displayEditLines(gEditTop, gEditLinesIndex, gEditBottom, true, /*true*/false);
else
--curpos.y;
console.gotoxy(curpos);
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
console.print(chooseEditColor()); // Make sure the edit color is correct
}
break;
case KEY_DOWN:
// Move the cursor down one line.
if (gEditLinesIndex < gEditLines.length-1)
{
++gEditLinesIndex;
// gTextLineIndex should containg the index in the text
// line where the cursor would add text. If the next
// line is shorter than the one we just left, then
// gTextLineIndex and curpos.x need to be adjusted.
if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
{
gTextLineIndex = gEditLines[gEditLinesIndex].length();
curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
}
// Figure out the vertical coordinate of where the
// cursor should be.
// If the cursor is at the bottom of the edit area,
// then scroll down through the message by 1 line.
if (curpos.y == gEditBottom)
{
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
gEditBottom, true, /*true*/false);
}
else
++curpos.y;
console.gotoxy(curpos);
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
console.print(chooseEditColor()); // Make sure the edit color is correct
}
break;
case KEY_LEFT:
// If the horizontal cursor position is right of the
// leftmost edit position, then let it move left.
if (curpos.x > gEditLeft)
{
--curpos.x;
console.gotoxy(curpos);
if (gTextLineIndex > 0)
--gTextLineIndex;
}
else
{
// The cursor is at the leftmost position in the
// edit area. If there are text lines above the
// current line, then move the cursor to the end
// of the previous line.
if (gEditLinesIndex > 0)
{
--gEditLinesIndex;
curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
// Move the cursor up or scroll up by one line
if (curpos.y > 1)
--curpos.y;
else
displayEditLines(gEditTop, gEditLinesIndex, gEditBottom, true, /*true*/false);
gTextLineIndex = gEditLines[gEditLinesIndex].length();
console.gotoxy(curpos);
}
}
console.print(chooseEditColor());
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
// Make sure the edit color is correct
console.print(chooseEditColor());
break;
case KEY_RIGHT:
// If the horizontal cursor position is left of the
// rightmost edit position, then the cursor can move
// to the right.
if (curpos.x < gEditRight)
{
// The current line index must be within bounds
// before we can move the cursor to the right.
if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
{
++curpos.x;
console.gotoxy(curpos);
++gTextLineIndex;
}
else
{
// The cursor is at the rightmost position on the
// line. If there are text lines below the current
// line, then move the cursor to the start of the
// next line.
if (gEditLinesIndex < gEditLines.length-1)
{
++gEditLinesIndex;
curpos.x = gEditLeft;
// Move the cursor down or scroll down by one line
if (curpos.y < gEditBottom)
++curpos.y;
else
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
gEditBottom, true, /*true*/false);
gTextLineIndex = 0;
console.gotoxy(curpos);
}
}
}
else
{
// The cursor is at the rightmost position in the
// edit area. If there are text lines below the
// current line, then move the cursor to the start
// of the next line.
if (gEditLinesIndex < gEditLines.length-1)
{
++gEditLinesIndex;
curpos.x = gEditLeft;
// Move the cursor down or scroll down by one line
if (curpos.y < gEditBottom)
++curpos.y;
else
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), gEditBottom, true, false);
gTextLineIndex = 0;
console.gotoxy(curpos);
}
}
console.print(chooseEditColor());
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
// Make sure the edit color is correct
console.print(chooseEditColor());
break;
case KEY_HOME:
// Go to the beginning of the line
gTextLineIndex = 0;
curpos.x = gEditLeft;
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
break;
case KEY_END:
// Go to the end of the line
if (gEditLinesIndex < gEditLines.length)
{
gTextLineIndex = gEditLines[gEditLinesIndex].length();
curpos.x = gEditLeft + gTextLineIndex;
// If the cursor position would be to the right of the edit
// area, then place it at gEditRight.
if (curpos.x > gEditRight)
{
var difference = curpos.x - gEditRight;
curpos.x -= difference;
gTextLineIndex -= difference;
}
// Place the cursor where it should be.
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
break;
case BACKSPACE:
// Delete the previous character
if (textLineIsEditable(gEditLinesIndex))
{
var retObject = doBackspace(curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
// Make sure the edit color is correct
console.print(chooseEditColor());
}
break;
case KEY_DEL:
// Delete the next character
if (textLineIsEditable(gEditLinesIndex))
{
var retObject = doDeleteKey(curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
// Make sure the edit color is correct
console.print(chooseEditColor());
}
break;
case KEY_ENTER:
var cursorAtBeginningOrEnd = ((curpos.x == 1) || (curpos.x >= gEditLines[gEditLinesIndex].length()));
var letUserEditLine = (cursorAtBeginningOrEnd ? true : textLineIsEditable(gEditLinesIndex));
if (letUserEditLine)
{
var retObject = doEnterKey(curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
returnCode = retObject.returnCode;
continueOn = retObject.continueOn;
// Check for whether we should do quote selection or
// show the help screen (if the user entered /Q or /?)
if (continueOn)
{
if (retObject.doQuoteSelection)
{
if (gUseQuotes)
{
retObject = doQuoteSelection(curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
// If user input timed out, then abort.
if (retObject.timedOut)
{
returnCode = 1; // Aborted
continueOn = false;
console.crlf();
console.print("\1n\1h\1r" + EDITOR_PROGRAM_NAME + ": Input timeout reached.");
continue;
}
}
}
else if (retObject.showHelp)
{
displayProgramInfo(true, false);
displayCommandList(false, false, true, gCanCrossPost, gConfigSettings.userIsSysop,
gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
displayEditLines);
console.gotoxy(curpos);
}
else if (retObject.doCrossPostSelection)
{
if (gCanCrossPost)
doCrossPosting();
}
}
// Make sure the edit color is correct
console.print(chooseEditColor());
}
break;
// Insert/overwrite mode toggle
case KEY_INSERT:
case TOGGLE_INSERT_KEY:
toggleInsertMode(null);
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
console.gotoxy(curpos);
break;
case KEY_ESC:
// Do the ESC menu
var retObj = fpHandleESCMenu(curpos, currentWordLength);
returnCode = retObj.returnCode;
continueOn = retObj.continueOn;
curpos.x = retObj.x;
curpos.y = retObj.y;
currentWordLength = retObj.currentWordLength;
// If we can continue on, put the cursor back
// where it should be.
if (continueOn)
{
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
console.gotoxy(curpos);
}
break;
case SEARCH_TEXT_KEY:
var retObj = findText(curpos);
curpos.x = retObj.x;
curpos.y = retObj.y;
console.print(chooseEditColor()); // Make sure the edit color is correct
break;
case IMPORT_FILE_KEY:
// Only let sysops import files.
if (gConfigSettings.userIsSysop)
{
var retObj = importFile(gConfigSettings.userIsSysop, curpos);
curpos.x = retObj.x;
curpos.y = retObj.y;
currentWordLength = retObj.currentWordLength;
console.print(chooseEditColor()); // Make sure the edit color is correct
}
break;
case EXPORT_TO_FILE_KEY:
// Only let sysops export/save the message to a file.
if (gConfigSettings.userIsSysop)
{
exportToFile(gConfigSettings.userIsSysop);
console.gotoxy(curpos);
}
break;
case DELETE_LINE_KEY:
var retObj = doDeleteLine(curpos);
curpos.x = retObj.x;
curpos.y = retObj.y;
currentWordLength = retObj.currentWordLength;
console.print(chooseEditColor()); // Make sure the edit color is correct
break;
case KEY_PAGE_UP: // Move 1 page up in the message
// Calculate the index of the message line shown at the top
// of the edit area.
var topEditIndex = gEditLinesIndex-(curpos.y-gEditTop);
// If topEditIndex is > 0, then we can page up.
if (topEditIndex > 0)
{
// Calculate the new top edit line index.
// If there is a screenful or more of lines above the top,
// then set topEditIndex to what it would need to be for the
// previous page. Otherwise, set topEditIndex to 0.
if (topEditIndex >= gEditHeight)
topEditIndex -= gEditHeight;
else
topEditIndex = 0;
// Refresh the edit area
displayEditLines(gEditTop, topEditIndex, gEditBottom, true, /*true*/false);
// Set the cursor to the last place on the last line.
gEditLinesIndex = topEditIndex + gEditHeight - 1;
gTextLineIndex = gEditLines[gEditLinesIndex].length();
if ((gTextLineIndex > 0) && (gEditLines[gEditLinesIndex].length == gEditWidth))
--gTextLineIndex;
curpos.x = gEditLeft + gTextLineIndex;
curpos.y = gEditBottom;
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
else
{
// topEditIndex is 0. If gEditLinesIndex is not already 0,
// then make it 0 and place the cursor at the first line.
if (gEditLinesIndex > 0)
{
gEditLinesIndex = 0;
gTextLineIndex = 0;
curpos.x = gEditLeft;
curpos.y = gEditTop;
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
}
console.print(chooseEditColor()); // Make sure the edit color is correct
break;
case KEY_PAGE_DOWN: // Move 1 page down in the message
// Calculate the index of the message line shown at the top
// of the edit area, and the index of the line that would be
// shown at the bottom of the edit area.
var topEditIndex = gEditLinesIndex-(curpos.y-gEditTop);
var bottomEditIndex = topEditIndex + gEditHeight - 1;
// If bottomEditIndex is less than the last index, then we can
// page down.
var lastEditLineIndex = gEditLines.length-1;
if (bottomEditIndex < lastEditLineIndex)
{
// Calculate the new top edit line index.
// If there is a screenful or more of lines below the bottom,
// then set topEditIndex to what it would need to be for the
// next page. Otherwise, set topEditIndex to the right
// index to display the last full page.
if (gEditLines.length - gEditHeight > bottomEditIndex)
topEditIndex += gEditHeight;
else
topEditIndex = gEditLines.length - gEditHeight;
// Refresh the edit area
displayEditLines(gEditTop, topEditIndex, gEditBottom, true, /*true*/false);
// Set the cursor to the first place on the first line.
gEditLinesIndex = topEditIndex;
gTextLineIndex = 0;
curpos.x = gEditLeft;
curpos.y = gEditTop;
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
else
{
// bottomEditIndex >= the last edit line index.
// If gEditLinesIndex is not already equal to bottomEditIndex,
// make it so and put the cursor at the end of the last line.
if (gEditLinesIndex < bottomEditIndex)
{
var oldEditLinesIndex = gEditLinesIndex;
// Make sure gEditLinesIndex is valid. It should be set to the
// last edit line index. It's possible that bottomEditIndex is
// beyond the last edit line index, so we need to be careful here.
if (bottomEditIndex == lastEditLineIndex)
gEditLinesIndex = bottomEditIndex;
else
gEditLinesIndex = lastEditLineIndex;
gTextLineIndex = gEditLines[gEditLinesIndex].length();
if ((gTextLineIndex > 0) && (gEditLines[gEditLinesIndex].length == gEditWidth))
--gTextLineIndex;
curpos.x = gEditLeft + gTextLineIndex;
curpos.y += (gEditLinesIndex-oldEditLinesIndex);
console.gotoxy(curpos);
// Update the current word length.
currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
}
console.print(chooseEditColor()); // Make sure the edit color is correct
break;
case CROSSPOST_KEY:
if (gCanCrossPost)
doCrossPosting();
break;
case LIST_TXT_REPLACEMENTS_KEY:
if (gConfigSettings.enableTextReplacements)
listTextReplacements();
break;
case USER_SETTINGS_KEY:
doUserSettings(curpos, true);
break;
default:
// For the tab character, insert 3 spaces. Otherwise,
// if it's a printable character, add the character.
if (textLineIsEditable(gEditLinesIndex))
{
if (/\t/.test(userInput))
{
var retObject;
for (var i = 0; i < 3; ++i)
{
retObject = doPrintableChar(" ", curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
}
}
else
{
if (isPrintableChar(userInput))
{
var retObject = doPrintableChar(userInput, curpos, currentWordLength);
curpos.x = retObject.x;
curpos.y = retObject.y;
currentWordLength = retObject.currentWordLength;
}
}
}
break;
}
// Update the time strings on the screen
updateTimeLeft = (initialTimeLeft - bbs.time_left >= 60);
// For every 5 keys pressed, check the current time and update
// it on the screen if necessary. updateTime() is also being
// called when the first key is pressed so that the function's
// time string variable gets initially set.
// Note: The 2nd parameter to updateTime() is whether or not to move the
// cursor back to the original location after updating the time on the
// screen. For optimization, we don't want to do that if we'll also be
// updating the time left on the screen.
if ((numKeysPressed == 1) || (numKeysPressed % 5 == 0))
updateTime(curpos, !updateTimeLeft);
// If the user's time left has gone down by at least 60 seconds, then
// update the time & user's time left on the screen.
if (updateTimeLeft)
{
fpDisplayTimeRemaining();
// Change back to the edit color and place the cursor back
// where it needs to be.
console.print(chooseEditColor());
console.gotoxy(curpos); // Place the cursor back where it needs to be
initialTimeLeft = bbs.time_left;
}
}
// If the user has not aborted the message and taglines is enabled in their user settings
// and the user is not editing their signature & is not editing an existing message, then
// prompt the user for a tag line to be appended to the message.
var isEditingSignature = (gMsgSubj == format("%04d.sig", user.number));
var isEditingExistingMsg = !isEditingSignature && ((gMsgSubj == "MSGTMP") || (gMsgSubj == "DDMsgReader_message.txt") || (gMsgSubj == "DDMsgLister_message.txt"));
if ((returnCode == 0) && gUserSettings.enableTaglines && !isEditingSignature && !isEditingExistingMsg && txtFileContainsLines(gConfigSettings.tagLineFilename))
{
if (promptYesNo("Add a tagline", true, "Add tagline", true))
{
var taglineRetObj = doTaglineSelection();
if (taglineRetObj.taglineWasSelected && taglineRetObj.tagline.length > 0)
{
// If the tagline filename was specified in the MSGINF drop file,
// then write the tag line to that file (Synchronet will read that
// and append its contents after the user's signature). Otherwise,
// append the tagline to the message directly.
if (gTaglineFile.length > 0)
writeTaglineToMsgTaglineFile(taglineRetObj.tagline, gTaglineFile);
else
{
// Append a blank line and then append the tagline to the message
gEditLines.push(new TextLine());
var newLine = new TextLine();
newLine.text = taglineRetObj.tagline;
gEditLines.push(newLine);
reAdjustTextLines(gEditLines, gEditLines.length-1, gEditLines.length, gEditWidth);
}
}
}
}
// If gEditLines has only 1 line in it and it's blank, then
// remove it so that we can test to see if the message is empty.
if (gEditLines.length == 1)
{
if (gEditLines[0].length() == 0)
gEditLines.splice(0, 1);
}
return returnCode;
}
// Helper function for doEditLoop(): Handles the backspace behavior.
//
// Parameters:
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing x and y values representing the cursor
// position and currentLength, the current word length.
function doBackspace(pCurpos, pCurrentWordLength)
{
// Create the return object.
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.currentWordLength = pCurrentWordLength;
var didBackspace = false;
// For later, store a backup of the current edit line index and
// cursor position.
var originalCurrentLineIndex = gEditLinesIndex;
var originalX = pCurpos.x;
var originalY = pCurpos.y;
var originallyOnLastLine = (gEditLinesIndex == gEditLines.length-1);
// If the cursor is beyond the leftmost position in
// the edit area, then we can simply remove the last
// character in the current line and move the cursor
// over to the left.
if (retObj.x > gEditLeft)
{
if (gTextLineIndex > 0)
{
console.print(BACKSPACE);
console.print(" ");
--retObj.x;
console.gotoxy(retObj.x, retObj.y);
// Remove the previous character from the text line
var textLineLength = gEditLines[gEditLinesIndex].length();
if (textLineLength > 0)
{
var textLine = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex-1)
+ gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
gEditLines[gEditLinesIndex].text = textLine;
didBackspace = true;
--gTextLineIndex;
}
}
}
else
{
// The cursor is at the leftmost position in the edit area.
// If we are beyond the first text line, then move as much of
// the current text line as possible up to the previous line,
// if there's room (if not, don't do anything).
if (gEditLinesIndex > 0)
{
var prevLineIndex = gEditLinesIndex - 1;
if (gEditLines[gEditLinesIndex].length() > 0)
{
// Store the previous line's original length
var originalPrevLineLen = gEditLines[prevLineIndex].length();
// See how much space is at the end of the previous line
var previousLineEndSpace = gEditWidth - gEditLines[prevLineIndex].length();
if (previousLineEndSpace > 0)
{
var index = previousLineEndSpace - 1;
// If that index is valid for the current line, then find the first
// space in the current line so that the text would fit at the end
// of the previous line. Otherwise, set index to the length of the
// current line so that we'll move the whole current line up to the
// previous line.
if (index < gEditLines[gEditLinesIndex].length())
{
for (; index >= 0; --index)
{
if (gEditLines[gEditLinesIndex].text.charAt(index) == " ")
break;
}
}
else
index = gEditLines[gEditLinesIndex].length();
// If we found a space, then move the part of the current line before
// the space to the end of the previous line.
if (index > 0)
{
var linePart = gEditLines[gEditLinesIndex].text.substr(0, index);
gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(index);
gEditLines[prevLineIndex].text += linePart;
gEditLines[prevLineIndex].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
// If the current line is now blank, then remove it from gEditLines.
if (gEditLines[gEditLinesIndex].length() == 0)
gEditLines.splice(gEditLinesIndex, 1);
// Update the global edit variables so that the cursor is placed
// on the previous line.
--gEditLinesIndex;
// Search for linePart in the line - If found, the cursor should
// be placed where it starts. If it' snot found, place the cursor
// at the end of the line.
var linePartIndex = gEditLines[gEditLinesIndex].text.indexOf(linePart);
if (linePartIndex > -1)
gTextLineIndex = linePartIndex;
else
gTextLineIndex = gEditLines[gEditLinesIndex].length();
retObj.x = gEditLeft + gTextLineIndex;
if (retObj.y > gEditTop)
--retObj.y;
didBackspace = true;
}
}
}
else
{
// The current line's length is 0.
// If there's enough room on the previous line, remove the
// current line and place the cursor at the end of the
// previous line.
if (gEditLines[prevLineIndex].length() <= gEditWidth-1)
{
// Copy the current line's "hard newline end" setting to the
// previous line (so that if there's a blank line below the
// current line, the blank line will be preserved), then remove
// the current edit line.
gEditLines[gEditLinesIndex-1].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
gEditLines.splice(gEditLinesIndex, 1);
--gEditLinesIndex;
gTextLineIndex = gEditLines[prevLineIndex].length();
retObj.x = gEditLeft + gEditLines[prevLineIndex].length();
if (retObj.y > gEditTop)
--retObj.y;
didBackspace = true;
}
}
}
}
// If the backspace was performed, then re-adjust the text lines
// and refresh the screen.
if (didBackspace)
{
// Store the previous line of text now so we can compare it later
var prevTextline = "";
if (gEditLinesIndex > 0)
prevTextline = gEditLines[gEditLinesIndex-1].text;
// Re-adjust the text lines
reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
// If the previous line's length increased, that probably means that the
// user backspaced to the beginning of the current line and the word was
// moved to the end of the previous line. If so, then move the cursor to
// the end of the previous line.
//var scrolled = false;
if ((gEditLinesIndex > 0) &&
(gEditLines[gEditLinesIndex-1].length() > prevTextline.length))
{
// Update the text index variables and cusor position variables.
--gEditLinesIndex;
gTextLineIndex = gEditLines[gEditLinesIndex].length();
retObj.x = gEditLeft + gTextLineIndex;
if (retObj.y > gEditTop)
--retObj.y;
}
// If the cursor was at the leftmost position in the edit area,
// update the edit lines from the currently-set screen line #.
if (originalX == gEditLeft)
{
// Since the original X position was at the left edge of the edit area,
// display the edit lines starting with the previous line if possible.
if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
else
displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
}
// If the original horizontal cursor position was in the middle of
// the line, and the line is the last line on the screen, then
// only refresh that one line on the screen.
else if ((originalX > gEditLeft) && (originalX < gEditLeft + gEditWidth - 1) && originallyOnLastLine)
displayEditLines(originalY, originalCurrentLineIndex, originalY, false);
// If scrolling was to be done, then refresh the entire
// current message text on the screen from the top of the
// edit area. Otherwise, only refresh starting from the
// original horizontal position and message line.
else
{
// Display the edit lines starting with the previous line if possible.
if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
else
displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
}
// Make sure the current word length is correct.
retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
// Make sure the cursor is placed where it should be.
console.gotoxy(retObj.x, retObj.y);
return retObj;
}
// Helper function for doEditLoop(): Handles the delete key behavior.
//
// Parameters:
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing x and y values representing the cursor
// position and currentLength, the current word length.
function doDeleteKey(pCurpos, pCurrentWordLength)
{
// Create the return object
var returnObject = new Object();
returnObject.x = pCurpos.x;
returnObject.y = pCurpos.y;
returnObject.currentWordLength = pCurrentWordLength;
// If gEditLinesIndex is invalid, then return without doing anything.
if ((gEditLinesIndex < 0) || (gEditLinesIndex >= gEditLines.length))
return returnObject;
// Store the original line text (for testing to see if we should update the screen).
var originalLineText = gEditLines[gEditLinesIndex].text;
// If the text line index is within bounds, then we can
// delete the next character and refresh the screen.
if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
{
var lineText = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex)
+ gEditLines[gEditLinesIndex].text.substr(gTextLineIndex+1);
gEditLines[gEditLinesIndex].text = lineText;
// If the current character is a space, then reset the current word length.
// to 0. Otherwise, set it to the current word length.
if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
{
if (gEditLines[gEditLinesIndex].text.charAt(gTextLineIndex) == " ")
returnObject.currentWordLength = 0;
else
{
var spacePos = gEditLines[gEditLinesIndex].text.indexOf(" ", gTextLineIndex);
if (spacePos > -1)
returnObject.currentWordLength = spacePos - gTextLineIndex;
else
returnObject.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
}
// Re-adjust the line lengths and refresh the edit area.
var textChanged = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
// If the line text changed, then update the message area from the
// current line on down.
textChanged = textChanged || (gEditLines[gEditLinesIndex].text != originalLineText);
if (textChanged)
{
// Calculate the bottommost edit area row to update, and then
// refresh the edit area.
var bottommostRow = calcBottomUpdateRow(returnObject.y, gEditLinesIndex);
displayEditLines(returnObject.y, gEditLinesIndex, bottommostRow, true, true);
}
}
else
{
// The textChanged variable will be used by this code to store whether or
// not any text changed so we'll know if the screen needs to be refreshed.
var textChanged = false;
// The text line index is at the end of the line.
// Set the current line's hardNewlineEnd property to false
// so that we can bring up text from the next line,
// if possible.
gEditLines[gEditLinesIndex].hardNewlineEnd = false;
// If the current line is blank and is not the last line, then remove it.
if (gEditLines[gEditLinesIndex].length() == 0)
{
if (gEditLinesIndex < gEditLines.length-1)
{
gEditLines.splice(gEditLinesIndex, 1);
textChanged = true;
}
}
// If the next line is blank, then set its
// hardNewlineEnd to false too, so that lower
// text lines can be brought up.
else if (gEditLinesIndex < gEditLines.length-1)
{
var nextLineIndex = gEditLinesIndex + 1;
if (gEditLines[nextLineIndex].length() == 0)
gEditLines[nextLineIndex].hardNewlineEnd = false;
}
// Re-adjust the text lines, update textChanged & set a few other things
textChanged = textChanged || reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
returnObject.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
var startRow = returnObject.y;
var startEditLinesIndex = gEditLinesIndex;
if (returnObject.y > gEditTop)
{
--startRow;
--startEditLinesIndex;
}
// If text changed, then refresh the edit area.
textChanged = textChanged || (gEditLines[gEditLinesIndex].text != originalLineText);
if (textChanged)
{
// Calculate the bottommost edit area row to update, and then
// refresh the edit area.
var bottommostRow = calcBottomUpdateRow(startRow, startEditLinesIndex);
displayEditLines(startRow, startEditLinesIndex, bottommostRow, true, true);
}
}
// Move the cursor where it should be.
console.gotoxy(returnObject.x, returnObject.y);
return returnObject;
}
// Helper function for doEditLoop(): Handles printable characters.
//
// Parameters:
// pUserInput: The user's input
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing the following properties:
// x: The horizontal component of the cursor position
// y: The vertical component of the cursor position
// currentLength: The length of the current word
function doPrintableChar(pUserInput, pCurpos, pCurrentWordLength)
{
// Create the return object.
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.currentWordLength = pCurrentWordLength;
// Note: gTextLineIndex is where the new character will appear in the line.
// If gTextLineIndex is somehow past the end of the current line, then
// fill it with spaces up to gTextLineIndex.
if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
{
var numSpaces = gTextLineIndex - gEditLines[gEditLinesIndex].length();
if (numSpaces > 0)
gEditLines[gEditLinesIndex].text += format("%" + numSpaces + "s", "");
gEditLines[gEditLinesIndex].text += pUserInput;
}
// If gTextLineIndex is at the end of the line, then just append the char.
else if (gTextLineIndex == gEditLines[gEditLinesIndex].length())
gEditLines[gEditLinesIndex].text += pUserInput;
else
{
// gTextLineIndex is at the beginning or in the middle of the line.
if (inInsertMode())
{
gEditLines[gEditLinesIndex].text = spliceIntoStr(gEditLines[gEditLinesIndex].text,
gTextLineIndex, pUserInput);
}
else
{
gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex)
+ pUserInput + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex+1);
}
}
// Handle text replacement (AKA macros). Added 2013-08-31.
var madeTxtReplacement = false; // For screen refresh purposes
if (gConfigSettings.enableTextReplacements && (pUserInput == " "))
{
var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex,
gConfigSettings.textReplacementsUseRegex);
madeTxtReplacement = txtReplaceObj.madeTxtReplacement;
if (madeTxtReplacement)
{
retObj.x += txtReplaceObj.wordLenDiff;
gTextLineIndex += txtReplaceObj.wordLenDiff;
}
}
// Store a copy of the current line so that we can compare it later to see
// if it was modified by reAdjustTextLines().
var originalAfterCharApplied = gEditLines[gEditLinesIndex].text;
// If the line is now too long to fit in the edit area, then we will have
// to re-adjust the text lines.
var reAdjusted = false;
if (gEditLines[gEditLinesIndex].length() >= gEditWidth)
reAdjusted = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
// placeCursorAtEnd specifies whether or not to place the cursor at its
// spot using console.gotoxy() at the end. This is an optimization.
var placeCursorAtEnd = true;
// If the current text line is now different (modified by reAdjustTextLines())
// or text replacements were made, then we'll need to refresh multiple lines
// on the screen.
if ((reAdjusted && (gEditLines[gEditLinesIndex].text != originalAfterCharApplied)) || madeTxtReplacement)
{
// If gTextLineIndex is >= gEditLines[gEditLinesIndex].length(), then
// we know the current word was wrapped to the next line. Figure out what
// retObj.x, retObj.currentWordLength, gEditLinesIndex, and gTextLineIndex
// should be, and increment retObj.y. Also figure out what lines on the
// screen to update, and deal with scrolling if necessary.
if (gTextLineIndex >= gEditLines[gEditLinesIndex].length())
{
// I changed this on 2010-02-14 to (hopefully) place the cursor where
// it should be
// Old line (prior to 2010-02-14):
//var numChars = gTextLineIndex - gEditLines[gEditLinesIndex].length();
// New (2010-02-14):
var numChars = 0;
// Special case: If the current line's length is exactly the longest
// edit with, then the # of chars should be 0 or 1, depending on whether the
// entered character was a space or not. Otherwise, calculate numChars
// normally.
if (gEditLines[gEditLinesIndex].length() == gEditWidth-1)
numChars = ((pUserInput == " ") ? 0 : 1);
else
numChars = gTextLineIndex - gEditLines[gEditLinesIndex].length();
retObj.x = gEditLeft + numChars;
var originalEditLinesIndex = gEditLinesIndex++;
gTextLineIndex = numChars;
// The following line is now done at the end:
//retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
// Figure out which lines we need to update on the screen and whether
// to do scrolling and what retObj.y should be.
if (retObj.y < gEditBottom)
{
// We're above the last line on the screen, so we can go one
// line down.
var originalY = retObj.y++;
// Update the lines on the screen.
var bottommostRow = calcBottomUpdateRow(originalY, originalEditLinesIndex);
displayEditLines(originalY, originalEditLinesIndex, bottommostRow, true, true);
}
else
{
// We're on the last line in the edit area, so we need to scroll
// the text lines up on the screen.
var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
}
}
else
{
// gTextLineIndex is < the line's length. Update the lines on the
// screen from the current line down. Increment retObj.x,
// retObj.currentWordLength, and gTextLineIndex.
var bottommostRow = calcBottomUpdateRow(retObj.y, gEditLinesIndex);
displayEditLines(retObj.y, gEditLinesIndex, bottommostRow, true, true);
if (pUserInput == " ")
retObj.currentWordLength = 0;
else
++retObj.currentWordLength;
++retObj.x;
++gTextLineIndex;
}
}
else
{
// The text line wasn't changed by reAdjustTextLines.
// If gTextLineIndex is not the last index of the line, then refresh the
// entire line on the screen. Otherwise, just output the character that
// the user typed.
if (gTextLineIndex < gEditLines[gEditLinesIndex].length()-1)
displayEditLines(retObj.y, gEditLinesIndex, retObj.y, false, true);
else
{
console.print(pUserInput);
placeCursorAtEnd = false; // Since we just output the character
}
// Keep housekeeping variables up to date.
++retObj.x;
++gTextLineIndex;
/* retObj.currentWordLength is now calculated at the end, but we could do this:
if (pUserInput == " ")
retObj.currentWordLength = 0;
else
++retObj.currentWordLength;
*/
}
// Make sure the current word length is correct.
retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
// Make sure the cursor is placed where it should be.
if (placeCursorAtEnd)
console.gotoxy(retObj.x, retObj.y);
return retObj;
}
// Helper function for doEditLoop(): Performs the action for when the user
// presses the enter key.
//
// Parameters:
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing the following values:
// x: The horizontal component of the cursor position
// y: The vertical component of the cursor position
// currentWordLength: The current word length
// returnCode: The return code for the program (in case the
// user saves or aborts)
// continueOn: Whether or not the edit loop should continue
// doQuoteSelection: Whether or not the user typed the command
// to do quote selection.
// showHelp: Whether or not the user wants to show the help screen
function doEnterKey(pCurpos, pCurrentWordLength)
{
// Create the return object
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.currentWordLength = pCurrentWordLength;
retObj.returnCode = 0;
retObj.continueOn = true;
retObj.doQuoteSelection = false;
retObj.doCrossPostSelection = false;
retObj.showHelp = false;
// Store the current screen row position and gEditLines index.
var initialScreenLine = pCurpos.y;
var initialEditLinesIndex = gEditLinesIndex;
// Check for slash commands (/S, /A, /?). If the user has
// typed one of them by itself at the beginning of the line,
// then save, abort, or show help, respectively.
if (gEditLines[gEditLinesIndex].length() == 2)
{
var lineUpper = gEditLines[gEditLinesIndex].text.toUpperCase();
// /S: Save
if (lineUpper == "/S")
{
// If the current text line is the last one, remove it; otherwise,
// blank it out.
if (gEditLinesIndex == gEditLines.length-1)
gEditLines.splice(gEditLinesIndex, 1);
else
gEditLines[gEditLinesIndex].text = "";
retObj.continueOn = false;
return(retObj);
}
// /A: Abort
else if (lineUpper == "/A")
{
// Confirm with the user
if (promptYesNo("Abort message", false, "Abort", false))
{
retObj.returnCode = 1; // 1: Abort
retObj.continueOn = false;
return(retObj);
}
else
{
// Make sure the edit color attribute is set back.
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
// Blank out the data in the text line, set the data in
// retObj, and return it.
gEditLines[gEditLinesIndex].text = "";
retObj.currentWordLength = 0;
gTextLineIndex = 0;
retObj.x = gEditLeft;
retObj.y = pCurpos.y;
// Blank out the /A on the screen
//console.print("1n" + gTextAttrs);
console.print(chooseEditColor());
console.gotoxy(retObj.x, retObj.y);
console.print(" ");
// Put the cursor where it should be and return.
console.gotoxy(retObj.x, retObj.y);
return(retObj);
}
}
// /Q: Do quote selection, and /?: Show help
else if ((lineUpper == "/Q") || (lineUpper == "/?"))
{
retObj.doQuoteSelection = (lineUpper == "/Q");
retObj.showHelp = (lineUpper == "/?");
retObj.currentWordLength = 0;
gTextLineIndex = 0;
gEditLines[gEditLinesIndex].text = "";
// Blank out the /? on the screen
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
retObj.x = gEditLeft;
console.gotoxy(retObj.x, retObj.y);
console.print(" ");
// Put the cursor where it should be and return.
console.gotoxy(retObj.x, retObj.y);
return(retObj);
}
else if (lineUpper == "/C")
{
retObj.doCrossPostSelection = true;
// Blank out the data in the text line, set the data in
// retObj, and return it.
gEditLines[gEditLinesIndex].text = "";
retObj.currentWordLength = 0;
gTextLineIndex = 0;
retObj.x = gEditLeft;
retObj.y = pCurpos.y;
// Blank out the /C on the screen
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
retObj.x = gEditLeft;
console.gotoxy(retObj.x, retObj.y);
console.print(" ");
// Put the cursor where it should be and return.
console.gotoxy(retObj.x, retObj.y);
return(retObj);
}
else if (lineUpper == "/T")
{
if (gConfigSettings.enableTextReplacements)
listTextReplacements();
// Blank out the data in the text line, set the data in
// retObj, and return it.
gEditLines[gEditLinesIndex].text = "";
retObj.currentWordLength = 0;
gTextLineIndex = 0;
retObj.x = gEditLeft;
retObj.y = pCurpos.y;
// Blank out the /T on the screen
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
retObj.x = gEditLeft;
console.gotoxy(retObj.x, retObj.y);
console.print(" ");
// Put the cursor where it should be and return.
console.gotoxy(retObj.x, retObj.y);
return(retObj);
}
else if (lineUpper == "/U")
{
var currentCursorPos = new Object();
currentCursorPos.x = retObj.x;
currentCursorPos.y = retObj.y;
doUserSettings(currentCursorPos, false);
// Blank out the data in the text line, set the data in
// retObj, and return it.
gEditLines[gEditLinesIndex].text = "";
retObj.currentWordLength = 0;
gTextLineIndex = 0;
retObj.x = gEditLeft;
retObj.y = pCurpos.y;
// Blank out the /T on the screen
//console.print("\1n" + gTextAttrs);
console.print(chooseEditColor());
retObj.x = gEditLeft;
console.gotoxy(retObj.x, retObj.y);
console.print(" ");
// Put the cursor where it should be and return.
console.gotoxy(retObj.x, retObj.y);
return(retObj);
}
}
// Handle text replacement (AKA macros). Added 2013-08-31.
var reAdjustedTxtLines = false; // For screen refresh purposes
// cursorHorizDiff will be set if the replaced word is too long to fit on
// the end of the line - In that case, the cursor and line index will have
// to be adjusted since the new word will be moved to the next line.
var cursorHorizDiff = 0;
if (gConfigSettings.enableTextReplacements)
{
var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex-1,
gConfigSettings.textReplacementsUseRegex);
if (txtReplaceObj.madeTxtReplacement)
{
gTextLineIndex += txtReplaceObj.wordLenDiff;
retObj.x += txtReplaceObj.wordLenDiff;
retObj.currentWordLength += txtReplaceObj.wordLenDiff;
// If the replaced text on the line is too long to print on the screen,then
// then we'll need to wrap the line.
// If the logical screen column of the last character of the last word
// is beyond the rightmost colum of the edit area, then wrap the line.
if (gEditLeft + txtReplaceObj.newTextEndIdx - 1 >= gEditRight - 1)
{
//reAdjustedTxtLines = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
// If the replaced text contains at least one space, then look for
// the last space that can appear within the edit area on the screen.
if (gEditLines[gEditLinesIndex].text.indexOf(" ", txtReplaceObj.wordStartIdx) > -1)
{
var spaceIdx = gEditLines[gEditLinesIndex].text.lastIndexOf(" ", txtReplaceObj.textLineIndex);
while ((spaceIdx > -1) && (spaceIdx > gEditWidth-2)) // To split lines at the 79th column
spaceIdx = gEditLines[gEditLinesIndex].text.lastIndexOf(" ", spaceIdx-1);
// If a space was found after the start of the new text, then
// set gTextLineIndex to the first character of the word we want
// to split the line at, and the horizontal cursor offset based on
// the difference.
if (spaceIdx > txtReplaceObj.wordStartIdx)
{
gTextLineIndex = spaceIdx + 1;
cursorHorizDiff = txtReplaceObj.newTextEndIdx - spaceIdx;
}
else
{
gTextLineIndex = txtReplaceObj.wordStartIdx;
cursorHorizDiff = txtReplaceObj.newTextLen;
}
}
else
{
// The new text doesn't contain a space, so set gTextLineIndex
// to the start of the new word so that
// enterKey_InsertOrAppendNewLine() will split the line there.
gTextLineIndex = txtReplaceObj.wordStartIdx;
cursorHorizDiff = txtReplaceObj.newTextLen;
}
}
}
}
// If we're currently on the last line, then we'll need to append
// a new line. Otherwise, we'll need to splice a new line into
// gEditLines where appropriate.
var appendLineToEnd = (gEditLinesIndex == gEditLines.length-1);
var retObject = enterKey_InsertOrAppendNewLine(pCurpos, pCurrentWordLength, appendLineToEnd);
retObj.x = retObject.x;
retObj.y = retObject.y;
retObj.currentWordLength = retObject.currentWordLength;
// If a line was added to gEditLines, then set the hardNewlineEnd property
// to true for both lines.
if (retObject.addedATextLine)
{
gEditLines[initialEditLinesIndex].hardNewlineEnd = true;
gEditLines[gEditLinesIndex].hardNewlineEnd = true;
}
// Refresh the message text on the screen if that wasn't done by
// enterKey_InsertOrAppendNewLine().
if (!retObject.displayedEditlines)
displayEditLines(initialScreenLine, initialEditLinesIndex, gEditBottom, true, true);
// Note: cursorHorizDiff is set if a word was replaced and the line was
// wrapped because the new word wastoo long to fit on the end of the line.
retObj.x += cursorHorizDiff;
gTextLineIndex += cursorHorizDiff;
console.gotoxy(retObj.x, retObj.y);
return retObj;
}
// Helper function for doEnterKey(): Appends/inserts a line to gEditLines
// and returns the position of where the cursor should be.
//
// Parameters:
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
// pAppendLine: Whether or not to append the new line (true/false). If false,
// then the new line will be spliced into the middle of the array
// where it belongs rather than appended to the end.
//
// Return value: An object containing the following values:
// - x and y values representing the cursor position
// - currentLength: The current word length
// - displayedEditlines: Whether or not the edit lines were refreshed
// - addedATextLine: Whether or not a line of text was added to gEditLines
// - addedTextLineBelow: If addedATextLine is true, whether or not
// the line was added below the line
function enterKey_InsertOrAppendNewLine(pCurpos, pCurrentWordLength, pAppendLine)
{
var returnObject = new Object();
returnObject.displayedEditlines = false;
returnObject.addedATextLine = false;
returnObject.addedTextLineBelow = false;
// If we're at the end of the line, then we can simply
// add a new blank line & set the cursor there.
// Otherwise, we need to split the current line, and
// the text to the right of the cursor will go on the new line.
if (gTextLineIndex == gEditLines[gEditLinesIndex].length())
{
if (pAppendLine)
{
// Add a new blank line to the end of the message, and set
// the cursor there.
gEditLines.push(new TextLine());
++gEditLinesIndex;
returnObject.addedATextLine = true;
returnObject.addedTextLineBelow = true;
}
else
{
// Increment gEditLinesIndex and add a new line there.
++gEditLinesIndex;
gEditLines.splice(gEditLinesIndex, 0, new TextLine());
returnObject.addedATextLine = true;
}
gTextLineIndex = 0;
pCurrentWordLength = 0;
pCurpos.x = gEditLeft;
// Update the vertical cursor position.
// If the cursor is at the bottom row, then we need
// to scroll the message down by 1 line. Otherwise,
// we can simply increment pCurpos.y.
if (pCurpos.y == gEditBottom)
{
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
gEditBottom, true, true);
returnObject.displayedEditlines = true;
}
else
++pCurpos.y;
}
else
{
// We're in the middle of the line.
// Get the text to the end of the current line.
var lineEndText = gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
// Remove that text from the current line.
gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex);
if (pAppendLine)
{
// Create a new line containing lineEndText and append it to
// gEditLines. Then place the cursor at the start of that line.
var newTextLine = new TextLine();
newTextLine.text = lineEndText;
newTextLine.hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
newTextLine.isQuoteLine = gEditLines[gEditLinesIndex].isQuoteLine;
gEditLines.push(newTextLine);
++gEditLinesIndex;
returnObject.addedATextLine = true;
returnObject.addedTextLineBelow = true;
}
else
{
// Create a new line containing lineEndText and splice it into
// gEditLines on the next line. Then place the cursor at the
// start of that line.
var oldIndex = gEditLinesIndex++;
var newTextLine = new TextLine();
newTextLine.text = lineEndText;
newTextLine.hardNewlineEnd = gEditLines[oldIndex].hardNewlineEnd;
newTextLine.isQuoteLine = gEditLines[oldIndex].isQuoteLine;
// If the user pressed enter at the beginning of a line, then a new
// blank line will be inserted above, so we want to make sure its
// isQuoteLine property is set to false.
if (gTextLineIndex == 0)
gEditLines[oldIndex].isQuoteLine = false;
// Splice the new text line into gEditLines at gEditLinesIndex.
gEditLines.splice(gEditLinesIndex, 0, newTextLine);
returnObject.addedATextLine = true;
}
gTextLineIndex = 0;
pCurpos.x = gEditLeft;
// Update the vertical cursor position.
// If the cursor is at the bottom row, then we need
// to scroll the message down by 1 line. Otherwise,
// we can simply increment pCurpos.y.
if (pCurpos.y == gEditBottom)
{
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
gEditBottom, true, true);
returnObject.displayedEditlines = true;
}
else
++pCurpos.y;
// Figure out the current word length.
// Look for a space in lineEndText. If a space is found,
// the word length is the length of the word up until the
// space. If a space is not found, then the word length
// is the entire length of lineEndText.
var spacePos = lineEndText.indexOf(" ");
if (spacePos > -1)
pCurrentWordLength = spacePos;
else
pCurrentWordLength = lineEndText.length;
}
// Set some stuff in the return object, and return it.
returnObject.x = pCurpos.x;
returnObject.y = pCurpos.y;
returnObject.currentWordLength = pCurrentWordLength;
return returnObject;
}
// Helper function for doEditLoop(): Returns whether a text line is editable
// (for instance, quote lines might not be editable).
//
// Parameters:
// pLineIdx: The index of the text line
//
// Return value: Boolean - Whether or not the text line is editable
function textLineIsEditable(pLineIdx)
{
if (typeof(pLineIdx) != "number")
return true;
if ((pLineIdx < 0) || (pLineIdx >= gEditLines.length))
return true;
// The main concern is whether quote lines are editable.
var lineIsEditable = true;
if (!gConfigSettings.allowEditQuoteLines)
lineIsEditable = !(gEditLines[pLineIdx].isQuoteLine);
return lineIsEditable;
}
// This function handles quote selection and is called by doEditLoop().
//
// Parameters:
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing the following properties:
// x and y: The horizontal and vertical cursor position
// timedOut: Whether or not the user input timed out (boolean)
// currentWordLength: The length of the current word
function doQuoteSelection(pCurpos, pCurrentWordLength)
{
// Create the return object
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.timedOut = false;
retObj.currentWordLength = pCurrentWordLength;
// Note: Quote lines are in the gQuoteLines array, where each element is
// a string.
// If gQuoteLines is empty, then we have nothing to do, so just return.
if ((gQuoteLines.length == 0) || !gUseQuotes)
return retObj;
// The first time this function runs, save the user's settings for using initials
// in quote lines and whether or not to indent quote lines containing initials.
// These will be checked against the user's current settings to see if we need
// to wrap the quote lines again in case the user changed these settings.
if (typeof(doQuoteSelection.useQuoteLineInitials) == "undefined")
doQuoteSelection.useQuoteLineInitials = gUserSettings.useQuoteLineInitials;
if (typeof(doQuoteSelection.indentQuoteLinesWithInitials) == "undefined")
doQuoteSelection.indentQuoteLinesWithInitials = gUserSettings.indentQuoteLinesWithInitials;
if (typeof(doQuoteSelection.trimSpacesFromQuoteLines) == "undefined")
doQuoteSelection.trimSpacesFromQuoteLines = gUserSettings.trimSpacesFromQuoteLines;
// If the setting to re-wrap quote lines is enabled, then do it.
// We're re-wrapping the quote lines here in case the user changes their
// setting for prefixing quote lines with author initials.
// wrapQuoteLines() will also prefix the quote lines with author's
// initials if configured to do so.
// If not configured to re-wrap quote lines, then if configured to
// prefix quote lines with author's initials, then we need to
// prefix them here with gQuotePrefix.
if (gQuoteLines.length > 0)
{
// The first time this function runs, create a backup array of the original
// quote lines
if (typeof(doQuoteSelection.backupQuoteLines) == "undefined")
{
doQuoteSelection.backupQuoteLines = new Array();
for (var i = 0; i < gQuoteLines.length; ++i)
doQuoteSelection.backupQuoteLines.push(gQuoteLines[i]);
}
// If this is the first time the user has opened the quote window or if the
// user has changed their settings for using author's initials in quote lines,
// then re-copy the original quote lines into gQuoteLines and re-wrap them.
var quoteLineInitialsSettingChanged = (doQuoteSelection.useQuoteLineInitials != gUserSettings.useQuoteLineInitials);
var indentQuoteLinesWithInitialsSettingChanged = (doQuoteSelection.indentQuoteLinesWithInitials != gUserSettings.indentQuoteLinesWithInitials);
var trimSpacesFromQuoteLinesSettingChanged = (doQuoteSelection.trimSpacesFromQuoteLines != gUserSettings.trimSpacesFromQuoteLines);
if (!gUserHasOpenedQuoteWindow || quoteLineInitialsSettingChanged || indentQuoteLinesWithInitialsSettingChanged || trimSpacesFromQuoteLinesSettingChanged)
{
doQuoteSelection.useQuoteLineInitials = gUserSettings.useQuoteLineInitials;
doQuoteSelection.indentQuoteLinesWithInitials = gUserSettings.indentQuoteLinesWithInitials;
doQuoteSelection.trimSpacesFromQuoteLines = gUserSettings.trimSpacesFromQuoteLines;
// If the user changed the setting for trimming spaces from quote lines,
// then re-populate gQuoteLines with the original quote lines.
if (trimSpacesFromQuoteLinesSettingChanged)
{
//readQuoteOrMessageFile();
gQuoteLines = [];
for (var i = 0; i < doQuoteSelection.backupQuoteLines.length; ++i)
gQuoteLines.push(doQuoteSelection.backupQuoteLines[i]);
}
// If the user has opened the quote window before, then empty gQuoteLines
// and re-copy the original quote lines back into it.
if (gUserHasOpenedQuoteWindow)
{
gQuoteLines.length = 0;
for (var i = 0; i < doQuoteSelection.backupQuoteLines.length; ++i)
gQuoteLines.push(doQuoteSelection.backupQuoteLines[i]);
}
// Reset the selected quote line index & top displayed quote line index
// back to 0 to ensure valid screen display & scrolling behavior
gQuoteLinesIndex = 0;
gQuoteLinesTopIndex = 0;
// Update the quote line prefix text and wrap the quote lines
setQuotePrefix();
if (gConfigSettings.reWrapQuoteLines)
{
// TODO: This seemed to never be finishing for certain messages - Entering
// an infinite loop? I believe this was fixed as of version 1.49.
wrapQuoteLines(gUserSettings.useQuoteLineInitials, gUserSettings.indentQuoteLinesWithInitials,
gUserSettings.trimSpacesFromQuoteLines);
}
else if (gUserSettings.useQuoteLineInitials)
{
var maxQuoteLineWidth = gEditWidth - gQuotePrefix.length;
for (var i = 0; i < gQuoteLines.length; ++i)
gQuoteLines[i] = quote_msg(gQuoteLines[i], maxQuoteLineWidth, gQuotePrefix);
}
}
}
// Set up some variables
var curpos = new Object();
curpos.x = pCurpos.x;
curpos.y = pCurpos.y;
// Make the quote window's height about 42% of the edit area.
const quoteWinHeight = Math.floor(gEditHeight * 0.42) + 1;
// The first and last lines on the screen where quote lines are written
const quoteTopScreenRow = console.screen_rows - quoteWinHeight + 2;
const quoteBottomScreenRow = console.screen_rows - 2;
// Quote window parameters
const quoteWinTopScreenRow = quoteTopScreenRow-1;
const quoteWinWidth = gEditRight - gEditLeft + 1;
// For pageUp/pageDown functionality - Calculate the top quote line index
// for the last page.
var quoteWinInnerHeight = quoteBottomScreenRow - quoteTopScreenRow + 1; // # of quote lines in the quote window
var numPages = Math.ceil(gQuoteLines.length / quoteWinInnerHeight);
var topIndexForLastPage = (quoteWinInnerHeight * numPages) - quoteWinInnerHeight;
// Display the top border of the quote window.
fpDrawQuoteWindowTopBorder(quoteWinHeight, gEditLeft, gEditRight);
// Display the remainder of the quote window, with the quote lines in it.
displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true, gQuoteLinesIndex);
// Position the cursor at the currently-selected quote line.
var screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
console.gotoxy(gEditLeft, screenLine);
// User input loop
var quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
retObj.timedOut = false;
var userInput = null;
var continueOn = true;
while (continueOn)
{
// Get a keypress from the user
userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN|K_NOECHO, gConfigSettings);
if (userInput == "")
{
// The input timeout was reached. Abort.
retObj.timedOut = true;
continueOn = false;
break;
}
// If we got here, that means the user input didn't time out.
switch (userInput)
{
case KEY_UP:
// Go up 1 quote line
if (gQuoteLinesIndex > 0)
{
// If the cursor is at the topmost position, then
// we need to scroll up 1 line in gQuoteLines.
if (screenLine == quoteTopScreenRow)
{
--gQuoteLinesIndex;
--gQuoteLinesTopIndex;
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
// Redraw the quote lines in the quote window.
displayQuoteWindowLines(gQuoteLinesIndex, quoteWinHeight, quoteWinWidth,
true, gQuoteLinesIndex);
// Put the cursor back where it should be.
console.gotoxy(gEditLeft, screenLine);
}
// If the cursor is below the topmost position, then
// we can just go up 1 line.
else if (screenLine > quoteTopScreenRow)
{
// Write the current quote line using the normal color
// Note: This gets the quote line again using getQuoteTextLine()
// so that the color codes in the line will be correct.
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine);
// Go up one line and display that quote line in the
// highlighted color.
--screenLine;
--gQuoteLinesIndex;
quoteLine = strip_ctrl(getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth));
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine);
// Make sure the cursor is where it should be.
console.gotoxy(gEditLeft, screenLine);
}
}
break;
case KEY_DOWN:
// Go down 1 line in the quote window.
var downRetObj = moveDownOneQuoteLine(gQuoteLinesIndex, screenLine,
quoteWinHeight, quoteWinWidth,
quoteBottomScreenRow);
gQuoteLinesIndex = downRetObj.quoteLinesIndex;
screenLine = downRetObj.screenLine;
quoteLine = downRetObj.quoteLine;
break;
case KEY_HOME: // Select the first quote line on the current page
if (gQuoteLinesIndex != gQuoteLinesTopIndex)
{
gQuoteLinesIndex = gQuoteLinesTopIndex;
// Write the current quote line with unhighlighted colors
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine);
// Calculate the new screen line and draw the new quote line with
// highlighted colors
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine);
console.gotoxy(gEditLeft, screenLine);
}
break;
case KEY_END: // Select the last quote line on the current page
var lastIndexForCurrentPage = gQuoteLinesTopIndex + quoteWinInnerHeight - 1;
if (gQuoteLinesIndex != lastIndexForCurrentPage)
{
gQuoteLinesIndex = lastIndexForCurrentPage;
// Write the current quote line with unhighlighted colors
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine);
// Calculate the new screen line and draw the new quote line with
// highlighted colors
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteLineHighlightColor, quoteLine);
console.gotoxy(gEditLeft, screenLine);
}
break;
case KEY_PAGE_UP: // Go up 1 page in the quote lines
// If the current top quote line index is greater than 0, then go to
// the previous page of quote lines and select the top index on that
// page as the current selected quote line.
if (gQuoteLinesTopIndex > 0)
{
gQuoteLinesTopIndex -= quoteWinInnerHeight;
if (gQuoteLinesTopIndex < 0)
gQuoteLinesTopIndex = 0;
gQuoteLinesIndex = gQuoteLinesTopIndex;
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true,
gQuoteLinesIndex);
}
break;
case KEY_PAGE_DOWN: // Go down 1 page in the quote lines
// If the current top quote line index is below the top index for the
// last page, then go to the next page of quote lines and select the
// top index on that page as the current selected quote line.
if (gQuoteLinesTopIndex < topIndexForLastPage)
{
gQuoteLinesTopIndex += quoteWinInnerHeight;
if (gQuoteLinesTopIndex > topIndexForLastPage)
gQuoteLinesTopIndex = topIndexForLastPage;
gQuoteLinesIndex = gQuoteLinesTopIndex;
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true,
gQuoteLinesIndex);
}
break;
case "F": // Go to the first page
if (gQuoteLinesTopIndex > 0)
{
gQuoteLinesTopIndex = 0;
gQuoteLinesIndex = gQuoteLinesTopIndex;
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true,
gQuoteLinesIndex);
}
break;
case "L": // Go to the last page
if (gQuoteLinesTopIndex < topIndexForLastPage)
{
gQuoteLinesTopIndex = topIndexForLastPage;
gQuoteLinesIndex = gQuoteLinesTopIndex;
quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
screenLine = quoteTopScreenRow + (gQuoteLinesIndex - gQuoteLinesTopIndex);
displayQuoteWindowLines(gQuoteLinesTopIndex, quoteWinHeight, quoteWinWidth, true,
gQuoteLinesIndex);
}
break;
case KEY_ENTER:
// numTimesToMoveDown specifies how many times to move the cursor
// down after inserting the quote line into the message.
var numTimesToMoveDown = 1;
// Insert the quote line into gEditLines after the current gEditLines index.
var insertedBelow = insertLineIntoMsg(gEditLinesIndex, quoteLine, true, true);
if (insertedBelow)
{
// The cursor will need to be moved down 1 more line.
// So, increment numTimesToMoveDown, and set curpos.x
// and gTextLineIndex to the beginning of the line.
++numTimesToMoveDown;
curpos.x = gEditLeft;
gTextLineIndex = 0;
retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
else
retObj.currentWordLength = 0;
// Refresh the part of the message that needs to be refreshed on the
// screen (above the quote window).
if (curpos.y < quoteTopScreenRow-1)
displayEditLines(curpos.y, gEditLinesIndex, quoteTopScreenRow-2, false, true);
gEditLinesIndex += numTimesToMoveDown;
// Go down one line in the quote window.
var tempReturnObj = moveDownOneQuoteLine(gQuoteLinesIndex, screenLine,
quoteWinHeight, quoteWinWidth,
quoteBottomScreenRow);
gQuoteLinesIndex = tempReturnObj.quoteLinesIndex;
screenLine = tempReturnObj.screenLine;
quoteLine = tempReturnObj.quoteLine;
// Move the cursor down as specified by numTimesToMoveDown. If
// the cursor is at the bottom of the edit area, then refresh
// the message on the screen, scrolled down by one line.
for (var i = 0; i < numTimesToMoveDown; ++i)
{
if (curpos.y == gEditBottom)
{
// Refresh the message on the screen, scrolled down by
// one line, but only if this is the last time we're
// doing this (for efficiency).
if (i == numTimesToMoveDown-1)
{
displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
quoteTopScreenRow-2, false, true);
}
}
else
++curpos.y;
}
break;
// ESC or CTRL-Q: Stop quoting
case KEY_ESC:
case CTRL_Q:
// Quit out of the input loop (get out of quote mode).
continueOn = false;
break;
}
}
// We've exited quote mode. Refresh the message text on the screen. Note:
// This will refresh only the quote window portion of the screen if the
// cursor row is at or below the top of the quote window, and it will also
// refresh the screen if the cursor row is above the quote window.
displayEditLines(quoteWinTopScreenRow, gEditLinesIndex-(curpos.y-quoteWinTopScreenRow),
gEditBottom, true, true);
// Draw the bottom edit border to erase the bottom border of the
// quote window.
fpDisplayTextAreaBottomBorder(gEditBottom+1, gUseQuotes, gEditLeft, gEditRight,
gInsertMode, gConfigSettings.allowColorSelection);
// Make sure the color is correct for editing.
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
// Put the cursor where it should be.
console.gotoxy(curpos);
gUserHasOpenedQuoteWindow = true;
// Set the settings in the return object, and return it.
retObj.x = curpos.x;
retObj.y = curpos.y;
return retObj;
}
// Helper for doQuoteSelection(): This function moves the quote selection
// down one line and updates the quote window.
//
// Parameters:
// pQuoteLinesIndex: The index of the current line in gQuoteLines
// pScreenLine: The vertical position of the cursor on the screen
// pQuoteWinHeight: The height of the quote window
// pQuoteWinWidth: The width of the quote window
// pQuoteBottomScreenLine: The bottommost screen line where quote lines are displayed
function moveDownOneQuoteLine(pQuoteLinesIndex, pScreenLine, pQuoteWinHeight,
pQuoteWinWidth, pQuoteBottomScreenLine)
{
// Create the return object
var returnObj = new Object();
returnObj.quoteLinesIndex = pQuoteLinesIndex;
returnObj.screenLine = pScreenLine;
returnObj.quoteLine = "";
// If the current quote line is above the last one, then we can
// move down one quote line.
if (pQuoteLinesIndex < gQuoteLines.length-1)
{
// If the cursor is at the bottommost position, then
// we need to scroll up 1 line in gQuoteLines.
if (pScreenLine == pQuoteBottomScreenLine)
{
++pQuoteLinesIndex;
++gQuoteLinesTopIndex;
returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth);
// Redraw the quote lines in the quote window.
var topQuoteIndex = pQuoteLinesIndex - pQuoteWinHeight + 4;
displayQuoteWindowLines(topQuoteIndex, pQuoteWinHeight, pQuoteWinWidth, true,
pQuoteLinesIndex);
// Put the cursor back where it should be.
console.gotoxy(gEditLeft, pScreenLine);
}
// If the cursor is above the bottommost position, then
// we can just go down 1 line.
else if (pScreenLine < pQuoteBottomScreenLine)
{
// Write the current quote line using the normal color.
// Note: This gets the quote line again using getQuoteTextLine()
// so that the color codes in the line will be correct.
console.gotoxy(gEditLeft, pScreenLine);
returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth);
printf(gFormatStrWithAttr, gQuoteWinTextColor, returnObj.quoteLine);
// Go down one line and display that quote line in the
// highlighted color.
++pScreenLine;
++pQuoteLinesIndex;
returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth);
console.gotoxy(gEditLeft, pScreenLine);
printf(gFormatStrWithAttr, gQuoteLineHighlightColor, returnObj.quoteLine);
// Put the cursor back where it should be.
console.gotoxy(gEditLeft, pScreenLine);
}
}
else // This else case is for when we're already on the last quote line.
returnObj.quoteLine = getQuoteTextLine(pQuoteLinesIndex, pQuoteWinWidth);
// Make sure the properties of returnObj have the correct
// values (except quoteLine, which is already set), and
// return the returnObj.
returnObj.quoteLinesIndex = pQuoteLinesIndex;
returnObj.screenLine = pScreenLine;
return returnObj;
}
// Helper for doQuoteSelection(): This displays the quote window, except for its
// top border.
//
// Parameters:
// pQuoteLinesIndex: The index into gQuoteLines to start at. The quote line
// at this index will be displayed at the top of the quote
// window.
// pQuoteWinHeight: The height of the quote window
// pQuoteWinWidth: The width of the quote window
// pDrawBottomBorder: Whether or not to draw the bottom border of the quote
// window.
// pHighlightIndex: Optional - An index of a quote line to highlight.
function displayQuoteWindowLines(pQuoteLinesIndex, pQuoteWinHeight, pQuoteWinWidth, pDrawBottomBorder, pHighlightIndex)
{
var quoteLinesIndex = pQuoteLinesIndex;
var quoteLine = ""; // A line of text from gQuoteLines
var screenLine = console.screen_rows - pQuoteWinHeight + 2;
if (gQuoteLines.length > 0)
{
var color = ""; // The color to use when writing the text
var lineLength = 0; // Length of a quote line
while ((quoteLinesIndex < gQuoteLines.length) && (screenLine < console.screen_rows-1))
{
quoteLine = getQuoteTextLine(quoteLinesIndex, pQuoteWinWidth);
// Go to the line on screen and display the quote line text.
console.gotoxy(gEditLeft, screenLine);
// If pHighlightIndex is valid, and if quoteLinesIndex matches
// pHighlightIndex, then use the highlight color for this quote line.
if ((pHighlightIndex != null) && (pHighlightIndex >= 0) && (pHighlightIndex < gQuoteLines.length))
{
if (quoteLinesIndex == pHighlightIndex)
{
color = gQuoteLineHighlightColor;
quoteLine = quoteLine;
}
else
color = gQuoteWinTextColor;
}
else
{
color = gQuoteWinTextColor;
quoteLine = quoteLine;
}
// Write the quote line, and fill the rest of the line with spaces.
printf(gFormatStrWithAttr, color, quoteLine);
++quoteLinesIndex;
++screenLine;
}
}
// Fill the remainder of the quote window area
for (; screenLine < console.screen_rows-1; ++screenLine)
{
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, gQuoteWinTextColor, "");
}
// If pDrawBottomBorder is true, then display the bottom border of the
// quote window.
if (pDrawBottomBorder)
{
console.gotoxy(gEditLeft, screenLine);
fpDrawQuoteWindowBottomBorder(gEditLeft, gEditRight);
}
}
// This function returns a line of text from gQuoteLines, with "> "
// added to the front if it's not blank. Also, the text line will
// be limited in length by the screen width.
//
// Parameters:
// pIndex: The index of the quote line to retrieve
// pMaxWidth: The maximum width of the line
//
// Return value: The line of text from gQuoteLines
function getQuoteTextLine(pIndex, pMaxWidth)
{
var textLine = "";
if ((pIndex >= 0) && (pIndex < gQuoteLines.length))
{
if (gUserSettings.useQuoteLineInitials)
{
if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0))
textLine = gQuoteLines[pIndex].substr(0, pMaxWidth-1);
}
else
{
if ((gQuoteLines[pIndex] != null) && (gQuoteLines[pIndex].length > 0))
textLine = quote_msg(gQuoteLines[pIndex], pMaxWidth-1, gQuotePrefix);
}
}
return textLine;
}
// This function deletes the current edit line. This function is called
// by doEditLoop().
//
// Parameters:
// pCurpos: An object containing the x and y cursor position.
//
// Return value: An object containing the following properties:
// x: The horizontal component of the cursor location
// y: The vertical component of the cursor location
// currentWordLength: The length of the current word
function doDeleteLine(pCurpos)
{
// Construct the object that we'll be returning
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.currentWordLength = 0;
// Remove the current line from gEditLines. If we're on the last line,
// then we'll need to add a blank line to gEditLines. We'll also need
// to refresh the edit lines on the screen.
if (gEditLinesIndex == gEditLines.length-1)
{
// We're on the last line. Remove it & replace it with a new line.
gEditLines.splice(gEditLinesIndex, 1, new TextLine());
// Refresh (clear) the line on the screen
displayEditLines(pCurpos.y, gEditLinesIndex, pCurpos.y, true, true);
console.gotoxy(gEditLeft, pCurpos.y);
printf(gFormatStr, "");
}
else
{
// We weren't on the last line. Remove the current line and get the
// word length, and then refresh the message on the screen.
gEditLines.splice(gEditLinesIndex, 1);
displayEditLines(pCurpos.y, gEditLinesIndex, gEditBottom, true, true);
// Update the current word length
retObj.currentWordLength = getWordLength(gEditLinesIndex, 0);
// If there is a line above the current line, then set its hardNewlineEnd
// to true. This is for a scenario where the user deletes a line in the
// middle of their message - If the user goes back up to the previous line
// and starts typing in the middle and SlyEdit has to wrap it to the next
// line, SlyEdit would basically remove a line without this change.
if (gEditLinesIndex > 0)
gEditLines[gEditLinesIndex-1].hardNewlineEnd = true;
}
// Adjust global message parameters, make sure the cursor position is
// correct in retObj, and place the cursor where it's supposed to be.
gTextLineIndex = 0;
retObj.x = gEditLeft;
console.gotoxy(retObj.x, retObj.y);
return retObj;
}
// Toggles insert mode between insert and overwrite mode and updates it
// on the screen. Insert/overwrite mode is signified by gInsertMode
// (either "INS" or "OVR");
//
// Parameters:
// pCurpos: An object containing the cursor's position (X and Y coordinates).
// The cursor will be returned here when finished.
function toggleInsertMode(pCurpos)
{
// Change gInsertMode, and then refresh it on the screen.
gInsertMode = inInsertMode() ? "OVR" : "INS";
fpUpdateInsertModeOnScreen(gEditRight, gEditBottom, gInsertMode);
if ((pCurpos != null) && (typeof(pCurpos) != "undefined"))
console.gotoxy(pCurpos);
}
// Displays the contents of the gEditLines array, starting at a given
// line on the screen and index into the array.
//
// Parameters:
// pStartScreenRow: The line on the screen at which to start printing the
// message lines (1-based)
// pArrayIndex: The starting index to use for the message lines array
// (0-based)
// pEndScreenRow: Optional. This specifies the row on the screen to stop
// at. If this is not specified, this function will stop
// at the edit area's bottom row (gEditBottom).
// pClearRemainingScreenRows: Optional. This is a boolean that specifies
// whether or not to clear the remaining lines
// on the screen between the end of the message
// text and the last row on the screen.
// pIgnoreEditAreaBuffer: Optional. This is a boolean that specifies whether
// to always write the edit text regardless of gEditAreaBuffer.
// By default, gEditAreaBuffer is always checked.
function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRemainingScreenRows,
pIgnoreEditAreaBuffer)
{
// Make sure the array has lines in it, the given array index is valid, and
// that the given line # is valid. If not, then just return.
if ((gEditLines.length == 0) || (pArrayIndex < 0) || (pStartScreenRow < 1) || (pStartScreenRow > gEditBottom))
return;
// Choose which ending screen row to use for displaying text,
// pEndScreenRow or gEditBottom.
var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom);
// Display the message lines
console.print("n" + gTextAttrs);
var screenLine = pStartScreenRow;
var arrayIndex = pArrayIndex;
while ((screenLine <= endScreenRow) && (arrayIndex < gEditLines.length))
{
// Print the text from the current line in gEditLines. Note: Lines starting
// with " >" are assumed to be quote lines - Display those lines with cyan
// color and the normal lines with gTextAttrs.
var color = gTextAttrs;
// Note: gEditAreaBuffer is also used in clearMsgAreaToBottom().
if ((gEditAreaBuffer[screenLine] != gEditLines[arrayIndex].text) || pIgnoreEditAreaBuffer)
{
// Choose the quote line color or the normal color for the line, then
// display the line on the screen.
color = (isQuoteLine(gEditLines, arrayIndex) ? gQuoteLineColor : gTextAttrs);
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStrWithAttr, color, gEditLines[arrayIndex].text);
gEditAreaBuffer[screenLine] = gEditLines[arrayIndex].text;
}
++screenLine;
++arrayIndex;
}
if (arrayIndex > 0)
--arrayIndex;
// incrementLineBeforeClearRemaining stores whether or not we
// should increment screenLine before clearing the remaining
// lines in the edit area.
var incrementLineBeforeClearRemaining = true;
// If the array index is valid, and if the current line is shorter
// than the edit area width, then place the cursor after the last
// character in the line.
if ((arrayIndex >= 0) && (arrayIndex < gEditLines.length) &&
(gEditLines[arrayIndex] != undefined) && (gEditLines[arrayIndex].text != undefined))
{
var lineLength = gEditLines[arrayIndex].length();
if (lineLength < gEditWidth)
{
--screenLine;
console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLine);
}
else if ((lineLength == gEditWidth) || (lineLength == 0))
incrementLineBeforeClearRemaining = false;
}
else
incrementLineBeforeClearRemaining = false;
// Edge case: If the current screen line is below the last line, then
// clear the lines up until that point.
var clearRemainingScreenLines = (pClearRemainingScreenRows != null ? pClearRemainingScreenRows : true);
if (clearRemainingScreenLines && (screenLine <= endScreenRow))
{
console.print("n" + gTextAttrs);
var screenLineBackup = screenLine; // So we can move the cursor back
clearMsgAreaToBottom(incrementLineBeforeClearRemaining ? screenLine+1 : screenLine,
pIgnoreEditAreaBuffer);
// Move the cursor back to the end of the current text line.
if (typeof(gEditLines[arrayIndex]) != "undefined")
console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLineBackup);
else
console.gotoxy(gEditLeft, screenLineBackup);
}
// Make sure the correct color is set for the current line.
console.print(chooseEditColor());
}
// Clears the lines in the message area from a given line to the bottom.
//
// Parameters:
// pStartLine: The line number at which to start clearing.
// pIgnoreEditAreaBuffer: Optional. This is a boolean that specifies whether
// to always write the edit text regardless of gEditAreaBuffer.
// By default, gEditAreaBuffer is always checked.
function clearMsgAreaToBottom(pStartLine, pIgnoreEditAreaBuffer)
{
for (var screenLine = pStartLine; screenLine <= gEditBottom; ++screenLine)
{
// Note: gEditAreaBuffer is also used in displayEditLines().
if ((gEditAreaBuffer[screenLine].length > 0) || pIgnoreEditAreaBuffer)
{
console.gotoxy(gEditLeft, screenLine);
printf(gFormatStr, "");
gEditAreaBuffer[screenLine] = "";
}
}
}
// Returns whether or not the message is empty (gEditLines may have lines in
// it, and this tests to see if they are all empty).
function messageIsEmpty()
{
var msgEmpty = true;
for (var i = 0; i < gEditLines.length; ++i)
{
if (gEditLines[i].length() > 0)
{
msgEmpty = false;
break;
}
}
return msgEmpty;
}
// Displays a part of the message text in a rectangle on the screen. This
// is useful for refreshing part of the message area that may have been
// written over (i.e., by a text dialog).
//
// Parameters:
// pX: The upper-left X coordinate
// pY: The upper-left Y coordinate
// pWidth: The width of the rectangle
// pHeight: The height of the rectangle
// pEditLinesIndex: The starting index to use with gEditLines
// pClearExtraWidth: Boolean - Optional. If true, then space after the end of the line
// up to the specified width will be cleared. Defaults to false.
function displayMessageRectangle(pX, pY, pWidth, pHeight, pEditLinesIndex, pClearExtraWidth)
{
// If any of the parameters are out of bounds, then just return without
// doing anything.
if ((pX < gEditLeft) || (pY < gEditTop) || (pWidth < 0) || (pHeight < 0) || (pEditLinesIndex < 0))
return;
// If pWidth is too long with the given pX, then fix it.
if (pWidth > (gEditRight - pX + 1))
pWidth = gEditRight - pX + 1;
// If pHeight is too much with the given pY, then fix it.
if (pHeight > (gEditBottom - pY + 1))
pHeight = gEditBottom - pY + 1;
// Calculate the index into the edit line using pX and gEditLeft. This
// assumes that pX is within the edit area (and it should be).
const editLineIndex = pX - gEditLeft;
// Go to the given position on the screen and output the message text.
var messageStr = ""; // Will contain a portion of the message text
var screenY = pY;
var editLinesIndex = pEditLinesIndex;
var formatStr = "%-" + pWidth + "s";
var actualLenWritten = 0; // Actual length of text written for each line
for (var rectangleLine = 0; rectangleLine < pHeight; ++rectangleLine)
{
// Output the correct color for the line
console.print("n" + (isQuoteLine(gEditLines, editLinesIndex) ? gQuoteLineColor : gTextAttrs));
// Go to the position on the screen
screenY = pY + rectangleLine;
console.gotoxy(pX, screenY);
// Display the message text. If the current edit line is valid,
// then print it; otherwise, just print spaces to blank out the line.
if (typeof(gEditLines[editLinesIndex]) != "undefined")
{
actualLenWritten = printEditLine(editLinesIndex, false, editLineIndex, pWidth);
// If pClearExtraWidth is true, then if the box width is longer than
// the text that was written, then output spaces to clear the rest
// of the line to erase the rest of the box line.
if (pClearExtraWidth)
{
if (pWidth > actualLenWritten)
printf("%" + +(pWidth-actualLenWritten) + "s", "");
}
}
else
printf(formatStr, "");
++editLinesIndex;
}
}
// Displays the DCTEdit-style ESC menu and handles user input from that menu.
// This is used by the main input loop.
//
// Parameters:
// pCurpos: The current cursor position
// pEditLineDiff: The difference between the current edit line and the top of
// the edit area.
// pCurrentWordLength: The length of the current word
//
// Return value: An object containing values to be used by the main input loop.
// The object will contain these values:
// returnCode: The value to use as the editor's return code
// continueOn: Whether or not the input loop should continue
// x: The horizontal component of the cursor position
// y: The vertical component of the cursor position
// currentWordLength: The length of the current word
function handleDCTESCMenu(pCurpos, pCurrentWordLength)
{
var returnObj = new Object();
returnObj.returnCode = 0;
returnObj.continueOn = true;
returnObj.x = pCurpos.x;
returnObj.y = pCurpos.y;
returnObj.currentWordLength = pCurrentWordLength;
// Call doDCTMenu() to display the DCT Edit menu and get the
// user's choice.
var editLineDiff = pCurpos.y - gEditTop;
var menuChoice = doDCTMenu(gEditLeft, gEditRight, gEditTop,
displayMessageRectangle, gEditLinesIndex,
editLineDiff, gConfigSettings.userIsSysop, gCanCrossPost);
// Take action according to the user's choice.
// Save
if ((menuChoice == "S") || (menuChoice == CTRL_Z) ||
(menuChoice == DCTMENU_FILE_SAVE))
{
returnObj.returnCode = 0;
returnObj.continueOn = false;
}
// Abort
else if ((menuChoice == "A") || (menuChoice == CTRL_A) ||
(menuChoice == DCTMENU_FILE_ABORT))
{
// Before aborting, ask they user if they really want to abort.
if (promptYesNo("Abort message", false, "Abort", false))
{
returnObj.returnCode = 1; // Aborted
returnObj.continueOn = false;
}
else
{
// Make sure the edit color attribute is set back.
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
}
}
// Toggle insert/overwrite mode
else if ((menuChoice == CTRL_V) || (menuChoice == DCTMENU_EDIT_INSERT_TOGGLE))
toggleInsertMode(pCurpos);
// Import file (sysop only)
else if (menuChoice == DCTMENU_SYSOP_IMPORT_FILE)
{
var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
returnObj.x = retval.x;
returnObj.y = retval.y;
returnObj.currentWordLength = retval.currentWordLength;
}
// Import file for sysop, or Insert/Overwrite toggle for non-sysop
else if (menuChoice == "I")
{
if (gConfigSettings.userIsSysop)
{
var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
returnObj.x = retval.x;
returnObj.y = retval.y;
returnObj.currentWordLength = retval.currentWordLength;
}
else
toggleInsertMode(pCurpos);
}
// Find text
else if ((menuChoice == CTRL_F) || (menuChoice == "F") ||
(menuChoice == DCTMENU_EDIT_FIND_TEXT))
{
var retval = findText(pCurpos);
returnObj.x = retval.x;
returnObj.y = retval.y;
}
// Command List
else if ((menuChoice == "O") || (menuChoice == DCTMENU_HELP_COMMAND_LIST))
{
displayCommandList(true, true, true, gCanCrossPost, gConfigSettings.userIsSysop,
gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
displayEditLines);
}
// General help
else if ((menuChoice == "G") || (menuChoice == DCTMENU_HELP_GENERAL))
{
displayGeneralHelp(true, true, true);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
displayEditLines);
}
// Program info
else if ((menuChoice == "P") || (menuChoice == DCTMENU_HELP_PROGRAM_INFO))
{
displayProgramInfo(true, true);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
displayEditLines);
}
// Export the message
else if ((menuChoice == "X") || (menuChoice == DCTMENU_SYSOP_EXPORT_FILE))
{
if (gConfigSettings.userIsSysop)
{
exportToFile(gConfigSettings.userIsSysop);
console.gotoxy(returnObj.x, returnObj.y);
}
}
// Edit the message
else if ((menuChoice == "E") || (menuChoice == KEY_ESC))
{
// We don't need to do do anything in here.
}
// Cross-post
else if ((menuChoice == CTRL_C) || (menuChoice == "C") || (menuChoice == DCTMENU_CROSS_POST))
{
if (gCanCrossPost)
doCrossPosting(pCurpos);
}
// List text replacements
else if ((menuChoice == CTRL_T) || (menuChoice == "T") || (menuChoice == DCTMENU_LIST_TXT_REPLACEMENTS))
{
if (gConfigSettings.enableTextReplacements)
listTextReplacements();
}
// User settings
else if ((menuChoice == CTRL_U) || (menuChoice == "N") || (menuChoice == DCTMENU_EDIT_SETTINGS))
doUserSettings(pCurpos, true);
// Make sure the edit color attribute is set back.
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
return returnObj;
}
// Displays the IceEdit-style ESC menu and handles user input from that menu.
// This is used by the main input loop.
//
// Parameters:
// curpos: The current cursor position
// pEditLineDiff: The difference between the current edit line and the top of
// the edit area.
// pCurrentWordLength: The length of the current word
//
// Return value: An object containing values to be used by the main input loop.
// The object will contain these values:
// returnCode: The value to use as the editor's return code
// continueOn: Whether or not the input loop should continue
// x: The horizontal component of the cursor position
// y: The vertical component of the cursor position
// currentWordLength: The length of the current word
function handleIceESCMenu(pCurpos, pCurrentWordLength)
{
var returnObj = new Object();
returnObj.returnCode = 0;
returnObj.continueOn = true;
returnObj.x = pCurpos.x;
returnObj.y = pCurpos.y;
returnObj.currentWordLength = pCurrentWordLength;
// Call doIceESCMenu() to display the choices, and then take the
// chosen action.
var userChoice = doIceESCMenu(console.screen_rows, gCanCrossPost);
switch (userChoice)
{
case ICE_ESC_MENU_SAVE:
returnObj.returnCode = 0;
returnObj.continueOn = false;
break;
case ICE_ESC_MENU_ABORT:
// Before aborting, ask they user if they really want to abort.
if (promptYesNo("Abort message", false, "Abort", false))
{
returnObj.returnCode = 1; // Aborted
returnObj.continueOn = false;
}
break;
case ICE_ESC_MENU_EDIT:
// Nothing needs to be done for this option.
break;
case ICE_ESC_MENU_SETTINGS:
doUserSettings(pCurpos, true);
break;
case ICE_ESC_MENU_HELP:
displayProgramInfo(true, false);
displayCommandList(false, false, true, gCanCrossPost, gConfigSettings.userIsSysop,
gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
clearEditAreaBuffer();
fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
displayEditLines);
break;
case ICE_ESC_MENU_CROSS_POST:
if (gCanCrossPost)
doCrossPosting(pCurpos);
break;
}
// If the user didn't choose help, then we only need to refresh the bottom
// row on the screen.
if (userChoice != ICE_ESC_MENU_HELP)
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
// Make sure the edit color attribute is set back.
//console.print("n" + gTextAttrs);
console.print(chooseEditColor());
return returnObj;
}
// Figures out and returns the length of a word in the message text,based on
// a given edit lines index and text line index.
//
// Parameters:
// pEditLinesIndex: The index into the gEditLines array
// pTextLineIndex: The index into the line's text
//
// Return value: The length of the word at the given indexes
function getWordLength(pEditLinesIndex, pTextLineIndex)
{
// pEditLinesIndex and pTextLineIndex should be >= 0 before we can do
// anything in this function.
if ((pEditLinesIndex < 0) || (pTextLineIndex < 0))
return 0;
// Also, make sure gEditLines[pEditLinesIndex] is valid.
if ((gEditLines[pEditLinesIndex] == null) || (typeof(gEditLines[pEditLinesIndex]) == "undefined"))
return 0;
// This function counts and returns the number of non-whitespace characters
// before the current character.
function countBeforeCurrentChar()
{
var charCount = 0;
for (var i = pTextLineIndex-1; i >= 0; --i)
{
if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
++charCount;
else
break;
}
return charCount;
}
var wordLen = 0;
// If there are only characters to the left, or if the current
// character is a space, then count before the current character.
if ((pTextLineIndex == gEditLines[pEditLinesIndex].length()) ||
(gEditLines[pEditLinesIndex].text.charAt(gTextLineIndex) == " "))
wordLen = countBeforeCurrentChar();
// If there are charactrs to the left and at the current line index,
// then count to the left only if the current character is not whitespace.
else if (pTextLineIndex == gEditLines[pEditLinesIndex].length()-1)
{
if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
wordLen = countBeforeCurrentChar() + 1;
}
// If there are characters to the left and right, then count to the left
// and right only if the current character is not whitespace.
else if (pTextLineIndex < gEditLines[pEditLinesIndex].length()-1)
{
if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
{
// Count non-whitespace characters to the left, and include the current one.
wordLen = countBeforeCurrentChar() + 1;
// Count characters to the right.
for (var i = pTextLineIndex+1; i < gEditLines[pEditLinesIndex].length(); ++i)
{
if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
++wordLen;
else
break;
}
}
}
return wordLen;
}
// Inserts a string into gEditLines after a given index.
//
// Parameters:
// pInsertLineIndex: The index for gEditLines at which to insert the string.
// pString: The string to insert
// pHardNewline: Whether or not to enable the hard newline flag for the line
// pIsQuoteLine: Whether or not the line is a quote line
//
// Return value: Whether or not the line was inserted below the given index
// (as opposed to above).
function insertLineIntoMsg(pInsertLineIndex, pString, pHardNewline, pIsQuoteLine)
{
var insertedBelow = false;
// Create the new text line
var line = new TextLine();
line.text = pString;
line.hardNewlineEnd = false;
if ((pHardNewline != null) && (typeof(pHardNewline) != "undefined"))
line.hardNewlineEnd = pHardNewline;
if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) != "undefined"))
line.isQuoteLine = pIsQuoteLine;
// If the current message line is empty, insert the quote line above
// the current line. Otherwise, insert the quote line below the
// current line.
if (typeof(gEditLines[pInsertLineIndex]) == "undefined")
gEditLines.splice(pInsertLineIndex, 0, line);
// Note: One time, I noticed an error with the following test:
// gEditLines[pInsertLineIndex] has no properties
// Thus, I added the above test to see if the edit line is valid.
else if (gEditLines[pInsertLineIndex].length() == 0)
gEditLines.splice(pInsertLineIndex, 0, line);
else
{
// Insert the quote line below the given line index
gEditLines.splice(pInsertLineIndex + 1, 0, line);
// The current message line should have its hardNewlineEnd set
// true so that the quote line won't get wrapped up.
gEditLines[pInsertLineIndex].hardNewlineEnd = true;
insertedBelow = true;
}
return insertedBelow;
}
// Prompts the user for a filename on the BBS computer and loads its contents
// into the message. This is for sysops only!
//
// Parameters:
// pIsSysop: Whether or not the user is the sysop
// pCurpos: The current cursor position (with x and y properties)
//
// Return value: An object containing the following information:
// x: The horizontal component of the cursor's location
// y: The vertical component of the cursor's location
// currentWordLength: The length of the current word
function importFile(pIsSysop, pCurpos)
{
// Create the return object
var retObj = new Object();
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
// Don't let non-sysops do this.
if (!pIsSysop)
return retObj;
var loadedAFile = false;
// This loop continues to prompt the user until they enter a valid
// filename or a blank string.
var continueOn = true;
while (continueOn)
{
// Go to the last row on the screen and prompt the user for a filename
var promptText = "ncFile:h";
var promptTextLen = strip_ctrl(promptText).length;
console.gotoxy(1, console.screen_rows);
console.cleartoeol("n");
console.print(promptText);
var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
continueOn = (filename != "");
if (continueOn)
{
filename = file_getcase(filename);
if (filename != undefined)
{
// Open the file and insert its contents into the message.
var inFile = new File(filename);
if (inFile.exists && inFile.open("r"))
{
const maxLineLength = gEditWidth - 1; // Don't insert lines longer than this
var fileLine;
while (!inFile.eof)
{
fileLine = inFile.readln(1024);
// fileLine should always be a string, but there seem to be
// situations where it isn't. So if it's a string, we can
// insert text into gEditLines as normal. If it's not a
// string, insert a blank line.
if (typeof(fileLine) == "string")
{
// Tab characters can cause problems, so replace tabs with 3 spaces.
fileLine = fileLine.replace(/\t/, " ");
// Insert the line into the message, splitting up the line,
// if the line is longer than the edit area.
do
{
insertLineIntoMsg(gEditLinesIndex, fileLine.substr(0, maxLineLength),
true, false);
fileLine = fileLine.substr(maxLineLength);
++gEditLinesIndex;
} while (fileLine.length > maxLineLength);
// Edge case, if the line still has characters in it
if (fileLine.length > 0)
{
insertLineIntoMsg(gEditLinesIndex, fileLine, true, false);
++gEditLinesIndex;
}
}
else
{
insertLineIntoMsg(gEditLinesIndex, "", true, false);
++gEditLinesIndex;
}
}
inFile.close();
// If the last text line is blank, then remove it.
if (gEditLines[gEditLinesIndex].length() == 0)
{
gEditLines.splice(gEditLinesIndex, 1);
--gEditLinesIndex;
}
loadedAFile = true;
continueOn = false;
}
else // Unable to open the file
writeWithPause(1, console.screen_rows, "yhUnable to open the file!", ERRORMSG_PAUSE_MS);
}
else // Could not find the correct case for the file (it doesn't exist?)
writeWithPause(1, console.screen_rows, "yhUnable to locate the file!", ERRORMSG_PAUSE_MS);
}
}
// Refresh the help line on the bottom of the screen
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
// If we loaded a file, then refresh the message text.
if (loadedAFile)
{
// Insert a blank line into gEditLines so that the user ends up on a new
// blank line.
//displayEditLines(pScreenLine, pArrayIndex, pEndScreenRow, pClearRemainingScreenRows)
// Figure out the index to start at in gEditLines
var startIndex = 0;
if (gEditLines.length > gEditHeight)
startIndex = gEditLines.length - gEditHeight;
// Refresh the message on the screen
displayEditLines(gEditTop, startIndex, gEditBottom, true, true);
// Set up the edit lines & text line index for the last line, and
// place the cursor at the beginning of the last edit line.
// If the last line is short enough, place the cursor at the end
// of it. Otherwise, append a new line and place the cursor there.
if (gEditLines[gEditLinesIndex].length() < gEditWidth-1)
{
gEditLinesIndex = gEditLines.length - 1;
gTextLineIndex = gEditLines[gEditLinesIndex].length();
retObj.x = gEditLeft + gTextLineIndex;
retObj.y = gEditBottom;
if (gEditLines.length < gEditHeight)
retObj.y = gEditTop + gEditLines.length - 1;
retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
}
else
{
// Append a new line and place the cursor there
gEditLines.push(new TextLine());
gEditLinesIndex = gEditLines.length - 1;
gTextLineIndex = 0;
retObj.x = gEditLeft;
retObj.y = gEditBottom;
if (gEditLines.length < gEditHeight)
retObj.y = gEditTop + gEditLines.length - 1;
retObj.currentWordLength = 0;
}
}
// Make sure the cursor is where it's supposed to be.
console.gotoxy(retObj.x, retObj.y);
return retObj;
}
// This function lets sysops export (save) the current message to
// a file.
//
// Parameters:
// pIsSysop: Whether or not the user is the sysop
function exportToFile(pIsSysop)
{
// Don't let non-sysops do this.
if (!pIsSysop)
return;
// Go to the last row on the screen and prompt the user for a filename
var promptText = "ncFile:h";
var promptTextLen = strip_ctrl(promptText).length;
console.gotoxy(1, console.screen_rows);
console.cleartoeol("n");
console.print(promptText);
var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
if (filename != "")
{
var outFile = new File(filename);
if (outFile.open("w"))
{
const lastLineIndex = gEditLines.length - 1;
for (var i = 0; i < gEditLines.length; ++i)
{
// Use writeln to write all lines with CRLF except the last line.
if (i < lastLineIndex)
outFile.writeln(gEditLines[i].text);
else
outFile.write(gEditLines[i].text);
}
outFile.close();
writeWithPause(1, console.screen_rows, "mhMessage exported.", ERRORMSG_PAUSE_MS);
}
else // Could not open the file for writing
writeWithPause(1, console.screen_rows, "yhUnable to open the file for writing!", ERRORMSG_PAUSE_MS);
}
else // No filename specified
writeWithPause(1, console.screen_rows, "mhMessage not exported.", ERRORMSG_PAUSE_MS);
// Refresh the help line on the bottom of the screen
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
}
// Performs a text search.
//
// Parameters:
// pCurpos: The current cursor position (with x and y properties)
//
// Return value: An object containing the following properties:
// x: The horizontal component of the cursor position
// y: The vertical component of the cursor position
function findText(pCurpos)
{
// Create the return object.
var returnObj = new Object();
returnObj.x = pCurpos.x;
returnObj.y = pCurpos.y;
// This function makes use of the following "static" variables:
// lastSearchText: The text searched for last
// searchStartIndex: The starting index for gEditLines that should
// be used for the search
if (typeof(findText.lastSearchText) == "undefined")
findText.lastSearchText = "";
if (typeof(findText.searchStartIndex) == "undefined")
findText.searchStartIndex = 0;
// Go to the last row on the screen and prompt the user for text to find
var promptText = "ncText:h";
var promptTextLen = strip_ctrl(promptText).length;
console.gotoxy(1, console.screen_rows);
console.cleartoeol("n");
console.print(promptText);
var searchText = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
// If the user's search is text is different from last time, then set the
// starting gEditLines index to 0. Also, update the last search text.
if (searchText != findText.lastSearchText)
findText.searchStartIndex = 0;
findText.lastSearchText = searchText;
// Search for the text.
var caseSensitive = false; // Case-sensitive search?
var textIndex = 0; // The index of the text in the edit lines
if (searchText.length > 0)
{
// editLinesTopIndex is the index of the line currently displayed
// at the top of the edit area, and also the line to be displayed
// at the top of the edit area.
var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
// Look for the text in gEditLines
var textFound = false;
for (var i = findText.searchStartIndex; i < gEditLines.length; ++i)
{
if (caseSensitive)
textIndex = gEditLines[i].text.indexOf(searchText);
else
textIndex = gEditLines[i].text.toUpperCase().indexOf(searchText.toUpperCase());
// If the text was found in this line, then highlight it and
// exit the search loop.
if (textIndex > -1)
{
gTextLineIndex = textIndex;
textFound = true;
// If the line is above or below the edit area, then we'll need
// to refresh the edit lines on the screen. We also need to set
// the cursor position to the proper place.
returnObj.x = gEditLeft + gTextLineIndex;
var refresh = false;
if (i < editLinesTopIndex)
{
// The line is above the edit area.
refresh = true;
returnObj.y = gEditTop;
editLinesTopIndex = i;
}
else if (i >= editLinesTopIndex + gEditHeight)
{
// The line is below the edit area.
refresh = true;
returnObj.y = gEditBottom;
editLinesTopIndex = i - gEditHeight + 1;
}
else
{
// The line is inside the edit area.
returnObj.y = pCurpos.y + (i - gEditLinesIndex);
}
gEditLinesIndex = i;
if (refresh)
displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
// Highlight the found text on the line by briefly displaying it in a
// different color.
var highlightText = gEditLines[i].text.substr(textIndex, searchText.length);
console.gotoxy(returnObj.x, returnObj.y);
console.print("nk4" + highlightText);
mswait(1500);
console.gotoxy(returnObj.x, returnObj.y);
//console.print(gTextAttrs + highlightText);
console.print(chooseEditColor() + highlightText);
// The next time the user searches with the same text, we'll want
// to start searching at the next line. Wrap around if necessary.
findText.searchStartIndex = i + 1;
if (findText.searchStartIndex >= gEditLines.length)
findText.searchStartIndex = 0;
break;
}
}
// If the text wasn't found, tell the user. Also, make sure searchStartIndex
// is reset to 0.
if (!textFound)
{
console.gotoxy(1, console.screen_rows);
console.cleartoeol("n");
console.print("yhThe text wasn't found!");
mswait(ERRORMSG_PAUSE_MS);
findText.searchStartIndex = 0;
}
}
// Refresh the help line on the bottom of the screen
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
// Make sure the cursor is positioned where it should be.
console.gotoxy(returnObj.x, returnObj.y);
return returnObj;
}
// Returns whether we're in insert mode (if not, we're in overwrite mode).
function inInsertMode()
{
return (gInsertMode == "INS");
}
// Returns either the normal edit color (gTextAttrs) or the quote line
// color (gQuoteLineColor), depending on whether the current edit line
// is a normal line or a quote line.
function chooseEditColor()
{
return ("n" + (isQuoteLine(gEditLines, gEditLinesIndex) ? gQuoteLineColor : gTextAttrs));
}
// This function calculates the row on the screen to stop updating the
// message text.
//
// Parameters:
// pY: The topmost row at which we'll start writing
// pTopIndex: The topmost index in gEditLines
//
// Return value: The row on the screen to stop updating the
// message text.
function calcBottomUpdateRow(pY, pTopIndex)
{
var bottomScreenRow = gEditBottom;
// Note: This is designed to return the screen row #
// below the last message line. To return the exact
// bottommost screen row, subtract 1 from gEditLines.length-pTopIndex.
var bottommost = (pY + (gEditLines.length-pTopIndex));
if (bottomScreenRow > bottommost)
bottomScreenRow = bottommost;
return bottomScreenRow;
}
// This function updates the time on the screen and puts
// the cursor back to where it was.
//
// Parameters:
// pCurpos: An object containg the X and Y coordinates of the cursor position
// pMoveCursorBack: Boolean - Whether or not to move the cursor back after updating
// the time on the screen
function updateTime(pCurpos, pMoveCursorBack)
{
if (typeof(updateTime.timeStr) == "undefined")
updateTime.timeStr = getCurrentTimeStr();
// If the current time has changed since the last time this
// function was called, then update the time on the screen.
var currentTime = getCurrentTimeStr();
if (currentTime != updateTime.timeStr)
{
// Get the current cursor position so we can move
// the cursor back there when we're done.
var curpos = (typeof(pCurpos) == "object" ? pCurpos : console.getxy());
// Display the current time on the screen
fpDisplayTime(currentTime);
// Make sure the edit color attribute is set.
console.print("n" + gTextAttrs);
// Move the cursor back to where it was
if (pMoveCursorBack)
console.gotoxy(curpos);
// Update this function's time variable
updateTime.timeStr = currentTime;
}
}
// This function lets the user change the text color and is called by doEditLoop().
//
// Parameters:
// pTxtAttrs: The current text color & attributes
// pCurpos: An object containing x and y values representing the
// cursor position.
// pCurrentWordLength: The length of the current word that has been typed
//
// Return value: An object containing the following properties:
// txtAttrs: The chosen text color & attributes
// x and y: The horizontal and vertical cursor position
// timedOut: Whether or not the user input timed out (boolean)
// currentWordLength: The length of the current word
function doColorSelection(pTxtAttrs, pCurpos, pCurrentWordLength)
{
// Create the return object
var retObj = new Object();
retObj.txtAttrs = pTxtAttrs;
retObj.x = pCurpos.x;
retObj.y = pCurpos.y;
retObj.timedOut = false;
retObj.currentWordLength = pCurrentWordLength;
const originalScreenY = pCurpos.y; // For screen refreshing
// Display the 3 rows of color/attribute options and the prompt for the
// user
var colorSelTopLine = console.screen_rows - 2;
var curpos = new Object();
curpos.x = 1;
curpos.y = colorSelTopLine;
console.gotoxy(curpos);
console.print("ncForeground: whK:nkBlack whR:nrRed whG:ngGreen whY:nyYellow whB:nbBlue whM:nmMagenta whC:ncCyan whW:nwWhite");
console.cleartoeol("n");
console.crlf();
console.print("ncBackground: wh0:n0Blackn wh1:n1Redn wh2:n2kGreenn wh3:3Yellown wh4:n4Bluen wh5:n5Magentan wh6:n6kCyann wh7:n7kWhite");
console.cleartoeol("n");
console.crlf();
console.clearline("n");
console.print("cSpecial: whH:nhHigh Intensity wI:niBlinking nwhN:nNormal hgþ ncChoose colors/attributeshg: c");
// Get the attribute codes from the user. Ideally, we'd use console.getkeys(),
// but that outputs a CR at the end, which is undesirable. So instead, we call
// getUserInputWithSetOfInputStrs (defined in SlyEdit_Misc.js).
//var key = console.getkeys("KRGYBMCW01234567HIN").toString(); // Outputs a CR.. bad
var validKeys = ["KRGYBMCW", // Foreground color codes
"01234567", // Background color codes
"HIN"]; // Special color codes
var attrCodeKeys = getUserInputWithSetOfInputStrs(K_UPPER|K_NOCRLF|K_NOSPIN, validKeys, gConfigSettings);
// If the user entered some attributes, then set them in retObj.txtAttrs.
if (attrCodeKeys.length > 0)
{
retObj.txtAttrs = (attrCodeKeys.charAt(0) == "N" ? "" : "n");
for (var i = 0; i < attrCodeKeys.length; ++i)
retObj.txtAttrs += "" + attrCodeKeys.charAt(i);
}
// Display the parts of the screen text that we covered up with the
// color selection: Message edit lines, bottom border, and bottom help line.
var screenYDiff = colorSelTopLine - originalScreenY;
displayEditLines(colorSelTopLine, gEditLinesIndex + screenYDiff, gEditBottom, true, true);
fpDisplayTextAreaBottomBorder(gEditBottom+1, gUseQuotes, gEditLeft, gEditRight,
gInsertMode, gConfigSettings.allowColorSelection);
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
// Move the cursor to where it should be before returning
curpos.x = pCurpos.x;
curpos.y = pCurpos.y;
console.gotoxy(curpos);
// Set the settings in the return object, and return it.
retObj.x = curpos.x;
retObj.y = curpos.y;
return retObj;
}
// For the cross-posting UI: Draws the initial top border of
// the selection box
//
// Parameters:
// pTopLeft: The coordinates of the top-left corner of the box
// (must have x and y properties)
// pWidth: The width of the box
// pBorderColor: The color to use for the border
// pTextColor: The color to use for the border text
function drawInitialCrossPostSelBoxTopBorder(pTopLeft, pWidth, pBorderColor, pTextColor)
{
console.gotoxy(pTopLeft);
console.print(pBorderColor + UPPER_LEFT_SINGLE + RIGHT_T_SINGLE +
pTextColor + "Cross-posting: Choose group" +
pBorderColor + LEFT_T_SINGLE);
var len = pWidth - 31;
for (var i = 0; i < len; ++i)
console.print(HORIZONTAL_SINGLE);
}
// For the cross-posting UI: Draws the initial bottom border of
// the selection box
//
// Parameters:
// pBottomLeft: The coordinates of the bottom-left corner of the box
// (must have x and y properties)
// pWidth: The width of the box
// pBorderColor: The color to use for the border
// pMsgSubs: Boolean - Whether or not this is being used for the message sub-boards.
// If true, then this will output "Toggle" for the Enter action. Otherwise
// (for message groups), this will output "Select" for the Enter action.
function drawInitialCrossPostSelBoxBottomBorder(pBottomLeft, pWidth, pBorderColor,
pMsgSubs)
{
console.gotoxy(pBottomLeft);
console.print(pBorderColor + LOWER_LEFT_SINGLE + RIGHT_T_SINGLE +
"nhcb, cb, cPgUpb, cPgDnb, cFy)birst, cLy)bast, cEntery=b"
+ (pMsgSubs ? "Toggle" : "Select") + ", cCtrl-Cnc/hQy=bEnd, c?y=bHelpn"
+ pBorderColor + LEFT_T_SINGLE);
len = pWidth - 71;
for (var i = 0; i < len; ++i)
console.print(HORIZONTAL_SINGLE);
console.print(LOWER_RIGHT_SINGLE);
}
// Displays help text for cross-posting, for use in cross-post selection mode.
//
// Parameters:
// selBoxUpperLeft: An object containg x and y coordinates for the upper-left
// corner of the selection box
// selBoxLowerRight: An object containg x and y coordinates for the lower-right
// corner of the selection box
function displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight)
{
// Create an array of help text, but do it only once, the
// first time this function is called.
if (typeof(displayCrossPostHelp.helpLines) == "undefined")
{
displayCrossPostHelp.helpLines = new Array();
displayCrossPostHelp.helpLines.push("ncCross-posing allows you to post a message in more than one message");
displayCrossPostHelp.helpLines.push("area. To select areas for cross-posting, do the following:");
displayCrossPostHelp.helpLines.push(" h1. ncChoose a message group from the list with the Enter key.");
displayCrossPostHelp.helpLines.push(" Alternately, you may type the number of the message group.");
displayCrossPostHelp.helpLines.push(" h2. ncIn the list of message sub-boards that appears, toggle individual");
displayCrossPostHelp.helpLines.push(" sub-boards with the Enter key. Alternately, you may type the");
displayCrossPostHelp.helpLines.push(" number of the message sub-board.");
displayCrossPostHelp.helpLines.push("Message sub-boards that are toggled for cross-posting will include a");
displayCrossPostHelp.helpLines.push("check mark (" + gConfigSettings.genColors.crossPostChk + CHECK_CHAR + "nc) in the sub-board list. Initially, your current message");
displayCrossPostHelp.helpLines.push("sub-board is enabled by default. Also, your current message group is");
displayCrossPostHelp.helpLines.push("marked with an asterisk (" + gConfigSettings.genColors.crossPostMsgGrpMark + "*nc).");
displayCrossPostHelp.helpLines.push("To navigate the list, you may use the up & down arrow keys, N to go to");
displayCrossPostHelp.helpLines.push("the next page, P to go to the previous page, F to go to the first page,");
displayCrossPostHelp.helpLines.push("and L to go to the last page. Ctrl-C, Q, and ESC end the selection.");
}
// Display the help text
var selBoxInnerWidth = selBoxLowerRight.x - selBoxUpperLeft.x - 1;
var selBoxInnerHeight = selBoxLowerRight.y - selBoxUpperLeft.y - 1;
var lineLen = 0;
var screenRow = selBoxUpperLeft.y+1;
console.print("n");
for (var i = 0; (i < displayCrossPostHelp.helpLines.length) && (screenRow < selBoxLowerRight.y); ++i)
{
console.gotoxy(selBoxUpperLeft.x+1, screenRow++);
console.print(displayCrossPostHelp.helpLines[i]);
// If the text line is shorter than the inner width of the box, then
// blank the rest of the line.
lineLen = strip_ctrl(displayCrossPostHelp.helpLines[i]).length;
if (lineLen < selBoxInnerWidth)
{
var numSpaces = selBoxInnerWidth - lineLen;
for (var ii = 0; ii < numSpaces; ++ii)
console.print(" ");
}
}
// If screenRow is below the bottommost inner row in the selection box, then
// blank the rest of the lines in the selection box.
if (screenRow < selBoxLowerRight.y)
{
var printfStr = "%-" + selBoxInnerWidth + "s";
console.print("n");
for (; screenRow < selBoxLowerRight.y; ++screenRow)
{
console.gotoxy(selBoxUpperLeft.x+1, screenRow);
printf(printfStr, "");
}
}
}
// Handles the cross-posting functionality. Displays a menu of
// the message areas and allows the user to select other areas
// into which to cross-post their message.
//
// Parameters:
// pOriginalCurpos: Optional - The correct position of the cursor
// when this function is called. If this parameter
// is not passed, then this function will call
// console.getxy() to get the position of the cursor.
// This is used for refreshing the message area to
// erase the cross-post selection box when the user
// quits out of cross-post selection.
function doCrossPosting(pOriginalCurpos)
{
// If cross-posting is not allowed, then just return.
if (!gCanCrossPost)
return;
// This function returns the index of the bottommost message group that
// can be displayed on the screen.
//
// Parameters:
// pTopGrpIndex: The index of the topmost message group displayed on screen
// pNumItemsPerPage: The number of items per page
function getBottommostGrpIndex(pTopGrpIndex, pNumItemsPerPage)
{
var bottomGrpIndex = pTopGrpIndex + pNumItemsPerPage - 1;
// If bottomGrpIndex is beyond the last index, then adjust it.
if (bottomGrpIndex >= msg_area.grp_list.length)
bottomGrpIndex = msg_area.grp_list.length - 1;
return bottomGrpIndex;
}
// Re-writes the "Choose group" text in the top border of the selection
// box. For use when returning from the sub-board list.
//
// Parameters:
// pSelBoxUpperLeft: An object containing x and y values for the upper-left
// corner of the selection box
// pSelBoxInnerWidth: The inner width (inside the left & right borders) of the
// selection box
// pGrpIndex: The index of message group tht was chosen
function reWriteInitialTopBorderText(pSelBoxUpperLeft, pSelBoxInnerWidth, pGrpIndex)
{
// Position the cursor after the "Cross-posting: " text in the border and
// write the "Choose group" text
console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y);
console.print("n" + gConfigSettings.genColors.listBoxBorderText + "Choose group");
// Re-write the border characters to overwrite the message group name
grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25);
// Write the updated border character(s)
console.print("n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
if (grpDesc.length > 3)
{
var numChars = grpDesc.length - 3;
for (var i = 0; i < numChars; ++i)
console.print(HORIZONTAL_SINGLE);
}
}
// Store the position of the cursor when we started so that we
// can return the cursor back to this position at the end
var origStartingCurpos = null;
if ((pOriginalCurpos != null) && (typeof(pOriginalCurpos) != "undefined"))
origStartingCurpos = pOriginalCurpos;
else
origStartingCurpos = console.getxy();
// Construct objects to represent the screen locations of the upper-left
// and lower-right corners of the selection box. Initially, let the box
// borders be 1 character into the edit area on all sides.
var selBoxUpperLeft = new Object();
selBoxUpperLeft.x = gEditLeft + 3;
selBoxUpperLeft.y = gEditTop + 1;
var selBoxLowerRight = new Object();
selBoxLowerRight.x = gEditRight - 3;
selBoxLowerRight.y = gEditBottom - 1;
// Total and inner text width & height of the selection box
var selBoxWidth = selBoxLowerRight.x - selBoxUpperLeft.x + 1;
var selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
// Don't let the box's height be more than 17 characters.
if (selBoxHeight > 17)
{
selBoxLowerRight.y = selBoxUpperLeft.y + 16; // For a height of 17 characters
selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
}
// Inner size of the box (for text)
var selBoxInnerWidth = selBoxWidth - 2;
var selBoxInnerHeight = selBoxHeight - 2;
// Calculate the index of the message line at the top of the edit area, which
// which is where the message area list box will start. We need to store
// this so that we can erase the selection box when the user is done
// selecting a message area. We'll erase the box by re-writing the message
// text.
var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (origStartingCurpos.y-selBoxUpperLeft.y);
// Variables for keeping track of the message group/area list
var topMsgGrpIndex = 0; // The index of the message group at the top of the list
// Figure out the index of the last message group to appear on the screen.
var bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, selBoxInnerHeight);
var numPages = Math.ceil(msg_area.grp_list.length / selBoxInnerHeight);
var numItemsPerPage = selBoxInnerHeight;
var topIndexForLastPage = (selBoxInnerHeight * numPages) - selBoxInnerHeight;
// msgGrpFieldLen will store the length to use for the message group numbers
// in the list. It should be able to accommodate the highest message group
// number on the system.
var msgGrpFieldLen = msg_area.grp_list.length.toString().length;
var selectedGrpIndex = 0; // The currently-selected group index
// Draw the selection box borders
// Top border
drawInitialCrossPostSelBoxTopBorder(selBoxUpperLeft, selBoxWidth,
gConfigSettings.genColors.listBoxBorder,
gConfigSettings.genColors.listBoxBorderText);
// Side borders
console.print(UPPER_RIGHT_SINGLE);
for (var row = selBoxUpperLeft.y+1; row < selBoxLowerRight.y; ++row)
{
console.gotoxy(selBoxUpperLeft.x, row);
console.print(VERTICAL_SINGLE);
console.gotoxy(selBoxLowerRight.x, row);
console.print(VERTICAL_SINGLE);
}
// Bottom border
drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
selBoxWidth, gConfigSettings.genColors.listBoxBorder,
false);
// Write the message groups
var pageNum = 1;
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// Move the cursor to the inner upper-left corner of the selection box
var curpos = new Object(); // Current cursor position
curpos.x = selBoxUpperLeft.x+1;
curpos.y = selBoxUpperLeft.y+1;
console.gotoxy(curpos);
// User input loop
var userInput = null;
var continueChoosingMsgArea = true;
while (continueChoosingMsgArea)
{
pageNum = calcPageNum(topMsgGrpIndex, selBoxInnerHeight);
// Get a key from the user (upper-case) and take action based upon it.
userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
switch (userInput)
{
case KEY_UP: // Move up one message group in the list
if (selectedGrpIndex > 0)
{
// If the previous group index is on the previous page, then
// display the previous page.
var previousGrpIndex = selectedGrpIndex - 1;
if (previousGrpIndex < topMsgGrpIndex)
{
// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgGrpIndex -= numItemsPerPage;
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
ListScreenfulOfMsgGrps(topMsgGrpIndex, previousGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// We'll want to move the cursor to the leftmost character
// of the selected line.
curpos.x = selBoxUpperLeft.x+1;
curpos.y = selBoxUpperLeft.y+selBoxInnerHeight;
}
else
{
// Display the current line un-highlighted
console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
// Display the previous line highlighted
curpos.x = selBoxUpperLeft.x+1;
--curpos.y;
console.gotoxy(curpos);
writeMsgGroupLine(previousGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
}
selectedGrpIndex = previousGrpIndex;
console.gotoxy(curpos); // Move the cursor into place where it should be
}
break;
case KEY_DOWN: // Move down one message group in the list
if (selectedGrpIndex < msg_area.grp_list.length - 1)
{
// If the next group index is on the next page, then display
// the next page.
var nextGrpIndex = selectedGrpIndex + 1;
if (nextGrpIndex > bottomMsgGrpIndex)
{
// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgGrpIndex += numItemsPerPage;
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
ListScreenfulOfMsgGrps(topMsgGrpIndex, nextGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// We'll want to move the cursor to the leftmost character
// of the selected line.
curpos.x = selBoxUpperLeft.x+1;
curpos.y = selBoxUpperLeft.y+1;
}
else
{
// Display the current line un-highlighted
console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
// Display the next line highlighted
curpos.x = selBoxUpperLeft.x+1;
++curpos.y;
console.gotoxy(curpos);
writeMsgGroupLine(nextGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
}
selectedGrpIndex = nextGrpIndex;
console.gotoxy(curpos); // Move the cursor into place where it should be
}
break;
case KEY_HOME: // Go to the top message group on the screen
if (selectedGrpIndex > topMsgGrpIndex)
{
// Display the current line un-highlighted, adjust
// selectedGrpIndex, then display the new line
// highlighted.
console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
selectedGrpIndex = topMsgGrpIndex;
curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
console.gotoxy(curpos);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
console.gotoxy(curpos);
}
break;
case KEY_END: // Go to the bottom message group on the screen
if (selectedGrpIndex < bottomMsgGrpIndex)
{
// Display the current line un-highlighted, adjust
// selectedGrpIndex, then display the new line
// highlighted.
console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
selectedGrpIndex = bottomMsgGrpIndex;
curpos.x = selBoxUpperLeft.x + 1;
curpos.y = selBoxUpperLeft.y + (bottomMsgGrpIndex-topMsgGrpIndex+1);
console.gotoxy(curpos);
writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
console.gotoxy(curpos);
}
break;
case KEY_PAGE_DOWN: // Go to the next page
var nextPageTopIndex = topMsgGrpIndex + numItemsPerPage;
if (nextPageTopIndex < msg_area.grp_list.length)
{
// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgGrpIndex = nextPageTopIndex;
pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
selectedGrpIndex = topMsgGrpIndex;
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case KEY_PAGE_UP: // Go to the previous page
var prevPageTopIndex = topMsgGrpIndex - numItemsPerPage;
if (prevPageTopIndex >= 0)
{
// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgGrpIndex = prevPageTopIndex;
pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
selectedGrpIndex = topMsgGrpIndex;
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case 'F': // Go to the first page
if (topMsgGrpIndex > 0)
{
topMsgGrpIndex = 0;
pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
selectedGrpIndex = 0;
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case 'L': // Go to the last page
if (topMsgGrpIndex < topIndexForLastPage)
{
topMsgGrpIndex = topIndexForLastPage;
pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
selectedGrpIndex = topIndexForLastPage;
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
msgGrpFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case CTRL_C: // Quit (Ctrl-C is the cross-post hotkey)
case KEY_ESC: // Quit
case "Q": // Quit
continueChoosingMsgArea = false;
break;
case KEY_ENTER: // Select the currently-highlighted message group
// Store the current cursor position for later, then show the
// sub-boards in the chosen message group and let the user
// toggle ones for cross-posting.
var selectCurrentGrp_originalCurpos = curpos;
var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(selectedGrpIndex,
selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
// If the user toggled some sub-boards...
if (selectMsgAreaRetObj.subBoardsToggled)
{
// TODO: Does anything need to be done here?
}
// Update the Enter action text in the bottom border to say "Select"
// (instead of "Toggle").
console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
console.print("nhbSelect");
// Refresh the top border of the selection box, refresh the list of
// message groups in the box, and move the cursor back to its original
// position.
reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, selectedGrpIndex);
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
selBoxLowerRight.x-1, msgGrpFieldLen, true);
console.gotoxy(selectCurrentGrp_originalCurpos);
break;
case '?': // Display cross-post help
displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight);
console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y-1);
console.pause();
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
selBoxLowerRight.x-1, msgGrpFieldLen, true);
console.gotoxy(curpos);
break;
default:
// If the user entered a numeric digit, then treat it as
// the start of the message group number.
if (userInput.match(/[0-9]/))
{
var originalCurpos = curpos;
// Put the user's input back in the input buffer to
// be used for getting the rest of the message number.
console.ungetstr(userInput);
// We want to write the prompt text only if the first digit entered
// by the user is an ambiguous message group number (i.e., if
// the first digit is 2 and there's a message group # 2 and 20).
var writePromptText = (msg_area.grp_list.length >= +userInput * 10);
if (writePromptText)
{
console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
printf("ncChoose group #:%" + +(selBoxInnerWidth-15) + "s", "");
console.gotoxy(selBoxUpperLeft.x+17, selBoxLowerRight.y);
console.print("h");
}
else
console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
userInput = console.getnum(msg_area.grp_list.length);
// Re-draw the bottom border of the selection box
if (writePromptText)
{
drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
selBoxWidth, gConfigSettings.genColors.listBoxBorder,
false);
}
else
{
console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
}
// If the user made a selection, then let them choose a
// sub-board from the group.
if (userInput > 0)
{
// Show the sub-boards in the chosen message group and
// let the user toggle ones for cross-posting.
// userInput-1 is the group index
var chosenGrpIndex = userInput - 1;
var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(chosenGrpIndex,
selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
// If the user chose a sub-board, then set bbs.curgrp and
// bbs.cursub, and don't continue the input loop anymore.
if (selectMsgAreaRetObj.subBoardsToggled)
{
// TODO: Does anything need to be done here?
}
// Update the Enter action text in the bottom border to say "Select"
// (instead of "Toggle").
console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
console.print("nhbSelect");
// Refresh the top border of the selection box
reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, chosenGrpIndex);
}
// Refresh the list of message groups in the box and move the
// cursor back to its original position.
ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
selBoxLowerRight.x-1, msgGrpFieldLen, true);
console.gotoxy(originalCurpos);
}
break;
}
}
// We're done selecting message areas for cross-posting.
// Erase the message area selection rectangle by re-drawing the message text.
// Then, move the cursor back to where it was when we started the message
// area selection.
displayMessageRectangle(selBoxUpperLeft.x, selBoxUpperLeft.y, selBoxWidth,
selBoxHeight, editLineIndexAtSelBoxTopRow, true);
console.gotoxy(origStartingCurpos);
}
// Displays a screenful of message groups, for the cross-posting
// interface.
//
// Parameters:
// pStartIndex: The message group index to start at (0-based)
// pSelectedIndex: The index of the currently-selected message group
// pStartScreenRow: The row on the screen to start at (1-based)
// pStartScreenCol: The column on the screen to start at (1-based)
// pEndScreenRow: The row on the screen to end at (1-based)
// pEndScreenCol: The column on the screen to end at (1-based)
// pMsgGrpFieldLen: The length to use for the group number field
// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end
// screen row if there aren't enough message groups to fill
// the screen.
function ListScreenfulOfMsgGrps(pStartIndex, pSelectedIndex, pStartScreenRow,
pStartScreenCol, pEndScreenRow, pEndScreenCol,
pMsgGrpFieldLen, pBlankToEndRow)
{
// If the parameters are invalid, then just return.
if ((typeof(pStartIndex) != "number") || (typeof(pSelectedIndex) != "number") ||
(typeof(pStartScreenRow) != "number") || (typeof(pStartScreenCol) != "number") ||
(typeof(pEndScreenRow) != "number") || (typeof(pEndScreenCol) != "number"))
{
return;
}
if ((pStartIndex < 0) || (pStartIndex >= msg_area.grp_list.length))
return;
if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows))
return;
if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows))
return;
if ((pStartScreenCol < 1) || (pStartScreenCol > console.screen_columns))
return;
if ((pEndScreenCol < 1) || (pEndScreenCol > console.screen_columns))
return;
// If pStartScreenRow is greater than pEndScreenRow, then swap them.
// Do the same with pStartScreenCol and pEndScreenCol.
if (pStartScreenRow > pEndScreenRow)
{
var temp = pStartScreenRow;
pStartScreenRow = pEndScreenRow;
pEndScreenRow = temp;
}
if (pStartScreenCol > pEndScreenCol)
{
var temp = pStartScreenCol;
pStartScreenCol = pEndScreenCol;
pEndScreenCol = temp;
}
// Calculate the ending index to use for the message groups array.
var endIndex = pStartIndex + (pEndScreenRow-pStartScreenRow);
if (endIndex >= msg_area.grp_list.length)
endIndex = msg_area.grp_list.length - 1;
var onePastEndIndex = endIndex + 1;
// Go to the specified screen row, and display the message group information.
var textWidth = pEndScreenCol - pStartScreenCol + 1;
var row = pStartScreenRow;
var grpIndex = pStartIndex;
for (; grpIndex < onePastEndIndex; ++grpIndex)
{
console.gotoxy(pStartScreenCol, row++);
// The 4th parameter to writeMsgGroupLine() is whether or not to
// write the message group with highlight colors.
writeMsgGroupLine(grpIndex, textWidth, pMsgGrpFieldLen, (grpIndex == pSelectedIndex));
}
// If pBlankToEndRow is true and we're not at the end row yet, then
// write blank lines to the end row.
if (pBlankToEndRow)
{
var screenRow = pStartScreenRow + (endIndex - pStartIndex) + 1;
if (screenRow <= pEndScreenRow)
{
console.print("n");
var areaWidth = pEndScreenCol - pStartScreenCol + 1;
var formatStr = "%-" + areaWidth + "s";
for (; screenRow <= pEndScreenRow; ++screenRow)
{
console.gotoxy(pStartScreenCol, screenRow)
printf(formatStr, "");
}
}
}
}
// Writes a message group information line (for choosing a message group
// for cross-posing).
//
// Parameters:
// pGrpIndex: The index of the message group to write (assumed to be valid)
// pTextWidth: The maximum text width
// pMsgGrpFieldLen: The length to use for the group number field
// pHighlight: Boolean - Whether or not to write the line highlighted.
function writeMsgGroupLine(pGrpIndex, pTextWidth, pMsgGrpFieldLen, pHighlight)
{
if ((typeof(pGrpIndex) != "number") || (typeof(pTextWidth) != "number"))
return;
// Build a printf format string
var grpDescLen = pTextWidth - pMsgGrpFieldLen - 2;
var printfStr = "n";
if (pHighlight)
{
printfStr += gConfigSettings.genColors.crossPostMsgGrpMarkHighlight + "%1s"
+ gConfigSettings.genColors.crossPostMsgAreaNumHighlight + "%" + pMsgGrpFieldLen
+ "d " + gConfigSettings.genColors.crossPostMsgAreaDescHighlight + "%-"
+ grpDescLen + "s";
}
else
{
printfStr += gConfigSettings.genColors.crossPostMsgGrpMark + "%1s"
+ gConfigSettings.genColors.crossPostMsgAreaNum + "%" + pMsgGrpFieldLen + "d "
+ gConfigSettings.genColors.crossPostMsgAreaDesc + "%-" + grpDescLen + "s";
}
// Write the message group information line
var markChar = (pGrpIndex == gMsgAreaInfo.grpIndex ? "*" : " ");
printf(printfStr, markChar, +(pGrpIndex+1), msg_area.grp_list[pGrpIndex].description.substr(0, grpDescLen));
}
// For cross-posting: Lets the user choose a sub-board within a message group
//
// Parameters:
// pGrpIndex: The index of the message group to choose from.
// pSelBoxUpperLeft: An object containing the following values:
// x: The horizontal coordinate (column) of the selection box's
// upper-left corner
// y: The vertical coordinate (row) of the selection box's
// upper-left corner
// pSelBoxLowerRight: An object containing the following values:
// x: The horizontal coordinate (column) of the selection box's
// lower-right corner
// y: The vertical coordinate (row) of the selection box's
// lower-right corner
// pSelBoxWidth: The width of the selection box
// pSelBoxHeight: The height of the selection box
// pSelBoxInnerWidth: The inner width of the selection box (inside the borders)
// pSelBoxInnerHeight: The inner height of the selection box (inside the borders)
//
// Return value: An object containing the following values:
// subBoardsToggled: Boolean - Whether or not any sub-boards were toggled
function crossPosting_selectSubBoardInGrp(pGrpIndex, pSelBoxUpperLeft, pSelBoxLowerRight,
pSelBoxWidth, pSelBoxHeight, pSelBoxInnerWidth,
pSelBoxInnerHeight)
{
// Create the return object with default values
var retObj = new Object();
retObj.subBoardsToggled = false;
// Check the parameters and return if any are invalid
if ((typeof(pGrpIndex) != "number") || (typeof(pSelBoxWidth) != "number") ||
(typeof(pSelBoxHeight) != "number") || (typeof(pSelBoxInnerWidth) != "number") ||
(typeof(pSelBoxInnerHeight) != "number") || (typeof(pSelBoxUpperLeft) != "object") ||
(typeof(pSelBoxLowerRight) != "object") || (typeof(pSelBoxUpperLeft.x) != "number") ||
(typeof(pSelBoxUpperLeft.y) != "number") || (typeof(pSelBoxLowerRight.x) != "number") ||
(typeof(pSelBoxLowerRight.y) != "number"))
{
return retObj;
}
// Clear the text inside the selection box
console.print("n");
var printfStr = "%" + pSelBoxInnerWidth + "s";
for (var rowOffset = 1; rowOffset <= pSelBoxInnerHeight; ++rowOffset)
{
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+rowOffset);
printf(printfStr, "");
}
// If there are no sub-boards in the given message group, then show
// an error and return.
if (msg_area.grp_list[pGrpIndex].sub_list.length == 0)
{
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+1);
console.print("yhThere are no message areas in the chosen group.");
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxUpperLeft.y+2);
console.pause();
return retObj;
}
// This function returns the index of the bottommost message sub-board that
// can be displayed on the screen.
//
// Parameters:
// pTopSubIndex: The index of the topmost message sub-board displayed on screen
// pNumItemsPerPage: The number of items per page
function getBottommostSubBoardIndex(pTopSubIndex, pNumItemsPerPage)
{
var bottomSubIndex = pTopSubIndex + pNumItemsPerPage - 1;
// If bottomSubIndex is beyond the last index, then adjust it.
if (bottomSubIndex >= msg_area.grp_list[pGrpIndex].sub_list.length)
bottomSubIndex = msg_area.grp_list[pGrpIndex].sub_list.length - 1;
return bottomSubIndex;
}
// This function writes the error message that the user can't post in a
// message sub-board, pauses, then refreshes the bottom border of the
// selection box.
//
// Parameters:
// pX: The column of the lower-right corner of the selection box
// pY: The row of the lower-right corner of the selection box
// pSubBoardNum: The number of the sub-board (1-based)
// pSelBoxWidth: The width of the selection box
// pSelBoxInnerWidth: The width of the selection box inside the left & right borders
// pPauseMS: The number of millisecons to pause after displaying the error message
// pCurpos: The position of the cursor before calling this function
function writeCantPostErrMsg(pX, pY, pSubBoardNum, pSelBoxWidth, pSelBoxInnerWidth, pPauseMS, pCurpos)
{
var cantPostErrMsg = "You're not allowed to post in area " + pSubBoardNum + ".";
console.gotoxy(pX+1, pY);
printf("nhy%-" + pSelBoxInnerWidth + "s", cantPostErrMsg);
console.gotoxy(pX+cantPostErrMsg.length+1, pY);
mswait(pPauseMS);
// Refresh the bottom border of the selection box
drawInitialCrossPostSelBoxBottomBorder({ x: pX, y: pY }, pSelBoxWidth,
gConfigSettings.genColors.listBoxBorder, true);
console.gotoxy(pCurpos);
}
// Update the text in the top border of the selection box to include
// the message area description
//¦Cross-posting: Choose group
//¦Cross-posting: Areas in
console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y);
var grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25);
console.print("n" + gConfigSettings.genColors.listBoxBorderText + "Areas in " +
grpDesc);
// Write the updated border character(s)
console.print("n" + gConfigSettings.genColors.listBoxBorder);
// If the length of the group description is shorter than the remaining text
// the selection box border, then draw horizontal lines to fill in the gap.
if (grpDesc.length < 3)
{
var numChars = 3 - grpDesc.length;
for (var i = 0; i < numChars; ++i)
console.print(HORIZONTAL_SINGLE);
}
console.print(LEFT_T_SINGLE);
// Update the Enter action text in the bottom border to say "Toggle"
// (instead of "Select").
console.gotoxy(pSelBoxUpperLeft.x+41, pSelBoxLowerRight.y);
console.print("nhbToggle");
// Variables for keeping track of the message group/area list
var topMsgSubIndex = 0; // The index of the message sub-board at the top of the list
// Figure out the index of the last message group to appear on the screen.
var bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, pSelBoxInnerHeight);
var numPages = Math.ceil(msg_area.grp_list[pGrpIndex].sub_list.length / pSelBoxInnerHeight);
var numItemsPerPage = pSelBoxInnerHeight;
var topIndexForLastPage = (pSelBoxInnerHeight * numPages) - pSelBoxInnerHeight;
var selectedMsgSubIndex = 0; // The currently-selected message sub-board index
// subNumFieldLen will store the length to use for the sub-board numbers in the list.
// It should be able to accommodate the highest message sub-board number in the
// group.
var subNumFieldLen = msg_area.grp_list[pGrpIndex].sub_list.length.toString().length;
// The number of milliseconds to pause after displaying the error message
// that the user isn't allowed to post in a sub-board (due to ARS).
var cantPostErrPauseMS = 2000;
// Write the sub-boards
var pageNum = 1;
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex, pSelBoxUpperLeft.y+1,
pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1, pSelBoxLowerRight.x-1,
subNumFieldLen, true);
// Move the cursor to the inner upper-left corner of the selection box
var curpos = new Object(); // Current cursor position
curpos.x = pSelBoxUpperLeft.x+1;
curpos.y = pSelBoxUpperLeft.y+1;
console.gotoxy(curpos);
// User input loop
var userInput = null;
var continueChoosingMsgSubBoard = true;
while (continueChoosingMsgSubBoard)
{
pageNum = calcPageNum(topMsgSubIndex, pSelBoxInnerHeight);
// Get a key from the user (upper-case) and take action based upon it.
userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
switch (userInput)
{
case KEY_UP: // Move up one message sub-board in the list
if (selectedMsgSubIndex > 0)
{
// If the previous group index is on the previous page, then
// display the previous page.
var previousMsgSubIndex = selectedMsgSubIndex - 1;
if (previousMsgSubIndex < topMsgSubIndex)
{
// Adjust topMsgSubIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgSubIndex -= numItemsPerPage;
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, previousMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// We'll want to move the cursor to the leftmost character
// of the selected line.
curpos.x = pSelBoxUpperLeft.x+1;
curpos.y = pSelBoxUpperLeft.y+pSelBoxInnerHeight;
}
else
{
// Display the current line un-highlighted
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, false);
// Display the previous line highlighted
curpos.x = pSelBoxUpperLeft.x+1;
--curpos.y;
console.gotoxy(curpos);
writeMsgSubLine(pGrpIndex, previousMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, true);
}
selectedMsgSubIndex = previousMsgSubIndex;
console.gotoxy(curpos); // Move the cursor into place where it should be
}
break;
case KEY_DOWN: // Move down one message sub-board in the list
if (selectedMsgSubIndex < msg_area.grp_list[pGrpIndex].sub_list.length - 1)
{
// If the next sub-board index is on the next page, then display
// the next page.
var nextMsgSubIndex = selectedMsgSubIndex + 1;
if (nextMsgSubIndex > bottomMsgSubIndex)
{
// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
// refresh the list on the screen.
topMsgSubIndex += numItemsPerPage;
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, nextMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// We'll want to move the cursor to the leftmost character
// of the selected line.
curpos.x = pSelBoxUpperLeft.x+1;
curpos.y = pSelBoxUpperLeft.y+1;
}
else
{
// Display the current line un-highlighted
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, false);
// Display the next line highlighted
curpos.x = pSelBoxUpperLeft.x+1;
++curpos.y;
console.gotoxy(curpos);
writeMsgSubLine(pGrpIndex, nextMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, true);
}
selectedMsgSubIndex = nextMsgSubIndex;
console.gotoxy(curpos); // Move the cursor into place where it should be
}
break;
case KEY_HOME: // Go to the top message sub-board on the screen
if (selectedMsgSubIndex > topMsgSubIndex)
{
// Display the current line un-highlighted, adjust
// selectedMsgSubIndex, then display the new line
// highlighted.
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, false);
selectedMsgSubIndex = topMsgSubIndex;
curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 };
console.gotoxy(curpos);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, true);
console.gotoxy(curpos);
}
break;
case KEY_END: // Go to the bottom message sub-board on the screen
if (selectedMsgSubIndex < bottomMsgSubIndex)
{
// Display the current line un-highlighted, adjust
// selectedGrpIndex, then display the new line
// highlighted.
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, false);
selectedMsgSubIndex = bottomMsgSubIndex;
curpos.x = pSelBoxUpperLeft.x + 1;
curpos.y = pSelBoxUpperLeft.y + (bottomMsgSubIndex-topMsgSubIndex+1);
console.gotoxy(curpos);
writeMsgSubLine(pGrpIndex, selectedMsgSubIndex, pSelBoxInnerWidth,
subNumFieldLen, true);
console.gotoxy(curpos);
}
break;
case KEY_PAGE_DOWN: // Go to the next page
var nextPageTopIndex = topMsgSubIndex + numItemsPerPage;
if (nextPageTopIndex < msg_area.grp_list[pGrpIndex].sub_list.length)
{
// Adjust the top and bottom indexes, and refresh the list on the
// screen.
topMsgSubIndex = nextPageTopIndex;
pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage);
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
selectedMsgSubIndex = topMsgSubIndex;
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case KEY_PAGE_UP: // Go to the previous page
var prevPageTopIndex = topMsgSubIndex - numItemsPerPage;
if (prevPageTopIndex >= 0)
{
// Adjust the top and bottom indexes, and refresh the list on the
// screen.
topMsgSubIndex = prevPageTopIndex;
pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage);
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
selectedMsgSubIndex = topMsgSubIndex;
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case 'F': // Go to the first page
if (topMsgSubIndex > 0)
{
topMsgSubIndex = 0;
pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage);
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
selectedMsgSubIndex = 0;
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case 'L': // Go to the last page
if (topMsgSubIndex < topIndexForLastPage)
{
topMsgSubIndex = topIndexForLastPage;
pageNum = calcPageNum(topMsgSubIndex, numItemsPerPage);
bottomMsgSubIndex = getBottommostSubBoardIndex(topMsgSubIndex, numItemsPerPage);
selectedMsgSubIndex = topIndexForLastPage;
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
// Put the cursor at the beginning of the topmost row of message groups
curpos = { x: pSelBoxUpperLeft.x+1, y: pSelBoxUpperLeft.y+1 };
console.gotoxy(curpos);
}
break;
case CTRL_C: // Quit (Ctrl-C is the cross-post hotkey)
case "Q": // Quit
case KEY_ESC: // Quit
continueChoosingMsgSubBoard = false;
break;
case KEY_ENTER: // Select the currently-highlighted message sub-board
// If the sub-board code is toggled on, then toggle it off, and vice-versa.
var msgSubCode = msg_area.grp_list[pGrpIndex].sub_list[selectedMsgSubIndex].code;
if (gCrossPostMsgSubs.subCodeExists(msgSubCode))
{
// Remove it from gCrossPostMsgSubs and refresh the line on the screen
gCrossPostMsgSubs.remove(msgSubCode);
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
// Write a blank space using highlight colors
console.print(gConfigSettings.genColors.crossPostChkHighlight + " ");
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
}
else
{
// If the user is allowed to post in the selected sub, then add it
// to gCrossPostMsgSubs and refresh the line on the screen;
// otherwise, show an error message.
if (user.compare_ars(msg_area.sub[msgSubCode].post_ars))
{
gCrossPostMsgSubs.add(msgSubCode);
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
// Write a checkmark using highlight colors
console.print(gConfigSettings.genColors.crossPostChkHighlight + CHECK_CHAR);
console.gotoxy(pSelBoxUpperLeft.x+1, curpos.y);
}
else
{
// Go to the bottom row of the selection box and display an error that
// the user can't post in the selected sub-board and pause for a moment.
writeCantPostErrMsg(pSelBoxUpperLeft.x, pSelBoxLowerRight.y, selectedMsgSubIndex+1,
pSelBoxWidth, pSelBoxInnerWidth, cantPostErrPauseMS, curpos);
}
}
break;
case '?': // Display cross-post help
displayCrossPostHelp(pSelBoxUpperLeft, pSelBoxLowerRight);
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1);
console.pause();
ListScreenfulOfMsgSubs(pGrpIndex, topMsgSubIndex, selectedMsgSubIndex,
pSelBoxUpperLeft.y+1, pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y-1,
pSelBoxLowerRight.x-1, subNumFieldLen, true);
console.gotoxy(curpos);
break;
default:
// If the user entered a numeric digit, then treat it as
// the start of the message sub-board number.
if (userInput.match(/[0-9]/))
{
var originalCurpos = curpos;
// Put the user's input back in the input buffer to
// be used for getting the rest of the message number.
console.ungetstr(userInput);
// We want to write the prompt text only if the first digit entered
// by the user is an ambiguous message sub-board number (i.e., if
// the first digit is 2 and there's a message group # 2 and 20).
var writePromptText = (msg_area.grp_list[pGrpIndex].sub_list.length >= +userInput * 10);
if (writePromptText)
{
// Move the cursor to the bottom border of the selection box and
// prompt the user for the message number.
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y);
printf("ncToggle sub-board #:%" + +(pSelBoxInnerWidth-19) + "s", "");
console.gotoxy(pSelBoxUpperLeft.x+21, pSelBoxLowerRight.y);
console.print("h");
}
else
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y);
userInput = console.getnum(msg_area.grp_list[pGrpIndex].sub_list.length);
var chosenMsgSubIndex = userInput - 1;
// Re-draw the bottom border of the selection box
if (writePromptText)
{
drawInitialCrossPostSelBoxBottomBorder({ x: pSelBoxUpperLeft.x, y: pSelBoxLowerRight.y },
pSelBoxWidth, gConfigSettings.genColors.listBoxBorder,
true);
}
else
{
console.gotoxy(pSelBoxUpperLeft.x+1, pSelBoxLowerRight.y);
console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
}
// If the user made a selection, then toggle it on/off.
if (userInput > 0)
{
var msgSubCode = msg_area.grp_list[pGrpIndex].sub_list[chosenMsgSubIndex].code;
if (gCrossPostMsgSubs.subCodeExists(msgSubCode))
{
// Remove it from gCrossPostMsgSubs and refresh the line on the screen
gCrossPostMsgSubs.remove(msgSubCode);
if ((chosenMsgSubIndex >= topMsgSubIndex) && (chosenMsgSubIndex <= bottomMsgSubIndex))
{
var screenRow = pSelBoxUpperLeft.y + (chosenMsgSubIndex - topMsgSubIndex + 1);
console.gotoxy(pSelBoxUpperLeft.x+1, screenRow);
// Write a blank space
var color = (chosenMsgSubIndex == selectedMsgSubIndex ?
gConfigSettings.genColors.crossPostChkHighlight :
gConfigSettings.genColors.crossPostChk);
console.print(color + " ");
}
}
else
{
// If the user is allowed to post in the selected sub, then add it
// to gCrossPostMsgSubs and refresh the line on the screen;
// otherwise, show an error message.
if (user.compare_ars(msg_area.sub[msgSubCode].post_ars))
{
gCrossPostMsgSubs.add(msgSubCode);
if ((chosenMsgSubIndex >= topMsgSubIndex) && (chosenMsgSubIndex <= bottomMsgSubIndex))
{
var screenRow = pSelBoxUpperLeft.y + (chosenMsgSubIndex - topMsgSubIndex + 1);
console.gotoxy(pSelBoxUpperLeft.x+1, screenRow);
// Write a checkmark
var color = (chosenMsgSubIndex == selectedMsgSubIndex ?
gConfigSettings.genColors.crossPostChkHighlight :
gConfigSettings.genColors.crossPostChk);
console.print(color + CHECK_CHAR);
}
}
else
{
// Go to the bottom row of the selection box and display an error that
// the user can't post in the selected sub-board and pause for a moment.
writeCantPostErrMsg(pSelBoxUpperLeft.x, pSelBoxLowerRight.y, chosenMsgSubIndex+1,
pSelBoxWidth, pSelBoxInnerWidth, cantPostErrPauseMS, curpos);
}
}
}
console.gotoxy(originalCurpos);
}
break;
}
}
return retObj;
}
// Displays a screenful of message groups, for the cross-posting
// interface.
//
// Parameters:
// pGrpIndex: The message group index
// pStartIndex: The message group index to start at (0-based)
// pSelectedIndex: The index of the currently-selected message sub
// pStartScreenRow: The row on the screen to start at (1-based)
// pStartScreenCol: The column on the screen to start at (1-based)
// pEndScreenRow: The row on the screen to end at (1-based)
// pEndScreenCol: The column on the screen to end at (1-based)
// pSubNumFieldLen: The length to use for the sub-board number field
// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end
// screen row if there aren't enough message groups to fill
// the screen.
function ListScreenfulOfMsgSubs(pGrpIndex, pStartIndex, pSelectedIndex, pStartScreenRow,
pStartScreenCol, pEndScreenRow, pEndScreenCol,
pSubNumFieldLen, pBlankToEndRow)
{
// If the parameters are invalid, then just return.
if ((typeof(pGrpIndex) != "number") || (typeof(pStartIndex) != "number") ||
(typeof(pSelectedIndex) != "number") || (typeof(pStartScreenRow) != "number") ||
(typeof(pStartScreenCol) != "number") || (typeof(pEndScreenRow) != "number") ||
(typeof(pEndScreenCol) != "number"))
{
return;
}
if ((pGrpIndex < 0) || (pGrpIndex >= msg_area.grp_list.length))
return;
if ((pStartIndex < 0) || (pStartIndex >= msg_area.grp_list[pGrpIndex].sub_list.length))
return;
if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows))
return;
if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows))
return;
if ((pStartScreenCol < 1) || (pStartScreenCol > console.screen_columns))
return;
if ((pEndScreenCol < 1) || (pEndScreenCol > console.screen_columns))
return;
// If pStartScreenRow is greater than pEndScreenRow, then swap them.
// Do the same with pStartScreenCol and pEndScreenCol.
if (pStartScreenRow > pEndScreenRow)
{
var temp = pStartScreenRow;
pStartScreenRow = pEndScreenRow;
pEndScreenRow = temp;
}
if (pStartScreenCol > pEndScreenCol)
{
var temp = pStartScreenCol;
pStartScreenCol = pEndScreenCol;
pEndScreenCol = temp;
}
// Calculate the ending index to use for the message groups array.
var endIndex = pStartIndex + (pEndScreenRow-pStartScreenRow);
if (endIndex >= msg_area.grp_list[pGrpIndex].sub_list.length)
endIndex = msg_area.grp_list[pGrpIndex].sub_list.length - 1;
var onePastEndIndex = endIndex + 1;
// Go to the specified screen row, and display the message group information.
var textWidth = pEndScreenCol - pStartScreenCol + 1;
var row = pStartScreenRow;
var msgSubIndex = pStartIndex;
for (; msgSubIndex < onePastEndIndex; ++msgSubIndex)
{
console.gotoxy(pStartScreenCol, row++);
// The 5th parameter to writeMsgSubLine() is whether or not to
// write the message sub information with highlight colors.
writeMsgSubLine(pGrpIndex, msgSubIndex, textWidth, pSubNumFieldLen,
(msgSubIndex == pSelectedIndex));
}
// If pBlankToEndRow is true and we're not at the end row yet, then
// write blank lines to the end row.
if (pBlankToEndRow)
{
var screenRow = pStartScreenRow + (endIndex - pStartIndex) + 1;
if (screenRow <= pEndScreenRow)
{
console.print("n");
var areaWidth = pEndScreenCol - pStartScreenCol + 1;
var formatStr = "%-" + areaWidth + "s";
for (; screenRow <= pEndScreenRow; ++screenRow)
{
console.gotoxy(pStartScreenCol, screenRow)
printf(formatStr, "");
}
}
}
}
// Writes a message sub-board information line (for choosing a message sub-board
// for cross-posing).
//
// Parameters:
// pGrpIndex: The index of the message group (assumed to be valid)
// pSubIndex: The index of the message sub-board within the group (assumed to be valid)
// pSubCode: The sub-board code (assumed to be valid)
// pTextWidth: The maximum text width
// pSubNumFieldLen: The length to use for the sub-board number field
// pHighlight: Boolean - Whether or not to write the line highlighted.
function writeMsgSubLine(pGrpIndex, pSubIndex, pTextWidth, pSubNumFieldLen, pHighlight)
{
if ((typeof(pGrpIndex) != "number") || (typeof(pSubIndex) != "number") ||
(typeof(pTextWidth) != "number"))
{
return;
}
// Put together the printf format string
var msgSubDescLen = pTextWidth - pSubNumFieldLen - 2;
var printfStr = "n";
if (pHighlight)
{
printfStr += gConfigSettings.genColors.crossPostChkHighlight + "%1s"
+ gConfigSettings.genColors.crossPostMsgAreaNumHighlight + "%" + pSubNumFieldLen + "d "
+ gConfigSettings.genColors.crossPostMsgAreaDescHighlight + "%-" + msgSubDescLen + "s";
}
else
{
printfStr += gConfigSettings.genColors.crossPostChk + "%1s"
+ gConfigSettings.genColors.crossPostMsgAreaNum + "%" + pSubNumFieldLen + "d "
+ gConfigSettings.genColors.crossPostMsgAreaDesc + "%-" + msgSubDescLen + "s";
}
// Write the message group information line
var subCode = msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code;
printf(printfStr, (gCrossPostMsgSubs.subCodeExists(subCode) ? CHECK_CHAR : " "), +(pSubIndex+1),
msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, msgSubDescLen));
}
// Writes a line in the edit lines array
//
// Parameters:
// pIndex: Integer - The index of the line to write. Required.
// pUseColors: Boolean - Whether or not to use the line's colors.
// Optional. If omitted, the colors will be used.
// pStart: Integer - The index in the line of where to start.
// Optional. If omitted, 0 will be used.
// pLength: Integer - The length to write. Optional. If
// omitted, the entire line will be written. <= 0 can be
// passed to write the entire string.
//
// Return value: The actual length of text written
function printEditLine(pIndex, pUseColors, pStart, pLength)
{
if (typeof(pIndex) != "number")
return 0;
var useColors = true;
var start = 0;
var length = -1;
if (typeof(pUseColors) == "boolean")
useColors = pUseColors;
if (typeof(pStart) == "number")
start = pStart;
if (typeof(pLength) == "number")
length = pLength;
// Validation of variable values
if (pIndex < 0)
pIndex = 0;
else if (pIndex >= gEditLines.length)
{
// Before returning, write spaces for the length specified so
// that the screen is updated correctly
for (var i = 0; i < length; ++i)
console.print(" ");
return length;
}
if (start < 0)
start = 0;
else if (start >= gEditLines[pIndex].text.length)
{
// Before returning, write spaces for the length specified so
// that the screen is updated correctly
for (var i = 0; i < length; ++i)
console.print(" ");
return length;
}
//if (length > (gEditLines[pIndex].text.length - start))
// length = gEditLines[pIndex].text.length - start;
var lengthWritten = 0;
if (useColors)
{
}
else
{
// Don't use the line colors
// Cases where the start index is at the beginning of the line
if (start == 0)
{
// Simplest case: start is 0 and length is negative -
// Just print the entire line.
lengthWritten = gEditLines[pIndex].text.length;
if (length <= 0)
console.print(gEditLines[pIndex].text);
else
{
var textToWrite = gEditLines[pIndex].text.substr(start, length);
console.print(textToWrite);
lengthWritten = textToWrite.length;
}
}
else
{
// Start is > 0
var textToWrite = "";
if (length <= 0)
textToWrite = gEditLines[pIndex].text.substr(start);
else
textToWrite = gEditLines[pIndex].text.substr(start, length);
console.print(textToWrite);
lengthWritten = textToWrite.length;
}
}
return lengthWritten;
}
// Lists the text replacements configured in SlyEdit using a scrollable list box.
function listTextReplacements()
{
if (gNumTxtReplacements == 0)
{
var originalCurpos = console.getxy();
writeMsgOntBtmHelpLineWithPause("nhyThere are no text replacements.", ERRORMSG_PAUSE_MS);
console.print(chooseEditColor()); // Make sure the edit color is correct
console.gotoxy(originalCurpos);
return;
}
// Calculate the text width for each column, which will then be used to
// calculate the width of the box. For the width of the box, we need to
// subtract at least 3 from the edit area with to accomodate the box's side
// borders and the space between the text columns.
var txtWidth = Math.floor((gEditWidth - 10)/2);
// In order to be able to navigate forward and backwards through the text
// replacements, we need to copy them into an array, since gTxtReplacements
// is an object and not navigable both ways. This will also allow us to easily
// know how many text replacements there are (using the .length property of
// the array).
// For speed, create this only once.
if (typeof(listTextReplacements.txtReplacementArr) == "undefined")
{
var txtReplacementObj = null;
listTextReplacements.txtReplacementArr = new Array();
for (var prop in gTxtReplacements)
{
txtReplacementObj = new Object();
txtReplacementObj.originalText = prop;
txtReplacementObj.replacement = gTxtReplacements[prop];
listTextReplacements.txtReplacementArr.push(txtReplacementObj);
}
}
// We'll want to have an object with the box dimensions.
var boxInfo = new Object();
// Construct the top & bottom border strings if they don't exist already.
if (typeof(listTextReplacements.topBorder) == "undefined")
{
listTextReplacements.topBorder = "n" + gConfigSettings.genColors.listBoxBorder
+ UPPER_LEFT_SINGLE + "n" + gConfigSettings.genColors.listBoxBorderText + "Text"
+ "n" + gConfigSettings.genColors.listBoxBorder;
for (var i = 0; i < (txtWidth-3); ++i)
listTextReplacements.topBorder += HORIZONTAL_SINGLE;
listTextReplacements.topBorder += "n" + gConfigSettings.genColors.listBoxBorderText
+ "Replacement" + "n" + gConfigSettings.genColors.listBoxBorder;
for (var i = 0; i < (txtWidth-11); ++i)
listTextReplacements.topBorder += HORIZONTAL_SINGLE;
listTextReplacements.topBorder += UPPER_RIGHT_SINGLE;
}
boxInfo.width = strip_ctrl(listTextReplacements.topBorder).length;
if (typeof(listTextReplacements.bottomBorder) == "undefined")
{
var numReplacementsStr = "Total: " + listTextReplacements.txtReplacementArr.length;
listTextReplacements.bottomBorder = "n" + gConfigSettings.genColors.listBoxBorder
+ LOWER_LEFT_SINGLE + "n" + gConfigSettings.genColors.listBoxBorderText
+ UP_ARROW + ", " + DOWN_ARROW + ", ESC/Ctrl-T/C=Close" + "n"
+ gConfigSettings.genColors.listBoxBorder;
var maxNumChars = boxInfo.width - numReplacementsStr.length - 28;
for (var i = 0; i < maxNumChars; ++i)
listTextReplacements.bottomBorder += HORIZONTAL_SINGLE;
listTextReplacements.bottomBorder += RIGHT_T_SINGLE + "n"
+ gConfigSettings.genColors.listBoxBorderText + numReplacementsStr + "n"
+ gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE;
listTextReplacements.bottomBorder += LOWER_RIGHT_SINGLE;
}
// printf format strings for the list
if (typeof(listTextReplacements.listFormatStr) == "undefined")
{
listTextReplacements.listFormatStr = "n" + gConfigSettings.genColors.listBoxItemText
+ "%-" + txtWidth + "s %-" + txtWidth + "s";
}
if (typeof(listTextReplacements.listFormatStrNormalAttr) == "undefined")
listTextReplacements.listFormatStrNormalAttr = "n%-" + txtWidth + "s %-" + txtWidth + "s";
// Limit the box height to up to 12 lines.
boxInfo.height = gNumTxtReplacements + 2;
if (boxInfo.height > 12)
boxInfo.height = 12;
boxInfo.topLeftX = gEditLeft + Math.floor((gEditWidth/2) - (boxInfo.width/2));
boxInfo.topLeftY = gEditTop + Math.floor((gEditHeight/2) - (boxInfo.height/2));
// Draw the top & bottom box borders for the list of text replacements
var originalCurpos = console.getxy();
console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY);
console.print(listTextReplacements.topBorder);
console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+boxInfo.height-1);
console.print(listTextReplacements.bottomBorder);
// Draw the side borders
console.print("n" + gConfigSettings.genColors.listBoxBorder);
for (var i = 0; i < boxInfo.height-2; ++i)
{
console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+i+1);
console.print(VERTICAL_SINGLE);
console.gotoxy(boxInfo.topLeftX+boxInfo.width-1, boxInfo.topLeftY+i+1);
console.print(VERTICAL_SINGLE);
}
// Set up some variables for the user input loop
const numItemsPerPage = boxInfo.height - 2;
const numPages = Math.ceil(listTextReplacements.txtReplacementArr.length / numItemsPerPage);
// For the horizontal location of the page number text for the box border:
// Based on the fact that there can be up to 9999 text replacements and 10
// per page, there will be up to 1000 pages of replacements. To write the
// text, we'll want to be 20 characters to the left of the end of the border
// of the box.
const pageNumTxtStartX = boxInfo.topLeftX + boxInfo.width - 20;
var pageNum = 0;
var startArrIndex = 0;
var endArrIndex = 0; // One past the last array item
var screenY = 0;
// User input loop (also drawing the list of items)
var continueOn = true;
var refreshList = true; // For screen redraw optimizations
while (continueOn)
{
if (refreshList)
{
// Write the list of items for the current page
startArrIndex = pageNum * numItemsPerPage;
endArrIndex = startArrIndex + numItemsPerPage;
if (endArrIndex > listTextReplacements.txtReplacementArr.length)
endArrIndex = listTextReplacements.txtReplacementArr.length;
screenY = boxInfo.topLeftY + 1;
for (var i = startArrIndex; i < endArrIndex; ++i)
{
console.gotoxy(boxInfo.topLeftX+1, screenY);
printf(listTextReplacements.listFormatStr,
listTextReplacements.txtReplacementArr[i].originalText.substr(0, txtWidth),
listTextReplacements.txtReplacementArr[i].replacement.substr(0, txtWidth));
++screenY;
}
// If the current screen row is below the bottom row inside the box,
// continue and write blank lines to the bottom of the inside of the box
// to blank out any text that might still be there.
while (screenY < boxInfo.topLeftY+boxInfo.height-1)
{
console.gotoxy(boxInfo.topLeftX+1, screenY);
printf(listTextReplacements.listFormatStrNormalAttr, "", "");
++screenY;
}
// Update the page number in the top border of the box.
console.gotoxy(pageNumTxtStartX, boxInfo.topLeftY);
console.print("n" + gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
printf("n" + gConfigSettings.genColors.listBoxBorderText + "Page %4d of %4d", pageNum+1, numPages);
console.print("n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
// Just for sane appearance: Move the cursor to the first character of
// the first row and make it the color for the text replacements.
console.gotoxy(boxInfo.topLeftX+1, boxInfo.topLeftY+1);
console.print(gConfigSettings.genColors.listBoxItemText);
}
// Get a key from the user (upper-case) and take action based upon it.
userInput = getUserKey(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
switch (userInput)
{
case KEY_UP:
// Go up one page
refreshList = (pageNum > 0);
if (refreshList)
--pageNum;
break;
case KEY_DOWN:
// Go down one page
refreshList = (pageNum < numPages-1);
if (refreshList)
++pageNum;
break;
// Quit for ESC, Ctrl-T, Ctrl-A, and 'C' (close).
case KEY_ESC:
case CTRL_T:
case CTRL_A:
case 'C':
refreshList = false;
continueOn = false;
break;
default:
// Unrecognized command. Don't refresh the list of the screen.
refreshList = false;
break;
}
}
// We're done listing the text replacements.
// Erase the list box rectangle by re-drawing the message text. Then, move
// the cursor back to where it was originally.
var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (originalCurpos.y-boxInfo.topLeftY);
displayMessageRectangle(boxInfo.topLeftX, boxInfo.topLeftY, boxInfo.width,
boxInfo.height, editLineIndexAtSelBoxTopRow, true);
console.gotoxy(originalCurpos);
console.print(chooseEditColor());
}
// Lets the user manage their preferences/settings.
//
// Parameters:
// pCurpos: The position of the cursor on the screen when this function is called.
// This is optional. If not specified, this function will call console.getxy()
// to get the cursor position.
// pReturnCursorToOriginalPos: Optional, boolean - Whether or not to return the cursor
// to its original position when done.
function doUserSettings(pCurpos, pReturnCursorToOriginalPos)
{
if (!gConfigSettings.allowUserSettings)
return;
const originalCurpos = (typeof(pCurpos) == "object" ? pCurpos : console.getxy());
var returnCursorWhenDone = true;
if (typeof(pReturnCursorToOriginalPos) == "boolean")
returnCursorWhenDone = pReturnCursorToOriginalPos;
// Save the user's current settings so that we can check them later to see if any
// of them changed, in order to determine whether to save the user's settings file.
var originalSettings = new Object();
for (var prop in gUserSettings)
{
if (gUserSettings.hasOwnProperty(prop))
originalSettings[prop] = gUserSettings[prop];
}
// Create the user settings box
var optBoxTitle = "Setting Enabled";
var optBoxWidth = ChoiceScrollbox_MinWidth();
//var optBoxHeight = 6;
var optBoxHeight = 9;
var optBoxStartX = gEditLeft + Math.floor((gEditWidth/2) - (optBoxWidth/2));
if (optBoxStartX < gEditLeft)
optBoxStartX = gEditLeft;
var optionBox = new ChoiceScrollbox(optBoxStartX, gEditTop+1, optBoxWidth, optBoxHeight, optBoxTitle,
gConfigSettings, false, true);
optionBox.addInputLoopExitKey(CTRL_U);
// Update the bottom help text to be more specific to the user settings box
var bottomBorderText = "\1n\1h\1c\1b, \1c\1b, \1cEnter\1y=\1bSelect\1n\1c/\1h\1btoggle, "
+ "\1cESC\1n\1c/\1hQ\1n\1c/\1hCtrl-U\1y=\1bClose";
// This one contains the page navigation keys.. Don't really need to show those,
// since the settings box only has one page right now:
/*var bottomBorderText = "\1n\1h\1c\1b, \1c\1b, \1cN\1y)\1bext, \1cP\1y)\1brev, "
+ "\1cF\1y)\1birst, \1cL\1y)\1bast, \1cEnter\1y=\1bSelect\1n\1c/\1h\1btoggle, "
+ "\1cCtrl-U\1y=\1bClose";*/
optionBox.setBottomBorderText(bottomBorderText, true, false);
// Add the options to the option box
const checkIdx = 48;
const TAGLINE_OPT_INDEX = optionBox.addTextItem("Taglines [ ]");
const QUOTE_INITIALS_OPT_INDEX = optionBox.addTextItem("Quote with author's initials [ ]");
const QUOTE_INITIALS_INDENT_OPT_INDEX = optionBox.addTextItem("Indent quote lines containing initials [ ]");
const TRIM_QUOTE_SPACES_OPT_INDEX = optionBox.addTextItem("Trim spaces from quote lines [ ]");
const AUTO_SIGN_OPT_INDEX = optionBox.addTextItem("Auto-sign messages [ ]");
const SIGN_REAL_ONLY_FIRST_NAME_OPT_INDEX = optionBox.addTextItem(" When using real name, use only first name [ ]");
const SIGN_EMAILS_REAL_NAME_OPT_INDEX = optionBox.addTextItem(" Sign emails with real name [ ]");
if (gUserSettings.enableTaglines)
optionBox.chgCharInTextItem(TAGLINE_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.useQuoteLineInitials)
optionBox.chgCharInTextItem(QUOTE_INITIALS_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.indentQuoteLinesWithInitials)
optionBox.chgCharInTextItem(QUOTE_INITIALS_INDENT_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.trimSpacesFromQuoteLines)
optionBox.chgCharInTextItem(TRIM_QUOTE_SPACES_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.autoSignMessages)
optionBox.chgCharInTextItem(AUTO_SIGN_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.autoSignRealNameOnlyFirst)
optionBox.chgCharInTextItem(SIGN_REAL_ONLY_FIRST_NAME_OPT_INDEX, checkIdx, CHECK_CHAR);
if (gUserSettings.autoSignEmailsRealName)
optionBox.chgCharInTextItem(SIGN_EMAILS_REAL_NAME_OPT_INDEX, checkIdx, CHECK_CHAR);
// Create an object containing toggle values (true/false) for each option index
var optionToggles = new Object();
optionToggles[TAGLINE_OPT_INDEX] = gUserSettings.enableTaglines;
optionToggles[QUOTE_INITIALS_OPT_INDEX] = gUserSettings.useQuoteLineInitials;
optionToggles[QUOTE_INITIALS_INDENT_OPT_INDEX] = gUserSettings.indentQuoteLinesWithInitials;
optionToggles[TRIM_QUOTE_SPACES_OPT_INDEX] = gUserSettings.trimSpacesFromQuoteLines;
optionToggles[AUTO_SIGN_OPT_INDEX] = gUserSettings.autoSignMessages;
optionToggles[SIGN_REAL_ONLY_FIRST_NAME_OPT_INDEX] = gUserSettings.autoSignRealNameOnlyFirst;
optionToggles[SIGN_EMAILS_REAL_NAME_OPT_INDEX] = gUserSettings.autoSignEmailsRealName;
// Set up the enter key in the box to toggle the selected item.
optionBox.setEnterKeyOverrideFn(function(pBox) {
var itemIndex = pBox.getChosenTextItemIndex();
if (itemIndex > -1)
{
// If there's an option for the chosen item, then update the text on the
// screen depending on whether the option is enabled or not.
if (optionToggles.hasOwnProperty(itemIndex))
{
// Toggle the option and refresh it on the screen
optionToggles[itemIndex] = !optionToggles[itemIndex];
if (optionToggles[itemIndex])
optionBox.chgCharInTextItem(itemIndex, checkIdx, CHECK_CHAR);
else
optionBox.chgCharInTextItem(itemIndex, checkIdx, " ");
optionBox.refreshItemCharOnScreen(itemIndex, checkIdx);
// Toggle the setting for the user in global user setting object.
switch (itemIndex)
{
case TAGLINE_OPT_INDEX:
gUserSettings.enableTaglines = !gUserSettings.enableTaglines;
break;
case QUOTE_INITIALS_OPT_INDEX:
gUserSettings.useQuoteLineInitials = !gUserSettings.useQuoteLineInitials;
break;
case QUOTE_INITIALS_INDENT_OPT_INDEX:
gUserSettings.indentQuoteLinesWithInitials = !gUserSettings.indentQuoteLinesWithInitials;
break;
case TRIM_QUOTE_SPACES_OPT_INDEX:
gUserSettings.trimSpacesFromQuoteLines = !gUserSettings.trimSpacesFromQuoteLines;
break;
case AUTO_SIGN_OPT_INDEX:
gUserSettings.autoSignMessages = !gUserSettings.autoSignMessages;
break;
case SIGN_REAL_ONLY_FIRST_NAME_OPT_INDEX:
gUserSettings.autoSignRealNameOnlyFirst = !gUserSettings.autoSignRealNameOnlyFirst;
break;
case SIGN_EMAILS_REAL_NAME_OPT_INDEX:
gUserSettings.autoSignEmailsRealName = !gUserSettings.autoSignEmailsRealName;
break;
default:
break;
}
}
}
}); // Option box enter key override function
// Display the option box and have it do its input loop
var boxRetObj = optionBox.doInputLoop(true);
// If the user changed any of their settings, then save the user settings.
// If the save fails, then output an error message.
var settingsChanged = false;
for (var prop in gUserSettings)
{
if (gUserSettings.hasOwnProperty(prop))
{
settingsChanged = (originalSettings[prop] != gUserSettings[prop]);
if (settingsChanged)
break;
}
}
if (settingsChanged)
{
if (!WriteUserSettingsFile(gUserSettings))
writeMsgOntBtmHelpLineWithPause("\1n\1y\1hFailed to save settings!\1n", ERRORMSG_PAUSE_MS);
}
// We're done, so erase the option box.
var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (originalCurpos.y-optionBox.dimensions.topLeftY);
displayMessageRectangle(optionBox.dimensions.topLeftX, optionBox.dimensions.topLeftY,
optionBox.dimensions.width, optionBox.dimensions.height,
editLineIndexAtSelBoxTopRow, true);
if (returnCursorWhenDone)
console.gotoxy(originalCurpos);
}
// Allows the user to select a tagline. Returns an object with the following
// properties:
// taglineWasSelected: Boolean - Whether or not a tag line was selected
// tagline: String - The tag line that was selected
function doTaglineSelection()
{
var retObj = {
taglineWasSelected: false,
tagline: ""
};
// Read the tagline file
var taglines = readTxtFileIntoArray(gConfigSettings.tagLineFilename, true, true, 5000);
if (taglines.length == 0)
return retObj;
// If the configuration option to shuffle the taglines is enabled, then
// shuffle them.
if (gConfigSettings.shuffleTaglines)
shuffleArray(taglines);
// Create the list box for the taglines. Make the box up to 14 lines tall.
var boxHeight = (taglines.length > 12 ? 14 : taglines.length+2);
var boxTopRow = gEditTop + Math.floor((gEditHeight/2) - (boxHeight/2));
var taglineBox = new ChoiceScrollbox(gEditLeft, boxTopRow, gEditWidth, boxHeight, "Taglines", gConfigSettings, true, false);
var bottomBorderText = "nhcb, cb, cNy)bext, cPy)brev, "
+ "cFy)birst, cLy)bast, cHOMEb, cENDb, cEntery=bSelect, "
+ "cRy)bandom, cESCnc/hcQy=bEnd";
taglineBox.setBottomBorderText(bottomBorderText, false, false);
// Add R as an input loop exit key, to choose a random tagline.
taglineBox.addInputLoopExitKey("R");
taglineBox.addInputLoopExitKey("r");
// Set the tagline item array in the list box. Don't strip control characters
// because we've already done that when we read the file.
taglineBox.setItemArray(taglines, false);
// Let the user choose a tagline
var taglineRetObj = taglineBox.doInputLoop(true);
retObj.taglineWasSelected = taglineRetObj.itemWasSelected;
if (retObj.taglineWasSelected)
retObj.tagline = taglineRetObj.selectedItem;
// If the R key was pressed, then choose a random tagline.
else if ((taglineRetObj.lastKeypress == "R") || (taglineRetObj.lastKeypress == "r"))
{
retObj.tagline = taglines[random(taglines.length)];
retObj.taglineWasSelected = true;
}
// If a tagline was selected, then add the tagline prefix in front of it, and
// also quote the tagline if the option to do so is enabled.
if (retObj.taglineWasSelected)
{
if (gConfigSettings.taglinePrefix.length > 0)
retObj.tagline = gConfigSettings.taglinePrefix + retObj.tagline;
// If the option to quote taglines is enabled, then do it.
if (gConfigSettings.quoteTaglines)
retObj.tagline = "\"" + retObj.tagline + "\"";
}
return retObj;
}
// Sets the quote prefix, gQuotePrefix (the text to use for prefixing quote lines).
function setQuotePrefix()
{
// To minimize disk reads in case the user changes their preference for author
// initials in quote lines, get the from name of the message being replied to
// and store it in a persistent variable. This is done at the beginning of
// this function whether or not the user wants author initials in quote lines
// to ensure we get the correct name in case anything in the message base
// changes after this function is first called.
if (!setQuotePrefix.curMsgFromName)
setQuotePrefix.curMsgFromName = getFromNameForCurMsg(gMsgAreaInfo);
gQuotePrefix = " > "; // The default quote prefix
// If we're configured to use poster's initials in the
// quote lines, then do it.
if (gUserSettings.useQuoteLineInitials)
{
// For the name to use for quote line initials:
// If posting in a message sub-board, get the author's name from the
// header of the current message being read in the sub-board (in
// case the user changes the "To" name). Otherwise (if not posting in
// a message sub-board), use the gToName value read from the drop file.
// Remove any leading, multiple, or trailing spaces.
var quotedName = "";
if (postingInMsgSubBoard(gMsgArea))
{
quotedName = trimSpaces(setQuotePrefix.curMsgFromName, true, true, true);
if (quotedName.length == 0)
quotedName = trimSpaces(gToName, true, true, true);
}
else
quotedName = trimSpaces(gToName, true, true, true);
// If configured to indent quote lines w/ initials with
// a space, then do it.
gQuotePrefix = "";
if (gUserSettings.indentQuoteLinesWithInitials)
gQuotePrefix = " ";
// Use the initials or first 2 characters from the
// quoted name for gQuotePrefix.
var spaceIndex = quotedName.indexOf(" ");
if (spaceIndex > -1) // If a space exists, use the initials
{
gQuotePrefix += quotedName.charAt(0).toUpperCase();
if (quotedName.length > spaceIndex+1)
gQuotePrefix += quotedName.charAt(spaceIndex+1).toUpperCase();
gQuotePrefix += "> ";
}
else // A space doesn't exist; use the first 2 letters
gQuotePrefix += quotedName.substr(0, 2) + "> ";
}
}
// Writes an array of strings to the message tagline file (editor.tag in the node's
// temp directory).
//
// Parameters:
// pTagline: The tagline to write to the file (a string)
// pTaglineFilename: The full path & filename of the tagline file
//
// Return value: Boolean - Whether or not the tagline file was successfully written.
// If pTaglineFilename is empty or is not a string, or if pTagline is
// not a string, this will return false. If pTagline is empty, then
// this will return true.
function writeTaglineToMsgTaglineFile(pTagline, pTaglineFilename)
{
// Sanity checking
if ((typeof(pTagline) != "string") || (typeof(pTaglineFilename) != "string"))
return false;
if (pTaglineFilename.length == 0)
return false;
if (pTagline.length == 0)
return true;
var wroteTaglineFile = false;
var taglineFile = new File(pTaglineFilename);
if (taglineFile.open("w"))
{
// If we wanted to wrap the tagline to 79 characters, it could be
// done as follows:
/*
var taglineArray = new Array();
taglineArray.push(""); // Leave a blank line between the signature & tagline
taglineArray.push(pTagline);
wrapTextLines(taglineArray, 0, taglineArray.length, 79);
// Write the tagline strings to the file
for (var i = 0; i < taglineArray.length; ++i)
taglineFile.writeln(taglineArray[i]);
*/
// Write the tagline to the file, with a blank line before it for spacing.
taglineFile.writeln("");
taglineFile.writeln(pTagline);
taglineFile.close();
wroteTaglineFile = true;
}
return wroteTaglineFile;
}
// Writes some text over the bottom help line, with a pause before erasing the
// text and refreshing the bottom help line.
//
// Parameters:
// pMsg: The text to write
// pPauseMS: The pause (in milliseconds) to wait while displaying the message
function writeMsgOntBtmHelpLineWithPause(pMsg, pPauseMS)
{
// Write the message with the pause, then refresh the help line on the
// bottom of the screen.
writeWithPause(1, console.screen_rows, pMsg, pPauseMS);
fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
}
// Gets the user's alias/name to use for auto-signing the message.
//
// Parameters:
// pSubCode: The sub-board code ("mail" for email)
// pRealNameOnlyFirst: Whether or not to use the user's first name only when using their real name
// pRealNameForEmail: Whether or not to use the user's real name for email
//
// Return value: The user's name to use for auto-signing
function getSignName(pSubCode, pRealNameOnlyFirst, pRealNameForEmail)
{
var useRealName = false;
if (pSubCode.toUpperCase() == "MAIL")
useRealName = pRealNameForEmail;
else
{
var msgbase = new MsgBase(pSubCode);
if (msgbase.open())
{
useRealName = ((msgbase.cfg.settings & SUB_NAME) == SUB_NAME);
msgbase.close();
}
}
var signName = "";
if (useRealName)
{
signName = trimSpaces(user.name, true, false, true);
if (pRealNameOnlyFirst)
{
var spacePos = signName.indexOf(" ");
if (spacePos > -1)
signName = signName.substr(0, spacePos);
}
}
else
signName = trimSpaces(user.alias, true, false, true);
return signName;
}