Repository: nvaccess/nvda

Issue:
Support opening the Elements List dialog in focus mode
<!--
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/wiki/Github-issue-template-explanation-and-examples
-->

### Is your feature request related to a problem? Please describe.

The Elements List dialog can currently be opened (with `NVDA+F7`) only when in browse mode.
It is confusing for beginners, and uselessly tedious for more experienced ones.

### Describe the solution you'd like

Support invoking it when in focus mode too.
I guess the lesser impact UX would be to switch to browse mode when invoking the dialog from focus mode.

### Describe alternatives you've considered

### Additional context

PR on its way.


Discussion:

Resolving patch:
diff --git a/source/browseMode.py b/source/browseMode.py
--- a/source/browseMode.py
+++ b/source/browseMode.py
@@ -205,9 +205,15 @@ def activate(self):
 		self.textInfo.obj._activatePosition(info=self.textInfo)
 
 	def moveTo(self):
-		info=self.textInfo.copy()
+		if self.document.passThrough and getattr(self, "obj", False):
+			if controlTypes.STATE_FOCUSABLE in self.obj.states:
+				self.obj.setFocus()
+				return
+			self.document.passThrough = False
+			reportPassThrough(self.document)
+		info = self.textInfo.copy()
 		info.collapse()
-		self.document._set_selection(info,reason=REASON_QUICKNAV)
+		self.document._set_selection(info, reason=REASON_QUICKNAV)
 
 	@property
 	def isAfterSelection(self):
@@ -466,7 +472,7 @@ def addQuickNav(
 		if key is not None:
 			cls.__gestures["kb:shift+%s" % key] = scriptName
 
-	def script_elementsList(self,gesture):
+	def script_elementsList(self, gesture):
 		# We need this to be a modal dialog, but it mustn't block this script.
 		def run():
 			gui.mainFrame.prePopup()
@@ -475,8 +481,10 @@ def run():
 			d.Destroy()
 			gui.mainFrame.postPopup()
 		wx.CallAfter(run)
+
 	# Translators: the description for the Elements List command in browse mode.
 	script_elementsList.__doc__ = _("Lists various types of elements in this document")
+	script_elementsList.ignoreTreeInterceptorPassThrough = True
 
 	def _activateNVDAObject(self, obj):
 		"""Activate an object in response to a user request.
@@ -1171,6 +1179,7 @@ def onFilterEditTextChange(self, evt):
 		evt.Skip()
 
 	def onAction(self, activate):
+		prevFocus = gui.mainFrame.prevFocus
 		self.Close()
 		# Save off the last selected element type on to the class so its used in initialization next time.
 		self.__class__.lastSelectedElementType=self.lastSelectedElementType
@@ -1181,10 +1190,17 @@ def onAction(self, activate):
 		else:
 			def move():
 				speech.cancelSpeech()
-				# #8831: Report before moving because moving might change the focus, which
-				# might mutate the document, potentially invalidating info if it is
-				# offset-based.
-				item.report()
+				# Avoid double announce if item.obj is about to gain focus.
+				if not (
+					self.document.passThrough
+					and getattr(item, "obj", False)
+					and item.obj != prevFocus
+					and controlTypes.STATE_FOCUSABLE in item.obj.states
+				):
+					# #8831: Report before moving because moving might change the focus, which
+					# might mutate the document, potentially invalidating info if it is
+					# offset-based.
+					item.report()
 				item.moveTo()
 			# 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.


Test patch: