Skip to content

Commit

Permalink
Changed the strecture of the code, added the ability to wrap in nvda …
Browse files Browse the repository at this point in the history
…find, and added a setting to turn off the beeps caused by wrapping.
  • Loading branch information
hamadatrichine committed Nov 19, 2021
1 parent 2c917c0 commit 4fa67f6
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 97 deletions.
124 changes: 27 additions & 97 deletions addon/globalPlugins/screenWrapping/__init__.py
Original file line number Diff line number Diff line change
@@ -1,109 +1,45 @@
# screenWrapping
# Copyright 2018 Hamada Trichine, released under GPLv2.

import api
import addonHandler
import config
import textInfos
import gui
import cursorManager
from globalPluginHandler import GlobalPlugin
from browseMode import BrowseModeTreeInterceptor
from core import callLater
from inputCore import SCRCAT_BROWSEMODE
from ui import message
from tones import beep
from speech import cancelSpeech, speak
from scriptHandler import script, willSayAllResume
from scriptHandler import script
from . import wrappingFind, wrappingQuickNav, wrappingAlerts, wrappingSettingsGui

addonHandler.initTranslation()

confspec = {"isActive":"boolean(default=True)"}
confspec = {"isActive":"boolean(default=True)", "turnOnBeeps":"boolean(default=True)"}
config.conf.spec["screenWrapping"] = confspec

oldQuickNav = BrowseModeTreeInterceptor._quickNavScript

# Text to translate
# Translators: Text spoken when screen wrapping to top.
msgwrpTop = _("wrapping to top")
# Translators: Text spoken when screen wrapping to bottom.
msgwrpBottom = _("wrapping to bottom")

def getCurrentPos(treeInterceptor):
posDelta = treeInterceptor.makeTextInfo(textInfos.POSITION_FIRST)
posDelta.setEndPoint(treeInterceptor.makeTextInfo(textInfos.POSITION_CARET), "endToStart")
return len(posDelta.text)

def resetPosition(treeInterceptor, offset, errorMessage):
cursor = treeInterceptor.makeTextInfo(textInfos.POSITION_FIRST)
cursor.move(textInfos.UNIT_CHARACTER, offset)
if hasattr(treeInterceptor, "selection"):
treeInterceptor.selection = cursor
else:
cursor.updateCaret()
cancelSpeech()
cursor.move(textInfos.UNIT_LINE,1,endPoint="end")
message(errorMessage)

def updatePosition(treeInterceptor, position):
cursor = treeInterceptor.makeTextInfo(position)
cursor.updateCaret()
cancelSpeech()

def initNavItemsGenerator(treeInterceptor, itemType):
if itemType=="notLinkBlock":
return treeInterceptor._iterNotLinkBlock
else:
return lambda direction, info: treeInterceptor._iterNodesByType(itemType, direction, info)

def screenWrapping(treeInterceptor, itemType, readUnit, msg, wrp2, tone=(500,80), reverse="previous", direction="next"):
updatePosition(treeInterceptor, wrp2)
navItems = initNavItemsGenerator(treeInterceptor, itemType)
try:
wrapping = next(navItems(direction, treeInterceptor.selection))
speak([msg])
wrapping.moveTo()
try:
wrapping = next(navItems(reverse, treeInterceptor.selection))
callLater(300,wrapping.moveTo)
except StopIteration:
pass
beep(tone[0],tone[1])
wrapping.report(readUnit=readUnit)
return True
except StopIteration:
pass

def quickNavWrapping(treeInterceptor, gesture, itemType, direction, errorMessage, readUnit):
iterFactory = initNavItemsGenerator(treeInterceptor, itemType)
try:
item = next(iterFactory(direction, treeInterceptor.selection))
except NotImplementedError:
# Translators: a message when a particular quick nav command is not supported in the current document.
message(_("Not supported in this document"))
return
except StopIteration:
if direction == "next":
lastPos = getCurrentPos(treeInterceptor)
if not screenWrapping(treeInterceptor, itemType, readUnit, msgwrpTop, wrp2=textInfos.POSITION_FIRST):
resetPosition(treeInterceptor, lastPos, errorMessage)
else:
lastPos = getCurrentPos(treeInterceptor)
if not screenWrapping(treeInterceptor, itemType, readUnit, msgwrpBottom, wrp2=textInfos.POSITION_LAST, tone=(100,80), reverse="next", direction="previous"):
resetPosition(treeInterceptor, lastPos, errorMessage)
return

