[orca] Announce when one or more rows or columns gets selected/unselected in Calc



commit 8530a3d244438b6cd8ae524f1b5f1025a3c2f4fa
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Jul 2 18:11:04 2018 -0400

    Announce when one or more rows or columns gets selected/unselected in Calc
    
    Note that this functionality assumes Calc tells us; it doesn't always.

 src/orca/messages.py                              | 55 +++++++++++++++
 src/orca/scripts/apps/soffice/script.py           | 29 ++------
 src/orca/scripts/apps/soffice/script_utilities.py | 86 ++++++++++++++++++++++-
 3 files changed, 146 insertions(+), 24 deletions(-)
---
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 9e7b9e5eb..b8c39437a 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -423,6 +423,13 @@ DIGITS_SUPERSCRIPT =  _(" superscript %s")
 # document by pressing Ctrl+A.
 DOCUMENT_SELECTED_ALL = _("entire document selected")
 
+# Translators: when the user selects (highlights) or unselects text in a
+# document, Orca will speak information about what they have selected or
+# unselected. This message is presented when the entire document had been
+# selected but the user presses a key (e.g. an arrow key) causing the
+# selection to be completely removed.
+DOCUMENT_UNSELECTED_ALL = _("entire document unselected")
+
 # Translators: when the user selects (highlights) or unselects text in a
 # document, Orca will speak information about what they have selected or
 # unselected. This message is presented when the user selects from the
@@ -2134,6 +2141,30 @@ TABLE_COLUMN_BOTTOM = _("Bottom of column.")
 # the cell above the current cell and is already in the first row.
 TABLE_COLUMN_TOP = _("Top of column.")
 
+# Translators: this message is spoken to announce that a table column just became
+# selected (e.g as a result of navigation via Shift + Arrows). The string substitution
+# is the column label (e.g. "B").
+TABLE_COLUMN_SELECTED = _("Column %s selected")
+
+# Translators: this message is spoken to announce that multiple table columns just
+# became selected (e.g as a result of navigation via Shift + Arrows). The first
+# string substitution is the label of the first column in the range. The second string
+# substitution is the label in the last column in the range. An example message for
+# Calc would be "Columns B through F selected".
+TABLE_COLUMN_RANGE_SELECTED = _("Columns %s through %s selected")
+
+# Translators: this message is spoken to announce that multiple table columns just
+# became unselected (e.g as a result of navigation via Shift + Arrows). The first
+# string substitution is the label of the first column in the range. The second string
+# substitution is the label in the last column in the range. An example message for
+# Calc would be "Columns B through F unselected".
+TABLE_COLUMN_RANGE_UNSELECTED = _("Columns %s through %s unselected")
+
+# Translators: this message is spoken to announce that a table column just became
+# unselected (e.g as a result of navigation via Shift + Arrows). The string substitution
+# is the column label (e.g. "B").
+TABLE_COLUMN_UNSELECTED = _("Column %s unselected")
+
 # Translators: this is in reference to a row in a table. The substitution is
 # the index (e.g. the first row is "row 1").
 TABLE_ROW = _("row %d")
@@ -2169,6 +2200,30 @@ TABLE_ROW_INSERTED = _("Row inserted.")
 # user presses Tab from within the last cell of the table.
 TABLE_ROW_INSERTED_AT_END = _("Row inserted at the end of the table.")
 
