|
4 | 4 | # Copyright (C) 2018-2021 NV Access Limited, Leonard de Ruijter |
5 | 5 |
|
6 | 6 | from typing import Optional, Tuple |
| 7 | +from comtypes import COMError |
| 8 | +import winVersion |
7 | 9 | import UIAHandler |
8 | 10 | import _UIAHandler |
9 | 11 | import _UIAConstants |
|
16 | 18 | from _UIACustomProps import ( |
17 | 19 | CustomPropertyInfo, |
18 | 20 | ) |
| 21 | +from _UIACustomAnnotations import ( |
| 22 | + CustomAnnotationTypeInfo, |
| 23 | +) |
19 | 24 | from comtypes import GUID |
20 | 25 | from scriptHandler import script |
21 | 26 | import ui |
@@ -90,10 +95,35 @@ def __init__(self): |
90 | 95 | ) |
91 | 96 |
|
92 | 97 |
|
| 98 | +class ExcelCustomAnnotationTypes: |
| 99 | + """ UIA 'custom annotation types' specific to Excel. |
| 100 | + Once registered, all subsequent registrations will return the same ID value. |
| 101 | + This class should be used as a singleton via ExcelCustomAnnotationTypes.get() |
| 102 | + to prevent unnecessary work by repeatedly interacting with UIA. |
| 103 | + """ |
| 104 | + #: Singleton instance |
| 105 | + _instance: "Optional[ExcelCustomAnnotationTypes]" = None |
| 106 | + |
| 107 | + @classmethod |
| 108 | + def get(cls) -> "ExcelCustomAnnotationTypes": |
| 109 | + """Get the singleton instance or initialise it. |
| 110 | + """ |
| 111 | + if cls._instance is None: |
| 112 | + cls._instance = cls() |
| 113 | + return cls._instance |
| 114 | + |
| 115 | + def __init__(self): |
| 116 | + self.note = CustomAnnotationTypeInfo( |
| 117 | + guid=GUID("{4E863D9A-F502-4A67-808F-9E711702D05E}"), |
| 118 | + ) |
| 119 | + |
| 120 | + |
93 | 121 | class ExcelObject(UIA): |
94 | 122 | """Common base class for all Excel UIA objects |
95 | 123 | """ |
96 | 124 | _UIAExcelCustomProps = ExcelCustomProperties.get() |
| 125 | + _UIAExcelCustomAnnotationTypes = ExcelCustomAnnotationTypes.get() |
| 126 | + |
97 | 127 |
|
98 | 128 |
|
99 | 129 | class ExcelCell(ExcelObject): |
@@ -367,6 +397,15 @@ def _get_states(self): |
367 | 397 | states.add(controlTypes.State.HASFORMULA) |
368 | 398 | if self._getUIACacheablePropertyValue(self._UIAExcelCustomProps.hasDataValidationDropdown.id): |
369 | 399 | states.add(controlTypes.State.HASPOPUP) |
| 400 | + if winVersion.getWinVer() >= winVersion.WIN11: |
| 401 | + try: |
| 402 | + annotationTypes = self._getUIACacheablePropertyValue(UIAHandler.UIA_AnnotationTypesPropertyId) |
| 403 | + except COMError: |
| 404 | + # annotationTypes cannot be fetched on older Operating Systems such as Windows 7. |
| 405 | + annotationTypes = None |
| 406 | + if annotationTypes: |
| 407 | + if self._UIAExcelCustomAnnotationTypes.note.id in annotationTypes: |
| 408 | + states.add(controlTypes.State.HASNOTE) |
370 | 409 | return states |
371 | 410 |
|
372 | 411 | def _get_cellCoordsText(self): |
@@ -421,28 +460,39 @@ def _get_cellCoordsText(self): |
421 | 460 | description=_("Reports the note or comment thread on the current cell"), |
422 | 461 | gesture="kb:NVDA+alt+c") |
423 | 462 | def script_reportComment(self, gesture): |
| 463 | + if winVersion.getWinVer() >= winVersion.WIN11: |
| 464 | + noteElement = self.UIAAnnotationObjects.get(self._UIAExcelCustomAnnotationTypes.note.id) |
| 465 | + if noteElement: |
| 466 | + name = noteElement.CurrentName |
| 467 | + desc = noteElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId) |
| 468 | + # Translators: a note on a cell in Microsoft excel. |
| 469 | + text = _("{name}: {desc}").format(name=name, desc=desc) |
| 470 | + ui.message(text) |
| 471 | + else: |
| 472 | + # Translators: message when a cell in Excel contains no note |
| 473 | + ui.message(_("No note on this cell")) |
424 | 474 | commentsElement = self.UIAAnnotationObjects.get(UIAHandler.AnnotationType_Comment) |
425 | 475 | if commentsElement: |
426 | 476 | comment = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId) |
427 | 477 | author = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_AnnotationAuthorPropertyId) |
428 | 478 | numReplies = commentsElement.GetCurrentPropertyValue(self._UIAExcelCustomProps.commentReplyCount.id) |
429 | 479 | if numReplies == 0: |
430 | 480 | # Translators: a comment on a cell in Microsoft excel. |
431 | | - text = _("{comment} by {author}").format( |
| 481 | + text = _("Comment thread: {comment} by {author}").format( |
432 | 482 | comment=comment, |
433 | 483 | author=author |
434 | 484 | ) |
435 | 485 | else: |
436 | 486 | # Translators: a comment on a cell in Microsoft excel. |
437 | | - text = _("{comment} by {author} with {numReplies} replies").format( |
| 487 | + text = _("Comment thread: {comment} by {author} with {numReplies} replies").format( |
438 | 488 | comment=comment, |
439 | 489 | author=author, |
440 | 490 | numReplies=numReplies |
441 | 491 | ) |
442 | 492 | ui.message(text) |
443 | 493 | else: |
444 | | - # Translators: A message in Excel when there is no note |
445 | | - ui.message(_("No note or comment thread on this cell")) |
| 494 | + # Translators: A message in Excel when there is no comment thread |
| 495 | + ui.message(_("No comment thread on this cell")) |
446 | 496 |
|
447 | 497 |
|
448 | 498 | class ExcelWorksheet(ExcelObject): |
|
0 commit comments