Repository: nvaccess/nvda

Issue:
Search history of NVDA find dialog does not handle Escape correctly when using Japanese IME
### Steps to reproduce:

1. start NVDA and Internet Explorer
2. make sure NVDA is in browse mode
3. press NVDA+Ctrl+F and open search dialog of NVDA
4. enable Japanese IME. editing of reading string starts.
5. type Latin characters, such as: t e s u t o
6. press Escape key, instead of IME translation key

### Actual behavior:

Search dialog closes.

### Expected behavior:

The reading string of IME is cleared.
Search dialog should not close.

### System configuration
#### NVDA installed/portable/running from source:

installer

#### NVDA version:

nvda_snapshot_beta-18133,b29acb42

#### Windows version:

Windows 10 version 1903 (Japanese)

#### Name and version of other software in use when reproducing the issue:

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your PC?

#### Have you tried any other versions of NVDA? If so, please report their behaviors.

2019.1.1 works as expected.

We believe it is related to #8482


Discussion:
CC @marlon-sousa

This isn't limited to Japanese - it also shows up when I try to type Korean text and attempt to close it - find dialog closes, whereas it should close composition window.

Thanks.
Hi,

Threshold nor wxPython 4.1.0 will resolve this problem unless we hack wxWidgets/wxPython. One possible solution might be wx.ComboCTRL, but it is a bit risky at this point unless we're aiming for 2019.3.

Thanks.
Hi,

According to:
https://trac.wxwidgets.org/ticket/11386

This is caused by the escape key being eaten by wxPython. In this case, we need to edit onCancel event in cursor manager's find dialog to not destroy the window if IME (input method editor) is on. Because of the fact that it affects languages with IME, this is a major regression (chiefly Chinese, Japanese, Korean, possibly more).

I can think of three ways to resolve this, depending on severity of this problem and how long it takes to edit and test a potential fix:

1. If it can be done this weekend or sometime next week: fix it as part of 2019.2 release candidate 1.
2. If it requires more testing: prepare 2019.2.1.
3. If this will impact more languages besides CJK: revert search history, reintroduce it in 2019.3 with Python 3 changes and fixes added.

I personally vote for option 2. Although someone can come up with a fix in the next few days, it must be tested to make sure it won't introduce more problems. Option 3 is a bit risky at the moment because my own fix for find dialog issue may or may not revert cleanly and we need to make sure the improved find dialog is Python 3 compliant (and test to see if it can be subjected to GUI helper enhancements).

Thanks.
L.Hello,
Can you confirm that this bug is found only in this version? I did not change anything related to key handling at all. If the behaviour is already found, we can take care of it latter.

Obrigado,
Marlon

Em 19 de jul de 2019, à(s) 20:37, Joseph Lee <notifications@github.com> escreveu:

> CC @marlon-sousa
> 
> This isn't limited to Japanese - it also shows up when I try to type Korean text and attempt to close it - find dialog closes, whereas it should close composition window.
> 
> Thanks.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or mute the thread.


Resolving patch:
diff --git a/source/cursorManager.py b/source/cursorManager.py
--- a/source/cursorManager.py
+++ b/source/cursorManager.py
@@ -1,4 +1,5 @@
 #cursorManager.py
+#A part of NonVisual Desktop Access (NVDA)
 #Copyright (C) 2006-2018 NV Access Limited, Joseph Lee, Derek Riemer, Davy Kager
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
@@ -26,15 +27,11 @@
 import ui
 from textInfos import DocumentWithPageTurns
 
-# search history list constants
-SEARCH_HISTORY_MOST_RECENT_INDEX = 0
-SEARCH_HISTORY_LEAST_RECENT_INDEX = 19
-
 class FindDialog(wx.Dialog):
 	"""A dialog used to specify text to find in a cursor manager.
 	"""
 
-	def __init__(self, parent, cursorManager, caseSensitivity, searchEntries):
+	def __init__(self, parent, cursorManager, text, caseSensitivity):
 		# Translators: Title of a dialog to find text.
 		super(FindDialog, self).__init__(parent, wx.ID_ANY, _("Find"))
 		# Have a copy of the active cursor manager, as this is needed later for finding text.
@@ -45,11 +42,8 @@ def __init__(self, parent, cursorManager, caseSensitivity, searchEntries):
 		# Translators: Dialog text for NvDA's find command.
 		textToFind = wx.StaticText(self, wx.ID_ANY, label=_("Type the text you wish to find"))
 		findSizer.Add(textToFind)
-		self.findTextField = wx.ComboBox(self, wx.ID_ANY, choices = searchEntries,style=wx.CB_DROPDOWN)
-
-		# if there is a previous list of searched entries, make sure we present the last searched term  selected by default
-		if searchEntries:
-			self.findTextField.Select(SEARCH_HISTORY_MOST_RECENT_INDEX)
+		self.findTextField = wx.TextCtrl(self, wx.ID_ANY)
+		self.findTextField.SetValue(text)
 		findSizer.Add(self.findTextField)
 		mainSizer.Add(findSizer,border=20,flag=wx.LEFT|wx.RIGHT|wx.TOP)
 		# Translators: An option in find dialog to perform case-sensitive search.
@@ -65,36 +59,8 @@ def __init__(self, parent, cursorManager, caseSensitivity, searchEntries):
 		self.CentreOnScreen()
 		self.findTextField.SetFocus()
 
