From 2b46c12d21e1e12356e374e0db6a9c1a351f258c Mon Sep 17 00:00:00 2001
From: nightfox <>
Date: Sat, 11 Jul 2020 23:07:46 +0000
Subject: [PATCH] Version 1.37: Added mouse support to the scrollable reader
 interface.  The integrated area changer functionality doesn't have mouse
 support yet.

---
 xtrn/DDMsgReader/DDMsgReader.js       | 301 ++++++++++++++++++++++++--
 xtrn/DDMsgReader/readme.txt           |   4 +-
 xtrn/DDMsgReader/revision_history.txt |   3 +
 3 files changed, 290 insertions(+), 18 deletions(-)

diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js
index 72aff6c03f..d7360a6566 100644
--- a/xtrn/DDMsgReader/DDMsgReader.js
+++ b/xtrn/DDMsgReader/DDMsgReader.js
@@ -65,6 +65,10 @@
  *                              email to the user.  And for integration with Synchronet
  *                              via the "Read Email" loadable module, this is to
  *                              be used together with the updated DDReadPersonalEmail.js.
+ * 2020-07-11 Eric Oulashin     Version 1.37
+ *                              Added mouse support to the scrollable reader interface.
+ *                              The integrated area changer functionality doesn't have mouse
+ *                              support yet.
  */
 
 
@@ -148,6 +152,7 @@ if (requireFnExists)
 	require("utf8_cp437.js", "utf8_cp437");
 	require("userdefs.js", "USER_UTF8");
 	require("dd_lightbar_menu.js", "DDLightbarMenu");
+	require("mouse_getkey.js", "mouse_getkey");
 }
 else
 {
@@ -156,6 +161,7 @@ else
 	load("utf8_cp437.js");
 	load("userdefs.js");
 	load("dd_lightbar_menu.js");
+	load("mouse_getkey.js");
 }
 
 // This script requires Synchronet version 3.15 or higher.
@@ -174,8 +180,8 @@ if (system.version_num < 31500)
 }
 
 // Reader version information
-var READER_VERSION = "1.35";
-var READER_DATE = "2020-05-13";
+var READER_VERSION = "1.37";
+var READER_DATE = "2020-07-11";
 
 // Keyboard key codes for displaying on the screen
 var UP_ARROW = ascii(24);
@@ -729,6 +735,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	this.WriteMsgListScreenTopHeader = DigDistMsgReader_WriteMsgListScreenTopHeader;
 	this.ReadMessageEnhanced = DigDistMsgReader_ReadMessageEnhanced;
 	this.ReadMessageEnhanced_Scrollable = DigDistMsgReader_ReadMessageEnhanced_Scrollable;
+	this.ScrollReaderDetermineClickCoordAction = DigDistMsgReader_ScrollReaderDetermineClickCoordAction;
 	this.ReadMessageEnhanced_Traditional = DigDistMsgReader_ReadMessageEnhanced_Traditional;
 	this.EnhReaderPrepLast2LinesForPrompt = DigDistMsgReader_EnhReaderPrepLast2LinesForPrompt;
 	this.LookForNextOrPriorNonDeletedMsg = DigDistMsgReader_LookForNextOrPriorNonDeletedMsg;
@@ -917,6 +924,10 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	// extended read mode
 	this.numTabSpaces = 3;
 