item.moveTo()
if not gesture or not willSayAllResume(gesture):
item.report(readUnit=readUnit)


class GlobalPlugin(GlobalPlugin):
def __init__(self):
super(GlobalPlugin, self).__init__()
global oldQuickNav
gui.settingsDialogs.NVDASettingsDialog.categoryClasses.append(wrappingSettingsGui.ScreenWrappingSettings)
self.oldQuickNav = BrowseModeTreeInterceptor._quickNavScript
self.oldFindDialog = cursorManager.FindDialog
self.oldFindBackend = cursorManager.CursorManager.doFindText
self.isActivated = config.conf["screenWrapping"]["isActive"]
if self.isActivated:
BrowseModeTreeInterceptor._quickNavScript = quickNavWrapping
self.patch()

def patch(self):
BrowseModeTreeInterceptor._quickNavScript = wrappingQuickNav.quickNavWrapping
cursorManager.CursorManager.doFindText = wrappingFind.customDoFindText
setattr(cursorManager.CursorManager, '_wrapFind', True)
cursorManager.FindDialog = wrappingFind.CustomFindDialog

def clean(self):
BrowseModeTreeInterceptor._quickNavScript = self.oldQuickNav
cursorManager.CursorManager.doFindText = self.oldFindBackend
cursorManager.FindDialog = self.oldFindDialog
setattr(cursorManager.CursorManager, '_wrapFind', None)

@script(
# Translators: A discription for the toggleScreenWrapping script.
Expand All @@ -113,22 +49,16 @@ def __init__(self):
)
def script_toggleScreenWrapping(self, gesture):
if self.isActivated:
BrowseModeTreeInterceptor._quickNavScript = oldQuickNav
self.clean()
config.conf["screenWrapping"]["isActive"] = False
self.isActivated = config.conf["screenWrapping"]["isActive"]
message(
# Translators: Text spoken when screen rapping is turned off.
_("Screen wrapping off."))
beep(100,150)
else:
BrowseModeTreeInterceptor._quickNavScript = quickNavWrapping
self.patch()
config.conf["screenWrapping"]["isActive"] = True
self.isActivated = config.conf["screenWrapping"]["isActive"]
message(
# Translators: Text spoken when screen rapping is turned on.
_("Screen wrapping on."))
beep(400,150)

def terminate(self):
BrowseModeTreeInterceptor._quickNavScript = oldQuickNav
wrappingAlerts.alertToggleFunctionality(not self.isActivated)

def terminate(self):
self.clean()
gui.settingsDialogs.NVDASettingsDialog.categoryClasses.remove(wrappingSettingsGui.ScreenWrappingSettings)
38 changes: 38 additions & 0 deletions addon/globalPlugins/screenWrapping/wrappingAlerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# screenWrapping
# Copyright 2018 Hamada Trichine, released under GPLv2.

import config
from speech import cancelSpeech
from ui import message
from tones import beep


DIRECTION_NEXT = "next"
DIRECTION_PREV = "previous"

def playBeep(hz, length):
if config.conf["screenWrapping"]["turnOnBeeps"]:
beep(hz, length)

def alertWrp(direction):
cancelSpeech()
if direction == DIRECTION_PREV:
# Translators: Text spoken when screen wrapping to bottom.
message(_("wrapping to bottom"))
playBeep(100, 80)
else:
# Translators: Text spoken when screen wrapping to top.
message(_("wrapping to top"))
playBeep(500, 80)

def alertToggleFunctionality(state):
if state:
message(
# Translators: Text spoken when screen rapping is turned off.
_("Screen wrapping off."))
playBeep(100,150)
else:
message(
# Translators: Text spoken when screen rapping is turned on.
_("Screen wrapping on."))
playBeep(400,150)
82 changes: 82 additions & 0 deletions addon/globalPlugins/screenWrapping/wrappingFind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# screenWrapping
# Copyright 2018 Hamada Trichine, released under GPLv2.

