def slideAndTaskPanes(self, obj=orca_state.locusOfFocus): """Attempts to locate the Impress slide pane and task pane.""" drawingView = self.drawingView(obj) if not drawingView: return None, None parent = drawingView.parent if parent: parent = parent.parent if not parent: return None, None hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_SPLIT_PANE panes = pyatspi.findAllDescendants(parent, hasRole) if not panes: return None, None slidePane = taskPane = None if pyatspi.findAllDescendants(panes[0], self.isDocument): slidePane = panes[0] if len(panes) == 2: taskPane = panes[1] else: taskPane = panes[0] if len(panes) == 2: slidePane = panes[1] return slidePane, taskPane
def slideAndTaskPanes(self, obj=orca_state.locusOfFocus): """Attempts to locate the Impress slide pane and task pane.""" drawingView = self.drawingView(obj) if not drawingView: return None, None parent = drawingView.parent if parent: parent = parent.parent if not parent: return None, None hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_SPLIT_PANE panes = pyatspi.findAllDescendants(parent, hasRole) if not panes: return None, None slidePane = taskPane = None hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_DOCUMENT_FRAME if pyatspi.findAllDescendants(panes[0], hasRole): slidePane = panes[0] if len(panes) == 2: taskPane = panes[1] else: taskPane = panes[0] if len(panes) == 2: slidePane = panes[1] return slidePane, taskPane
def locateInputLine(self, obj): """Return the spread sheet input line. This only needs to be found the very first time a spread sheet table cell gets focus. We use the table cell to work back up the component hierarchy until we have found the common panel that both it and the input line reside in. We then use that as the base component to search for a component which has a paragraph role. This will be the input line. Arguments: - obj: the spread sheet table cell that has just got focus. Returns the spread sheet input line component. """ if self._script.inputLineForCell: return self._script.inputLineForCell isScrollPane = lambda x: x and x.getRole() == pyatspi.ROLE_SCROLL_PANE scrollPane = pyatspi.findAncestor(obj, isScrollPane) if not scrollPane: return None toolbar = None for child in scrollPane.parent: if child and child.getRole() == pyatspi.ROLE_TOOL_BAR: toolbar = child break isParagraph = lambda x: x and x.getRole() == pyatspi.ROLE_PARAGRAPH allParagraphs = pyatspi.findAllDescendants(toolbar, isParagraph) if len(allParagraphs) == 1: self._script.inputLineForCell = allParagraphs[0] return self._script.inputLineForCell
def onWindowCreated(self, event): """Callback for window:create accessibility events.""" hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_LABEL allLabels = pyatspi.findAllDescendants(event.source, hasRole) texts = [self.utilities.displayedText(acc) for acc in allLabels] text = '%s %s' % (messages.NOTIFICATION, ' '.join(texts)) speech.speak(text, None, True)
def onWindowActivated(self, event): """Called whenever a toplevel window is activated.""" # Hack to "tickle" the accessible hierarchy. Otherwise, the # events we need to present text added to the chatroom are # missing. hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_PAGE_TAB allPageTabs = pyatspi.findAllDescendants(event.source, hasRole) default.Script.onWindowActivated(self, event)
def onWindowCreated(self, event): """Callback for window:create accessibility events.""" hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_LABEL allLabels = pyatspi.findAllDescendants(event.source, hasRole) texts = [self.utilities.displayedText(acc) for acc in allLabels] text = "%s %s" % (messages.NOTIFICATION, " ".join(texts)) speech.speak(text, None, True) self.displayBrailleMessage(text, flashTime=settings.brailleFlashTime) notification_messages.saveMessage(text)
def getMessageFromEvent(self, event): """Get the actual displayed message. This will almost always be the unaltered any_data from an event of type object:text-changed:insert. Arguments: - event: the Event from which to take the text. Returns the string which should be presented as the newly-inserted text. (Things like chatroom name prefacing get handled elsewhere.) """ string = "" # IMs are written in areas that look like bubbles. When a new bubble # is inserted, we see an embedded object character inserted into the # document frame. The first paragraph is the bubble title; the # rest (usually just one) are the message itself. # if event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME: bubble = event.source[event.detail1] hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_PARAGRAPH paragraphs = pyatspi.findAllDescendants(bubble, hasRole) # If the user opted the non-default, "simple" appearance, then this # might not be a bubble at all, but a paragraph. # if not paragraphs and bubble.getRole() == pyatspi.ROLE_PARAGRAPH: paragraphs.append(bubble) for paragraph in paragraphs: msg = self._script.utilities.substring(paragraph, 0, -1) if msg == self._script.EMBEDDED_OBJECT_CHARACTER: # This seems to occur for non-focused conversations. # msg = self._script.utilities.substring(paragraph[0], 0, -1) string = self._script.utilities.appendString(string, msg) return string # If we instead have a section, we are writing another message into # the existing bubble. In this case, we get three separate items # inserted: a separator, a paragraph with the desired text, and an # empty section. # if event.source.getRole() == pyatspi.ROLE_SECTION: obj = event.source[event.detail1] if obj and obj.getRole() == pyatspi.ROLE_PARAGRAPH: try: text = obj.queryText() except: pass else: string = text.getText(0, -1) return string
def findFunc(self): if self.findtype == 'find': try: searchings = [pyatspi.findDescendant(self.appname, lambda x: x.getRoleName() == self.ctrlname)] except TypeError: searchings = [] elif self.findtype == 'findAll': try: searchings = pyatspi.findAllDescendants(self.appname, lambda x: x.getRoleName() == self.ctrlname) except TypeError: searchings = [] return searchings
def _generateListBoxItemWidgets(self, obj, **args): widgetRoles = [ pyatspi.ROLE_CHECK_BOX, pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_PUSH_BUTTON, pyatspi.ROLE_RADIO_BUTTON, pyatspi.ROLE_SLIDER, pyatspi.ROLE_TOGGLE_BUTTON ] isWidget = lambda x: x and x.getRole() in widgetRoles result = [] if obj.parent and obj.parent.getRole() == pyatspi.ROLE_LIST_BOX: widgets = pyatspi.findAllDescendants(obj, isWidget) for widget in widgets: result.extend(self.generate(widget, includeContext=False)) result.append(braille.Region(" ")) return result
def _generateListBoxItemWidgets(self, obj, **args): widgetRoles = [pyatspi.ROLE_CHECK_BOX, pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_PUSH_BUTTON, pyatspi.ROLE_RADIO_BUTTON, pyatspi.ROLE_SLIDER, pyatspi.ROLE_TOGGLE_BUTTON] isWidget = lambda x: x and x.getRole() in widgetRoles result = [] if obj.parent and obj.parent.getRole() == pyatspi.ROLE_LIST_BOX: widgets = pyatspi.findAllDescendants(obj, isWidget) for widget in widgets: result.extend(self.generate(widget, includeContext=False)) result.append(braille.Region(" ")) return result
def selectedChildren(self, obj): try: selection = obj.querySelection() except: # This is a workaround for bgo#738705. if obj.getRole() != pyatspi.ROLE_PANEL: return [] isSelected = lambda x: x and x.getState().contains(pyatspi.STATE_SELECTED) children = pyatspi.findAllDescendants(obj, isSelected) else: children = [] for x in range(selection.nSelectedChildren): children.append(selection.getSelectedChild(x)) return children
def locateInputLine(self, obj): """Return the spread sheet input line. This only needs to be found the very first time a spread sheet table cell gets focus. We use the table cell to work back up the component hierarchy until we have found the common panel that both it and the input line reside in. We then use that as the base component to search for a component which has a paragraph role. This will be the input line. Arguments: - obj: the spread sheet table cell that has just got focus. Returns the spread sheet input line component. """ if self._script.inputLineForCell: try: topLevel = self.topLevelObject(self._script.inputLineForCell) except: msg = "ERROR: Exception getting topLevelObject for inputline" debug.println(debug.LEVEL_INFO, msg, True) self._script.inputLineForCell = None else: if self.isSameObject(orca_state.activeWindow, topLevel): return self._script.inputLineForCell isScrollPane = lambda x: x and x.getRole() == pyatspi.ROLE_SCROLL_PANE scrollPane = pyatspi.findAncestor(obj, isScrollPane) if not scrollPane: return None toolbar = None for child in scrollPane.parent: if child and child.getRole() == pyatspi.ROLE_TOOL_BAR: toolbar = child break if not toolbar: msg = "ERROR: Calc inputline toolbar not found." debug.println(debug.LEVEL_INFO, msg, True) return None isParagraph = lambda x: x and x.getRole() == pyatspi.ROLE_PARAGRAPH allParagraphs = pyatspi.findAllDescendants(toolbar, isParagraph) if len(allParagraphs) == 1: self._script.inputLineForCell = allParagraphs[0] return self._script.inputLineForCell
def onValueChanged(self, event): """Called whenever an object's value changes. Overridden here because new chat windows are not issuing text-inserted events for the chat history until we "tickle" the hierarchy. However, we do seem to get object:property-change:accessible-value events on the split pane. So we'll use that as our trigger to do the tickling. Arguments: - event: the Event """ if event.source.getRole() == pyatspi.ROLE_SPLIT_PANE: hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_TEXT textObjects = pyatspi.findAllDescendants(event.source, hasRole) return default.Script.onValueChanged(self, event)
def selectedChildren(self, obj): try: selection = obj.querySelection() except: # This is a workaround for bgo#738705. if obj.getRole() != pyatspi.ROLE_PANEL: return [] isSelected = lambda x: x and x.getState().contains(pyatspi. STATE_SELECTED) children = pyatspi.findAllDescendants(obj, isSelected) else: children = [] for x in range(selection.nSelectedChildren): children.append(selection.getSelectedChild(x)) return children
def _generatePositionInList(self, obj, **args): """Returns an array of strings (and possibly voice and audio specifications) that represent the relative position of an object in a list. """ if _settingsManager.getSetting('onlySpeakDisplayedText'): return [] listObj = None if obj and obj.getRole() == pyatspi.ROLE_COMBO_BOX: hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_LIST allLists = pyatspi.findAllDescendants(obj, hasRole) if len(allLists) == 1: listObj = allLists[0] if not listObj: return speech_generator.SpeechGenerator._generatePositionInList( self, obj, **args) result = [] acss = self.voice(speech_generator.SYSTEM) name = self._generateName(obj) position = -1 index = total = 0 for child in listObj: nextName = self._generateName(child) if not nextName or nextName[0] in ["", "Empty", "separator"] \ or not child.getState().contains(pyatspi.STATE_VISIBLE): continue index += 1 total += 1 if nextName == name: position = index if (_settingsManager.getSetting('enablePositionSpeaking') \ or args.get('forceList', False)) \ and position >= 0: result.append(self._script.formatting.getString( mode='speech', stringType='groupindex') \ % {"index" : position, "total" : total}) result.extend(acss) return result
def setLivePolitenessOff(self, script, inputEvent): """User toggle to set all live regions to LIVE_OFF or back to their original politeness.""" if not _settingsManager.getSetting('inferLiveRegions'): self._script.presentMessage(messages.LIVE_REGIONS_OFF) return # start at the document frame docframe = self._script.utilities.documentFrame() # get the URI of the page. It is used as a partial key. uri = self._script.bookmarks.getURIKey() # The user is currently monitoring live regions but now wants to # change all live region politeness on page to LIVE_OFF if self.monitoring: self._script.presentMessage(messages.LIVE_REGIONS_ALL_OFF) self.msg_queue.clear() # First we'll save off a copy for quick restoration self._restoreOverrides = copy.copy(self._politenessOverrides) # Set all politeness overrides to LIVE_OFF. for override in self._politenessOverrides.keys(): self._politenessOverrides[override] = LIVE_OFF # look through all the objects on the page and set/add to # politeness overrides. This only adds live regions with good # markup. matches = pyatspi.findAllDescendants(docframe, self.matchLiveRegion) for match in matches: objectid = self._getObjectId(match) self._politenessOverrides[(uri, objectid)] = LIVE_OFF # Toggle our flag self.monitoring = False # The user wants to restore politeness levels else: for key, value in self._restoreOverrides.items(): self._politenessOverrides[key] = value self._script.presentMessage(messages.LIVE_REGIONS_ALL_RESTORED) # Toggle our flag self.monitoring = True
def setLivePolitenessOff(self): """User toggle to set all live regions to LIVE_OFF or back to their original politeness.""" # start at the document frame docframe = self._script.utilities.documentFrame() # get the URI of the page. It is used as a partial key. uri = self._script.bookmarks.getURIKey() # The user is currently monitoring live regions but now wants to # change all live region politeness on page to LIVE_OFF if self.monitoring: # Translators: This lets the user know that all live regions # have been turned off. self._script.presentMessage(_("All live regions set to off")) self.msg_queue.clear() # First we'll save off a copy for quick restoration self._restoreOverrides = copy.copy(self._politenessOverrides) # Set all politeness overrides to LIVE_OFF. for override in self._politenessOverrides.keys(): self._politenessOverrides[override] = LIVE_OFF # look through all the objects on the page and set/add to # politeness overrides. This only adds live regions with good # markup. matches = pyatspi.findAllDescendants(docframe, self.matchLiveRegion) for match in matches: objectid = self._getObjectId(match) self._politenessOverrides[(uri, objectid)] = LIVE_OFF # Toggle our flag self.monitoring = False # The user wants to restore politeness levels else: for key, value in self._restoreOverrides.iteritems(): self._politenessOverrides[key] = value # Translators: This lets the user know that all live regions # have been restored to their original politeness level. self._script.presentMessage( _("live regions politeness levels restored")) # Toggle our flag self.monitoring = True
def searchObj(self, appname, ctrlname, elementname): reg = pyatspi.Registry desktop = reg.getDesktop(0) app = pyatspi.findDescendant(desktop, lambda x: x.name == self.appname) self.appname = app if self.ctrlname != None and self.ctrlname.startswith("application"): searchings = [] for i in desktop: if i is not None: searchings.append(i) elif self.ctrlname == None: searchings = pyatspi.findAllDescendants(self.appname, lambda x: x.name == self.elementname) elif self.ctrlname == None and self.findtype == "find": searchings = pyatspi.findDescendant(self.appname, lambda x: x.name == self.elementname) else: searchings = self.findFunc() return searchings
def onWindowActivated(self, event): """Called whenever a toplevel window is activated.""" if not settings.enableSadPidginHack: msg = "PIDGIN: Hack for missing events disabled" debug.println(debug.LEVEL_INFO, msg, True) GAIL.Script.onWindowActivated(self, event) return msg = "PIDGIN: Starting hack for missing events" debug.println(debug.LEVEL_INFO, msg, True) # Hack to "tickle" the accessible hierarchy. Otherwise, the # events we need to present text added to the chatroom are # missing. hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_PAGE_TAB allPageTabs = pyatspi.findAllDescendants(event.source, hasRole) msg = "PIDGIN: Hack to work around missing events complete" debug.println(debug.LEVEL_INFO, msg, True) GAIL.Script.onWindowActivated(self, event)
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus): """Called when the visual object with focus changes. Arguments: - event: if not None, the Event that caused the change - oldLocusOfFocus: Accessible that is the old locus of focus - newLocusOfFocus: Accessible that is the new locus of focus """ brailleGen = self.brailleGenerator speechGen = self.speechGenerator details = debug.getAccessibleDetails(self.debugLevel, event.source) debug.printObjectEvent(self.debugLevel, event, details) # [[[TODO - JD: what follows here should be replaced with methods # in this script's speech and braille generators. That will require # making each generator, moving this script into a new directory, # etc.]]] # # Here we handle the case when focus is in the "Work online/offline" # button near the status bar that has an image without a description. # We speak and braille "Online/Offline button" here, until the # developer of the application adds a description to the images # associated with the button, which shows the online or offline # work mode. # rolesList = [ pyatspi.ROLE_PUSH_BUTTON, pyatspi.ROLE_FILLER, pyatspi.ROLE_FILLER, pyatspi.ROLE_FRAME ] # We are checking if the button with the focus is the button to # turn on/off the work mode in liferea. This push button is # hierarchically located in the main window of the application # (frame), inside a filler and inside another filler. # if self.utilities.hasMatchingHierarchy(event.source, rolesList): # If we are focusing this button we construct a utterance and # a braille region to speak/braille "online/offline button". # Here we declare utterances and add the localized string # "online/offline". # utterances = [] utterances.append(_("Work online / offline")) # Here we extend the utterances with the speech generator for # the object with focus (the push button). # utterances.extend(speechGen.generateSpeech(event.source)) # Finally we speak/braille the utterances/regions. # speech.speak(utterances) regions = brailleGen.generateBraille(event.source) regions[0].insert(0, self.getNewBrailleRegion(utterances[0] + " ")) self.displayBrailleRegions(regions) return # Here we handle the case when the focus is in the headlines table. # See comment #3 of bug #350233. # http://bugzilla.gnome.org/show_bug.cgi?id=350233 # if orca_state.locusOfFocus.getRole() == \ pyatspi.ROLE_TABLE_COLUMN_HEADER: table = event.source.parent hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_TABLE_CELL cells = pyatspi.findAllDescendants(event.source, hasRole) eventsynthesizer.clickObject(cells[1], 1) default.Script.locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus)
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus): """Called when the visual object with focus changes. Arguments: - event: if not None, the Event that caused the change - oldLocusOfFocus: Accessible that is the old locus of focus - newLocusOfFocus: Accessible that is the new locus of focus """ brailleGen = self.brailleGenerator speechGen = self.speechGenerator details = debug.getAccessibleDetails(self.debugLevel, event.source) debug.printObjectEvent(self.debugLevel, event, details) # [[[TODO - JD: what follows here should be replaced with methods # in this script's speech and braille generators. That will require # making each generator, moving this script into a new directory, # etc.]]] # # Here we handle the case when focus is in the "Work online/offline" # button near the status bar that has an image without a description. # We speak and braille "Online/Offline button" here, until the # developer of the application adds a description to the images # associated with the button, which shows the online or offline # work mode. # rolesList = [pyatspi.ROLE_PUSH_BUTTON, pyatspi.ROLE_FILLER, pyatspi.ROLE_FILLER, pyatspi.ROLE_FRAME] # We are checking if the button with the focus is the button to # turn on/off the work mode in liferea. This push button is # hierarchically located in the main window of the application # (frame), inside a filler and inside another filler. # if self.utilities.hasMatchingHierarchy(event.source, rolesList): # If we are focusing this button we construct a utterance and # a braille region to speak/braille "online/offline button". # Here we declare utterances and add the localized string # "online/offline". # utterances = [] utterances.append(_("Work online / offline")) # Here we extend the utterances with the speech generator for # the object with focus (the push button). # utterances.extend(speechGen.generateSpeech(event.source)) # Finally we speak/braille the utterances/regions. # speech.speak(utterances) regions = brailleGen.generateBraille(event.source) regions[0].insert(0, self.getNewBrailleRegion(utterances[0] + " ")) self.displayBrailleRegions(regions) return # Here we handle the case when the focus is in the headlines table. # See comment #3 of bug #350233. # http://bugzilla.gnome.org/show_bug.cgi?id=350233 # if orca_state.locusOfFocus.getRole() == \ pyatspi.ROLE_TABLE_COLUMN_HEADER: table = event.source.parent hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_TABLE_CELL cells = pyatspi.findAllDescendants(event.source, hasRole) eventsynthesizer.clickObject(cells[1], 1) default.Script.locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus)
def readMisspeltWord(self, event, pane): """Speak/braille the current misspelt word plus its context. The spell check dialog contains a "paragraph" which shows the context for the current spelling mistake. After speaking/brailling the default action for this component, that a selection of the surronding text from that paragraph with the misspelt word is also spoken. Arguments: - event: the event. - pane: the option pane in the spell check dialog. Returns True if this is the spell check dialog (whether we actually wind up reading the word or not). """ def isMatch(obj): if not (obj and obj.getRole() == pyatspi.ROLE_PARAGRAPH): return False if not obj.getState().contains(pyatspi.STATE_EDITABLE): return False try: text = obj.queryText() except: return False return text.characterCount > 0 paragraph = pyatspi.findAllDescendants(pane, isMatch) # If there is not exactly one paragraph, this isn't the spellcheck # dialog. # if len(paragraph) != 1: return False # If there's not any text displayed in the paragraph, this isn't # the spellcheck dialog. # try: text = paragraph[0].queryText() except: return False else: textLength = text.characterCount if not textLength: return False # Determine which word is the misspelt word. This word will have # non-default text attributes associated with it. # startFound = False startOff = 0 endOff = textLength for i in range(0, textLength): attributes = text.getAttributes(i) if len(attributes[0]) != 0: if not startFound: startOff = i startFound = True else: if startFound: endOff = i break if not startFound: # If there are no text attributes in this paragraph, this isn't # the spellcheck dialog. # return False badWord = self.utilities.substring(paragraph[0], startOff, endOff - 1) # Note that we often get two or more of these focus or property-change # events each time there is a new misspelt word. We extract the # length of the line of text, the misspelt word, the start and end # offsets for that word and compare them against the values saved # from the last time this routine was called. If they are the same # then we ignore it. # debug.println(debug.LEVEL_INFO, "StarOffice.readMisspeltWord: type=%s word=%s(%d,%d) len=%d" % \ (event.type, badWord, startOff, endOff, textLength)) if (textLength == self.lastTextLength) and \ (badWord == self.lastBadWord) and \ (startOff == self.lastStartOff) and \ (endOff == self.lastEndOff): return True # Create a list of all the words found in the misspelt paragraph. # text = self.utilities.substring(paragraph[0], 0, -1) allTokens = text.split() self.speakMisspeltWord(allTokens, badWord) # Save misspelt word information for comparison purposes next # time around. # self.lastTextLength = textLength self.lastBadWord = badWord self.lastStartOff = startOff self.lastEndOff = endOff return True