+	// Things for mouse support
+	this.mouseTimeout = 0; // Timeout in ms.  Currently using 0 for no timeout.
+	this.mouseEnabled = false; // To pass to mouse_getkey
+
 	// this.text is an object containing text used for various prompts & functions.
 	this.text = {
 		scrollbarBGChar: BLOCK1,
@@ -1114,6 +1125,9 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	// Enhanced reader help line (will be set up in
 	// DigDistMsgReader_SetEnhancedReaderHelpLine())
 	this.enhReadHelpLine = "";
+	// This array will store object with x and y coordinates for mouse click locations
+	// for the enhanced reader help line, as well as a string describing the action.
+	this.enhReadHelpLineClickCoords = [];
 
 	// Read the enhanced message header file and populate this.enhMsgHeaderLines,
 	// the header text for enhanced reader mode.  The enhanced reader header file
@@ -4465,11 +4479,25 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA
 		}
 		else
 		{
+			var scrollbarInfoObj = {
+				solidBlockLastStartRow: 0,
+				numSolidScrollBlocks: 0
+			};
+			scrollbarInfoObj.solidBlockLastStartRow = solidBlockLastStartRow;
+			scrollbarInfoObj.numSolidScrollBlocks = numSolidScrollBlocks;
 			scrollRetObj = scrollTextLines(msgInfo.messageLines, topMsgLineIdx,
-										   this.colors["msgBodyColor"], writeMessage,
+										   this.colors.msgBodyColor, writeMessage,
 										   this.msgAreaLeft, this.msgAreaTop, this.msgAreaWidth,
 										   msgAreaHeight, 1, console.screen_rows,
-										   msgScrollbarUpdateFn);
+										   msgScrollbarUpdateFn, scrollbarInfoObj,
+										   this.enhReadHelpLineClickCoords);
+			if (scrollRetObj.mouse != null)
+			{
+				// See if there was a click in one of the reader help line click coordinates
+				var clickCoordRetObj = this.ScrollReaderDetermineClickCoordAction(scrollRetObj, this.enhReadHelpLineClickCoords);
+				if (clickCoordRetObj.actionStr.length > 0)
+					scrollRetObj.lastKeypress = clickCoordRetObj.actionStr; // A bit of a kludge
+			}
 		}
 		topMsgLineIdx = scrollRetObj.topLineIdx;
 		retObj.lastKeypress = scrollRetObj.lastKeypress;
@@ -5416,6 +5444,67 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA
 
 	return retObj;
 }
+// Helper method for ReadMessageEnhanced() - Determines the next keypress for a click
+// coordinate outside the scroll area.
+//
+// Parameters:
+//  pScrollRetObj: The return object of the message scroll function
+//  pEnhReadHelpLineClickCoords: An array of click coordinates & action strings
+//
+// Return value: An object containing the following properties:
+//               actionStr: A string containing the next action for the enhanced reader,
+//                          or an empty string if there was no valid action found.
+function DigDistMsgReader_ScrollReaderDetermineClickCoordAction(pScrollRetObj, pEnhReadHelpLineClickCoords)
+{
+	var retObj = {
+		actionStr: ""
+	};
+
+	for (var coordIdx = 0; coordIdx < pEnhReadHelpLineClickCoords.length; ++coordIdx)
+	{
+		if ((pScrollRetObj.mouse.x == pEnhReadHelpLineClickCoords[coordIdx].x) && (pScrollRetObj.mouse.y == pEnhReadHelpLineClickCoords[coordIdx].y))
+		{
+			// The up arrow, down arrow, PageUp, PageDown, Home, and End aren't handled
+			// here - Those are handled in scrollTextlines().
+			if (pEnhReadHelpLineClickCoords[coordIdx].actionStr == LEFT_ARROW)
+				retObj.actionStr = this.enhReaderKeys.previousMsg;
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr == RIGHT_ARROW)
+				retObj.actionStr = this.enhReaderKeys.nextMsg;
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("DEL") == 0)
+				retObj.actionStr = this.enhReaderKeys.deleteMessage;
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("E)") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.editMsg;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("F") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.firstMsg;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("L") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.lastMsg;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("R") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.reply;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("C") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.chgMsgArea;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("Q") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.quit;
+			}
+			else if (pEnhReadHelpLineClickCoords[coordIdx].actionStr.indexOf("?") == 0)
+			{
+				retObj.actionStr = this.enhReaderKeys.showHelp;
+			}
+			break;
+		}
+	}
+	return retObj;
+}
 // Helper method for ReadMessageEnhanced() - Does the traditional (non-scrollable) reader interface
 function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsgArea, messageText, msgHasANSICodes, pOffset)
 {
@@ -7324,6 +7413,47 @@ function DigDistMsgReader_SetEnhancedReaderHelpLine()
 		for (var i = 0; i < numCharsRemaining; ++i)
 		this.enhReadHelpLineWithoutChgArea += " ";
 	}
