Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beta to master #17093

Merged
merged 1 commit into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 62 additions & 111 deletions source/appModules/poedit.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2012-2023 Mesar Hameed, NV Access Limited, Leonard de Ruijter, Rui Fontes, Cyrille Bougot
# Copyright (C) 2012-2024 Mesar Hameed, NV Access Limited, Leonard de Ruijter, Rui Fontes, Cyrille Bougot

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

from enum import IntEnum

import ctypes
import api
import appModuleHandler
import controlTypes
Expand All @@ -19,7 +18,6 @@
from NVDAObjects import NVDAObject
from NVDAObjects.window import Window
from scriptHandler import getLastScriptRepeatCount, script
from logHandler import log


LEFT_TO_RIGHT_EMBEDDING = "\u202a"
Expand All @@ -29,31 +27,35 @@
SCRCAT_POEDIT = _("Poedit")


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

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

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

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

PRO_OFFSET = -5
OLD_SOURCE_TEXT = 36
TRANSLATOR_NOTES = 39
COMMENT = 42
NOTES_FOR_TRANSLATOR = 10104
"""
Text of notes for translators (extracted from source code) for current item
(shown in sidebar, may be hidden, is static control with the text)
"""

TRANSLATOR_COMMENT = 10105
"""
Text of translator's comment for current item
(shown in sidebar, may be hidden, is static control with the text)
"""


