psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__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 (325) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/validators.py +2 -2
  19. psychopy/app/coder/codeEditorBase.py +8 -8
  20. psychopy/app/coder/coder.py +4 -4
  21. psychopy/app/connections/sendusage.py +2 -2
  22. psychopy/app/connections/updates.py +9 -9
  23. psychopy/app/dialogs.py +34 -2
  24. psychopy/app/idle.py +31 -0
  25. psychopy/app/jobs.py +21 -3
  26. psychopy/app/linuxconfig/__init__.py +9 -0
  27. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  29. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  31. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  32. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  34. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
  36. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  37. psychopy/app/pavlovia_ui/_base.py +33 -3
  38. psychopy/app/pavlovia_ui/search.py +0 -1
  39. psychopy/app/plugin_manager/dialog.py +104 -51
  40. psychopy/app/plugin_manager/packages.py +5 -0
  41. psychopy/app/plugin_manager/plugins.py +145 -67
  42. psychopy/app/preferencesDlg.py +8 -8
  43. psychopy/app/psychopyApp.py +11 -5
  44. psychopy/app/ribbon.py +124 -14
  45. psychopy/app/runner/runner.py +6 -1
  46. psychopy/app/stdout/stdOutRich.py +27 -11
  47. psychopy/app/themes/icons.py +52 -2
  48. psychopy/assets/__init__.py +0 -0
  49. psychopy/assets/click.png +0 -0
  50. psychopy/assets/clicknext.png +0 -0
  51. psychopy/assets/next.png +0 -0
  52. psychopy/assets/psychopy.ico +0 -0
  53. psychopy/assets/psychopy.png +0 -0
  54. psychopy/assets/templates/__init__.py +0 -0
  55. psychopy/assets/touch.png +0 -0
  56. psychopy/assets/touchnext.png +0 -0
  57. psychopy/assets/window.ico +0 -0
  58. psychopy/changes/2023.1.0.md +9 -0
  59. psychopy/changes/2024.1.0.md +16 -0
  60. psychopy/changes/__init__.py +0 -0
  61. psychopy/clock.py +2 -2
  62. psychopy/colors.py +2 -1
  63. psychopy/compatibility.py +53 -1
  64. psychopy/contrib/.DS_Store +0 -0
  65. psychopy/contrib/configobj/__init__.py +10 -8
  66. psychopy/data/__init__.py +3 -2
  67. psychopy/data/base.py +5 -5
  68. psychopy/data/experiment.py +130 -4
  69. psychopy/data/routine.py +56 -0
  70. psychopy/data/staircase.py +2 -2
  71. psychopy/data/trial.py +559 -97
  72. psychopy/data/utils.py +56 -21
  73. psychopy/demos/.DS_Store +0 -0
  74. psychopy/demos/builder/.DS_Store +0 -0
  75. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  76. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  77. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  80. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  82. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  83. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  84. psychopy/demos/coder/.DS_Store +0 -0
  85. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  86. psychopy/demos/coder/iohub/.DS_Store +0 -0
  87. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  88. psychopy/event.py +30 -29
  89. psychopy/experiment/.DS_Store +0 -0
  90. psychopy/experiment/_experiment.py +6 -6
  91. psychopy/experiment/components/.DS_Store +0 -0
  92. psychopy/experiment/components/__init__.py +6 -3
  93. psychopy/experiment/components/_base.py +286 -131
  94. psychopy/experiment/components/aperture/.DS_Store +0 -0
  95. psychopy/experiment/components/brush/.DS_Store +0 -0
  96. psychopy/experiment/components/button/.DS_Store +0 -0
  97. psychopy/experiment/components/button/__init__.py +5 -1
  98. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  99. psychopy/experiment/components/camera/.DS_Store +0 -0
  100. psychopy/experiment/components/code/.DS_Store +0 -0
  101. psychopy/experiment/components/dots/.DS_Store +0 -0
  102. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  103. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  104. psychopy/experiment/components/form/.DS_Store +0 -0
  105. psychopy/experiment/components/form/__init__.py +6 -2
  106. psychopy/experiment/components/grating/.DS_Store +0 -0
  107. psychopy/experiment/components/grating/__init__.py +14 -3
  108. psychopy/experiment/components/image/.DS_Store +0 -0
  109. psychopy/experiment/components/image/__init__.py +14 -3
  110. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  111. psychopy/experiment/components/joystick/.DS_Store +0 -0
  112. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  113. psychopy/experiment/components/keyboard/__init__.py +22 -10
  114. psychopy/experiment/components/microphone/.DS_Store +0 -0
  115. psychopy/experiment/components/microphone/__init__.py +59 -39
  116. psychopy/experiment/components/mouse/.DS_Store +0 -0
  117. psychopy/experiment/components/mouse/__init__.py +44 -29
  118. psychopy/experiment/components/movie/.DS_Store +0 -0
  119. psychopy/experiment/components/movie/__init__.py +1 -1
  120. psychopy/experiment/components/panorama/.DS_Store +0 -0
  121. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  122. psychopy/experiment/components/patch/.DS_Store +0 -0
  123. psychopy/experiment/components/polygon/.DS_Store +0 -0
  124. psychopy/experiment/components/polygon/__init__.py +26 -6
  125. psychopy/experiment/components/progress/.DS_Store +0 -0
  126. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  127. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  128. psychopy/experiment/components/roi/.DS_Store +0 -0
  129. psychopy/experiment/components/roi/__init__.py +5 -0
  130. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  131. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  132. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  133. psychopy/experiment/components/settings/.DS_Store +0 -0
  134. psychopy/experiment/components/settings/__init__.py +117 -42
  135. psychopy/experiment/components/slider/.DS_Store +0 -0
  136. psychopy/experiment/components/sound/.DS_Store +0 -0
  137. psychopy/experiment/components/sound/__init__.py +54 -19
  138. psychopy/experiment/components/static/.DS_Store +0 -0
  139. psychopy/experiment/components/static/__init__.py +1 -1
  140. psychopy/experiment/components/text/.DS_Store +0 -0
  141. psychopy/experiment/components/text/__init__.py +28 -3
  142. psychopy/experiment/components/textbox/.DS_Store +0 -0
  143. psychopy/experiment/components/textbox/__init__.py +12 -2
  144. psychopy/experiment/components/unknown/.DS_Store +0 -0
  145. psychopy/experiment/components/unknown/__init__.py +1 -2
  146. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  147. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  148. psychopy/experiment/components/variable/.DS_Store +0 -0
  149. psychopy/experiment/flow.py +11 -4
  150. psychopy/experiment/loops.py +85 -37
  151. psychopy/experiment/params.py +74 -32
  152. psychopy/experiment/py2js_transpiler.py +8 -1
  153. psychopy/experiment/routines/.DS_Store +0 -0
  154. psychopy/experiment/routines/_base.py +102 -22
  155. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  156. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  157. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  158. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  159. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  160. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  161. psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
  162. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  163. psychopy/gui/wxgui.py +4 -4
  164. psychopy/hardware/.DS_Store +0 -0
  165. psychopy/hardware/__init__.py +1 -1
  166. psychopy/hardware/base.py +12 -0
  167. psychopy/hardware/camera/__init__.py +1 -15
  168. psychopy/hardware/cedrus.py +10 -11
  169. psychopy/hardware/crs/colorcal.py +13 -22
  170. psychopy/hardware/crs/optical.py +10 -20
  171. psychopy/hardware/emulator.py +17 -14
  172. psychopy/hardware/eyetracker.py +42 -118
  173. psychopy/hardware/gammasci.py +4 -15
  174. psychopy/hardware/keyboard.py +102 -10
  175. psychopy/hardware/listener.py +3 -0
  176. psychopy/hardware/microphone.py +148 -18
  177. psychopy/hardware/minolta.py +8 -15
  178. psychopy/hardware/photodiode.py +191 -16
  179. psychopy/hardware/photometer/__init__.py +11 -19
  180. psychopy/hardware/pr.py +8 -15
  181. psychopy/hardware/speaker.py +39 -4
  182. psychopy/info.py +0 -71
  183. psychopy/iohub/.DS_Store +0 -0
  184. psychopy/iohub/__init__.py +1 -1
  185. psychopy/iohub/client/__init__.py +30 -20
  186. psychopy/iohub/client/keyboard.py +24 -24
  187. psychopy/iohub/datastore/__init__.py +2 -2
  188. psychopy/iohub/datastore/util.py +2 -2
  189. psychopy/iohub/default_config.yaml +1 -1
  190. psychopy/iohub/devices/.DS_Store +0 -0
  191. psychopy/iohub/devices/__init__.py +112 -25
  192. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  193. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  194. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  195. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  196. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  197. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  198. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  199. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  200. psychopy/iohub/server.py +2 -2
  201. psychopy/iohub/start_iohub_process.py +3 -0
  202. psychopy/iohub/util/__init__.py +62 -70
  203. psychopy/layout.py +5 -5
  204. psychopy/logging.py +8 -1
  205. psychopy/microphone.py +10 -37
  206. psychopy/platform_specific/__init__.py +0 -2
  207. psychopy/platform_specific/darwin.py +1 -3
  208. psychopy/platform_specific/linux.py +31 -33
  209. psychopy/platform_specific/win32.py +38 -13
  210. psychopy/plugins/__init__.py +148 -116
  211. psychopy/plugins/util.py +39 -0
  212. psychopy/preferences/Darwin.spec +4 -2
  213. psychopy/preferences/FreeBSD.spec +4 -2
  214. psychopy/preferences/Linux.spec +4 -2
  215. psychopy/preferences/Windows.spec +4 -2
  216. psychopy/preferences/baseNoArch.spec +4 -2
  217. psychopy/preferences/preferences.py +47 -24
  218. psychopy/projects/pavlovia.py +47 -4
  219. psychopy/scripts/psyexpCompile.py +0 -4
  220. psychopy/session.py +153 -21
  221. psychopy/sound/__init__.py +31 -21
  222. psychopy/sound/_base.py +20 -3
  223. psychopy/sound/audioclip.py +320 -33
  224. psychopy/sound/backend_ptb.py +47 -58
  225. psychopy/sound/backend_pygame.py +1 -1
  226. psychopy/sound/backend_pysound.py +6 -15
  227. psychopy/sound/transcribe.py +53 -0
  228. psychopy/tests/.DS_Store +0 -0
  229. psychopy/tests/data/.DS_Store +0 -0
  230. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  231. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  232. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  233. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  234. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  235. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  243. psychopy/tests/data/correctScript/.DS_Store +0 -0
  244. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  245. psychopy/tests/data/test_session/.DS_Store +0 -0
  246. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  247. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  248. psychopy/tests/test_app/.DS_Store +0 -0
  249. psychopy/tests/test_app/conftest.py +2 -2
  250. psychopy/tests/test_app/test_speed.py +4 -1
  251. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  252. psychopy/tests/test_experiment/.DS_Store +0 -0
  253. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  254. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  255. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  256. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  257. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  258. psychopy/tests/test_experiment/test_py2js.py +1 -1
  259. psychopy/tests/test_hardware/test_keyboard.py +31 -0
  260. psychopy/tests/test_hardware/test_ports.py +1 -11
  261. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  262. psychopy/tests/test_misc/test_core.py +5 -0
  263. psychopy/tests/test_session/test_Session.py +5 -1
  264. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  265. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  266. psychopy/tests/test_visual/test_image.py +6 -5
  267. psychopy/tests/test_visual/test_textbox.py +36 -0
  268. psychopy/tests/utils.py +4 -0
  269. psychopy/tools/filetools.py +1 -1
  270. psychopy/tools/pkgtools.py +160 -137
  271. psychopy/tools/versionchooser.py +10 -10
  272. psychopy/tools/wizard.py +3 -3
  273. psychopy/visual/.DS_Store +0 -0
  274. psychopy/visual/backends/pygletbackend.py +24 -13
  275. psychopy/visual/basevisual.py +5 -11
  276. psychopy/visual/button.py +2 -14
  277. psychopy/visual/helpers.py +5 -5
  278. psychopy/visual/line.py +1 -2
  279. psychopy/visual/movie2.py +7 -816
  280. psychopy/visual/movie3.py +7 -589
  281. psychopy/visual/movies/__init__.py +8 -11
  282. psychopy/visual/movies/frame.py +5 -2
  283. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  284. psychopy/visual/noise.py +8 -7
  285. psychopy/visual/patch.py +7 -16
  286. psychopy/visual/radial.py +9 -7
  287. psychopy/visual/ratingscale.py +8 -1415
  288. psychopy/visual/secondorder.py +10 -9
  289. psychopy/visual/shape.py +7 -2
  290. psychopy/visual/text.py +1 -1
  291. psychopy/visual/textbox2/textbox2.py +28 -5
  292. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  293. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
  294. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  295. psychopy/app/Resources/click.png +0 -0
  296. psychopy/app/Resources/next.png +0 -0
  297. psychopy/experiment/components/patch/__init__.py +0 -121
  298. psychopy/experiment/components/patch/classic/patch.png +0 -0
  299. psychopy/experiment/components/patch/dark/patch.png +0 -0
  300. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  301. psychopy/experiment/components/patch/light/patch.png +0 -0
  302. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  303. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  304. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  305. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  306. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  307. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  308. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  310. psychopy/platform_specific/posix.py +0 -16
  311. psychopy/tests/test_sound/test_microphone.py +0 -217
  312. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  313. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  314. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  315. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  316. /psychopy/{app/Resources → assets}/USB.png +0 -0
  317. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  318. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  319. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  320. /psychopy/{app/Resources → assets}/default.png +0 -0
  321. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  322. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  323. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  324. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  325. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -23,7 +23,7 @@ profiling = False # turning on will save profile files in currDir
23
23
 
24
24
  import psychopy
25
25
  from psychopy import prefs
26
- from pkg_resources import parse_version
26
+ from packaging.version import Version
27
27
  from . import urls
28
28
  from . import frametracker
29
29
  from . import themes
@@ -96,21 +96,25 @@ class MenuFrame(wx.Frame, themes.handlers.ThemeMixin):
96
96
 
97
97
  self.viewMenu = wx.Menu()
98
98
  self.menuBar.Append(self.viewMenu, _translate('&View'))
99
- mtxt = _translate("&Open Builder view\t%s")
100
- self.app.IDs.openBuilderView = self.viewMenu.Append(wx.ID_ANY,
101
- mtxt,
102
- _translate("Open a new Builder view")).GetId()
99
+ mtxt = _translate("&Open Builder view\t")
100
+ self.app.IDs.openBuilderView = self.viewMenu.Append(
101
+ wx.ID_ANY,
102
+ mtxt,
103
+ _translate("Open a new Builder view")).GetId()
103
104
  self.Bind(wx.EVT_MENU, self.app.showBuilder,
104
105
  id=self.app.IDs.openBuilderView)
105
- mtxt = _translate("&Open Coder view\t%s")
106
- self.app.IDs.openCoderView = self.viewMenu.Append(wx.ID_ANY,
107
- mtxt,
108
- _translate("Open a new Coder view")).GetId()
106
+ mtxt = _translate("&Open Coder view\t")
107
+ self.app.IDs.openCoderView = self.viewMenu.Append(
108
+ wx.ID_ANY,
109
+ mtxt,
110
+ _translate("Open a new Coder view")).GetId()
109
111
  self.Bind(wx.EVT_MENU, self.app.showCoder,
110
112
  id=self.app.IDs.openCoderView)
