Skip to content

Commit

Permalink
Support for the MSAA implementations of standard IME/TSF candidate li…
Browse files Browse the repository at this point in the history
…sts. This allows reporting and navigating of candidate lists for most input methods included in Win7 and win8, and some included in XP with TSF emulation turned on. This code replaces the old IME code we had that was extremely broken. This code borrows some ideas from NVDAJP but is still heavily rewritten.

Particular input methods tested:
Chinese Traditional Taiwan, Microsoft New Phonetic: works in both XP and Win7
Chinese Traditional Taiwan, New ChangJei: Works in win7, not XP
Chinese Simplified PRC, Microsoft Pinyin: Works in Win7, not XP
Hopefully some Japanese input methods should also work (since NVDAJP makes use of this kind of access).
  • Loading branch information
michaelDCurran committed Jun 26, 2012
1 parent b423707 commit 62c10a3
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 36 deletions.
21 changes: 9 additions & 12 deletions source/IAccessibleHandler.py
Expand Up @@ -98,11 +98,6 @@ def addEvent(self,eventID,window,objectID,childID,threadID):
if k in self._genericEventCache:
del self._genericEventCache[k]
return
elif eventID==winUser.EVENT_OBJECT_DESTROY:
k=(winUser.EVENT_OBJECT_CREATE,window,objectID,childID,threadID)
if k in self._genericEventCache:
del self._genericEventCache[k]
return
elif eventID in MENU_EVENTIDS:
self._lastMenuEvent=(next(self._eventCounter),eventID,window,objectID,childID,threadID)
return
Expand Down Expand Up @@ -454,6 +449,7 @@ def accNavigate(ia,child,direction):
winUser.EVENT_SYSTEM_SWITCHEND:"switchEnd",
winUser.EVENT_OBJECT_FOCUS:"gainFocus",
winUser.EVENT_OBJECT_SHOW:"show",
winUser.EVENT_OBJECT_HIDE:"hide",
winUser.EVENT_OBJECT_DESTROY:"destroy",
winUser.EVENT_OBJECT_DESCRIPTIONCHANGE:"descriptionChange",
winUser.EVENT_OBJECT_LOCATIONCHANGE:"locationChange",
Expand Down Expand Up @@ -485,9 +481,6 @@ def winEventToNVDAEvent(eventID,window,objectID,childID,useCache=True):
@returns: the NVDA event name and the NVDAObject the event is for
@rtype: tuple of string and L{NVDAObjects.IAccessible.IAccessible}
"""
#We can't handle MSAA create events. (Destroys are handled elsewhere.)
if eventID == winUser.EVENT_OBJECT_CREATE:
return None
NVDAEventName=winEventIDsToNVDAEventNames.get(eventID,None)
if not NVDAEventName:
return None
Expand Down Expand Up @@ -525,6 +518,9 @@ def winEventCallback(handle,eventID,window,objectID,childID,threadID,timestamp):
#Ignore all locationChange events except ones for the caret
if eventID==winUser.EVENT_OBJECT_LOCATIONCHANGE and objectID!=winUser.OBJID_CARET:
return
if eventID==winUser.EVENT_OBJECT_DESTROY:
processDestroyWinEvent(window,objectID,childID)
return
#Change window objIDs to client objIDs for better reporting of objects
if (objectID==0) and (childID==0):
objectID=winUser.OBJID_CLIENT
Expand Down Expand Up @@ -688,7 +684,7 @@ def processDesktopSwitchWinEvent(window,objectID,childID):
eventHandler.executeEvent("gainFocus",obj)

def _correctFocus():
eventHandler.executeEvent("gainFocus",api.getDesktopObject().objectWithFocus())
eventHandler.queueEvent("gainFocus",api.getDesktopObject().objectWithFocus())

def processForegroundWinEvent(window,objectID,childID):
"""checks to see if the foreground win event is not the same as the existing focus or any of its parents,
Expand Down Expand Up @@ -730,7 +726,7 @@ def processForegroundWinEvent(window,objectID,childID):
def processShowWinEvent(window,objectID,childID):
className=winUser.getClassName(window)
#For now we only support 'show' event for tooltips as otherwize we get flooded
if className=="tooltips_class32" and objectID==winUser.OBJID_CLIENT:
if className in ("tooltips_class32","mscandui21.candidate","mscandui40.candidate","MSCandUIWindow_Candidate") and objectID==winUser.OBJID_CLIENT:
NVDAEvent=winEventToNVDAEvent(winUser.EVENT_OBJECT_SHOW,window,objectID,childID)
if NVDAEvent:
eventHandler.queueEvent(*NVDAEvent)
Expand All @@ -743,6 +739,9 @@ def processDestroyWinEvent(window,objectID,childID):
del liveNVDAObjectTable[(window,objectID,childID)]
except KeyError:
pass
focus=api.getFocusObject()
if objectID==0 and childID==0 and isinstance(focus,NVDAObjects.window.Window) and window==focus.windowHandle:
_correctFocus()