def _findDescendantObject(
Expand All @@ -80,68 +82,19 @@ def _findDescendantObject(
class AppModule(appModuleHandler.AppModule):
cachePropertiesByDefault = True

_dataViewControlId: int | None
"""Type definition for auto prop '_get__dataViewControlId'"""

def _get__dataViewControlId(self) -> int | None:
fg = api.getForegroundObject()
dataView = _findDescendantObject(fg.windowHandle, className="wxDataView")
if not dataView:
return None
return dataView.windowControlID

_sidebarControlId: int | None
"""Type definition for auto prop '_get__sidebarControlId'"""

def _get__sidebarControlId(self) -> int | None:
dataViewControlId = self._dataViewControlId
splitterControlID = dataViewControlId + _WindowControlIdOffsetFromDataView.MAIN_SPLITTER_IDENTIFIER
fg = api.getForegroundObject()
splitterHwnd = windowUtils.findDescendantWindow(fg.windowHandle, controlID=splitterControlID)
sidebarHwnd = winUser.getWindow(splitterHwnd, winUser.GW_HWNDNEXT)
while sidebarHwnd and not ctypes.windll.user32.IsWindowVisible(sidebarHwnd):
sidebarHwnd = winUser.getWindow(sidebarHwnd, winUser.GW_HWNDNEXT)
if not sidebarHwnd:
return None
return winUser.getControlID(sidebarHwnd)

_isPro: bool
"""Type definition for auto prop '_get__isPro'"""

def _get__isPro(self) -> bool:
"""Returns whether this instance of Poedit is a pro version."""
obj = self._getNVDAObjectForWindowControlIdOffsetFromDataView(
_WindowControlIdOffsetFromDataView.PRO_IDENTIFIER,
)
return obj is None

def _getNVDAObjectForWindowControlIdOffsetFromDataView(
self,
windowControlIdOffset: _WindowControlIdOffsetFromDataView,
) -> Window | None:
fg = api.getForegroundObject()
return _findDescendantObject(fg.windowHandle, self._dataViewControlId + windowControlIdOffset)

def _getNVDAObjectForWindowControlIdOffsetFromSidebar(
def _getNVDAObjectForWindowControlId(
self,
windowControlIdOffset: _WindowControlIdOffsetFromSidebar,
windowControlId: _WindowControlId,
) -> Window | None:
fg = api.getForegroundObject()
sidebarControlId = self._sidebarControlId
if sidebarControlId is None:
log.error("Sidebar can not be found")
return None
extraOffset = 0
if self._isPro:
extraOffset = _WindowControlIdOffsetFromSidebar.PRO_OFFSET
return _findDescendantObject(fg.windowHandle, sidebarControlId + extraOffset + windowControlIdOffset)
return _findDescendantObject(fg.windowHandle, windowControlId)

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

def _get__translatorNotesObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
_WindowControlIdOffsetFromSidebar.TRANSLATOR_NOTES,
return self._getNVDAObjectForWindowControlId(
_WindowControlId.NOTES_FOR_TRANSLATOR,
)

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

def _get__commentObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
_WindowControlIdOffsetFromSidebar.COMMENT,
)
return self._getNVDAObjectForWindowControlId(_WindowControlId.TRANSLATOR_COMMENT)

@script(
description=pgettext(
Expand All @@ -214,64 +165,64 @@ def script_reportCommentsWindow(self, gesture):
pgettext("poedit", "comment"),
)

_oldSourceTextObj: Window | None
"""Type definition for auto prop '_get__oldSourceTextObj'"""
_previousSourceTextObj: Window | None
"""Type definition for auto prop '_get__previousSourceTextObj'"""

def _get__oldSourceTextObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlIdOffsetFromSidebar(
_WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT,
def _get__previousSourceTextObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlId(
_WindowControlId.PREVIOUS_SOURCE_TEXT,
)

@script(
description=pgettext(
"poedit",
# Translators: The description of an NVDA command for Poedit.
"Reports the old source text, if any. If pressed twice, presents the text in browse mode",
"Reports the previous source text, if any. If pressed twice, presents the text in browse mode",
),
gesture="kb:control+shift+o",
category=SCRCAT_POEDIT,
speakOnDemand=True,
)
def script_reportOldSourceText(self, gesture):
self._reportControlScriptHelper(
self._oldSourceTextObj,
# Translators: The description of the "old source text" window in poedit.
self._previousSourceTextObj,
# Translators: The description of the "previous source text" window in poedit.
# This text is reported when the given window contains no item to report or could not be found.
pgettext("poedit", "old source text"),
pgettext("poedit", "previous source text"),
)

_translationWarningObj: Window | None
"""Type definition for auto prop '_get__translationWarningObj'"""
_translationIssueObj: Window | None
"""Type definition for auto prop '_get__translationIssueObj'"""

def _get__translationWarningObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlIdOffsetFromDataView(
_WindowControlIdOffsetFromDataView.TRANSLATION_WARNING,
def _get__translationIssueObj(self) -> Window | None:
return self._getNVDAObjectForWindowControlId(
_WindowControlId.TRANSLATION_ISSUE_TEXT,
)

@script(
description=pgettext(
"poedit",
# Translators: The description of an NVDA command for Poedit.
"Reports a translation warning, if any. If pressed twice, presents the warning in browse mode",
"Reports a translation issue, if any. If pressed twice, presents the warning in browse mode",
),
gesture="kb:control+shift+w",
category=SCRCAT_POEDIT,
speakOnDemand=True,
)
def script_reportTranslationWarning(self, gesture):
self._reportControlScriptHelper(
self._translationWarningObj,
# Translators: The description of the "translation warning" window in poedit.
self._translationIssueObj,
# Translators: The description of the "translation issue" window in poedit.
# This text is reported when the given window contains no item to report or could not be found.
pgettext("poedit", "translation warning"),
pgettext("poedit", "translation issue"),
)

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

def _get__needsWorkObj(self) -> Window | None:
obj = self._getNVDAObjectForWindowControlIdOffsetFromDataView(
_WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH,
obj = self._getNVDAObjectForWindowControlId(
_WindowControlId.NEEDS_WORK_SWITCH,
)
if obj and obj.role == controlTypes.Role.CHECKBOX:
return obj
Expand Down Expand Up @@ -301,19 +252,19 @@ def _get_name(self) -> str:


class PoeditListItem(NVDAObject):
_warningControlToReport: _WindowControlIdOffsetFromDataView | None
_warningControlToReport: _WindowControlId | None
appModule: AppModule

def _get__warningControlToReport(self) -> int | None:
obj = self.appModule._needsWorkObj
if obj and controlTypes.State.CHECKED in obj.states:
return _WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH
obj = self.appModule._oldSourceTextObj
obj = self.appModule._previousSourceTextObj
if obj and not obj.hasIrrelevantLocation:
return _WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT
obj = self.appModule._translationWarningObj
return _WindowControlId.PREVIOUS_SOURCE_TEXT
obj = self.appModule._translationIssueObj
if obj and obj.parent and obj.parent.parent and not obj.parent.parent.hasIrrelevantLocation:
return _WindowControlIdOffsetFromDataView.TRANSLATION_WARNING
return _WindowControlId.TRANSLATION_ISSUE_TEXT
obj = self.appModule._needsWorkObj
if obj and controlTypes.State.CHECKED in obj.states:
return _WindowControlId.NEEDS_WORK_SWITCH
return None

def _get_name(self):
Expand All @@ -332,9 +283,9 @@ def reportFocus(self):
tones.beep(440, 50)
return
match self._warningControlToReport:
case _WindowControlIdOffsetFromSidebar.OLD_SOURCE_TEXT:
case _WindowControlId.PREVIOUS_SOURCE_TEXT:
tones.beep(495, 50)
case _WindowControlIdOffsetFromDataView.TRANSLATION_WARNING:
case _WindowControlId.TRANSLATION_ISSUE_TEXT:
tones.beep(550, 50)
case _WindowControlIdOffsetFromDataView.NEEDS_WORK_SWITCH:
case _WindowControlId.NEEDS_WORK_SWITCH:
tones.beep(660, 50)
3 changes: 3 additions & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Cursor routing reliability has been improved, and support for routing keys in Po
All lines of cells will now be used when using a multi-line braille display via HID braille.
NVDA is no longer unstable after restarting NVDA during an automatic Braille Bluetooth scan.

The minimum required version of Poedit that works with NVDA is now version 3.5.

eSpeak NG has been updated, adding support for the Faroese and Xextan languages.

There have also been a number of fixes, including to mouse tracking in Firefox, and the on-demand speech mode.
Expand All @@ -43,6 +45,7 @@ There have also been a number of fixes, including to mouse tracking in Firefox,
* eSpeak NG has been updated to 1.52-dev commit `961454ff`. (#16775)
* Added new languages Faroese and Xextan.
* When using a multi-line braille display via the standard HID braille driver, all lines of cells will be used. (#16993, @alexmoon)
* 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)

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion user_docs/en/userGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ Note: The above shortcuts work only with the default formatting string for fooba

### Poedit {#Poedit}

NVDA offers enhanced support for Poedit 3.4 or newer.
NVDA offers enhanced support for Poedit 3.5 or newer.

<!-- KC:beginInclude -->

Expand Down