psychopy 2025.1.0__py3-none-any.whl → 2025.2.1__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.

Files changed (226) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/colors.py +10 -5
  113. psychopy/data/experiment.py +133 -23
  114. psychopy/data/routine.py +12 -0
  115. psychopy/data/staircase.py +42 -20
  116. psychopy/data/trial.py +20 -12
  117. psychopy/data/utils.py +43 -3
  118. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  120. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  121. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  122. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  123. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  124. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  125. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  126. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  127. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  128. psychopy/event.py +20 -15
  129. psychopy/experiment/_experiment.py +86 -10
  130. psychopy/experiment/components/__init__.py +3 -10
  131. psychopy/experiment/components/_base.py +9 -20
  132. psychopy/experiment/components/button/__init__.py +1 -1
  133. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  134. psychopy/experiment/components/camera/__init__.py +137 -359
  135. psychopy/experiment/components/keyboard/__init__.py +17 -24
  136. psychopy/experiment/components/microphone/__init__.py +61 -110
  137. psychopy/experiment/components/movie/__init__.py +2 -3
  138. psychopy/experiment/components/serialOut/__init__.py +192 -93
  139. psychopy/experiment/components/settings/__init__.py +45 -27
  140. psychopy/experiment/components/sound/__init__.py +82 -73
  141. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  142. psychopy/experiment/devices.py +303 -0
  143. psychopy/experiment/exports.py +20 -18
  144. psychopy/experiment/flow.py +7 -0
  145. psychopy/experiment/loops.py +47 -29
  146. psychopy/experiment/monitor.py +74 -0
  147. psychopy/experiment/params.py +48 -10
  148. psychopy/experiment/plugins.py +28 -108
  149. psychopy/experiment/py2js_transpiler.py +1 -1
  150. psychopy/experiment/routines/__init__.py +1 -1
  151. psychopy/experiment/routines/_base.py +59 -24
  152. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  153. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  154. psychopy/hardware/__init__.py +20 -57
  155. psychopy/hardware/button.py +15 -2
  156. psychopy/hardware/camera/__init__.py +2237 -1394
  157. psychopy/hardware/joystick/__init__.py +1 -1
  158. psychopy/hardware/keyboard.py +5 -8
  159. psychopy/hardware/listener.py +4 -1
  160. psychopy/hardware/manager.py +75 -35
  161. psychopy/hardware/microphone.py +53 -7
  162. psychopy/hardware/monitor.py +144 -0
  163. psychopy/hardware/photometer/__init__.py +156 -117
  164. psychopy/hardware/serialdevice.py +16 -2
  165. psychopy/hardware/soundsensor.py +4 -1
  166. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  167. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  168. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  169. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  170. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  171. psychopy/iohub/util/__init__.py +7 -8
  172. psychopy/localization/generateTranslationTemplate.py +208 -116
  173. psychopy/localization/messages.pot +4305 -3502
  174. psychopy/monitors/MonitorCenter.py +174 -74
  175. psychopy/plugins/__init__.py +6 -4
  176. psychopy/preferences/devices.py +80 -0
  177. psychopy/preferences/generateHints.py +2 -1
  178. psychopy/preferences/preferences.py +35 -11
  179. psychopy/scripts/psychopy-pkgutil.py +969 -0
  180. psychopy/scripts/psyexpCompile.py +1 -1
  181. psychopy/session.py +34 -38
  182. psychopy/sound/__init__.py +6 -260
  183. psychopy/sound/audioclip.py +164 -0
  184. psychopy/sound/backend_ptb.py +8 -0
  185. psychopy/sound/backend_pygame.py +10 -0
  186. psychopy/sound/backend_pysound.py +9 -0
  187. psychopy/sound/backends/__init__.py +0 -0
  188. psychopy/sound/microphone.py +3 -0
  189. psychopy/sound/sound.py +58 -0
  190. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  191. psychopy/tests/data/duplicateHeaders.csv +2 -0
  192. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  193. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  194. psychopy/tests/test_data/test_utils.py +5 -1
  195. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  196. psychopy/tests/test_hardware/test_ports.py +0 -12
  197. psychopy/tests/test_tools/test_stringtools.py +1 -1
  198. psychopy/tools/attributetools.py +12 -5
  199. psychopy/tools/fontmanager.py +17 -14
  200. psychopy/tools/gltools.py +4 -2
  201. psychopy/tools/movietools.py +43 -2
  202. psychopy/tools/stringtools.py +33 -8
  203. psychopy/tools/versionchooser.py +1 -1
  204. psychopy/validation/audio.py +5 -1
  205. psychopy/validation/visual.py +5 -1
  206. psychopy/visual/basevisual.py +8 -7
  207. psychopy/visual/circle.py +2 -2
  208. psychopy/visual/helpers.py +3 -1
  209. psychopy/visual/image.py +29 -109
  210. psychopy/visual/movies/__init__.py +1800 -313
  211. psychopy/visual/polygon.py +4 -0
  212. psychopy/visual/shape.py +2 -2
  213. psychopy/visual/window.py +35 -12
  214. psychopy/voicekey/__init__.py +41 -669
  215. psychopy/voicekey/labjack_vks.py +7 -48
  216. psychopy/voicekey/parallel_vks.py +7 -42
  217. psychopy/voicekey/vk_tools.py +114 -263
  218. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
  219. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
  220. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  221. psychopy/visual/movies/players/__init__.py +0 -62
  222. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  223. psychopy/voicekey/demo_vks.py +0 -12
  224. psychopy/voicekey/signal.py +0 -42
  225. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  226. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -17,6 +17,7 @@ from collections import OrderedDict
