orca r4272 - in branches/phase2/src/orca: . scripts
- From: wwalker svn gnome org
- To: svn-commits-list gnome org
- Subject: orca r4272 - in branches/phase2/src/orca: . scripts
- Date: Wed, 1 Oct 2008 12:23:23 +0000 (UTC)
Author: wwalker
Date: Wed Oct 1 12:23:23 2008
New Revision: 4272
URL: http://svn.gnome.org/viewvc/orca?rev=4272&view=rev
Log:
More work on deciding what a script really is.
Modified:
branches/phase2/src/orca/script.py
branches/phase2/src/orca/scripts/default.py
Modified: branches/phase2/src/orca/script.py
==============================================================================
--- branches/phase2/src/orca/script.py (original)
+++ branches/phase2/src/orca/script.py Wed Oct 1 12:23:23 2008
@@ -97,7 +97,7 @@
#
self._settings = self._loadSettings(userSettings)
- # The object which has the STATE_FOCUS state.
+ # The object which has the STATE_FOCUSED state.
#
self.focus = None
@@ -111,6 +111,7 @@
# caretOffset
# startOffset
# endOffset
+ # string
# row
# column
# activeDescendantInfo
Modified: branches/phase2/src/orca/scripts/default.py
==============================================================================
--- branches/phase2/src/orca/scripts/default.py (original)
+++ branches/phase2/src/orca/scripts/default.py Wed Oct 1 12:23:23 2008
@@ -137,6 +137,676 @@
return accessible
+ def isTextArea(self, accessible):
+ """Returns True if accessible is a GUI component that is for
+ entering text.
+
+ Arguments:
+ - accessible: an accessible
+ """
+ return accessible and \
+ accessible.getRole() in (pyatspi.ROLE_TEXT,
+ pyatspi.ROLE_PARAGRAPH,
+ pyatspi.ROLE_TERMINAL)
+
+ def isReadOnlyTextArea(self, accessible):
+ """Returns True if accessible is a text entry area that is
+ read only."""
+ state = accessible.getState()
+ readOnly = self.isTextArea(accessible) \
+ and state.contains(pyatspi.STATE_FOCUSABLE) \
+ and not state.contains(pyatspi.STATE_EDITABLE)
+ return readOnly
+
+ def getText(self, accessible, startOffset, endOffset):
+ """Returns the substring of the given accessible's text
+ specialization.
+
+ Arguments:
+ - accessible: an accessible supporting the accessible text
+ specialization
+ - startOffset: the starting character position
+ - endOffset: the ending character position
+ """
+ return accessible.queryText().getText(startOffset, endOffset)
+
+ def isFunctionalDialog(self, accessible):
+ """Returns true if the window is a functioning as a dialog.
+ This method should be subclassed by application scripts as needed.
+ """
+ return False
+
+ def getUnfocusedAlertAndDialogCount(self, accessible):
+ """If the current application has one or more alert or dialog
+ windows and the currently focused window is not an alert or a dialog,
+ return a count of the number of alert and dialog windows, otherwise
+ return a count of zero.
+
+ Arguments:
+ - accessible: the Accessible object
+
+ Returns the alert and dialog count.
+ """
+ alertAndDialogCount = 0
+ application = accessible.getApplication()
+ window = self.getTopLevel(accessible)
+ if window.getRole() != pyatspi.ROLE_ALERT and \
+ window.getRole() != pyatspi.ROLE_DIALOG and \
+ not self.isFunctionalDialog(window):
+ for child in application:
+ if child.getRole() == pyatspi.ROLE_ALERT or \
+ child.getRole() == pyatspi.ROLE_DIALOG or \
+ self.isFunctionalDialog(child):
+ alertAndDialogCount += 1
+
+ return alertAndDialogCount
+
+ def findCommonAncestor(self, a, b):
+ """Finds the common ancestor between Accessible a and Accessible b.
+
+ Arguments:
+ - a: Accessible
+ - b: Accessible
+ """
+ if (not a) or (not b):
+ return None
+
+ if a == b:
+ return a
+
+ aParents = [a]
+ try:
+ parent = a.parent
+ while parent and (parent.parent != parent):
+ aParents.append(parent)
+ parent = parent.parent
+ aParents.reverse()
+ except:
+ log.exception("exception handled while looking at parent:")
+
+ bParents = [b]
+ try:
+ parent = b.parent
+ while parent and (parent.parent != parent):
+ bParents.append(parent)
+ parent = parent.parent
+ bParents.reverse()
+ except:
+ log.exception("exception handled while looking at parent:")
+
+ commonAncestor = None
+
+ maxSearch = min(len(aParents), len(bParents))
+ i = 0
+ while i < maxSearch:
+ if self.isSameObject(aParents[i], bParents[i]):
+ commonAncestor = aParents[i]
+ i += 1
+ else:
+ break
+
+ return commonAncestor
+
+ def isSameObject(self, obj1, obj2):
+ if (obj1 == obj2):
+ return True
+ elif (not obj1) or (not obj2):
+ return False
+
+ try:
+ if (obj1.name != obj2.name) or (obj1.getRole() != obj2.getRole()):
+ return False
+ else:
+ # Gecko sometimes creates multiple accessibles to represent
+ # the same object. If the two objects have the same name
+ # and the same role, check the extents. If those also match
+ # then the two objects are for all intents and purposes the
+ # same object.
+ #
+ extents1 = \
+ obj1.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ extents2 = \
+ obj2.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ if (extents1.x == extents2.x) and \
+ (extents1.y == extents2.y) and \
+ (extents1.width == extents2.width) and \
+ (extents1.height == extents2.height):
+ return True
+
+ # When we're looking at children of objects that manage
+ # their descendants, we will often get different objects
+ # that point to the same logical child. We want to be able
+ # to determine if two objects are in fact pointing to the
+ # same child.
+ # If we cannot do so easily (i.e., object equivalence), we examine
+ # the hierarchy and the object index at each level.
+ #
+ parent1 = obj1
+ parent2 = obj2
+ while (parent1 and parent2 and \
+ parent1.getState().contains( \
+ pyatspi.STATE_TRANSIENT) and \
+ parent2.getState().contains(pyatspi.STATE_TRANSIENT)):
+ if parent1.getIndexInParent() != parent2.getIndexInParent():
+ return False
+ parent1 = parent1.parent
+ parent2 = parent2.parent
+ if parent1 and parent2 and parent1 == parent2:
+ return self.getRealActiveDescendant(obj1).name == \
+ self.getRealActiveDescendant(obj2).name
+ except:
+ pass
+
+ # In java applications, TRANSIENT state is missing for tree items
+ # (fix for bug #352250)
+ #
+ try:
+ parent1 = obj1
+ parent2 = obj2
+ while parent1 and parent2 and \
+ parent1.getRole() == pyatspi.ROLE_LABEL and \
+ parent2.getRole() == pyatspi.ROLE_LABEL:
+ if parent1.getIndexInParent() != parent2.getIndexInParent():
+ return False
+ parent1 = parent1.parent
+ parent2 = parent2.parent
+ if parent1 and parent2 and parent1 == parent2:
+ return True
+ except:
+ pass
+
+ return False
+
+ def appendString(self, text, newText, delimiter=" "):
+ """Appends the newText to the given text with the delimiter in
+ between and returns the new string. Edge cases, such as no
+ initial text or no newText, are handled gracefully.
+ """
+ if (not newText) or (len(newText) == 0):
+ return text
+ elif text and len(text):
+ return text + delimiter + newText
+ else:
+ return newText
+
+ def __hasLabelForRelation(self, label):
+ """Check if label has a LABEL_FOR relation
+
+ Arguments:
+ - label: the label in question
+
+ Returns TRUE if label has a LABEL_FOR relation.
+ """
+ if (not label) or (label.getRole() != pyatspi.ROLE_LABEL):
+ return False
+
+ relations = label.getRelationSet()
+
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_LABEL_FOR:
+ return True
+
+ return False
+
+ def __isLabeling(self, label, accessible):
+ """Check if label is connected via LABEL_FOR relation with the
+ accessible.
+
+ Arguments:
+ - label: the label in question
+ - accessible: the object in question
+
+ Returns TRUE if label has a relation LABEL_FOR for accessibleect.
+ """
+ if (not accessible) \
+ or (not label) \
+ or (label.getRole() != pyatspi.ROLE_LABEL):
+ return False
+
+ relations = label.getRelationSet()
+ if not relations:
+ return False
+
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_LABEL_FOR:
+
+ for i in range(0, relation.getNTargets()):
+ target = relation.getTarget(i)
+ if target == accessible:
+ return True
+
+ return False
+
+ def getUnicodeCurrencySymbols(self):
+ """Return a list of the unicode currency symbols, populating the list
+ if this is the first time that this routine has been called.
+
+ Returns a list of unicode currency symbols.
+ """
+ if not self._unicodeCurrencySymbols:
+ self._unicodeCurrencySymbols = [ \
+ u'\u0024', # dollar sign
+ u'\u00A2', # cent sign
+ u'\u00A3', # pound sign
+ u'\u00A4', # currency sign
+ u'\u00A5', # yen sign
+ u'\u0192', # latin small letter f with hook
+ u'\u060B', # afghani sign
+ u'\u09F2', # bengali rupee mark
+ u'\u09F3', # bengali rupee sign
+ u'\u0AF1', # gujarati rupee sign
+ u'\u0BF9', # tamil rupee sign
+ u'\u0E3F', # thai currency symbol baht
+ u'\u17DB', # khmer currency symbol riel
+ u'\u2133', # script capital m
+ u'\u5143', # cjk unified ideograph-5143
+ u'\u5186', # cjk unified ideograph-5186
+ u'\u5706', # cjk unified ideograph-5706
+ u'\u5713', # cjk unified ideograph-5713
+ u'\uFDFC', # rial sign
+ ]
+
+ # Add 20A0 (EURO-CURRENCY SIGN) to 20B5 (CEDI SIGN)
+ #
+ for ordChar in range(ord(u'\u20A0'), ord(u'\u20B5') + 1):
+ self._unicodeCurrencySymbols.append(unichr(ordChar))
+
+ return self._unicodeCurrencySymbols
+
+ def findDisplayedLabel(self, accessible):
+ """Return a list of the objects that are labelling this accessible.
+
+ Argument:
+ - accessible: the object in question
+
+ Returns a list of the objects that are labelling this object.
+ """
+ # For some reason, some objects are labelled by the same thing
+ # more than once. Go figure, but we need to check for this.
+ #
+ label = []
+ relations = accessible.getRelationSet()
+ allTargets = []
+
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_LABELLED_BY:
+
+ # The pbject can be labelled by more than one thing, so we just
+ # get all the labels (from unique objects) and append them
+ # together. An example of such objects live in the "Basic"
+ # page of the gnome-accessibility-keyboard-properties app.
+ # The "Delay" and "Speed" objects are labelled both by
+ # their names and units.
+ #
+ for i in range(0, relation.getNTargets()):
+ target = relation.getTarget(i)
+ if not target in allTargets:
+ allTargets.append(target)
+ label.append(target)
+
+ # [[[TODO: HACK - we've discovered oddness in hierarchies such as
+ # the gedit Edit->Preferences dialog. In this dialog, we have
+ # labeled groupings of objects. The grouping is done via a FILLER
+ # with two children - one child is the overall label, and the
+ # other is the container for the grouped objects. When we detect
+ # this, we add the label to the overall context.
+ #
+ # We are also looking for objects which have a PANEL or a FILLER as
+ # parent, and its parent has more children. Through these children,
+ # a potential label with LABEL_FOR relation may exists. We want to
+ # present this label.
+ # This case can be seen in FileChooserDemo application, in Open dialog
+ # window, the line with "Look In" label, a combobox and some
+ # presentation buttons.
+ #
+ # Finally, we are searching the hierarchy of embedded components for
+ # children that are labels.]]]
+ #
+ if not len(label):
+ potentialLabels = []
+ useLabel = False
+ if (accessible.getRole() == pyatspi.ROLE_EMBEDDED):
+ candidate = accessible
+ while candidate.childCount:
+ candidate = candidate[0]
+ # The parent of this object may contain labels
+ # or it may contain filler that contains labels.
+ #
+ candidate = candidate.parent
+ for child in candidate:
+ if child.getRole() == pyatspi.ROLE_FILLER:
+ candidate = child
+ break
+ # If there are labels in this embedded component,
+ # they should be here.
+ #
+ for child in candidate:
+ if child.getRole() == pyatspi.ROLE_LABEL:
+ useLabel = True
+ potentialLabels.append(child)
+ elif ((accessible.getRole() == pyatspi.ROLE_FILLER) \
+ or (accessible.getRole() == pyatspi.ROLE_PANEL)) \
+ and (accessible.childCount == 2):
+ child0, child1 = accessible
+ child0_role = child0.getRole()
+ child1_role = child1.getRole()
+ if child0_role == pyatspi.ROLE_LABEL \
+ and not self.__hasLabelForRelation(child0) \
+ and child1_role in [pyatspi.ROLE_FILLER, \
+ pyatspi.ROLE_PANEL]:
+ useLabel = True
+ potentialLabels.append(child0)
+ elif child1_role == pyatspi.ROLE_LABEL \
+ and not self.__hasLabelForRelation(child1) \
+ and child0_role in [pyatspi.ROLE_FILLER, \
+ pyatspi.ROLE_PANEL]:
+ useLabel = True
+ potentialLabels.append(child1)
+ else:
+ parent = accessible.parent
+ if parent and \
+ ((parent.getRole() == pyatspi.ROLE_FILLER) \
+ or (parent.getRole() == pyatspi.ROLE_PANEL)):
+ for potentialLabel in parent:
+ try:
+ useLabel = self.__isLabeling(potentialLabel,
+ accessible)
+ if useLabel:
+ potentialLabels.append(potentialLabel)
+ break
+ except:
+ pass
+
+ if useLabel and len(potentialLabels):
+ label = potentialLabels
+
+ return label
+
+ def getDisplayedLabel(self, accessible):
+ """If there is an object labelling the given object, return the
+ text being displayed for the object labelling this object.
+ Otherwise, return None.
+
+ Argument:
+ - accessible: the object in question
+
+ Returns the string of the object labelling this object, or None
+ if there is nothing of interest here.
+ """
+ labelString = None
+ labels = self.findDisplayedLabel(accessible)
+ for label in labels:
+ labelString = self.appendString(labelString,
+ self.getDisplayedText(label))
+
+ return labelString
+
+ def __getDisplayedTextInComboBox(self, combo):
+
+ """Returns the text being displayed in a combo box. If nothing is
+ displayed, then None is returned.
+
+ Arguments:
+ - combo: the combo box
+
+ Returns the text in the combo box or an empty string if nothing is
+ displayed.
+ """
+ displayedText = None
+
+ # Find the text displayed in the combo box. This is either:
+ #
+ # 1) The last text object that's a child of the combo box
+ # 2) The selected child of the combo box.
+ # 3) The contents of the text of the combo box itself when
+ # treated as a text object.
+ #
+ # Preference is given to #1, if it exists.
+ #
+ # If the label of the combo box is the same as the utterance for
+ # the child object, then this utterance is only displayed once.
+ #
+ # [[[TODO: WDW - Combo boxes are complex beasts. This algorithm
+ # needs serious work. Logged as bugzilla bug 319745.]]]
+ #
+ textObj = None
+ for child in combo:
+ if child and child.getRole() == pyatspi.ROLE_TEXT:
+ textObj = child
+
+ if textObj:
+ [displayedText, caretOffset, startOffset] = \
+ self.getTextLineAtCaret(textObj)
+ #print "TEXTOBJ", displayedText
+ else:
+ try:
+ comboSelection = combo.querySelection()
+ selectedItem = comboSelection.getSelectedChild(0)
+ except:
+ selectedItem = None
+
+ if selectedItem:
+ displayedText = self.getDisplayedText(selectedItem)
+ #print "SELECTEDITEM", displayedText
+ elif combo.name and len(combo.name):
+ # We give preference to the name over the text because
+ # the text for combo boxes seems to never change in
+ # some cases. The main one where we see this is in
+ # the gaim "Join Chat" window.
+ #
+ displayedText = combo.name
+ #print "NAME", displayedText
+ else:
+ [displayedText, caretOffset, startOffset] = \
+ self.getTextLineAtCaret(combo)
+ # Set to None instead of empty string.
+ displayedText = displayedText or None
+ #print "TEXT", displayedText
+
+ return displayedText
+
+ def getDisplayedText(self, accessible):
+ """Returns the text being displayed for an object.
+
+ Arguments:
+ - accessible: the object
+
+ Returns the text being displayed for an object or None if there isn't
+ any text being shown.
+ """
+ displayedText = None
+
+ role = accessible.getRole()
+ if role == pyatspi.ROLE_COMBO_BOX:
+ return self.__getDisplayedTextInComboBox(accessible)
+
+ # The accessible text of an object is used to represent what is
+ # drawn on the screen.
+ #
+ try:
+ text = accessible.queryText()
+ except NotImplementedError:
+ pass
+ else:
+ displayedText = text.getText(0, -1)
+
+ # [[[WDW - HACK to account for things such as Gecko that want
+ # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
+ # object that has the real accessible text for the label. We
+ # detect this by the specfic case where the text for the
+ # current object is a single EMBEDDED_OBJECT_CHARACTER. In
+ # this case, we look to the child for the real text.]]]
+ #
+ unicodeText = displayedText.decode("UTF-8")
+ if unicodeText \
+ and (len(unicodeText) == 1) \
+ and (unicodeText[0] == self.EMBEDDED_OBJECT_CHARACTER) \
+ and accessible.childCount > 0:
+ try:
+ displayedText = self.getDisplayedText(accessible[0])
+ except:
+ log.exception("exception handled getting text:")
+ elif unicodeText:
+ # [[[TODO: HACK - Welll.....we'll just plain ignore any
+ # text with EMBEDDED_OBJECT_CHARACTERs here. We still need a
+ # general case to handle this stuff and expand objects
+ # with EMBEDDED_OBJECT_CHARACTERs.]]]
+ #
+ for i in range(0, len(unicodeText)):
+ if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
+ displayedText = None
+ break
+
+ if not displayedText:
+ displayedText = accessible.name
+
+ # [[[WDW - HACK because push buttons can have labels as their
+ # children. An example of this is the Font: button on the General
+ # tab in the Editing Profile dialog in gnome-terminal.
+ #
+ if not displayedText and role == pyatspi.ROLE_PUSH_BUTTON:
+ for child in accessible:
+ if child.getRole() == pyatspi.ROLE_LABEL:
+ childText = self.getDisplayedText(child)
+ if childText and len(childText):
+ displayedText = self.appendString(displayedText,
+ childText)
+
+ return displayedText
+
+ def getTextForValue(self, accessible):
+ """Returns the text to be displayed for the object's current value.
+
+ Arguments:
+ - accessible: the Accessible object that may or may not have a value.
+
+ Returns a string representing the value.
+ """
+ try:
+ value = accessible.queryValue()
+ except NotImplementedError:
+ return ""
+
+ # OK, this craziness is all about trying to figure out the most
+ # meaningful formatting string for the floating point values.
+ # The number of places to the right of the decimal point should
+ # be set by the minimumIncrement, but the minimumIncrement isn't
+ # always set. So...we'll default the minimumIncrement to 1/100
+ # of the range. But, if max == min, then we'll just go for showing
+ # them off to two meaningful digits.
+ #
+ try:
+ minimumIncrement = value.minimumIncrement
+ except:
+ minimumIncrement = 0.0
+
+ if minimumIncrement == 0.0:
+ minimumIncrement = (value.maximumValue - value.minimumValue) \
+ / 100.0
+
+ try:
+ decimalPlaces = max(0, -math.log10(minimumIncrement))
+ except:
+ try:
+ decimalPlaces = max(0, -math.log10(value.minimumValue))
+ except:
+ try:
+ decimalPlaces = max(0, -math.log10(value.maximumValue))
+ except:
+ decimalPlaces = 0
+
+ formatter = "%%.%df" % decimalPlaces
+ valueString = formatter % value.currentValue
+ #minString = formatter % value.minimumValue
+ #maxString = formatter % value.maximumValue
+
+ # [[[TODO: WDW - probably want to do this as a percentage at some
+ # point? Logged as bugzilla bug 319743.]]]
+ #
+ return valueString
+
+ def findFocusedAccessible(self, root):
+ """Returns the accessible that has focus under or including the
+ given root.
+
+ TODO: This will currently traverse all children, whether they are
+ visible or not and/or whether they are children of parents that
+ manage their descendants. At some point, this method should be
+ optimized to take such things into account.
+
+ Arguments:
+ - root: the root object where to start searching
+
+ Returns the object with the FOCUSED state or None if no object with
+ the FOCUSED state can be found.
+ """
+ if root.getState().contains(pyatspi.STATE_FOCUSED):
+ return root
+
+ for child in root:
+ try:
+ candidate = self.findFocusedAccessible(child)
+ if candidate:
+ return candidate
+ except:
+ pass
+
+ return None
+
+ def getRealActiveDescendant(self, accessible):
+ """Given an object that should be a child of an object that
+ manages its descendants, return the child that is the real
+ active descendant carrying useful information.
+
+ Arguments:
+ - accessible: an object that should be a child of an object that
+ manages its descendants.
+ """
+ # If obj is a table cell and all of it's children are table cells
+ # (probably cell renderers), then return the first child which has
+ # a non zero length text string. If no such object is found, just
+ # fall through and use the default approach below. See bug #376791
+ # for more details.
+ #
+ if accessible.getRole() == pyatspi.ROLE_TABLE_CELL \
+ and accessible.childCount:
+ nonTableCellFound = False
+ for child in accessible:
+ if child.getRole() != pyatspi.ROLE_TABLE_CELL:
+ nonTableCellFound = True
+ if not nonTableCellFound:
+ for child in accessible:
+ try:
+ text = child.queryText()
+ except NotImplementedError:
+ continue
+ else:
+ if text.getText(0, -1):
+ return child
+
+ # [[[TODO: WDW - this is an odd hacky thing I've somewhat drawn
+ # from Gnopernicus. The notion here is that we get an active
+ # descendant changed event, but that object tends to have children
+ # itself and we need to decide what to do. Well...the idea here
+ # is that the last child (Gnopernicus chooses child(1)), tends to
+ # be the child with information. The previous children tend to
+ # be non-text or just there for spacing or something. You will
+ # see this in the various table demos of gtk-demo and you will
+ # also see this in the Contact Source Selector in Evolution.
+ #
+ # Just note that this is most likely not a really good solution
+ # for the general case. That needs more thought. But, this
+ # comment is here to remind us this is being done in poor taste
+ # and we need to eventually clean up our act.]]]
+ #
+ if accessible and accessible.childCount:
+ return accessible[-1]
+ else:
+ return accessible
+
####################################################################
# #
# AT-SPI OBJECT EVENT HANDLERS #
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]