Skip to content

Commit 8d767bd

Browse files
Merge fff2ee3 into f392c23
2 parents f392c23 + fff2ee3 commit 8d767bd

4 files changed

Lines changed: 168 additions & 11 deletions

File tree

devDocs/developerGuide.t2t

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,8 @@ You can navigate through the history of previously entered lines using the up an
824824

825825
Output (responses from the interpreter) will be spoken when enter is pressed.
826826
The f6 key toggles between the input and output controls.
827+
When on the output control, alt+up/down jumps to the previous/next result (add shift for selecting).
828+
Pressing control+l clears the output.
827829

828830
The result of the last executed command is stored in the "_" global variable.
829831
This shadows the gettext function which is stored as a built-in with the same name.

source/appModules/nvda.py

Lines changed: 145 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
1-
#appModules/nvda.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#Copyright (C) 2008-2017 NV Access Limited
4-
#This file is covered by the GNU General Public License.
5-
#See the file COPYING for more details.
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2008-2021 NV Access Limited, James Teh, Michael Curran, Leonard de Ruijter, Reef Turner,
3+
# Julien Cochuyt
4+
# This file may be used under the terms of the GNU General Public License, version 2 or later.
5+
# For more details see: https://www.gnu.org/licenses/gpl-2.0.html
6+
7+
8+
import typing
69

710
import appModuleHandler
811
import api
912
import controlTypes
1013
import versionInfo
1114
from NVDAObjects.IAccessible import IAccessible
15+
from baseObject import ScriptableObject
1216
import gui
17+
from scriptHandler import script
1318
import speech
19+
import textInfos
1420
import braille
1521
import config
1622
from logHandler import log
1723

24+
if typing.TYPE_CHECKING:
25+
import inputCore
26+
27+
1828
nvdaMenuIaIdentity = None
1929

2030
class NvdaDialog(IAccessible):
@@ -44,6 +54,118 @@ def _get_description(self):
4454
"""
4555
return ""
4656

57+
58+
# Translators: The name of a category of NVDA commands.
59+
SCRCAT_PYTHON_CONSOLE = _("Python Console")
60+
61+
62+
class NvdaPythonConsoleUIOutputClear(ScriptableObject):
63+
64+
# Allow the bound gestures to be edited through the Input Gestures dialog (see L{gui.prePopup})
65+
isPrevFocusOnNvdaPopup = True
66+
67+
@script(
68+
gesture="kb:control+l",
69+
# Translators: Description of a command to clear the Python Console output pane
70+
description=_("Clear the output pane"),
71+
category=SCRCAT_PYTHON_CONSOLE,
72+
)
73+
def script_clearOutput(self, gesture: "inputCore.InputGesture"):
74+
from pythonConsole import consoleUI
75+
consoleUI.clear()
76+
77+
78+
class NvdaPythonConsoleUIOutputCtrl(ScriptableObject):
79+
80+
# Allow the bound gestures to be edited through the Input Gestures dialog (see L{gui.prePopup})
81+
isPrevFocusOnNvdaPopup = True
82+
83+
@script(
84+
gesture="kb:alt+downArrow",
85+
# Translators: Description of a command to move to the next result in the Python Console output pane
86+
description=_("Move to the next result"),
87+
category=SCRCAT_PYTHON_CONSOLE
88+
)
89+
def script_moveToNextResult(self, gesture: "inputCore.InputGesture"):
90+
self._resultNavHelper(direction="next", select=False)
91+
92+
@script(
93+
gesture="kb:alt+upArrow",
94+
# Translators: Description of a command to move to the previous result
95+
# in the Python Console output pane
96+
description=_("Move to the previous result"),
97+
category=SCRCAT_PYTHON_CONSOLE
98+
)
99+
def script_moveToPrevResult(self, gesture: "inputCore.InputGesture"):
100+
self._resultNavHelper(direction="previous", select=False)
101+
102+
@script(
103+
gesture="kb:alt+downArrow+shift",
104+
# Translators: Description of a command to select from the current caret position to the end
105+
# of the current result in the Python Console output pane
106+
description=_("Select until the end of the current result"),
107+
category=SCRCAT_PYTHON_CONSOLE
108+
)
109+
def script_selectToResultEnd(self, gesture: "inputCore.InputGesture"):
110+
self._resultNavHelper(direction="next", select=True)
111+
112+
@script(
113+
gesture="kb:alt+shift+upArrow",
114+
# Translators: Description of a command to select from the current caret position to the start
115+
# of the current result in the Python Console output pane
116+
description=_("Select until the start of the current result"),
117+
category=SCRCAT_PYTHON_CONSOLE
118+
)
119+
def script_selectToResultStart(self, gesture: "inputCore.InputGesture"):
120+
self._resultNavHelper(direction="previous", select=True)
121+
122+
def _resultNavHelper(self, direction: str = "next", select: bool = False):
123+
from pythonConsole import consoleUI
124+
startPos, endPos = consoleUI.outputCtrl.GetSelection()
125+
if self.isTextSelectionAnchoredAtStart:
126+
curPos = endPos
127+
else:
128+
curPos = startPos
129+
if direction == "previous":
130+
for pos in reversed(consoleUI.outputPositions):
131+
if pos < curPos:
132+
break
133+
else:
134+
# Translators: Reported when attempting to move to the previous result in the Python Console
135+
# output pane while there is no previous result.
136+
speech.speakMessage(_("Top"))
137+
return
138+
elif direction == "next":
139+
for pos in consoleUI.outputPositions:
140+
if pos > curPos:
141+
break
142+
else:
143+
# Translators: Reported when attempting to move to the next result in the Python Console
144+
# output pane while there is no next result.
145+
speech.speakMessage(_("Bottom"))
146+
return
147+
else:
148+
raise ValueError(u"Unexpected direction: {!r}".format(direction))
149+
if select:
150+
consoleUI.outputCtrl.Freeze()
151+
anchorPos = startPos if self.isTextSelectionAnchoredAtStart else endPos
152+
consoleUI.outputCtrl.SetSelection(anchorPos, pos)
153+
consoleUI.outputCtrl.Thaw()
154+
self.detectPossibleSelectionChange()
155+
self.isTextSelectionAnchoredAtStart = anchorPos < pos
156+
else:
157+
consoleUI.outputCtrl.SetSelection(pos, pos)
158+
info = self.makeTextInfo(textInfos.POSITION_CARET)
159+
copy = info.copy()
160+
info.expand(textInfos.UNIT_LINE)
161+
if (
162+
copy.move(textInfos.UNIT_CHARACTER, 4, endPoint="end") == 4
163+
and copy.text == ">>> "
164+
):
165+
info.move(textInfos.UNIT_CHARACTER, 4, endPoint="start")
166+
speech.speakTextInfo(info, reason=controlTypes.OutputReason.CARET)
167+
168+
47169
class AppModule(appModuleHandler.AppModule):
48170
# The configuration profile that has been previously edited.
49171
# This ought to be a class property.
@@ -108,8 +230,26 @@ def isNvdaSettingsDialog(self, obj):
108230
return True
109231
return False
110232

233+
def isNvdaPythonConsoleUIInputCtrl(self, obj):
234+
from pythonConsole import consoleUI
235+
if not consoleUI:
236+
return
237+
return obj.windowHandle == consoleUI.inputCtrl.GetHandle()
238+
239+
def isNvdaPythonConsoleUIOutputCtrl(self, obj):
240+
from pythonConsole import consoleUI
241+
if not consoleUI:
242+
return
243+
return obj.windowHandle == consoleUI.outputCtrl.GetHandle()
244+
111245
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
112246
if obj.windowClassName == "#32770" and obj.role == controlTypes.ROLE_DIALOG:
113247
clsList.insert(0, NvdaDialog)
114248
if self.isNvdaSettingsDialog(obj):
115249
clsList.insert(0, NvdaDialogEmptyDescription)
250+
return
251+
if self.isNvdaPythonConsoleUIInputCtrl(obj):
252+
clsList.insert(0, NvdaPythonConsoleUIOutputClear)
253+
elif self.isNvdaPythonConsoleUIOutputCtrl(obj):
254+
clsList.insert(0, NvdaPythonConsoleUIOutputClear)
255+
clsList.insert(0, NvdaPythonConsoleUIOutputCtrl)

source/gui/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ def prePopup(self):
8585
"""
8686
nvdaPid = os.getpid()
8787
focus = api.getFocusObject()
88-
if focus.processID != nvdaPid:
88+
# Do not set prevFocus if the focus is on a control rendered by NVDA itself, such as the NVDA menu.
89+
# This allows to refer to the control that had focus before opening the menu while still using NVDA
90+
# on its own controls. The L{nvdaPid} check can be bypassed by setting the optional attribute
91+
# L{isPrevFocusOnNvdaPopup} to L{True} when a NVDA dialog offers customizable bound gestures,
92+
# eg. the NVDA Python Console.
93+
if focus.processID != nvdaPid or getattr(focus, "isPrevFocusOnNvdaPopup", False):
8994
self.prevFocus = focus
9095
self.prevFocusAncestors = api.getFocusAncestors()
9196
if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != nvdaPid:

