psychopy 2024.1.1__py3-none-any.whl → 2024.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/__init__.py +2 -2
- psychopy/alerts/_alerts.py +6 -2
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -0
- psychopy/app/builder/dialogs/paramCtrls.py +4 -2
- psychopy/app/builder/localizedStrings.py +16 -4
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3548 -5422
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/plugin_manager/plugins.py +1 -1
- psychopy/app/preferencesDlg.py +2 -1
- psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +552 -550
- psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +572 -570
- psychopy/demos/coder/timing/timeByFrames.py +7 -1
- psychopy/experiment/components/_base.py +122 -0
- psychopy/experiment/components/aperture/__init__.py +6 -2
- psychopy/experiment/components/brush/__init__.py +3 -1
- psychopy/experiment/components/button/__init__.py +6 -2
- psychopy/experiment/components/buttonBox/__init__.py +2 -1
- psychopy/experiment/components/camera/__init__.py +13 -0
- psychopy/experiment/components/code/__init__.py +34 -1
- psychopy/experiment/components/form/formItems.xltx +0 -0
- psychopy/experiment/components/mouse/__init__.py +12 -0
- psychopy/experiment/components/polygon/__init__.py +14 -3
- psychopy/experiment/components/settings/__init__.py +4 -1
- psychopy/experiment/components/sound/__init__.py +1 -1
- psychopy/experiment/components/text/__init__.py +1 -1
- psychopy/experiment/params.py +17 -0
- psychopy/experiment/routines/_base.py +122 -0
- psychopy/experiment/routines/counterbalance/__init__.py +1 -1
- psychopy/hardware/button.py +4 -3
- psychopy/hardware/camera/__init__.py +6 -0
- psychopy/hardware/keyboard.py +8 -3
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +1 -1
- psychopy/localization/generateTranslationTemplate.py +45 -19
- psychopy/localization/messages.pot +5049 -3418
- psychopy/preferences/Darwin.spec +2 -0
- psychopy/preferences/FreeBSD.spec +2 -0
- psychopy/preferences/Linux.spec +2 -0
- psychopy/preferences/Windows.spec +2 -0
- psychopy/preferences/baseNoArch.spec +2 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/hints.py +118 -97
- psychopy/tests/test_experiment/test_components/__init__.py +1 -1
- psychopy/tests/test_experiment/test_components/{test_ButtonBox.py → test_ButtonBoxComponent.py} +9 -27
- psychopy/tests/test_experiment/test_components/{test_Code.py → test_CodeComponent.py} +8 -20
- psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
- psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
- psychopy/tests/test_experiment/test_components/{test_Mouse.py → test_MouseComponent.py} +44 -57
- psychopy/tests/test_experiment/test_components/{test_Polygon.py → test_PolygonComponent.py} +9 -25
- psychopy/tests/test_experiment/test_components/{test_ResourceManager.py → test_ResourceManagerComponent.py} +3 -13
- psychopy/tests/test_experiment/test_components/{test_Settings.py → test_SettingsComponent.py} +1 -3
- psychopy/tests/test_experiment/test_components/{test_Static.py → test_StaticComponent.py} +3 -12
- psychopy/tests/test_experiment/test_components/test_all_components.py +8 -66
- psychopy/tests/test_experiment/test_components/test_base_components.py +212 -125
- psychopy/tools/fontmanager.py +1 -1
- psychopy/tools/versionchooser.py +1 -1
- psychopy/visual/noise.py +9 -0
- psychopy/visual/radial.py +8 -0
- psychopy/visual/secondorder.py +9 -0
- psychopy/visual/textbox2/textbox2.py +19 -21
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/METADATA +1 -1
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/RECORD +95 -66
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/WHEEL +1 -1
- psychopy/tests/test_experiment/test_components/test_Image.py +0 -24
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,27 +6,9 @@ from pathlib import Path
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
8
|
from psychopy import experiment
|
|
9
|
+
from psychopy.experiment.loops import TrialHandler
|
|
9
10
|
from psychopy.experiment.components import BaseComponent
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _make_minimal_experiment(obj):
|
|
13
|
-
"""
|
|
14
|
-
Make a minimal experiment with just one routine containing just one component, of the same class as the current
|
|
15
|
-
component but with all default params.
|
|
16
|
-
"""
|
|
17
|
-
# Skip whole test if required attributes aren't present
|
|
18
|
-
if not hasattr(obj, "comp"):
|
|
19
|
-
pytest.skip()
|
|
20
|
-
# Make blank experiment
|
|
21
|
-
exp = experiment.Experiment()
|
|
22
|
-
rt = exp.addRoutine(routineName='TestRoutine')
|
|
23
|
-
exp.flow.addRoutine(rt, 0)
|
|
24
|
-
# Create instance of this component with all default params
|
|
25
|
-
compClass = type(obj.comp)
|
|
26
|
-
comp = compClass(exp=exp, parentName='TestRoutine', name=f"test{compClass.__name__}")
|
|
27
|
-
rt.append(comp)
|
|
28
|
-
# Return experiment, routine and component
|
|
29
|
-
return comp, rt, exp
|
|
11
|
+
from psychopy.experiment.exports import IndentingBuffer
|
|
30
12
|
|
|
31
13
|
|
|
32
14
|
def _find_global_resource_in_js_experiment(script, resource):
|
|
@@ -48,34 +30,228 @@ def _find_global_resource_in_js_experiment(script, resource):
|
|
|
48
30
|
return resource in resourcesStr
|
|
49
31
|
|
|
50
32
|
|
|
51
|
-
class
|
|
52
|
-
#
|
|
53
|
-
|
|
33
|
+
class BaseComponentTests:
|
|
34
|
+
# component class to test
|
|
35
|
+
comp = None
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
# --- Utility methods ---
|
|
38
|
+
def make_minimal_experiment(self):
|
|
39
|
+
"""
|
|
40
|
+
Make a minimal experiment with just one routine containing just one component, of the same class as the current component but with all default params.
|
|
41
|
+
"""
|
|
42
|
+
# make blank experiment
|
|
43
|
+
exp = experiment.Experiment()
|
|
44
|
+
# add a Routine
|
|
45
|
+
rt = exp.addRoutine(routineName='TestRoutine')
|
|
46
|
+
exp.flow.addRoutine(rt, 0)
|
|
47
|
+
# add a loop around the Routine
|
|
48
|
+
loop = TrialHandler(exp=exp, name="testLoop")
|
|
49
|
+
exp.flow.addLoop(loop, 0, -1)
|
|
50
|
+
# create instance of this test's Component with all default params
|
|
51
|
+
comp = self.comp(exp=exp, parentName='TestRoutine', name=f"test{self.comp.__name__}")
|
|
52
|
+
rt.append(comp)
|
|
53
|
+
# return experiment, Routine and Component
|
|
54
|
+
return comp, rt, exp
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(autouse=True)
|
|
57
|
+
def assert_comp_class(self):
|
|
58
|
+
"""
|
|
59
|
+
Make sure this test object has an associated Component class - and skip the test if not. This is run before each test by default.
|
|
60
|
+
"""
|
|
61
|
+
# skip whole test if there is no Component connected to test class
|
|
62
|
+
if self.comp is None:
|
|
59
63
|
pytest.skip()
|
|
60
|
-
#
|
|
64
|
+
# continue with the test as normal
|
|
65
|
+
yield
|
|
66
|
+
|
|
67
|
+
# --- Heritable tests ---
|
|
68
|
+
|
|
69
|
+
def test_icons(self):
|
|
70
|
+
"""
|
|
71
|
+
Check that Component has icons for each app theme and that these point to real files
|
|
72
|
+
"""
|
|
73
|
+
# pathify icon file path
|
|
61
74
|
icon = Path(self.comp.iconFile)
|
|
62
|
-
#
|
|
75
|
+
# get paths for each theme
|
|
63
76
|
files = [
|
|
64
77
|
icon.parent / "light" / icon.name,
|
|
65
78
|
icon.parent / "dark" / icon.name,
|
|
66
79
|
icon.parent / "classic" / icon.name,
|
|
67
80
|
]
|
|
68
|
-
#
|
|
81
|
+
# check that each path is a file
|
|
69
82
|
for file in files:
|
|
70
|
-
assert file.is_file()
|
|
83
|
+
assert file.is_file(), (
|
|
84
|
+
f"Could not find file: {file}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def test_indentation_consistency(self):
|
|
88
|
+
"""
|
|
89
|
+
No component should exit any of its write methods at a different indent level as it entered, as this would break subsequent components / routines.
|
|
90
|
+
"""
|
|
91
|
+
# make minimal experiment just for this test
|
|
92
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
93
|
+
# skip if component doesn't have a start/stop time
|
|
94
|
+
if "startVal" not in comp.params or "stopVal" not in comp.params:
|
|
95
|
+
pytest.skip()
|
|
96
|
+
# create a text buffer to write to
|
|
97
|
+
buff = IndentingBuffer(target="PsychoPy")
|
|
98
|
+
# template message for if test fails
|
|
99
|
+
errMsgTemplate = "Writing {} code for {} changes indent level by {} when start is `{}` and stop is `{}`."
|
|
100
|
+
# setup flow for writing
|
|
101
|
+
exp.flow.writeStartCode(buff)
|
|
102
|
+
# combinations of start/stop being set/unset to try
|
|
103
|
+
cases = [
|
|
104
|
+
{"startVal": "0", "stopVal": "1"},
|
|
105
|
+
{"startVal": "", "stopVal": "1"},
|
|
106
|
+
{"startVal": "0", "stopVal": ""},
|
|
107
|
+
{"startVal": "", "stopVal": ""},
|
|
108
|
+
]
|
|
109
|
+
for case in cases:
|
|
110
|
+
# update error message for this case
|
|
111
|
+
errMsg = errMsgTemplate.format(
|
|
112
|
+
"{}", type(comp).__name__, "{}", case['startVal'], case['stopVal']
|
|
113
|
+
)
|
|
114
|
+
# set start/stop types
|
|
115
|
+
comp.params["startType"].val = "time (s)"
|
|
116
|
+
comp.params["stopType"].val = "time (s)"
|
|
117
|
+
# set start/stop values
|
|
118
|
+
for param, val in case.items():
|
|
119
|
+
comp.params[param].val = val
|
|
120
|
+
# write init code
|
|
121
|
+
comp.writeInitCode(buff)
|
|
122
|
+
# check indent
|
|
123
|
+
assert buff.indentLevel == 0, errMsg.format(
|
|
124
|
+
"init", buff.indentLevel
|
|
125
|
+
)
|
|
126
|
+
# write routine start code
|
|
127
|
+
comp.writeRoutineStartCode(buff)
|
|
128
|
+
# check indent
|
|
129
|
+
assert buff.indentLevel == 0, errMsg.format(
|
|
130
|
+
"routine start", buff.indentLevel
|
|
131
|
+
)
|
|
132
|
+
# write each frame code
|
|
133
|
+
comp.writeFrameCode(buff)
|
|
134
|
+
# check indent
|
|
135
|
+
assert buff.indentLevel == 0, errMsg.format(
|
|
136
|
+
"each frame", buff.indentLevel
|
|
137
|
+
)
|
|
138
|
+
# write end routine code
|
|
139
|
+
comp.writeRoutineEndCode(buff)
|
|
140
|
+
# check indent
|
|
141
|
+
assert buff.indentLevel == 0, errMsg.format(
|
|
142
|
+
"routine end", buff.indentLevel
|
|
143
|
+
)
|
|
144
|
+
# write end experiment code
|
|
145
|
+
comp.writeExperimentEndCode(buff)
|
|
146
|
+
# check indent
|
|
147
|
+
assert buff.indentLevel == 0, errMsg.format(
|
|
148
|
+
"experiment end", buff.indentLevel
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def test_disabled_default_val(self):
|
|
152
|
+
"""
|
|
153
|
+
Test that components created with default params are not disabled
|
|
154
|
+
"""
|
|
155
|
+
# make minimal experiment just for this test
|
|
156
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
157
|
+
# check whether it can be disabled
|
|
158
|
+
assert 'disabled' in comp.params, (
|
|
159
|
+
f"{type(comp).__name__} does not have a 'disabled' attribute."
|
|
160
|
+
)
|
|
161
|
+
# check that disabled defaults to False
|
|
162
|
+
assert comp.params['disabled'].val is False, f"{type(comp).__name__} is defaulting to disabled."
|
|
163
|
+
|
|
164
|
+
def test_disabled_code_muting(self):
|
|
165
|
+
"""
|
|
166
|
+
Test that components are only written when enabled and targets match.
|
|
167
|
+
"""
|
|
168
|
+
# Code Component is never referenced by name, so skip it for this test
|
|
169
|
+
if self.comp.__name__ == "CodeComponent":
|
|
170
|
+
pytest.skip()
|
|
171
|
+
# Make minimal experiment just for this test
|
|
172
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
173
|
+
# Write experiment and check that component is written
|
|
174
|
+
pyScript = exp.writeScript(target="PsychoPy")
|
|
175
|
+
if "PsychoPy" in type(comp).targets:
|
|
176
|
+
assert comp.name in pyScript, (
|
|
177
|
+
f"{type(comp).__name__} not found in compiled Python script when enabled and PsychoPy in targets."
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
assert comp.name not in pyScript, (
|
|
181
|
+
f"{type(comp).__name__} found in compiled Python script when enabled but PsychoPy not in targets."
|
|
182
|
+
)
|
|
183
|
+
# ## disabled until js can compile without saving
|
|
184
|
+
# jsScript = exp.writeScript(target="PsychoJS")
|
|
185
|
+
# if "PsychoJS" in type(comp).targets:
|
|
186
|
+
# assert comp.name in jsScript, (
|
|
187
|
+
# f"{type(comp).__name__} not found in compiled Python script when enabled and PsychoJS in targets."
|
|
188
|
+
# )
|
|
189
|
+
# else:
|
|
190
|
+
# assert comp.name not in jsScript, (
|
|
191
|
+
# f"{type(comp).__name__} found in compiled Python script when enabled but PsychoJS not in targets."
|
|
192
|
+
# )
|
|
193
|
+
|
|
194
|
+
# disable component then do same tests but assert not present
|
|
195
|
+
comp.params['disabled'].val = True
|
|
196
|
+
|
|
197
|
+
pyScript = exp.writeScript(target="PsychoPy")
|
|
198
|
+
if "PsychoPy" in type(comp).targets:
|
|
199
|
+
assert comp.name not in pyScript, (
|
|
200
|
+
f"{type(comp).__name__} found in compiled Python script when disabled but PsychoPy in targets."
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
assert comp.name not in pyScript, (
|
|
204
|
+
f"{type(comp).__name__} found in compiled Python script when disabled and PsychoPy not in targets."
|
|
205
|
+
)
|
|
206
|
+
# ## disabled until js can compile without saving
|
|
207
|
+
# jsScript = exp.writeScript(target="PsychoJS")
|
|
208
|
+
# if "PsychoJS" in type(comp).targets:
|
|
209
|
+
# assert comp.name not in jsScript, (
|
|
210
|
+
# f"{type(comp).__name__} found in compiled Python script when disabled but PsychoJS in targets."
|
|
211
|
+
# )
|
|
212
|
+
# else:
|
|
213
|
+
# assert comp.name not in jsScript, (
|
|
214
|
+
# f"{type(comp).__name__} found in compiled Python script when disabled and PsychoJS not in targets."
|
|
215
|
+
# )
|
|
71
216
|
|
|
72
|
-
def
|
|
217
|
+
def test_disabled_components_stay_in_routine(self):
|
|
218
|
+
"""
|
|
219
|
+
Test that disabled components aren't removed from their routine when experiment is written.
|
|
220
|
+
"""
|
|
221
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
222
|
+
# Disable component
|
|
223
|
+
comp.params['disabled'].val = True
|
|
224
|
+
# Writing the script drops the component but, if working properly, only from a copy of the routine, not the
|
|
225
|
+
# original!
|
|
226
|
+
exp.writeScript()
|
|
227
|
+
|
|
228
|
+
assert comp in rt, f"Disabling {type(comp).name} appears to remove it from its routine on compile."
|
|
229
|
+
class _TestLibraryClassMixin:
|
|
230
|
+
# class in the PsychoPy libraries (visual, sound, hardware, etc.) corresponding to this component
|
|
231
|
+
libraryClass = None
|
|
232
|
+
|
|
233
|
+
# --- Utility methods ---
|
|
234
|
+
|
|
235
|
+
@pytest.fixture(autouse=True)
|
|
236
|
+
def assert_lib_class(self):
|
|
237
|
+
"""
|
|
238
|
+
Make sure this test object has an associated library class - and skip the test if not. This is run before each test by default.
|
|
239
|
+
"""
|
|
240
|
+
# skip whole test if there is no Component connected to test class
|
|
241
|
+
if self.libraryClass is None:
|
|
242
|
+
pytest.skip()
|
|
243
|
+
# continue with the test as normal
|
|
244
|
+
yield
|
|
245
|
+
|
|
246
|
+
# --- Heritable tests ---
|
|
247
|
+
|
|
248
|
+
def test_device_class_refs(self):
|
|
73
249
|
"""
|
|
74
250
|
Check that any references to device classes in this Routine object point to classes which
|
|
75
251
|
exist.
|
|
76
252
|
"""
|
|
77
253
|
# make minimal experiment just for this test
|
|
78
|
-
comp, rt, exp =
|
|
254
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
79
255
|
# skip test if this element doesn't point to any hardware class
|
|
80
256
|
if not hasattr(comp, "deviceClasses"):
|
|
81
257
|
pytest.skip()
|
|
@@ -91,13 +267,7 @@ class _TestBaseComponentsMixin:
|
|
|
91
267
|
|
|
92
268
|
def test_params_used(self):
|
|
93
269
|
# Make minimal experiment just for this test
|
|
94
|
-
comp, rt, exp =
|
|
95
|
-
# Skip if component shouldn't use all of its params
|
|
96
|
-
if type(comp).__name__ in ["SettingsComponent", "CodeComponent"]:
|
|
97
|
-
pytest.skip()
|
|
98
|
-
# Skip if component is deprecated
|
|
99
|
-
if type(comp).__name__ in ['RatingScaleComponent', 'PatchComponent']:
|
|
100
|
-
pytest.skip()
|
|
270
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
101
271
|
# Try with PsychoPy and PsychoJS
|
|
102
272
|
for target in ("PsychoPy", "PsychoJS"):
|
|
103
273
|
## Skip PsychoJS until can write script without saving
|
|
@@ -134,11 +304,8 @@ class _TestBaseComponentsMixin:
|
|
|
134
304
|
"""
|
|
135
305
|
Check that all params which are settable each frame/repeat have a set method in the corresponding class.
|
|
136
306
|
"""
|
|
137
|
-
# Skip if there's no corresponding library class
|
|
138
|
-
if self.libraryClass is None:
|
|
139
|
-
return
|
|
140
307
|
# Make minimal experiment just for this test
|
|
141
|
-
comp, rt, exp =
|
|
308
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
142
309
|
# Check each param
|
|
143
310
|
for paramName, param in comp.params.items():
|
|
144
311
|
if not param.direct:
|
|
@@ -167,91 +334,11 @@ class _TestBaseComponentsMixin:
|
|
|
167
334
|
)
|
|
168
335
|
|
|
169
336
|
|
|
170
|
-
class _TestDisabledMixin:
|
|
171
|
-
def test_disabled_default_val(self):
|
|
172
|
-
"""
|
|
173
|
-
Test that components created with default params are not disabled
|
|
174
|
-
"""
|
|
175
|
-
# Make minimal experiment just for this test
|
|
176
|
-
comp, rt, exp = _make_minimal_experiment(self)
|
|
177
|
-
# Check whether it can be disabled
|
|
178
|
-
assert 'disabled' in comp.params, (
|
|
179
|
-
f"{type(comp).__name__} does not have a 'disabled' attribute."
|
|
180
|
-
)
|
|
181
|
-
# Check that disabled defaults to False
|
|
182
|
-
assert comp.params['disabled'].val is False, f"{type(comp).__name__} is defaulting to disabled."
|
|
183
|
-
|
|
184
|
-
def test_code_muting(self):
|
|
185
|
-
"""
|
|
186
|
-
Test that components are only written when enabled and targets match.
|
|
187
|
-
"""
|
|
188
|
-
# Make minimal experiment just for this test
|
|
189
|
-
comp, rt, exp = _make_minimal_experiment(self)
|
|
190
|
-
# Skip for Code components as these purely inject code, name isn't used
|
|
191
|
-
if type(comp).__name__ in ("CodeComponent"):
|
|
192
|
-
pytest.skip()
|
|
193
|
-
# Write experiment and check that component is written
|
|
194
|
-
pyScript = exp.writeScript(target="PsychoPy")
|
|
195
|
-
if "PsychoPy" in type(comp).targets:
|
|
196
|
-
assert comp.name in pyScript, (
|
|
197
|
-
f"{type(comp).__name__} not found in compiled Python script when enabled and PsychoPy in targets."
|
|
198
|
-
)
|
|
199
|
-
else:
|
|
200
|
-
assert comp.name not in pyScript, (
|
|
201
|
-
f"{type(comp).__name__} found in compiled Python script when enabled but PsychoPy not in targets."
|
|
202
|
-
)
|
|
203
|
-
# ## disabled until js can compile without saving
|
|
204
|
-
# jsScript = exp.writeScript(target="PsychoJS")
|
|
205
|
-
# if "PsychoJS" in type(comp).targets:
|
|
206
|
-
# assert comp.name in jsScript, (
|
|
207
|
-
# f"{type(comp).__name__} not found in compiled Python script when enabled and PsychoJS in targets."
|
|
208
|
-
# )
|
|
209
|
-
# else:
|
|
210
|
-
# assert comp.name not in jsScript, (
|
|
211
|
-
# f"{type(comp).__name__} found in compiled Python script when enabled but PsychoJS not in targets."
|
|
212
|
-
# )
|
|
213
|
-
|
|
214
|
-
# Disable component then do same tests but assert not present
|
|
215
|
-
comp.params['disabled'].val = True
|
|
216
|
-
|
|
217
|
-
pyScript = exp.writeScript(target="PsychoPy")
|
|
218
|
-
if "PsychoPy" in type(comp).targets:
|
|
219
|
-
assert comp.name not in pyScript, (
|
|
220
|
-
f"{type(comp).__name__} found in compiled Python script when disabled but PsychoPy in targets."
|
|
221
|
-
)
|
|
222
|
-
else:
|
|
223
|
-
assert comp.name not in pyScript, (
|
|
224
|
-
f"{type(comp).__name__} found in compiled Python script when disabled and PsychoPy not in targets."
|
|
225
|
-
)
|
|
226
|
-
# ## disabled until js can compile without saving
|
|
227
|
-
# jsScript = exp.writeScript(target="PsychoJS")
|
|
228
|
-
# if "PsychoJS" in type(comp).targets:
|
|
229
|
-
# assert comp.name not in jsScript, (
|
|
230
|
-
# f"{type(comp).__name__} found in compiled Python script when disabled but PsychoJS in targets."
|
|
231
|
-
# )
|
|
232
|
-
# else:
|
|
233
|
-
# assert comp.name not in jsScript, (
|
|
234
|
-
# f"{type(comp).__name__} found in compiled Python script when disabled and PsychoJS not in targets."
|
|
235
|
-
# )
|
|
236
|
-
|
|
237
|
-
def test_disabled_components_stay_in_routine(self):
|
|
238
|
-
"""
|
|
239
|
-
Test that disabled components aren't removed from their routine when experiment is written.
|
|
240
|
-
"""
|
|
241
|
-
comp, rt, exp = _make_minimal_experiment(self)
|
|
242
|
-
# Disable component
|
|
243
|
-
comp.params['disabled'].val = True
|
|
244
|
-
# Writing the script drops the component but, if working properly, only from a copy of the routine, not the
|
|
245
|
-
# original!
|
|
246
|
-
exp.writeScript()
|
|
247
|
-
|
|
248
|
-
assert comp in rt, f"Disabling {type(comp).name} appears to remove it from its routine on compile."
|
|
249
|
-
|
|
250
337
|
|
|
251
338
|
class _TestDepthMixin:
|
|
252
339
|
def test_depth(self):
|
|
253
340
|
# Make minimal experiment
|
|
254
|
-
comp, rt, exp =
|
|
341
|
+
comp, rt, exp = self.make_minimal_experiment()
|
|
255
342
|
# Get class we're currently working with
|
|
256
343
|
compClass = type(comp)
|
|
257
344
|
# Add index to component name
|
psychopy/tools/fontmanager.py
CHANGED
|
@@ -947,7 +947,7 @@ class FontManager():
|
|
|
947
947
|
return False
|
|
948
948
|
# If font is found, make glfont
|
|
949
949
|
fontInfo = fontInfos[0]
|
|
950
|
-
identifier = "{}_{}".format(str(fontInfo), size)
|
|
950
|
+
identifier = "{}_{}_{}".format(str(fontInfo), size, lineSpacing)
|
|
951
951
|
glFont = self._glFonts.get(identifier)
|
|
952
952
|
if glFont is None:
|
|
953
953
|
glFont = GLFont(fontInfo.path, size, lineSpacing=lineSpacing)
|
psychopy/tools/versionchooser.py
CHANGED
|
@@ -33,7 +33,7 @@ _remoteVersionsCache = []
|
|
|
33
33
|
versionMap = OrderedDict({
|
|
34
34
|
Version('2.7'): (Version("0.0"), Version("2020.2.0")),
|
|
35
35
|
Version('3.6'): (Version("1.9"), Version("2022.1.0")),
|
|
36
|
-
Version('3.8'): (Version("2022.1.0"),
|
|
36
|
+
Version('3.8'): (Version("2022.1.0"), None),
|
|
37
37
|
Version('3.10'): (Version("2023.2.0"), None),
|
|
38
38
|
})
|
|
39
39
|
# fill out intermediate versions
|
psychopy/visual/noise.py
CHANGED
|
@@ -30,5 +30,14 @@ except (ModuleNotFoundError, ImportError):
|
|
|
30
30
|
"Support for `NoiseStim` is not available this session. Please install "
|
|
31
31
|
"`psychopy-visionscience` and restart the session to enable support.")
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
class NoiseStim:
|
|
35
|
+
"""
|
|
36
|
+
`psychopy.visual.NoiseStim` is now located within the `psychopy-visionscience` plugin. You
|
|
37
|
+
can find the documentation for it `here
|
|
38
|
+
<https://psychopy.github.io/psychopy-visionscience/coder/NoiseStim>`_
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
if __name__ == "__main__":
|
|
34
43
|
pass
|
psychopy/visual/radial.py
CHANGED
|
@@ -26,6 +26,14 @@ except (ModuleNotFoundError, ImportError):
|
|
|
26
26
|
"Support for `RadialStim` is not available this session. Please install "
|
|
27
27
|
"`psychopy-visionscience` and restart the session to enable support.")
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
class RadialStim:
|
|
31
|
+
"""
|
|
32
|
+
`psychopy.visual.RadialStim` is now located within the `psychopy-visionscience` plugin. You
|
|
33
|
+
can find the documentation for it `here <https://psychopy.github.io/psychopy-visionscience/coder/RadialStim>`_
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
29
37
|
if __name__ == "__main__":
|
|
30
38
|
pass
|
|
31
39
|
|
psychopy/visual/secondorder.py
CHANGED
|
@@ -25,5 +25,14 @@ except (ModuleNotFoundError, ImportError):
|
|
|
25
25
|
"install `psychopy-visionscience` and restart the session to enable "
|
|
26
26
|
"support.")
|
|
27
27
|
|
|
28
|
+
|
|
29
|
+
class EnvelopeGrating:
|
|
30
|
+
"""
|
|
31
|
+
`psychopy.visual.EnvelopeGrating` is now located within the `psychopy-visionscience` plugin. You
|
|
32
|
+
can find the documentation for it
|
|
33
|
+
`here <https://psychopy.github.io/psychopy-visionscience/coder/EnvelopeGrating>`_
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
if __name__ == "__main__":
|
|
29
38
|
pass
|
|
@@ -79,7 +79,7 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
79
79
|
bold=False,
|
|
80
80
|
italic=False,
|
|
81
81
|
placeholder="Type here...",
|
|
82
|
-
lineSpacing=
|
|
82
|
+
lineSpacing=1.0,
|
|
83
83
|
letterSpacing=None,
|
|
84
84
|
padding=None, # gap between box and text
|
|
85
85
|
speechPoint=None,
|
|
@@ -190,10 +190,11 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
190
190
|
self._pixelScaling = self.letterHeightPix / self.letterHeight
|
|
191
191
|
self.bold = bold
|
|
192
192
|
self.italic = italic
|
|
193
|
+
if lineSpacing is None:
|
|
194
|
+
lineSpacing = 1.0
|
|
195
|
+
self.lineSpacing = lineSpacing
|
|
193
196
|
self.glFont = None # will be set by the self.font attribute setter
|
|
194
197
|
self.font = font
|
|
195
|
-
if lineSpacing is not None:
|
|
196
|
-
self.lineSpacing = lineSpacing
|
|
197
198
|
self.letterSpacing = letterSpacing
|
|
198
199
|
# If font not found, default to Open Sans Regular and raise alert
|
|
199
200
|
if not self.glFont:
|
|
@@ -353,7 +354,7 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
353
354
|
self.caret.color = self._foreColor
|
|
354
355
|
|
|
355
356
|
@attributeSetter
|
|
356
|
-
def font(self, fontName
|
|
357
|
+
def font(self, fontName):
|
|
357
358
|
if isinstance(fontName, GLFont):
|
|
358
359
|
self.glFont = fontName
|
|
359
360
|
self.__dict__['font'] = fontName.name
|
|
@@ -362,7 +363,9 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
362
363
|
self.glFont = allFonts.getFont(
|
|
363
364
|
fontName,
|
|
364
365
|
size=self.letterHeightPix,
|
|
365
|
-
bold=self.bold,
|
|
366
|
+
bold=self.bold,
|
|
367
|
+
italic=self.italic,
|
|
368
|
+
lineSpacing=self.lineSpacing)
|
|
366
369
|
|
|
367
370
|
@attributeSetter
|
|
368
371
|
def overflow(self, value):
|
|
@@ -610,19 +613,6 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
610
613
|
"""
|
|
611
614
|
return self._letterHeight.pix[1]
|
|
612
615
|
|
|
613
|
-
@property
|
|
614
|
-
def lineSpacing(self):
|
|
615
|
-
if hasattr(self.glFont, "lineSpacing"):
|
|
616
|
-
return self.glFont.lineSpacing
|
|
617
|
-
|
|
618
|
-
@lineSpacing.setter
|
|
619
|
-
def lineSpacing(self, value):
|
|
620
|
-
if hasattr(self, "_placeholder"):
|
|
621
|
-
self._placeholder.lineSpacing = value
|
|
622
|
-
if hasattr(self.glFont, "lineSpacing"):
|
|
623
|
-
self.glFont.lineSpacing = value
|
|
624
|
-
self._needVertexUpdate = True
|
|
625
|
-
|
|
626
616
|
@attributeSetter
|
|
627
617
|
def letterSpacing(self, value):
|
|
628
618
|
"""
|
|
@@ -930,6 +920,9 @@ class TextBox2(BaseVisualStim, DraggingMixin, ContainerMixin, ColorMixin):
|
|
|
930
920
|
|
|
931
921
|
# are we wrapping the line?
|
|
932
922
|
if charcode == "\n":
|
|
923
|
+
# check if we have stored the top/bottom of the previous line yet
|
|
924
|
+
if lineN + 1 > len(_lineBottoms):
|
|
925
|
+
_lineBottoms.append(current[1])
|
|
933
926
|
lineWPix = current[0]
|
|
934
927
|
current[0] = 0
|
|
935
928
|
current[1] -= font.height
|
|
@@ -1628,6 +1621,8 @@ class Caret(ColorMixin):
|
|
|
1628
1621
|
self.index = len(self.textbox._lineNs)
|
|
1629
1622
|
# Get line of index
|
|
1630
1623
|
if self.index >= len(self.textbox._lineNs):
|
|
1624
|
+
if len(self.textbox._lineBottoms) - 1 > self.textbox._lineNs[-1]:
|
|
1625
|
+
return len(self.textbox._lineBottoms) - 1
|
|
1631
1626
|
return self.textbox._lineNs[-1]
|
|
1632
1627
|
else:
|
|
1633
1628
|
return self.textbox._lineNs[self.index]
|
|
@@ -1718,9 +1713,12 @@ class Caret(ColorMixin):
|
|
|
1718
1713
|
else:
|
|
1719
1714
|
# Otherwise, get caret position from character vertices
|
|
1720
1715
|
if self.index >= len(textbox._lineNs):
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1716
|
+
if len(textbox._lineBottoms) - 1 > textbox._lineNs[-1]:
|
|
1717
|
+
x = textbox._lineWidths[len(textbox._lineBottoms) - 1]
|
|
1718
|
+
else:
|
|
1719
|
+
# If the caret is after the last char, position it to the right
|
|
1720
|
+
chrVerts = textbox._vertices.pix[range((ii-1) * 4, (ii-1) * 4 + 4)]
|
|
1721
|
+
x = chrVerts[2, 0] # x-coord of left edge (of final char)
|
|
1724
1722
|
else:
|
|
1725
1723
|
# Otherwise, position it to the left
|
|
1726
1724
|
chrVerts = textbox._vertices.pix[range(ii * 4, ii * 4 + 4)]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: psychopy
|
|
3
|
-
Version: 2024.1.
|
|
3
|
+
Version: 2024.1.3
|
|
4
4
|
Summary: PsychoPy provides easy, precise, flexible experiments in behavioural sciences
|
|
5
5
|
Author-Email: Open Science Tools Ltd <support@opensciencetools.org>
|
|
6
6
|
Maintainer-Email: Open Science Tools Ltd <support@opensciencetools.org>
|