17
17
  import numpy
18
18
  import re
19
19
  import wx
20
+ from wx.lib import scrolledpanel
20
21
 
21
22
  import psychopy.experiment.utils
22
23
  from psychopy.experiment import Param
@@ -77,6 +78,10 @@ class ParamCtrls():
77
78
  self.dlg = dlg
78
79
  self.dpi = self.dlg.dpi
79
80
  self.valueWidth = self.dpi * 3.5
81
+ # get warnings handler
82
+ warnings = None
83
+ if hasattr(dlg, "warnings"):
84
+ warnings = dlg.warnings
80
85
  # try to find the experiment
81
86
  self.exp = None
82
87
  tryForExp = self.dlg
@@ -89,7 +94,13 @@ class ParamCtrls():
89
94
  tryForExp = tryForExp.parent # try going up a level
90
95
  except Exception:
91
96
  tryForExp.parent
92
-
97
+ # get the element to which this set of param ctrls pertains
98
+ self.element = None
99
+ if isinstance(parent, ParamNotebook.CategoryPage):
100
+ # if a category page, get element from notebook
101
+ self.element = parent.parent.element
102
+ elif isinstance(parent, DlgLoopProperties.LoopParamsPanel):
103
+ self.element = parent.loop
93
104
  # param has the fields:
94
105
  # val, valType, allowedVals=[],allowedTypes=[],
95
106
  # hint="", updates=None, allowedUpdates=None
@@ -114,162 +125,11 @@ class ParamCtrls():
114
125
  versions = vc._versionFilter(
115
126
  vc.availableVersions(local=False), wx.__version__)
116
127
  param.allowedVals = (options + [''] + versions)