111
113
  mtxt = _translate("&Quit\t%s")
112
- item = self.viewMenu.Append(wx.ID_EXIT, mtxt % self.app.keys['quit'],
113
- _translate("Terminate the program"))
114
+ item = self.viewMenu.Append(
115
+ wx.ID_EXIT,
116
+ mtxt % self.app.keys['quit'],
117
+ _translate("Terminate the program"))
114
118
  self.Bind(wx.EVT_MENU, self.app.quit, id=item.GetId())
115
119
  self.SetMenuBar(self.menuBar)
116
120
  self.Show()
@@ -149,9 +153,9 @@ class _Showgui_Hack():
149
153
  if not os.path.isfile(noopPath):
150
154
  code = """from psychopy import gui
151
155
  dlg = gui.Dlg().Show() # non-blocking
152
- try:
156
+ try:
153
157
  dlg.Destroy() # might as well
154
- except Exception:
158
+ except Exception:
155
159
  pass"""
156
160
  with open(noopPath, 'wb') as fd:
157
161
  fd.write(bytes(code))
@@ -162,7 +166,7 @@ class _Showgui_Hack():
162
166
  class PsychoPyApp(wx.App, handlers.ThemeMixin):
163
167
  _called_from_test = False # pytest needs to change this
164
168
 
165
- def __init__(self, arg=0, testMode=False, **kwargs):
169
+ def __init__(self, arg=0, testMode=False, startView=None, **kwargs):
166
170
  """With a wx.App some things get done here, before App.__init__
167
171
  then some further code is launched in OnInit() which occurs after
168
172
  """
@@ -209,7 +213,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
209
213
  # allocated when `OnInit` is called.
210
214
  self._sharedMemory = None
211
215
  self._singleInstanceChecker = None # checker for instances
212
- self._timer = None
216
+ self._lastInstanceCheckTime = -1.0
213
217
  # Size of the memory map buffer, needs to be large enough to hold UTF-8
214
218
  # encoded long file paths.
215
219
  self.mmap_sz = 2048
@@ -241,7 +245,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
241
245
  self.locale.AddCatalog(self.GetAppName())
242
246
 
243
247
  logging.flush()
244
- self.onInit(testMode=testMode, **kwargs)
248
+ self.onInit(testMode=testMode, startView=startView, **kwargs)
245
249
  if profiling:
246
250
  profile.disable()
247
251
  print("time to load app = {:.2f}".format(time.time()-t0))
@@ -250,9 +254,9 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
250
254
 
251
255
  # if we're on linux, check if we have the permissions file setup
252
256
  from psychopy.app.linuxconfig import (
253
- LinuxConfigDialog, linuxConfigFileExists)
257
+ LinuxConfigDialog, linuxRushAllowed)
254
258
 
255
- if not linuxConfigFileExists():
259
+ if not linuxRushAllowed():
256
260
  linuxConfDlg = LinuxConfigDialog(
257
261
  None, timeout=1000 if self.testMode else None)
258
262
  linuxConfDlg.ShowModal()
@@ -364,7 +368,6 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
364
368
  Since panels are created before loading plugins, calling this method is
365
369
  required after loading plugins which contain components to have them
366
370
  appear.
367
-
368
371
  """
369
372
  if not hasattr(self, 'builder') or self.builder is None:
370
373
  return # nop if we haven't realized the builder UI yet
@@ -375,7 +378,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
375
378
  for builderFrame in self.builder:
376
379
  builderFrame.componentButtons.populate()
377
380
 
378
- def onInit(self, showSplash=True, testMode=False, safeMode=False):
381
+ def onInit(self, showSplash=True, testMode=False, safeMode=False, startView=None):
379
382
  """This is launched immediately *after* the app initialises with
380
383
  wxPython.
381
384
 
@@ -421,7 +424,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
421
424
  # SLOW IMPORTS - these need to be imported after splash screen starts
422
425
  # but then that they end up being local so keep track in self
423
426
 
424
- from psychopy.compatibility import checkCompatibility
427
+ from psychopy.compatibility import checkCompatibility, checkUpdatesInfo
425
428
  # import coder and builder here but only use them later
426
429
  from psychopy.app import coder, builder, runner, dialogs
427
430
 
@@ -457,7 +460,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
457
460
  self.dpi = int(wx.GetDisplaySize()[0] /
458
461
  float(wx.GetDisplaySizeMM()[0]) * 25.4)
459
462
  # detect retina displays
460
- self.isRetina = self.dpi>80 and wx.Platform == '__WXMAC__'
463
+ self.isRetina = self.dpi > 80 and wx.Platform == '__WXMAC__'
461
464
  if self.isRetina:
462
465
  fontScale = 1.2 # fonts are looking tiny on macos (only retina?) right now
463
466
  # mark icons as being retina
@@ -513,43 +516,51 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
513
516
  # self._codeFont.SetPointSize(
514
517
  # self._mainFont.GetPointSize()) # unify font size
515
518
 
519
+ # load plugins so they're available before frames are created
520
+ if splash:
521
+ splash.SetText(_translate(" Loading plugins..."))
522
+ from psychopy.plugins import activatePlugins
523
+ activatePlugins()
524
+
516
525
  # create both frame for coder/builder as necess
517
526
  if splash:
518
527
  splash.SetText(_translate(" Creating frames..."))
519
-
520
- # Parse incoming call
521
- parser = argparse.ArgumentParser(prog=self)
522
- parser.add_argument('--builder', dest='builder', action="store_true")
523
- parser.add_argument('-b', dest='builder', action="store_true")
524
- parser.add_argument('--coder', dest='coder', action="store_true")
525
- parser.add_argument('-c', dest='coder', action="store_true")
526
- parser.add_argument('--runner', dest='runner', action="store_true")
527
- parser.add_argument('-r', dest='runner', action="store_true")
528
- parser.add_argument('-x', dest='direct', action='store_true')
529
- view, args = parser.parse_known_args(sys.argv)
530
- # Check from filetype if any windows need to be open
531
- if any(arg.endswith('.psyexp') for arg in args):
532
- view.builder = True
533
- exps = [file for file in args if file.endswith('.psyexp')]
534
- if any(arg.endswith('.psyrun') for arg in args):
535
- view.runner = True
536
- runlist = [file for file in args if file.endswith('.psyrun')]
537
- # If still no window specified, use default from prefs
538
- if not any(getattr(view, key) for key in ['builder', 'coder', 'runner']):
539
- if self.prefs.app['defaultView'] in view:
540
- setattr(view, self.prefs.app['defaultView'], True)
541
- elif self.prefs.app['defaultView'] == 'all':
542
- view.builder = True
543
- view.coder = True
544
- view.runner = True
545
-
546
- # set the dispatcher for standard output
547
- # self.stdStreamDispatcher = console.StdStreamDispatcher(self)
548
- # self.stdStreamDispatcher.redirect()
549
-
550
- # Create windows
551
- if view.runner:
552
- # open Runner is requested
528
+
529
+ # get starting windows
530
+ if startView in (None, []):
531
+ # if no window specified, use default from prefs
532
+ if self.prefs.app['defaultView'] == 'all':
533
+ startView = ["builder", "coder", "runner"]
534
+ elif self.prefs.app['defaultView'] == "last":
535
+ startView = self.prefs.appData['lastFrame'].split("-")
536
+ elif self.prefs.app['defaultView'] in ["builder", "coder", "runner"]:
537
+ startView = self.prefs.app['defaultView']
538
+ else:
539
+ startView = ["builder"]
540
+ # if specified as a single string, convert to list
541
+ if isinstance(startView, str):
542
+ startView = [startView]
543
+
544
+ # get files to open from commandline args
545
+ for arg in sys.argv:
546
+ if arg.endswith(".psyexp"):
547
+ exps.append(arg)
548
+ if arg.endswith(".py"):
549
+ scripts.append(arg)
550
+ if "runner" not in startView and arg.endswith(".psyrun"):
551
+ runlist.append(arg)
552
+
553
+ # show frames according to files
554
+ if exps and "builder" not in startView:
555
+ startView.append("builder")
556
+ if scripts and "coder" not in startView:
557
+ startView.append("coder")
558
+ if runlist and "runner" not in startView:
559
+ startView.append("runner")
560
+
561
+ # create windows
562
+ if "runner" in startView:
563
+ # open Runner if requested
553
564
  try:
554
565
  self.showRunner(fileList=runlist)
555
566
  except Exception as err:
@@ -565,7 +576,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
565
576
  "\n".join(traceback.format_exception(err))
566
577
  )
567
578
 
568
- if view.coder:
579
+ if "coder" in startView:
569
580
  # open Coder if requested
570
581
  try:
571
582
  self.showCoder(fileList=scripts)
@@ -580,7 +591,8 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
580
591
  "\n".join(traceback.format_exception(err))
581
592
  )
582
593
  self.showCoder()
583
- if view.builder:
594
+
595
+ if "builder" in startView:
584
596
  # open Builder if requested
585
597
  try:
586
598
  self.showBuilder(fileList=exps)
@@ -593,11 +605,12 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
593
605
  "Requested: {}\n"
594
606
  "Err: {}"
595
607
  ).format(exps, err))
596
-
597
- if view.direct:
608
+
609
+ if "direct" in startView:
598
610
  self.showRunner()
599
- for exp in [file for file in args if file.endswith('.psyexp') or file.endswith('.py')]:
611
+ for exp in [file for file in sys.argv if file.endswith('.psyexp') or file.endswith('.py')]:
600
612
  self.runner.panel.runFile(exp)
613
+
601
614
  # if we started a busy cursor which never finished, finish it now
602
615
  if wx.IsBusy():
603
616
  wx.EndBusyCursor()
@@ -611,6 +624,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
611
624
 
612
625
  prefsConn = self.prefs.connections
613
626
 
627
+ # check for potential compatability issues
614
628
  ok, msg = checkCompatibility(last, self.version, self.prefs, fix=True)
615
629
  # tell the user what has changed
616
630
  if not ok and not self.firstRun and not self.testMode:
@@ -619,11 +633,20 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
619
633
  title=title)
620
634
  dlg.ShowModal()
621
635
 
636
+ # check for non-issue updates
637
+ messages = checkUpdatesInfo(old=last, new=self.version)
638
+ if messages:
639
+ dlg = dialogs.RichMessageDialog(
640
+ parent=None,
641
+ message="\n\n".join(messages)
642
+ )
643
+ dlg.Show()
644
+
622
645
  if self.prefs.app['showStartupTips'] and not self.testMode:
623
646
  tipFile = os.path.join(
624
647
  self.prefs.paths['resources'], _translate("tips.txt"))
625
648
  tipIndex = self.prefs.appData['tipIndex']
626
- if parse_version(wx.__version__) >= parse_version('4.0.0a1'):
649
+ if Version(wx.__version__) >= Version('4.0.0a1'):
627
650
  tp = wx.adv.CreateFileTipProvider(tipFile, tipIndex)
628
651
  showTip = wx.adv.ShowTip(None, tp)
629
652
  else:
@@ -639,7 +662,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
639
662
 
640
663
  # doing this once subsequently enables the app to open & switch among
641
664
  # wx-windows on some platforms (Mac 10.9.4) with wx-3.0:
642
- v = parse_version
665
+ v = Version
643
666
  if sys.platform == 'darwin':
644
667
  if v('3.0') <= v(wx.__version__) < v('4.0'):
645
668
  _Showgui_Hack() # returns ~immediately, no display
@@ -653,21 +676,6 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
653
676
  if self.coder:
654
677
  self.coder.setOutputWindow() # takes control of sys.stdout
655
678
 
656
- # if the program gets here, there are no other instances running
657
- self._timer = wx.PyTimer(self._bgCheckAndLoad)
658
- self._timer.Start(250)
659
-
660
- # load plugins after the app has been mostly realized
661
- if splash:
662
- splash.SetText(_translate(" Loading plugins..."))
663
-
664
- # Load plugins here after everything is realized, make sure that we
665
- # refresh UI elements which are affected by plugins (e.g. the component
666
- # panel in Builder).
667
- from psychopy.plugins import activatePlugins
668
- activatePlugins()
669
- self._refreshComponentPanels()
670
-
671
679
  # flush any errors to the last run log file
672
680
  logging.flush()
673
681
  sys.stdout.flush()
@@ -677,16 +685,17 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
677
685
 
678
686
  return True
679
687
 
680
- def _bgCheckAndLoad(self):
681
- """Check shared memory for messages from other instances. This only is
682
- called periodically in the first and only instance of PsychoPy.
688
+ def _bgCheckAndLoad(self, *args):
689
+ """Check shared memory for messages from other instances.
690
+
691
+ This only is called periodically in the first and only instance of
692
+ PsychoPy running on the machine. This is called within the app's main
693
+ idle event method, with a frequency no more that once per second.
683
694
 
