Skip to content

Commit

Permalink
Merge f57560b into 6b4cf45
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelDCurran committed Sep 20, 2021
2 parents 6b4cf45 + f57560b commit c72e5e7
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 4 deletions.
10 changes: 10 additions & 0 deletions nvdaHelper/local/UIAUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ PROPERTYID registerUIAProperty(GUID* guid, LPCWSTR programmaticName, UIAutomatio
registrar->Release();
return propertyId;
}

int registerUIAAnnotationType(GUID* guid) {
if(!guid) {
LOG_DEBUGWARNING(L"NULL GUID given");
return 0;
}
winrt::Windows::UI::UIAutomation::Core::CoreAutomationRegistrar registrar {};
auto res = registrar.RegisterAnnotationType(*guid);
return res.LocalId;
}
6 changes: 6 additions & 0 deletions nvdaHelper/local/UIAUtils.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#ifndef NVDAHELPERLOCAL_UIAUTILS_H
#define NVDAHELPERLOCAL_UIAUTILS_H

// The following header included to allow winrt::guid to be converted to GUID
#include <unknwn.h>

#include <winrt/windows.ui.uiautomation.core.h>
#include <uiAutomationCore.h>

PROPERTYID registerUIAProperty(GUID* guid, LPCWSTR programmaticName, UIAutomationType propertyType);
int registerUIAAnnotationType(GUID* guid);


#endif

1 change: 1 addition & 0 deletions nvdaHelper/local/nvdaHelperLocal.def
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ EXPORTS
calculateCharacterOffsets
findWindowWithClassInThread
registerUIAProperty
registerUIAAnnotationType
dllImportTableHooks_hookSingle
dllImportTableHooks_unhookSingle
audioDucking_shouldDelay
Expand Down
2 changes: 2 additions & 0 deletions source/NVDAObjects/UIA/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import languageHandler
import UIAHandler
import _UIACustomProps
import _UIACustomAnnotations
import globalVars
import eventHandler
import controlTypes
Expand Down Expand Up @@ -818,6 +819,7 @@ def updateSelection(self):

class UIA(Window):
_UIACustomProps = _UIACustomProps.CustomPropertiesCommon.get()
_UIACustomAnnotationTypes = _UIACustomAnnotations.CustomAnnotationTypesCommon.get()

shouldAllowDuplicateUIAFocusEvent = False

Expand Down
58 changes: 54 additions & 4 deletions source/NVDAObjects/UIA/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# Copyright (C) 2018-2021 NV Access Limited, Leonard de Ruijter

from typing import Optional, Tuple
from comtypes import COMError
import winVersion
import UIAHandler
import _UIAHandler
import _UIAConstants
Expand All @@ -16,6 +18,9 @@
from _UIACustomProps import (
CustomPropertyInfo,
)
from _UIACustomAnnotations import (
CustomAnnotationTypeInfo,
)
from comtypes import GUID
from scriptHandler import script
import ui
Expand Down Expand Up @@ -90,10 +95,35 @@ def __init__(self):
)


class ExcelCustomAnnotationTypes:
""" UIA 'custom annotation types' specific to Excel.
Once registered, all subsequent registrations will return the same ID value.
This class should be used as a singleton via ExcelCustomAnnotationTypes.get()
to prevent unnecessary work by repeatedly interacting with UIA.
"""
#: Singleton instance
_instance: "Optional[ExcelCustomAnnotationTypes]" = None

@classmethod
def get(cls) -> "ExcelCustomAnnotationTypes":
"""Get the singleton instance or initialise it.
"""
if cls._instance is None:
cls._instance = cls()
return cls._instance

def __init__(self):
self.note = CustomAnnotationTypeInfo(
guid=GUID("{4E863D9A-F502-4A67-808F-9E711702D05E}"),
)


class ExcelObject(UIA):
"""Common base class for all Excel UIA objects
"""
_UIAExcelCustomProps = ExcelCustomProperties.get()
_UIAExcelCustomAnnotationTypes = ExcelCustomAnnotationTypes.get()



class ExcelCell(ExcelObject):
Expand Down Expand Up @@ -367,6 +397,15 @@ def _get_states(self):
states.add(controlTypes.State.HASFORMULA)
if self._getUIACacheablePropertyValue(self._UIAExcelCustomProps.hasDataValidationDropdown.id):
states.add(controlTypes.State.HASPOPUP)
if winVersion.getWinVer() >= winVersion.WIN11:
try:
annotationTypes = self._getUIACacheablePropertyValue(UIAHandler.UIA_AnnotationTypesPropertyId)
except COMError:
# annotationTypes cannot be fetched on older Operating Systems such as Windows 7.
annotationTypes = None
if annotationTypes:
if self._UIAExcelCustomAnnotationTypes.note.id in annotationTypes:
states.add(controlTypes.State.HASNOTE)
return states

