Repository: nvaccess/nvda

Issue:
Object explorer not working correctly in Visual Studio 2017 (v15.3 and up)
<!--
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
-->

### Steps to reproduce:

1. Open any type of project with object explorer available, such _.NET_.
1. Open object explorer with <kbd>ALT+CTRL+j</kbd>.
1. Ensure you're in the objects tree and select the first expandable item.
1. Expand it by pressing <kbd>right arrow</kbd>.
1. Enter into it with <kbd>right</kbd> or <kbd>down</kbd> arrow.
1. Contract it by pressing <kbd>left arrow</kbd> twice.
1. Press the <kbd>END</kbd> key to select the last item on the tree.
1. Repeat the last four steps with this last one.

### Actual behavior:

When entering into the expanded item, the next item gains the focus. It occurs only if the expanded item is not the last.

If the expanded item is the last, it works as expected.

### Expected behavior:

Expected behavior is like any other tree view even under visual studio, like the solution explorer.

### System configuration
#### NVDA installed/portable/running from source:

Installed

#### NVDA version:

alpha-16813,a0b991af

#### Windows version:

Version	10.0.17763 build 17763

#### Name and version of other software in use when reproducing the issue:

Visual Studio Community 15.9.7

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your PC?

Yes, reboot doesn't change anything.

#### Have you tried any other versions of NVDA?

I've used all stable versions of nvda between 2017.3 and this alpha I'm using now.


Discussion:

Resolving patch:
diff --git a/source/appModules/devenv.py b/source/appModules/devenv.py
--- a/source/appModules/devenv.py
+++ b/source/appModules/devenv.py
@@ -48,8 +48,10 @@
 from NVDAObjects.window import Window
 
 from NVDAObjects.window import DisplayModelEditableText
+from NVDAObjects.IAccessible import IAccessible
 
 import appModuleHandler
+import controlTypes
 
 
 #
@@ -88,6 +90,9 @@
 
 class AppModule(appModuleHandler.AppModule):
 	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
+		vsMajor, vsMinor, rest = self.productVersion.split(".", 2)
+		vsMajor, vsMinor = int(vs_major), int(vs_minor)
+
 		# Only use this overlay class if the top level automation object for the IDE can be retrieved,
 		# as it will not work otherwise.
 		if obj.windowClassName == VsTextEditPaneClassName and self._getDTE():
@@ -96,7 +101,13 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList):
 			except ValueError:
 				pass
 			clsList.insert(0, VsTextEditPane)
-			
+
+		if ((vsMajor == 15 and vsMinor >= 3)
+			or vsMajor >= 16):
+			if obj.role == controlTypes.ROLE_TREEVIEWITEM and obj.windowClassName == "LiteTreeView32":
+				clsList.insert(0, ObjectsTreeItem)
+
+
 	def _getDTE(self):
 	# Return the already fetched instance if there is one.
 		try:
@@ -104,7 +115,7 @@ def _getDTE(self):
 				return self._DTE
 		except AttributeError:
 			pass
-		
+
 		# Retrieve and cache the top level automation object for the IDE
 		DTEVersion = VsVersion_None
 		bctx = objbase.CreateBindCtx()
@@ -120,7 +131,7 @@ def _getDTE(self):
 				DTEVersion = VsVersion_2003
 			elif "!VisualStudio.DTE:%d"%self.processID==displayName:
 				DTEVersion = VsVersion_2002
-				
+
 			if DTEVersion != VsVersion_None:
 				self._DTEVersion = DTEVersion
 				self._DTE = comtypes.client.dynamic.Dispatch(ROT.GetObject(mon).QueryInterface(IDispatch))
@@ -134,7 +145,7 @@ def _getDTE(self):
 
 		# Loop has completed
 		return self._DTE
-		
+
 	def _getTextManager(self):
 		try:
 			if self._textManager:
@@ -145,7 +156,6 @@ def _getTextManager(self):
 		self._textManager = serviceProvider.QueryService(SVsTextManager, IVsTextManager)
 		return self._textManager
 
-
 class VsTextEditPaneTextInfo(textInfos.offsets.OffsetsTextInfo):
 	def _InformUnsupportedWindowType(self,type):
 		log.error("An unsupported window type `%d' was encountered, please inform the NVDA development team." %type)
@@ -473,3 +483,23 @@ class IVsTextManager(comtypes.IUnknown):
     COMMETHOD([], HRESULT, 'SetTopLine',
               ( ['in'], c_int, 'iBaseLine' )),
 ]
+
+class ObjectsTreeItem(IAccessible):
+
+	def _get_focusRedirect(self):
+		"""
+		Returns the correct focused item in the object explorer trees
+		"""
+
+		if not controlTypes.STATE_FOCUSED in self.states:
+			# Object explorer tree views have a bad IAccessible implementation.
+			# When expanding a primary node and going to secondary node, the 
+			# focus is placed to the next root node, so we need to redirect
+			# it to the real focused widget. Fortunately, the states are
+			# still correct and we can detect if this is really focused or not.
+			return self.objectWithFocus()
+
+	def _get_positionInfo(self):
+		return {
+			"level": int(self.IAccessibleObject.accValue(self.IAccessibleChildID))
+		}


Test patch: