From 7a106715392207985aeeb3d5d05c7d74154501ee Mon Sep 17 00:00:00 2001
From: Eric Oulashin <nightfox@synchro.net>
Date: Tue, 14 Dec 2021 22:58:35 +0000
Subject: [PATCH] Better handling of ESC key input if mouse support is disabled

---
 exec/load/dd_lightbar_menu.js | 240 +++++++++++++++++++++++-----------
 1 file changed, 166 insertions(+), 74 deletions(-)

diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js
index ecd4170090..1487fcb6b9 100644
--- a/exec/load/dd_lightbar_menu.js
+++ b/exec/load/dd_lightbar_menu.js
@@ -234,6 +234,13 @@ If you want to set the currently selected item before calling GetVal() to allow
 you should call the SetSelectedItemIdx() function and pass the index to that.
 lbMenu.SetSelectedItemIdx(5);
 
+The property inputTimeoutMS sets the input timeout in milliseconds (defaults to 300000).
+lbMenu.inputTimeoutMS = 300000; // 300,000 milliseconds (5 minutes)
+
+The property mouseEnabled can be used to enable mouse support.  By default it is false.
+When mouse support is enabled, there can be problems inputting the ESC key from the user.
+lbMenu.mouseEnabled = true;
+
 For selecting an item, it may be desirable to validate whether a user should be allowed
 to select the item.  DDLightbarMenu has a member function it calls, ValidateSelectItem(),
 to do just that.  It takes the selected item's return value and returns a boolean to signify
@@ -427,9 +434,8 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight)
 	// (i.e. with ENTER; not for toggling with multi-select)
 	this.exitOnItemSelect = true;
 
-	// 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.inputTimeoutMS = 300000; // Input timeout in ms
+	this.mouseEnabled = false;
 
 	// Member functions
 	this.Add = DDLightbarMenu_Add;
@@ -1133,58 +1139,44 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 
 		// TODO: With mouse_getkey(), it seems you need to press ESC twice
 		// to get the ESC key and exit the menu
-		var mk = mouse_getkey(K_NOECHO|K_NOSPIN|K_NOCRLF, this.mouseTimeout > 1 ? this.mouseTimeout : undefined, this.mouseEnabled);
+		var inputMode = K_NOECHO|K_NOSPIN|K_NOCRLF;
+		var mk = null; // Will be used for mouse support
 		var mouseNoAction = false;
-		if (mk.mouse !== null)
+		if (this.mouseEnabled)
 		{
-			// See if the user clicked anywhere in the region where items are
-			// listed or the scrollbar
-			var clickRegion = this.GetMouseClickRegion();
-			// 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))
+			mk = mouse_getkey(inputMode, this.inputTimeoutMS > 1 ? this.inputTimeoutMS : undefined, this.mouseEnabled);
+			if (mk.mouse !== null)
 			{
-				var isDoubleClick = ((this.lastMouseClickTime > -1) && (system.timer - this.lastMouseClickTime <= 0.4));
-
-				// 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 = this.pos.x + this.size.width - 1;
-				if (this.borderEnabled)
-					--scrollbarX;
-				if ((mk.mouse.x == scrollbarX) && this.scrollbarEnabled)
+				// See if the user clicked anywhere in the region where items are
+				// listed or the scrollbar
+				var clickRegion = this.GetMouseClickRegion();
+				// 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))
 				{
-					var scrollbarSolidBlockEndRow = this.scrollbarInfo.solidBlockLastStartRow + this.scrollbarInfo.numSolidScrollBlocks - 1;
-					if (mk.mouse.y < this.scrollbarInfo.solidBlockLastStartRow)
-						this.lastUserInput = KEY_PAGEUP;
-					else if (mk.mouse.y > scrollbarSolidBlockEndRow)
-						this.lastUserInput = KEY_PAGEDN;
-					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?
-						this.lastUserInput = "";
-						mouseNoAction = true;
-						mouseInputOnly_continue = true;
-					}
-				}
-				else
-				{
-					// The user didn't click on the scrollbar or the scrollbar
-					// isn't enabled.
-					// For a double-click, if multi-select is enabled, set the
-					// last user input to a space to select/de-select the item.
-					if (isDoubleClick)
+					var isDoubleClick = ((this.lastMouseClickTime > -1) && (system.timer - this.lastMouseClickTime <= 0.4));
+
+					// 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 = this.pos.x + this.size.width - 1;
+					if (this.borderEnabled)
+						--scrollbarX;
+					if ((mk.mouse.x == scrollbarX) && this.scrollbarEnabled)
 					{
-						if (this.multiSelect)
-							this.lastUserInput = " ";
+						var scrollbarSolidBlockEndRow = this.scrollbarInfo.solidBlockLastStartRow + this.scrollbarInfo.numSolidScrollBlocks - 1;
+						if (mk.mouse.y < this.scrollbarInfo.solidBlockLastStartRow)
+							this.lastUserInput = KEY_PAGEUP;
+						else if (mk.mouse.y > scrollbarSolidBlockEndRow)
+							this.lastUserInput = KEY_PAGEDN;
 						else
 						{
-							// No mouse action
+							// 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?
 							this.lastUserInput = "";
 							mouseNoAction = true;
 							mouseInputOnly_continue = true;
@@ -1192,41 +1184,64 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 					}
 					else
 					{
-						// Make the clicked-on item the currently highlighted
-						// item.  Only select the item if the index is valid.
-						var topItemY = (this.borderEnabled ? this.pos.y + 1 : this.pos.y);
-						var distFromTopY = mk.mouse.y - topItemY;
-						var itemIdx = this.topItemIdx + distFromTopY;
-						if ((itemIdx >= 0) && (itemIdx < this.NumItems()))
+						// The user didn't click on the scrollbar or the scrollbar
+						// isn't enabled.
+						// For a double-click, if multi-select is enabled, set the
+						// last user input to a space to select/de-select the item.
+						if (isDoubleClick)
 						{
-							this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
-							this.selectedItemIdx = itemIdx;
-							this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
+							if (this.multiSelect)
+								this.lastUserInput = " ";
+							else
+							{
+								// No mouse action
+								this.lastUserInput = "";
+								mouseNoAction = true;
+								mouseInputOnly_continue = true;
+							}
+						}
+						else
+						{
+							// Make the clicked-on item the currently highlighted
+							// item.  Only select the item if the index is valid.
+							var topItemY = (this.borderEnabled ? this.pos.y + 1 : this.pos.y);
+							var distFromTopY = mk.mouse.y - topItemY;
+							var itemIdx = this.topItemIdx + distFromTopY;
+							if ((itemIdx >= 0) && (itemIdx < this.NumItems()))
+							{
+								this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
+								this.selectedItemIdx = itemIdx;
+								this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx));
+							}
+							// Don't have the later code do anything
+							this.lastUserInput = "";
+							mouseNoAction = true;
+							mouseInputOnly_continue = true;
 						}
-						// Don't have the later code do anything
-						this.lastUserInput = "";
-						mouseNoAction = true;
-						mouseInputOnly_continue = true;
 					}
-				}
 
-				this.lastMouseClickTime = system.timer;
+					this.lastMouseClickTime = system.timer;
+				}
+				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
+					this.lastUserInput = "";
+					mouseNoAction = true;
+					mouseInputOnly_continue = true;
+				}
 			}
 			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
-				this.lastUserInput = "";
-				mouseNoAction = true;
-				mouseInputOnly_continue = true;
+				// mouse is null, so a keybaord key must have been pressed
+				this.lastUserInput = mk.key;
 			}
 		}
-		else
+		else // this.mouseEnabled is false
 		{
-			// mouse is null, so a keybaord key must have been pressed
-			this.lastUserInput = mk.key;
+			this.lastUserInput = getKeyWithESCChars(inputMode, this.inputTimeoutMS);
 		}
 
 		// If no further input processing needs to be done due to a mouse click
@@ -1240,8 +1255,14 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 			// Only exit if there was not a no-action mouse click
 			// TODO: Is this logic good and clean?
 			var goAheadAndExit = true;
-			if (mk.mouse !== null)
+			if (mk !== null && mk.mouse !== null)
+			{
 				goAheadAndExit = !mouseNoAction; // Only really needed with an input timer?
+				// Temporary
+				console.print("\1n\r\nHere! - mouseNoAction: " + goAheadAndExit + ", goAheadAndExit: " + goAheadAndExit + "\r\n");
+				console.pause();
+				// End Temporary
+			}
 			if (goAheadAndExit)
 			{
 				continueOn = false;
@@ -2584,3 +2605,74 @@ function printedToRealIdxInStr(pStr, pIdx)
 	}
 	return realIdx;
 }
+
+// Inputs a keypress from the user and handles some ESC-based
+// characters such as PageUp, PageDown, and ESC.  If PageUp
+// or PageDown are pressed, this function will return the
+// string defined by KEY_PAGE_UP or EY_PAGE_DOWN,
+// respectively.  Also, F1-F5 will be returned as "\1F1"
+// through "\1F5", respectively.
+// Thanks goes to Psi-Jack for the original impementation
+// of this function.
+//
+// Parameters:
+//  pGetKeyMode: Optional - The mode bits for console.getkey().
+//               If not specified, K_NONE will be used.
+//  pInputTimeoutMS: The input timeout in milliseconds (defaults to 300000).
+//                   If the user is a sysop, this will use a timeout of 0 for no timeout.
+//
+// Return value: The user's keypress
+function getKeyWithESCChars(pGetKeyMode, pInputTimeoutMS)
+{
+	var getKeyMode = (typeof(pGetKeyMode) === "number" ? pGetKeyMode : K_NONE);
+	var inputTimeoutMS = (typeof(pInputTimeoutMS) === "number" ? pInputTimeoutMS : 300000);
+	if (inputTimeoutMS == 0)
+		inputTimeoutMS = 300000;
+	// Input a key from the user and take action based on the user's input.  If
+	// the user is a sysop, don't use an input timeout.
+	var userInput = "";
+	if (user.compare_ars("SYSOP"))
+		userInput = console.getkey(getKeyMode);
+	else
+		userInput = console.inkey(getKeyMode, inputTimeoutMS);
+	if (userInput == KEY_ESC)
+	{
+		switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
+		{
+			case '[':
+				switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
+				{
+					case 'V':
+						userInput = KEY_PAGE_UP;
+						break;
+					case 'U':
+						userInput = KEY_PAGE_DOWN;
+						break;
+				}
+				break;
+			case 'O':
+				switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
+				{
+					case 'P':
+						userInput = KEY_F1;
+						break;
+					case 'Q':
+						userInput = KEY_F2;
+						break;
+					case 'R':
+						userInput = KEY_F3;
+						break;
+					case 'S':
+						userInput = KEY_F4;
+						break;
+					case 't':
+						userInput = KEY_F5;
+						break;
+				}
+			default:
+				break;
+		}
+	}
+
+	return userInput;
+}
-- 
GitLab