+
+	// Set up this.enhReadHelpLineClickCoords as an array of objects containing X and Y
+	// coordinates for mouse click coordinates
+	this.enhReadHelpLineClickCoords = [];
+	var helpLineNoAttrs = stripCtrlFromEnhReadHelpLine_ReplaceArrowChars(this.enhReadHelpLine);
+	var clickX = 0;
+	var toSearch = [UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, "PgUp", "Dn,", "HOME", "END", "DEL",
+	                "E)", "F)", "L)", "R)", "C)", "Q)", "?"];
+	for (var i = 0; i < toSearch.length; ++i)
+	{
+		var helpLineIdx = helpLineNoAttrs.indexOf(toSearch[i]);
+		if (helpLineIdx > -1)
+		{
+			// TODO: We don't really need to include the ) on the ones with the ).  That is
+			// just to ensure we find the right ones.
+			for (strI = 0; strI < toSearch[i].length; ++strI)
+			{
+				var clickInfoObj = { x: helpLineIdx+strI+1,
+				                     y: console.screen_rows,
+				                     actionStr: toSearch[i]
+								   };
+				this.enhReadHelpLineClickCoords.push(clickInfoObj);
+			}
+		}
+	}
+}
+function stripCtrlFromEnhReadHelpLine_ReplaceArrowChars(pHelpLine)
+{
+	var helpLineNoAttrs = strip_ctrl(pHelpLine);
+	var charsToPutBack = [UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW];
+	var helpLineIdx = -1;
+	for (var i = 0; i < charsToPutBack.length; ++i)
+	{
+		helpLineIdx = helpLineNoAttrs.indexOf(",", helpLineIdx+1);
+		if (helpLineIdx > -1)
+		{
+			helpLineNoAttrs = helpLineNoAttrs.substr(0, helpLineIdx) + charsToPutBack[i] + helpLineNoAttrs.substr(helpLineIdx);
+			++helpLineIdx;
+		}
+	}
+	return helpLineNoAttrs;
 }
 // For the DigDistMsgReader class: Reads the configuration file (by default,
 // DDMsgReader.cfg) and sets the object properties
@@ -14942,8 +15072,14 @@ function userHandleAliasNameMatch(pName)
 // Return value: An object with the following properties:
 //               lastKeypress: The last key pressed by the user (a string)
 //               topLineIdx: The new top line index of the text lines, in case of scrolling
+//               mouse: An object containing mouse event information, or null
+//                      if the user didn't use the mouse on the last user input
+// TODO: Use the parameter pOutsideMouseEventCoords for X & Y coordinates of mouse click
+// coordinates outside the scrollable region so that calling code can respond to those
+// mouse events
 function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTopLeftX, pTopLeftY,
