Skip to content
Closed
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
33 changes: 15 additions & 18 deletions addon/globalPlugins/emoticons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import globalVars
import config
import api
import speechDictHandler
import ui
import characterProcessing
import languageHandler
Expand All @@ -30,6 +29,7 @@

from .smileysList import emoticons
from .skipTranslation import translate
from .speechDictCompat import REGEXP_ENTRY_TYPE, SpeechDict, SpeechDictEntry, SpeechDictionaryBridge

addonHandler.initTranslation()

Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -551,15 +548,15 @@ 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:
self.dic = noEmojisDic
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()

Expand Down
83 changes: 83 additions & 0 deletions addon/globalPlugins/emoticons/speechDictCompat.py
Original file line number Diff line number Diff line change
@@ -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 = ()
Loading