Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uia in ms word by default #8919

Merged
merged 11 commits into from
Jan 15, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions source/NVDAObjects/UIA/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,8 @@ def findOverlayClasses(self,clsList):
UIAClassName=self.UIAElement.cachedClassName
if UIAClassName=="WpfTextView":
clsList.append(WpfTextView)
elif UIAClassName=="NetUIDropdownAnchor":
clsList.append(NetUIDropdownAnchor)
elif self.TextInfo==UIATextInfo and (UIAClassName=='_WwG' or self.windowClassName=='_WwG' or self.UIAElement.cachedAutomationID.startswith('UIA_AutomationId_Word_Content')):
from .wordDocument import WordDocument, WordDocumentNode
if self.role==controlTypes.ROLE_DOCUMENT:
Expand Down Expand Up @@ -1602,3 +1604,14 @@ def event_UIA_elementSelected(self):
self.reportFocus()
# Display results as flash messages.
braille.handler.message(braille.getBrailleTextForProperties(name=self.name, role=self.role, positionInfo=self.positionInfo))

# NetUIDropdownAnchor comboBoxes (such as in the MS Office Options dialog)
class NetUIDropdownAnchor(UIA):

def _get_name(self):
name=super(NetUIDropdownAnchor,self).name
# In MS Office 2010, these combo boxes had no name.
# However, the name can be found as the direct previous sibling label element.
if not name and self.previous and self.previous.role==controlTypes.ROLE_STATICTEXT:
name=self.previous.name
return name
35 changes: 26 additions & 9 deletions source/_UIAHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,22 +351,39 @@ def _isUIAWindowHelper(self,hwnd):
# There are certain window classes that just had bad UIA implementations
if windowClass in badUIAWindowClassNames:
return False
if windowClass=="NetUIHWND":
parentHwnd=winUser.getAncestor(hwnd,winUser.GA_ROOT)
# #2816: Outlook 2010 auto complete does not fire enough UIA events, IAccessible is better.
# #4056: Combo boxes in Office 2010 Options dialogs don't expose a name via UIA, but do via MSAA.
if winUser.getClassName(parentHwnd) in {"Net UI Tool Window","NUIDialog"}:
return False
# allow the appModule for the window to also choose if this window is bad
if appModule and appModule.isBadUIAWindow(hwnd):
return False
# Ask the window if it supports UIA natively
res=windll.UIAutomationCore.UiaHasServerSideProvider(hwnd)
if res:
# the window does support UIA natively, but
# Microsoft Word should not use UIA unless we can't inject or the user explicitly chose to use UIA with Microsoft word
if windowClass=="_WwG" and not (config.conf['UIA']['useInMSWordWhenAvailable'] or not appModule.helperLocalBindingHandle):
return False
# MS Word documents now have a very usable UI Automation implementation. However,
# Builds of MS Office 2016 before build 9000 or so had bugs which we cannot work around.
# Therefore refuse to use UIA for builds earlier than this, if we can inject in-process.
if (
# An MS Word document window
windowClass=="_WwG"
# Disabling is only useful if we can inject in-process (and use our older code)
and appModule.helperLocalBindingHandle
# Allow the user to explisitly force UIA support for MS Word documents no matter the Office version
and not config.conf['UIA']['useInMSWordWhenAvailable']
):
# We can only safely check the version of known Office apps using the Word document control.
# Other uses for now we just need to assume the implementation is good.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not clear to me yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment could be reworded. Are you saying that "for unknown office apps, which we can not safely check the version using the Word document control, we must just assume the implementation is good"?
Are we assuming that the UIA implementation is good?

if appModule.appName not in ('outlook','winword','excel'):
log.debugWarning("Unknown application using MS Word document control: %s"%appModule.appName)
return True
try:
versionMajor,versionMinor,versionBuild,versionPatch=[int(x) for x in appModule.productVersion.split('.')]
except Exception as e:
log.error("Error parsing versioninformation %s, %s"%(appModule.productVersion,e))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space between version and information

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This space is still missing.

return True
if (
versionMajor<16
or versionMajor==16 and versionMinor==0 and versionBuild<9000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer to see these values (16, 0, 9000) put in a named constant.

):
return False
return bool(res)

def isUIAWindow(self,hwnd):
Expand Down
30 changes: 22 additions & 8 deletions source/appModules/outlook.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,15 @@ def _get_outlookVersion(self):
return outlookVersion

