Skip to content

Commit 4015f1c

Browse files
authored
Use static ids in Poedit 3.5+ (#16889)
Related to vslavik/poedit#850 Summary of the issue: The Poedit appmodule relies on some complex calculation logic to announce several pieces of information with shortcuts, e.g. translator notes/comments/warnings.
1 parent 4d1a43c commit 4015f1c

3 files changed

Lines changed: 66 additions & 112 deletions

File tree

‎source/appModules/poedit.py‎

Lines changed: 62 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# A part of NonVisual Desktop Access (NVDA)
22
# This file is covered by the GNU General Public License.
33
# See the file COPYING for more details.
4-
# Copyright (C) 2012-2023 Mesar Hameed, NV Access Limited, Leonard de Ruijter, Rui Fontes, Cyrille Bougot
4+
# Copyright (C) 2012-2024 Mesar Hameed, NV Access Limited, Leonard de Ruijter, Rui Fontes, Cyrille Bougot
55

66
"""App module for Poedit 3.4+."""
77

88
from enum import IntEnum
99

10-
import ctypes
1110
import api
1211
import appModuleHandler
1312
import controlTypes
@@ -19,7 +18,6 @@
1918
from NVDAObjects import NVDAObject
2019
from NVDAObjects.window import Window
2120
from scriptHandler import getLastScriptRepeatCount, script
22-
from logHandler import log
2321

2422

2523
LEFT_TO_RIGHT_EMBEDDING = "\u202a"
@@ -29,31 +27,35 @@
2927
SCRCAT_POEDIT = _("Poedit")
3028

3129

32-
class _WindowControlIdOffsetFromDataView(IntEnum):
33-
"""Window control ID's are not static, however, the order of ids stays the same.
34-
Therefore, using a wxDataView control in the translations list as a reference,
35-
we can safely calculate control ids accross releases or instances.
36-
This class contains window control id offsets relative to the wxDataView window.
37-
"""
30+
class _WindowControlId(IntEnum):
31+
"""Static window control ID's as defined in poedit src/static_ids.h."""
3832

39-
PRO_IDENTIFIER = -10 # This is a button in the free version
40-
MAIN_SPLITTER_IDENTIFIER = -2 # The splitter that holds the translation list
41-
TRANSLATION_WARNING = 17
42-
NEEDS_WORK_SWITCH = 21
33+
NEEDS_WORK_SWITCH = 10101
34+
"""The "Needs work" toggle in editing area at the bottom"""
4335

36+
TRANSLATION_ISSUE_TEXT = 10102
37+
"""
38+
The error or warning line above translation field
39+
(hidden when there's no issue; ID is of the static text child window with issue's text)
40+
"""
4441

45-
class _WindowControlIdOffsetFromSidebar(IntEnum):
46-
"""Window control ID's are not static, however, the order of ids stays the same.
47-
Therefore, using the Sidebar window as a reference,
48-
we can safely calculate control ids accross releases or instances.
49-
This class contains window control id offsets relative to the Sidebar window.
50-
Note that this Sidebar window itself is found relative to the dataview's ancestor splitter control.
42+
PREVIOUS_SOURCE_TEXT = 10103
43+
"""
44+
Text of previous source text
45+
(msgid) for current item (shown in sidebar, may be hidden, is static control with the text)
5146
"""
5247

53-
PRO_OFFSET = -5
54-
OLD_SOURCE_TEXT = 36
55-
TRANSLATOR_NOTES = 39
56-
COMMENT = 42
48+
NOTES_FOR_TRANSLATOR = 10104
49+
"""
50+
Text of notes for translators (extracted from source code) for current item
51+
(shown in sidebar, may be hidden, is static control with the text)
52+
"""
53+
54+
TRANSLATOR_COMMENT = 10105
55+
"""
56+
Text of translator's comment for current item
57+
(shown in sidebar, may be hidden, is static control with the text)
58+
"""
5759

5860

5961
def _findDescendantObject(
@@ -80,68 +82,19 @@ def _findDescendantObject(
8082
class AppModule(appModuleHandler.AppModule):
8183
cachePropertiesByDefault = True
8284

83-
_dataViewControlId: int | None
84-
"""Type definition for auto prop '_get__dataViewControlId'"""
85-
86-
def _get__dataViewControlId(self) -> int | None:
87-
fg = api.getForegroundObject()
88-
dataView = _findDescendantObject(fg.windowHandle, className="wxDataView")
89-
if not dataView:
90-
return None
91-
return dataView.windowControlID
92-
93-
_sidebarControlId: int | None
94-
"""Type definition for auto prop '_get__sidebarControlId'"""
95-
96-
def _get__sidebarControlId(self) -> int | None:
97-
dataViewControlId = self._dataViewControlId
98-
splitterControlID = dataViewControlId + _WindowControlIdOffsetFromDataView.MAIN_SPLITTER_IDENTIFIER
99-
fg = api.getForegroundObject()
100-
splitterHwnd = windowUtils.findDescendantWindow(fg.windowHandle, controlID=splitterControlID)
101-
sidebarHwnd = winUser.getWindow(splitterHwnd, winUser.GW_HWNDNEXT)
102-
while sidebarHwnd and not ctypes.windll.user32.IsWindowVisible(sidebarHwnd):
103-
sidebarHwnd = winUser.getWindow(sidebarHwnd, winUser.GW_HWNDNEXT)
104-
if not sidebarHwnd:
105-
return None
106-
return winUser.getControlID(sidebarHwnd)
107-
108-
_isPro: bool
109-
"""Type definition for auto prop '_get__isPro'"""
110-
111-
def _get__isPro(self) -> bool:
112-
"""Returns whether this instance of Poedit is a pro version."""
113-
obj = self._getNVDAObjectForWindowControlIdOffsetFromDataView(
114-
_WindowControlIdOffsetFromDataView.PRO_IDENTIFIER,
115-
)
116-
return obj is None
117-
118-
def _getNVDAObjectForWindowControlIdOffsetFromDataView(
119-
self,
120-
windowControlIdOffset: _WindowControlIdOffsetFromDataView,
121-
) -> Window | None:
122-
fg = api.getForegroundObject()
123-
return _findDescendantObject(fg.windowHandle, self._dataViewControlId + windowControlIdOffset)
124-
125-
def _getNVDAObjectForWindowControlIdOffsetFromSidebar(
85+
def _getNVDAObjectForWindowControlId(
12686
self,
127-
windowControlIdOffset: _WindowControlIdOffsetFromSidebar,
87+
windowControlId: _WindowControlId,
12888
) -> Window | None:
12989
fg = api.getForegroundObject()
130-
sidebarControlId = self._sidebarControlId
131-
if sidebarControlId is None:
132-
log.error("Sidebar can not be found")
133-
return None
134-
extraOffset = 0
135-
if self._isPro:
136-
extraOffset = _WindowControlIdOffsetFromSidebar.PRO_OFFSET
137-
return _findDescendantObject(fg.windowHandle, sidebarControlId + extraOffset + windowControlIdOffset)
90+
return _findDescendantObject(fg.windowHandle, windowControlId)
13891

13992
_translatorNotesObj: Window | None
14093
"""Type definition for auto prop '_get__translatorNotesObj'"""
14194

14295
def _get__translatorNotesObj(self) -> Window | None:
143-
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
144-
_WindowControlIdOffsetFromSidebar.TRANSLATOR_NOTES,
96+
return self._getNVDAObjectForWindowControlId(
97+
_WindowControlId.NOTES_FOR_TRANSLATOR,
14598
)
14699

147100
def _reportControlScriptHelper(self, obj: Window, description: str):
@@ -191,9 +144,7 @@ def script_reportAutoCommentsWindow(self, gesture):
191144
"""Type definition for auto prop '_get__commentObj'"""
192145

193146
def _get__commentObj(self) -> Window | None:
194-
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
195-
_WindowControlIdOffsetFromSidebar.COMMENT,
196-
)
147+
return self._getNVDAObjectForWindowControlId(_WindowControlId.TRANSLATOR_COMMENT)
197148

198149
@script(
199150
description=pgettext(
@@ -214,64 +165,64 @@ def script_reportCommentsWindow(self, gesture):
214165
pgettext("poedit", "comment"),
215166
)
216167

217-
_oldSourceTextObj: Window | None
218-
"""Type definition for auto prop '_get__oldSourceTextObj'"""
168+
_previousSourceTextObj: Window | None
169+
"""Type definition for auto prop '_get__previousSourceTextObj'"""
219170

220-
def _get__oldSourceTextObj(self) -> Window | None:
221-
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
222-
_WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT,
171+
def _get__previousSourceTextObj(self) -> Window | None:
172+
return self._getNVDAObjectForWindowControlId(
173+
_WindowControlId.PREVIOUS_SOURCE_TEXT,
223174
)
224175

225176
@script(
226177
description=pgettext(
227178
"poedit",
228179
# Translators: The description of an NVDA command for Poedit.
229-
"Reports the old source text, if any. If pressed twice, presents the text in browse mode",
180+
"Reports the previous source text, if any. If pressed twice, presents the text in browse mode",
230181
),
231182
gesture="kb:control+shift+o",
232183
category=SCRCAT_POEDIT,
233184
speakOnDemand=True,
234185
)
235186
def script_reportOldSourceText(self, gesture):
236187
self._reportControlScriptHelper(
237-
self._oldSourceTextObj,
238-
# Translators: The description of the "old source text" window in poedit.
188+
self._previousSourceTextObj,
189+
# Translators: The description of the "previous source text" window in poedit.
239190
# This text is reported when the given window contains no item to report or could not be found.
240-
pgettext("poedit", "old source text"),
191+
pgettext("poedit", "previous source text"),
241192
)
242193

243-
_translationWarningObj: Window | None
244-
"""Type definition for auto prop '_get__translationWarningObj'"""
194+
_translationIssueObj: Window | None
195+
"""Type definition for auto prop '_get__translationIssueObj'"""
245196

246-
def _get__translationWarningObj(self) -> Window | None:
247-
return self._getNVDAObjectForWindowControlIdOffsetFromDataView(
248-
_WindowControlIdOffsetFromDataView.TRANSLATION_WARNING,
197+
def _get__translationIssueObj(self) -> Window | None:
198+
return self._getNVDAObjectForWindowControlId(
199+
_WindowControlId.TRANSLATION_ISSUE_TEXT,
249200
)
250201

251202
@script(
252203
description=pgettext(
253204
"poedit",
254205
# Translators: The description of an NVDA command for Poedit.
255-
"Reports a translation warning, if any. If pressed twice, presents the warning in browse mode",
206+
"Reports a translation issue, if any. If pressed twice, presents the warning in browse mode",
256207
),
257208
gesture="kb:control+shift+w",
258209
category=SCRCAT_POEDIT,
259210
speakOnDemand=True,
260211
)
261212
def script_reportTranslationWarning(self, gesture):
262213
self._reportControlScriptHelper(
263-
self._translationWarningObj,
264-
# Translators: The description of the "translation warning" window in poedit.
214+
self._translationIssueObj,
215+
# Translators: The description of the "translation issue" window in poedit.
265216
# This text is reported when the given window contains no item to report or could not be found.
266-
pgettext("poedit", "translation warning"),
217+
pgettext("poedit", "translation issue"),
267218
)
268219

269220
_needsWorkObj: Window | None
270221
"""Type definition for auto prop '_get__needsWorkObj'"""
271222

272223
def _get__needsWorkObj(self) -> Window | None:
273-
obj = self._getNVDAObjectForWindowControlIdOffsetFromDataView(
274-
_WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH,
224+
obj = self._getNVDAObjectForWindowControlId(
225+
_WindowControlId.NEEDS_WORK_SWITCH,
275226
)
276227
if obj and obj.role == controlTypes.Role.CHECKBOX:
277228
return obj
@@ -301,19 +252,19 @@ def _get_name(self) -> str:
301252

302253

303254
class PoeditListItem(NVDAObject):
304-
_warningControlToReport: _WindowControlIdOffsetFromDataView | None
255+
_warningControlToReport: _WindowControlId | None
305256
appModule: AppModule
306257

307258
def _get__warningControlToReport(self) -> int | None:
308-
obj = self.appModule._needsWorkObj
309-
if obj and controlTypes.State.CHECKED in obj.states:
310-
return _WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH
311-
obj = self.appModule._oldSourceTextObj
259+
obj = self.appModule._previousSourceTextObj
312260
if obj and not obj.hasIrrelevantLocation:
313-
return _WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT
314-
obj = self.appModule._translationWarningObj
261+
return _WindowControlId.PREVIOUS_SOURCE_TEXT
262+
obj = self.appModule._translationIssueObj
315263
if obj and obj.parent and obj.parent.parent and not obj.parent.parent.hasIrrelevantLocation:
316-
return _WindowControlIdOffsetFromDataView.TRANSLATION_WARNING
264+
return _WindowControlId.TRANSLATION_ISSUE_TEXT
265+
obj = self.appModule._needsWorkObj
266+
if obj and controlTypes.State.CHECKED in obj.states:
267+
return _WindowControlId.NEEDS_WORK_SWITCH
317268
return None
318269

319270
def _get_name(self):
@@ -332,9 +283,9 @@ def reportFocus(self):
332283
tones.beep(440, 50)
333284
return
334285
match self._warningControlToReport:
335-
case _WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT:
286+
case _WindowControlId.PREVIOUS_SOURCE_TEXT:
336287
tones.beep(495, 50)
337-
case _WindowControlIdOffsetFromDataView.TRANSLATION_WARNING:
288+
case _WindowControlId.TRANSLATION_ISSUE_TEXT:
338289
tones.beep(550, 50)
339-
case _WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH:
290+
case _WindowControlId.NEEDS_WORK_SWITCH:
340291
tones.beep(660, 50)

‎user_docs/en/changes.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Cursor routing reliability has been improved, and support for routing keys in Po
1818
All lines of cells will now be used when using a multi-line braille display via HID braille.
1919
NVDA is no longer unstable after restarting NVDA during an automatic Braille Bluetooth scan.
2020

21+
The minimum required version of Poedit that works with NVDA is now version 3.5.
22+
2123
eSpeak NG has been updated, adding support for the Faroese and Xextan languages.
2224

2325
There have also been a number of fixes, including to mouse tracking in Firefox, and the on-demand speech mode.
@@ -43,6 +45,7 @@ There have also been a number of fixes, including to mouse tracking in Firefox,
4345
* eSpeak NG has been updated to 1.52-dev commit `961454ff`. (#16775)
4446
* Added new languages Faroese and Xextan.
4547
* When using a multi-line braille display via the standard HID braille driver, all lines of cells will be used. (#16993, @alexmoon)
48+
* The stability of NVDA's Poedit support has been improved with the side effect that the minimum required version of Poedit is now version 3.5. (#16889, @LeonarddeR)
4649

4750
### Bug Fixes
4851

‎user_docs/en/userGuide.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1610,7 +1610,7 @@ Note: The above shortcuts work only with the default formatting string for fooba
16101610

16111611
### Poedit {#Poedit}
16121612

1613-
NVDA offers enhanced support for Poedit 3.4 or newer.
1613+
NVDA offers enhanced support for Poedit 3.5 or newer.
16141614

16151615
<!-- KC:beginInclude -->
16161616

0 commit comments

Comments
 (0)