+# Translators: this message is spoken to announce that a table row just became selected
+# (e.g as a result of navigation via Shift + Arrows). The string substitution is the row
+# label (e.g. "2").
+TABLE_ROW_SELECTED = _("Row %s selected")
+
+# Translators: this message is spoken to announce that multiple table rows just
+# became selected (e.g as a result of navigation via Shift + Arrows). The first
+# string substitution is the label of the first row in the range. The second string
+# substitution is the label of the last row in the range. An example message for
+# Calc would be "Rows 2 through 10 selected".
+TABLE_ROW_RANGE_SELECTED = _("Rows %s through %s selected")
+
+# Translators: this message is spoken to announce that multiple table rows just
+# became unselected (e.g as a result of navigation via Shift + Arrows). The first
+# string substitution is the label of the first row in the range. The second string
+# substitution is the label of the last row in the range. An example message for
+# Calc would be "Rows 2 through 10 unselected".
+TABLE_ROW_RANGE_UNSELECTED = _("Rows %s through %s unselected")
+
+# Translators: this message is spoken to announce that a table row just became
+# unselected (e.g as a result of navigation via Shift + Arrows). The string
+# substitution is the row label (e.g. "2").
+TABLE_ROW_UNSELECTED = _("Row %s unselected")
+
 # Translators: when the user selects (highlights) text in a document, Orca lets
 # them know.
 TEXT_SELECTED = C_("text", "selected")
diff --git a/src/orca/scripts/apps/soffice/script.py b/src/orca/scripts/apps/soffice/script.py
index 56e3b00cb..9b2af1931 100644
--- a/src/orca/scripts/apps/soffice/script.py
+++ b/src/orca/scripts/apps/soffice/script.py
@@ -427,28 +427,6 @@ class Script(default.Script):
 
         return True
 