import wx
import textInfos
import speech
import controlTypes
from core import callLater
from cursorManager import CursorManager
from gui import guiHelper, messageBox
from gui.contextHelp import ContextHelpMixin
from . import wrappingAlerts


class CustomFindDialog(ContextHelpMixin, wx.Dialog, ):
helpId = "SearchingForText"
def __init__(self, parent, cursorManager, text, isCaseSensitive, reverse=False):
# Translators: the title of the find dialog
super().__init__(parent=parent, title=_("Find"))
self.currentCursorManager = cursorManager
self.reverse = reverse

dialogSizer = wx.BoxSizer(wx.VERTICAL)
helperSizer = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL)
# Translators: a label for the edit text box where you type your search query
editTextLabel = _("Type the text you wish to find")
self.findTextField = helperSizer.addLabeledControl(editTextLabel, wx.TextCtrl, value=text)
self.caseSensitiveCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("Case &sensitive"))
self.caseSensitiveCheckBox.SetValue(isCaseSensitive)
# Translators: An option in the find dialog to allow wrapping on search.
self.wrapAroundCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("&Wrap around"))
self.wrapAroundCheckBox.SetValue(self.currentCursorManager._wrapFind)
helperSizer.addItem(self.caseSensitiveCheckBox)
helperSizer.addItem(self.wrapAroundCheckBox)
helperSizer.addDialogDismissButtons(self.CreateButtonSizer(wx.OK|wx.CANCEL))
dialogSizer.Add(helperSizer.sizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
dialogSizer.Fit(self)

self.SetSizer(dialogSizer)
self.CentreOnScreen()
self.SetFocus()
self.findTextField.SetFocus()

self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK)
self.Bind(wx.EVT_BUTTON, self.onCancel, id=wx.ID_CANCEL)

def onOk(self, event):
searchQuery = self.findTextField.GetValue()
isCaseSensitive = self.caseSensitiveCheckBox.GetValue()
CursorManager._wrapFind = self.wrapAroundCheckBox.GetValue()
callLater(100, self.currentCursorManager.doFindText, searchQuery, caseSensitive=isCaseSensitive, reverse=self.reverse)
self.Destroy()

def onCancel(self, event):
self.Destroy()


def speakFindResult(currentCursorManager, textInfoRange, result, willSayAllResume):
currentCursorManager.selection = textInfoRange
textInfoRange.move(textInfos.UNIT_LINE,1,endPoint="end")
if not willSayAllResume:
speech.speakTextInfo(textInfoRange, reason=controlTypes.OutputReason.CARET)

def customDoFindText(cursorManagerInst, text, reverse=False, caseSensitive=False, willSayAllResume=False):
if not text:
return
textInfoRange = cursorManagerInst.makeTextInfo(textInfos.POSITION_CARET)
result = textInfoRange.find(text, reverse=reverse, caseSensitive=caseSensitive)
if result:
speech.cancelSpeech()
speakFindResult(cursorManagerInst, textInfoRange, result, willSayAllResume)
else:
textInfoRange=cursorManagerInst.makeTextInfo(textInfos.POSITION_LAST if reverse else textInfos.POSITION_FIRST)
result = textInfoRange.find(text,reverse=reverse,caseSensitive=caseSensitive)
if not cursorManagerInst._wrapFind or not result:
wx.CallAfter(messageBox, _('text "%s" not found') % text, _("Find Error"), wx.OK|wx.ICON_ERROR)
else:
wrappingAlerts.alertWrp(wrappingAlerts.DIRECTION_NEXT if not reverse else wrappingAlerts.DIRECTION_PREV)
speakFindResult(cursorManagerInst, textInfoRange, result, willSayAllResume)

CursorManager._lastFindText = text
CursorManager._lastCaseSensitivity = caseSensitive
78 changes: 78 additions & 0 deletions addon/globalPlugins/screenWrapping/wrappingQuickNav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# screenWrapping
# Copyright 2018 Hamada Trichine, released under GPLv2.