117
-
118
- if param.inputType == "single":
119
- # Create single line string control
120
- self.valueCtrl = paramCtrls.SingleLineCtrl(
121
- parent,
122
- val=str(param.val),
123
- valType=param.valType,
124
- fieldName=fieldName,
125
- size=wx.Size(int(self.valueWidth), 24))
126
- elif param.inputType == 'multi':
127
- if param.valType == "extendedCode":
128
- # Create multiline code control
129
- self.valueCtrl = paramCtrls.CodeCtrl(
130
- parent,
131
- val=str(param.val),
132
- valType=param.valType,
133
- fieldName=fieldName,
134
- size=wx.Size(int(self.valueWidth), 144))
135
- else:
136
- # Create multiline string control
137
- self.valueCtrl = paramCtrls.MultiLineCtrl(
138
- parent,
139
- val=str(param.val),
140
- valType=param.valType,
141
- fieldName=fieldName,
142
- size=wx.Size(int(self.valueWidth), 144))
143
- # Set focus if field is text of a Textbox or Text component
144
- if fieldName == 'text':
145
- self.valueCtrl.SetFocus()
146
- elif param.inputType == 'spin':
147
- # Create single line string control
148
- self.valueCtrl = paramCtrls.SingleLineCtrl(
149
- parent,
150
- val=str(param.val),
151
- valType=param.valType,
152
- fieldName=fieldName,
153
- size=wx.Size(int(self.valueWidth), 24))
154
- # Will have to disable spinCtrl until we have a dropdown for inputType, sadly
155
- # self.valueCtrl = paramCtrls.IntCtrl(parent,
156
- # val=param.val, valType=param.valType,
157
- # fieldName=fieldName,size=wx.Size(self.valueWidth, 24),
158
- # limits=param.allowedVals)
159
- elif param.inputType == 'choice':
160
- self.valueCtrl = paramCtrls.ChoiceCtrl(
161
- parent,
162
- val=str(param.val),
163
- valType=param.valType,
164
- choices=param.allowedVals,
165
- labels=param.allowedLabels,
166
- fieldName=fieldName)
167
- elif param.inputType == 'multiChoice':
168
- self.valueCtrl = paramCtrls.MultiChoiceCtrl(
169
- parent,
170
- valType=param.valType,
171
- vals=param.val,
172
- choices=param.allowedVals,
173
- fieldName=fieldName,
174
- size=wx.Size(int(self.valueWidth), -1))
175
- elif param.inputType == 'richChoice':
176
- self.valueCtrl = paramCtrls.RichChoiceCtrl(
177
- parent,
178
- valType=param.valType,
179
- vals=param.val,
180
- choices=param.allowedVals,
181
- labels=param.allowedLabels,
182
- fieldName=fieldName,
183
- size=wx.Size(int(self.valueWidth), -1))
184
- elif param.inputType == 'bool':
185
- self.valueCtrl = paramCtrls.BoolCtrl(
186
- parent,
187
- name=fieldName,
188
- size=wx.Size(int(self.valueWidth), 24))
189
- self.valueCtrl.SetValue(bool(param))
190
- elif param.inputType == 'file' or browse:
191
- self.valueCtrl = paramCtrls.FileCtrl(
192
- parent,
193
- val=str(param.val),
194
- valType=param.valType,
195
- fieldName=fieldName,
196
- size=wx.Size(int(self.valueWidth), 24))
197
- self.valueCtrl.allowedVals = param.allowedVals
198
- elif param.inputType == 'font':
199
- self.valueCtrl = paramCtrls.FontCtrl(
200
- parent,
201
- val=str(param.val),
202
- valType=param.valType,
203
- fieldName=fieldName,
204
- size=wx.Size(int(self.valueWidth), 24))
205
- elif param.inputType == 'survey':
206
- self.valueCtrl = paramCtrls.SurveyCtrl(
207
- parent,
208
- val=str(param.val),
209
- valType=param.valType,
210
- fieldName=fieldName,
211
- size=wx.Size(int(self.valueWidth), 24))
212
- self.valueCtrl.allowedVals = param.allowedVals
213
- elif param.inputType == 'fileList':
214
- self.valueCtrl = paramCtrls.FileListCtrl(
215
- parent,
216
- choices=param.val,
217
- valType=param.valType,
218
- size=wx.Size(int(self.valueWidth), 100),
219
- pathtype="rel")
220
- elif param.inputType == 'table':
221
- self.valueCtrl = paramCtrls.TableCtrl(
222
- parent,
223
- param=param,
224
- fieldName=fieldName,
225
- size=wx.Size(int(self.valueWidth), 24))
226
- elif param.inputType == 'color':
227
- self.valueCtrl = paramCtrls.ColorCtrl(
228
- parent,
229
- val=param.val,
230
- valType=param.valType,
231
- fieldName=fieldName,
232
- size=wx.Size(int(self.valueWidth), 24))
233
- elif param.inputType == 'dict':
234
- self.valueCtrl = paramCtrls.DictCtrl(
235
- parent,
236
- val=param.val,
237
- labels=param.allowedLabels,
238
- valType=param.valType,
239
- fieldName=fieldName)
240
- elif param.inputType == 'inv':
241
- self.valueCtrl = paramCtrls.InvalidCtrl(
242
- parent,
243
- val=str(param.val),
244
- valType=param.valType,
245
- fieldName=fieldName,
246
- size=wx.Size(int(self.valueWidth), 24))
247
- else:
248
- self.valueCtrl = paramCtrls.SingleLineCtrl(
249
- parent,
250
- val=str(param.val),
251
- valType=param.valType,
252
- fieldName=fieldName,
253
- size=wx.Size(int(self.valueWidth), 24))
254
- logging.warn(
255
- f"Parameter {fieldName} has unrecognised inputType \"{param.inputType}\"")
256
-
257
- # if fieldName == 'Experiment info':
258
- # # for expInfo convert from a string to the list-of-dicts
259
- # val = self.expInfoToListWidget(param.val)
260
- # self.valueCtrl = dialogs.ListWidget(
261
- # parent, val, order=['Field', 'Default'])
262
- if hasattr(self.valueCtrl, 'SetToolTip'):
263
- self.valueCtrl.SetToolTip(wx.ToolTip(_translate(param.hint)))
264
- if not callable(param.allowedVals) and len(param.allowedVals) == 1 or param.readOnly:
265
- self.valueCtrl.Disable() # visible but can't be changed
266
-
267
- # add a Validator to the valueCtrl
268
- if fieldName == "name":
269
- self.valueCtrl.SetValidator(NameValidator())
270
- elif param.inputType in ("single", "multi"):
271
- # only want anything that is valType code, or can be with $
272
- self.valueCtrl.SetValidator(CodeSnippetValidator(fieldName, param.label))
128
+
129
+ # create a param ctrl
130
+ self.valueCtrl = paramCtrls.ParamCtrl(
131
+ parent, field=fieldName, param=param, element=self.element, warnings=warnings
132
+ )
273
133
 