-	def updateSearchEntries(self, searchEntries, currentSearchTerm):
-		if not currentSearchTerm:
-			return
-		if not searchEntries:
-			searchEntries.insert(SEARCH_HISTORY_MOST_RECENT_INDEX, currentSearchTerm)
-			return
-		# we can not accept entries that differ only on text case
-		#because of a wxComboBox limitation on MS Windows
-		# see https://wxpython.org/Phoenix/docs/html/wx.ComboBox.html
-		#notice also that python 2 does not offer caseFold functionality
-		# so lower is the best we can have for comparing strings
-		for index, item in enumerate(searchEntries):
-			if(item.lower() == currentSearchTerm.lower()):
-				# if the user has selected a previous search term in the list or retyped an already listed term , we need to make sure the 
-				# current search term becomes the first item of the list, so that it will appear selected by default when the dialog is
-				# shown again. If the current search term differs from the current item only in case letters, we will choose to store the
-				# new search as we can not store both.
-				searchEntries.pop(index)
-				searchEntries.insert(SEARCH_HISTORY_MOST_RECENT_INDEX, currentSearchTerm)
-				return
-		# not yet listed. Save it.
-		if len(searchEntries) > SEARCH_HISTORY_LEAST_RECENT_INDEX:
-			self._truncateSearchHistory(searchEntries)
-		searchEntries.insert(SEARCH_HISTORY_MOST_RECENT_INDEX, currentSearchTerm)
-		
 	def onOk(self, evt):
 		text = self.findTextField.GetValue()
-		# update the list of searched entries so that it can be exibited in the next find dialog call
-		self.updateSearchEntries(self.activeCursorManager._searchEntries, text)
-		
 		caseSensitive = self.caseSensitiveCheckBox.GetValue()
 		# We must use core.callLater rather than wx.CallLater to ensure that the callback runs within NVDA's core pump.
 		# If it didn't, and it directly or indirectly called wx.Yield, it could start executing NVDA's core pump from within the yield, causing recursion.
@@ -104,9 +70,6 @@ def onOk(self, evt):
 	def onCancel(self, evt):
 		self.Destroy()
 
-	def _truncateSearchHistory(self, entries):
-		del entries[SEARCH_HISTORY_LEAST_RECENT_INDEX:]
-
 class CursorManager(documentBase.TextContainerObject,baseObject.ScriptableObject):
 	"""
 	A mix-in providing caret navigation and selection commands for the object's virtual text range.
@@ -123,13 +86,8 @@ class CursorManager(documentBase.TextContainerObject,baseObject.ScriptableObject
 	# Translators: the script category for browse mode
 	scriptCategory=SCRCAT_BROWSEMODE
 
+	_lastFindText=""
 	_lastCaseSensitivity=False
-	#  History of search terms.
-	# Sorted in most to least recently searched order.
-	# First, most recently searched item index: SEARCH_HISTORY_MOST_RECENT_INDEX
-	# Last, least recently searched item index: SEARCH_HISTORY_LEAST_RECENT_INDEX
-	# Items that differ only by case will only have one entry. 
-	_searchEntries = []
 
 	def __init__(self, *args, **kwargs):
 		super(CursorManager, self).__init__(*args, **kwargs)
@@ -200,13 +158,14 @@ def doFindText(self,text,reverse=False,caseSensitive=False):
 			speech.speakTextInfo(info,reason=controlTypes.REASON_CARET)
 		else:
 			wx.CallAfter(gui.messageBox,_('text "%s" not found')%text,_("Find Error"),wx.OK|wx.ICON_ERROR)
+		CursorManager._lastFindText=text
 		CursorManager._lastCaseSensitivity=caseSensitive
 
 	def script_find(self,gesture):
 		# #8566: We need this to be a modal dialog, but it mustn't block this script.
 		def run():
 			gui.mainFrame.prePopup()
-			d = FindDialog(gui.mainFrame, self, self._lastCaseSensitivity, self._searchEntries)
+			d = FindDialog(gui.mainFrame, self, self._lastFindText, self._lastCaseSensitivity)
 			d.ShowModal()
 			gui.mainFrame.postPopup()
 		wx.CallAfter(run)
@@ -214,18 +173,18 @@ def run():
 	script_find.__doc__ = _("find a text string from the current cursor position")
 
 	def script_findNext(self,gesture):
-		if not self._searchEntries:
+		if not self._lastFindText:
 			self.script_find(gesture)
 			return
-		self.doFindText(self._searchEntries[SEARCH_HISTORY_MOST_RECENT_INDEX], caseSensitive = self._lastCaseSensitivity)
+		self.doFindText(self._lastFindText, caseSensitive = self._lastCaseSensitivity)
 	# Translators: Input help message for find next command.
 	script_findNext.__doc__ = _("find the next occurrence of the previously entered text string from the current cursor's position")
 
 	def script_findPrevious(self,gesture):
-		if not self._searchEntries:
+		if not self._lastFindText:
 			self.script_find(gesture)
 			return
-		self.doFindText(self._searchEntries[SEARCH_HISTORY_MOST_RECENT_INDEX], reverse=True, caseSensitive = self._lastCaseSensitivity)
+		self.doFindText(self._lastFindText,reverse=True, caseSensitive = self._lastCaseSensitivity)
 	# Translators: Input help message for find previous command.
 	script_findPrevious.__doc__ = _("find the previous occurrence of the previously entered text string from the current cursor's position")
 


Test patch: