|
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,39 @@ 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 | + # Available custom Annotations list at https://docs.microsoft.com/en-us/office/uia/excel/excelannotations |
| 117 | + # Note annotation: |
| 118 | + # Represents an old-style comment (now known as a note) |
| 119 | + # which contains non-threaded plain text content. |
| 120 | + self.note = CustomAnnotationTypeInfo( |
| 121 | + guid=GUID("{4E863D9A-F502-4A67-808F-9E711702D05E}"), |
| 122 | + ) |
| 123 | + |
| 124 | + |
93 | 125 | class ExcelObject(UIA): |
94 | 126 | """Common base class for all Excel UIA objects |
95 | 127 | """ |
96 | 128 | _UIAExcelCustomProps = ExcelCustomProperties.get() |
| 129 | + _UIAExcelCustomAnnotationTypes = ExcelCustomAnnotationTypes.get() |
| 130 | + |
97 | 131 |
|
98 | 132 |
|
99 | 133 | class ExcelCell(ExcelObject): |
@@ -367,6 +401,15 @@ def _get_states(self): |
367 | 401 | states.add(controlTypes.State.HASFORMULA) |
368 | 402 | if self._getUIACacheablePropertyValue(self._UIAExcelCustomProps.hasDataValidationDropdown.id): |
369 | 403 | states.add(controlTypes.State.HASPOPUP) |
| 404 | + if winVersion.getWinVer() >= winVersion.WIN11: |
| 405 | + try: |
| 406 | + annotationTypes = self._getUIACacheablePropertyValue(UIAHandler.UIA_AnnotationTypesPropertyId) |
| 407 | + except COMError: |
| 408 | + # annotationTypes cannot be fetched on older Operating Systems such as Windows 7. |
| 409 | + annotationTypes = None |
| 410 | + if annotationTypes: |
| 411 | + if self._UIAExcelCustomAnnotationTypes.note.id in annotationTypes: |
| 412 | + states.add(controlTypes.State.HASNOTE) |
370 | 413 | return states |
371 | 414 |
|
372 | 415 | def _get_cellCoordsText(self): |
@@ -421,28 +464,39 @@ def _get_cellCoordsText(self): |
421 | 464 | description=_("Reports the note or comment thread on the current cell"), |
422 | 465 | gesture="kb:NVDA+alt+c") |
423 | 466 | def script_reportComment(self, gesture): |
| 467 | + if winVersion.getWinVer() >= winVersion.WIN11: |
| 468 | + noteElement = self.UIAAnnotationObjects.get(self._UIAExcelCustomAnnotationTypes.note.id) |
| 469 | + if noteElement: |
| 470 | + name = noteElement.CurrentName |
| 471 | + desc = noteElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId) |
| 472 | + # Translators: a note on a cell in Microsoft excel. |
| 473 | + text = _("{name}: {desc}").format(name=name, desc=desc) |
| 474 | + ui.message(text) |
| 475 | + else: |
| 476 | + # Translators: message when a cell in Excel contains no note |
| 477 | + ui.message(_("No note on this cell")) |
424 | 478 | commentsElement = self.UIAAnnotationObjects.get(UIAHandler.AnnotationType_Comment) |
425 | 479 | if commentsElement: |
426 | 480 | comment = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId) |
427 | 481 | author = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_AnnotationAuthorPropertyId) |
428 | 482 | numReplies = commentsElement.GetCurrentPropertyValue(self._UIAExcelCustomProps.commentReplyCount.id) |
429 | 483 | if numReplies == 0: |
430 | 484 | # Translators: a comment on a cell in Microsoft excel. |
431 | | - text = _("{comment} by {author}").format( |
| 485 | + text = _("Comment thread: {comment} by {author}").format( |
432 | 486 | comment=comment, |
433 | 487 | author=author |
434 | 488 | ) |
435 | 489 | else: |
436 | 490 | # Translators: a comment on a cell in Microsoft excel. |
437 | | - text = _("{comment} by {author} with {numReplies} replies").format( |
| 491 | + text = _("Comment thread: {comment} by {author} with {numReplies} replies").format( |
438 | 492 | comment=comment, |
439 | 493 | author=author, |
440 | 494 | numReplies=numReplies |
441 | 495 | ) |
442 | 496 | ui.message(text) |
443 | 497 | else: |
444 | | - # Translators: A message in Excel when there is no note |
445 | | - ui.message(_("No note or comment thread on this cell")) |
| 498 | + # Translators: A message in Excel when there is no comment thread |
| 499 | + ui.message(_("No comment thread on this cell")) |
446 | 500 |
|
447 | 501 |
|
448 | 502 | class ExcelWorksheet(ExcelObject): |
|
0 commit comments