684
695
  """
685
696
  if not self._appLoaded: # only open files if we have a UI
686
697
  return
687
698
 
688
- self._timer.Stop()
689
-
690
699
  self._sharedMemory.seek(0)
691
700
  if self._sharedMemory.read(1) == b'+': # available data
692
701
  data = self._sharedMemory.read(self.mmap_sz - 1)
@@ -706,8 +715,6 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
706
715
  else:
707
716
  topWindow.Raise()
708
717
 
709
- self._timer.Start(1000) # 1 second interval
710
-
711
718
  @property
712
719
  def appLoaded(self):
713
720
  """`True` if the app has been fully loaded (`bool`)."""
@@ -846,8 +853,8 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
846
853
  from .builder.builder import BuilderFrame
847
854
  title = "PsychoPy Builder (v%s)"
848
855
  self.builder = BuilderFrame(None, -1,
849
- title=title % self.version,
850
- fileName=fileName, app=self)
856
+ title=title % self.version,
857
+ fileName=fileName, app=self)
851
858
  self.builder.Show(True)
852
859
  self.builder.Raise()
853
860
  self.SetTopWindow(self.builder)
@@ -887,9 +894,9 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
887
894
  title = "PsychoPy Runner (v{})".format(self.version)
888
895
  wx.BeginBusyCursor()
889
896
  self.runner = RunnerFrame(parent=None,
890
- id=-1,
891
- title=title,
892
- app=self)
897
+ id=-1,
898
+ title=title,
899
+ app=self)
893
900
  self.updateWindowMenu()
894
901
  wx.EndBusyCursor()
895
902
  return self.runner
@@ -967,7 +974,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
967
974
  import socket
968
975
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
969
976
  sock.settimeout(1.0)
970
- iohubAddress = '127.0.0.1', 9034
977
+ iohubAddress = '127.0.0.1', 9036
971
978
  import msgpack
972
979
  txData = msgpack.Packer().pack(('STOP_IOHUB_SERVER',))
973
980
  return sock.sendto(txData, iohubAddress)
@@ -1009,13 +1016,16 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
1009
1016
  return # user cancelled quit
1010
1017
 
1011
1018
  # save info about current frames for next run
1012
- if self.coder and len(self.getAllFrames("builder")) == 0:
1013
- self.prefs.appData['lastFrame'] = 'coder'
1014
- elif self.coder is None:
1015
- self.prefs.appData['lastFrame'] = 'builder'
1016
- else:
1017
- self.prefs.appData['lastFrame'] = 'both'
1018
-
1019
+ openFrames = []
1020
+ for frame in self.getAllFrames():
1021
+ if type(frame).__name__ == "BuilderFrame" and "builder" not in openFrames:
1022
+ openFrames.append("builder")
1023
+ if type(frame).__name__ == "CoderFrame" and "coder" not in openFrames:
1024
+ openFrames.append("coder")
1025
+ if type(frame).__name__ == "RunnerFrame" and "runner" not in openFrames:
1026
+ openFrames.append("runner")
1027
+ self.prefs.appData['lastFrame'] = "-".join(openFrames)
1028
+ # save current version for next run
1019
1029
  self.prefs.appData['lastVersion'] = self.version
1020
1030
  # update app data while closing each frame
1021
1031
  # start with an empty list to be appended by each frame
@@ -1032,7 +1042,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
1032
1042
  pass # we don't care if this fails - we're quitting anyway
1033
1043
  # must do this before destroying the frame?
1034
1044
  self.prefs.saveAppData()
1035
- #self.Destroy()
1045
+ # self.Destroy()
1036
1046
 
1037
1047
  # Reset streams back to default
1038
1048
  sys.stdout = sys.__stdout__
@@ -1059,7 +1069,7 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
1059
1069
  "For stimulus generation and experimental control in Python.\n"
1060
1070
  "PsychoPy depends on your feedback. If something doesn't work\n"
1061
1071
  "then let us know at psychopy-users@googlegroups.com")
1062
- if parse_version(wx.__version__) >= parse_version('4.0a1'):
1072
+ if Version(wx.__version__) >= Version('4.0a1'):
1063
1073
  info = wx.adv.AboutDialogInfo()
1064
1074
  showAbout = wx.adv.AboutBox
1065
1075
  else:
@@ -1178,8 +1188,23 @@ class PsychoPyApp(wx.App, handlers.ThemeMixin):
1178
1188
  self._allFrames.remove(entry)
1179
1189
 
1180
1190
  def onIdle(self, evt):
1191
+ """Run idle tasks, including background checks for new instances.
1192
+
1193
+ Some of these run once while others are added as tasks to be run
1194
+ repeatedly.
1195
+
1196
+ """
1181
1197
  from . import idle
1182
- idle.doIdleTasks(app=self)
1198
+ idle.doIdleTasks(app=self) # run once
1199
+
1200
+ # do the background check for new instances, check each second
1201
+ tNow = time.time()
1202
+ if tNow - self._lastInstanceCheckTime > 1.0: # every second
1203
+ if not self.testMode:
1204
+ self._bgCheckAndLoad()
1205
+
1206
+ self._lastInstanceCheckTime = time.time()
1207
+
1183
1208
  evt.Skip()
1184
1209
 
1185
1210
  @property
@@ -23,7 +23,7 @@ import numpy
23
23
  import requests
24
24
  import io
25
25
 
26
- from pkg_resources import parse_version
26
+ from packaging.version import Version
27
27
  import wx.stc
28
28
  from wx.lib import scrolledpanel
29
29
  from wx.lib import platebtn
@@ -50,7 +50,7 @@ try:
50
50
  except ImportError:
51
51
  from wx import PseudoDC
52
52
 
53
- if parse_version(wx.__version__) < parse_version('4.0.3'):
53
+ if Version(wx.__version__) < Version('4.0.3'):
54
54
  wx.NewIdRef = wx.NewId
55
55
 
56
56
  from psychopy.localization import _translate
@@ -64,7 +64,7 @@ from psychopy.tools.filetools import mergeFolder
64
64
  from .dialogs import (DlgComponentProperties, DlgExperimentProperties,
65
65
  DlgCodeComponentProperties, DlgLoopProperties,
66
66
  ParamNotebook, DlgNewRoutine, BuilderFindDlg)
67
- from ..utils import (BasePsychopyToolbar, HoverButton, WindowFrozen,
67
+ from ..utils import (BasePsychopyToolbar, HoverButton, ThemedPanel, WindowFrozen,
68
68
  FileDropTarget, FrameSwitcher, updateDemosMenu,
69
69
  ToggleButtonArray, HoverMixin)
70
70
 
@@ -863,6 +863,10 @@ class BuilderFrame(BaseAuiFrame, handlers.ThemeMixin):
863
863
  self.filename = newPath
864
864
  self.fileExists = True
865
865
  self.fileSave(event=None, filename=newPath)
866
+ # enable/disable reveal button
867
+ if hasattr(self, "menuIDs"):
868
+ self.fileMenu.Enable(self.menuIDs.ID_REVEAL, True)
869
+ # update pavlovia project
866
870
  self.project = pavlovia.getProject(filename)
867
871
  returnVal = 1
868
872
  dlg.Destroy()
@@ -1632,10 +1636,6 @@ class BuilderFrame(BaseAuiFrame, handlers.ThemeMixin):
1632
1636
  # Run debug function from runner
1633
1637
  self.app.runner.panel.runOnlineDebug(evt=evt)
1634
1638
 
1635
- def setPavloviaUser(self, user):
1636
- # TODO: update user icon on button to user avatar
1637
- pass
1638
-
1639
1639
  @property
1640
1640
  def project(self):
1641
1641
  """A PavloviaProject object if one is known for this experiment