-    def columnConvert(self, column):
-        """ Convert a spreadsheet column into it's column label
-
-        Arguments:
-        - column: the column number to convert.
-
-        Returns a string representing the spread sheet column.
-        """
-
-        base26 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-
-        if column <= len(base26):
-            return base26[column-1]
-
-        res = ""
-        while column > 0:
-            digit = column % len(base26)
-            res = " " + base26[digit-1] + res
-            column /= len(base26)
-
-        return res
-
     def setDynamicRowHeaders(self, inputEvent):
         """Set the column for the dynamic header rows to use when speaking
         calc cell entries. In order to set the column, the user should first
@@ -470,7 +448,7 @@ class Script(default.Script):
         if table:
             self.dynamicRowHeaders[hash(table)] = column
             self.presentMessage(
-                messages.DYNAMIC_ROW_HEADER_SET % self.columnConvert(column+1))
+                messages.DYNAMIC_ROW_HEADER_SET % self.utilities.columnConvert(column+1))
 
         return True
 
@@ -863,6 +841,11 @@ class Script(default.Script):
     def onSelectionChanged(self, event):
         """Callback for object:selection-changed accessibility events."""
 
+        if self.utilities.isSpreadSheetTable(event.source):
+            if not _settingsManager.getSetting('onlySpeakDisplayedText'):
+                self.utilities.handleRowAndColumnSelectionChange(event.source)
+            return
+
         if not self.utilities.isComboBoxSelectionChange(event):
             super().onSelectionChanged(event)
             return
diff --git a/src/orca/scripts/apps/soffice/script_utilities.py 
b/src/orca/scripts/apps/soffice/script_utilities.py
index cf1355697..7babe0471 100644
--- a/src/orca/scripts/apps/soffice/script_utilities.py
+++ b/src/orca/scripts/apps/soffice/script_utilities.py
@@ -32,6 +32,7 @@ import pyatspi
 
 import orca.debug as debug
 import orca.keybindings as keybindings
+import orca.messages as messages
 import orca.orca_state as orca_state
 import orca.script_utilities as script_utilities
 
@@ -52,6 +53,9 @@ class Utilities(script_utilities.Utilities):
 
         script_utilities.Utilities.__init__(self, script)
 
+        self._calcSelectedRows = []
+        self._calcSelectedColumns = []
+
     #########################################################################
     #                                                                       #
     # Utilities for finding, identifying, and comparing accessibles         #
@@ -709,9 +713,15 @@ class Utilities(script_utilities.Utilities):
 
         # Things only seem broken for certain tables, e.g. the Paths table.
         # TODO - JD: File the LibreOffice bugs and reference them here.
-        if role != pyatspi.ROLE_TABLE or self.isSpreadSheetTable(obj):
+        if role != pyatspi.ROLE_TABLE:
             return super().selectedChildren(obj)
 
+        # We will need to special case this due to the possibility of there
+        # being lots of children (which may also prove to be zombie objects).
+        # This is why we can't have nice things.
+        if self.isSpreadSheetTable(obj):
+            return []
+
         try:
             selection = obj.querySelection()
         except:
@@ -748,3 +758,77 @@ class Utilities(script_utilities.Utilities):
 
     def presentEventFromNonShowingObject(self, event):
         return self.inDocumentContent(event.source)
+
+    def columnConvert(self, column):
+        """ Convert a spreadsheet column into it's column label."""
+
+        base26 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+        if column <= len(base26):
+            return base26[column-1]
+
+        res = ""
+        while column > 0:
+            digit = column % len(base26)
+            res = " " + base26[digit-1] + res
+            column = int(column / len(base26))
+
+        return res
+
+    def handleRowAndColumnSelectionChange(self, obj):
+        interfaces = pyatspi.listInterfaces(obj)
+        if not ("Table" in interfaces and "Selection" in interfaces):
+            return True
+
+        table = obj.queryTable()
+        cols = set(table.getSelectedColumns())
+        rows = set(table.getSelectedRows())
+
+        selectedCols = sorted(cols.difference(set(self._calcSelectedColumns)))
+        unselectedCols = sorted(set(self._calcSelectedColumns).difference(cols))
+        convert = lambda x: self.columnConvert(x+1)
+        selectedCols = list(map(convert, selectedCols))
+        unselectedCols = list(map(convert, unselectedCols))
+
+        selectedRows = sorted(rows.difference(set(self._calcSelectedRows)))
+        unselectedRows = sorted(set(self._calcSelectedRows).difference(rows))
+        convert = lambda x: x + 1
+        selectedRows = list(map(convert, selectedRows))
+        unselectedRows = list(map(convert, unselectedRows))
+
+        self._calcSelectedColumns = list(cols)
+        self._calcSelectedRows = list(rows)
+
+        if len(cols) == table.nColumns:
+            self._script.speakMessage(messages.DOCUMENT_SELECTED_ALL)
+            return True
+
+        if not len(cols) and len(unselectedCols) == table.nColumns:
+            self._script.speakMessage(messages.DOCUMENT_UNSELECTED_ALL)
+            return True
+
+        msgs = []
+        if len(unselectedCols) == 1:
+            msgs.append(messages.TABLE_COLUMN_UNSELECTED % unselectedCols[0])
+        elif len(unselectedCols) > 1:
+            msgs.append(messages.TABLE_COLUMN_RANGE_UNSELECTED % (unselectedCols[0], unselectedCols[-1]))
+
+        if len(unselectedRows) == 1:
+            msgs.append(messages.TABLE_ROW_UNSELECTED % unselectedRows[0])
+        elif len(unselectedRows) > 1:
+            msgs.append(messages.TABLE_ROW_RANGE_UNSELECTED % (unselectedRows[0], unselectedRows[-1]))
+
+        if len(selectedCols) == 1:
+            msgs.append(messages.TABLE_COLUMN_SELECTED % selectedCols[0])
+        elif len(selectedCols) > 1:
+            msgs.append(messages.TABLE_COLUMN_RANGE_SELECTED % (selectedCols[0], selectedCols[-1]))
+
+        if len(selectedRows) == 1:
+            msgs.append(messages.TABLE_ROW_SELECTED % selectedRows[0])
+        elif len(selectedRows) > 1:
+            msgs.append(messages.TABLE_ROW_RANGE_SELECTED % (selectedRows[0], selectedRows[-1]))
+
+        for msg in msgs:
+            self._script.speakMessage(msg, interrupt=False)
+
+        return bool(len(msgs))


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]