274
134
  # create the type control
275
135
  if len(param.allowedTypes):
@@ -305,6 +165,8 @@ class ParamCtrls():
305
165
  allowedUpdates.append(msg + fullName)
306
166
  updateLabels.append(localizedMsg + fullName)
307
167
  self.updateCtrl = wx.Choice(parent, choices=updateLabels)
168
+ # bind method to update value ctrl's param on updating updateCtrl
169
+ self.updateCtrl.Bind(wx.EVT_CHOICE, self.onChangeUpdate)
308
170
  # stash non-localized choices to allow retrieval by index:
309
171
  self.updateCtrl._choices = copy.copy(allowedUpdates)
310
172
  # If parameter isn't in list, default to the first choice
@@ -442,6 +304,16 @@ class ParamCtrls():
442
304
  expInfoStr = repr(expInfo)
443
305
  return expInfoStr
444
306
 
307
+ def onChangeUpdate(self, evt=None):
308
+ """
309
+ On changes to the update ctrl, set updates on the param associated with the valuectrl
310
+ """
311
+ if self.updateCtrl is not None and self.valueCtrl is not None:
312
+ # set updates on value ctrl's param
313
+ self.valueCtrl.param.updates = self.updateCtrl.GetStringSelection()
314
+ # trigger onchange to update appearance
315
+ self.valueCtrl.onChange()
316
+
445
317
  def setChangesCallback(self, callbackFunction):
446
318
  """Set a callback to detect any changes in this value (whether it's
447
319
  a checkbox event or a text event etc
@@ -464,7 +336,7 @@ class ParamCtrls():
464
336
  self.valueCtrl.Bind(wx.EVT_CHECKBOX, callbackFunction)
465
337
  elif isinstance(self.valueCtrl, paramCtrls.CodeCtrl):
466
338
  self.valueCtrl.Bind(wx.EVT_KEY_UP, callbackFunction)
467
- elif isinstance(self.valueCtrl, (paramCtrls.DictCtrl, paramCtrls.FileListCtrl)):
339
+ elif isinstance(self.valueCtrl, (paramCtrls.BaseParamCtrl, paramCtrls.DictCtrl, paramCtrls.FileListCtrl)):
468
340
  pass
469
341
  else:
470
342
  print("setChangesCallback doesn't know how to handle ctrl {}"
@@ -557,9 +429,12 @@ class StartStopCtrls(wx.GridBagSizer):
557
429
 
558
430
 
559
431
  class ParamNotebook(wx.Notebook, handlers.ThemeMixin):
560
- class CategoryPage(wx.Panel, handlers.ThemeMixin):
432
+ class CategoryPage(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
561
433
  def __init__(self, parent, dlg, params, categ=None):
562
434
  wx.Panel.__init__(self, parent, size=(600, -1))
435
+ self.SetupScrolling()
436
+ self.SetMaxSize((-1, 1080))
437
+ self.SetMinSize((720, 512))
563
438
  self.parent = parent
564
439
  self.parent = parent
565
440
  self.dlg = dlg
@@ -607,6 +482,8 @@ class ParamNotebook(wx.Notebook, handlers.ThemeMixin):
607
482
  def addParam(self, name, param):
608
483
  # Make ctrl
609
484
  self.ctrls[name] = ParamCtrls(self.dlg, param.label, param, self, name)
485
+ # Bind change event
486
+ self.ctrls[name].valueCtrl.Bind(paramCtrls.EVT_PARAM_CHANGED, self.emitChangeEvent)
610
487
  # Add value ctrl
611
488
  _flag = wx.EXPAND | wx.ALL
612
489
  if hasattr(self.ctrls[name].valueCtrl, '_szr'):
@@ -718,6 +595,9 @@ class ParamNotebook(wx.Notebook, handlers.ThemeMixin):
718
595
  if isinstance(self.dlg, wx.Dialog):
719
596
  self.dlg.Fit()
720
597
  self.Refresh()
598
+
599
+ def emitChangeEvent(self, evt):
600
+ wx.PostEvent(self, evt)
721
601
 
722
602
  def doValidate(self, event=None):
723
603
  self.Validate()
@@ -758,8 +638,13 @@ class ParamNotebook(wx.Notebook, handlers.ThemeMixin):
758
638
  for categ, params in paramsByCateg.items():
759
639
  page = self.CategoryPage(self, self.parent, params, categ=categ)
760
640
  self.paramCtrls.update(page.ctrls)
641
+ # Bind change event
642
+ page.Bind(paramCtrls.EVT_PARAM_CHANGED, self.emitChangeEvent)
761
643
  # Add page to notebook
762
644
  self.AddPage(page, _translate(categ))
645
+
646
+ def emitChangeEvent(self, evt):
647
+ wx.PostEvent(self, evt)
763
648
 
764
649
  def checkDepends(self, event=None):
765
650
  """
