Skip to content

Commit af18109

Browse files
authored
Merge 0574a35 into c32e0e7
2 parents c32e0e7 + 0574a35 commit af18109

8 files changed

Lines changed: 93 additions & 43 deletions

File tree

source/NVDAObjects/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import (
1414
Dict,
1515
Optional,
16+
TYPE_CHECKING,
1617
)
1718
import weakref
1819
import textUtils
@@ -45,6 +46,9 @@
4546
import aria
4647
from winAPI.sessionTracking import isLockScreenModeActive
4748

49+
if TYPE_CHECKING:
50+
from textInfos import _LinkData
51+
4852

4953
class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo):
5054
"""A default TextInfo which is used to enable text review of information about widgets that don't support text content.
@@ -1642,3 +1646,7 @@ def _get_linkType(self) -> controlTypes.State | None:
16421646
if not isinstance(ti, BrowseModeDocumentTreeInterceptor):
16431647
return None
16441648
return ti.getLinkTypeInDocument(self.value)
1649+
1650+
def _get_linkData(self) -> "_LinkData | None":
1651+
"""If the object has an associated link, returns the link's data (target and text)."""
1652+
raise NotImplementedError

source/NVDAObjects/window/excel.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import config
3030
from config.configFlags import ReportCellBorders
3131
import textInfos
32+
from textInfos import _LinkData
3233
import colors
3334
import eventHandler
3435
import api
@@ -1368,21 +1369,6 @@ def _getFormatFieldAndOffsets(self, offset, formatConfig, calculateOffsets=True)
13681369
def _get_locationText(self):
13691370
return self.obj.getCellPosition()
13701371

1371-
def _getLinkDataAtCaretPosition(self) -> textInfos._Link | None:
1372-
links = self.obj.excelCellObject.Hyperlinks
1373-
if links.count == 0:
1374-
return None
1375-
link = links(1)
1376-
if link.Type == MsoHyperlink.RANGE:
1377-
text = link.TextToDisplay
1378-
else:
1379-
log.debugWarning(f"No text to display for link type {link.Type}")
1380-
text = None
1381-
return textInfos._Link(
1382-
displayText=text,
1383-
destination=link.Address,
1384-
)
1385-
13861372

13871373
NVCELLINFOFLAG_ADDRESS = 0x1
13881374
NVCELLINFOFLAG_TEXT = 0x2
@@ -1710,6 +1696,21 @@ def _get_role(self):
17101696
return controlTypes.Role.LINK
17111697
return controlTypes.Role.TABLECELL
17121698

1699+
def _get_linkData(self) -> _LinkData | None:
1700+
links = self.excelCellObject.Hyperlinks
1701+
if links.count == 0:
1702+
return None
1703+
link = links(1)
1704+
if link.Type == MsoHyperlink.RANGE:
1705+
text = link.TextToDisplay
1706+
else:
1707+
log.debugWarning(f"No text to display for link type {link.Type}")
1708+
text = None
1709+
return _LinkData(
1710+
displayText=text,
1711+
destination=link.Address,
1712+
)
1713+
17131714
TextInfo = ExcelCellTextInfo
17141715

17151716
def _isEqual(self, other):

source/NVDAObjects/window/winword.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from enum import IntEnum
4747
import documentBase
4848
from utils.displayString import DisplayStringIntEnum
49+
from textInfos import _LinkData
4950

5051
if TYPE_CHECKING:
5152
import inputCore
@@ -866,7 +867,7 @@ def _getShapeAtCaretPosition(self) -> comtypes.client.lazybind.Dispatch | None:
866867
return shapes[1]
867868
return None
868869

