diff --git a/addon/globalPlugins/emoticons/__init__.py b/addon/globalPlugins/emoticons/__init__.py index 5160939..206fc53 100644 --- a/addon/globalPlugins/emoticons/__init__.py +++ b/addon/globalPlugins/emoticons/__init__.py @@ -10,7 +10,6 @@ import globalVars import config import api -import speechDictHandler import ui import characterProcessing import languageHandler @@ -30,6 +29,7 @@ from .smileysList import emoticons from .skipTranslation import translate +from .speechDictCompat import REGEXP_ENTRY_TYPE, SpeechDict, SpeechDictEntry, SpeechDictionaryBridge addonHandler.initTranslation() @@ -49,9 +49,10 @@ config.conf.spec["emoticons"] = confspec -defaultDic = speechDictHandler.SpeechDict() -noEmojisDic = speechDictHandler.SpeechDict() -sD = speechDictHandler.SpeechDict() +defaultDic = SpeechDict() +noEmojisDic = SpeechDict() +sD = SpeechDict() +_announcementDictionary = SpeechDictionaryBridge(sD) profileName = oldProfileName = None @@ -76,13 +77,11 @@ def loadDic(): def activateAnnouncement(): - speechDictHandler.dictionaries["temp"].extend(sD) + _announcementDictionary.activate() def deactivateAnnouncement(): - for entry in sD: - if entry in speechDictHandler.dictionaries["temp"]: - speechDictHandler.dictionaries["temp"].remove(entry) + _announcementDictionary.deactivate() @disableInSecureMode @@ -104,33 +103,31 @@ def __init__(self): super(GlobalPlugin, self).__init__() for em in emoticons: if em.isEmoji: - # Translators: A prefix to each emoticon name, added to the temporary speech dictionary, - # visible in temporary speech dictionary dialog when the addon is active, to explain an entry. + # Translators: A prefix to each emoticon name in the Emoticons dictionary. emType = _("Emoji") else: - # Translators: A prefix to each emoticon name, added to the temporary speech dictionary, - # visible in temporary speech dictionary dialog when the addon is active, to explain an entry. + # Translators: A prefix to each emoticon name in the Emoticons dictionary. emType = _("Emoticon") comment = "{type}: {name}".format(type=emType, name=em.name) otherReplacement = " %s; " % em.name # Case and reg are always True defaultDic.append( - speechDictHandler.SpeechDictEntry( + SpeechDictEntry( em.pattern, otherReplacement, comment, True, - speechDictHandler.ENTRY_TYPE_REGEXP, + REGEXP_ENTRY_TYPE, ), ) if not em.isEmoji: noEmojisDic.append( - speechDictHandler.SpeechDictEntry( + SpeechDictEntry( em.pattern, otherReplacement, comment, True, - speechDictHandler.ENTRY_TYPE_REGEXP, + REGEXP_ENTRY_TYPE, ), ) global profileName, oldProfileName @@ -551,7 +548,7 @@ def makeSettings(self, settingsSizer): def OnResetClick(self, evt): self.dictList.DeleteAllItems() self.tempSpeechDict = [] - self.dic = speechDictHandler.SpeechDict() + self.dic = SpeechDict() if config.conf["emoticons"]["speakAddonEmojis"]: self.dic = defaultDic else: @@ -559,7 +556,7 @@ def OnResetClick(self, evt): self.tempSpeechDict.extend(self.dic) for entry in self.dic: self.dictList.Append( - (entry.comment, entry.pattern, entry.replacement, True, speechDictHandler.ENTRY_TYPE_REGEXP), + (entry.comment, entry.pattern, entry.replacement, True, REGEXP_ENTRY_TYPE), ) self.dictList.SetFocus() diff --git a/addon/globalPlugins/emoticons/speechDictCompat.py b/addon/globalPlugins/emoticons/speechDictCompat.py new file mode 100644 index 0000000..ee9376a --- /dev/null +++ b/addon/globalPlugins/emoticons/speechDictCompat.py @@ -0,0 +1,83 @@ +# -*- coding: UTF-8 -*- +# Copyright (C) 2026 Noelia Ruiz Martínez, Mesar Hameed, Francisco Javier Estrada Martínez +# Released under GPL 2 + +import globalVars +import speechDictHandler +from logHandler import log + +try: + from speech.extensions import filter_speechSequence as _speechSequenceFilter +except ImportError: + _speechSequenceFilter = None + +try: + from speechDictHandler.types import EntryType, SpeechDict, SpeechDictEntry +except ImportError: + # NVDA 2026.1 and earlier expose these types directly from speechDictHandler. + SpeechDict = speechDictHandler.SpeechDict + SpeechDictEntry = speechDictHandler.SpeechDictEntry + REGEXP_ENTRY_TYPE = speechDictHandler.ENTRY_TYPE_REGEXP +else: + REGEXP_ENTRY_TYPE = EntryType.REGEXP + +__all__ = ( + "REGEXP_ENTRY_TYPE", + "SpeechDict", + "SpeechDictEntry", + "SpeechDictionaryBridge", +) + + +class SpeechDictionaryBridge: + """Activate an add-on dictionary through the best API available in NVDA.""" + + def __init__(self, speechDictionary): + self._speechDictionary = speechDictionary + self._active = False + self._filterHandler = self._filterSpeechSequence + self._legacyTempDictionary = None + self._legacyEntries = () + + def _filterSpeechSequence(self, speechSequence): + if not globalVars.speechDictionaryProcessing: + return speechSequence + return [ + self._speechDictionary.sub(item) if isinstance(item, str) else item for item in speechSequence + ] + + def activate(self): + if self._active: + return + try: + if _speechSequenceFilter is not None: + _speechSequenceFilter.register(self._filterHandler) + else: + # Compatibility with NVDA versions predating the public speech filter. + tempDictionary = speechDictHandler.dictionaries["temp"] + self._legacyEntries = tuple(self._speechDictionary) + tempDictionary.extend(self._legacyEntries) + self._legacyTempDictionary = tempDictionary + except Exception: + log.exception("Unable to activate the Emoticons speech dictionary") + return + self._active = True + + def deactivate(self): + if not self._active: + return + try: + if _speechSequenceFilter is not None: + _speechSequenceFilter.unregister(self._filterHandler) + elif self._legacyTempDictionary is not None: + for entry in self._legacyEntries: + for index, tempEntry in enumerate(self._legacyTempDictionary): + if tempEntry is entry: + del self._legacyTempDictionary[index] + break + except Exception: + log.exception("Unable to deactivate the Emoticons speech dictionary") + finally: + self._active = False + self._legacyTempDictionary = None + self._legacyEntries = ()