import textInfos
from core import callLater
from ui import message
from scriptHandler import willSayAllResume
from speech import cancelSpeech
from tones import beep
from . import wrappingAlerts


def getCurrentPos(treeInterceptor):
posDelta = treeInterceptor.makeTextInfo(textInfos.POSITION_FIRST)
posDelta.setEndPoint(treeInterceptor.makeTextInfo(textInfos.POSITION_CARET), "endToStart")
return len(posDelta.text)

def resetPosition(treeInterceptor, offset, errorMessage):
cursor = treeInterceptor.makeTextInfo(textInfos.POSITION_FIRST)
cursor.move(textInfos.UNIT_CHARACTER, offset)
if hasattr(treeInterceptor, "selection"):
treeInterceptor.selection = cursor
else:
cursor.updateCaret()
cancelSpeech()
cursor.move(textInfos.UNIT_LINE,1,endPoint="end")
message(errorMessage)

def updatePosition(treeInterceptor, position):
cursor = treeInterceptor.makeTextInfo(position)
cursor.updateCaret()
cancelSpeech()

def initNavItemsGenerator(treeInterceptor, itemType):
if itemType=="notLinkBlock":
return treeInterceptor._iterNotLinkBlock
else:
return lambda direction, info: treeInterceptor._iterNodesByType(itemType, direction, info)

def screenWrapping(treeInterceptor, itemType, readUnit, wrp2, reverse="previous", direction="next"):
updatePosition(treeInterceptor, wrp2)
navItems = initNavItemsGenerator(treeInterceptor, itemType)
try:
wrapping = next(navItems(direction, treeInterceptor.selection))
wrappingAlerts.alertWrp(direction)
wrapping.moveTo()
try:
wrapping = next(navItems(reverse, treeInterceptor.selection))
callLater(300,wrapping.moveTo)
except StopIteration:
pass
wrapping.report(readUnit=readUnit)
return True
except StopIteration:
pass

def quickNavWrapping(treeInterceptor, gesture, itemType, direction, errorMessage, readUnit):
iterFactory = initNavItemsGenerator(treeInterceptor, itemType)
try:
item = next(iterFactory(direction, treeInterceptor.selection))
except NotImplementedError:
# Translators: a message when a particular quick nav command is not supported in the current document.
message(_("Not supported in this document"))
return
except StopIteration:
if direction == "next":
lastPos = getCurrentPos(treeInterceptor)
if not screenWrapping(treeInterceptor, itemType, readUnit, wrp2=textInfos.POSITION_FIRST):
resetPosition(treeInterceptor, lastPos, errorMessage)
else:
lastPos = getCurrentPos(treeInterceptor)
if not screenWrapping(treeInterceptor, itemType, readUnit, wrp2=textInfos.POSITION_LAST, reverse="next", direction="previous"):
resetPosition(treeInterceptor, lastPos, errorMessage)
return

item.moveTo()
if not gesture or not willSayAllResume(gesture):
item.report(readUnit=readUnit)
20 changes: 20 additions & 0 deletions addon/globalPlugins/screenWrapping/wrappingSettingsGui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# screenWrapping
# Copyright 2018 Hamada Trichine, released under GPLv2.

import wx, gui, config

class ScreenWrappingSettings(gui.SettingsPanel):
# Translators: The name of the addon.
title = _("Screen Wrapping")

def makeSettings(self, sizer):
self.sizer = gui.guiHelper.BoxSizerHelper(self, sizer=sizer)
# Translators: Text for the check box in the settings that turns on or off the beeps when wrapping.
self.beepsCheckBox = self.sizer.addItem(wx.CheckBox(self, id=wx.ID_ANY, label=_("&Play a beep when wrapping")))
self.beepsCheckBox.SetValue(config.conf["screenWrapping"]["turnOnBeeps"])

def onSave(self):
config.conf["screenWrapping"]["turnOnBeeps"] = self.beepsCheckBox.GetValue()

def onDiscard(self):
return

0 comments on commit 4fa67f6

Please sign in to comment.