source/pythonConsole.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#pythonConsole.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#This file is covered by the GNU General Public License.
4-
#See the file COPYING for more details.
5-
#Copyright (C) 2008-2019 NV Access Limited, Leonard de Ruijter
1+
# pythonConsole.py
2+
# A part of NonVisual Desktop Access (NVDA)
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
# Copyright (C) 2008-2020 NV Access Limited, Leonard de Ruijter, Julien Cochuyt
66

77
import watchdog
88

@@ -12,6 +12,7 @@
1212

1313
import builtins
1414
import os
15+
from typing import Sequence
1516
import code
1617
import codeop
1718
import sys
@@ -253,6 +254,7 @@ def __init__(self, parent):
253254
# Even the most recent line has a position in the history, so initialise with one blank line.
254255
self.inputHistory = [""]
255256
self.inputHistoryPos = 0
257+
self.outputPositions: Sequence[int] = [0]
256258

257259
def onActivate(self, evt):
258260
if evt.GetActive():
@@ -268,6 +270,12 @@ def output(self, data):
268270
if data and not data.isspace():
269271
queueHandler.queueFunction(queueHandler.eventQueue, speech.speakText, data)
270272

273+
def clear(self):
274+
"""Clear the output.
275+
"""
276+
self.outputCtrl.Clear()
277+
self.outputPositions[:] = [0]
278+
271279
def echo(self, data):
272280
self.outputCtrl.write(data)
273281

@@ -292,6 +300,8 @@ def execute(self):
292300
self.inputHistory.append("")
293301
self.inputHistoryPos = len(self.inputHistory) - 1
294302
self.inputCtrl.ChangeValue("")
303+
if self.console.prompt != "...":
304+
self.outputPositions.append(self.outputCtrl.GetInsertionPoint())
295305

296306
def historyMove(self, movement):
297307
newIndex = self.inputHistoryPos + movement

0 commit comments

Comments
 (0)