def processMenuStartWinEvent(eventID, window, objectID, childID, validFocus):
"""Process a menuStart win event.
Expand Down Expand Up @@ -818,8 +817,6 @@ def pumpAll():
focusWinEvents=[]
if winEvent[0]==winUser.EVENT_SYSTEM_DESKTOPSWITCH:
processDesktopSwitchWinEvent(*winEvent[1:])
elif winEvent[0]==winUser.EVENT_OBJECT_DESTROY:
processDestroyWinEvent(*winEvent[1:])
elif winEvent[0]==winUser.EVENT_OBJECT_SHOW:
processShowWinEvent(*winEvent[1:])
elif winEvent[0] in MENU_EVENTIDS+(winUser.EVENT_SYSTEM_SWITCHEND,):
Expand Down
22 changes: 0 additions & 22 deletions source/NVDAObjects/IAccessible/IME.py

This file was deleted.

6 changes: 4 additions & 2 deletions source/NVDAObjects/IAccessible/__init__.py
Expand Up @@ -404,7 +404,10 @@ def findOverlayClasses(self,clsList):
clsList.append(newCls)

# Some special cases.
if windowClassName=="GeckoPluginWindow" and self.event_objectID==0 and self.IAccessibleChildID==0:
if windowClassName.lower().startswith('mscandui'):
import mscandui
mscandui.findExtraOverlayClasses(self,clsList)
elif windowClassName=="GeckoPluginWindow" and self.event_objectID==0 and self.IAccessibleChildID==0:
from mozilla import GeckoPluginWindowRoot
clsList.append(GeckoPluginWindowRoot)
if (windowClassName in ("MozillaWindowClass", "GeckoPluginWindow") and not isinstance(self.IAccessibleObject, IAccessibleHandler.IAccessible2)) or windowClassName in ("MacromediaFlashPlayerActiveX", "ApolloRuntimeContentWindow", "ShockwaveFlash", "ShockwaveFlashLibrary", "ShockwaveFlashFullScreen"):
Expand Down Expand Up @@ -1603,7 +1606,6 @@ class ListviewPane(IAccessible):
("TPTShellList",oleacc.ROLE_SYSTEM_LISTITEM):"sysListView32.ListItem",
("TProgressBar",oleacc.ROLE_SYSTEM_PROGRESSBAR):"ProgressBar",
("AcrobatSDIWindow",oleacc.ROLE_SYSTEM_CLIENT):"adobeAcrobat.AcrobatSDIWindowClient",
("mscandui21.candidate",oleacc.ROLE_SYSTEM_PUSHBUTTON):"IME.IMECandidate",
("SysMonthCal32",oleacc.ROLE_SYSTEM_CLIENT):"SysMonthCal32.SysMonthCal32",
("hh_kwd_vlist",oleacc.ROLE_SYSTEM_LIST):"hh.KeywordList",
("Scintilla",oleacc.ROLE_SYSTEM_CLIENT):"scintilla.Scintilla",
Expand Down
163 changes: 163 additions & 0 deletions source/NVDAObjects/IAccessible/mscandui.py
@@ -0,0 +1,163 @@
import time
import oleacc
import eventHandler
import controlTypes
import characterProcessing
import api
import speech
import winUser
from . import IAccessible

def reportSelectedCandidate(candidateObject,allowDuplicate=False):
if not eventHandler.isPendingEvents("gainFocus") and (allowDuplicate or candidateObject!=api.getFocusObject()):
candidateObject.container.container=api.getDesktopObject().objectWithFocus()
eventHandler.queueEvent("gainFocus",candidateObject)

class BaseCandidateItem(IAccessible):

role=controlTypes.ROLE_GRAPHIC

def _get_keyboardShortcut(self):
return ""

def _get_value(self):
return super(BaseCandidateItem,self).keyboardShortcut

def _get_description(self):
symbol=self.name
try:
descriptions=characterProcessing.getCharacterDescription(speech.getCurrentLanguage(),symbol)
except TypeError:
descriptions=None
if descriptions:
return ", ".join(descriptions)

class MSCandUI_candidateListItem(BaseCandidateItem):

def _get_states(self):
states=super(MSCandUI_candidateListItem,self).states
if controlTypes.STATE_SELECTED in states:
states.discard(controlTypes.STATE_SELECTED)
states.add(controlTypes.STATE_FOCUSED)
return states

def event_stateChange(self):
if controlTypes.STATE_FOCUSED in self.states:
reportSelectedCandidate(self)

class MSCandUI21_candidateMenuItem(BaseCandidateItem):

def doAction(self,index=None):
if not index:
l=self.location
if l:
x=l[0]
y=l[1]
oldX,oldY=winUser.getCursorPos()
winUser.setCursorPos(x,y)
winUser.mouse_event(winUser.MOUSEEVENTF_LEFTDOWN,0,0,None,None)
time.sleep(0.2)
winUser.mouse_event(winUser.MOUSEEVENTF_LEFTUP,0,0,None,None)
winUser.setCursorPos(oldX,oldY)
return
raise NotImplementedError

def script_nextItem(self,gesture):
item=self.next
if not item or controlTypes.STATE_INVISIBLE in item.states: return
item=MSCandUI21_candidateMenuItem(IAccessibleObject=item.IAccessibleObject,IAccessibleChildID=item.IAccessibleChildID)
reportSelectedCandidate(item)

def script_previousItem(self,gesture):
item=self.previous
if not item or controlTypes.STATE_INVISIBLE in item.states: return
item=MSCandUI21_candidateMenuItem(IAccessibleObject=item.IAccessibleObject,IAccessibleChildID=item.IAccessibleChildID)
reportSelectedCandidate(item)

def script_changePage(self,gesture):
gesture.send()
api.processPendingEvents()
item=self.parent.firstChild.next.next
item=MSCandUI21_candidateMenuItem(IAccessibleObject=item.IAccessibleObject,IAccessibleChildID=item.IAccessibleChildID)
reportSelectedCandidate(item,allowDuplicate=True)

def script_activate(self,gesture):
self.doAction()

__gestures={
"kb:downArrow":"nextItem",
"kb:upArrow":"previousItem",
"kb:pageDown":"changePage",
"kb:pageUp":"changePage",
"kb:enter":"activate",
}

class MSCandUI21(IAccessible):

def _get_isPresentableFocusAncestor(self):
return False

def event_show(self):
candidateList=self.simpleFirstChild
if not candidateList: return
role=candidateList.role
if role==controlTypes.ROLE_LIST:
api.setNavigatorObject(candidateList)
item=candidateList.firstChild
while item and controlTypes.STATE_FOCUSED not in item.states:
item=item.next
if item:
reportSelectedCandidate(item)
return
elif role==controlTypes.ROLE_MENUBUTTON:
item=candidateList.firstChild.next.next
item=MSCandUI21_candidateMenuItem(IAccessibleObject=item.IAccessibleObject,IAccessibleChildID=item.IAccessibleChildID)
reportSelectedCandidate(item)

###IME 2002

class MSCandUIWindow_candidateListItem(MSCandUI_candidateListItem):

def _get_value(self):
index=self.IAccessibleChildID-2
if index>0:
return unicode(index)

def _get_next(self):
childID=self.IAccessibleChildID+1
item=self.__class__(IAccessibleObject=self.IAccessibleObject,IAccessibleChildID=childID)
if item.IAccessibleRole==oleacc.ROLE_SYSTEM_LISTITEM:
return item

def _get_previous(self):
childID=self.IAccessibleChildID-1
if childID>=3:
return self.__class__(IAccessibleObject=self.IAccessibleObject,IAccessibleChildID=childID)

class MSCandUIWindow(IAccessible):

name=_("Candidates")
role=controlTypes.ROLE_LIST

def _get_states(self):
states=super(MSCandUIWindow,self).states
states.discard(controlTypes.STATE_UNAVAILABLE)
return states

def event_show(self):
item=MSCandUIWindow_candidateListItem(IAccessibleObject=self.IAccessibleObject,IAccessibleChildID=3)
reportSelectedCandidate(item)

def findExtraOverlayClasses(obj,clsList):
windowClassName=obj.windowClassName
role=obj.IAccessibleRole
if windowClassName=="MSCandUIWindow_Candidate":
if role==oleacc.ROLE_SYSTEM_CLIENT:
clsList.append(MSCandUIWindow)
elif role==oleacc.ROLE_SYSTEM_LISTITEM:
clsList.append(MSCandUIWindow_candidateListItem)
elif windowClassName in ("mscandui21.candidate","mscandui40.candidate"):
if role==oleacc.ROLE_SYSTEM_LISTITEM:
clsList.append(MSCandUI_candidateListItem)
elif role==oleacc.ROLE_SYSTEM_CLIENT:
clsList.append(MSCandUI21)

0 comments on commit 62c10a3

Please sign in to comment.