-                         pWidth, pHeight, pPostWriteCurX, pPostWriteCurY, pScrollUpdateFn)
+                         pWidth, pHeight, pPostWriteCurX, pPostWriteCurY, pScrollUpdateFn,
+                         pScrollbarInfo, pOutsideMouseEventCoords)
 {
 	// Variables for the top line index for the last page, scrolling, etc.
 	var topLineIdxForLastPage = pTxtLines.length - pHeight;
@@ -14958,7 +15094,8 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo
 
 	var retObj = {
 		lastKeypress: "",
-		topLineIdx: pTopLineIdx
+		topLineIdx: pTopLineIdx,
+		mouse: null
 	};
 
 	// Create an array of color/attribute codes for each line of
@@ -14975,8 +15112,11 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo
 
 	var writeTxtLines = pWriteTxtLines;
 	var continueOn = true;
+	var mouseInputOnly_continue = false;
 	while (continueOn)
 	{
+		mouseInputOnly_continue = false;
+
 		// If we are to write the text lines, then write each of them and also
 		// clear out the rest of the row on the screen
 		if (writeTxtLines)
@@ -15011,7 +15151,136 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo
 
 		// Get a keypress from the user and take action based on it
 		console.gotoxy(pPostWriteCurX, pPostWriteCurY);
-		retObj.lastKeypress = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOECHO|K_NOSPIN);
+		//retObj.lastKeypress = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOECHO|K_NOSPIN);
+		var mk = mouse_getkey(K_NOCRLF|K_NOECHO|K_NOSPIN, this.mouseTimeout > 1 ? this.mouseTimeout : undefined, this.mouseEnabled);
+		retObj.mouse = mk.mouse;
+		var mouseNoAction = false;
+		if (mk.mouse !== null)
+		{
+			// See if the user clicked anywhere in the scrollable window area
+			var clickRegion = {
+				left: pTopLeftX,
+				//right: pTopLeftX + pWidth - 1,
+				right: pTopLeftX + pWidth,
+				top: pTopLeftY,
+				bottom: pTopLeftY + pHeight - 1
+			};
+			// Button 0 is the left/main mouse button
+			if (mk.mouse.press && (mk.mouse.button == 0) && (mk.mouse.motion == 0) &&
+			    (mk.mouse.x >= clickRegion.left) && (mk.mouse.x <= clickRegion.right) &&
+			    (mk.mouse.y >= clickRegion.top) && (mk.mouse.y <= clickRegion.bottom))
+			{
+				// If the scrollbar is enabled, then see if the mouse click was
+				// in the scrollbar region.  If below the scrollbar bright blocks,
+				// then we'll want to do a PageDown.  If above the scrollbar bright
+				// blocks, then we'll want to do a PageUp.
+				var scrollbarX = console.screen_columns;
+				if (mk.mouse.x == scrollbarX)
+				{
+					// If scrollbar information is available, then we can check to see if
+					// the mouse was clicked in the empty regions of the scrollbar.
+					if ((typeof(pScrollbarInfo) == "object") && pScrollbarInfo.hasOwnProperty("solidBlockLastStartRow") && pScrollbarInfo.hasOwnProperty("numSolidScrollBlocks"))
+					{
+						var scrollbarSolidBlockEndRow = pScrollbarInfo.solidBlockLastStartRow + pScrollbarInfo.numSolidScrollBlocks - 1;
+						if (mk.mouse.y < pScrollbarInfo.solidBlockLastStartRow)
+							retObj.lastKeypress = KEY_PAGE_UP;
+						else if (mk.mouse.y > scrollbarSolidBlockEndRow)
+							retObj.lastKeypress = KEY_PAGE_DOWN;
+						else
+						{
+							// Mouse click no-action
+							// TODO: Can we detect if they're holding the mouse down
+							// and scroll while the user holds the mouse & scrolls on
+							// the scrollbar?
+							retObj.lastKeypress = "";
+							mouseNoAction = true;
+							mouseInputOnly_continue = true;
+						}
+					}
+					else
+					{
+						// No mouse action
+						retObj.lastKeypress = "";
+						mouseNoAction = true;
+						mouseInputOnly_continue = true;
+					}
+				}
+			}
+			// If pOutsideMouseEventCoords is an array, then look through it
+			// for any coordinates outside of clickRegion, and if found,
+			// we'll want to exit the input loop and return.
+			else if((typeof(pOutsideMouseEventCoords) == "object") && (pOutsideMouseEventCoords.length > 0))
+			{
+				var foundOutsideCoord = false;
+				var coordActionStr = "";
+				for (var coordsIdx = 0; (coordsIdx < pOutsideMouseEventCoords.length) && !foundOutsideCoord; ++coordsIdx)
+				{
+					// If the current element has x & y properties, then
+					// if either the x & y coordinate is outside the scrollable
+					// region and the mouse click x & y coordinates match the current
+					// element's coordinates, then we've found an ousdide coordinate.
+					if (pOutsideMouseEventCoords[coordsIdx].hasOwnProperty("x") && pOutsideMouseEventCoords[coordsIdx].hasOwnProperty("y"))
+					{
+						var xCoordOutsideClickRegion = ((pOutsideMouseEventCoords[coordsIdx].x < clickRegion.left) || (pOutsideMouseEventCoords[coordsIdx].x > clickRegion.right));
+						var yCoordOutsideClickRegion = ((pOutsideMouseEventCoords[coordsIdx].y < clickRegion.top) || (pOutsideMouseEventCoords[coordsIdx].y > clickRegion.bottom));
+						if (xCoordOutsideClickRegion || yCoordOutsideClickRegion)
+						{
+							foundOutsideCoord = ((mk.mouse.x == pOutsideMouseEventCoords[coordsIdx].x) && (mk.mouse.y == pOutsideMouseEventCoords[coordsIdx].y));
+							if (foundOutsideCoord)
+								coordActionStr = pOutsideMouseEventCoords[coordsIdx].actionStr;
+						}
+					}
+				}
+				// If we found an outside coordinate, check to see if it's for a
+				// scroll navigation action.  If not, then we went to exit the input loop.
+				if (foundOutsideCoord)
+				{
+					if (coordActionStr == UP_ARROW)
+						retObj.lastKeypress = KEY_UP;
+					else if (coordActionStr == DOWN_ARROW)
+						retObj.lastKeypress = KEY_DOWN;
+					else if (coordActionStr.indexOf("PgUp") == 0)
+						retObj.lastKeypress = KEY_PAGE_UP;
+					else if (coordActionStr.indexOf("Dn") == 0)
+						retObj.lastKeypress = KEY_PAGE_DOWN;
+					else if (coordActionStr.indexOf("HOME") == 0)
+						retObj.lastKeypress = KEY_HOME;
+					else if (coordActionStr.indexOf("END") == 0)
+						retObj.lastKeypress = KEY_END;
+					else
+					{
+						// The click coordinate is not for a scroll action, so
+						// we should exit the input loop to let the calling code
+						// handle it.
+						retObj.lastKeypress = "";
+						mouseNoAction = true;
+						mouseInputOnly_continue = false;
+						continueOn = false;
+						break;
+					}
+				}
+			}
+			else
+			{
+				// The mouse click is outside the click region.  Set the appropriate
+				// variables for mouse no-action.
+				// TODO: Perhaps this may also need to be done in some places above
+				// where no action needs to be taken
+				retObj.lastKeypress = "";
+				mouseNoAction = true;
+				mouseInputOnly_continue = true;
+			}
+		}
+		else
+		{
+			// mouse is null, so a keybaord key must have been pressed
+			retObj.lastKeypress = mk.key.toUpperCase();
+		}
+		if (mouseInputOnly_continue)
+			continue;
+		if (!continueOn)
+			break;
+
 		switch (retObj.lastKeypress)
 		{
 			case KEY_UP:
@@ -15028,15 +15297,6 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo
 					writeTxtLines = true;
 				}
 				break;
-			case KEY_PAGE_DOWN: // Next page
-				if (retObj.topLineIdx < topLineIdxForLastPage)
-				{
-					retObj.topLineIdx += pHeight;
-					if (retObj.topLineIdx > topLineIdxForLastPage)
-						retObj.topLineIdx = topLineIdxForLastPage;
-					writeTxtLines = true;
-				}
-				break;
 			case KEY_PAGE_UP: // Previous page
 				if (retObj.topLineIdx > 0)
 				{
@@ -15046,6 +15306,15 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo
 					writeTxtLines = true;
 				}
 				break;
+			case KEY_PAGE_DOWN: // Next page
+				if (retObj.topLineIdx < topLineIdxForLastPage)
+				{
+					retObj.topLineIdx += pHeight;
+					if (retObj.topLineIdx > topLineIdxForLastPage)
+						retObj.topLineIdx = topLineIdxForLastPage;
+					writeTxtLines = true;
+				}
+				break;
 			case KEY_HOME: // First page
 				if (retObj.topLineIdx > 0)
 				{
diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt
index 0deaf63386..40441f125b 100644
--- a/xtrn/DDMsgReader/readme.txt
+++ b/xtrn/DDMsgReader/readme.txt
@@ -1,6 +1,6 @@
                       Digital Distortion Message Reader
-                                 Version 1.36
-                           Release date: 2020-05-23
+                                 Version 1.37
+                           Release date: 2020-07-11
 
                                      by
 
diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt
index 4a2a299d5d..d09a21e96a 100644
--- a/xtrn/DDMsgReader/revision_history.txt
+++ b/xtrn/DDMsgReader/revision_history.txt
@@ -5,6 +5,9 @@ Revision History (change log)
 =============================
 Version  Date         Description
 -------  ----         -----------
+1.37     2020-07-11   Added mouse support to the scrollable reader interface.
+                      The integrated area changer functionality doesn't have
+                      mouse support yet.
 1.36     2020-05-23   Added a command-line parameter, -onlyNewPersonalEmail,
                       which specifies to list/read only new/unread personal
                       email to the user.  And for integration with Synchronet
-- 
GitLab