#appModules/miranda32.py #A part of NonVisual Desktop Access (NVDA) #Copyright (C) 2006-2012 NVDA Contributors #This file is covered by the GNU General Public License. #See the file COPYING for more details. import ui import config from ctypes import * from ctypes.wintypes import * import winKernel from NVDAObjects.IAccessible import IAccessible, ContentGenericClient from NVDAObjects.behaviors import Dialog import appModuleHandler import speech import braille import controlTypes from scriptHandler import isScriptWaiting import api import mouseHandler import oleacc from keyboardHandler import KeyboardInputGesture import watchdog #contact list window messages CLM_FIRST=0x1000 #this is the same as LVM_FIRST CLM_LAST=0x1100 #messages, compare with equivalent TVM_s in the MSDN CLM_ENSUREVISIBLE=CLM_FIRST+6 #wParam=hItem, lParam=partialOk CLE_TOGGLE=-1 CLE_COLLAPSE=0 CLE_EXPAND=1 CLE_INVALID=0xFFFF CLM_EXPAND=CLM_FIRST+7 #wParam=hItem, lParam=CLE_ CLM_FINDCONTACT=CLM_FIRST+8 #wParam=hContact, returns an hItem CLM_FINDGROUP=CLM_FIRST+9 #wParam=hGroup, returns an hItem CLM_GETBKCOLOR=CLM_FIRST+10 #returns a COLORREF CLM_GETCHECKMARK=CLM_FIRST+11 #wParam=hItem, returns 1 or 0 CLM_GETCOUNT=CLM_FIRST+12 #returns the total number of items CLM_GETEXPAND=CLM_FIRST+14 #wParam=hItem, returns a CLE_, CLE_INVALID if not a group CLM_GETEXTRACOLUMNS=CLM_FIRST+15 #returns number of extra columns CLM_GETEXTRAIMAGE=CLM_FIRST+16 #wParam=hItem, lParam=MAKELPARAM(iColumn (0 based),0), returns iImage or 0xFF CLM_GETEXTRAIMAGELIST=CLM_FIRST+17 #returns HIMAGELIST CLM_GETFONT=CLM_FIRST+18 #wParam=fontId, see clm_setfont. returns hFont. CLM_GETINDENT=CLM_FIRST+19 #wParam=new group indent CLM_GETISEARCHSTRING=CLM_FIRST+20 #lParam=(char*)pszStr, max 120 bytes, returns number of chars in string MAXITEMTEXTLEN=120 CLM_GETITEMTEXT=CLM_FIRST+21 #wParam=hItem, lParam=(char*)pszStr, max 120 bytes CLM_GETSELECTION=CLM_FIRST+23 #returns hItem CLM_SELECTITEM=CLM_FIRST+26 #wParam=hItem CLM_GETHIDEOFFLINEROOT=CLM_FIRST+40 #returns TRUE/FALSE CLM_GETEXSTYLE=CLM_FIRST+44 #returns CLS_EX_ flags CLM_GETLEFTMARGIN=CLM_FIRST+46 #returns count of pixels CLCIT_INVALID=-1 CLCIT_GROUP=0 CLCIT_CONTACT=1 CLCIT_DIVIDER=2 CLCIT_INFO=3 CLM_GETITEMTYPE=CLM_FIRST+49 #wParam=hItem, returns a CLCIT_ CLGN_ROOT=0 CLGN_CHILD=1 CLGN_PARENT=2 CLGN_NEXT=3 CLGN_PREVIOUS=4 CLGN_NEXTCONTACT=5 CLGN_PREVIOUSCONTACT=6 CLGN_NEXTGROUP=7 CLGN_PREVIOUSGROUP=8 CLM_GETNEXTITEM=CLM_FIRST+50 #wParam=flag, lParam=hItem, returns an hItem CLM_GETTEXTCOLOR=CLM_FIRST+51 #wParam=FONTID_, returns COLORREF MAXSTATUSMSGLEN=256 CLM_GETSTATUSMSG=CLM_FIRST+105 #other constants ANSILOGS=(1001,1006) MESSAGEVIEWERS=(1001,1005,5005) class AppModule(appModuleHandler.AppModule): lastTextLengths={} lastMessages=[] # Must not be > 9. MessageHistoryLength=3 def chooseNVDAObjectOverlayClasses(self, obj, clsList): if obj.role == controlTypes.ROLE_WINDOW: return windowClass = obj.windowClassName if windowClass == "CListControl": try: clsList.remove(ContentGenericClient) except ValueError: pass clsList.insert(0, mirandaIMContactList) elif windowClass in ("MButtonClass", "TSButtonClass", "CLCButtonClass"): clsList.insert(0, mirandaIMButton) elif windowClass == "Hyperlink": clsList.insert(0, mirandaIMHyperlink) elif isinstance(obj, IAccessible) and obj.IAccessibleRole == oleacc.ROLE_SYSTEM_PROPERTYPAGE: clsList.insert(0, MPropertyPage) elif isinstance(obj, IAccessible) and obj.IAccessibleRole == oleacc.ROLE_SYSTEM_SCROLLBAR and obj.windowControlID in MESSAGEVIEWERS: clsList.insert(0, MirandaMessageViewerScrollbar) elif windowClass == "ListBox" and obj.windowControlID == 0: clsList.insert(0, DuplicateFocusListBox) def event_NVDAObject_init(self,obj): if obj.windowClassName=="ColourPicker": obj.role=controlTypes.ROLE_COLORCHOOSER elif (obj.windowControlID in ANSILOGS) and (obj.windowClassName=="RichEdit20A"): obj._isWindowUnicode=False def script_readMessage(self,gesture): num=int(gesture.mainKeyName[-1]) if len(self.lastMessages)>num-1: ui.message(self.lastMessages[num-1]) else: # Translators: This is presented to inform the user that no instant message has been received. ui.message(_("No message yet")) # Translators: The description of an NVDA command to view one of the recent messages. script_readMessage.__doc__=_("Displays one of the recent messages") def __init__(self, *args, **kwargs): super(AppModule, self).__init__(*args, **kwargs) for n in xrange(1, self.MessageHistoryLength + 1): self.bindGesture("kb:NVDA+control+%s" % n, "readMessage") class mirandaIMContactList(IAccessible): def _get_name(self): hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0) internalBuf=winKernel.virtualAllocEx(self.processHandle,None,MAXITEMTEXTLEN,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: watchdog.cancellableSendMessage(self.windowHandle,CLM_GETITEMTEXT,hItem,internalBuf) buf=create_unicode_buffer(MAXITEMTEXTLEN) winKernel.readProcessMemory(self.processHandle,internalBuf,buf,MAXITEMTEXTLEN,None) text=buf.value statusMsgPtr=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSTATUSMSG,hItem,0) if statusMsgPtr>0: buf2=create_unicode_buffer(MAXSTATUSMSGLEN) winKernel.readProcessMemory(self.processHandle,statusMsgPtr,buf2,MAXSTATUSMSGLEN,None) text="%s %s"%(text,buf2.value) finally: winKernel.virtualFreeEx(self.processHandle,internalBuf,0,winKernel.MEM_RELEASE) return text def _get_role(self): hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0) iType=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETITEMTYPE,hItem,0) if iType==CLCIT_DIVIDER or iType==CLCIT_INVALID: #some clists treat invalid as divider return controlTypes.ROLE_SEPARATOR else: return controlTypes.ROLE_TREEVIEWITEM def _get_states(self): newStates=super(mirandaIMContactList,self)._get_states() hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0) state=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETEXPAND,hItem,0) if state==CLE_EXPAND: newStates.add(controlTypes.STATE_EXPANDED) elif state==CLE_COLLAPSE: newStates.add(controlTypes.STATE_COLLAPSED) return newStates def script_changeItem(self,gesture): gesture.send() if not isScriptWaiting(): api.processPendingEvents() speech.speakObject(self,reason=controlTypes.REASON_FOCUS) braille.handler.handleGainFocus(self) __changeItemGestures = ( "kb:downArrow", "kb:upArrow", "kb:leftArrow", "kb:rightArrow", "kb:home", "kb:end", "kb:pageUp", "kb:pageDown", ) def initOverlayClass(self): for gesture in self.__changeItemGestures: self.bindGesture(gesture, "changeItem") class mirandaIMButton(IAccessible): def _get_name(self): api.moveMouseToNVDAObject(self) return super(mirandaIMButton,self)._get_name() def _get_role(self): return controlTypes.ROLE_BUTTON def getActionName(self): if controlTypes.STATE_FOCUSED not in self.states: return return "Click" def doAction(self): if controlTypes.STATE_FOCUSED not in self.states: return KeyboardInputGesture.fromName("space").send() def script_doDefaultAction(self,gesture): self.doAction() def initOverlayClass(self): self.bindGesture("kb:enter", "doDefaultAction") class mirandaIMHyperlink(mirandaIMButton): def _get_role(self): return controlTypes.ROLE_LINK class MPropertyPage(Dialog,IAccessible): def _get_name(self): name=super(MPropertyPage,self)._get_name() if not name: try: tc=self.parent.next.firstChild except AttributeError: tc=None if tc and tc.role==controlTypes.ROLE_TABCONTROL: children=tc.children for index in xrange(len(children)): if (children[index].role==controlTypes.ROLE_TAB) and (controlTypes.STATE_SELECTED in children[index].states): name=children[index].name break return name class MirandaMessageViewerScrollbar(IAccessible): def event_valueChange(self): curTextLength=len(self.windowText) if self.windowHandle not in self.appModule.lastTextLengths: self.appModule.lastTextLengths[self.windowHandle]=curTextLength elif self.appModule.lastTextLengths[self.windowHandle]