@@ -884,6 +769,7 @@ class _BaseParamsDlg(wx.Dialog):
884
769
  self.params = params = element.params # dict
885
770
  self.title = title
886
771
  self.timeout = timeout
772
+ self.warnings = WarningManager(self)
887
773
  if (not editing and
888
774
  title != 'Experiment Settings' and
889
775
  'name' in self.params):
@@ -999,7 +885,6 @@ class _BaseParamsDlg(wx.Dialog):
999
885
  CANCEL = wx.Button(self, wx.ID_CANCEL, _translate(" Cancel "))
1000
886
 
1001
887
  # Add validator stuff
1002
- self.warnings = WarningManager(self)
1003
888
  self.mainSizer.Add(self.warnings.output, border=3, flag=wx.EXPAND | wx.ALL)
1004
889
  self.Validate() # disables OKbtn if bad name, syntax error, etc
1005
890
 
@@ -1058,8 +943,8 @@ class _BaseParamsDlg(wx.Dialog):
1058
943
  """
1059
944
  # run "on ok" validation for each ctrl
1060
945
  for ctrl in self.paramCtrls.values():
1061
- if hasattr(ctrl, "valueCtrl") and hasattr(ctrl.valueCtrl, "onOK"):
1062
- ctrl.valueCtrl.onOK()
946
+ if hasattr(ctrl, "valueCtrl") and hasattr(ctrl.valueCtrl, "onElementOk"):
947
+ ctrl.valueCtrl.onElementOk()
1063
948
 
1064
949
  event.Skip()
1065
950
 
@@ -1205,6 +1090,12 @@ class _BaseParamsDlg(wx.Dialog):
1205
1090
  class DlgLoopProperties(_BaseParamsDlg):
1206
1091
  _style = wx.DEFAULT_DIALOG_STYLE | wx.DIALOG_NO_PARENT | wx.RESIZE_BORDER
1207
1092
 
1093
+ class LoopParamsPanel(wx.Panel):
1094
+ def __init__(self, parent, loop):
1095
+ wx.Panel.__init__(self, parent)
1096
+ # store loop object
1097
+ self.loop = loop
1098
+
1208
1099
  def __init__(self, frame, title="Loop Properties", loop=None,
1209
1100
  helpUrl=None, pos=wx.DefaultPosition, size=wx.DefaultSize,
1210
1101
  style=_style, depends=[], timeout=None):
@@ -1233,6 +1124,8 @@ class DlgLoopProperties(_BaseParamsDlg):
1233
1124
  self.conditions = None
1234
1125
  self.conditionsFile = None
1235
1126
  self.condNamesInFile = []
1127
+ self.warnings = WarningManager(self)
1128
+ self.loop = loop
1236
1129
  # create a valid new name; save old name in case we need to revert
1237
1130
  namespace = frame.exp.namespace
1238
1131
  defaultName = namespace.makeValid('trials')
@@ -1401,7 +1294,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1401
1294
  return str(self.expPath / self._conditionsFile)
1402
1295
 
1403
1296
  def makeGlobalCtrls(self):
1404
- panel = wx.Panel(parent=self)
1297
+ panel = self.LoopParamsPanel(parent=self, loop=self.loop)
1405
1298
  panelSizer = wx.GridBagSizer(0, 0)
1406
1299
  panel.SetSizer(panelSizer)
1407
1300
  row = 0
@@ -1424,8 +1317,9 @@ class DlgLoopProperties(_BaseParamsDlg):
1424
1317
  row += 1
1425
1318
  panelSizer.AddGrowableCol(1, 1)
1426
1319
  self.globalCtrls['name'].valueCtrl.Bind(wx.EVT_TEXT, self.Validate)
1427
- self.Bind(wx.EVT_CHOICE, self.onTypeChanged,
1428
- self.globalCtrls['loopType'].valueCtrl)
1320
+ self.globalCtrls['loopType'].valueCtrl.Bind(
1321
+ paramCtrls.EVT_PARAM_CHANGED, self.onTypeChanged,
1322
+ )
1429
1323
  return panel
1430
1324
 
1431
1325
  def makeConstantsCtrls(self):
@@ -1434,7 +1328,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1434
1328
  handler = self.trialHandler
1435
1329
  # loop through the params
1436
1330
  keys = list(handler.params.keys())
1437
- panel = wx.Panel(parent=self)
1331
+ panel = self.LoopParamsPanel(parent=self, loop=self.loop)
1438
1332
  panel.app=self.app
1439
1333
  panelSizer = wx.GridBagSizer(0, 0)
1440
1334
  panel.SetSizer(panelSizer)
@@ -1500,7 +1394,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1500
1394
  row += 1
1501
1395
  # Link conditions file browse button to its own special method
1502
1396
  if fieldName == 'conditionsFile':
1503
- ctrls.valueCtrl.findBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
1397
+ ctrls.valueCtrl.fileBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
1504
1398
  ctrls.setChangesCallback(self.setNeedUpdate)
1505
1399
  # store info about the field
1506
1400
  self.constantsCtrls[fieldName] = ctrls
@@ -1509,7 +1403,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1509
1403
 
1510
1404
  def makeMultiStairCtrls(self):
1511
1405
  # a list of controls for the random/sequential versions
1512
- panel = wx.Panel(parent=self)
1406
+ panel = self.LoopParamsPanel(parent=self, loop=self.loop)
1513
1407
  panel.app = self.app
1514
1408
  panelSizer = wx.GridBagSizer(0, 0)
1515
1409
  panel.SetSizer(panelSizer)
@@ -1579,7 +1473,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1579
1473
  row += 1
1580
1474
  # Bind file button with its own special method
1581
1475
  if fieldName == 'conditionsFile':
1582
- ctrls.valueCtrl.findBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
1476
+ ctrls.valueCtrl.fileBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
1583
1477
  # store info about the field
1584
1478
  self.multiStairCtrls[fieldName] = ctrls
1585
1479
  panelSizer.AddGrowableCol(1, 1)
@@ -1588,7 +1482,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1588
1482
  def makeStaircaseCtrls(self):
1589
1483
  """Setup the controls for a StairHandler
