From 8eec123ef08bce07312a8dadef00612d005780f5 Mon Sep 17 00:00:00 2001 From: nightfox <> Date: Sat, 22 Dec 2012 07:37:47 +0000 Subject: [PATCH] Adding SlyEdit: This is a full-screen message editor for Synchronet that mimics the look & feel of IceEdit and DCTEdit. --- docs/SlyEdit_ReadMe.txt | 600 +++++++ exec/SlyEdit.js | 3561 ++++++++++++++++++++++++++++++++++++++ exec/SlyEdit_DCTStuff.js | 1432 +++++++++++++++ exec/SlyEdit_IceStuff.js | 783 +++++++++ exec/SlyEdit_Misc.js | 1636 +++++++++++++++++ 5 files changed, 8012 insertions(+) create mode 100644 docs/SlyEdit_ReadMe.txt create mode 100644 exec/SlyEdit.js create mode 100644 exec/SlyEdit_DCTStuff.js create mode 100644 exec/SlyEdit_IceStuff.js create mode 100644 exec/SlyEdit_Misc.js diff --git a/docs/SlyEdit_ReadMe.txt b/docs/SlyEdit_ReadMe.txt new file mode 100644 index 0000000000..ca7137a040 --- /dev/null +++ b/docs/SlyEdit_ReadMe.txt @@ -0,0 +1,600 @@ + SlyEdit message editor + Version 1.16 + Release date: 2012-12-21 + + by + + Eric Oulashin + Sysop of Digital Distortion BBS + BBS internet address: digdist.bbsindex.com + Email: eric.oulashin@gmail.com + + + +This file describes SlyEdit, a message editor for Synchronet. +Note: For sysops who already have a previous version of SlyEdit +installed and are upgrading to this version, please see the file +Upgrading.txt. + +Contents +======== +1. Disclaimer +2. Introduction +3. Installation & Setup +4. Features +5. Configuration file +6. Ice-style Color Theme Settings +7. DCT-style Color Theme Settings +8. Revision History (change log) + + +1. Disclaimer +============= +Although I have tested SlyEdit, I cannot guarantee that it is 100% bug free or +will work as expected in all environments in all cases. That said, I hope you +find SlyEdit useful and enjoy using it. + + +2. Introduction +=============== +SlyEdit is a message editor that I wrote in JavaScript for Synchronet, which +can mimic the look and feel of IceEdit or DCT Edit. SlyEdit also supports +customization of colors via theme files. + +The motivation for creating this was that IceEdit and DCT Edit were always +my two favorite BBS message editors, but in a world where 32-bit (and 64-bit) +Windows and *nix platforms are common, 16-bit DOS emulation is required to +run IceEdit and DCT Edit, which results in slow (and sometimes unreliable) +operation. Since SlyEdit is written in JavaScript, it is faster and does +not require a 16-bit virtual DOS machine. Also, being written in JavaScript, +it should run on any platform where Synchronet runs, so all Synchronet sysops +can use it. + +SlyEdit will recognize the user's terminal size and set up the screen +accordingly. The width of the edit area will always be 80 characters; however, +an increased terminal size will provide more room for message information to be +displayed, as well as a taller edit area (if the terminal size is taller). + +Thanks goes out to Nick of Lightning BBS (lightningbbs.dyndns.org) for testing. + +3. Installation & Setup +======================= +These are the steps for installation: + 1. Unzip the archive. If you're viewing this file, then you've probably + already done this. :) + 2. There are 2 ways SlyEdit's files can be copied onto your Synchronet system: + 1. Copy the JavaScript files into your sbbs/exec directory and the .cfg files + into your sbbs/ctrl directory + 2. Copy all files together into their own directory of your choice + 3. Set up SlyEdit on your BBS with Synchronet's configration program (SCFG). + +SlyEdit can be set to mimic IceEdit or DCT Edit via a command-line parameter. +The values for this parameter are as follows: + ICE: Mimic the IceEdit look & feel + DCT: Mimic the DCT Edit look & feel + RANDOM: Randomly select either the IceEdit or DCT Edit look & feel + +To add SlyEdit to Synchronet's list of external editors, run Synchronet's +configuration program (SCFG) and select "External Programs", and then +"External Editors". The following describes setting up SlyEdit using the +ICE parameter for IceEdit emulation: + 1. Scroll down to the empty slot in the editor list and press Enter to + select it. + 2. For the external editor name, enter "SlyEdit (Ice style)" (without the quotes) + (or similar, depending on your personal preference) + 3. For the internal code, use SLYEDICE (or whatever you want, depending on your + personal preference) + 4. Press Enter to select and edit the new entry. Asuming that the .js files + are in the sbbs/exect directory, the settings should be as follows: + Command line: ?SlyEdit.js %f ICE + Access requirement string: ANSI + Intercept standard I/O: No + Native (32-bit) Executable: No + Use Shell to Execute: No + Quoted Text: All + Editor Information Files: QuickBBS MSGINF/MSGTMP + Expand Line Feeds to CRLF: Yes + Strip FidoNet Kludge Lines: No + BBS Drop File Type: None + After you've added SlyEdit, your Synchronet configuration window should look + like this: + +[�][?]--------------------------------------------------------------+ + � SlyEdit (Ice style) Editor � + �--------------------------------------------------------------------� + � �Name SlyEdit (Ice style) � + � �Internal Code SLYEDICE � + � �Remote Command Line ?SlyEdit.js %f ICE � + � �Access Requirements ANSI � + � �Intercept Standard I/O No � + � �Native (32-bit) Executable No � + � �Use Shell to Execute No � + � �Quoted Text All � + � �Editor Information Files QuickBBS MSGINF/MSGTMP � + � �Expand Line Feeds to CRLF Yes � + � �Strip FidoNet Kludge Lines No � + � �BBS Drop File Type None � + +--------------------------------------------------------------------+ + + For DCT Edit mode, use DCT in place of ICE on the command line. For + random mode, use RANDOM in place of ICE. + + Note that if you placed the files in a different directory, then the + command line should include the full path to SlyEdit.js. For example, + if SlyEdit was placed in sbbs/xtrn/SlyEdit, then the command line would + be /BBS/sbbs/xtrn/DigDist/SlyEdit/SlyEdit.js %f ICE + + +4. Features +=========== +As mentioned earlier, SlyEdit is can mimic the look & feel of IceEdit or +DCT Edit. It also has the following features: +- Text search: Allows the user to search for text in the message. If + the text is found, the message area will scroll to it, and it will be + highlighted. Repeated searches for the same text will look for the + next occurrance of that text. +- Navigation: Page up/down, home/end of line, and arrow keys +- Slash commands (at the start of the line): + /A: Abort + /S: Save + /Q: Quote message +- Sysops can import a file (from the BBS machine) into the message +- Sysops can export the current message to a file (on the BBS machine) +- Configuration file with behavior and color settings. The configuration + settings are fairly limited right now, and I plan to add more color + settings in future versions. See section 4 (Configuration File) for + more information. + +The following is a summary of the keyboard shortcuts (from SlyEdit's command +help screen): + +Help keys Slash commands (@ line start) +--------- ----------------------------- +Ctrl-G : General help | /A : Abort +Ctrl-P : Command key help | /S : Save +Ctrl-R : Program information | /Q : Quote message + +Command/edit keys +----------------- +Ctrl-A : Abort message | Ctrl-W : Page up +Ctrl-Z : Save message | Ctrl-S : Page down +Ctrl-Q : Quote message | Ctrl-N : Find text +Insert/Ctrl-I: Toggle insert/overwrite mode | ESC : Command menu +Ctrl-O : Import a file | Ctrl-X : Export to file +Ctrl-D : Delete line + + +5. Configuration file +===================== +The configuration file, SlyEdit.cfg, is split up into 3 sections - +Behavior, Ice colors, and DCT colors. These sections are designated +by [BEHAVIOR], [ICE_COLORS], and [DCT_COLORS], respectively. These +settings are described below: + +Behavior settings +----------------- +Setting Description +------- ----------- +displayEndInfoScreen Whether or not to display the info + screen when SlyEdit exits. Valid values + are true and false. If this option is + not specified, this feature will be + enabled by default. + +userInputTimeout Whether or not to use an input timeout + for users. Valid values are true and + false. Note: The input timeout is not + used for sysops. If this option is not + specified, this feature will be enabled + by default. + +inputTimeoutMS The amount of time (in milliseconds) to + use for the input timeout. If this option + is not specified, this option will default + to 300000. + +reWrapQuoteLines Whether or not to re-wrap quote lines. Valid + values are true and false, and this feature + is enabled by default. With this feature + enabled, SlyEdit will re-wrap quote lines + to still be complete and readable after the + quote prefix character is added to the front + of the quote lines. SlyEdit is able to + recognize quote lines beginning with > + or 2 letters and a > (such as EO>). If this + feature is disabled, quote lines will simply + be trimmed to make room for the quote prefix + character to be added to the front. + +add3rdPartyStartupScript Add a 3rd-party JavaScript script to execute + (via loading) upon startup of SlyEdit. The + parameter must specify the full path & filename + of the JavaScript script. For example (using + the excellent Desafortunadamente add-on by Art + of Fat Cats BBS): + add3rdPartyStartupScript=D:/BBS/sbbs/xtrn/desafortunadamente/desafortunadamente.js + +addJSOnStart Add a JavaScript command to run on startup. Any + commands added this way will be executed after + 3rd-party scripts are loaded. + Example (using the excellent Desafortunadamente + add-on by Art of Fat Cats BBS): + addJSOnStart=fortune_load(); + +add3rdPartyExitScript Add a 3rd-party JavaScript script to execute + (via loading) upon exit of SlyEdit. The + parameter must specify the full path & filename + of the JavaScript script. + +addJSOnExit Add a JavaScript command to run on exit. + Example (don't actually do this): + addJSOnStart=console.print("Hello\r\n\1p"); + +Ice colors +---------- +Setting Description +------- ----------- +ThemeFilename The name of the color theme file to use. + Note: Ice-style theme settings are described + in Section 5: Ice-style Color Theme Settings. + If no theme file is specified, then default + colors will be used. + +DCT colors +---------- +Setting Description +------- ----------- +ThemeFilename The name of the color theme file to use. + Note: DCT-style theme settings are described + in Section 6: DCT-style Color Theme Settings. + If no theme file is specified, then default + colors will be used. + +The color theme files are plain text files that can be edited with a +text editor. + + +6. Ice-style Color Theme Settings +================================= +The following options are valid for Ice-style theme files: +---------------------------------------------------------- +TextEditColor The color for the message text + +QuoteLineColor The color for quoted lines in the message + +BorderColor1 The first color to use for borders + (for alternating border colors) + +BorderColor2 The other color to use for borders + (for alternating border colors) + +KeyInfoLabelColor The color to use for key information + labels (displayed on the bottom border) + +TopInfoBkgColor The color to use for the background in + the informational area at the top + +TopLabelColor The color to use for informational labels + in the informational area at the top + +TopLabelColonColor The color to use for the colons (:) in the + informational area at the top + +TopToColor The color to use for the "To" name in the + informational area at the top + +TopFromColor The color to use for the "From" name in the + informational area at the top + +TopSubjectColor The color to use for the subject in the + informational area at the top + +TopTimeColor The color to use for the time left in the + informational area at the top + +TopTimeLeftColor The color to use for the time left in the + informational area at the top + +EditMode The color to use for the edit mode text + +QuoteWinText The color for non-highlighted text in + the quote window + +QuoteLineHighlightColor The color for highlighted text in the + quote window + +QuoteWinBorderTextColor The color for the quote window borders + +; Colors for the multi-choice options +SelectedOptionBorderColor The color to use for the borders around + text for selected multi-choice options + +SelectedOptionTextColor The color to use for the text for selected + multi-choice options + +UnselectedOptionBorderColor The color to use for the borders around + text for unselected multi-choice options + +UnselectedOptionTextColor The color to use for the text for unselected + multi-choice options + + +7. DCT-style Color Theme Settings +================================= +The following options are valid for DCT-style theme files: +---------------------------------------------------------- +TextEditColor The color for the message text + +QuoteLineColor The color for quoted lines in the message + +TopBorderColor1 The first color to use for the + top borders (for alternating border + colors) + +TopBorderColor2 The other color to use for the + top borders (for alternating border + colors) + +EditAreaBorderColor1 The first color to use for the + edit area borders (for alternating border + colors) + +EditAreaBorderColor2 The other color to use for the + edit area borders (for alternating border + colors) + +EditModeBrackets The color to use for the square brackets + around the edit mode text that appears + in the bottom border (the [ and ] around + the "INS"/"OVR") + +EditMode The color to use for the edit mode text + +TopLabelColor The color to use for the informational labels + in the informational area at the top + +TopLabelColonColor The color to use for the colons (:) in the + informational area at the top + +TopFromColor The color to use for the "From" name in the + informational area at the top + +TopFromFillColor The color to use for the filler dots in the + "From" name in the informational area at the top + +TopToColor The color to use for the "To" name in the + informational area at the top + +TopToFillColor The color to use for the filler dots in the + "To" name in the informational area at the top + +TopSubjColor The color to use for the subject in the informational + area at the top + +TopSubjFillColor The color to use for the filler dots in the subject + in the informational area at the top + +TopAreaColor The color to use for the "Area" text in the + informational area at the top + +TopAreaFillColor The color to use for the filler dots in the "Area" + field in the informational area at the top + +TopTimeColor The color to use for the "Time" text in the + informational area at the top + +TopTimeFillColor The color to use for the filler dots in the "Time" + field in the informational area at the top + +TopTimeLeftColor The color to use for the "Time left" text in the + informational area at the top + +TopTimeLeftFillColor The color to use for the filler dots in the "Time left" + field in the informational area at the top + +TopInfoBracketColor The color to use for the square brackets in the + informational area at the top + +QuoteWinText The color for non-highlighted text in + the quote window + +QuoteLineHighlightColor The color for highlighted text in the + quote window + +QuoteWinBorderTextColor The color to use for the text in the quote window + borders + +QuoteWinBorderColor The color to use for the quote window borders + +BottomHelpBrackets The color to use for the brackets displayed in + the line of help text at the bottom + +BottomHelpKeys The color to use for the key names written in + the line of help text at the botom + +BottomHelpFill The color to use for the filler dots in the line of + help text at the bottom + +BottomHelpKeyDesc The color to use for the key descriptions in the + line of help text at the bottom + +TextBoxBorder The color to use for text box borders (i.e., the + abort confirmation prompt) + +TextBoxBorderText The color to use for text in the borders of text + boxes (i.e., the abort confirmation prompt) + +TextBoxInnerText The color to use for text inside text boxes + +YesNoBoxBrackets The color to use for the square brackets used for + yes/no confirmation prompt boxes + +YesNoBoxYesNoText The color to use for the actual "Yes"/"No" text in + yes/no confirmation prompt boxes + +SelectedMenuLabelBorders The color to use for the border characters for the + labels of currently active drop-down menus + +SelectedMenuLabelText The color to use for the text for the labels of + currently active drop-down menus + +UnselectedMenuLabelText The color to use for the text for the labels of + inactive drop-down menus + +MenuBorders The color to use for the drop-down menu borders + +MenuSelectedItems The color to use for selected items on the drop-down + menus + +MenuUnselectedItems The color to use for unselected items on the + drop-down menus + +MenuHotkeys The color to use for the hotkey characters in the + menu items on the drop-down menus + + +8. Revision History (change log) +================================ +Version Date Description +------- ---- ----------- +1.16 2012-12-21 Updated to look for the .cfg files first in + the sbbs/ctrl directory, and if they're not + found there, assume they're in the same + directory as the .js files. +1.15 2012-04-22 Improved quoting with the ability to re-wrap quote lines + so that they are complete but still look good when + quoted. SlyEdit recognizes quote lines beginning with > + or 1 or 2 intials followed by a >. The configuration + option "splitLongQuoteLines" was replaced by + "reWrapQuoteLines", and it is enabled by default. + Also, added the following configuration options and capabilities: + add3rdPartyStartupScript: + Add a 3rd-party JavaScript script to execute + (via loading) upon startup of SlyEdit. The + parameter must specify the full path & filename + of the JavaScript script. For example (using + the excellent Desafortunadamente add-on by Art + of Fat Cats BBS): + add3rdPartyStartupScript=D:/BBS/sbbs/xtrn/desafortunadamente/desafortunadamente.js + addJSOnStart: + Add a JavaScript command to run on startup. Any + commands added this way will be executed after + 3rd-party scripts are loaded. + Example (using the excellent Desafortunadamente + add-on by Art of Fat Cats BBS): + addJSOnStart=fortune_load(); + add3rdPartyExitScript: + Add a 3rd-party JavaScript script to execute + (via loading) upon exit of SlyEdit. The + parameter must specify the full path & filename + of the JavaScript script. + addJSOnExit: + Add a JavaScript command to run on exit. + Example (don't actually do this): + addJSOnStart=console.print("Hello\n\1p"); +1.145 2011-02-07 The time on the screen will now be updated. The + time is checked every 5 keystrokes and will be + updated on the screen when it changes. +1.144 2010-11-21 Minor bug fix: In DCT mode, if the top or bottom border + of one of the menus or the abort confirmation box is + on the first or last line of text on the screen and the + text line ends before the box border ends, the box border + is now fully erased when it disappears. +1.143 2010-06-19 Minor bug fix: When typing an entire line of text that + doesn't have any spaces, the last character was being + discarded when wrapping to the next line. +1.142 2010-02-04 Minor bug fix: When reading quote lines and the + splitLongQuoteLines is disabled, it will no longer + (incorrectly) insert "null" as the last quote line + (as is done when the splitLongQuoteLines option is + enabled). +1.141 2010-01-23 Bug fix: The screen wouldn't update when pressing the Delete + key on a blank line, which would remove the line. +1.14 2010-01-19 Bug fix: The screen wouldn't update when pressing the Delete + key at the end of a line (specifically, with a blank line + below it followed by a non-blank line). + Also, updated to allow combining quote lines by pressing + the Delete key at the end of a quote line. +1.131 2010-01-10 Minor update - The option for splitting long quote + lines, which was enabled by default in the previous + version, is now disabled by default in this verison. + It seems that there may be more sysops that don't + like it than those who do like it. + The code in the .js files in this version is also + a little more refactored. +1.13 2010-01-04 Includes the ability to split up quote lines that + are too long, rather than truncating them. This + is an option that can be toggled and is enabled by + default. + Includes several bug fixes related to message editing + (i.e., such as word wrapping for the last word on a + line) and other behind-the-scenes bug fixes. + Efficiency of screen updates has been improved somewhat + in this release. This is more noticeable on slower + connections. +1.12 2009-12-14 Behavior change: Now never removes any spaces from + the beginning of a line when the user presses enter + at the beginning of a line. +1.11 2009-12-10 Added the ability to customize the quote line color + in the color theme files (QuoteLineColor). + Fixed a bug where the text color temporarily went + back to default (not using the customized text + color) when moving to a new line when typing a + message. + Updated to (hopefully) fixed a bug that could + cause the script to abort when adding a line to + the message in rare circumstances. +1.10 2009-12-03 Added support for customizable color themes. + Fixed a couple of text editing bugs/annoyances. +1.08 2009-11-10 Changed the way the message is saved back to the + way SlyEdit was saving in 1.06 and earlier, as + this simplifies the code. The "Expand Line Feeds + to CRLF" option needs to be enabled in SCFG for + messages to be saved properly in all platforms. + Added configuration options to enable/disable the + user input timeout, and to specify the input timeout + time (in MS). + The sysop is now always exempt from the input timeout. + Also, started to work on improving the efficiency + of refreshing the message area. +1.07 2009-10-23 Bug fix: Changed how the end-of-line newline + characters are written to the message file so + that the message text lines are saved correctly + in Linux. The bug in previous versions was causing + messages going across certain networks to lose their + end-of-line characters so that text lines weren't + terminated where they were supposed to be. Thanks + goes to Tracker1 (sysop of The Roughnecks BBS) for + the tip of how to fix this. + New feature: Configuration file with settings + for whether or not to display the ending info + screen, as well as quote window colors for both + Ice and DCT modes. +1.06 2009-09-12 Bug fix: Updated the way it checks for printable + characters. This should fix the problem I've seen + with some BBSs where it wouldn't allow typing an + upper-case letter. +1.05 2009-08-30 Bug fix: When editing an existing message, the cursor + would be at the bottom of the edit area, and it would + appear stuck there, unable to move up. This has been + fixed - Now the cursor is always initially placed at + the start of the edit area. + Bug fix: When saving a message, blank lines are now + removed from the end of a message before saving. +1.04 2009-08-27 Bug fix: When wrapping a text line, it would place + individual words on lines by themselves, due to a + change in 1.03. This has been fixed. +1.03 2009-08-27 Bug fix: With a small message (less than one screenful), + Ctrl-S (page down) now doesn't crash SlyEdit. + Bug fix: When typing and the end of the line and it has to + wrap the word to the next line, it now always inserts a new + line below the current line, pushing the rest of the message + down. +1.02 2009-08-26 Bug fix: Now prevents invalid text lines from quotes.txt or + the message file from being used. +1.01 2009-08-23 Bug fix: Blank edit lines would be removed + when they weren't supposed to if the user + used the /S or /A commands on a line that + wasn't the last line. +1.00 2009-08-22 First public release +0.99 2009-08-13- Test release. Finishing up features, testing, + 2009-08-20 and fixing bugs before general release. \ No newline at end of file diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js new file mode 100644 index 0000000000..126b2fe380 --- /dev/null +++ b/exec/SlyEdit.js @@ -0,0 +1,3561 @@ +/* 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... Note: I've started working on Ctrl-A + * color selection (for message text colorization), but that feature + * is not complete yet. + * 2010-11-19 Eric Oulashin For version 1.144: Added a parameter to + * displayMessageRectangle() to optionally + * enable the following behavior: + * When erasing the menu box, if the box width + * is longer than the text that was written, + * then it will output spaces to clear the rest + * of the line to erase the rest of the box line. + * 2010-11-21 Eric Oulashin Version 1.144 + * Bug fix release: In DCT mode, now erases the + * whole upper/lower border of the menu & abort + * confirm boxes on the first/last line of the + * message text when the box disappears. + * 2011-02-03 Eric Oulashin Started updating to update the time on the + * screen periodically. + * 2011-02-07 Eric Oulashin Version 1.145 + * Updated to update the time on the screen + * when it changes. Checks every 5 keystrokes. + * 2012-02-18 Eric Oulashin Version 1.146 beta X + * Working on enhanced quote line wrapping + * 2012-03-31 Eric Oulashin Added the ability to run 3rd-party JavaScript + * scripts upon startup & exit (via load()) and + * to run extra JavaScript commands upon startup + * & exit (configurable in SlyEdit.cfg). + * 2012-04-01 Eric Oulashin Version 1.146 beta 4 + * Worked on wrapping quote lines smarter: Added + * handling for quotes with 1 or 2 initials (i.e., + * "EO>" or "E>"). + * 2012-04-11 Eric Oulashin Fixed a bug with quote line wrapping where it + * wasn't properly dealing with quote lines that + * were blank after the quote text. + * 2012-04-22 Eric Oulashin Version 1.15 + * Quoting update looks good, so I'm releasing + * this version. + * 2012-12-21 Eric Oulashin Version 1.16 + * Updated to look for the .cfg files first in + * the sbbs/ctrl directory, and if they're not + * found there, assume they're in the same + * directory as the .js files. + */ + +/* 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(/[\/\\][^\/\\]*$/,'')); + +// Load sbbsdefs.js and SlyEdit's misc. defs first +load("sbbsdefs.js"); +load(gStartupPath + "SlyEdit_Misc.js"); + +// Load program settings from SlyEdit.cfg +var gConfigSettings = ReadSlyEditConfigFile(); +// 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]); + +// Now, load the DCT & Ice scripts (they use settings gConfigSettings) +load(gStartupPath + "SlyEdit_DCTStuff.js"); +load(gStartupPath + "SlyEdit_IceStuff.js"); + +const EDITOR_PROGRAM_NAME = "SlyEdit"; + +// 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("n"); + console.crlf(); + console.print("nhyi* Warning:nhw " + EDITOR_PROGRAM_NAME); + console.print(" " + "requires version g3.14w or"); + console.crlf(); + console.print("higher of Synchronet. This BBS is using version g"); + console.print(system.version + "w. 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("n\r\nhyERROR: w" + EDITOR_PROGRAM_NAME + + " requires an ANSI terminal.n\r\np"); + exit(1); // 1: Aborted +} + +// Constants +const EDITOR_VERSION = "1.16"; +const EDITOR_VER_DATE = "2012-12-21"; + + +// Program variables +var gIsSysop = user.compare_ars("SYSOP"); // Whether or not the user is a sysop +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; +} +// Note: gQuotePrefix is declared in SlyEdit_Misc.js. + +// Colors +var gQuoteWinTextColor = "n7k"; // Normal text color for the quote window (DCT default) +var gQuoteLineHighlightColor = "nw"; // Highlighted text color for the quote window (DCT default) +var gTextAttrs = "nw"; // The text color for edit mode +var gQuoteLineColor = "nc"; // The text color for quote lines +var gUseTextAttribs = false; // Will be set to true if text colors start to be used + +// 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"; +} +// Set variables properly for the different editor styles. Also, set up +// function pointers for functionality common to the IceEdit & DCTedit styles. +var fpDrawQuoteWindowTopBorder = DrawQuoteWindowTopBorder_DCTStyle; +var fpDisplayTextAreaBottomBorder = DisplayTextAreaBottomBorder_DCTStyle; +var fpDrawQuoteWindowBottomBorder = DrawQuoteWindowBottomBorder_DCTStyle; +var fpRedrawScreen = redrawScreen_DCTStyle; +var fpUpdateInsertModeOnScreen = updateInsertModeOnScreen_DCTStyle; +var fpDisplayBottomHelpLine = DisplayBottomHelpLine_DCTStyle; +var fpHandleESCMenu = handleDCTESCMenu; +var fpDisplayTime = displayTime_DCTStyle; +if (EDITOR_STYLE == "DCT") +{ + gEditTop = 6; + gQuoteWinTextColor = gConfigSettings.DCTColors.QuoteWinText; + gQuoteLineHighlightColor = gConfigSettings.DCTColors.QuoteLineHighlightColor; + gTextAttrs = gConfigSettings.DCTColors.TextEditColor; + gQuoteLineColor = gConfigSettings.DCTColors.QuoteLineColor; +} +else if (EDITOR_STYLE == "ICE") +{ + gEditTop = 5; + gQuoteWinTextColor = gConfigSettings.iceColors.QuoteWinText; + gQuoteLineHighlightColor = gConfigSettings.iceColors.QuoteLineHighlightColor; + gTextAttrs = gConfigSettings.iceColors.TextEditColor; + gQuoteLineColor = gConfigSettings.iceColors.QuoteLineColor; + + // Function pointers for the styled 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; +} + +// 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 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(); + +// 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_"; +// Enable delete line in SyncTERM (Disabling ANSI Music in the process) +console.write("\033[=1M"); +console.clear(); +// Open the quote file / message file +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); + gQuoteLines.push(textLine); + } + } + // If the setting to re-wrap quote lines is enabled, then do it. + if (gConfigSettings.reWrapQuoteLines && (gQuoteLines.length > 0)) + wrapQuoteLines(); + } + 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(); +} + +// Read the message from name, to name, and subject from the drop file +// (msginf in the node directory). +var gMsgSubj = ""; +var gFromName = user.alias; +var gToName = gInputFilename; +var gMsgArea = ""; +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]; + } + } + file_remove(dropFileName); +} + +// 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; + } +} + +// 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)) +{ + // 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); + 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 = "nmhMessage aborted."; +else if (exitCode == 0) +{ + if (gEditLines.length > 0) + endStatusMessage = "nchThe message has been saved."; + else + endStatusMessage = "nmhEmpty message not sent."; +} +// We shouldn't hit this else case, but it's here just to be safe. +else + endStatusMessage = "nmhPossible message error."; + +// Display the end-of-program information (if the setting is enabled) and +// the ending program status. +console.clear("n"); +if (gConfigSettings.displayEndInfoScreen) +{ + displayProgramExitInfo(false); + console.crlf(); +} +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 + +// Edit mode & input loop +function doEditLoop() +{ + // Return codes: + // 0: Success + // 1: Aborted + var returnCode = 0; + + // Set the shortcut keys. + const ABORT_KEY = CTRL_A; + 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 FIND_TEXT_KEY = CTRL_N; + const IMPORT_FILE_KEY = CTRL_O; + const CMDLIST_HELP_KEY = CTRL_P; + const QUOTE_KEY = CTRL_Q; + const PROGRAM_INFO_HELP_KEY = CTRL_R; + const PAGE_DOWN_KEY = CTRL_S; + const PAGE_UP_KEY = CTRL_W; + const EXPORT_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 editin 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); + + // 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) + { + // Get a key, and time out after 5 minutes. + // Get a keypress from the user. If the setting for using the + // input timeout is enabled and the user is not a sysop, then use + // the input timeout specified in the config file. Otherwise, + // don't use a timeout. + if (gConfigSettings.userInputTimeout && !gIsSysop) + userInput = console.inkey(K_NOCRLF|K_NOSPIN, gConfigSettings.inputTimeoutMS); + else + userInput = console.getkey(K_NOCRLF|K_NOSPIN); + // If userInput is blank, then the input timeout was probably + // reached, so abort. + if (userInput == "") + { + returnCode = 1; // Aborted + continueOn = false; + console.crlf(); + console.print("nhr" + EDITOR_PROGRAM_NAME + ": Input timeout reached."); + continue; + } + + // If we reach this code, 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")) + { + returnCode = 1; // Aborted + continueOn = false; + } + else + { + // Make sure the edit color attribute is set. + //console.print("n" + gTextAttrs); + console.print(chooseEditColor()); + } + break; + case SAVE_KEY: + returnCode = 0; // Save + continueOn = false; + break; + case CMDLIST_HELP_KEY: + displayCommandList(true, true, true, gIsSysop); + 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, 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("nhr" + EDITOR_PROGRAM_NAME + ": Input timeout reached."); + continue; + } + } + break; + case CHANGE_COLOR_KEY: + /* + // Let the user change the text color. + if (gConfigSettings.allowColorSelection) + { + var retObject = doColorSelection(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("nhr" + 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 + 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 + 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 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("nhr" + EDITOR_PROGRAM_NAME + ": Input timeout reached."); + continue; + } + } + } + else if (retObject.showHelp) + { + displayProgramInfo(true, true, false); + displayCommandList(false, false, true, gIsSysop); + clearEditAreaBuffer(); + fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs, + gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop), + displayEditLines); + console.gotoxy(curpos); + } + } + // 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("n" + 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("n" + gTextAttrs); + console.print(chooseEditColor()); + console.gotoxy(curpos); + } + break; + case FIND_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 (gIsSysop) + { + var retObj = importFile(gIsSysop, 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_FILE_KEY: + // Only let sysops export files. + if (gIsSysop) + { + exportToFile(gIsSysop); + 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 PAGE_UP_KEY: // 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 PAGE_DOWN_KEY: // 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; + default: + // For the tab character, insert 3 spaces. Otherwise, + // if it's a printabel character, add the character. + 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; + } + + // For every 5 keys pressed, dheck the current time and update + // it on the screen if necessary. + if (numKeysPressed % 5 == 0) + updateTime(); + } + + // 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) + { + 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; + + // Store the original line text (for testing to see if we should update the screen). + var originalLineText = gEditLines[gEditLinesIndex].text; + + // If gEditLinesIndex is invalid, then return without doing anything. + if ((gEditLinesIndex < 0) || (gEditLinesIndex >= gEditLines.length)) + return returnObject; + + // 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; + // Also, temporarily set the line's isQuoteLine property to false + // so that text from the next line can be brought up. Store the + // current isQuoteLine value so it can be restored later. + var lineIsQuoteLine = gEditLines[gEditLinesIndex].isQuoteLine; + gEditLines[gEditLinesIndex].isQuoteLine = 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, restore the line's + // isQuoteLine property, and set a few other things. + textChanged = textChanged || reAdjustTextLines(gEditLines, gEditLinesIndex, + gEditLines.length, gEditWidth); + gEditLines[gEditLinesIndex].isQuoteLine = lineIsQuoteLine; + 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); + } + } + + // 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()), + // then we'll need to refresh multiple lines on the screen. + if (reAdjusted && (gEditLines[gEditLinesIndex].text != originalAfterCharApplied)) + { + // TODO: In case the user entered a whole line of text without any spaces, + // the new character would appear on the next line, so we need to figure + // out where the cursor location should be. + + // 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()) + { + // TODO: 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.showHelp = false; + + // 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")) + { + retObj.returnCode = 1; // 1: Abort + retObj.continueOn = false; + return(retObj); + } + else + { + // Make sure the edit color attribute is set back. + //console.print("n" + 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("n" + 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); + } + } + + // Store the current screen row position and gEditLines index. + var initialScreenLine = pCurpos.y; + var initialEditLinesIndex = gEditLinesIndex; + + // 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); + + 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 shoul dbe. +// +// 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; +} + +// 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; + + // Set up some variables + var curpos = new Object(); + curpos.x = pCurpos.x; + curpos.y = pCurpos.y; + const quoteWinHeight = 8; + // 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; + + // 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 key, and time out after 1 minute. + userInput = console.inkey(0, 100000); + 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_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); + + // 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 ((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); + } + + // 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"; + 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") + { + //printf(formatStr, gEditLines[editLinesIndex].text.substr(editLineIndex, pWidth)); + //printEditLine(pIndex, pUseColors, pStart, pLength) + // TODO: Change the false to the parameter for whether or not to allow + // message colors + 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) + { + var displayedTextLen = gEditLines[editLinesIndex].text.length - editLineIndex; + if (pWidth > displayedTextLen) + printf("%" + (pWidth-displayedTextLen) + "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: +// 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 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, gIsSysop); + // 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")) + { + 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(gIsSysop, 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 (gIsSysop) + { + var retval = importFile(gIsSysop, 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 == "C") || (menuChoice == DCTMENU_HELP_COMMAND_LIST)) + { + displayCommandList(true, true, true, gIsSysop); + 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, 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 (gIsSysop) + { + exportToFile(gIsSysop); + 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. + } + + // 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); + 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")) + { + 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_HELP: + displayProgramInfo(true, true, false); + displayCommandList(false, false, true, gIsSysop); + clearEditAreaBuffer(); + fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs, + gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop), + displayEditLines); + 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!", 1500); + } + else // Could not find the correct case for the file (it doesn't exist?) + writeWithPause(1, console.screen_rows, "yhUnable to locate the file!", 1500); + } + } + + // 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.", 1500); + } + else // Could not open the file for writing + writeWithPause(1, console.screen_rows, "yhUnable to open the file for writing!", 1500); + } + else // No filename specified + writeWithPause(1, console.screen_rows, "mhMessage not exported.", 1500); + + // 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(1500); + + 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. +function updateTime() +{ + 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 = 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 + 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: +// 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 doColorSelection(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: The current text color is stored in gTextAttrs + + var colorSelTopLine = console.screen_rows - 2; + var curpos = new Object(); + curpos.x = 1; + curpos.y = colorSelTopLine; + console.gotoxy(curpos); + console.print("nForeground: whK:nkBlack whR:nrRed whG:ngGreen whY:nyYellow whB:nbBlue whM:nmMagenta whC:ncCyan whW:nwWhite"); + console.cleartoeol("n"); + console.crlf(); + console.print("nBackground: wh0:n" + gTextAttrs + "0Blackn wh1:n" + gTextAttrs + "1Redn wh2:n" + gTextAttrs + "2Greenn wh3:n" + gTextAttrs + "3Yellown wh4:n" + gTextAttrs + "4Bluen wh5:n" + gTextAttrs + "5Magentan wh6:n" + gTextAttrs + "6Cyann wh7:n" + gTextAttrs + "7White"); + console.cleartoeol("n"); + console.crlf(); + console.clearline("n"); + console.print("Special: whH:n" + gTextAttrs + "hHigh Intensity wI:n" + gTextAttrs + "iBlinking nwhN:nNormal c� nChoose Color: "); + var attr = FORE_ATTR; + var toggle = true; + //var key = console.getkeys("KRGYBMCW01234567HIN").toString(); // Outputs a CR.. bad + var key = console.getkey(K_UPPER|K_NOCRLF); + switch (key) + { + // Foreground colors: + case 'K': // Black + case 'R': // Red + case 'G': // Green + case 'Y': // Yellow + case 'B': // Blue + case 'M': // Magenta + case 'C': // Cyan + case 'W': // White + attr = FORE_ATTR; + break; + // Background colors: + case '0': // Black + case '1': // Red + case '2': // Green + case '3': // Yellow + case '4': // Blue + case '5': // Magenta + case '6': // Cyan + case '7': // White + attr = BKG_ATTR; + break; + // Special attributes: + case 'H': // High intensity + case 'I': // Blinking + attr = SPECIAL_ATTR; + break; + case 'N': // Normal + gTextAttrs = "N"; + toggle = false; + break; + default: + toggle = false; + break; + } + if (key != "Q") + { + if (toggle) + { + gTextAttrs = toggleAttr(attr, gTextAttrs, key); + // TODO: Set the attribute in the current text line + } + } + + + // Display the parts of the screen text that we covered up with the + // color selection: Message edit lines, bottom border, and bottom help line. + displayEditLines(colorSelTopLine, gEditLinesIndex-(gEditBottom-colorSelTopLine), + gEditBottom, true, true); + fpDisplayTextAreaBottomBorder(gEditBottom+1, gUseQuotes, gEditLeft, gEditRight, + gInsertMode, gConfigSettings.allowColorSelection); + fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes); + + console.print(gTextAttrs); + + // Move the cursor to where it should be before returning + curpos.x = pCurpos.x; + curpos.y = pCurpos.y; + console.gotoxy(curpos); + + // This code was copied from doQuoteSelection(). +/* + // Set up some variables + var curpos = new Object(); + curpos.x = pCurpos.x; + curpos.y = pCurpos.y; + const quoteWinHeight = 8; + // 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; + + // 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 key, and time out after 1 minute. + userInput = console.inkey(0, 100000); + 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_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); +*/ + // Set the settings in the return object, and return it. + retObj.x = curpos.x; + retObj.y = curpos.y; + return retObj; +} + +// 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. +function printEditLine(pIndex, pUseColors, pStart, pLength) +{ + if (typeof(pIndex) != "number") + return; + 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; + } + 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; + } + //if (length > (gEditLines[pIndex].text.length - start)) + // length = gEditLines[pIndex].text.length - start; + + 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. + if (length <= 0) + console.print(gEditLines[pIndex].text); + else + console.print(gEditLines[pIndex].text.substr(start, length)); + } + else + { + // Start is > 0 + if (length <= 0) + console.print(gEditLines[pIndex].text.substr(start)); + else + console.print(gEditLines[pIndex].text.substr(start, length)); + } + } +} \ No newline at end of file diff --git a/exec/SlyEdit_DCTStuff.js b/exec/SlyEdit_DCTStuff.js new file mode 100644 index 0000000000..f6cce88a99 --- /dev/null +++ b/exec/SlyEdit_DCTStuff.js @@ -0,0 +1,1432 @@ +/* This contains some DCTEdit-specific functions for the Digital Distortion + * Editor which don't rely on any global variables. + * + * Author: Eric Oulashin (AKA Nightfox) + * BBS: Digital Distortion + * BBS address: digdist.bbsindex.com + * + * Date User Description + * 2009-05-11 Eric Oulashin Started development + * 2009-08-09 Eric Oulashin More development & testing + * 2009-08-22 Eric Oulashin Version 1.00 + * Initial public release + * 2009-12-03 Eric Oulashin Added support for color schemes. + * Added readColorConfig(). + * 2009-12-31 Eric Oulashin Updated promptYesNo_DCTStyle() + * so that the return variable, + * userResponse, defaults to the + * value of pDefaultYes. + * 2010-01-02 Eric Oulashin Removed abortConfirm_DCTStyle(), + * since it's no longer used anymore. + * 2010-11-19 Eric Oulashin Updated doDCTMenu() so that when the + * pDisplayMessageRectangle() function is + * called, it passes true at the end to + * tell it to clear extra spaces between + * the end of the text lines up to the + * width given. + * 2010-11-21 Eric Oulashin Updated promptYesNo_DCTStyle() so that when the + * pDisplayMessageRectangle() function is + * called, it passes true at the end to + * tell it to clear extra spaces between + * the end of the text lines up to the + * width given. + * 2011-02-02 Eric Oulashin Moved the time displaying code into + * a new function, displayTime_DCTStyle(). + * 2012-12-21 Eric Oulashin Removed gStartupPath from the beginning + * of the theme filename, since the path is + * now set in ReadSlyEditConfigFile() in + * SlyEdit_Misc.js. + */ + +load("sbbsdefs.js"); +load(getScriptDir() + "SlyEdit_Misc.js"); + +// DCTEdit menu item return values +var DCTMENU_FILE_SAVE = 0; +var DCTMENU_FILE_ABORT = 1; +var DCTMENU_FILE_EDIT = 2; +var DCTMENU_EDIT_INSERT_TOGGLE = 3; +var DCTMENU_EDIT_FIND_TEXT = 4; +//var DCTMENU_EDIT_SPELL_CHECKER = 5; +//var DCTMENU_EDIT_SETUP = 6; +var DCTMENU_SYSOP_IMPORT_FILE = 7; +var DCTMENU_SYSOP_EXPORT_FILE = 11; +var DCTMENU_HELP_COMMAND_LIST = 8; +var DCTMENU_HELP_GENERAL = 9; +var DCTMENU_HELP_PROGRAM_INFO = 10; + +// Read the color configuration file +readColorConfig(gConfigSettings.DCTColors.ThemeFilename); + +/////////////////////////////////////////////////////////////////////////////////// +// Functions + +// This function reads the color configuration for DCT style. +// +// Parameters: +// pFilename: The name of the color configuration file +function readColorConfig(pFilename) +{ + var colors = readValueSettingConfigFile(pFilename, 512); + if (colors != null) + gConfigSettings.DCTColors = colors; +} + +// Re-draws the screen, in the style of DCTEdit. +// +// Parameters: +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +// pEditTop: The topmost row of the edit area +// pEditBottom: The bottommost row of the edit area +// pEditColor: The edit color +// pInsertMode: The insert mode ("INS" or "OVR") +// pUseQuotes: Whether or not message quoting is enabled +// pEditLinesIndex: The index of the message line at the top of the edit area +// pDisplayEditLines: The function that displays the edit lines +function redrawScreen_DCTStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEditColor, + pInsertMode, pUseQuotes, pEditLinesIndex, pDisplayEditLines) +{ + // Top header + // Generate & display the top border line (Note: Generate this + // border only once, for efficiency). + if (typeof(redrawScreen_DCTStyle.topBorder) == "undefined") + { + var innerWidth = console.screen_columns - 2; + redrawScreen_DCTStyle.topBorder = UPPER_LEFT_SINGLE; + for (var i = 0; i < innerWidth; ++i) + redrawScreen_DCTStyle.topBorder += HORIZONTAL_SINGLE; + redrawScreen_DCTStyle.topBorder += UPPER_RIGHT_SINGLE; + redrawScreen_DCTStyle.topBorder = randomTwoColorString(redrawScreen_DCTStyle.topBorder, + gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2); + } + // Print the border line on the screen + console.clear(); + console.print(redrawScreen_DCTStyle.topBorder); + + // Next line + // From name + var lineNum = 2; + console.gotoxy(1, lineNum); + // Calculate the width of the from name field: 28 characters, based + // on an 80-column screen width. + var fieldWidth = (console.screen_columns * (28/80)).toFixed(0); + screenText = gFromName.substr(0, fieldWidth); + console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2) + + " " + gConfigSettings.DCTColors.TopLabelColor + "From " + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopFromFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopFromColor + screenText + + gConfigSettings.DCTColors.TopFromFillColor); + fieldWidth -= (screenText.length+1); + for (var i = 0; i < fieldWidth; ++i) + console.print(DOT_CHAR); + console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "]"); + + // Message area + fieldWidth = (console.screen_columns * (27/80)).toFixed(0); + screenText = gMsgArea.substr(0, fieldWidth); + var startX = console.screen_columns - fieldWidth - 9; + console.gotoxy(startX, lineNum); + console.print(gConfigSettings.DCTColors.TopLabelColor + "Area" + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopAreaFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopAreaColor + screenText + + gConfigSettings.DCTColors.TopAreaFillColor); + fieldWidth -= (screenText.length+1); + for (var i = 0; i < fieldWidth; ++i) + console.print(DOT_CHAR); + console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " + + randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2)); + + // Next line: To, Time, time Left + ++lineNum; + console.gotoxy(1, lineNum); + // To name + fieldWidth = (console.screen_columns * (28/80)).toFixed(0); + screenText = gToName.substr(0, fieldWidth); + console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2) + + " " + gConfigSettings.DCTColors.TopLabelColor + "To " + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopToFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopToColor + screenText + + gConfigSettings.DCTColors.TopToFillColor); + fieldWidth -= (screenText.length+1); + for (var i = 0; i < fieldWidth; ++i) + console.print(DOT_CHAR); + console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "]"); + + // Current time + console.gotoxy(startX, lineNum); + console.print(gConfigSettings.DCTColors.TopLabelColor + "Time" + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopTimeFillColor + DOT_CHAR); + displayTime_DCTStyle(); + console.print(gConfigSettings.DCTColors.TopTimeFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopInfoBracketColor + "]"); + + // Time left + fieldWidth = (console.screen_columns * (7/80)).toFixed(0); + startX = console.screen_columns - fieldWidth - 9; + console.gotoxy(startX, lineNum); + var timeStr = Math.floor(bbs.time_left / 60).toString(); + console.print(gConfigSettings.DCTColors.TopLabelColor + "Left" + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopTimeLeftFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopTimeLeftColor + timeStr + + gConfigSettings.DCTColors.TopTimeLeftFillColor); + fieldWidth -= (timeStr.length+1); + for (var i = 0; i < fieldWidth; ++i) + console.print(DOT_CHAR); + console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " + + randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2)); + + // Line 3: Subject + ++lineNum; + console.gotoxy(1, lineNum); + //fieldWidth = (console.screen_columns * (66/80)).toFixed(0); + fieldWidth = console.screen_columns - 13; + screenText = gMsgSubj.substr(0, fieldWidth); + console.print(randomTwoColorString(LOWER_LEFT_SINGLE, gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor1) + + " " + gConfigSettings.DCTColors.TopLabelColor + "Subj " + + gConfigSettings.DCTColors.TopLabelColonColor + ": " + + gConfigSettings.DCTColors.TopInfoBracketColor + "[" + + gConfigSettings.DCTColors.TopSubjFillColor + DOT_CHAR + + gConfigSettings.DCTColors.TopSubjColor + screenText + + gConfigSettings.DCTColors.TopSubjFillColor); + fieldWidth -= (screenText.length+1); + for (var i = 0; i < fieldWidth; ++i) + console.print(DOT_CHAR); + console.print(gConfigSettings.DCTColors.TopInfoBracketColor + "] " + + randomTwoColorString(LOWER_RIGHT_SINGLE, + gConfigSettings.DCTColors.TopBorderColor1, + gConfigSettings.DCTColors.TopBorderColor2)); + + // Line 4: Top border for message area + ++lineNum; + displayTextAreaTopBorder_DCTStyle(lineNum, pEditLeft, pEditRight); + // If the screen is at least 82 characters wide, display horizontal + // lines around the message editing area. + if (console.screen_columns >= 82) + { + for (lineNum = pEditTop; lineNum <= pEditBottom; ++lineNum) + { + console.gotoxy(pEditLeft-1, lineNum); + console.print(randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.DCTColors.EditAreaBorderColor1, + gConfigSettings.DCTColors.EditAreaBorderColor2)); + console.gotoxy(pEditRight+1, lineNum); + console.print(randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.DCTColors.EditAreaBorderColor1, + gConfigSettings.DCTColors.EditAreaBorderColor2)); + } + } + + // Display the bottom message area border and help line + DisplayTextAreaBottomBorder_DCTStyle(pEditBottom+1, null, pEditLeft, pEditRight, pInsertMode); + DisplayBottomHelpLine_DCTStyle(console.screen_rows, pUseQuotes); + + // Go to the start of the edit area + console.gotoxy(pEditLeft, pEditTop); + + // Write the message text that has been entered thus far. + pDisplayEditLines(pEditTop, pEditLinesIndex); + console.print(pEditColor); +} + +// Displays the top border of the message area, in the style of DCTEdit. +// +// Parameters: +// pLineNum: The line number on the screen at which to draw the border +// pEditLeft: The leftmost edit area column on the screen +// pEditRight: The rightmost edit area column on the screen +function displayTextAreaTopBorder_DCTStyle(pLineNum, pEditLeft, pEditRight) +{ + // The border will use random bright/normal colors. The colors + // should stay the same each time we draw it, so a "static" + // variable is used for the border text. If that variable has + // not been defined yet, then build it. + if (typeof(displayTextAreaTopBorder_DCTStyle.border) == "undefined") + { + var numHorizontalChars = pEditRight - pEditLeft - 1; + if (console.screen_columns >= 82) + numHorizontalChars += 2; + + displayTextAreaTopBorder_DCTStyle.border = UPPER_LEFT_SINGLE; + for (var i = 0; i < numHorizontalChars; ++i) + displayTextAreaTopBorder_DCTStyle.border += HORIZONTAL_SINGLE; + displayTextAreaTopBorder_DCTStyle.border += UPPER_RIGHT_SINGLE; + displayTextAreaTopBorder_DCTStyle.border = + randomTwoColorString(displayTextAreaTopBorder_DCTStyle.border, + gConfigSettings.DCTColors.EditAreaBorderColor1, + gConfigSettings.DCTColors.EditAreaBorderColor2); + } + + // Draw the line on the screen + console.gotoxy((console.screen_columns >= 82 ? pEditLeft-1 : pEditLeft), pLineNum); + console.print(displayTextAreaTopBorder_DCTStyle.border); +} + +// Displays the bottom border of the message area, in the style of DCTEdit. +// +// Parameters: +// pLineNum: The line number on the screen at which to draw the border +// pUseQuotes: This is not used; this is only here so that the signatures of +// the IceEdit and DCTEdit versions match. +// pEditLeft: The leftmost edit area column on the screen +// pEditRight: The rightmost edit area column on the screen +// pInsertMode: The insert mode ("INS" or "OVR") +// pCanChgMsgColor: Whether or not changing the text color is allowed +function DisplayTextAreaBottomBorder_DCTStyle(pLineNum, pUseQuotes, pEditLeft, pEditRight, + pInsertMode, pCanChgMsgColor) +{ + // The border will use random bright/normal colors. The colors + // should stay the same each time we draw it, so a "static" + // variable is used for the border text. If that variable has + // not been defined yet, then build it. + if (typeof(DisplayTextAreaBottomBorder_DCTStyle.border) == "undefined") + { + var innerWidth = pEditRight - pEditLeft - 1; + // If the screen is at least 82 characters wide, add 2 to innerWidth + // to make room for the vertical lines around the text area. + if (console.screen_columns >= 82) + innerWidth += 2; + + DisplayTextAreaBottomBorder_DCTStyle.border = LOWER_LEFT_SINGLE; + + // This loop uses innerWidth-6 to make way for the insert mode + // text. + for (var i = 0; i < innerWidth-6; ++i) + DisplayTextAreaBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE; + DisplayTextAreaBottomBorder_DCTStyle.border = + randomTwoColorString(DisplayTextAreaBottomBorder_DCTStyle.border, + gConfigSettings.DCTColors.EditAreaBorderColor1, + gConfigSettings.DCTColors.EditAreaBorderColor2); + // Insert mode + DisplayTextAreaBottomBorder_DCTStyle.border += gConfigSettings.DCTColors.EditModeBrackets + + "[" + gConfigSettings.DCTColors.EditMode + + pInsertMode + + gConfigSettings.DCTColors.EditModeBrackets + + "]"; + // The last 2 border characters + DisplayTextAreaBottomBorder_DCTStyle.border += + randomTwoColorString(HORIZONTAL_SINGLE + LOWER_RIGHT_SINGLE, + gConfigSettings.DCTColors.EditAreaBorderColor1, + gConfigSettings.DCTColors.EditAreaBorderColor2); + } + + // Draw the border line on the screen. + console.gotoxy((console.screen_columns >= 82 ? pEditLeft-1 : pEditLeft), pLineNum); + console.print(DisplayTextAreaBottomBorder_DCTStyle.border); +} + +// Displays the help line at the bottom of the screen, in the style +// of DCTEdit. +// +// Parameters: +// pLineNum: The line number on the screen at which to draw the help line +// pUsingQuotes: Boolean - Whether or not message quoting is enabled. +function DisplayBottomHelpLine_DCTStyle(pLineNum, pUsingQuotes) +{ + // For efficiency, define the help line variable only once. + if (typeof(DisplayBottomHelpLine_DCTStyle.helpText) == "undefined") + { + DisplayBottomHelpLine_DCTStyle.helpText = gConfigSettings.DCTColors.BottomHelpBrackets + + "[" + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" + + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR + + gConfigSettings.DCTColors.BottomHelpKeys + "Z" + + gConfigSettings.DCTColors.BottomHelpBrackets + "]n " + + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Saven " + + gConfigSettings.DCTColors.BottomHelpBrackets + "[" + + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" + + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR + + gConfigSettings.DCTColors.BottomHelpKeys + "A" + + gConfigSettings.DCTColors.BottomHelpBrackets + "]n " + + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Abort"; + // If we can allow message quoting, then add a text to show Ctrl-Q for + // quoting. + if (pUsingQuotes) + DisplayBottomHelpLine_DCTStyle.helpText += "n " + + gConfigSettings.DCTColors.BottomHelpBrackets + "[" + + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" + + gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR + + gConfigSettings.DCTColors.BottomHelpKeys + "Q" + + gConfigSettings.DCTColors.BottomHelpBrackets + "]n " + + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Quote"; + DisplayBottomHelpLine_DCTStyle.helpText += "n " + + gConfigSettings.DCTColors.BottomHelpBrackets + "[" + + gConfigSettings.DCTColors.BottomHelpKeys + "ESC" + + gConfigSettings.DCTColors.BottomHelpBrackets + "]n " + + gConfigSettings.DCTColors.BottomHelpKeyDesc + "Menu"; + // Center the text by padding it in the front with spaces. This is done instead + // of using console.center() because console.center() will output a newline, + // which would not be good on the last line of the screen. + var numSpaces = (console.screen_columns/2).toFixed(0) + - (strip_ctrl(DisplayBottomHelpLine_DCTStyle.helpText).length/2).toFixed(0); + for (var i = 0; i < numSpaces; ++i) + DisplayBottomHelpLine_DCTStyle.helpText = " " + DisplayBottomHelpLine_DCTStyle.helpText; + } + + // Display the help line on the screen + var lineNum = console.screen_rows; + if ((typeof(pLineNum) != "undefined") && (pLineNum != null)) + lineNum = pLineNum; + console.gotoxy(1, lineNum); + console.print(DisplayBottomHelpLine_DCTStyle.helpText); +} + +// Updates the insert mode displayd on the screen, for DCT Edit style. +// +// Parameters: +// pEditRight: The rightmost column on the screen for the edit area +// pEditBottom: The bottommost row on the screen for the edit area +// pInsertMode: The insert mode ("INS" or "OVR") +function updateInsertModeOnScreen_DCTStyle(pEditRight, pEditBottom, pInsertMode) +{ + console.gotoxy(pEditRight-6, pEditBottom+1); + console.print(gConfigSettings.DCTColors.EditModeBrackets + "[" + + gConfigSettings.DCTColors.EditMode + pInsertMode + + gConfigSettings.DCTColors.EditModeBrackets + "]"); +} + +// Draws the top border of the quote window, DCT Edit style. +// +// Parameters: +// pQuoteWinHeight: The height of the quote window +// pEditLeft: The leftmost column on the screen for the edit window +// pEditRight: The rightmost column on the screen for the edit window +function DrawQuoteWindowTopBorder_DCTStyle(pQuoteWinHeight, pEditLeft, pEditRight) +{ + // Generate the top border vairable only once. + if (typeof(DrawQuoteWindowTopBorder_DCTStyle.border) == "undefined") + { + DrawQuoteWindowTopBorder_DCTStyle.border = gConfigSettings.DCTColors.QuoteWinBorderColor + + UPPER_LEFT_SINGLE + HORIZONTAL_SINGLE + " " + + gConfigSettings.DCTColors.QuoteWinBorderTextColor + + "Quote Window " + gConfigSettings.DCTColors.QuoteWinBorderColor; + var curLength = strip_ctrl(DrawQuoteWindowTopBorder_DCTStyle.border).length; + var endCol = console.screen_columns-1; + for (var i = curLength; i < endCol; ++i) + DrawQuoteWindowTopBorder_DCTStyle.border += HORIZONTAL_SINGLE; + DrawQuoteWindowTopBorder_DCTStyle.border += UPPER_RIGHT_SINGLE; + } + + // Draw the top border line + var screenLine = console.screen_rows - pQuoteWinHeight + 1; + console.gotoxy(pEditLeft, screenLine); + console.print(DrawQuoteWindowTopBorder_DCTStyle.border); +} + +// Draws the bottom border of the quote window, DCT Edit style. Note: +// The cursor should be placed at the start of the line (leftmost screen +// column on the screen line where the border should be drawn). +// +// Parameters: +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +function DrawQuoteWindowBottomBorder_DCTStyle(pEditLeft, pEditRight) +{ + // Generate the bottom border vairable only once. + if (typeof(DrawQuoteWindowBottomBorder_DCTStyle.border) == "undefined") + { + // Create a string containing the quote help text. + var quoteHelpText = " " + gConfigSettings.DCTColors.QuoteWinBorderTextColor + + "[Enter] Quote Line " + gConfigSettings.DCTColors.QuoteWinBorderColor + + " "; + for (var i = 0; i < 3; ++i) + quoteHelpText += HORIZONTAL_SINGLE; + quoteHelpText += " " + gConfigSettings.DCTColors.QuoteWinBorderTextColor + + "[ESC] Stop Quoting " + gConfigSettings.DCTColors.QuoteWinBorderColor + + " "; + for (var i = 0; i < 3; ++i) + quoteHelpText += HORIZONTAL_SINGLE; + quoteHelpText += " " + gConfigSettings.DCTColors.QuoteWinBorderTextColor + + "[Up/Down] Scroll Lines "; + + // Figure out the starting horizontal position on the screen so that + // the quote help text line can be centered. + var helpTextStartX = ((console.screen_columns/2) - (strip_ctrl(quoteHelpText).length/2)).toFixed(0); + + // Start creating DrawQuoteWindowBottomBorder_DCTStyle.border with the + // bottom border lines, up until helpTextStartX. + DrawQuoteWindowBottomBorder_DCTStyle.border = gConfigSettings.DCTColors.QuoteWinBorderColor + + LOWER_LEFT_SINGLE; + for (var XPos = pEditLeft+2; XPos < helpTextStartX; ++XPos) + DrawQuoteWindowBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE; + // Add the help text, then display the rest of the bottom border characters. + DrawQuoteWindowBottomBorder_DCTStyle.border += quoteHelpText + + gConfigSettings.DCTColors.QuoteWinBorderColor; + for (var XPos = pEditLeft+2+strip_ctrl(quoteHelpText).length; XPos <= pEditRight-1; ++XPos) + DrawQuoteWindowBottomBorder_DCTStyle.border += HORIZONTAL_SINGLE; + DrawQuoteWindowBottomBorder_DCTStyle.border += LOWER_RIGHT_SINGLE; + } + + // Print the border text on the screen + console.print(DrawQuoteWindowBottomBorder_DCTStyle.border); +} + +// Prompts the user for a yes/no question, DCTEdit-style. +// +// Parameters: +// pQuestion: The question to ask, without the trailing "?" +// pBoxTitle: The text to appear in the top border of the question box +// pDefaultYes: Whether to default to "yes" (true/false). If false, this +// will default to "no". +// pParamObj: An object containing the following members: +// displayMessageRectangle: The function used for re-drawing the portion of the +// message on the screen when the yes/no box is no longer +// needed +// editTop: The topmost row of the edit area +// editBottom: The bottom row of the edit area +// editWidth: The edit area width +// editHeight: The edit area height +// editLinesIndex: The current index into the edit lines array +function promptYesNo_DCTStyle(pQuestion, pBoxTitle, pDefaultYes, pParamObj) +{ + var userResponse = pDefaultYes; + + // Get the current cursor position, so that we can place the cursor + // back there later. + const originalCurpos = console.getxy(); + + // Set up the abort confirmation box dimensions, and calculate + // where on the screen to display it. + //const boxWidth = 27; + const boxWidth = pQuestion.length + 14; + const boxHeight = 6; + const boxX = (console.screen_columns/2).toFixed(0) - (boxWidth/2).toFixed(0); + const boxY = +pParamObj.editTop + +(pParamObj.editHeight/2).toFixed(0) - +(boxHeight/2).toFixed(0); + const innerBoxWidth = boxWidth - 2; + + // Display the question box + // Upper-left corner, 1 horizontal line, and "Abort" text + console.gotoxy(boxX, boxY); + console.print(gConfigSettings.DCTColors.TextBoxBorder + UPPER_LEFT_SINGLE + + HORIZONTAL_SINGLE + " " + gConfigSettings.DCTColors.TextBoxBorderText + + pBoxTitle + gConfigSettings.DCTColors.TextBoxBorder + " "); + // Remaining top box border + for (var i = pBoxTitle.length + 5; i < boxWidth; ++i) + console.print(HORIZONTAL_SINGLE); + console.print(UPPER_RIGHT_SINGLE); + // Inner box: Blank + var endScreenLine = boxY + boxHeight - 2; + for (var screenLine = boxY+1; screenLine < endScreenLine; ++screenLine) + { + console.gotoxy(boxX, screenLine); + console.print(VERTICAL_SINGLE); + for (var i = 0; i < innerBoxWidth; ++i) + console.print(" "); + console.print(VERTICAL_SINGLE); + } + // Bottom box border + console.gotoxy(boxX, screenLine); + console.print(LOWER_LEFT_SINGLE); + for (var i = 0; i < innerBoxWidth; ++i) + console.print(HORIZONTAL_SINGLE); + console.print(LOWER_RIGHT_SINGLE); + + // Prompt the user whether or not to abort: Move the cursor to + // the proper location on the screen, output the propmt text, + // and get user input. + console.gotoxy(boxX+3, boxY+2); + console.print(gConfigSettings.DCTColors.TextBoxInnerText + pQuestion + "? " + + gConfigSettings.DCTColors.YesNoBoxBrackets + "[" + + gConfigSettings.DCTColors.YesNoBoxYesNoText); + // Default to yes/no, depending on the value of pDefaultYes. + if (pDefaultYes) + { + console.print("Yes"); + userResponse = true; + } + else + { + console.print("No "); + userResponse = false; + } + console.print(gConfigSettings.DCTColors.YesNoBoxBrackets + "]"); + + // Input loop + var userInput = ""; + var continueOn = true; + while (continueOn) + { + // Move the cursor where it needs to be to write the "Yes" + // or "No" + console.gotoxy(boxX+20, boxY+2); + // Get a key, (time out after 1 minute), and take appropriate action. + userInput = console.inkey(0, 100000).toUpperCase(); + if (userInput == KEY_ENTER) + continueOn = false; + else if (userInput == "Y") + { + userResponse = true; + continueOn = false; + } + else if ((userInput == "N") || (userInput == KEY_ESC)) + { + userResponse = false; + continueOn = false; + } + // Arrow keys: Toggle back and forth + else if ((userInput == KEY_UP) || (userInput == KEY_DOWN) || + (userInput == KEY_LEFT) || (userInput == KEY_RIGHT)) + { + // Toggle the userResponse variable + userResponse = !userResponse; + // Write "Yes" or "No", depending on the userResponse variable + if (userResponse) + { + console.print(gConfigSettings.DCTColors.YesNoBoxYesNoText + "Yes" + + gConfigSettings.DCTColors.YesNoBoxBrackets + "]"); + } + else + { + console.print(gConfigSettings.DCTColors.YesNoBoxYesNoText + "No " + + gConfigSettings.DCTColors.YesNoBoxBrackets + "]"); + } + } + } + + // If the user chose not to abort, then erase the confirmation box and + // put the cursor back where it originally was. + if (!userResponse) + { + // Calculate the difference in the edit lines index we'll need to use + // to erase the confirmation box. This will be the difference between + // the cursor position between boxY and the current cursor row. + const editLinesIndexDiff = boxY - originalCurpos.y; + pParamObj.displayMessageRectangle(boxX, boxY, boxWidth, boxHeight, + pParamObj.editLinesIndex + editLinesIndexDiff, + true); + // Put the cursor back where it was + console.gotoxy(originalCurpos); + } + + return userResponse; +} + +// Displays the time on the screen. +// +// Parameters: +// pTimeStr: The time to display (optional). If this is omitted, +// then this funtion will get the current time. +function displayTime_DCTStyle(pTimeStr) +{ + console.gotoxy(52, 3); + if (pTimeStr == null) + console.print(gConfigSettings.DCTColors.TopTimeColor + getCurrentTimeStr()); + else + console.print(gConfigSettings.DCTColors.TopTimeColor + pTimeStr); +} + +// Displays & handles the input loop for the DCT Edit menu. +// +// Parameters: +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +// pEditTop: The topmost row of the edit area +// pDisplayMessageRectangle: The function for drawing part of the +// edit text on the screen. This is used +// for refreshing the screen after a menu +// box disappears. +// pEditLinesIndex: The current index into the edit lines array. This +// is is required to pass to the pDisplayMessageRectangle +// function. +// pEditLineDiff: The difference between the current edit line and the top of +// the edit area. +// pIsSysop: Whether or not the user is a sysop. +// +// Return value: An object containing the following properties: +// userInput: The user's input from the menu loop. +// returnVal: The return code from the menu. +function doDCTMenu(pEditLeft, pEditRight, pEditTop, pDisplayMessageRectangle, + pEditLinesIndex, pEditLineDiff, pIsSysop) +{ + // This function displays the top menu options, with a given one highlighted. + // + // Parameters: + // pItemPositions: An object containing the menu item positions. + // pHighlightedItemNum: The index (0-based) of the menu item to be highlighted. + // pMenus: An array containing the menus + // pIsSysop: Whether or not the user is the sysop. + // + // Return value: The user's last keypress during the menu's input loop. + function displayTopMenuItems(pItemPositions, pHighlightedItemNum, pMenus, pIsSysop) + { + // File + console.gotoxy(pItemPositions.fileX, pItemPositions.mainMenuY); + if (pHighlightedItemNum == 0) + { + console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT + + gConfigSettings.DCTColors.SelectedMenuLabelText + "File" + + gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT); + } + else + { + console.print("n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "File" + + "n "); + } + // Edit + console.gotoxy(pItemPositions.editX, pItemPositions.mainMenuY); + if (pHighlightedItemNum == 1) + { + console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT + + gConfigSettings.DCTColors.SelectedMenuLabelText + "Edit" + + gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT); + } + else + { + console.print("n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "Edit" + + "n "); + } + // SysOp + if (pIsSysop) + { + console.gotoxy(pItemPositions.sysopX, pItemPositions.mainMenuY); + if (pHighlightedItemNum == 2) + { + console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT + + gConfigSettings.DCTColors.SelectedMenuLabelText + "SysOp" + + gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT); + } + else + { + console.print("n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "SysOp" + + "n "); + } + } + // Help + console.gotoxy(pItemPositions.helpX, pItemPositions.mainMenuY); + if (pHighlightedItemNum == 3) + { + console.print(gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_RIGHT + + gConfigSettings.DCTColors.SelectedMenuLabelText + "Help" + + gConfigSettings.DCTColors.SelectedMenuLabelBorders + THIN_RECTANGLE_LEFT); + } + else + { + console.print("n " + gConfigSettings.DCTColors.UnselectedMenuLabelText + "Help" + + "n "); + } + + // Display the menu (and capture the return object so that we can + // also return it here). + var returnObj = pMenus[pHighlightedItemNum].doInputLoop(); + + // Refresh the part of the edit text on the screen where the menu was. + pDisplayMessageRectangle(pMenus[pHighlightedItemNum].topLeftX, + pMenus[pHighlightedItemNum].topLeftY, + pMenus[pHighlightedItemNum].width, + pMenus[pHighlightedItemNum].height, + pEditLinesIndex-pEditLineDiff, true); + + return returnObj; + } + + // Set up an object containing the menu item positions. + // Only create this object once. + if (typeof(doDCTMenu.mainMenuItemPositions) == "undefined") + { + doDCTMenu.mainMenuItemPositions = new Object(); + // Vertical position on the screen for the main menu items + doDCTMenu.mainMenuItemPositions.mainMenuY = pEditTop - 1; + // Horizontal position of the "File" text + doDCTMenu.mainMenuItemPositions.fileX = gEditLeft + 5; + // Horizontal position of the "Edit" text + doDCTMenu.mainMenuItemPositions.editX = doDCTMenu.mainMenuItemPositions.fileX + 11; + // Horizontal position of the "SysOp" text + doDCTMenu.mainMenuItemPositions.sysopX = doDCTMenu.mainMenuItemPositions.editX + 11; + // Horizontal position of of the "Help" text + if (pIsSysop) + doDCTMenu.mainMenuItemPositions.helpX = doDCTMenu.mainMenuItemPositions.sysopX + 12; + else + doDCTMenu.mainMenuItemPositions.helpX = doDCTMenu.mainMenuItemPositions.sysopX; + } + + // Variables for the menu numbers + const fileMenuNum = 0; + const editMenuNum = 1; + const sysopMenuNum = 2; + const helpMenuNum = 3; + + // Set up the menu objects. Only create these objects once. + if (typeof(doDCTMenu.allMenus) == "undefined") + { + doDCTMenu.allMenus = new Array(); + // File menu + doDCTMenu.allMenus[fileMenuNum] = new DCTMenu(doDCTMenu.mainMenuItemPositions.fileX, doDCTMenu.mainMenuItemPositions.mainMenuY+1); + doDCTMenu.allMenus[fileMenuNum].addItem("&Save Ctrl-Z", DCTMENU_FILE_SAVE); + doDCTMenu.allMenus[fileMenuNum].addItem("&Abort Ctrl-A", DCTMENU_FILE_ABORT); + doDCTMenu.allMenus[fileMenuNum].addItem("&Edit ESC", DCTMENU_FILE_EDIT); + doDCTMenu.allMenus[fileMenuNum].addExitLoopKey(CTRL_Z, DCTMENU_FILE_SAVE); + doDCTMenu.allMenus[fileMenuNum].addExitLoopKey(CTRL_A, DCTMENU_FILE_ABORT); + doDCTMenu.allMenus[fileMenuNum].addExitLoopKey(KEY_ESC, DCTMENU_FILE_EDIT); + + // Edit menu + doDCTMenu.allMenus[editMenuNum] = new DCTMenu(doDCTMenu.mainMenuItemPositions.editX, doDCTMenu.mainMenuItemPositions.mainMenuY+1); + doDCTMenu.allMenus[editMenuNum].addItem("&Insert Mode Ctrl-I", DCTMENU_EDIT_INSERT_TOGGLE); + doDCTMenu.allMenus[editMenuNum].addItem("&Find Text Ctrl-N", DCTMENU_EDIT_FIND_TEXT); + //doDCTMenu.allMenus[editMenuNum].addItem("Spell &Checker Ctrl-W", DCTMENU_EDIT_SPELL_CHECKER); + //doDCTMenu.allMenus[editMenuNum].addItem("&Setup Ctrl-U", DCTMENU_EDIT_SETUP); + doDCTMenu.allMenus[editMenuNum].addExitLoopKey(CTRL_I, DCTMENU_EDIT_INSERT_TOGGLE); + doDCTMenu.allMenus[editMenuNum].addExitLoopKey(CTRL_N, DCTMENU_EDIT_FIND_TEXT); + + // SysOp menu + doDCTMenu.allMenus[sysopMenuNum] = new DCTMenu(doDCTMenu.mainMenuItemPositions.sysopX, doDCTMenu.mainMenuItemPositions.mainMenuY+1); + doDCTMenu.allMenus[sysopMenuNum].addItem("&Import file Ctrl-O", DCTMENU_SYSOP_IMPORT_FILE); + doDCTMenu.allMenus[sysopMenuNum].addItem("E&xport to file Ctrl-X", DCTMENU_SYSOP_EXPORT_FILE); + doDCTMenu.allMenus[sysopMenuNum].addExitLoopKey(CTRL_O, DCTMENU_SYSOP_IMPORT_FILE); + doDCTMenu.allMenus[sysopMenuNum].addExitLoopKey(CTRL_X, DCTMENU_SYSOP_EXPORT_FILE); + + // Help menu + doDCTMenu.allMenus[helpMenuNum] = new DCTMenu(doDCTMenu.mainMenuItemPositions.helpX, doDCTMenu.mainMenuItemPositions.mainMenuY+1); + doDCTMenu.allMenus[helpMenuNum].addItem("&Command List Ctrl-P", DCTMENU_HELP_COMMAND_LIST); + doDCTMenu.allMenus[helpMenuNum].addItem("&General Help Ctrl-G", DCTMENU_HELP_GENERAL); + doDCTMenu.allMenus[helpMenuNum].addItem("&Program Info Ctrl-R", DCTMENU_HELP_PROGRAM_INFO); + doDCTMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_P, DCTMENU_HELP_COMMAND_LIST); + doDCTMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_G, DCTMENU_HELP_GENERAL); + doDCTMenu.allMenus[helpMenuNum].addExitLoopKey(CTRL_R, DCTMENU_HELP_PROGRAM_INFO); + + // For each menu, add KEY_LEFT, KEY_RIGHT, and ESC as loop-exit keys; + // also, set the menu colors. + for (var i = 0; i < doDCTMenu.allMenus.length; ++i) + { + doDCTMenu.allMenus[i].addExitLoopKey(KEY_LEFT); + doDCTMenu.allMenus[i].addExitLoopKey(KEY_RIGHT); + doDCTMenu.allMenus[i].addExitLoopKey(KEY_ESC); + + doDCTMenu.allMenus[i].colors.border = gConfigSettings.DCTColors.MenuBorders; + doDCTMenu.allMenus[i].colors.selected = gConfigSettings.DCTColors.MenuSelectedItems; + doDCTMenu.allMenus[i].colors.unselected = gConfigSettings.DCTColors.MenuUnselectedItems; + doDCTMenu.allMenus[i].colors.hotkey = gConfigSettings.DCTColors.MenuHotkeys; + } + } + + // Boolean variables to keep track of which menus were displayed + // (for refresh purposes later) + var fileMenuDisplayed = false; + var editMenuDisplayed = false; + var sysopMenuDisplayed = false; + var helpMenuDisplayed = false; + + // Display the top menu options with "File" highlighted. + var menuNum = fileMenuNum; // 0: File, ..., 3: Help + var subMenuItemNum = 0; + var menuRetObj = displayTopMenuItems(doDCTMenu.mainMenuItemPositions, menuNum, + doDCTMenu.allMenus, pIsSysop); + // Input loop + var userInput = ""; + var matchedMenuRetval = false; // Whether one of the menu return values was matched + var continueOn = true; + while (continueOn) + { + matchedMenuRetval = valMatchesMenuCode(menuRetObj.returnVal, pIsSysop); + // If a menu return value was matched, then set userInput to it. + if (matchedMenuRetval) + userInput = menuRetObj.returnVal; + // If the user's input from the last menu was ESC, left, right, or one of the + // characters from the menus, then set userInput to the last menu keypress. + else if (inputMatchesMenuSelection(menuRetObj.userInput, pIsSysop)) + userInput = menuRetObj.userInput; + // If nothing from the menu was matched, then get a key from the user. + else + userInput = console.inkey(K_UPPER, 60000); + menuRetObj.userInput = ""; + + // If a menu return code was matched or userInput is blank (the + // timeout was hit), then exit out of the loop. + if (matchedMenuRetval || (userInput == "")) + break; + + // Take appropriate action based on the user's input. + switch (userInput) + { + case KEY_LEFT: + if (menuNum == 0) + menuNum = 3; + else + --menuNum; + // Don't allow the sysop menu for non-sysops. + if ((menuNum == sysopMenuNum) && !pIsSysop) + --menuNum; + subMenuItemNum = 0; + menuRetObj = displayTopMenuItems(doDCTMenu.mainMenuItemPositions, menuNum, doDCTMenu.allMenus, pIsSysop); + break; + case KEY_RIGHT: + if (menuNum == 3) + menuNum = 0; + else + ++menuNum; + // Don't allow the sysop menu for non-sysops. + if ((menuNum == sysopMenuNum) && !pIsSysop) + ++menuNum; + subMenuItemNum = 0; + menuRetObj = displayTopMenuItems(doDCTMenu.mainMenuItemPositions, menuNum, doDCTMenu.allMenus, pIsSysop); + break; + case KEY_UP: + case KEY_DOWN: + break; + case KEY_ENTER: // Selected an item from the menu + //displayDebugText(1, 1, "Pressed Enter", null, false); // Temporary + // Set userInput to the return code from the menu so that it will + // be returned from this function. + userInput = menuRetObj.returnVal; + case "S": // Save + case CTRL_Z: // Save + case "A": // Abort + case CTRL_A: // Abort + case "I": // Import file for sysop, or Insert/Overwrite toggle for non-sysop + case CTRL_O: // Import file (for sysop) + case "X": // Export file (for sysop) + case CTRL_X: // Export file (for sysop) + case CTRL_V: // Insert/overwrite toggle + case "F": // Find text + case CTRL_F: // Find text + case "C": // Command List + case "G": // General help + case "P": // Program info + case "E": // Edit the message + case KEY_ESC: // Edit the message + continueOn = false; + break; + default: + //displayDebugText(1, 1, "Last key:" + userInput + ":", null, true); // Temporary + break; + } + } + + // We've exited the menu, so refresh the top menu border and the message text + // on the screen. + displayTextAreaTopBorder_DCTStyle(doDCTMenu.mainMenuItemPositions.mainMenuY, pEditLeft, pEditRight); + + // Return the user's input from the menu loop. + return userInput; +} + +// This function returns whether a value matches any of the DCT Edit menu return +// values. This is used in doDCTMenu()'s input loop; +// +// Parameters: +// pVal: The value to test +// pIsSysop: Whether or not the user is a sysop +// +// Return: Boolean - Whether or not the value matches a DCT Edit menu return value. +function valMatchesMenuCode(pVal, pIsSysop) +{ + var valMatches = false; + if (pIsSysop) + { + valMatches = ((pVal == DCTMENU_FILE_SAVE) || (pVal == DCTMENU_FILE_ABORT) || + (pVal == DCTMENU_FILE_EDIT) || (pVal == DCTMENU_EDIT_INSERT_TOGGLE) || + (pVal == DCTMENU_EDIT_FIND_TEXT) || (pVal == DCTMENU_SYSOP_IMPORT_FILE) || + (pVal == DCTMENU_SYSOP_EXPORT_FILE) || (pVal == DCTMENU_HELP_COMMAND_LIST) || + (pVal == DCTMENU_HELP_GENERAL) || (pVal == DCTMENU_HELP_PROGRAM_INFO)); + } + else + { + valMatches = ((pVal == DCTMENU_FILE_SAVE) || (pVal == DCTMENU_FILE_ABORT) || + (pVal == DCTMENU_FILE_EDIT) || (pVal == DCTMENU_EDIT_INSERT_TOGGLE) || + (pVal == DCTMENU_EDIT_FIND_TEXT) || (pVal == DCTMENU_HELP_COMMAND_LIST) || + (pVal == DCTMENU_HELP_GENERAL) || (pVal == DCTMENU_HELP_PROGRAM_INFO)); + } + return valMatches; +} + +// This function returns whether a user input matches a selection from one of the +// menus. This is used in doDCTMenu()'s input loop; +// +// Parameters: +// pInput: The user input to test +// pIsSysop: Whether or not the user is a sysop +function inputMatchesMenuSelection(pInput, pIsSysop) +{ + return((pInput == KEY_ESC) || (pInput == KEY_LEFT) || + (pInput == KEY_RIGHT) || (pInput == KEY_ENTER) || + (pInput == "S") || (pInput == "A") || (pInput == "E") || + (pInput == "I") || (pIsSysop && (pInput == "X")) || + (pInput == "F") || (pInput == "C") || (pInput == "G") || + (pInput == "P")); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// DCTMenu object functions + +// Constructs a menu item for a DCT menu +function DCTMenuItem() +{ + this.text = ""; // The item text + this.hotkeyIndex = -1; // Index of the hotkey in the text (-1 for no hotkey). + this.hotkey = ""; // The shortcut key for the item (blank for no hotkey). + this.returnVal = 0; // Return value for the item +} + +// DCTMenu constructor: Constructs a DCTEdit-style menu. +// +// Parameters: +// pTopLeftX: The upper-left screen column +// pTopLeftY: The upper-left screen row +function DCTMenu(pTopLeftX, pTopLeftY) +{ + this.colors = new Object(); + // Unselected item colors + this.colors.unselected = "n7k"; + // Selected item colors + this.colors.selected = "nw"; + // Other colors + this.colors.hotkey = "hw"; + this.colors.border = "n7k"; + + this.topLeftX = 1; // Upper-left screen column + if ((pTopLeftX != null) && (pTopLeftX > 0) && (pTopLeftX <= console.screen_columns)) + this.topLeftX = pTopLeftX; + this.topLeftY = 1; // Upper-left screen row + if ((pTopLeftY != null) && (pTopLeftY > 0) && (pTopLeftY <= console.screen_rows)) + this.topLeftY = pTopLeftY; + this.width = 0; + this.height = 0; + this.selectedItemIndex = 0; + + // this.menuItems will contain DCTMenuItem objects. + this.menuItems = new Array(); + // hotkeyRetvals is an array, indexed by hotkey, that contains + // the return values for each hotkey. + this.hotkeyRetvals = new Array(); + + // exitLoopKeys will contain keys that will exit the input loop + // when pressed by the user, so that calling code can catch them. + // It's indexed by the key, and the value is the return code that + // should be returned for that key. + this.exitLoopKeys = new Array(); + + // Border style: "single" or "double" + this.borderStyle = "single"; + + // clearSpaceAroundMenu controls whether or not to clear one space + // around the menu when it is drawn. + this.clearSpaceAroundMenu = false; + // clearSpaceColor controls which color to use when drawing the + // clear space around the menu. + this.clearSpaceColor = "n"; + // clearSpaceTopText specifies text to display above the top of the + // menu when clearing space around it. + this.clearSpaceTopText = ""; + + // Timeout (in milliseconds) for the input loop. Default to 1 minute. + this.timeoutMS = 60000; + + // Member functions + this.addItem = DCTMenu_AddItem; + this.addExitLoopKey = DCTMenu_AddExitLoopKey; + this.displayItem = DCTMenu_DisplayItem; + this.doInputLoop = DCTMenu_DoInputLoop; + this.numItems = DCTMenu_NumItems; + this.removeAllItems = DCTMenu_RemoveAllItems; +} +// Adds an item to the DCTMenu. +// +// Parameters: +// pText: The text of the menu item. Note that a & precedes a hotkey. +// pReturnVal: The value to return upon selection of the item +function DCTMenu_AddItem(pText, pReturnVal) +{ + if (pText == "") + return; + + var item = new DCTMenuItem(); + item.returnVal = pReturnVal; + // Look for a & in pText, and if one is found, use the next character as + // its hotkey. + var ampersandIndex = pText.indexOf("&"); + if (ampersandIndex > -1) + { + // If pText has text after ampersandIndex, then set up + // the next character as the hotkey in the item. + if (pText.length > ampersandIndex+1) + { + item.hotkeyIndex = ampersandIndex; + item.hotkey = pText.substr(ampersandIndex+1, 1); + // Set the text of the item. The text should not include + // the ampersand. + item.text = pText.substr(0, ampersandIndex) + pText.substr(ampersandIndex+1); + // Add the hotkey & return value to this.hotkeyRetvals + this.hotkeyRetvals[item.hotkey.toUpperCase()] = pReturnVal; + + // If the menu is not wide enough for this item's text, then + // update this.width. + if (this.width < item.text.length + 2) + this.width = item.text.length + 2; + // Also update this.height + if (this.height == 0) + this.height = 3; + else + ++this.height; + } + else + { + // pText does not have text after ampersandIndex. + item.text = pText.substr(0, ampersandIndex); + } + } + else + { + // No ampersand was found in pText. + item.text = pText; + } + + // Add the item to this.menuItems + this.menuItems.push(item); +} +// Adds a key that will exit the input loop when pressed by the user. +// +// Parameters: +// pKey: The key that should cause the input loop to exit +// pReturnValue: The return value of the input loop when the key is pressed. +// If this is not specified, a default value of -1 will be used. +function DCTMenu_AddExitLoopKey(pKey, pReturnValue) +{ + var val = -1; + if ((pReturnValue != null) && (typeof(pReturnValue) != "undefined")) + val = pReturnValue; + + this.exitLoopKeys[pKey] = val; +} +// Displays an item on the menu. +// +// Parameters: +// pItemIndex: The index of the item in the menuItems array +// pPrintBorders: Boolean - Whether or not to display the horizontal +// borders on each side of the menu item text. +function DCTMenu_DisplayItem(pItemIndex, pPrintBorders) +{ + var printBorders = false; + if (pPrintBorders != null) + printBorders = pPrintBorders; + + // Determine whether to use the selected item color or unselected + // item color. + var itemColor = ""; + if (pItemIndex == this.selectedItemIndex) + itemColor = this.colors.selected; + else + itemColor = this.colors.unselected; + + // Print the menu item text on the screen. + if (printBorders) + { + console.gotoxy(this.topLeftX, this.topLeftY + pItemIndex + 1); + if (this.borderStyle == "single") + console.print(this.colors.border + VERTICAL_SINGLE); + else if (this.borderStyle == "double") + console.print(this.colors.border + VERTICAL_DOUBLE); + } + else + console.gotoxy(this.topLeftX + 1, this.topLeftY + pItemIndex + 1); + // If the menu item has a hotkey, then write the appropriate character + // in the hotkey color. + if (this.menuItems[pItemIndex].hotkeyIndex > -1) + { + console.print(itemColor + + this.menuItems[pItemIndex].text.substr(0, this.menuItems[pItemIndex].hotkeyIndex) + + this.colors.hotkey + this.menuItems[pItemIndex].hotkey + itemColor + + this.menuItems[pItemIndex].text.substr(this.menuItems[pItemIndex].hotkeyIndex + 1)); + } + else + { + console.print(itemColor + this.menuItems[pItemIndex].text); + } + // If the item text isn't wide enough to fill the entire inner width, then + // clear the line up until the right border. + var innerWidth = this.width - 2; + if (this.menuItems[pItemIndex].text.length < innerWidth) + { + for (var i = this.menuItems[pItemIndex].text.length; i < innerWidth; ++i) + console.print(" "); + } + // Print the right border character if specified. + if (printBorders) + { + if (this.borderStyle == "single") + console.print(this.colors.border + VERTICAL_SINGLE); + else if (this.borderStyle == "double") + console.print(this.colors.border + VERTICAL_DOUBLE); + } +} +// Displays the DCT menu and enters the input loop. +// +// Return value: An object containing the following properties: +// returnVal: The return code of the item selected, or -1 if no +// item was selected. +// userInput: The last user input +function DCTMenu_DoInputLoop() +{ + var returnObj = new Object(); + returnObj.returnVal = -1; + returnObj.userInput = ""; + + // If clearSpaceAroundMenu is true, then draw a blank row + // above the menu. + if (this.clearSpaceAroundMenu && (this.topLeftY > 1)) + { + // If there is room, output a space to the left, diagonal + // from the top-left corner of the menu. + if (this.topLeftX > 1) + { + console.gotoxy(this.topLeftX-1, this.topLeftY-1); + console.print(this.clearSpaceColor + " "); + } + else + console.gotoxy(this.topLeftX, this.topLeftY-1); + + // Output this.clearSpaceTopText + console.print(this.clearSpaceTopText); + // Output the rest of the blank space + var textLen = strip_ctrl(this.clearSpaceTopText).length; + if (textLen < this.width) + { + var numSpaces = this.width - textLen; + if (this.topLeftX + this.width < console.screen_columns) + ++numSpaces; + for (var i = 0; i < numSpaces; ++i) + console.print(this.clearSpaceColor + " "); + } + } + + // Before drawing the top border, if clearSpaceAroundMenu is + // true, put space before the border. + if (this.clearSpaceAroundMenu && (this.topLeftY > 1)) + { + console.gotoxy(this.topLeftX-1, this.topLeftY); + console.print(this.clearSpaceColor + " "); + } + else + console.gotoxy(this.topLeftX, this.topLeftY); + // Draw the top border + var innerWidth = this.width - 2; + if (this.borderStyle == "single") + { + console.print(this.colors.border + UPPER_LEFT_SINGLE); + for (var i = 0; i < innerWidth; ++i) + console.print(HORIZONTAL_SINGLE); + console.print(this.colors.border + UPPER_RIGHT_SINGLE); + } + else if (this.borderStyle == "double") + { + console.print(this.colors.border + UPPER_LEFT_DOUBLE); + for (var i = 0; i < innerWidth; ++i) + console.print(HORIZONTAL_DOUBLE); + console.print(this.colors.border + UPPER_RIGHT_DOUBLE); + } + // If clearSpaceAroundMenu is true, then put a space after the border. + if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns)) + console.print(this.clearSpaceColor + " "); + + // Print the menu items (and side spaces outside the menu if + // clearSpaceAroundMenu is true). + var itemColor = ""; + for (var i = 0; i < this.menuItems.length; ++i) + { + if (this.clearSpaceAroundMenu && (this.topLeftX > 1)) + { + console.gotoxy(this.topLeftX-1, this.topLeftY + i + 1); + console.print(this.clearSpaceColor + " "); + } + + this.displayItem(i, true); + + if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns)) + { + console.gotoxy(this.topLeftX + this.width, this.topLeftY + i + 1); + console.print(this.clearSpaceColor + " "); + } + } + + // Before drawing the bottom border, if clearSpaceAroundMenu is + // true, put space before the border. + if (this.clearSpaceAroundMenu && (this.topLeftY > 1)) + { + console.gotoxy(this.topLeftX - 1, this.topLeftY + this.height - 1); + console.print(this.clearSpaceColor + " "); + } + else + console.gotoxy(this.topLeftX, this.topLeftY + this.height - 1); + // Draw the bottom border + if (this.borderStyle == "single") + { + console.print(this.colors.border + LOWER_LEFT_SINGLE); + for (var i = 0; i < innerWidth; ++i) + console.print(HORIZONTAL_SINGLE); + console.print(LOWER_RIGHT_SINGLE); + } + else if (this.borderStyle == "double") + { + console.print(this.colors.border + LOWER_LEFT_DOUBLE); + for (var i = 0; i < innerWidth; ++i) + console.print(HORIZONTAL_DOUBLE); + console.print(LOWER_RIGHT_DOUBLE); + } + // If clearSpaceAroundMenu is true, then put a space after the border. + if (this.clearSpaceAroundMenu && (this.topLeftX + this.width < console.screen_columns)) + console.print(this.clearSpaceColor + " "); + + // If clearSpaceAroundMenu is true, then draw a blank row + // below the menu. + if (this.clearSpaceAroundMenu && (this.topLeftY + this.height < console.screen_rows)) + { + var numSpaces = this.width + 2; + if (this.topLeftX > 1) + console.gotoxy(this.topLeftX-1, this.topLeftY + this.height); + else + { + console.gotoxy(this.topLeftX, this.topLeftY + this.height); + --numSpaces; + } + + if (this.topLeftX + this.width >= console.screen_columns) + --numSpaces; + + for (var i = 0; i < numSpaces; ++i) + console.print(this.clearSpaceColor + " "); + } + + // Place the cursor on the line of the selected item + console.gotoxy(this.topLeftX + 1, this.topLeftY + this.selectedItemIndex + 1); + + // Keep track of the current cursor position + var curpos = new Object(); + curpos.x = this.topLeftX + 1; + curpos.y = this.topLeftY + this.selectedItemIndex + 1; + + // Input loop + const topItemLineNumber = this.topLeftY + 1; + const bottomItemLineNumber = this.topLeftY + this.height - 1; + var continueOn = true; + while (continueOn) + { + // Get a key, (time out after the selected time), and take appropriate action. + //returnObj.userInput = console.inkey(0, this.timeoutMS).toUpperCase(); + returnObj.userInput = console.getkey(K_NONE); + // If the user input is blank, then the input timed out, and we should quit. + if (returnObj.userInput == "") + { + continueOn = false; + break; + } + + // Take appropriate action, depending on the user's keypress. + switch (returnObj.userInput) + { + case KEY_ENTER: + // Set returnObj.returnVal to the currently-selected item's returnVal, + // and exit the input loop. + returnObj.returnVal = this.menuItems[this.selectedItemIndex].returnVal; + continueOn = false; + break; + case KEY_UP: + // Go up one item + if (this.menuItems.length > 1) + { + // If we're below the top menu item, then go up one item. Otherwise, + // go to the last menu item. + var oldIndex = this.selectedItemIndex; + if ((curpos.y > topItemLineNumber) && (this.selectedItemIndex > 0)) + { + --curpos.y; + --this.selectedItemIndex; + } + else + { + curpos.y = bottomItemLineNumber - 1; + this.selectedItemIndex = this.menuItems.length - 1; + } + // Refresh the items on the screen so that the item colors + // are updated. + this.displayItem(oldIndex, false); + this.displayItem(this.selectedItemIndex, false); + } + break; + case KEY_DOWN: + // Go down one item + if (this.menuItems.length > 1) + { + // If we're above the bottom menu item, then go down one item. Otherwise, + // go to the first menu item. + var oldIndex = this.selectedItemIndex; + if ((curpos.y < bottomItemLineNumber) && (this.selectedItemIndex < this.menuItems.length - 1)) + { + ++curpos.y; + ++this.selectedItemIndex; + } + else + { + curpos.y = this.topLeftY + 1; + this.selectedItemIndex = 0; + } + // Refresh the items on the screen so that the item colors + // are updated. + this.displayItem(oldIndex, false); + this.displayItem(this.selectedItemIndex, false); + } + break; + case KEY_ESC: + continueOn = false; + break; + default: + // If the user's input is one of the hotkeys, then stop the + // input loop and return with the return code for the hotkey. + if (typeof(this.hotkeyRetvals[returnObj.userInput]) != "undefined") + { + returnObj.returnVal = this.hotkeyRetvals[returnObj.userInput]; + continueOn = false; + } + // If the user's input is one of the loop-exit keys, then stop + // the input loop. + else if (typeof(this.exitLoopKeys[returnObj.userInput]) != "undefined") + { + returnObj.returnVal = this.exitLoopKeys[returnObj.userInput]; + continueOn = false; + } + break; + } + } + + return returnObj; +} +// Returns the number of items in the menu. +function DCTMenu_NumItems() +{ + return this.menuItems.length; +} +// Removes all items from a DCTMenu. +function DCTMenu_RemoveAllItems() +{ + this.width = 0; + this.height = 0; + this.selectedItemIndex = 0; + this.menuItems = new Array(); + this.hotkeyRetvals = new Array(); + this.exitLoopKeys = new Array(); +} + +// Returns the the script's execution directory. +// The code in this function is a trick that was created by Deuce, suggested +// by Rob Swindell as a way to detect which directory the script was executed +// in. I've shortened the code a little. +function getScriptDir() +{ + var startup_path = '.'; + try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; } + return(backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''))); +} \ No newline at end of file diff --git a/exec/SlyEdit_IceStuff.js b/exec/SlyEdit_IceStuff.js new file mode 100644 index 0000000000..7e193e987f --- /dev/null +++ b/exec/SlyEdit_IceStuff.js @@ -0,0 +1,783 @@ +/* This contains some IceEdit-specific functions for the Digital Distortion + * Editor which don't rely on any global variables. + * + * Author: Eric Oulashin (AKA Nightfox) + * BBS: Digital Distortion + * BBS address: digdist.bbsindex.com + * + * Date User Description + * 2009-06-06 Eric Oulashin Started development + * 2009-08-09 Eric Oulashin More development & testing + * 2009-08-22 Eric Oulashin Version 1.00 + * Initial public release + * 2009-12-03 Eric Oulashin Added support for color schemes. + * Added displayIceYesNoText() and + * readColorConfig(). + * 2010-01-02 Eric Oulashin Removed abortConfirm_DCTStyle(), + * since it's no longer used anymore. + * 2011-02-02 Eric Oulashin Moved the time displaying code into + * a new function, displayTime_IceStyle(). + * 2012-02-18 Eric Oulashin Changed the copyright year to 2012 + * 2012-12-21 Eric Oulashin Removed gStartupPath from the beginning + * of the theme filename, since the path is + * now set in ReadSlyEditConfigFile() in + * SlyEdit_Misc.js. + */ + +load("sbbsdefs.js"); +load(gStartupPath + "SlyEdit_Misc.js"); + +// IceEdit ESC menu item return values +var ICE_ESC_MENU_SAVE = 0; +var ICE_ESC_MENU_ABORT = 1; +var ICE_ESC_MENU_EDIT = 2; +var ICE_ESC_MENU_HELP = 3; + +// Read the color configuration file +readColorConfig(gConfigSettings.iceColors.ThemeFilename); + + +/////////////////////////////////////////////////////////////////////////////////// +// Functions + +// This function reads the color configuration for Ice style. +// +// Parameters: +// pFilename: The name of the color configuration file +function readColorConfig(pFilename) +{ + var colors = readValueSettingConfigFile(pFilename, 512); + if (colors != null) + gConfigSettings.iceColors = colors; +} + +// Re-draws the screen, in the style of IceEdit. +// +// Parameters: +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +// pEditTop: The topmost row of the edit area +// pEditBottom: The bottommost row of the edit area +// pEditColor: The edit color +// pInsertMode: The insert mode ("INS" or "OVR") +// pUseQuotes: Whether or not message quoting is enabled +// pEditLinesIndex: The index of the message line at the top of the edit area +// pDisplayEditLines: The function that displays the edit lines +function redrawScreen_IceStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEditColor, + pInsertMode, pUseQuotes, pEditLinesIndex, pDisplayEditLines) +{ + // Top header + // Generate & display the top border line (Note: Generate this + // border line only once, for efficiency). + if (typeof(redrawScreen_IceStyle.topBorder) == "undefined") + { + redrawScreen_IceStyle.topBorder = UPPER_LEFT_SINGLE; + var innerWidth = console.screen_columns - 2; + for (var i = 0; i < innerWidth; ++i) + redrawScreen_IceStyle.topBorder += HORIZONTAL_SINGLE; + redrawScreen_IceStyle.topBorder += UPPER_RIGHT_SINGLE; + redrawScreen_IceStyle.topBorder = randomTwoColorString(redrawScreen_IceStyle.topBorder, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + } + // Print the border line on the screen + console.clear(); + console.print(redrawScreen_IceStyle.topBorder); + + // Next line + // To name + var lineNum = 2; + console.gotoxy(1, lineNum); + // Calculate the width of the user alias field: 28 characters, based + // on an 80-column screen width. + var fieldWidth = (console.screen_columns * (29/80)).toFixed(0); + var screenText = gToName.substr(0, fieldWidth); + console.print("n" + randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + + gConfigSettings.iceColors.TopInfoBkgColor + " " + + gConfigSettings.iceColors.TopLabelColor + "TO" + + gConfigSettings.iceColors.TopLabelColonColor + ": " + + gConfigSettings.iceColors.TopToColor + screenText); + fieldWidth -= screenText.length; + for (var i = 0; i < fieldWidth; ++i) + console.print(" "); + + // From name + fieldWidth = (console.screen_columns * (29/80)).toFixed(0); + screenText = gFromName.substr(0, fieldWidth); + console.print(" " + gConfigSettings.iceColors.TopLabelColor + "FROM" + + gConfigSettings.iceColors.TopLabelColonColor + ": " + + gConfigSettings.iceColors.TopFromColor + screenText); + fieldWidth -= screenText.length; + for (var i = 0; i < fieldWidth; ++i) + console.print(" "); + // More spaces until the time location + var curpos = console.getxy(); + var startX = console.screen_columns - 8; + while (curpos.x < startX) + { + console.print(" "); + ++curpos.x; + } + + // Time + console.print(" "); + displayTime_IceStyle(); + console.print(" " + randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2)); + + // Next line: Subject, time left, insert/overwrite mode + ++lineNum; + console.gotoxy(1, lineNum); + // Subject + fieldWidth = (console.screen_columns * (54/80)).toFixed(0); + screenText = gMsgSubj.substr(0, fieldWidth); + console.print("n" + randomTwoColorString(VERTICAL_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + + gConfigSettings.iceColors.TopInfoBkgColor + " " + + gConfigSettings.iceColors.TopLabelColor + "SUBJECT" + + gConfigSettings.iceColors.TopLabelColonColor + ": " + + gConfigSettings.iceColors.TopSubjectColor + screenText); + fieldWidth -= screenText.length; + for (var i = 0; i < fieldWidth; ++i) + console.print(" "); + + // Time left + fieldWidth = (console.screen_columns * (4/80)).toFixed(0); + screenText = Math.floor(bbs.time_left / 60).toString().substr(0, fieldWidth); + startX = console.screen_columns - fieldWidth - 10; + // Before outputting the time left, write more spaces until the starting + // horizontal location. + curpos = console.getxy(); + while (curpos.x < startX) + { + console.print(" "); + ++curpos.x; + } + console.print(" " + gConfigSettings.iceColors.TopLabelColor + "TL" + + gConfigSettings.iceColors.TopLabelColonColor + ": " + + gConfigSettings.iceColors.TopTimeLeftColor, screenText); + fieldWidth -= screenText.length; + for (var i = 0; i < fieldWidth; ++i) + console.print(" "); + + // Insert/overwrite mode + console.print(" " + gConfigSettings.iceColors.EditMode + pInsertMode + " n" + + randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2)); + + // Next line: Top border for the message area and also includes the user #, + // message area, and node #. + // Generate this border line only once, for efficiency. + if (typeof(redrawScreen_IceStyle.msgAreaBorder) == "undefined") + { + redrawScreen_IceStyle.msgAreaBorder = "n" + + randomTwoColorString(LEFT_T_SINGLE + HORIZONTAL_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + // User #, padded with high-black dim block characters, 5 characters for a screen + // that's 80 characters wide. + + "h" + THIN_RECTANGLE_LEFT + "#k"; + fieldWidth = (console.screen_columns * (5/80)).toFixed(0) - user.number.toString().length; + for (var i = 0; i < fieldWidth; ++i) + redrawScreen_IceStyle.msgAreaBorder += BLOCK1; + redrawScreen_IceStyle.msgAreaBorder += "c" + user.number + + gConfigSettings.iceColors.BorderColor1 + + THIN_RECTANGLE_RIGHT; + + // The message area name should be centered on the line. So, based on its + // length (up to 20 characters), figure out its starting position before + // printing it. + var msgAreaName = gMsgArea.substr(0, 20); + // 2 is subtracted from the starting position to leave room for the + // block character and the space. + var startPos = (console.screen_columns/2).toFixed(0) - (msgAreaName.length/2).toFixed(0) - 2; + // Write border characters up to the message area name start position + screenText = ""; + for (var i = strip_ctrl(redrawScreen_IceStyle.msgAreaBorder).length; i < startPos; ++i) + screenText += HORIZONTAL_SINGLE; + redrawScreen_IceStyle.msgAreaBorder += randomTwoColorString(screenText, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + + // Write the message area name + redrawScreen_IceStyle.msgAreaBorder += "h" + gConfigSettings.iceColors.BorderColor1 + + THIN_RECTANGLE_LEFT + " " + iceText(msgAreaName, "w") + " h" + + gConfigSettings.iceColors.BorderColor1 + THIN_RECTANGLE_RIGHT; + + // Calculate the field width for the node number field. + // For the node # field, use 3 characters for a screen 80 characters wide. + fieldWidth = (console.screen_columns * (3/80)).toFixed(0); + // Calculate the horizontal starting position for the node field. + var nodeFieldStartPos = console.screen_columns - fieldWidth - 9; + + // Write horizontal border characters up until the point where we'll output + // the node number. + screenText = ""; + for (var posX = strip_ctrl(redrawScreen_IceStyle.msgAreaBorder).length; posX < nodeFieldStartPos; ++posX) + screenText += HORIZONTAL_SINGLE; + redrawScreen_IceStyle.msgAreaBorder += randomTwoColorString(screenText, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + + // Output the node # field + redrawScreen_IceStyle.msgAreaBorder += "h" + gConfigSettings.iceColors.BorderColor1 + + THIN_RECTANGLE_LEFT + iceText("Node", "w") + "nb:hk"; + fieldWidth -= bbs.node_num.toString().length; + for (var i = 0; i < fieldWidth; ++i) + redrawScreen_IceStyle.msgAreaBorder += BLOCK1; + redrawScreen_IceStyle.msgAreaBorder += "c" + bbs.node_num + + gConfigSettings.iceColors.BorderColor1 + THIN_RECTANGLE_RIGHT; + + // Write the last 2 characters of top border + redrawScreen_IceStyle.msgAreaBorder += randomTwoColorString(HORIZONTAL_SINGLE + RIGHT_T_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + } + // Draw the border line on the screen + ++lineNum; + console.gotoxy(1, lineNum); + console.print(redrawScreen_IceStyle.msgAreaBorder); + + // Display the bottom message area border and help line + DisplayTextAreaBottomBorder_IceStyle(pEditBottom + 1, pUseQuotes); + DisplayBottomHelpLine_IceStyle(console.screen_rows, pUseQuotes); + + // If the screen is at least 82 columns wide output vertical lines + // to frame the edit area. + if (console.screen_columns >= 82) + { + for (lineNum = pEditTop; lineNum <= pEditBottom; ++lineNum) + { + console.gotoxy(pEditLeft-1, lineNum); + console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2)); + console.gotoxy(pEditRight+1, lineNum); + console.print(randomTwoColorString(VERTICAL_SINGLE, gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2)); + } + } + + // Go to the start of the edit area + console.gotoxy(pEditLeft, pEditTop); + + // Write the message text that has been entered thus far. + pDisplayEditLines(pEditTop, pEditLinesIndex); + console.print(pEditColor); +} + +// Displays the first help line for the bottom of the screen, in the style +// of IceEdit. +// +// Parameters: +// pLineNum: The line number on the screen where the text should be placed +// pUseQuotes: Whether or not message quoting is enabled +// pCanChgMsgColor: Whether or not changing the text color is allowed +// The following parameters are not used; this is here to match the function signature of DCTEdit version. +// pEditLeft +// pEditRight +// pInsertMode +function DisplayTextAreaBottomBorder_IceStyle(pLineNum, pUseQuotes, pEditLeft, pEditRight, + pInsertMode, pCanChgMsgColor) +{ + // The border will use random bright/normal colors. The colors + // should stay the same each time we draw it, so a "static" + // variable is used for the border text. If that variable has + // not been defined yet, then build it. + if (typeof(DisplayTextAreaBottomBorder_IceStyle.border) == "undefined") + { + // Build the string of CTRL key combinations that will be displayed + var ctrlKeyHelp = gConfigSettings.iceColors.KeyInfoLabelColor + + "CTRL b(nwAhb)n" + + gConfigSettings.iceColors.KeyInfoLabelColor + "Abort"; + if (pUseQuotes) + { + ctrlKeyHelp += " b(nwQhb)n" + + gConfigSettings.iceColors.KeyInfoLabelColor + "Quote"; + } + ctrlKeyHelp += " b(nwZhb)n" + gConfigSettings.iceColors.KeyInfoLabelColor + + "Save"; + + // Start the border text with the first 2 border characters + // The beginning of this line shows that SlyEdit is registered + // to the sysop. :) + DisplayTextAreaBottomBorder_IceStyle.border = + randomTwoColorString(LOWER_LEFT_SINGLE + HORIZONTAL_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + + "h" + gConfigSettings.iceColors.BorderColor1 + THIN_RECTANGLE_LEFT + + iceText("Registered To: " + system.operator.substr(0, 20), "w") + + "h" + gConfigSettings.iceColors.BorderColor1 + THIN_RECTANGLE_RIGHT; + // Append border characters up until the point we'll have to write the CTRL key + // help text. + var screenText = ""; + var endPos = console.screen_columns - strip_ctrl(ctrlKeyHelp).length - 3; + var textLen = strip_ctrl(DisplayTextAreaBottomBorder_IceStyle.border).length; + for (var i = textLen+1; i < endPos; ++i) + screenText += HORIZONTAL_SINGLE; + DisplayTextAreaBottomBorder_IceStyle.border += randomTwoColorString(screenText, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + + // CTRL key help and the remaining 2 characters in the border. + DisplayTextAreaBottomBorder_IceStyle.border += "h" + gConfigSettings.iceColors.BorderColor1 + + THIN_RECTANGLE_LEFT + ctrlKeyHelp + gConfigSettings.iceColors.BorderColor1 + + THIN_RECTANGLE_RIGHT + + randomTwoColorString(HORIZONTAL_SINGLE + LOWER_RIGHT_SINGLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + } + + // Display the border line on the screen + // If pLineNum is not specified, then default to the 2nd to the last + // line on the screen. + var lineNum = console.screen_rows-1; + if ((typeof(pLineNum) != "undefined") && (pLineNum != null)) + lineNum = pLineNum; + console.gotoxy(1, lineNum); + console.print(DisplayTextAreaBottomBorder_IceStyle.border); +} + +// Displays the second (lower) help line for the bottom of the screen, +// in the style of IceEdit. +// +// Parameters: +// pLineNum: The line number on the screen where the text should be placed +// The following are not used and are only here to match the DCT-style function: +// pUsingQuotes: Boolean - Whether or not message quoting is enabled. +function DisplayBottomHelpLine_IceStyle(pLineNum, pUsingQuotes) +{ + // Construct the help text only once + if (typeof(DisplayBottomHelpLine_IceStyle.helpText) == "undefined") + { + // This line contains the copyright mesage & ESC key help + var screenText = iceText(EDITOR_PROGRAM_NAME + " v", "w") + "ch" + + EDITOR_VERSION.toString() + " " + + iceText("Copyright", "w") + " ch2012 " + + iceText("Eric Oulashin", "w") + " nb" + DOT_CHAR + " " + + iceText("Press ESCape For Help", "w"); + // Calculate the starting position to center the help text, and front-pad + // DisplayBottomHelpLine_IceStyle.helpText with that many spaces. + var xPos = (console.screen_columns / 2).toFixed(0) + - (strip_ctrl(screenText).length / 2).toFixed(0); + DisplayBottomHelpLine_IceStyle.helpText = ""; + for (var i = 0; i < xPos; ++i) + DisplayBottomHelpLine_IceStyle.helpText += " "; + DisplayBottomHelpLine_IceStyle.helpText += screenText; + } + + // If pLineNum is not specified, then default to the last line + // on the screen. + var lineNum = console.screen_rows; + if ((typeof(pLineNum) != "undefined") && (pLineNum != null)) + lineNum = pLineNum; + // Display the help text on the screen + console.gotoxy(1, lineNum); + console.print(DisplayBottomHelpLine_IceStyle.helpText); +} + +// Updates the insert mode displayd on the screen, for Ice Edit style. +// +// Parameters: +// pEditRight: Not used; this is only here to match the signature of the DCTEdit version. +// pEditBottom: Not used; this is only here to match the signature of the DCTEdit version. +// pInsertMode: The insert mode ("INS" or "OVR") +function updateInsertModeOnScreen_IceStyle(pEditRight, pEditBottom, pInsertMode) +{ + console.gotoxy(console.screen_columns-4, 3); + console.print(gConfigSettings.iceColors.TopInfoBkgColor + gConfigSettings.iceColors.EditMode + + pInsertMode); +} + +// Draws the top border of the quote window, IceEdit style. +// +// Parameters: +// pQuoteWinHeight: The height of the quote window +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +function DrawQuoteWindowTopBorder_IceStyle(pQuoteWinHeight, pEditLeft, pEditRight) +{ + // Top border of the quote window + // The border will use random bright/normal colors. The colors + // should stay the same each time we draw it, so a "static" + // variable is used for the border text. If that variable has + // not been defined yet, then build it. + if (typeof(DrawQuoteWindowTopBorder_IceStyle.border) == "undefined") + { + DrawQuoteWindowTopBorder_IceStyle.border = randomTwoColorString(UPPER_LEFT_VSINGLE_HDOUBLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.QuoteWinBorderTextColor + "Quote Window" + + gConfigSettings.iceColors.BorderColor2 + + THIN_RECTANGLE_RIGHT; + // The border from here to the end of the line: Random high/low blue + var screenText = ""; + for (var posX = pEditLeft+16; posX <= pEditRight; ++posX) + screenText += HORIZONTAL_DOUBLE; + screenText += UPPER_RIGHT_VSINGLE_HDOUBLE; + DrawQuoteWindowTopBorder_IceStyle.border += randomTwoColorString(screenText, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + } + + // Draw the border line on the screen + console.gotoxy(pEditLeft, console.screen_rows - pQuoteWinHeight + 1); + console.print(DrawQuoteWindowTopBorder_IceStyle.border); +} + +// Draws the bottom border of the quote window, IceEdit style. Note: +// The cursor should be placed at the start of the line (leftmost screen +// column on the screen line where the border should be drawn). +// +// Parameters: +// pEditLeft: The leftmost column of the edit area +// pEditRight: The rightmost column of the edit area +function DrawQuoteWindowBottomBorder_IceStyle(pEditLeft, pEditRight) +{ + // The border will use random bright/normal colors. The colors + // should stay the same each time we draw it, so a "static" + // variable is used for the border text. If that variable has + // not been defined yet, then build it. + if (typeof(DrawQuoteWindowBottomBorder_IceStyle.border) == "undefined") + { + DrawQuoteWindowBottomBorder_IceStyle.border = randomTwoColorString(LOWER_LEFT_VSINGLE_HDOUBLE, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2) + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.QuoteWinBorderTextColor + "^Q/ESC-End" + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT + + gConfigSettings.iceColors.BorderColor1 + HORIZONTAL_DOUBLE + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.QuoteWinBorderTextColor + "CR-Accept" + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT + + gConfigSettings.iceColors.BorderColor1 + HORIZONTAL_DOUBLE + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.QuoteWinBorderTextColor + "Up/Down-Scroll" + + gConfigSettings.iceColors.BorderColor2 + THIN_RECTANGLE_RIGHT; + // The border from here to the end of the line: Random high/low blue + var screenText = ""; + for (var posX = pEditLeft + 43; posX <= pEditRight; ++posX) + screenText += HORIZONTAL_DOUBLE; + screenText += LOWER_RIGHT_VSINGLE_HDOUBLE; + DrawQuoteWindowBottomBorder_IceStyle.border += randomTwoColorString(screenText, + gConfigSettings.iceColors.BorderColor1, + gConfigSettings.iceColors.BorderColor2); + } + + // Draw the border line on the screen + console.print(DrawQuoteWindowBottomBorder_IceStyle.border); +} + +// Prompts the user for a yes/no question, IceEdit-style. Note that +// the cursor should be position where it needs to be before calling +// this function. +// +// Parameters: +// pQuestion: The question to ask, without the trailing "?" +// pDefaultYes: Whether to default to "yes" (true/false). If false, this +// will default to "no". +function promptYesNo_IceStyle(pQuestion, pDefaultYes) +{ + var userResponse = pDefaultYes; + + // Print the question, and highlight "yes" or "no", depending on + // the value of pDefaultYes. + console.print(iceText(pQuestion + "? ", "w")); + displayIceYesNoText(pDefaultYes); + + // yesNoX contains the horizontal position for the "Yes" & "No" text. + const yesNoX = strip_ctrl(pQuestion).length + 3; + + // Input loop + var userInput = ""; + var continueOn = true; + while (continueOn) + { + // Move the cursor to the start of the "Yes" or "No" text (whichever + // one is currently selected). + console.gotoxy(userResponse ? yesNoX : yesNoX+7, console.screen_rows); + // Get a key, (time out after 1 minute), and take appropriate action. + userInput = console.inkey(0, 100000).toUpperCase(); + // If userInput is blank, then the timeout was hit, so exit the loop. + // Also exit the loop of the user pressed enter. + if ((userInput == "") || (userInput == KEY_ENTER)) + continueOn = false; + else if (userInput == "Y") + { + userResponse = true; + continueOn = false; + } + else if (userInput == "N") + { + userResponse = false; + continueOn = false; + } + // Left or right arrow key: Toggle userResponse and update the + // yes/no text with the appropriate colors + else if ((userInput == KEY_LEFT) || (userInput == KEY_RIGHT)) + { + // Move the cursor to the start of the "Yes" and "No" text, and + // update the text depending on the value of userResponse. + console.gotoxy(yesNoX, console.screen_rows); + userResponse = !userResponse; + displayIceYesNoText(userResponse); + } + } + + return userResponse; +} + +// Displays the time on the screen. +// +// Parameters: +// pTimeStr: The time to display (optional). If this is omitted, +// then this funtion will get the current time. +function displayTime_IceStyle(pTimeStr) +{ + console.gotoxy(73, 2); + if (pTimeStr == null) + console.print(gConfigSettings.iceColors.TopInfoBkgColor + gConfigSettings.iceColors.TopTimeColor + getCurrentTimeStr()); + else + console.print(gConfigSettings.iceColors.TopInfoBkgColor + gConfigSettings.iceColors.TopTimeColor + pTimeStr); +} + +// For IceEdit mode: This function takes a string and returns a copy +// of the string with uppercase letters dim and the rest bright for +// a given color. +// +// Parameters: +// pString: The string to convert +// pColor: The Synchronet color code to use as the base text color. +// pBkgColor: Optional - The background color (a Synchronet color code). +// Defaults to normal white. +function iceText(pString, pColor, pBkgColor) +{ + // Return if an invalid string is passed in. + if (typeof(pString) == "undefined") + return ""; + if (pString == null) + return ""; + + // Set the color. Default to blue. + var color = "b"; + if ((typeof(pColor) != "undefined") && (pColor != null)) + color = pColor; + + // Set the background color. Default to normal black. + var bkgColor = "nk"; + if ((typeof(pBkgColor) != "undefined") && (pBkgColor != null)) + bkgColor = pBkgColor; + + // Create a copy of the string without any control characters, + // and then add our coloring to it. + pString = strip_ctrl(pString); + var returnString = "n" + bkgColor + color; + var lastColor = "n" + color; + var character = ""; + for (var i = 0; i < pString.length; ++i) + { + character = pString.charAt(i); + + // Upper-case letters: Make it dim with the passed-in color. + if (character.match(/[A-Z]/) != null) + { + // If the last color was not the normal color, then append + // the normal color to returnString. + if (lastColor != "n" + color) + returnString += "n" + bkgColor + color; + lastColor = "n" + color; + } + // Lower-case letter: Make it bright with the passed-in color. + else if (character.match(/[a-z]/) != null) + { + // If this is the first character or if the last color was + // not the bright color, then append the bright color to + // returnString. + if ((i == 0) || (lastColor != ("h" + color))) + returnString += "h" + color; + lastColor = "h" + color; + } + // Number: Make it bright cyan + else if (character.match(/[0-9]/) != null) + { + // If the last color was not bright cyan, then append + // bright cyan to returnString. + if (lastColor != "hc") + returnString += "hc"; + lastColor = "hc"; + } + // All else: Make it bright blue + else + { + // If the last color was not bright cyan, then append + // bright cyan to returnString. + if (lastColor != "hb") + returnString += "hb"; + lastColor = "hb"; + } + + // Append the character from pString. + returnString += character; + } + + return returnString; +} + +// Displays & handles the input loop for the DCT Edit menu. +// +// Parameters: +// pY: The line number on the screen for the menu +// +// Return value: One of the ICE_ESC_MENU values, based on the +// user's response. +function doIceESCMenu(pY) +{ + var promptText = " Select An Option: "; + + console.gotoxy(1, pY); + console.print(iceText(promptText, "w")); + console.cleartoeol("n"); + // Input loop + var userInput; + var userChoice = ICE_ESC_MENU_SAVE; + var continueOn = true; + while (continueOn) + { + console.gotoxy(promptText.length, pY); + + // Display the options, with the correct one highlighted. + switch (userChoice) + { + case ICE_ESC_MENU_SAVE: + console.print(iceStyledPromptText("Save", true) + "n "); + console.print(iceStyledPromptText("Abort", false) + "n "); + console.print(iceStyledPromptText("Edit", false) + "n "); + console.print(iceStyledPromptText("Help", false)); + break; + case ICE_ESC_MENU_ABORT: + console.print(iceStyledPromptText("Save", false) + "n "); + console.print(iceStyledPromptText("Abort", true) + "n "); + console.print(iceStyledPromptText("Edit", false) + "n "); + console.print(iceStyledPromptText("Help", false)); + break; + case ICE_ESC_MENU_EDIT: + console.print(iceStyledPromptText("Save", false) + "n "); + console.print(iceStyledPromptText("Abort", false) + "n "); + console.print(iceStyledPromptText("Edit", true) + "n "); + console.print(iceStyledPromptText("Help", false)); + break; + case ICE_ESC_MENU_HELP: + console.print(iceStyledPromptText("Save", false) + "n "); + console.print(iceStyledPromptText("Abort", false) + "n "); + console.print(iceStyledPromptText("Edit", false) + "n "); + console.print(iceStyledPromptText("Help", true)); + break; + } + + // Get the user's choice + userInput = console.getkey(K_UPPER|K_ALPHA|K_NOECHO|K_NOSPIN|K_NOCRLF); + switch (userInput) + { + case KEY_UP: + case KEY_LEFT: + --userChoice; + if (userChoice < ICE_ESC_MENU_SAVE) + userChoice = ICE_ESC_MENU_HELP; + break; + case KEY_DOWN: + case KEY_RIGHT: + ++userChoice; + if (userChoice > ICE_ESC_MENU_HELP) + userChoice = ICE_ESC_MENU_SAVE; + break; + case "S": // Save + userChoice = ICE_ESC_MENU_SAVE; + continueOn = false; + break; + case "A": // Abort + userChoice = ICE_ESC_MENU_ABORT; + continueOn = false; + break; + case KEY_ESC: // Go back to editing the message + case "E": // Go back to editing the message + userChoice = ICE_ESC_MENU_EDIT; + continueOn = false; + break; + case "H": // Help + userChoice = ICE_ESC_MENU_HELP; + continueOn = false; + break; + case KEY_ENTER: // Accept the current choice + continueOn = false; + break; + } + } + + // Make sure special text attributes are cleared. + console.print("n"); + + return userChoice; +} + +// Returns text to be used in prompts, such as the ESC menu, etc. +// +// Parameters: +// pText: The text to display +// pHighlight: Whether or not to use highlight colors +// +// Return value: The styled text +function iceStyledPromptText(pText, pHighlight) +{ + var styledText; + if (pHighlight) + styledText = gConfigSettings.iceColors.SelectedOptionBorderColor + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.SelectedOptionTextColor + pText + + gConfigSettings.iceColors.SelectedOptionBorderColor + THIN_RECTANGLE_RIGHT; + else + styledText = gConfigSettings.iceColors.UnselectedOptionBorderColor + THIN_RECTANGLE_LEFT + + iceText(pText, "w") + gConfigSettings.iceColors.UnselectedOptionBorderColor + + THIN_RECTANGLE_RIGHT; + return styledText; +} + +// Displays yes/no options in Ice style. +// +// Parameters: +// pYesSelected: Whether or not the "yes" option is to be selected. +function displayIceYesNoText(pYesSelected) +{ + if (pYesSelected) + { + console.print(gConfigSettings.iceColors.SelectedOptionBorderColor + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.SelectedOptionTextColor + "YES" + + gConfigSettings.iceColors.SelectedOptionBorderColor + + THIN_RECTANGLE_RIGHT + gConfigSettings.iceColors.UnselectedOptionBorderColor + + " " + THIN_RECTANGLE_LEFT + gConfigSettings.iceColors.UnselectedOptionTextColor + + "NO" + gConfigSettings.iceColors.UnselectedOptionBorderColor + + THIN_RECTANGLE_RIGHT); + } + else + { + console.print(gConfigSettings.iceColors.UnselectedOptionBorderColor + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.UnselectedOptionTextColor + "YES" + + gConfigSettings.iceColors.UnselectedOptionBorderColor + THIN_RECTANGLE_RIGHT + + " " + gConfigSettings.iceColors.SelectedOptionBorderColor + THIN_RECTANGLE_LEFT + + gConfigSettings.iceColors.SelectedOptionTextColor + "NO" + + gConfigSettings.iceColors.SelectedOptionBorderColor + THIN_RECTANGLE_RIGHT + + "n"); + } +} + +// Returns the the script's execution directory. +// The code in this function is a trick that was created by Deuce, suggested +// by Rob Swindell as a way to detect which directory the script was executed +// in. I've shortened the code a little. +function getScriptDir() +{ + var startup_path = '.'; + try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; } + return(backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''))); +} \ No newline at end of file diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js new file mode 100644 index 0000000000..0b832c524c --- /dev/null +++ b/exec/SlyEdit_Misc.js @@ -0,0 +1,1636 @@ +/* This file declares functions and variables that are used by Digital + * Distortion Editor for both DCTEdit and IceEdit modes. + * + * Author: Eric Oulashin (AKA Nightfox) + * BBS: Digital Distortion + * BBS address: digdist.bbsindex.com + * + * Date User Description + * 2009-06-06 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... + * 2010-01-19 Eric Oulashin Updated reAdjustTextLines() to return a boolean + * to signify whether any text had been changed. + * Moved isQuoteLine() to here from SlyEdit.js. + * Updated isQuoteLine() to just use the isQuoteLine + * property of the line and not check to see if the + * line starts with a >. + * Updated displayProgramInfo(): Removed my BBS + * name & URL, as well as my handle. + * 2010-02-14 Eric Oulashin Updated reAdjustTextLines() so that it won't + * assume it's splitting around a space: If a space + * is not found in the line, it won't drop a + * character from the line. + * 2010-04-03 Eric Oulashin Started working on Ctrl-A color support. + * 2010-04-06 Eric Oulashin Updated ReadSlyEditConfigFile() to read the + * allowColorSelection value from the config file. + * 2010-04-10 Eric Oulashin Added toggleAttr(). + * 2012-02-17 Eric Oulashin Added rewrapTextLines(). Changed the configuration + * setting "splitLongQuoteLines" to "reWrapQuoteLines". + * 2012-03-31 Eric Oulashin Added the following configuration options: + * add3rdPartyStartupScript + * addJSOnStart + * add3rdPartyExitScript + * addJSOnExit + * 2012-04-11 Eric Oulashin Fixed a bug with quote line wrapping where it + * was incorrectly dealing with quote lines that + * were blank after the quote text. + * 2012-12-21 Eric Oulashin Updated to check for the .cfg files in the + * /sbbs/ctrl directory first, and if they aren't + * there, assume they're in the same directory as + * the .js file. + */ + +// Note: These variables are declared with "var" instead of "const" to avoid +// multiple declaration errors when this file is loaded more than once. + +// Values for attribute types (for text attribute substitution) +var FORE_ATTR = 1; // Foreground color attribute +var BKG_ATTR = 2; // Background color attribute +var SPECIAL_ATTR = 3; // Special attribute + +// Box-drawing/border characters: Single-line +var UPPER_LEFT_SINGLE = "�"; +var HORIZONTAL_SINGLE = "�"; +var UPPER_RIGHT_SINGLE = "�"; +var VERTICAL_SINGLE = "�"; +var LOWER_LEFT_SINGLE = "�"; +var LOWER_RIGHT_SINGLE = "�"; +var T_SINGLE = "�"; +var LEFT_T_SINGLE = "�"; +var RIGHT_T_SINGLE = "�"; +var BOTTOM_T_SINGLE = "�"; +var CROSS_SINGLE = "�"; +// Box-drawing/border characters: Double-line +var UPPER_LEFT_DOUBLE = "�"; +var HORIZONTAL_DOUBLE = "�"; +var UPPER_RIGHT_DOUBLE = "�"; +var VERTICAL_DOUBLE = "�"; +var LOWER_LEFT_DOUBLE = "�"; +var LOWER_RIGHT_DOUBLE = "�"; +var T_DOUBLE = "�"; +var LEFT_T_DOUBLE = "�"; +var RIGHT_T_DOUBLE = "�"; +var BOTTOM_T_DOUBLE = "�"; +var CROSS_DOUBLE = "�"; +// Box-drawing/border characters: Vertical single-line with horizontal double-line +var UPPER_LEFT_VSINGLE_HDOUBLE = "�"; +var UPPER_RIGHT_VSINGLE_HDOUBLE = "�"; +var LOWER_LEFT_VSINGLE_HDOUBLE = "�"; +var LOWER_RIGHT_VSINGLE_HDOUBLE = "�"; +// Other special characters +var DOT_CHAR = "�"; +var THIN_RECTANGLE_LEFT = "�"; +var THIN_RECTANGLE_RIGHT = "�"; +var BLOCK1 = "�"; // Dimmest block +var BLOCK2 = "�"; +var BLOCK3 = "�"; +var BLOCK4 = "�"; // Brightest block + +// Navigational keys +var UP_ARROW = ""; +var DOWN_ARROW = ""; +// CTRL keys +var CTRL_A = "\x01"; +var CTRL_B = "\x02"; +//var KEY_HOME = CTRL_B; +var CTRL_C = "\x03"; +var CTRL_D = "\x04"; +var CTRL_E = "\x05"; +//var KEY_END = CTRL_E; +var CTRL_F = "\x06"; +//var KEY_RIGHT = CTRL_F; +var CTRL_G = "\x07"; +var BEEP = CTRL_G; +var CTRL_H = "\x08"; +var BACKSPACE = CTRL_H; +var CTRL_I = "\x09"; +var TAB = CTRL_I; +var CTRL_J = "\x0a"; +//var KEY_DOWN = CTRL_J; +var CTRL_K = "\x0b"; +var CTRL_L = "\x0c"; +var INSERT_LINE = CTRL_L; +var CTRL_M = "\x0d"; +var CR = CTRL_M; +var KEY_ENTER = CTRL_M; +var CTRL_N = "\x0e"; +var CTRL_O = "\x0f"; +var CTRL_P = "\x10"; +var CTRL_Q = "\x11"; +var XOFF = CTRL_Q; +var CTRL_R = "\x12"; +var CTRL_S = "\x13"; +var XON = CTRL_S; +var CTRL_T = "\x14"; +var CTRL_U = "\x15"; +var CTRL_V = "\x16"; +var KEY_INSERT = CTRL_V; +var CTRL_W = "\x17"; +var CTRL_X = "\x18"; +var CTRL_Y = "\x19"; +var CTRL_Z = "\x1a"; +var KEY_ESC = "\x1b"; + +// gQuotePrefix contains the text to prepend to quote lines. +var gQuotePrefix = " > "; + +/////////////////////////////////////////////////////////////////////////////////// +// Object/class stuff + +// TextLine object constructor: This is used to keep track of a text line, +// and whether it has a hard newline at the end (i.e., if the user pressed +// enter to break the line). +// +// Parameters (all optional): +// pText: The text for the line +// pHardNewlineEnd: Whether or not the line has a "hard newline" - What +// this means is that text below it won't be wrapped up +// to this line when re-adjusting the text lines. +// pIsQuoteLine: Whether or not the line is a quote line. +function TextLine(pText, pHardNewlineEnd, pIsQuoteLine) +{ + this.text = ""; // The line text + this.hardNewlineEnd = false; // Whether or not the line has a hard newline at the end + this.isQuoteLine = false; // Whether or not this is a quote line + // Copy the parameters if they are valid. + if ((pText != null) && (typeof(pText) == "string")) + this.text = pText; + if ((pHardNewlineEnd != null) && (typeof(pHardNewlineEnd) == "boolean")) + this.hardNewlineEnd = pHardNewlineEnd; + if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) == "boolean")) + this.isQuoteLine = pIsQuoteLine; + + // NEW & EXPERIMENTAL: + // For color support + this.attrs = new Array(); // An array of attributes for the line + // Functions + this.length = TextLine_Length; + this.print = TextLine_Print; +} +// For the TextLine class: Returns the length of the text. +function TextLine_Length() +{ + return this.text.length; +} +// For the TextLine class: Prints the text line, using its text attributes. +// +// Parameters: +// pClearToEOL: Boolean - Whether or not to clear to the end of the line +function TextLine_Print(pClearToEOL) +{ + console.print(this.text); + + if (pClearToEOL) + console.cleartoeol(); +} + +// AbortConfirmFuncParams constructor: This object contains parameters used by +// the abort confirmation function (actually, there are separate ones for +// IceEdit and DCT Edit styles). +function AbortConfirmFuncParams() +{ + this.editTop = gEditTop; + this.editBottom = gEditBottom; + this.editWidth = gEditWidth; + this.editHeight = gEditHeight; + this.editLinesIndex = gEditLinesIndex; + this.displayMessageRectangle = displayMessageRectangle; +} + + +/////////////////////////////////////////////////////////////////////////////////// +// Functions + +// This function takes a string and returns a copy of the string +// with a color randomly alternating between dim & bright versions. +// +// Parameters: +// pString: The string to convert +// pColor: The color to use (Synchronet color code) +function randomDimBrightString(pString, pColor) +{ + // Return if an invalid string is passed in. + if (pString == null) + return ""; + if (typeof(pString) != "string") + return ""; + + // Set the color. Default to green. + var color = "g"; + if ((pColor != null) && (typeof(pColor) != "undefined")) + color = pColor; + + return(randomTwoColorString(pString, "n" + color, "nh" + color)); +} + +// This function takes a string and returns a copy of the string +// with colors randomly alternating between two given colors. +// +// Parameters: +// pString: The string to convert +// pColor11: The first color to use (Synchronet color code) +// pColor12: The second color to use (Synchronet color code) +function randomTwoColorString(pString, pColor1, pColor2) +{ + // Return if an invalid string is passed in. + if (pString == null) + return ""; + if (typeof(pString) != "string") + return ""; + + // Set the colors. Default to green. + var color1 = "ng"; + if ((pColor1 != null) && (typeof(pColor1) != "undefined")) + color1 = pColor1; + var color2 = "ngh"; + if ((pColor2 != null) && (typeof(pColor2) != "undefined")) + color2 = pColor2; + + // Create a copy of the string without any control characters, + // and then add our coloring to it. + pString = strip_ctrl(pString); + var returnString = color1; + var useColor1 = false; // Whether or not to use the useColor1 version of the color1 + var oldUseColor1 = useColor1; // The value of useColor1 from the last pass + for (var i = 0; i < pString.length; ++i) + { + // Determine if this character should be useColor1 + useColor1 = (Math.floor(Math.random()*2) == 1); + if (useColor1 != oldUseColor1) + returnString += (useColor1 ? color1 : color2); + + // Append the character from pString. + returnString += pString.charAt(i); + + oldUseColor1 = useColor1; + } + + return returnString; +} + +// Returns the current time as a string, to be displayed on the screen. +function getCurrentTimeStr() +{ + var timeStr = strftime("%I:%M%p", time()); + timeStr = timeStr.replace("AM", "a"); + timeStr = timeStr.replace("PM", "p"); + + return timeStr; +} + +// Returns whether or not a character is printable. +function isPrintableChar(pText) +{ + // Make sure pText is valid and is a string. + if ((pText == null) || (pText == undefined)) + return false; + if (typeof(pText) != "string") + return false; + if (pText.length == 0) + return false; + + // Make sure the character is a printable ASCII character in the range of 32 to 254, + // except for 127 (delete). + var charCode = pText.charCodeAt(0); + return ((charCode > 31) && (charCode < 255) && (charCode != 127)); +} + +// Removes multiple, leading, and/or trailing spaces +// The search & replace regular expressions used in this +// function came from the following URL: +// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces +// +// Parameters: +// pString: The string to trim +// pLeading: Whether or not to trim leading spaces (optional, defaults to true) +// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true) +// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true) +function trimSpaces(pString, pLeading, pMultiple, pTrailing) +{ + // Make sure pString is a string. + if (typeof(pString) == "string") + { + var leading = true; + var multiple = true; + var trailing = true; + if(typeof(pLeading) != "undefined") + leading = pLeading; + if(typeof(pMultiple) != "undefined") + multiple = pMultiple; + if(typeof(pTrailing) != "undefined") + trailing = pTrailing; + + // To remove both leading & trailing spaces: + //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); + + if (leading) + pString = pString.replace(/(^\s*)/gi,""); + if (multiple) + pString = pString.replace(/[ ]{2,}/gi," "); + if (trailing) + pString = pString.replace(/(\s*$)/gi,""); + } + + return pString; +} + +// Displays the text to display above help screens. +function displayHelpHeader() +{ + // Construct the header text lines only once. + if (typeof(displayHelpHeader.headerLines) == "undefined") + { + displayHelpHeader.headerLines = new Array(); + + var headerText = EDITOR_PROGRAM_NAME + " Help w(y" + + (EDITOR_STYLE == "DCT" ? "DCT" : "Ice") + + " modew)"; + var headerTextLen = strip_ctrl(headerText).length; + + // Top border + var headerTextStr = "nhc" + UPPER_LEFT_SINGLE; + for (var i = 0; i < headerTextLen + 2; ++i) + headerTextStr += HORIZONTAL_SINGLE; + headerTextStr += UPPER_RIGHT_SINGLE; + displayHelpHeader.headerLines.push(headerTextStr); + + // Middle line: Header text string + headerTextStr = VERTICAL_SINGLE + "4y " + headerText + " nhc" + + VERTICAL_SINGLE; + displayHelpHeader.headerLines.push(headerTextStr); + + // Lower border + headerTextStr = LOWER_LEFT_SINGLE; + for (var i = 0; i < headerTextLen + 2; ++i) + headerTextStr += HORIZONTAL_SINGLE; + headerTextStr += LOWER_RIGHT_SINGLE; + displayHelpHeader.headerLines.push(headerTextStr); + } + + // Print the header strings + for (var index in displayHelpHeader.headerLines) + console.center(displayHelpHeader.headerLines[index]); +} + +// Displays the command help. +// +// Parameters: +// pDisplayHeader: Whether or not to display the help header. +// pClear: Whether or not to clear the screen first +// pPause: Whether or not to pause at the end +// pIsSysop: Whether or not the user is the sysop. +function displayCommandList(pDisplayHeader, pClear, pPause, pIsSysop) +{ + if (pClear) + console.clear("n"); + if (pDisplayHeader) + { + displayHelpHeader(); + console.crlf(); + } + + var isSysop = false; + if (pIsSysop != null) + isSysop = pIsSysop; + else + isSysop = user.compare_ars("SYSOP"); + + // This function displays a key and its description with formatting & colors. + // + // Parameters: + // pKey: The key description + // pDesc: The description of the key's function + // pCR: Whether or not to display a carriage return (boolean). Optional; + // if not specified, this function won't display a CR. + function displayCmdKeyFormatted(pKey, pDesc, pCR) + { + printf("ch%-13sg: nc%s", pKey, pDesc); + if (pCR) + console.crlf(); + } + // This function does the same, but outputs 2 on the same line. + function displayCmdKeyFormattedDouble(pKey, pDesc, pKey2, pDesc2, pCR) + { + printf("ch%-13sg: nc%-28s kh" + VERTICAL_SINGLE + + " ch%-7sg: nc%s", pKey, pDesc, pKey2, pDesc2); + if (pCR) + console.crlf(); + } + + // Help keys and slash commands + printf("ng%-44s %-33s\r\n", "Help keys", "Slash commands (on blank line)"); + printf("kh%-44s %-33s\r\n", "���������", "������������������������������"); + displayCmdKeyFormattedDouble("Ctrl-G", "General help", "/A", "Abort", true); + displayCmdKeyFormattedDouble("Ctrl-P", "Command key help", "/S", "Save", true); + displayCmdKeyFormattedDouble("Ctrl-R", "Program information", "/Q", "Quote message", true); + printf(" ch%-7sg nc%s", "", "", "/?", "Show help"); + console.crlf(); + // Command/edit keys + console.print("ngCommand/edit keys\r\nkh�����������������\r\n"); + displayCmdKeyFormattedDouble("Ctrl-A", "Abort message", "Ctrl-W", "Page up", true); + displayCmdKeyFormattedDouble("Ctrl-Z", "Save message", "Ctrl-S", "Page down", true); + displayCmdKeyFormattedDouble("Ctrl-Q", "Quote message", "Ctrl-N", "Find text", true); + displayCmdKeyFormattedDouble("Insert/Ctrl-I", "Toggle insert/overwrite mode", + "ESC", "Command menu", true); + if (isSysop) + displayCmdKeyFormattedDouble("Ctrl-O", "Import a file", "Ctrl-X", "Export to file", true); + displayCmdKeyFormatted("Ctrl-D", "Delete line", true); + + if (pPause) + console.pause(); +} + +// Displays the general help screen. +// +// Parameters: +// pDisplayHeader: Whether or not to display the help header. +// pClear: Whether or not to clear the screen first +// pPause: Whether or not to pause at the end +function displayGeneralHelp(pDisplayHeader, pClear, pPause) +{ + if (pClear) + console.clear("n"); + if (pDisplayHeader) + displayHelpHeader(); + + console.print("ncThis is a full-screen message editor that mimics the look & feel of\r\n"); + console.print("IceEdit or DCT Edit, two popular editors. The editor is currently in " + + (EDITOR_STYLE == "DCT" ? "DCT" : "Ice") + "\r\nmode.\r\n"); + console.print("At the top of the screen, information about the message being written (or\r\n"); + console.print("file being edited) is displayed. The middle section is the edit area,\r\n"); + console.print("where the message/file is edited. Finally, the bottom section displays\r\n"); + console.print("some of the most common keys and/or status."); + console.crlf(); + if (pPause) + console.pause(); +} + +// Displays the text to display above program info screens. +function displayProgInfoHeader() +{ + // Construct the header text lines only once. + if (typeof(displayProgInfoHeader.headerLines) == "undefined") + { + displayProgInfoHeader.headerLines = new Array(); + + var progNameLen = strip_ctrl(EDITOR_PROGRAM_NAME).length; + + // Top border + var headerTextStr = "nhc" + UPPER_LEFT_SINGLE; + for (var i = 0; i < progNameLen + 2; ++i) + headerTextStr += HORIZONTAL_SINGLE; + headerTextStr += UPPER_RIGHT_SINGLE; + displayProgInfoHeader.headerLines.push(headerTextStr); + + // Middle line: Header text string + headerTextStr = VERTICAL_SINGLE + "4y " + EDITOR_PROGRAM_NAME + " nhc" + + VERTICAL_SINGLE; + displayProgInfoHeader.headerLines.push(headerTextStr); + + // Lower border + headerTextStr = LOWER_LEFT_SINGLE; + for (var i = 0; i < progNameLen + 2; ++i) + headerTextStr += HORIZONTAL_SINGLE; + headerTextStr += LOWER_RIGHT_SINGLE; + displayProgInfoHeader.headerLines.push(headerTextStr); + } + + // Print the header strings + for (var index in displayProgInfoHeader.headerLines) + console.center(displayProgInfoHeader.headerLines[index]); +} + +// Displays program information. +// +// Parameters: +// pDisplayHeader: Whether or not to display the help header. +// pClear: Whether or not to clear the screen first +// pPause: Whether or not to pause at the end +function displayProgramInfo(pDisplayHeader, pClear, pPause) +{ + if (pClear) + console.clear("n"); + if (pDisplayHeader) + displayProgInfoHeader(); + + // Print the program information + console.center("ncVersion g" + EDITOR_VERSION + " wh(b" + + EDITOR_VER_DATE + "w)"); + console.center("ncby Eric Oulashin"); + console.crlf(); + console.print("ncThis is a full-screen message editor written for Synchronet that mimics\r\n"); + console.print("the look & feel of IceEdit or DCT Edit."); + console.crlf(); + if (pPause) + console.pause(); +} + +// Displays the informational screen for the program exit. +// +// Parameters: +// pClearScreen: Whether or not to clear the screen. +function displayProgramExitInfo(pClearScreen) +{ + if (pClearScreen) + console.clear("n"); + + console.print("ncYou have been using:\r\n"); + console.print("hk�7����������������������������������0�\r\n"); + console.print("�7 nb7����� � ����� � � hk0�\r\n"); + console.print("�7 nb7���� � � � ���� ��� � ����� hk0�\r\n"); + console.print("�7 nb7� � � � � � � � � hk0�\r\n"); + console.print("�7 nb7���� � ��� ����� ��� � ��� hk0�\r\n"); + console.print("�7 nb7�� hk0�\r\n"); + console.print("�7 nb7� hk0�\r\n"); + console.print("������������������������������������\r\n"); + console.print("ngVersion hy" + EDITOR_VERSION + " nm(" + + EDITOR_VER_DATE + ")"); + console.crlf(); + console.print("nbhby Eric Oulashin nwof chDncigital hDncistortion hBncBS"); + console.crlf(); + console.crlf(); + console.print("ncAcknowledgements for look & feel go to the following people:"); + console.crlf(); + console.print("Dan Traczynski: Creator of DCT Edit"); + console.crlf(); + console.print("Jeremy Landvoigt: Original creator of IceEdit"); + console.crlf(); +} + +// Writes some text on the screen at a given location with a given pause. +// +// Parameters: +// pX: The column number on the screen at which to write the message +// pY: The row number on the screen at which to write the message +// pText: The text to write +// pPauseMS: The pause time, in milliseconds +// pClearLineAttrib: Optional - The color/attribute to clear the line with. +// If not specified, defaults to normal attribute. +function writeWithPause(pX, pY, pText, pPauseMS, pClearLineAttrib) +{ + var clearLineAttrib = "n"; + if ((pClearLineAttrib != null) && (typeof(pClearLineAttrib) == "string")) + clearLineAttrib = pClearLineAttrib; + console.gotoxy(pX, pY); + console.cleartoeol(clearLineAttrib); + console.print(pText); + mswait(pPauseMS); +} + +// Prompts the user for a yes/no question. +// +// Parameters: +// pQuestion: The question to ask the user +// pDefaultYes: Boolean - Whether or not the default should be Yes. +// For false, the default will be No. +// pBoxTitle: For DCT mode, this specifies the title to use for the +// prompt box. This is optional; if this is left out, +// the prompt box title will default to "Prompt". +// +// Return value: Boolean - true for a "Yes" answer, false for "No" +function promptYesNo(pQuestion, pDefaultYes, pBoxTitle) +{ + var userResponse = pDefaultYes; + + if (EDITOR_STYLE == "DCT") + { + // We need to create an object of parameters to pass to the DCT-style + // Yes/No function. + var paramObj = new AbortConfirmFuncParams(); + paramObj.editLinesIndex = gEditLinesIndex; + if (typeof(pBoxTitle) == "string") + userResponse = promptYesNo_DCTStyle(pQuestion, pBoxTitle, pDefaultYes, paramObj); + else + userResponse = promptYesNo_DCTStyle(pQuestion, "Prompt", pDefaultYes, paramObj); + } + else if (EDITOR_STYLE == "ICE") + { + const originalCurpos = console.getxy(); + // Go to the bottom line on the screen and prompt the user + console.gotoxy(1, console.screen_rows); + console.cleartoeol(); + console.gotoxy(1, console.screen_rows); + userResponse = promptYesNo_IceStyle(pQuestion, pDefaultYes); + // If the user chose "No", then re-display the bottom help line and + // move the cursor back to its original position. + if (!userResponse) + { + fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes); + console.gotoxy(originalCurpos); + } + } + + return userResponse; +} + +// Reads the SlyEdit configuration settings from SlyEdit.cfg. +// +// Return value: An object containing the settings as properties. +function ReadSlyEditConfigFile() +{ + // Create the configuration object + var cfgObj = new Object(); + cfgObj.thirdPartyLoadOnStart = new Array(); + cfgObj.runJSOnStart = new Array(); + cfgObj.thirdPartyLoadOnExit = new Array(); + cfgObj.runJSOnExit = new Array(); + cfgObj.displayEndInfoScreen = true; + cfgObj.userInputTimeout = true; + cfgObj.inputTimeoutMS = 300000; + cfgObj.reWrapQuoteLines = true; + cfgObj.allowColorSelection = true; + // Ice-style colors + cfgObj.iceColors = new Object(); + // Ice color theme file + cfgObj.iceColors.ThemeFilename = system.ctrl_dir + "SlyIceColors_BlueIce.cfg"; + if (!file_exists(cfgObj.iceColors.ThemeFilename)) + cfgObj.iceColors.ThemeFilename = gStartupPath + "SlyIceColors_BlueIce.cfg"; + // Text edit color + cfgObj.iceColors.TextEditColor = "nw"; + // Quote line color + cfgObj.iceColors.QuoteLineColor = "nc"; + // Ice colors for the quote window + cfgObj.iceColors.QuoteWinText = "nhw"; // White + cfgObj.iceColors.QuoteLineHighlightColor = "4hc"; // High cyan on blue background + cfgObj.iceColors.QuoteWinBorderTextColor = "nch"; // Bright cyan + cfgObj.iceColors.BorderColor1 = "nb"; // Blue + cfgObj.iceColors.BorderColor2 = "nbh"; // Bright blue + // Ice colors for multi-choice prompts + cfgObj.iceColors.SelectedOptionBorderColor = "nbh4"; + cfgObj.iceColors.SelectedOptionTextColor = "nch4" + cfgObj.iceColors.UnselectedOptionBorderColor = "nb"; + cfgObj.iceColors.UnselectedOptionTextColor = "nw"; + // Ice colors for the top info area + cfgObj.iceColors.TopInfoBkgColor = "4"; + cfgObj.iceColors.TopLabelColor = "ch"; + cfgObj.iceColors.TopLabelColonColor = "bh"; + cfgObj.iceColors.TopToColor = "wh"; + cfgObj.iceColors.TopFromColor = "wh"; + cfgObj.iceColors.TopSubjectColor = "wh"; + cfgObj.iceColors.TopTimeColor = "gh"; + cfgObj.iceColors.TopTimeLeftColor = "gh"; + cfgObj.iceColors.EditMode = "ch"; + cfgObj.iceColors.KeyInfoLabelColor = "ch"; + + // DCT-style colors + cfgObj.DCTColors = new Object(); + // DCT color theme file + cfgObj.DCTColors.ThemeFilename = system.ctrl_dir + "SlyDCTColors_Default.cfg"; + if (!file_exists(cfgObj.DCTColors.ThemeFilename)) + cfgObj.DCTColors.ThemeFilename = gStartupPath + "SlyDCTColors_Default.cfg"; + // Text edit color + cfgObj.DCTColors.TextEditColor = "nw"; + // Quote line color + cfgObj.DCTColors.QuoteLineColor = "nc"; + // DCT colors for the border stuff + cfgObj.DCTColors.TopBorderColor1 = "nr"; + cfgObj.DCTColors.TopBorderColor2 = "nrh"; + cfgObj.DCTColors.EditAreaBorderColor1 = "ng"; + cfgObj.DCTColors.EditAreaBorderColor2 = "ngh"; + cfgObj.DCTColors.EditModeBrackets = "nkh"; + cfgObj.DCTColors.EditMode = "nw"; + // DCT colors for the top informational area + cfgObj.DCTColors.TopLabelColor = "nbh"; + cfgObj.DCTColors.TopLabelColonColor = "nb"; + cfgObj.DCTColors.TopFromColor = "nch"; + cfgObj.DCTColors.TopFromFillColor = "nc"; + cfgObj.DCTColors.TopToColor = "nch"; + cfgObj.DCTColors.TopToFillColor = "nc"; + cfgObj.DCTColors.TopSubjColor = "nwh"; + cfgObj.DCTColors.TopSubjFillColor = "nw"; + cfgObj.DCTColors.TopAreaColor = "ngh"; + cfgObj.DCTColors.TopAreaFillColor = "ng"; + cfgObj.DCTColors.TopTimeColor = "nyh"; + cfgObj.DCTColors.TopTimeFillColor = "nr"; + cfgObj.DCTColors.TopTimeLeftColor = "nyh"; + cfgObj.DCTColors.TopTimeLeftFillColor = "nr"; + cfgObj.DCTColors.TopInfoBracketColor = "nm"; + // DCT colors for the quote window + cfgObj.DCTColors.QuoteWinText = "n7k"; + cfgObj.DCTColors.QuoteLineHighlightColor = "nw"; + cfgObj.DCTColors.QuoteWinBorderTextColor = "n7r"; + cfgObj.DCTColors.QuoteWinBorderColor = "nk7"; + // DCT colors for the quote window + cfgObj.DCTColors.QuoteWinText = "n7b"; + cfgObj.DCTColors.QuoteLineHighlightColor = "nw"; + cfgObj.DCTColors.QuoteWinBorderTextColor = "n7r"; + cfgObj.DCTColors.QuoteWinBorderColor = "nk7"; + // DCT colors for the bottom row help text + cfgObj.DCTColors.BottomHelpBrackets = "nkh"; + cfgObj.DCTColors.BottomHelpKeys = "nrh"; + cfgObj.DCTColors.BottomHelpFill = "nr"; + cfgObj.DCTColors.BottomHelpKeyDesc = "nc"; + // DCT colors for text boxes + cfgObj.DCTColors.TextBoxBorder = "nk7"; + cfgObj.DCTColors.TextBoxBorderText = "nr7"; + cfgObj.DCTColors.TextBoxInnerText = "nb7"; + cfgObj.DCTColors.YesNoBoxBrackets = "nk7"; + cfgObj.DCTColors.YesNoBoxYesNoText = "nwh7"; + // DCT colors for the menus + cfgObj.DCTColors.SelectedMenuLabelBorders = "nw"; + cfgObj.DCTColors.SelectedMenuLabelText = "nk7"; + cfgObj.DCTColors.UnselectedMenuLabelText = "nwh"; + cfgObj.DCTColors.MenuBorders = "nk7"; + cfgObj.DCTColors.MenuSelectedItems = "nw"; + cfgObj.DCTColors.MenuUnselectedItems = "nk7"; + cfgObj.DCTColors.MenuHotkeys = "nwh7"; + + // Open the configuration file + var ctrlCfgFileName = system.ctrl_dir + "SlyEdit.cfg"; + var cfgFile = new File(file_exists(ctrlCfgFileName) ? ctrlCfgFileName : gStartupPath + "SlyEdit.cfg"); + if (cfgFile.open("r")) + { + var settingsMode = "behavior"; + var fileLine = null; // A line read from the file + var equalsPos = 0; // Position of a = in the line + var commentPos = 0; // Position of the start of a comment + var setting = null; // A setting name (string) + var settingUpper = null; // Upper-case setting name + var value = null; // A value for a setting (string) + var valueUpper = null; // Upper-cased value + while (!cfgFile.eof) + { + // Read the next line from the config file. + fileLine = cfgFile.readln(2048); + + // fileLine should be a string, but I've seen some cases + // where for some reason it isn't. If it's not a string, + // then continue onto the next line. + if (typeof(fileLine) != "string") + continue; + + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // If in the "behavior" section, then set the behavior-related variables. + if (fileLine.toUpperCase() == "[BEHAVIOR]") + { + settingsMode = "behavior"; + continue; + } + else if (fileLine.toUpperCase() == "[ICE_COLORS]") + { + settingsMode = "ICEColors"; + continue; + } + else if (fileLine.toUpperCase() == "[DCT_COLORS]") + { + settingsMode = "DCTColors"; + continue; + } + + // If the line has a semicolon anywhere in it, then remove + // everything from the semicolon onward. + commentPos = fileLine.indexOf(";"); + if (commentPos > -1) + fileLine = fileLine.substr(0, commentPos); + + // Look for an equals sign, and if found, separate the line + // into the setting name (before the =) and the value (after the + // equals sign). + equalsPos = fileLine.indexOf("="); + if (equalsPos > 0) + { + // Read the setting & value, and trim leading & trailing spaces. + setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); + settingUpper = setting.toUpperCase(); + value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); + valueUpper = value.toUpperCase(); + + if (settingsMode == "behavior") + { + if (settingUpper == "DISPLAYENDINFOSCREEN") + cfgObj.displayEndInfoScreen = (valueUpper == "TRUE"); + else if (settingUpper == "USERINPUTTIMEOUT") + cfgObj.userInputTimeout = (valueUpper == "TRUE"); + else if (settingUpper == "INPUTTIMEOUTMS") + cfgObj.inputTimeoutMS = +value; + else if (settingUpper == "REWRAPQUOTELINES") + cfgObj.reWrapQuoteLines = (valueUpper == "TRUE"); + else if (settingUpper == "ALLOWCOLORSELECTION") + cfgObj.allowColorSelection = (valueUpper == "TRUE"); + else if (settingUpper == "ADD3RDPARTYSTARTUPSCRIPT") + cfgObj.thirdPartyLoadOnStart.push(value); + else if (settingUpper == "ADD3RDPARTYEXITSCRIPT") + cfgObj.thirdPartyLoadOnExit.push(value); + else if (settingUpper == "ADDJSONSTART") + cfgObj.runJSOnStart.push(value); + else if (settingUpper == "ADDJSONEXIT") + cfgObj.runJSOnExit.push(value); + } + else if (settingsMode == "ICEColors") + { + if (settingUpper == "THEMEFILENAME") + { + //system.ctrl_dir + //gStartupPath + cfgObj.iceColors.ThemeFilename = system.ctrl_dir + value; + if (!file_exists(cfgObj.iceColors.ThemeFilename)) + cfgObj.iceColors.ThemeFilename = gStartupPath + value; + } + } + else if (settingsMode == "DCTColors") + { + if (settingUpper == "THEMEFILENAME") + { + cfgObj.DCTColors.ThemeFilename = system.ctrl_dir + value; + if (!file_exists(cfgObj.DCTColors.ThemeFilename)) + cfgObj.DCTColors.ThemeFilename = gStartupPath + value; + } + } + } + } + + cfgFile.close(); + + // Validate the settings + if (cfgObj.inputTimeoutMS < 1000) + cfgObj.inputTimeoutMS = 300000; + } + + return cfgObj; +} + +// This function reads a configuration file containing +// setting=value pairs and returns the settings in +// an Object. +// +// Parameters: +// pFilename: The name of the configuration file. +// pLineReadLen: The maximum number of characters to read from each +// line. This is optional; if not specified, then up +// to 512 characters will be read from each line. +// +// Return value: An Object containing the value=setting pairs. If the +// file can't be opened or no settings can be read, then +// this function will return null. +function readValueSettingConfigFile(pFilename, pLineReadLen) +{ + var retObj = null; + + var cfgFile = new File(pFilename); + if (cfgFile.open("r")) + { + // Set the number of characters to read per line. + var numCharsPerLine = 512; + if (pLineReadLen != null) + numCharsPerLine = pLineReadLen; + + var fileLine = null; // A line read from the file + var equalsPos = 0; // Position of a = in the line + var commentPos = 0; // Position of the start of a comment + var setting = null; // A setting name (string) + var settingUpper = null; // Upper-case setting name + var value = null; // A value for a setting (string) + var valueUpper = null; // Upper-cased value + while (!cfgFile.eof) + { + // Read the next line from the config file. + fileLine = cfgFile.readln(numCharsPerLine); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // If the line has a semicolon anywhere in it, then remove + // everything from the semicolon onward. + commentPos = fileLine.indexOf(";"); + if (commentPos > -1) + fileLine = fileLine.substr(0, commentPos); + + // Look for an equals sign, and if found, separate the line + // into the setting name (before the =) and the value (after the + // equals sign). + equalsPos = fileLine.indexOf("="); + if (equalsPos > 0) + { + // If retObj hasn't been created yet, then create it. + if (retObj == null) + retObj = new Object(); + + // Read the setting & value, and trim leading & trailing spaces. Then + // set the value in retObj. + setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); + value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); + retObj[setting] = value; + } + } + + cfgFile.close(); + } + + return retObj; +} + +// Splits a string up by a maximum length, preserving whole words. +// +// Parameters: +// pStr: The string to split +// pMaxLen: The maximum length for the strings (strings longer than this +// will be split) +// +// Return value: An array of strings resulting from the string split +function splitStrStable(pStr, pMaxLen) +{ + var strings = new Array(); + + // Error checking + if (typeof(pStr) != "string") + { + console.print("1 - pStr not a string!\r\n"); + return strings; + } + + // If the string's length is less than or equal to pMaxLen, then + // just insert it into the strings array. Otherwise, we'll + // need to split it. + if (pStr.length <= pMaxLen) + strings.push(pStr); + else + { + // Make a copy of pStr so that we don't modify it. + var theStr = pStr; + + var tempStr = ""; + var splitIndex = 0; // Index of a space in a string + while (theStr.length > pMaxLen) + { + // If there isn't a space at the pMaxLen location in theStr, + // then assume there's a word there and look for a space + // before it. + splitIndex = pMaxLen; + if (theStr.charAt(splitIndex) != " ") + { + splitIndex = theStr.lastIndexOf(" ", splitIndex); + // If a space was not found, then we should split at + // pMaxLen. + if (splitIndex == -1) + splitIndex = pMaxLen; + } + + // Extract the first part of theStr up to splitIndex into + // tempStr, and then remove that part from theStr. + tempStr = theStr.substr(0, splitIndex); + theStr = theStr.substr(splitIndex+1); + + // If tempStr is not blank, then insert it into the strings + // array. + if (tempStr.length > 0) + strings.push(tempStr); + } + // Edge case: If theStr is not blank, then insert it into the + // strings array. + if (theStr.length > 0) + strings.push(theStr); + } + + return strings; +} + +// Inserts a string inside another string. +// +// Parameters: +// pStr: The string inside which to insert the other string +// pIndex: The index of pStr at which to insert the other string +// pStr2: The string to insert into the first string +// +// Return value: The spliced string +function spliceIntoStr(pStr, pIndex, pStr2) +{ + // Error checking + var typeofPStr = typeof(pStr); + var typeofPStr2 = typeof(pStr2); + if ((typeofPStr != "string") && (typeofPStr2 != "string")) + return ""; + else if ((typeofPStr == "string") && (typeofPStr2 != "string")) + return pStr; + else if ((typeofPStr != "string") && (typeofPStr2 == "string")) + return pStr2; + // If pIndex is beyond the last index of pStr, then just return the + // two strings concatenated. + if (pIndex >= pStr.length) + return (pStr + pStr2); + // If pIndex is below 0, then just return pStr2 + pStr. + else if (pIndex < 0) + return (pStr2 + pStr); + + return (pStr.substr(0, pIndex) + pStr2 + pStr.substr(pIndex)); +} + +// Fixes the text lines in the gEditLines array so that they all +// have a maximum width to fit within the edit area. +// +// Parameters: +// pTextLineArray: An array of TextLine objects to adjust +// pStartIndex: The index of the line in the array to start at. +// pEndIndex: One past the last index of the line in the array to end at. +// pEditWidth: The width of the edit area (AKA the maximum line length + 1) +// +// Return value: Boolean - Whether or not any text was changed. +function reAdjustTextLines(pTextLineArray, pStartIndex, pEndIndex, pEditWidth) +{ + // Returns without doing anything if any of the parameters are not + // what they should be. (Note: Not checking pTextLineArray for now..) + if (typeof(pStartIndex) != "number") + return false; + if (typeof(pEndIndex) != "number") + return false; + if (typeof(pEditWidth) != "number") + return false; + // Range checking + if ((pStartIndex < 0) || (pStartIndex >= pTextLineArray.length)) + return false; + if ((pEndIndex <= pStartIndex) || (pEndIndex < 0)) + return false; + if (pEndIndex > pTextLineArray.length) + pEndIndex = pTextLineArray.length; + if (pEditWidth <= 5) + return false; + + var textChanged = false; // We'll return this upon function exit. + + var nextLineIndex = 0; + var charsToRemove = 0; + var splitIndex = 0; + var spaceFound = false; // Whether or not a space was found in a text line + var splitIndexOriginal = 0; + var tempText = null; + var appendedNewLine = false; // If we appended another line + for (var i = pStartIndex; i < pEndIndex; ++i) + { + // As an extra precaution, check to make sure this array element is defined. + if (pTextLineArray[i] == undefined) + continue; + + nextLineIndex = i + 1; + // If the line's text is longer or equal to the edit width, then if + // possible, move the last word to the beginning of the next line. + if (pTextLineArray[i].text.length >= pEditWidth) + { + charsToRemove = pTextLineArray[i].text.length - pEditWidth + 1; + splitIndex = pTextLineArray[i].text.length - charsToRemove; + splitIndexOriginal = splitIndex; + // If the character in the text line at splitIndex is not a space, + // then look for a space before splitIndex. + spaceFound = (pTextLineArray[i].text.charAt(splitIndex) == " "); + if (!spaceFound) + { + splitIndex = pTextLineArray[i].text.lastIndexOf(" ", splitIndex-1); + spaceFound = (splitIndex > -1); + if (!spaceFound) + splitIndex = splitIndexOriginal; + } + tempText = pTextLineArray[i].text.substr(spaceFound ? splitIndex+1 : splitIndex); + pTextLineArray[i].text = pTextLineArray[i].text.substr(0, splitIndex); + textChanged = true; + // If we're on the last line, or if the current line has a hard + // newline or is a quote line, then append a new line below. + appendedNewLine = false; + if ((nextLineIndex == pTextLineArray.length) || pTextLineArray[i].hardNewlineEnd || + isQuoteLine(pTextLineArray, i)) + { + pTextLineArray.splice(nextLineIndex, 0, new TextLine()); + pTextLineArray[nextLineIndex].hardNewlineEnd = pTextLineArray[i].hardNewlineEnd; + pTextLineArray[i].hardNewlineEnd = false; + pTextLineArray[nextLineIndex].isQuoteLine = pTextLineArray[i].isQuoteLine; + appendedNewLine = true; + } + + // Move the text around and adjust the line properties. + if (appendedNewLine) + pTextLineArray[nextLineIndex].text = tempText; + else + { + // If we're in insert mode, then insert the text at the beginning of + // the next line. Otherwise, overwrite the text in the next line. + if (inInsertMode()) + pTextLineArray[nextLineIndex].text = tempText + " " + pTextLineArray[nextLineIndex].text; + else + { + // We're in overwrite mode, so overwite the first part of the next + // line with tempText. + if (pTextLineArray[nextLineIndex].text.length < tempText.length) + pTextLineArray[nextLineIndex].text = tempText; + else + { + pTextLineArray[nextLineIndex].text = tempText + + pTextLineArray[nextLineIndex].text.substr(tempText.length); + } + } + } + } + else + { + // pTextLineArray[i].text.length is < pEditWidth, so try to bring up text + // from the next line. + + // Only do it if the line doesn't have a hard newline and it's not a + // quote line and there is a next line. + if (!pTextLineArray[i].hardNewlineEnd && !isQuoteLine(pTextLineArray, i) && + (i < pTextLineArray.length-1)) + { + if (pTextLineArray[nextLineIndex].text.length > 0) + { + splitIndex = pEditWidth - pTextLineArray[i].text.length - 2; + // If splitIndex is negative, that means the entire next line + // can fit on the current line. + if ((splitIndex < 0) || (splitIndex > pTextLineArray[nextLineIndex].text.length)) + splitIndex = pTextLineArray[nextLineIndex].text.length; + else + { + // If the character in the next line at splitIndex is not a + // space, then look for a space before it. + if (pTextLineArray[nextLineIndex].text.charAt(splitIndex) != " ") + splitIndex = pTextLineArray[nextLineIndex].text.lastIndexOf(" ", splitIndex); + // If no space was found, then skip to the next line (we don't + // want to break up words from the next line). + if (splitIndex == -1) + continue; + } + + // Get the text to bring up to the current line. + // If the current line does not end with a space and the next line + // does not start with a space, then add a space between this line + // and the next line's text. This is done to avoid joining words + // accidentally. + tempText = ""; + if ((pTextLineArray[i].text.charAt(pTextLineArray[i].text.length-1) != " ") && + (pTextLineArray[nextLineIndex].text.substr(0, 1) != " ")) + { + tempText = " "; + } + tempText += pTextLineArray[nextLineIndex].text.substr(0, splitIndex); + // Move the text from the next line to the current line, if the current + // line has room for it. + if (pTextLineArray[i].text.length + tempText.length < pEditWidth) + { + pTextLineArray[i].text += tempText; + pTextLineArray[nextLineIndex].text = pTextLineArray[nextLineIndex].text.substr(splitIndex+1); + textChanged = true; + + // If the next line is now blank, then remove it. + if (pTextLineArray[nextLineIndex].text.length == 0) + { + // The current line should take on the next line's + // hardnewlineEnd property before removing the next line. + pTextLineArray[i].hardNewlineEnd = pTextLineArray[nextLineIndex].hardNewlineEnd; + pTextLineArray.splice(nextLineIndex, 1); + } + } + } + else + { + // The next line's text string is blank. If its hardNewlineEnd + // property is false, then remove the line. + if (!pTextLineArray[nextLineIndex].hardNewlineEnd) + { + pTextLineArray.splice(nextLineIndex, 1); + textChanged = true; + } + } + } + } + } + + return textChanged; +} + +// Returns indexes of the first unquoted text line and the next +// quoted text line in an array of text lines. +// +// Parameters: +// pTextLineArray: An array of TextLine objects +// pStartIndex: The index of where to start looking in the array +// pQuotePrefix: The quote line prefix (string) +// +// Return value: An object containing the following properties: +// noQuoteLineIndex: The index of the next non-quoted line. +// Will be -1 if none are found. +// nextQuoteLineIndex: The index of the next quoted line. +// Will be -1 if none are found. +function quotedLineIndexes(pTextLineArray, pStartIndex, pQuotePrefix) +{ + var retObj = new Object(); + retObj.noQuoteLineIndex = -1; + retObj.nextQuoteLineIndex = -1; + + if (pTextLineArray.length == 0) + return retObj; + if (typeof(pStartIndex) != "number") + return retObj; + if (pStartIndex >= pTextLineArray.length) + return retObj; + + var startIndex = (pStartIndex > -1 ? pStartIndex : 0); + + // Look for the first non-quoted line in the array. + retObj.noQuoteLineIndex = startIndex; + for (; retObj.noQuoteLineIndex < pTextLineArray.length; ++retObj.noQuoteLineIndex) + { + if (pTextLineArray[retObj.noQuoteLineIndex].text.indexOf(pQuotePrefix) == -1) + break; + } + // If the index is pTextLineArray.length, then what we're looking for wasn't + // found, so set the index to -1. + if (retObj.noQuoteLineIndex == pTextLineArray.length) + retObj.noQuoteLineIndex = -1; + + // Look for the next quoted line in the array. + // If we found a non-quoted line, then use that index; otherwise, + // start at the first line. + if (retObj.noQuoteLineIndex > -1) + retObj.nextQuoteLineIndex = retObj.noQuoteLineIndex; + else + retObj.nextQuoteLineIndex = 0; + for (; retObj.nextQuoteLineIndex < pTextLineArray.length; ++retObj.nextQuoteLineIndex) + { + if (pTextLineArray[retObj.nextQuoteLineIndex].text.indexOf(pQuotePrefix) == 0) + break; + } + // If the index is pTextLineArray.length, then what we're looking for wasn't + // found, so set the index to -1. + if (retObj.nextQuoteLineIndex == pTextLineArray.length) + retObj.nextQuoteLineIndex = -1; + + return retObj; +} + +// Returns whether a line in an array of TextLine objects is a quote line. +// This is true if the line's isQuoteLine property is true or the line's text +// starts with > (preceded by any # of spaces). +// +// Parameters: +// pLineArray: An array of TextLine objects +// pLineIndex: The index of the line in gEditLines +function isQuoteLine(pLineArray, pLineIndex) +{ + if (typeof(pLineArray) == "undefined") + return false; + if (typeof(pLineIndex) != "number") + return false; + + var lineIsQuoteLine = false; + if (typeof(pLineArray[pLineIndex]) != "undefined") + { + /* + lineIsQuoteLine = ((pLineArray[pLineIndex].isQuoteLine) || + (/^ *>/.test(pLineArray[pLineIndex].text))); + */ + lineIsQuoteLine = (pLineArray[pLineIndex].isQuoteLine); + } + return lineIsQuoteLine; +} + +// Replaces an attribute in a text attribute string. +// +// Parameters: +// pAttrType: Numeric: +// FORE_ATTR: Foreground attribute +// BKG_ATTR: Background attribute +// 3: Special attribute +// pAttrs: The attribute string to change +// pNewAttr: The new attribute to put into the attribute string (without the +// control character) +function toggleAttr(pAttrType, pAttrs, pNewAttr) +{ + // Removes an attribute from an attribute string, if it + // exists. Returns the new attribute string. + function removeAttrIfExists(pAttrs, pNewAttr) + { + var index = pAttrs.search(pNewAttr); + if (index > -1) + pAttrs = pAttrs.replace(pNewAttr, ""); + return pAttrs; + } + + // Convert pAttrs and pNewAttr to all uppercase for ease of searching + pAttrs = pAttrs.toUpperCase(); + pNewAttr = pNewAttr.toUpperCase(); + + // If pAttrs starts with the normal attribute, then + // remove it (we'll put it back on later). + var normalAtStart = false; + if (pAttrs.search(/^N/) == 0) + { + normalAtStart = true; + pAttrs = pAttrs.substr(2); + } + + // Prepend the attribute control character to the new attribute + var newAttr = "" + pNewAttr; + + // Set a regex for searching & replacing + var regex = ""; + switch (pAttrType) + { + case FORE_ATTR: // Foreground attribute + regex = /K|R|G|Y|B|M|C|W/g; + break; + case BKG_ATTR: // Background attribute + regex = /0|1|2|3|4|5|6|7/g; + break; + case SPECIAL_ATTR: // Special attribute + //regex = /H|I|N/g; + index = pAttrs.search(newAttr); + if (index > -1) + pAttrs = pAttrs.replace(newAttr, ""); + else + pAttrs += newAttr; + break; + default: + break; + } + + // If regex is not blank, then search & replace on it in + // pAttrs. + if (regex != "") + { + pAttrs = removeAttrIfExists(pAttrs, newAttr); + // If the regex is found, then replace it. Otherwise, + // add pNewAttr to the attribute string. + if (pAttrs.search(regex) > -1) + pAttrs = pAttrs.replace(regex, "" + pNewAttr); + else + pAttrs += "" + pNewAttr; + } + + // If pAttrs started with the normal attribute, then + // put it back on. + if (normalAtStart) + pAttrs = "N" + pAttrs; + + return pAttrs; +} + +// This function wraps an array of strings based on a line width. +// +// Parameters: +// pLineArr: An array of strings +// pStartLineIndex: The index of the text line in the array to start at +// pStopIndex: The index of where to stop in the array. This is one past +// the last line in the array. For example, to end at the +// last line in the array, use the array's .length property +// for this parameter. +// pLineWidth: The maximum width of each line +// +// Return value: The number of strings in lineArr +function wrapTextLines(pLineArr, pStartLineIndex, pStopIndex, pLineWidth) +{ + // Validate parameters + if (pLineArr == null) + return 0; + if ((pStartLineIndex == null) || (typeof(pStartLineIndex) != "number") || (pStartLineIndex < 0)) + pStartLineIndex = 0; + if (pStartLineIndex >= pLineArr.length) + return pLineArr.length; + if ((typeof(pStopIndex) != "number") || (pStopIndex == null) || (pStopIndex > pLineArr.length)) + pStopIndex = pLineArr.length; + + // Now for the actual code: + var trimLen = 0; // The number of characters to trim from the end of a string + var trimIndex = 0; // The index of where to start trimming + for (var i = pStartLineIndex; i < pStopIndex; ++i) + { + // If the object in pLineArr is not a string for some reason, then skip it. + if (typeof(pLineArr[i]) != "string") + continue; + + if (pLineArr[i].length > pLineWidth) + { + trimLen = pLineArr[i].length - pLineWidth; + trimIndex = pLineArr[i].lastIndexOf(" ", pLineArr[i].length - trimLen); + if (trimIndex == -1) + trimIndex = pLineArr[i].length - trimLen; + // Trim the text, and remove leading spaces from it too. + trimmedText = pLineArr[i].substr(trimIndex).replace(/^ +/, ""); + pLineArr[i] = pLineArr[i].substr(0, trimIndex); + if (i < pLineArr.length - 1) + { + // If the next line is blank, then append another blank + // line there to preserve the message's formatting. + if (pLineArr[i+1].length == 0) + pLineArr.splice(i+1, 0, ""); + else + { + // Since the next line is not blank, then append a space + // to the end of the trimmed text if it doesn't have one. + if (trimmedText.charAt(trimmedText.length-1) != " ") + trimmedText += " " + } + // Prepend the trimmed text to the next line. + pLineArr[i+1] = trimmedText + pLineArr[i+1]; + } + else + pLineArr.push(trimmedText); + } + } + return pLineArr.length; +} + +// Returns the index of a string for the first non-quote character. +// +// Parameters: +// pStr: A string to check +// +// Return value: An object containing the following properties: +// startIndex: The index of the first non-quote character in the string. +// If pStr is an invalid string, or if a non-quote character +// is not found, this will be -1. +// quoteLevel: The number of > characters at the start of the string +function firstNonQuoteTxtIndex(pStr) +{ + // Create the return object with initial values. + var retObj = new Object(); + retObj.startIndex = -1; + retObj.quoteLevel = 0; + + // If pStr is not a valid positive-length string, then just return. + if ((pStr == null) || (typeof(pStr) != "string") || (pStr.length == 0)) + return retObj; + + // Look for quote lines that begin with 1 or 2 initials followed by a > (i.e., + // "EO>" or "E>" at the start of the line. If found, set an index to look for + // & count the > characters from the >. + var searchStartIndex = 0; + // Regex notes: + // \w: Matches any alphanumerical character (word characters) including underscore (short for [a-zA-Z0-9_]) + // ?: Supposed to match 0 or 1 occurance, but seems to match 1 or 2 + var lineStartsWithQuoteText = /^ *\w?[^ ]>/.test(pStr); + if (lineStartsWithQuoteText) + { + searchStartIndex = pStr.indexOf(">"); + if (searchStartIndex < 0) searchStartIndex = 0; + } + + // Look for the first non-quote text and quote level in the string. + var strChar = ""; + var j = 0; + for (var i = searchStartIndex; i < pStr.length; ++i) + { + strChar = pStr.charAt(i); + if ((strChar != " ") && (strChar != ">")) + { + // We've found the first non-quote character. + retObj.startIndex = i; + // Count the number of times the > character appears at the start of + // the line, and set quoteLevel to that. + if (i >= 0) + { + for (j = 0; j < i; ++j) + { + if (pStr.charAt(j) == ">") + ++retObj.quoteLevel; + } + } + break; + } + } + + // If we haven't found non-quote text but the line starts with quote text, + // then set the starting index & quote level in retObj. + //displayDebugText(1, 2, "Search start index: " + searchStartIndex, console.getxy(), true, true); + if (lineStartsWithQuoteText && ((retObj.startIndex == -1) || (retObj.quoteLevel == 0))) + { + retObj.startIndex = pStr.indexOf(">") + 1; + retObj.quoteLevel = 1; + } + + return retObj; +} + +function wrapQuoteLines() +{ + if (gQuoteLines.length == 0) + return; + + // This function checks if a string has only > characters separated by + // whitespace and returns a version where the > characters are only separated + // by one space each, and if the line starts with " >", the leading space + // will be removed. + function normalizeGTChars(pStr) + { + if (/^\s*>\s*$/.test(pStr)) + pStr = ">"; + else + { + pStr = pStr.replace(/>\s*>/g, "> >") + .replace(/^\s>/, ">") + .replace(/^\s*$/, ""); + } + return pStr; + } + + // Create an array for line information objects, and append the + // first line's info to it. Also, store the first line's quote + // level in the lastQuoteLevel variable. + var lineInfos = new Array(); + var retObj = firstNonQuoteTxtIndex(gQuoteLines[0]); + lineInfos.push(retObj); + var lastQuoteLevel = retObj.quoteLevel; + + // Loop through the array starting at the 2nd line and wrap the lines + var startArrIndex = 0; + var endArrIndex = 0; + var quoteStr = ""; + var quoteLevel = 0; + var retObj = null; + var i = 0; // Index variable + for (var quoteLineIndex = 1; quoteLineIndex < gQuoteLines.length; ++quoteLineIndex) + { + retObj = firstNonQuoteTxtIndex(gQuoteLines[quoteLineIndex]); + lineInfos.push(retObj); + if (retObj.quoteLevel != lastQuoteLevel) + { + endArrIndex = quoteLineIndex; + // Remove the quote strings from the lines we're about to wrap + for (i = startArrIndex; i < endArrIndex; ++i) + { + // TODO + // Error on next line: !JavaScript TypeError: lineInfos[i] is undefined + // Fixed by checking that lineInfos[i] is not null.. but why would it be? + if (lineInfos[i] != null) + { + if (lineInfos[i].startIndex > -1) + gQuoteLines[i] = gQuoteLines[i].substr(lineInfos[i].startIndex); + else + gQuoteLines[i] = normalizeGTChars(gQuoteLines[i]); + // If the quote line now only consists of spaces after removing the quote + // characters, then make it blank. + if (/^ +$/.test(gQuoteLines[i])) gQuoteLines[i] = ""; + } + } + // Wrap the text lines in the range we've seen + // Note: 79 is assumed as the maximum line length because + // that seems to be a commonly-accepted message width for + // BBSs. Also, the following length is subtracted from it: + // (2*(lastQuoteLevel+1) + gQuotePrefix.length) + // That is because we'll be prepending "> " to the quote lines, + // and then SlyEdit will prepend gQuotePrefix to them during quoting. + var numLinesBefore = gQuoteLines.length; + wrapTextLines(gQuoteLines, startArrIndex, endArrIndex, 79 - (2*(lastQuoteLevel+1) + gQuotePrefix.length)); + // If quote lines were added as a result of wrapping, then + // determine the number of lines added, and update endArrIndex + // and quoteLineIndex accordingly. + if (gQuoteLines.length > numLinesBefore) + { + var numLinesAdded = gQuoteLines.length - numLinesBefore; + endArrIndex += numLinesAdded; + quoteLineIndex += (numLinesAdded-1); // - 1 because quoteLineIndex will be incremented by the for loop + } + // Put quote strings ("> ") back into the lines we just wrapped + if ((quoteLineIndex > 0) && (lastQuoteLevel > 0)) + { + quoteStr = ""; + for (i = 0; i < lastQuoteLevel; ++i) + quoteStr += "> "; + for (i = startArrIndex; i < endArrIndex; ++i) + gQuoteLines[i] = quoteStr + gQuoteLines[i].replace(/^\s*>/, ">"); + } + lastQuoteLevel = retObj.quoteLevel; + startArrIndex = quoteLineIndex; + } + } + // Wrap the last block of lines + wrapTextLines(gQuoteLines, startArrIndex, gQuoteLines.length, 79 - (2*(lastQuoteLevel+1) + gQuotePrefix.length)); + + // Go through the quote lines again, and for ones that start with " >", remove + // the leading whitespace. This is because the quote string is " > ", so it + // would insert an extra space before the first > in the quote line. + for (i = 0; i < gQuoteLines.length; ++i) + gQuoteLines[i] = gQuoteLines[i].replace(/^\s*>/, ">"); +} + +// This function displays debug text at a given location on the screen, then +// moves the cursor back to a given location. +// +// Parameters: +// pDebugX: The X lcoation of where to write the debug text +// pDebugY: The Y lcoation of where to write the debug text +// pText: The text to write at the debug location +// pOriginalPos: An object with x and y properties containing the original cursor position +// pClearDebugLineFirst: Whether or not to clear the debug line before writing the text +// pPauseAfter: Whether or not to pause after displaying the text +function displayDebugText(pDebugX, pDebugY, pText, pOriginalPos, pClearDebugLineFirst, pPauseAfter) +{ + console.gotoxy(pDebugX, pDebugY); + if (pClearDebugLineFirst) + console.clearline(); + // Output the text + console.print(pText); + if (pPauseAfter) + console.pause(); + if ((typeof(pOriginalPos) != "undefined") && (pOriginalPos != null)) + console.gotoxy(pOriginalPos); +} \ No newline at end of file -- GitLab