869-
def _getLinkDataAtCaretPosition(self) -> textInfos._Link | None:
870+
def _getLinkDataAtCaretPosition(self) -> _LinkData | None:
870871
link = self._getLinkAtCaretPosition()
871872
if not link:
872873
return None
@@ -879,7 +880,7 @@ def _getLinkDataAtCaretPosition(self) -> textInfos._Link | None:
879880
case _:
880881
log.debugWarning(f"No text to display for link type {link.Type}")
881882
text = None
882-
return textInfos._Link(
883+
return _LinkData(
883884
displayText=text,
884885
destination=link.Address,
885886
)

source/appModules/powerpnt.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from treeInterceptorHandler import DocumentTreeInterceptor
2828
from NVDAObjects import NVDAObjectTextInfo
2929
from displayModel import DisplayModelTextInfo, EditableTextDisplayModelTextInfo
30+
import textInfos
3031
import textInfos.offsets
3132
import eventHandler
3233
import appModuleHandler
@@ -41,6 +42,7 @@
4142
import scriptHandler
4243
from locationHelper import RectLTRB
4344
from NVDAObjects.window._msOfficeChart import OfficeChart
45+
from textInfos import _LinkData
4446

4547
# Translators: The name of a category of NVDA commands.
4648
SCRCAT_POWERPOINT = _("PowerPoint")
@@ -994,6 +996,19 @@ def _get_mathMl(self):
994996
except: # noqa: E722
995997
raise LookupError("Couldn't get MathML from MathType")
996998

999+
def _get_linkData(self) -> _LinkData | None:
1000+
mouseClickSetting = self.ppObject.ActionSettings(ppMouseClick)
1001+
if mouseClickSetting.action == ppActionHyperlink:
1002+
if self.value:
1003+
text = f"{self.roleText} {self.value}"
1004+
else:
1005+
text = self.roleText
1006+
return _LinkData(
1007+
displayText=text,
1008+
destination=mouseClickSetting.Hyperlink.Address,
1009+
)
1010+
return None
1011+
9971012
__gestures = {
9981013
"kb:leftArrow": "moveHorizontal",
9991014
"kb:rightArrow": "moveHorizontal",
@@ -1160,6 +1175,23 @@ def _getBoundingRectFromOffset(self, offset: int) -> RectLTRB:
11601175
bottom = self.obj.documentWindow.ppObjectModel.pointsToScreenPixelsY(rangeTop + rangeHeight)
11611176
return RectLTRB(left, top, right, bottom)
11621177

1178+
def _getCurrentRun(
1179+
self,
1180+
offset: int,
1181+
) -> tuple[comtypes.client.lazybind.Dispatch | None, int, int]:
1182+
runs = self.obj.ppObject.textRange.runs()
1183+
for run in runs:
1184+
start = run.start - 1
1185+
end = start + run.length
1186+
if start <= offset < end:
1187+
startOffset = start
1188+
endOffset = end
1189+
curRun = run
1190+
break
1191+
else:
1192+
curRun, startOffset, endOffset = None, 0, 0
1193+
return curRun, startOffset, endOffset
1194+
11631195
def _getFormatFieldAndOffsets(
11641196
self,
11651197
offset: int,
@@ -1169,15 +1201,7 @@ def _getFormatFieldAndOffsets(
11691201
formatField = textInfos.FormatField()
11701202
curRun = None
11711203
if calculateOffsets:
1172-
runs = self.obj.ppObject.textRange.runs()
1173-
for run in runs:
1174-
start = run.start - 1
1175-
end = start + run.length
1176-
if start <= offset < end:
1177-
startOffset = start
1178-
endOffset = end
1179-
curRun = run
1180-
break
1204+
curRun, startOffset, endOffset = _getCurrentRun(self)
11811205
if not curRun:
11821206
curRun = self.obj.ppObject.textRange.characters(offset + 1)
11831207
startOffset, endOffset = offset, self._endOffset
@@ -1213,6 +1237,17 @@ def _getFormatFieldAndOffsets(
12131237
formatField["link"] = True
12141238
return formatField, (startOffset, endOffset)
12151239

1240+
def _getLinkDataAtCaretPosition(self) -> _LinkData | None:
1241+
offset = self._getCaretOffset()
1242+
curRun, _startOffset, _endOffset = self._getCurrentRun(offset)
1243+
mouseClickSetting = curRun.actionSettings(ppMouseClick)
1244+
if mouseClickSetting.action == ppActionHyperlink:
1245+
return textInfos._LinkData(
1246+
displayText=mouseClickSetting.Hyperlink.TextToDisplay,
1247+
destination=mouseClickSetting.Hyperlink.Address,
1248+
)
1249+
return None
1250+
12161251
def _setCaretOffset(self, offset: int) -> None:
12171252
return self._setSelectionOffsets(offset, offset)
12181253

source/globalCommands.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4280,16 +4280,19 @@ def script_reportLinkDestination(
42804280
positioned on a link, or an element with an included link such as a graphic.
42814281
:param forceBrowseable: skips the press once check, and displays the browseableMessage version.
42824282
"""
4283+
focus = api.getFocusObject()
42834284
try:
42844285
ti: textInfos.TextInfo = api.getCaretPosition()
4286+
link = ti._getLinkDataAtCaretPosition()
42854287
except RuntimeError:
4286-
log.debugWarning("Unable to get the caret position.", exc_info=True)
4287-
ti: textInfos.TextInfo = api.getFocusObject().makeTextInfo(textInfos.POSITION_FIRST)
4288-
link = ti._getLinkDataAtCaretPosition()
4288+
try:
4289+
link = focus.linkData
4290+
except NotImplementedError:
4291+
link = None
42894292
presses = scriptHandler.getLastScriptRepeatCount()
42904293
if link:
42914294
if link.destination is None:
4292-
# Translators: Informs the user that the link has no destination
4295+
# Translators: Reported when using the command to report the destination of a link.
42934296
ui.message(_("Link has no apparent destination"))
42944297
return
42954298
if (
@@ -4304,18 +4307,19 @@ def script_reportLinkDestination(
43044307
link.destination,
43054308
# Translators: Informs the user that the window contains the destination of the
43064309
# link with given title
4307-
title=_("Destination of: {name}").format(
4308-
name=text,
4309-
closeButton=True,
4310-
copyButton=True,
4311-
),
4310+
title=_("Destination of: {name}").format(name=text),
4311+
closeButton=True,
4312+
copyButton=True,
43124313
)
43134314
elif presses == 0: # One press
43144315
ui.message(link.destination) # Speak the link
43154316
else: # Some other number of presses
43164317
return # Do nothing
4318+
elif focus.role == controlTypes.Role.LINK or controlTypes.State.LINKED in focus.states:
4319+
# Translators: Reported when using the command to report the destination of a link.
4320+
ui.message(_("Unable to get the destination of this link."))
43174321
else:
4318-
# Translators: Tell user that the command has been run on something that is not a link
4322+
# Translators: Reported when using the command to report the destination of a link.
43194323
ui.message(_("Not a link."))
43204324

43214325
@script(

source/textInfos/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ def _logBadSequenceTypes(sequence: SpeechSequence, shouldRaise: bool = True):
340340

341341

342342
@dataclass
343-
class _Link:
344-
"""Class to store information on a link in text."""
343+
class _LinkData:
344+
"""Class to store information on a link."""
345345

346346
displayText: str | None
347347
destination: str
@@ -713,7 +713,7 @@ def activate(self):
713713
mouseHandler.doPrimaryClick()
714714
winUser.setCursorPos(oldX, oldY)
715715

716-
def _getLinkDataAtCaretPosition(self) -> _Link | None:
716+
def _getLinkDataAtCaretPosition(self) -> _LinkData | None:
717717
self.expand(UNIT_CHARACTER)
718718
obj: NVDAObjects.NVDAObject = self.NVDAObjectAtStart
719719
if obj.role == controlTypes.role.Role.GRAPHIC and (
@@ -726,7 +726,7 @@ def _getLinkDataAtCaretPosition(self) -> _Link | None:
726726
obj.role == controlTypes.role.Role.LINK # If it's a link, or
727727
or controlTypes.state.State.LINKED in obj.states # if it isn't a link but contains one
728728
):
729-
return _Link(
729+
return _LinkData(
730730
displayText=obj.name,
731731
destination=obj.value,
732732
)
@@ -783,7 +783,7 @@ def moveToCodepointOffset(
783783
exactly 1 character.
784784
A good illustration of this is in Microsoft Word with UIA enabled always,
785785
the first character of a bullet list item would be represented by three pythonic codepoint characters:
786-
* Bullet character ""
786+
* Bullet character "•"
787787
* Tab character \t
788788
* And the first character of of list item per se.
789789

source/treeInterceptorHandler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
if TYPE_CHECKING:
2626
import NVDAObjects
27+
from textInfos import _LinkData
2728

2829
post_browseModeStateChange = extensionPoints.Action()
2930
"""
@@ -228,7 +229,7 @@ def find(self, text, caseSensitive=False, reverse=False):
228229
def activate(self):
229230
return self.innerTextInfo.activate()
230231

231-
def _getLinkDataAtCaretPosition(self) -> textInfos._Link | None:
232+
def _getLinkDataAtCaretPosition(self) -> "_LinkData | None":
232233
return self.innerTextInfo._getLinkDataAtCaretPosition()
233234

234235
def compareEndPoints(self, other, which):

user_docs/en/changes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Specifically, MathML inside of span and other elements that have the attribute `
6363
* When spelling, unicode normalization now works more appropriately:
6464
* After reporting a normalized character, NVDA no longer incorrectly reports subsequent characters as normalized. (#17286, @LeonarddeR)
6565
* Composite characters (such as é) are now reported correctly. (#17295, @LeonarddeR)
66-
* The command to Report the destination URL of a link now works as expected when using the legacy object model in Microsoft Word, Outlook and Excel. (#17292, #17362, @CyrilleB79)
66+
* The command to Report the destination URL of a link now works as expected when using the legacy object model in Microsoft Word, Outlook, Excel and PowerPoint. (#17292, #17362, #17435, @CyrilleB79)
6767
* NVDA will no longer announce Windows 11 clipboard history entries when closing the window while items are present. (#17308, @josephsl)
6868
* If the plugins are reloaded while a browseable message is opened, NVDA will no longer fail to report subsequent focus moves. (#17323, @CyrilleB79)
6969
* When using applications such as Skype, Discord, Signal and Phone Link for audio communication, NVDA speech and sounds no longer decrease in volume. (#17349, @jcsteh)

0 commit comments

Comments
 (0)