1590
1484
  """
1591
- panel = wx.Panel(parent=self)
1485
+ panel = self.LoopParamsPanel(parent=self, loop=self.loop)
1592
1486
  panelSizer = wx.GridBagSizer(0, 0)
1593
1487
  panel.SetSizer(panelSizer)
1594
1488
  row = 0
@@ -1699,7 +1593,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1699
1593
  self.setCtrls(value)
1700
1594
 
1701
1595
  def onTypeChanged(self, evt=None):
1702
- newType = evt.GetString()
1596
+ newType = self.globalCtrls['loopType'].valueCtrl.getValue()
1703
1597
  if newType == self.currentType:
1704
1598
  return
1705
1599
  self.setCtrls(newType)
@@ -1711,7 +1605,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1711
1605
  style=wx.FD_OPEN, defaultDir=str(self.expPath))
1712
1606
  if dlg.ShowModal() == wx.ID_OK:
1713
1607
  self.conditionsFile = dlg.GetPath()
1714
- self.currentCtrls['conditionsFile'].valueCtrl.SetValue(
1608
+ self.currentCtrls['conditionsFile'].valueCtrl.setValue(
1715
1609
  self.conditionsFile
1716
1610
  )
1717
1611
  self.updateSummary()
@@ -1728,7 +1622,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1728
1622
  or message, as appropriate. Upon completion this will disable the update button as
1729
1623
  we are now up to date.
1730
1624
  """
