Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Excel with UIA: announce merged cells #12843

Merged
merged 6 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions source/NVDAObjects/UIA/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ui
from logHandler import log
from . import UIA
import re


class ExcelCustomProperties:
Expand Down Expand Up @@ -131,6 +132,8 @@ class ExcelObject(UIA):

class ExcelCell(ExcelObject):

_coordinateRegEx = re.compile("([A-Z]+)([0-9]+)", re.IGNORECASE)

# selecting cells causes duplicate focus events
shouldAllowDuplicateUIAFocusEvent = True

Expand Down Expand Up @@ -411,6 +414,40 @@ def _get_states(self):
states.add(controlTypes.State.HASNOTE)
return states

@staticmethod
def _getColumnRepresentationForNumber(n: int) -> str:
"""
Convert a decimal number to its base alphabet representation.
See https://codereview.stackexchange.com/questions/182733/base-26-letters-and-base-10-using-recursion
for more details about the approach used.
"""
def modGenerator(x: int) -> Tuple[int, int]:
"""Generate digits from L{x} in base alphabet, least significants
bits first.

Since A is 1 rather than 0 in base alphabet, we are dealing with
L{x} - 1 at each iteration to be able to extract the proper digits.
"""
while x:
x, y = divmod(x - 1, 26)
yield y
return ''.join(
chr(ord("A") + i)
for i in modGenerator(n)
)[::-1]

@staticmethod
def _getNumberRepresentationForColumn(column: str) -> int:
"""
Convert an alphabet number to its decimal representation.
See https://codereview.stackexchange.com/questions/182733/base-26-letters-and-base-10-using-recursion
for more details about the approach used.
"""
return sum(
(ord(letter) - ord("A") + 1) * (26 ** i)
for i, letter in enumerate(reversed(column.upper()))
)

def _get_cellCoordsText(self):
if self._hasSelection():
sc = self._getUIACacheablePropertyValue(
Expand All @@ -423,7 +460,7 @@ def _get_cellCoordsText(self):

firstAddress = firstSelected.GetCurrentPropertyValue(
UIAHandler.UIA_NamePropertyId
).replace('"', '')
).replace('"', '').replace(' ', '')

firstValue = firstSelected.GetCurrentPropertyValue(
UIAHandler.UIA_ValueValuePropertyId
Expand All @@ -435,15 +472,15 @@ def _get_cellCoordsText(self):

lastAddress = lastSelected.GetCurrentPropertyValue(
UIAHandler.UIA_NamePropertyId
).replace('"', '')
).replace('"', '').replace(' ', '')

lastValue = lastSelected.GetCurrentPropertyValue(
UIAHandler.UIA_ValueValuePropertyId
)

cellCoordsTemplate = pgettext(
"excel-UIA",
# Translators: Excel, report range of cell coordinates
# Translators: Excel, report selected range of cell coordinates
"{firstAddress} {firstValue} through {lastAddress} {lastValue}"
)
return cellCoordsTemplate.format(
Expand All @@ -452,11 +489,33 @@ def _get_cellCoordsText(self):
lastAddress=lastAddress,
lastValue=lastValue
)
name = super().name
# Later builds of Excel 2016 quote the letter coordinate.
# We don't want the quotes.
name = name.replace('"', '')
return name
else:
name = super().name
# Later builds of Excel 2016 quote the letter coordinate.
# We don't want the quotes and also strip the space between column and row.
name = name.replace('"', '').replace(' ', '')
if self.rowSpan > 1 or self.columnSpan > 1:
# Excel does not offer information about merged cells
# but merges all merged cells into one UIA element named as the first cell in the merged range.
# We have to calculate the last address name manually.
firstAddress = name
firstColumn, firstRow = self._coordinateRegEx.match(firstAddress).groups()
firstRow = int(firstRow)
lastColumn = firstColumn if self.columnSpan == 1 else self._getColumnRepresentationForNumber(
self._getNumberRepresentationForColumn(firstColumn) + (self.columnSpan - 1)
)
lastRow = firstRow + (self.rowSpan - 1)
lastAddress = f"{lastColumn}{lastRow}"
cellCoordsTemplate = pgettext(
"excel-UIA",
# Translators: Excel, report merged range of cell coordinates
"{firstAddress} through {lastAddress}"
)
return cellCoordsTemplate.format(
firstAddress=firstAddress,
lastAddress=lastAddress,
)
return name

@script(
# Translators: the description for a script for Excel
Expand Down
2 changes: 2 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ What's New in NVDA


== Changes ==
- When using UI Automation to access Microsoft Excel spreadsheet controls, NVDA is now able to report when a cell is merged. (#12843)
-


== Bug Fixes ==
Expand Down