def _get_cellCoordsText(self):
Expand Down Expand Up @@ -421,28 +460,39 @@ def _get_cellCoordsText(self):
description=_("Reports the note or comment thread on the current cell"),
gesture="kb:NVDA+alt+c")
def script_reportComment(self, gesture):
if winVersion.getWinVer() >= winVersion.WIN11:
noteElement = self.UIAAnnotationObjects.get(self._UIAExcelCustomAnnotationTypes.note.id)
if noteElement:
name = noteElement.CurrentName
desc = noteElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId)
# Translators: a note on a cell in Microsoft excel.
text = _("{name}: {desc}").format(name=name, desc=desc)
ui.message(text)
else:
# Translators: message when a cell in Excel contains no note
ui.message(_("No note on this cell"))
commentsElement = self.UIAAnnotationObjects.get(UIAHandler.AnnotationType_Comment)
if commentsElement:
comment = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_FullDescriptionPropertyId)
author = commentsElement.GetCurrentPropertyValue(UIAHandler.UIA_AnnotationAuthorPropertyId)
numReplies = commentsElement.GetCurrentPropertyValue(self._UIAExcelCustomProps.commentReplyCount.id)
if numReplies == 0:
# Translators: a comment on a cell in Microsoft excel.
text = _("{comment} by {author}").format(
text = _("Comment thread: {comment} by {author}").format(
comment=comment,
author=author
)
else:
# Translators: a comment on a cell in Microsoft excel.
text = _("{comment} by {author} with {numReplies} replies").format(
text = _("Comment thread: {comment} by {author} with {numReplies} replies").format(
comment=comment,
author=author,
numReplies=numReplies
)
ui.message(text)
else:
# Translators: A message in Excel when there is no note
ui.message(_("No note or comment thread on this cell"))
# Translators: A message in Excel when there is no comment thread
ui.message(_("No comment thread on this cell"))


class ExcelWorksheet(ExcelObject):
Expand Down
80 changes: 80 additions & 0 deletions source/_UIACustomAnnotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2021 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from dataclasses import (
dataclass,
field,
)
from typing import Optional

from comtypes import (
GUID,
byref,
)
import winVersion


"""
This module provides helpers and a common format to define UIA custom annotation types.
The common custom annotation types are defined here.
Custom annotation types specific to an application should be defined within a NVDAObjects/UIA
submodule specific to that application, E.G. 'NVDAObjects/UIA/excel.py'
UIA originally had hard coded 'static' ID's for annotation types.
For an example see 'AnnotationType_SpellingError' in
`source/comInterfaces/_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py`
imported via `UIAutomationClient.py`.
When a new annotation type was added the UIA spec had to be updated.
Now a mechanism is in place to allow applications to register "custom annotation types".
This relies on both the UIA server application and the UIA client application sharing a known
GUID for the annotation type.
"""


@dataclass
class CustomAnnotationTypeInfo:
"""Holds information about a CustomAnnotationType
This makes it easy to define custom annotation types to be loaded.
"""
guid: GUID
id: int = field(init=False)

def __post_init__(self) -> None:
""" The id field must be initialised at runtime.
A GUID uniquely identifies a custom annotation, but the UIA system relies on integer IDs.
Any application (clients or providers) can register a custom annotation type, subsequent applications
will get the same id for a given GUID.
Registering custom annotations is only supported on Windows 11 and above.
For any lesser version, id will be 0.
"""
if winVersion.getWinVer() >= winVersion.WIN11:
import NVDAHelper
self.id = NVDAHelper.localLib.registerUIAAnnotationType(
byref(self.guid),
)
else:
self.id = 0


class CustomAnnotationTypesCommon:
"""UIA 'custom annotation types' common to all applications.
Once registered, all subsequent registrations will return the same ID value.
This class should be used as a singleton via CustomAnnotationTypesCommon.get()
to prevent unnecessary work by repeatedly interacting with UIA.
"""
#: Singleton instance
_instance: "Optional[CustomAnnotationTypesCommon]" = None

@classmethod
def get(cls) -> "CustomAnnotationTypesCommon":
"""Get the singleton instance or initialise it.
"""
if cls._instance is None:
cls._instance = cls()
return cls._instance

def __init__(self):
pass
3 changes: 3 additions & 0 deletions source/controlTypes/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def negativeDisplayString(self) -> str:
OVERFLOWING = 0x10000000000
UNLOCKED = 0x20000000000
HAS_ARIA_DETAILS = 0x40000000000
HASNOTE = 0x80000000000


STATES_SORTED = frozenset([State.SORTED, State.SORTED_ASCENDING, State.SORTED_DESCENDING])
Expand Down Expand Up @@ -160,6 +161,8 @@ def negativeDisplayString(self) -> str:
# Translators: a state that denotes that the object is unlocked (such as an unlocked cell in a protected
# Excel spreadsheet).
State.UNLOCKED: _("unlocked"),
# Translators: a state that denotes the existance of a note.
State.HASNOTE: _("has note"),
}


Expand Down

0 comments on commit c72e5e7

Please sign in to comment.