1731
- self.conditionsFile = self.currentCtrls['conditionsFile'].valueCtrl.GetValue()
1625
+ self.conditionsFile = self.currentCtrls['conditionsFile'].valueCtrl.getValue()
1732
1626
  # Check whether the file and path are the same as previously
1733
1627
  isSameFilePathAndName = self.conditionsFileAbs == self.conditionsFileOrig
1734
1628
  # Start off with no message and assumed valid
@@ -1901,17 +1795,10 @@ class DlgExperimentProperties(_BaseParamsDlg):
1901
1795
  self.app = frame.app
1902
1796
  self.dpi = self.app.dpi
1903
1797
 
1904
- # for input devices:
1905
- # do this just to set the initial values to be
1906
- self.paramCtrls['Full-screen window'].setChangesCallback(self.onFullScrChange)
1907
- self.onFullScrChange(event=None)
1908
- self.Bind(wx.EVT_CHECKBOX, self.onFullScrChange,
1909
- self.paramCtrls['Full-screen window'].valueCtrl)
1910
-
1911
1798
  # Add button to show screen numbers
1912
1799
  scrNumCtrl = self.paramCtrls['Screen'].valueCtrl
1913
- self.screenNsBtn = wx.Button(scrNumCtrl.GetParent(), label=_translate("Show screen numbers"))
1914
- scrNumCtrl._szr.Add(self.screenNsBtn, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT)
1800
+ self.screenNsBtn = wx.Button(scrNumCtrl, label=_translate("Show screen numbers"))
1801
+ scrNumCtrl.sizer.Add(self.screenNsBtn, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT)
1915
1802
  scrNumCtrl.Layout()
1916
1803
  self.screenNsBtn.Bind(wx.EVT_BUTTON, self.showScreenNumbers)
1917
1804
 
@@ -1925,44 +1812,6 @@ class DlgExperimentProperties(_BaseParamsDlg):
1925
1812
 
1926
1813
  self.Destroy()
1927
1814
 
1928
- def onFullScrChange(self, event=None):
1929
- """full-screen has been checked / unchecked.
1930
- Show or hide the window size field accordingly
1931
- """
1932
- if self.paramCtrls['Full-screen window'].valueCtrl.GetValue():
1933
- # get screen size for requested display
1934
- numDisplays = wx.Display.GetCount()
1935
- try:
1936
- screenValue = int(
1937
- self.paramCtrls['Screen'].valueCtrl.GetValue())
1938
- except ValueError:
1939
- # param control currently contains no integer value
1940
- screenValue = 1
1941
- if screenValue < 1 or screenValue > numDisplays:
1942
- logging.error("User requested non-existent screen")
1943
- screenN = 0
1944
- else:
1945
- screenN = screenValue - 1
1946
- size = list(wx.Display(screenN).GetGeometry()[2:])
1947
- # set vals and disable changes
1948
- field = 'Window size (pixels)'
1949
- self.paramCtrls[field].valueCtrl.SetValue(str(size))
1950
- self.paramCtrls[field].param.val = size
1951
- self.paramCtrls[field].valueCtrl.Disable()
1952
- self.paramCtrls[field].nameCtrl.Disable()
1953
- # enable show/hide mouse
1954
- self.paramCtrls['Show mouse'].valueCtrl.Enable()
1955
- self.paramCtrls['Show mouse'].nameCtrl.Enable()
1956
- else:
1957
- self.paramCtrls['Window size (pixels)'].valueCtrl.Enable()
1958
- self.paramCtrls['Window size (pixels)'].nameCtrl.Enable()
1959
- # set show mouse to visible and disable control
1960
- self.paramCtrls['Show mouse'].valueCtrl.Disable()
1961
- self.paramCtrls['Show mouse'].nameCtrl.Disable()
1962
- self.mainSizer.Layout()
1963
- self.Fit()
1964
- self.Refresh()
1965
-
1966
1815
 
1967
1816
  class DlgNewRoutine(wx.Dialog):