@@ -3289,6 +3289,8 @@ class ComponentsPanel(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
3289
3289
  self.pluginBtn.SetBitmapPressed(icon)
3290
3290
  self.pluginBtn.SetBitmapFocus(icon)
3291
3291
 
3292
+ self.Refresh()
3293
+
3292
3294
  def addToFavorites(self, comp):
3293
3295
  name = comp.__name__
3294
3296
  # Mark component as a favorite
@@ -3478,7 +3480,7 @@ class FlowPanel(wx.Panel, handlers.ThemeMixin):
3478
3480
  self.sizer = wx.BoxSizer(wx.HORIZONTAL)
3479
3481
  self.SetSizer(self.sizer)
3480
3482
  # buttons panel
3481
- self.btnPanel = wx.Panel(self)
3483
+ self.btnPanel = ThemedPanel(self)
3482
3484
  self.btnPanel.sizer = wx.BoxSizer(wx.VERTICAL)
3483
3485
  self.btnPanel.SetSizer(self.btnPanel.sizer)
3484
3486
  self.sizer.Add(self.btnPanel, border=6, flag=wx.EXPAND | wx.ALL)
@@ -3539,7 +3541,7 @@ class FlowCanvas(wx.ScrolledWindow, handlers.ThemeMixin):
3539
3541
 
3540
3542
  # create a PseudoDC to record our drawing
3541
3543
  self.pdc = PseudoDC()
3542
- if parse_version(wx.__version__) < parse_version('4.0.0a1'):
3544
+ if Version(wx.__version__) < Version('4.0.0a1'):
3543
3545
  self.pdc.DrawRoundedRectangle = self.pdc.DrawRoundedRectangleRect
3544
3546
  self.pen_cache = {}
3545
3547
  self.brush_cache = {}
@@ -4625,6 +4627,9 @@ class BuilderRibbon(ribbon.FrameRibbon):
4625
4627
 
4626
4628
  self.addSeparator()
4627
4629
 
4630
+ # --- Plugin sections ---
4631
+ self.addPluginSections("psychopy.app.builder")
4632
+
4628
4633
  # --- Views ---
4629
4634
  self.addStretchSpacer()
4630
4635
  self.addSeparator()
@@ -4651,7 +4656,6 @@ class BuilderRibbon(ribbon.FrameRibbon):
4651
4656
  callback=parent.app.showRunner
4652
4657
  )
4653
4658
 
4654
-
4655
4659
  def extractText(stream):
4656
4660
  """Take a byte stream (or any file object of type b?) and return
4657
4661
 
@@ -212,8 +212,7 @@ class ParamCtrls():
212
212
  elif param.inputType == 'table':
213
213
  self.valueCtrl = paramCtrls.TableCtrl(
214
214
  parent,
215
- val=param.val,
216
- valType=param.valType,
215
+ param=param,
217
216
  fieldName=fieldName,
218
217
  size=wx.Size(int(self.valueWidth), 24))
219
218
  elif param.inputType == 'color':
@@ -308,7 +307,7 @@ class ParamCtrls():
308
307
  # set by integer index, not string value
309
308
  self.updateCtrl.SetSelection(index)
310
309
 
311
- if param.allowedUpdates != None and len(param.allowedUpdates) == 1:
310
+ if self.updateCtrl is not None and len(self.updateCtrl.GetItems()) == 1:
312
311
  self.updateCtrl.Disable() # visible but can't be changed
313
312
 
314
313
  def _getCtrlValue(self, ctrl):
@@ -479,6 +478,7 @@ class StartStopCtrls(wx.GridBagSizer):
479
478
  # Add ctrl
480
479
  self.ctrls[name] = wx.TextCtrl(parent,
481
480
  value=str(param.val), size=wx.Size(-1, 24))
481
+ self.ctrls[name].SetToolTip(param.hint)
482
482
  self.ctrls[name].Bind(wx.EVT_TEXT, self.updateCodeFont)
483
483
  self.updateCodeFont(self.ctrls[name])
484
484
  self.label = wx.StaticText(parent, label=param.label)
@@ -522,6 +522,9 @@ class StartStopCtrls(wx.GridBagSizer):
522
522
  self.estimLabel.Show(visible)
523
523
  if hasattr(self, "label"):
524
524
  self.label.Show(visible)
525
+ # show/hide dollars
526
+ if hasattr(self, "dollar"):
527
+ self.dollar.Show(visible)
525
528
  # Set value to None if hidden (specific to start/stop)
526
529
  if not visible:
527
530
  if "startVal" in self.ctrls:
@@ -1675,7 +1678,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1675
1678
  style=wx.FD_OPEN, defaultDir=str(self.expPath))
1676
1679
  if dlg.ShowModal() == wx.ID_OK:
1677
1680
  self.conditionsFile = dlg.GetPath()
1678
- self.constantsCtrls['conditionsFile'].valueCtrl.SetValue(
1681
+ self.currentCtrls['conditionsFile'].valueCtrl.SetValue(
1679
1682
  self.conditionsFile
1680
1683
  )
1681
1684
  self.updateSummary()
@@ -1692,10 +1695,7 @@ class DlgLoopProperties(_BaseParamsDlg):
1692
1695
  or message, as appropriate. Upon completion this will disable the update button as
1693
1696
  we are now up to date.
1694
1697
  """
1695
- if "MultiStairHandler" in self.type:
1696
- self.conditionsFile = self.multiStairCtrls['conditionsFile'].valueCtrl.GetValue()
1697
- else:
1698
- self.conditionsFile = self.constantsCtrls['conditionsFile'].valueCtrl.GetValue()
1698
+ self.conditionsFile = self.currentCtrls['conditionsFile'].valueCtrl.GetValue()
1699
1699
  # Check whether the file and path are the same as previously
1700
1700
  isSameFilePathAndName = self.conditionsFileAbs == self.conditionsFileOrig
1701
1701
  # Start off with no message and assumed valid
@@ -12,7 +12,7 @@ import sys
12
12
  import pickle
13
13
  import wx
14
14
  from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
15
- from pkg_resources import parse_version
15
+ from packaging.version import Version
16
16
 
17
17
  from psychopy import gui
18
18
  from psychopy.experiment.utils import valid_var_re
@@ -116,7 +116,7 @@ class DlgConditions(wx.Dialog):
116
116
  except wx._core.PyNoAppError: # only needed during development?
117
117
  self.madeApp = True
118
118
  global app
119
- if parse_version(wx.__version__) < parse_version('2.9'):
119
+ if Version(wx.__version__) < Version('2.9'):
120
120
  app = wx.PySimpleApp()
121
121
  else:
122
122
  app = wx.App(False)
@@ -383,46 +383,45 @@ class DlgConditions(wx.Dialog):
383
383
  thisVal = repr(thisVal) # handles quoting ', ", ''' etc
384
384
  # convert to requested type:
385
385
  try:
386
- # todo: replace exec() with eval()
387
386
  if self.hasHeader and row == 0:
388
387
  # header always str
389
388
  val = self.inputFields[row][col].GetValue()
390
389
  lastRow.append(str(val))
391
390
  elif thisType in ['float', 'int', 'long']:
392
- exec("lastRow.append(" + thisType +
391
+ eval("lastRow.append(" + thisType +
393
392
  '(' + thisVal + "))")
394
393
  elif thisType in ['list']:
395
394
  thisVal = thisVal.lstrip('[').strip(']')
396
- exec("lastRow.append(" + thisType +
395
+ eval("lastRow.append(" + thisType +
397
396
  '([' + thisVal + "]))")
398
397
  elif thisType in ['tuple']:
399
398
  thisVal = thisVal.lstrip('(').strip(')')
400
399
  if thisVal:
401
- exec("lastRow.append((" +
400
+ eval("lastRow.append((" +
402
401
  thisVal.strip(',') + ",))")
403
402
  else:
404
403
  lastRow.append(tuple(()))
405
404
  elif thisType in ['array']:
406
405
  thisVal = thisVal.lstrip('[').strip(']')
407
- exec("lastRow.append(numpy.array" +
406
+ eval("lastRow.append(numpy.array" +
408
407
  '("[' + thisVal + ']"))')
409
408
  elif thisType in ['utf-8', 'bool']:
410
409
  if thisType == 'utf-8':
411
410
  thisType = 'unicode'
412
- exec("lastRow.append(" + thisType +
411
+ eval("lastRow.append(" + thisType +
413
412
  '(' + thisVal + '))')
414
413
  elif thisType in ['str']:
415
- exec("lastRow.append(str(" + thisVal + "))")
414
+ eval("lastRow.append(str(" + thisVal + "))")
416
415
  elif thisType in ['file']:
417
- exec("lastRow.append(repr(" + thisVal + "))")
416
+ eval("lastRow.append(repr(" + thisVal + "))")
418
417
  else:
419
- exec("lastRow.append(" + str(thisVal) + ')')
418
+ eval("lastRow.append(" + str(thisVal) + ')')
420
419
  except ValueError as msg:
421
420
  print('ValueError:', msg, '; using unicode')
422
- exec("lastRow.append(" + str(thisVal) + ')')
421
+ eval("lastRow.append(" + str(thisVal) + ')')
423
422
  except NameError as msg:
424
423
  print('NameError:', msg, '; using unicode')
425
- exec("lastRow.append(" + repr(thisVal) + ')')
424
+ eval("lastRow.append(" + repr(thisVal) + ')')
426
425
  self.data.append(lastRow)
427
426
  if self.trim:
428
427
  # the corresponding data have already been removed