def isBadUIAWindow(self,hwnd):
if winUser.getClassName(hwnd) in ("WeekViewWnd","DayViewWnd"):
windowClass=winUser.getClassName(hwnd)
# #2816: Outlook versions before 2016 auto complete does not fire enough UIA events, IAccessible is better.
if windowClass=="NetUIHWND":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few usages of this "NetUIHWnd" can you put them in a named constant, perhaps outlookWindowClass

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this would improve things. A window class string is as gooder constant from my point of view. Something like OutlookWindowClass does not addiquitly describe the string as this NetUIHWND class is used in more than just Outlook. It seems to be some kind of generic window class for Ribbons. But to be clear, not the only one though, there is also NetUIToolWindow. Personally I think the strings are good enough. However, I guess we could convert them in to constants I.e. NetUIHWND="NetUIHWND" but I really don't see the point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. My hope was that we could describe the string better. As someone unfamiliar with the value, it doesn't tell you any of the things you just mentioned. Having a constant with comment you just wrote would be helpful in my opinion. Ideally a name that describes it well, it seems like that is difficult.

parentHwnd=winUser.getAncestor(hwnd,winUser.GA_ROOT)
if winUser.getClassName(parentHwnd)=="Net UI Tool Window":
versionMajor=int(self.productVersion.split('.')[0])
if versionMajor<16:
return True
if windowClass in ("WeekViewWnd","DayViewWnd"):
return True
return False

Expand All @@ -156,9 +164,15 @@ def event_NVDAObject_init(self,obj):
obj.role=controlTypes.ROLE_LISTITEM

def chooseNVDAObjectOverlayClasses(self, obj, clsList):
# Currently all our custom classes are IAccessible
if isinstance(obj,UIA) and obj.UIAElement.cachedClassName in ("LeafRow","ThreadItem","ThreadHeader"):
clsList.insert(0,UIAGridRow)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might one to return in this block, otherwise the logic under it is needlessly executed (i.e. fetching the role)

role=obj.role
windowClassName=obj.windowClassName
# AutoComplete listItems.
# This class is abstract enough to support both UIA and MSAA
if role==controlTypes.ROLE_LISTITEM and (windowClassName.startswith("REListBox") or windowClassName.startswith("NetUIHWND")):
clsList.insert(0,AutoCompleteListItem)
# all remaining classes are IAccessible
if not isinstance(obj,IAccessible):
return
# Outlook uses dialogs for many forms such as appointment / meeting creation. In these cases, there is no sane dialog caption that can be calculated as the dialog inly contains controls.
Expand All @@ -169,8 +183,6 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList):
clsList.remove(Dialog)
if WordDocument in clsList:
clsList.insert(0,OutlookWordDocument)
role=obj.role
windowClassName=obj.windowClassName
states=obj.states
controlID=obj.windowControlID
# Support the date picker in Outlook Meeting / Appointment creation forms
Expand All @@ -180,8 +192,6 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList):
clsList.insert(0,DatePickerCell)
elif windowClassName=="REListBox20W" and role==controlTypes.ROLE_CHECKBOX:
clsList.insert(0,REListBox20W_CheckBox)
elif role==controlTypes.ROLE_LISTITEM and (windowClassName.startswith("REListBox") or windowClassName.startswith("NetUIHWND")):
clsList.insert(0,AutoCompleteListItem)
if role==controlTypes.ROLE_LISTITEM and windowClassName=="OUTEXVLB":
clsList.insert(0, AddressBookEntry)
return
Expand Down Expand Up @@ -335,14 +345,18 @@ def initOverlayClass(self):
for gesture in self.__moveByEntryGestures:
self.bindGesture(gesture, "moveByEntry")

class AutoCompleteListItem(IAccessible):
class AutoCompleteListItem(Window):

def event_stateChange(self):
states=self.states
focus=api.getFocusObject()
if (focus.role==controlTypes.ROLE_EDITABLETEXT or focus.role==controlTypes.ROLE_BUTTON) and controlTypes.STATE_SELECTED in states and controlTypes.STATE_INVISIBLE not in states and controlTypes.STATE_UNAVAILABLE not in states and controlTypes.STATE_OFFSCREEN not in states:
speech.cancelSpeech()
ui.message(self.name)
text=self.name
# Some newer versions of Outlook don't put the contact as the name of the listItem, rather it is on the parent
if not text:
text=self.parent.name
ui.message(text)

class CalendarView(IAccessible):
"""Support for announcing time slots and appointments in Outlook Calendar.
Expand Down
3 changes: 2 additions & 1 deletion source/eventHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ def shouldAcceptEvent(eventName, windowHandle=None):
return True

fg = winUser.getForegroundWindow()
if wClass == "NetUIHWND" and winUser.getClassName(fg) == "Net UI Tool Window Layered":
fgClassName=winUser.getClassName(fg)
if wClass == "NetUIHWND" and fgClassName in ("Net UI Tool Window Layered","Net UI Tool Window"):
# #5504: In Office >= 2013 with the ribbon showing only tabs,
# when a tab is expanded, the window we get from the focus object is incorrect.
# This window isn't beneath the foreground window,
Expand Down