1968
1817
 
@@ -16,14 +16,12 @@ from collections import OrderedDict
16
16
  from psychopy.experiment.components.code import CodeComponent
17
17
  from ..validators import WarningManager
18
18
  from ...themes import handlers
19
-
20
- from importlib.util import find_spec as loader
21
- hasMetapensiero = loader("metapensiero") is not None
19
+ import importlib
22
20
 
23
21
  from .. import validators
24
22
  from psychopy.localization import _translate
25
23
  from psychopy.app.coder.codeEditorBase import BaseCodeEditor
26
- from psychopy.experiment.py2js_transpiler import translatePythonToJavaScript
24
+ from psychopy.experiment import py2js_transpiler
27
25
 
28
26
 
29
27
  class DlgCodeComponentProperties(wx.Dialog):
@@ -68,6 +66,29 @@ class DlgCodeComponentProperties(wx.Dialog):
68
66
  # then we're adding a new component so ensure a valid name:
69
67
  makeValid = self.frame.exp.namespace.makeValid
70
68
  self.params['name'].val = makeValid(self.params['name'].val)
69
+
70
+ # if metapensiero isn't installed, install to the user folder now
71
+ if py2js_transpiler.translates is None:
72
+ # ask before installing
73
+ dlg = wx.MessageDialog(
74
+ self,
75
+ message=_translate(
76
+ "Could not detect the necessary Python library for translating code. Install "
77
+ "it now?"
78
+ ),
79
+ style=wx.YES | wx.NO
80
+ )
81
+ if dlg.ShowModal() == wx.ID_YES:
82
+ # install if they say yes
83
+ from psychopy.tools import pkgtools
84
+ pkgtools.installPackage(
85
+ "git+https://gitlab.com/peircej/metapensiero.pj"
86
+ )
87
+ # try to import again now that it exists
88
+ try:
89
+ py2js_transpiler.translates = importlib.import_module("metapensiero.pj.api")
90
+ except ModuleNotFoundError:
91
+ pass
71
92
 
72
93
  self.codeNotebook = wx.Notebook(self)
73
94
  # in AUI notebook the labels are blurry on retina mac
@@ -98,7 +119,7 @@ class DlgCodeComponentProperties(wx.Dialog):
98
119
  _selectedCodeTypeIndex = _codeTypes.index(_selectedCodeType)
99
120
  self.codeTypeMenu = wx.Choice(self, choices=_codeTypes)
100
121
  # If user does not have metapensiero but codetype is auto-js, revert to (Py?)
101
- if not hasMetapensiero and _selectedCodeType.lower() == 'auto->js':
122
+ if not py2js_transpiler.translates is not None and _selectedCodeType.lower() == 'auto->js':
102
123
  _selectedCodeTypeIndex -= 1
103
124
  # Set selection to value stored in self params
104
125
  self.codeTypeMenu.SetSelection(_selectedCodeTypeIndex)
@@ -233,7 +254,7 @@ class DlgCodeComponentProperties(wx.Dialog):
233
254
  """
234
255
  prevCodeType, param = self.codeChoice
235
256
  # If user doesn't have metapensiero and current choice is auto-js...
236
- if not hasMetapensiero and param.val.lower() == "auto->js" :
257
+ if not py2js_transpiler.translates is not None and param.val.lower() == "auto->js" :
237
258
  # Throw up error dlg instructing to get metapensiero
238
259
  msg = _translate("\nPy to JS auto-translation requires the metapensiero library.\n"
239
260
  "Available for Python 3.5+.\n")
@@ -312,7 +333,7 @@ class DlgCodeComponentProperties(wx.Dialog):
312
333
 
313
334
  try:
314
335
  if pythonCode:
315
- jsCode = translatePythonToJavaScript(pythonCode, namespace=None)
336
+ jsCode = py2js_transpiler.translatePythonToJavaScript(pythonCode, namespace=None)
316
337
 
317
338
  if codeChangeTest:
318
339
  return jsCode
@@ -488,7 +509,7 @@ class CodeBox(BaseCodeEditor, handlers.ThemeMixin):
488
509
  BaseCodeEditor.__init__(self, parent, ID, pos, size, style)
489
510
 
490
511
  self.parent = parent
491
- self.app = parent.app
512
+ # self.app = parent.app
492
513
  self.prefs = prefs.coder
493
514
  self.appData = prefs.appData
494
515
  self.paths = prefs.paths