psychopy 2024.1.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 (2950) hide show
  1. psychopy/CHANGELOG.txt +3571 -0
  2. psychopy/LICENSE.txt +11 -0
  3. psychopy/LICENSES.txt +55 -0
  4. psychopy/__init__.py +74 -0
  5. psychopy/alerts/__init__.py +65 -0
  6. psychopy/alerts/_alerts.py +184 -0
  7. psychopy/alerts/_errorHandler.py +51 -0
  8. psychopy/alerts/alertsCatalogue/1000.yaml +0 -0
  9. psychopy/alerts/alertsCatalogue/1100.yaml +0 -0
  10. psychopy/alerts/alertsCatalogue/1500.yaml +0 -0
  11. psychopy/alerts/alertsCatalogue/2000.yaml +0 -0
  12. psychopy/alerts/alertsCatalogue/2100.yaml +0 -0
  13. psychopy/alerts/alertsCatalogue/2110.yaml +0 -0
  14. psychopy/alerts/alertsCatalogue/2115.yaml +21 -0
  15. psychopy/alerts/alertsCatalogue/2120.yaml +22 -0
  16. psychopy/alerts/alertsCatalogue/2150.yaml +0 -0
  17. psychopy/alerts/alertsCatalogue/2155.yaml +21 -0
  18. psychopy/alerts/alertsCatalogue/2300.yaml +0 -0
  19. psychopy/alerts/alertsCatalogue/2500.yaml +0 -0
  20. psychopy/alerts/alertsCatalogue/3000.yaml +0 -0
  21. psychopy/alerts/alertsCatalogue/3100.yaml +0 -0
  22. psychopy/alerts/alertsCatalogue/3110.yaml +25 -0
  23. psychopy/alerts/alertsCatalogue/3115.yaml +24 -0
  24. psychopy/alerts/alertsCatalogue/3200.yaml +0 -0
  25. psychopy/alerts/alertsCatalogue/3400.yaml +0 -0
  26. psychopy/alerts/alertsCatalogue/4000.yaml +0 -0
  27. psychopy/alerts/alertsCatalogue/4051.yaml +19 -0
  28. psychopy/alerts/alertsCatalogue/4052.yaml +19 -0
  29. psychopy/alerts/alertsCatalogue/4100.yaml +0 -0
  30. psychopy/alerts/alertsCatalogue/4105.yaml +18 -0
  31. psychopy/alerts/alertsCatalogue/4115.yaml +21 -0
  32. psychopy/alerts/alertsCatalogue/4120.yaml +19 -0
  33. psychopy/alerts/alertsCatalogue/4125.yaml +23 -0
  34. psychopy/alerts/alertsCatalogue/4200.yaml +0 -0
  35. psychopy/alerts/alertsCatalogue/4205.yaml +19 -0
  36. psychopy/alerts/alertsCatalogue/4210.yaml +19 -0
  37. psychopy/alerts/alertsCatalogue/4305.yaml +21 -0
  38. psychopy/alerts/alertsCatalogue/4310.yaml +21 -0
  39. psychopy/alerts/alertsCatalogue/4315.yaml +21 -0
  40. psychopy/alerts/alertsCatalogue/4320.yaml +19 -0
  41. psychopy/alerts/alertsCatalogue/4325.yaml +23 -0
  42. psychopy/alerts/alertsCatalogue/4330.yaml +19 -0
  43. psychopy/alerts/alertsCatalogue/4335.yaml +19 -0
  44. psychopy/alerts/alertsCatalogue/4340.yaml +19 -0
  45. psychopy/alerts/alertsCatalogue/4405.yaml +24 -0
  46. psychopy/alerts/alertsCatalogue/4505.yaml +22 -0
  47. psychopy/alerts/alertsCatalogue/4510.yaml +25 -0
  48. psychopy/alerts/alertsCatalogue/4520.yaml +26 -0
  49. psychopy/alerts/alertsCatalogue/4530.yaml +23 -0
  50. psychopy/alerts/alertsCatalogue/4540.yaml +19 -0
  51. psychopy/alerts/alertsCatalogue/4545.yaml +22 -0
  52. psychopy/alerts/alertsCatalogue/4550.yaml +19 -0
  53. psychopy/alerts/alertsCatalogue/4605.yaml +22 -0
  54. psychopy/alerts/alertsCatalogue/4610.yaml +22 -0
  55. psychopy/alerts/alertsCatalogue/4615.yaml +19 -0
  56. psychopy/alerts/alertsCatalogue/4705.yaml +19 -0
  57. psychopy/alerts/alertsCatalogue/4710.yaml +19 -0
  58. psychopy/alerts/alertsCatalogue/5000.yaml +0 -0
  59. psychopy/alerts/alertsCatalogue/5055.yaml +25 -0
  60. psychopy/alerts/alertsCatalogue/6000.yaml +0 -0
  61. psychopy/alerts/alertsCatalogue/6105.yaml +18 -0
  62. psychopy/alerts/alertsCatalogue/7105.yaml +19 -0
  63. psychopy/alerts/alertsCatalogue/8105.yaml +21 -0
  64. psychopy/alerts/alertsCatalogue/8110.yaml +21 -0
  65. psychopy/alerts/alertsCatalogue/9998.yaml +19 -0
  66. psychopy/alerts/alertsCatalogue/9999.yaml +19 -0
  67. psychopy/alerts/alertsCatalogue/alertCategories.yaml +109 -0
  68. psychopy/alerts/alertsCatalogue/alertTemplate.yaml +19 -0
  69. psychopy/alerts/alertsCatalogue/alertmsg.py +144 -0
  70. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +34 -0
  71. psychopy/alerts/alerttools.py +338 -0
  72. psychopy/app/Resources/Psychopy Window Favicon@16w.png +0 -0
  73. psychopy/app/Resources/Psychopy Window Favicon@32w.png +0 -0
  74. psychopy/app/Resources/README.md +12 -0
  75. psychopy/app/Resources/USB-C.png +0 -0
  76. psychopy/app/Resources/USB.png +0 -0
  77. psychopy/app/Resources/__init__.py +0 -0
  78. psychopy/app/Resources/builder.ico +0 -0
  79. psychopy/app/Resources/classic/FlowBottom_CompLeft.png +0 -0
  80. psychopy/app/Resources/classic/FlowBottom_CompRight.png +0 -0
  81. psychopy/app/Resources/classic/FlowTop_CompLeft.png +0 -0
  82. psychopy/app/Resources/classic/FlowTop_CompRight.png +0 -0
  83. psychopy/app/Resources/classic/README.txt +9 -0
  84. psychopy/app/Resources/classic/__init__.py +0 -0
  85. psychopy/app/Resources/classic/_layouts.ai +1138 -1
  86. psychopy/app/Resources/classic/add.png +0 -0
  87. psychopy/app/Resources/classic/add@2x.png +0 -0
  88. psychopy/app/Resources/classic/addExp32.png +0 -0
  89. psychopy/app/Resources/classic/alerts.png +0 -0
  90. psychopy/app/Resources/classic/beta.png +0 -0
  91. psychopy/app/Resources/classic/browser.png +0 -0
  92. psychopy/app/Resources/classic/browser@2x.png +0 -0
  93. psychopy/app/Resources/classic/bug16.png +0 -0
  94. psychopy/app/Resources/classic/circle_mask.png +0 -0
  95. psychopy/app/Resources/classic/circle_mask@2x.png +0 -0
  96. psychopy/app/Resources/classic/clear.png +0 -0
  97. psychopy/app/Resources/classic/clear@2x.png +0 -0
  98. psychopy/app/Resources/classic/coderclass16.png +0 -0
  99. psychopy/app/Resources/classic/coderfunc16.png +0 -0
  100. psychopy/app/Resources/classic/coderimport16.png +0 -0
  101. psychopy/app/Resources/classic/coderjs16.png +0 -0
  102. psychopy/app/Resources/classic/coderpython16.png +0 -0
  103. psychopy/app/Resources/classic/codervar16.png +0 -0
  104. psychopy/app/Resources/classic/cogwindow32.png +0 -0
  105. psychopy/app/Resources/classic/color32.png +0 -0
  106. psychopy/app/Resources/classic/compile32.png +0 -0
  107. psychopy/app/Resources/classic/compile_js.png +0 -0
  108. psychopy/app/Resources/classic/compile_js@2x.png +0 -0
  109. psychopy/app/Resources/classic/compile_py.png +0 -0
  110. psychopy/app/Resources/classic/compile_py@2x.png +0 -0
  111. psychopy/app/Resources/classic/copy16.png +0 -0
  112. psychopy/app/Resources/classic/currentFile.png +0 -0
  113. psychopy/app/Resources/classic/currentFile@2x.png +0 -0
  114. psychopy/app/Resources/classic/delete16.png +0 -0
  115. psychopy/app/Resources/classic/desktop.png +0 -0
  116. psychopy/app/Resources/classic/desktop@2x.png +0 -0
  117. psychopy/app/Resources/classic/dirup16.png +0 -0
  118. psychopy/app/Resources/classic/docclose16.png +0 -0
  119. psychopy/app/Resources/classic/download.png +0 -0
  120. psychopy/app/Resources/classic/edit.png +0 -0
  121. psychopy/app/Resources/classic/edit@2x.png +0 -0
  122. psychopy/app/Resources/classic/editbtn.png +0 -0
  123. psychopy/app/Resources/classic/editbtn16.png +0 -0
  124. psychopy/app/Resources/classic/editbtn16@2x.png +0 -0
  125. psychopy/app/Resources/classic/editbtn@2x.png +0 -0
  126. psychopy/app/Resources/classic/email.png +0 -0
  127. psychopy/app/Resources/classic/email@2x.png +0 -0
  128. psychopy/app/Resources/classic/experiment.png +0 -0
  129. psychopy/app/Resources/classic/experiment@2x.png +0 -0
  130. psychopy/app/Resources/classic/expsettings.png +0 -0
  131. psychopy/app/Resources/classic/expsettings@2x.png +0 -0
  132. psychopy/app/Resources/classic/file.png +0 -0
  133. psychopy/app/Resources/classic/file@2x.png +0 -0
  134. psychopy/app/Resources/classic/filecsv16.png +0 -0
  135. psychopy/app/Resources/classic/fileimage16.png +0 -0
  136. psychopy/app/Resources/classic/filenew.png +0 -0
  137. psychopy/app/Resources/classic/filenew32.png +0 -0
  138. psychopy/app/Resources/classic/filenew@2x.png +0 -0
  139. psychopy/app/Resources/classic/fileopen.png +0 -0
  140. psychopy/app/Resources/classic/fileopen32.png +0 -0
  141. psychopy/app/Resources/classic/fileopen@2x.png +0 -0
  142. psychopy/app/Resources/classic/filesave.png +0 -0
  143. psychopy/app/Resources/classic/filesave32.png +0 -0
  144. psychopy/app/Resources/classic/filesave@2x.png +0 -0
  145. psychopy/app/Resources/classic/filesaveas.png +0 -0
  146. psychopy/app/Resources/classic/filesaveas32.png +0 -0
  147. psychopy/app/Resources/classic/filesaveas@2x.png +0 -0
  148. psychopy/app/Resources/classic/fileunknown16.png +0 -0
  149. psychopy/app/Resources/classic/filter.png +0 -0
  150. psychopy/app/Resources/classic/filter@2x.png +0 -0
  151. psychopy/app/Resources/classic/folder-open16.png +0 -0
  152. psychopy/app/Resources/classic/folder16.png +0 -0
  153. psychopy/app/Resources/classic/foldernew16.png +0 -0
  154. psychopy/app/Resources/classic/fork.png +0 -0
  155. psychopy/app/Resources/classic/fork@2x.png +0 -0
  156. psychopy/app/Resources/classic/github.png +0 -0
  157. psychopy/app/Resources/classic/github@2x.png +0 -0
  158. psychopy/app/Resources/classic/globe.png +0 -0
  159. psychopy/app/Resources/classic/globe@2x.png +0 -0
  160. psychopy/app/Resources/classic/globe_bug.png +0 -0
  161. psychopy/app/Resources/classic/globe_bug@2x.png +0 -0
  162. psychopy/app/Resources/classic/globe_greensync.png +0 -0
  163. psychopy/app/Resources/classic/globe_greensync@2x.png +0 -0
  164. psychopy/app/Resources/classic/globe_info.png +0 -0
  165. psychopy/app/Resources/classic/globe_info@2x.png +0 -0
  166. psychopy/app/Resources/classic/globe_magnifier.png +0 -0
  167. psychopy/app/Resources/classic/globe_magnifier@2x.png +0 -0
  168. psychopy/app/Resources/classic/globe_run.png +0 -0
  169. psychopy/app/Resources/classic/globe_run@2x.png +0 -0
  170. psychopy/app/Resources/classic/globe_user.png +0 -0
  171. psychopy/app/Resources/classic/globe_user@2x.png +0 -0
  172. psychopy/app/Resources/classic/goto16.png +0 -0
  173. psychopy/app/Resources/classic/greendot.png +0 -0
  174. psychopy/app/Resources/classic/greendot@2x.png +0 -0
  175. psychopy/app/Resources/classic/greenglobe.png +0 -0
  176. psychopy/app/Resources/classic/greenglobe@2x.png +0 -0
  177. psychopy/app/Resources/classic/greenglobe_bug.png +0 -0
  178. psychopy/app/Resources/classic/greenglobe_bug@2x.png +0 -0
  179. psychopy/app/Resources/classic/greenglobe_greensync.png +0 -0
  180. psychopy/app/Resources/classic/greenglobe_greensync@2x.png +0 -0
  181. psychopy/app/Resources/classic/greenglobe_info.png +0 -0
  182. psychopy/app/Resources/classic/greenglobe_info@2x.png +0 -0
  183. psychopy/app/Resources/classic/greenglobe_magnifier.png +0 -0
  184. psychopy/app/Resources/classic/greenglobe_magnifier@2x.png +0 -0
  185. psychopy/app/Resources/classic/greenglobe_run.png +0 -0
  186. psychopy/app/Resources/classic/greenglobe_run@2x.png +0 -0
  187. psychopy/app/Resources/classic/greenglobe_user.png +0 -0
  188. psychopy/app/Resources/classic/greenglobe_user@2x.png +0 -0
  189. psychopy/app/Resources/classic/greydot.png +0 -0
  190. psychopy/app/Resources/classic/greydot@2x.png +0 -0
  191. psychopy/app/Resources/classic/greytick.png +0 -0
  192. psychopy/app/Resources/classic/greytick@2x.png +0 -0
  193. psychopy/app/Resources/classic/invalid_img.png +0 -0
  194. psychopy/app/Resources/classic/jsPilot.png +0 -0
  195. psychopy/app/Resources/classic/jsPilot@2x.png +0 -0
  196. psychopy/app/Resources/classic/jsRun.png +0 -0
  197. psychopy/app/Resources/classic/jsRun@2x.png +0 -0
  198. psychopy/app/Resources/classic/libroot.png +0 -0
  199. psychopy/app/Resources/classic/libroot@2x.png +0 -0
  200. psychopy/app/Resources/classic/monitor16.png +0 -0
  201. psychopy/app/Resources/classic/monitors.png +0 -0
  202. psychopy/app/Resources/classic/monitors16.png +0 -0
  203. psychopy/app/Resources/classic/monitors32.png +0 -0
  204. psychopy/app/Resources/classic/monitors@2x.png +0 -0
  205. psychopy/app/Resources/classic/orangedot.png +0 -0
  206. psychopy/app/Resources/classic/orangedot@2x.png +0 -0
  207. psychopy/app/Resources/classic/pavlovia.png +0 -0
  208. psychopy/app/Resources/classic/pavlovia16.png +0 -0
  209. psychopy/app/Resources/classic/pavlovia16@2x.png +0 -0
  210. psychopy/app/Resources/classic/pavlovia@2x.png +0 -0
  211. psychopy/app/Resources/classic/pavsync.png +0 -0
  212. psychopy/app/Resources/classic/pavsync@2x.png +0 -0
  213. psychopy/app/Resources/classic/person_off.png +0 -0
  214. psychopy/app/Resources/classic/person_off@2x.png +0 -0
  215. psychopy/app/Resources/classic/person_on.png +0 -0
  216. psychopy/app/Resources/classic/person_on@2x.png +0 -0
  217. psychopy/app/Resources/classic/plugins32.png +0 -0
  218. psychopy/app/Resources/classic/plus.png +0 -0
  219. psychopy/app/Resources/classic/plus@2x.png +0 -0
  220. psychopy/app/Resources/classic/preferences-app.png +0 -0
  221. psychopy/app/Resources/classic/preferences-app48.png +0 -0
  222. psychopy/app/Resources/classic/preferences-app@2x.png +0 -0
  223. psychopy/app/Resources/classic/preferences-conn.png +0 -0
  224. psychopy/app/Resources/classic/preferences-conn48.png +0 -0
  225. psychopy/app/Resources/classic/preferences-conn@2x.png +0 -0
  226. psychopy/app/Resources/classic/preferences-debug.png +0 -0
  227. psychopy/app/Resources/classic/preferences-debug@2x.png +0 -0
  228. psychopy/app/Resources/classic/preferences-general.png +0 -0
  229. psychopy/app/Resources/classic/preferences-general48.png +0 -0
  230. psychopy/app/Resources/classic/preferences-general@2x.png +0 -0
  231. psychopy/app/Resources/classic/preferences-hardware.png +0 -0
  232. psychopy/app/Resources/classic/preferences-hardware48.png +0 -0
  233. psychopy/app/Resources/classic/preferences-hardware@2x.png +0 -0
  234. psychopy/app/Resources/classic/preferences-keyboard.png +0 -0
  235. psychopy/app/Resources/classic/preferences-keyboard48.png +0 -0
  236. psychopy/app/Resources/classic/preferences-keyboard@2x.png +0 -0
  237. psychopy/app/Resources/classic/preferences-pilot.png +0 -0
  238. psychopy/app/Resources/classic/preferences-pilot@2x.png +0 -0
  239. psychopy/app/Resources/classic/preferences32.png +0 -0
  240. psychopy/app/Resources/classic/pyPilot.png +0 -0
  241. psychopy/app/Resources/classic/pyPilot@2x.png +0 -0
  242. psychopy/app/Resources/classic/pyRun.png +0 -0
  243. psychopy/app/Resources/classic/pyRun@2x.png +0 -0
  244. psychopy/app/Resources/classic/reddot.png +0 -0
  245. psychopy/app/Resources/classic/reddot@2x.png +0 -0
  246. psychopy/app/Resources/classic/redglobe.png +0 -0
  247. psychopy/app/Resources/classic/redglobe@2x.png +0 -0
  248. psychopy/app/Resources/classic/redglobe_bug.png +0 -0
  249. psychopy/app/Resources/classic/redglobe_bug@2x.png +0 -0
  250. psychopy/app/Resources/classic/redglobe_greensync.png +0 -0
  251. psychopy/app/Resources/classic/redglobe_greensync@2x.png +0 -0
  252. psychopy/app/Resources/classic/redglobe_info.png +0 -0
  253. psychopy/app/Resources/classic/redglobe_info@2x.png +0 -0
  254. psychopy/app/Resources/classic/redglobe_magnifier.png +0 -0
  255. psychopy/app/Resources/classic/redglobe_magnifier@2x.png +0 -0
  256. psychopy/app/Resources/classic/redglobe_run.png +0 -0
  257. psychopy/app/Resources/classic/redglobe_run@2x.png +0 -0
  258. psychopy/app/Resources/classic/redglobe_user.png +0 -0
  259. psychopy/app/Resources/classic/redglobe_user@2x.png +0 -0
  260. psychopy/app/Resources/classic/redo.png +0 -0
  261. psychopy/app/Resources/classic/redo32.png +0 -0
  262. psychopy/app/Resources/classic/redo@2x.png +0 -0
  263. psychopy/app/Resources/classic/removeExp32.png +0 -0
  264. psychopy/app/Resources/classic/rename16.png +0 -0
  265. psychopy/app/Resources/classic/restart.png +0 -0
  266. psychopy/app/Resources/classic/restart@2x.png +0 -0
  267. psychopy/app/Resources/classic/run.png +0 -0
  268. psychopy/app/Resources/classic/run@2x.png +0 -0
  269. psychopy/app/Resources/classic/runner.png +0 -0
  270. psychopy/app/Resources/classic/runner@2x.png +0 -0
  271. psychopy/app/Resources/classic/runnerPilot.png +0 -0
  272. psychopy/app/Resources/classic/runnerPilot@2x.png +0 -0
  273. psychopy/app/Resources/classic/savebtn.png +0 -0
  274. psychopy/app/Resources/classic/savebtn16.png +0 -0
  275. psychopy/app/Resources/classic/savebtn16@2x.png +0 -0
  276. psychopy/app/Resources/classic/savebtn@2x.png +0 -0
  277. psychopy/app/Resources/classic/search.png +0 -0
  278. psychopy/app/Resources/classic/search@2x.png +0 -0
  279. psychopy/app/Resources/classic/showBuilder.png +0 -0
  280. psychopy/app/Resources/classic/showBuilder@2x.png +0 -0
  281. psychopy/app/Resources/classic/showCoder.png +0 -0
  282. psychopy/app/Resources/classic/showCoder@2x.png +0 -0
  283. psychopy/app/Resources/classic/showRunner.png +0 -0
  284. psychopy/app/Resources/classic/showRunner@2x.png +0 -0
  285. psychopy/app/Resources/classic/starred.png +0 -0
  286. psychopy/app/Resources/classic/starred@2x.png +0 -0
  287. psychopy/app/Resources/classic/start.png +0 -0
  288. psychopy/app/Resources/classic/start@2x.png +0 -0
  289. psychopy/app/Resources/classic/stdout.png +0 -0
  290. psychopy/app/Resources/classic/stop.png +0 -0
  291. psychopy/app/Resources/classic/stop32.png +0 -0
  292. psychopy/app/Resources/classic/stop@2x.png +0 -0
  293. psychopy/app/Resources/classic/switchCtrlBot.png +0 -0
  294. psychopy/app/Resources/classic/switchCtrlBot@2x.png +0 -0
  295. psychopy/app/Resources/classic/switchCtrlLeft.png +0 -0
  296. psychopy/app/Resources/classic/switchCtrlLeft@2x.png +0 -0
  297. psychopy/app/Resources/classic/switchCtrlRight.png +0 -0
  298. psychopy/app/Resources/classic/switchCtrlRight@2x.png +0 -0
  299. psychopy/app/Resources/classic/switchCtrlTop.png +0 -0
  300. psychopy/app/Resources/classic/switchCtrlTop@2x.png +0 -0
  301. psychopy/app/Resources/classic/tick.png +0 -0
  302. psychopy/app/Resources/classic/tick@2x.png +0 -0
  303. psychopy/app/Resources/classic/undo.png +0 -0
  304. psychopy/app/Resources/classic/undo32.png +0 -0
  305. psychopy/app/Resources/classic/undo@2x.png +0 -0
  306. psychopy/app/Resources/classic/unstarred.png +0 -0
  307. psychopy/app/Resources/classic/unstarred@2x.png +0 -0
  308. psychopy/app/Resources/classic/user_none.png +0 -0
  309. psychopy/app/Resources/classic/view-refresh16.png +0 -0
  310. psychopy/app/Resources/classic/viewbtn.png +0 -0
  311. psychopy/app/Resources/classic/viewbtn16.png +0 -0
  312. psychopy/app/Resources/classic/viewbtn16@2x.png +0 -0
  313. psychopy/app/Resources/classic/viewbtn@2x.png +0 -0
  314. psychopy/app/Resources/classic/windows16.png +0 -0
  315. psychopy/app/Resources/classic/windows16@2x.png +0 -0
  316. psychopy/app/Resources/click.png +0 -0
  317. psychopy/app/Resources/coder.ico +0 -0
  318. psychopy/app/Resources/creditCard.png +0 -0
  319. psychopy/app/Resources/dark/FlowBottom_CompLeft.png +0 -0
  320. psychopy/app/Resources/dark/FlowBottom_CompRight.png +0 -0
  321. psychopy/app/Resources/dark/FlowTop_CompLeft.png +0 -0
  322. psychopy/app/Resources/dark/FlowTop_CompRight.png +0 -0
  323. psychopy/app/Resources/dark/README.txt +3 -0
  324. psychopy/app/Resources/dark/__init__.py +0 -0
  325. psychopy/app/Resources/dark/add.png +0 -0
  326. psychopy/app/Resources/dark/add@2x.png +0 -0
  327. psychopy/app/Resources/dark/addExp32.png +0 -0
  328. psychopy/app/Resources/dark/addExp32@2x.png +0 -0
  329. psychopy/app/Resources/dark/alerts.png +0 -0
  330. psychopy/app/Resources/dark/alerts@2x.png +0 -0
  331. psychopy/app/Resources/dark/beta.png +0 -0
  332. psychopy/app/Resources/dark/beta@2x.png +0 -0
  333. psychopy/app/Resources/dark/browser.png +0 -0
  334. psychopy/app/Resources/dark/browser@2x.png +0 -0
  335. psychopy/app/Resources/dark/bug16.png +0 -0
  336. psychopy/app/Resources/dark/bug16@2x.png +0 -0
  337. psychopy/app/Resources/dark/circle_mask.png +0 -0
  338. psychopy/app/Resources/dark/circle_mask@2x.png +0 -0
  339. psychopy/app/Resources/dark/clear.png +0 -0
  340. psychopy/app/Resources/dark/clear@2x.png +0 -0
  341. psychopy/app/Resources/dark/coderclass16.png +0 -0
  342. psychopy/app/Resources/dark/coderclass16@2x.png +0 -0
  343. psychopy/app/Resources/dark/coderfunc16.png +0 -0
  344. psychopy/app/Resources/dark/coderfunc16@2x.png +0 -0
  345. psychopy/app/Resources/dark/coderimport16.png +0 -0
  346. psychopy/app/Resources/dark/coderimport16@2x.png +0 -0
  347. psychopy/app/Resources/dark/coderjs.png +0 -0
  348. psychopy/app/Resources/dark/coderjs@2x.png +0 -0
  349. psychopy/app/Resources/dark/coderpython.png +0 -0
  350. psychopy/app/Resources/dark/coderpython@2x.png +0 -0
  351. psychopy/app/Resources/dark/codervar16.png +0 -0
  352. psychopy/app/Resources/dark/codervar16@2x.png +0 -0
  353. psychopy/app/Resources/dark/cogwindow32.png +0 -0
  354. psychopy/app/Resources/dark/cogwindow32@2x.png +0 -0
  355. psychopy/app/Resources/dark/color32.png +0 -0
  356. psychopy/app/Resources/dark/color32@2x.png +0 -0
  357. psychopy/app/Resources/dark/compile_js.png +0 -0
  358. psychopy/app/Resources/dark/compile_js@2x.png +0 -0
  359. psychopy/app/Resources/dark/compile_py.png +0 -0
  360. psychopy/app/Resources/dark/compile_py@2x.png +0 -0
  361. psychopy/app/Resources/dark/copy16.png +0 -0
  362. psychopy/app/Resources/dark/copy16@2x.png +0 -0
  363. psychopy/app/Resources/dark/currentFile16.png +0 -0
  364. psychopy/app/Resources/dark/currentFile16@2x.png +0 -0
  365. psychopy/app/Resources/dark/delete16.png +0 -0
  366. psychopy/app/Resources/dark/delete16@2x.png +0 -0
  367. psychopy/app/Resources/dark/desktop.png +0 -0
  368. psychopy/app/Resources/dark/desktop@2x.png +0 -0
  369. psychopy/app/Resources/dark/dirup16.png +0 -0
  370. psychopy/app/Resources/dark/dirup16@2x.png +0 -0
  371. psychopy/app/Resources/dark/docclose16.png +0 -0
  372. psychopy/app/Resources/dark/docclose16@2x.png +0 -0
  373. psychopy/app/Resources/dark/download.png +0 -0
  374. psychopy/app/Resources/dark/download@2x.png +0 -0
  375. psychopy/app/Resources/dark/edit.png +0 -0
  376. psychopy/app/Resources/dark/edit@2x.png +0 -0
  377. psychopy/app/Resources/dark/editbtn16.png +0 -0
  378. psychopy/app/Resources/dark/editbtn16@2x.png +0 -0
  379. psychopy/app/Resources/dark/email.png +0 -0
  380. psychopy/app/Resources/dark/email@2x.png +0 -0
  381. psychopy/app/Resources/dark/experiment.png +0 -0
  382. psychopy/app/Resources/dark/experiment@2x.png +0 -0
  383. psychopy/app/Resources/dark/expsettings.png +0 -0
  384. psychopy/app/Resources/dark/expsettings@2x.png +0 -0
  385. psychopy/app/Resources/dark/file.png +0 -0
  386. psychopy/app/Resources/dark/file@16w.png +0 -0
  387. psychopy/app/Resources/dark/file@16w@2x.png +0 -0
  388. psychopy/app/Resources/dark/file@2x.png +0 -0
  389. psychopy/app/Resources/dark/filecsv16.png +0 -0
  390. psychopy/app/Resources/dark/filecsv16@2x.png +0 -0
  391. psychopy/app/Resources/dark/fileimage16.png +0 -0
  392. psychopy/app/Resources/dark/fileimage16@2x.png +0 -0
  393. psychopy/app/Resources/dark/filenew.png +0 -0
  394. psychopy/app/Resources/dark/filenew32.png +0 -0
  395. psychopy/app/Resources/dark/filenew32@2x.png +0 -0
  396. psychopy/app/Resources/dark/filenew@2x.png +0 -0
  397. psychopy/app/Resources/dark/fileopen.png +0 -0
  398. psychopy/app/Resources/dark/fileopen32.png +0 -0
  399. psychopy/app/Resources/dark/fileopen32@2x.png +0 -0
  400. psychopy/app/Resources/dark/fileopen@2x.png +0 -0
  401. psychopy/app/Resources/dark/filesave.png +0 -0
  402. psychopy/app/Resources/dark/filesave32.png +0 -0
  403. psychopy/app/Resources/dark/filesave32@2x.png +0 -0
  404. psychopy/app/Resources/dark/filesave@2x.png +0 -0
  405. psychopy/app/Resources/dark/filesaveas.png +0 -0
  406. psychopy/app/Resources/dark/filesaveas32.png +0 -0
  407. psychopy/app/Resources/dark/filesaveas32@2x.png +0 -0
  408. psychopy/app/Resources/dark/filesaveas@2x.png +0 -0
  409. psychopy/app/Resources/dark/fileunknown16.png +0 -0
  410. psychopy/app/Resources/dark/fileunknown16@2x.png +0 -0
  411. psychopy/app/Resources/dark/filter.png +0 -0
  412. psychopy/app/Resources/dark/filter@2x.png +0 -0
  413. psychopy/app/Resources/dark/folder-open16.png +0 -0
  414. psychopy/app/Resources/dark/folder-open16@2x.png +0 -0
  415. psychopy/app/Resources/dark/folder16.png +0 -0
  416. psychopy/app/Resources/dark/folder16@2x.png +0 -0
  417. psychopy/app/Resources/dark/foldernew16.png +0 -0
  418. psychopy/app/Resources/dark/foldernew16@2x.png +0 -0
  419. psychopy/app/Resources/dark/fork.png +0 -0
  420. psychopy/app/Resources/dark/fork@2x.png +0 -0
  421. psychopy/app/Resources/dark/github.png +0 -0
  422. psychopy/app/Resources/dark/github@2x.png +0 -0
  423. psychopy/app/Resources/dark/globe.png +0 -0
  424. psychopy/app/Resources/dark/globe@2x.png +0 -0
  425. psychopy/app/Resources/dark/globe_bug.png +0 -0
  426. psychopy/app/Resources/dark/globe_bug@2x.png +0 -0
  427. psychopy/app/Resources/dark/globe_greensync.png +0 -0
  428. psychopy/app/Resources/dark/globe_greensync@2x.png +0 -0
  429. psychopy/app/Resources/dark/globe_info.png +0 -0
  430. psychopy/app/Resources/dark/globe_info@2x.png +0 -0
  431. psychopy/app/Resources/dark/globe_magnifier.png +0 -0
  432. psychopy/app/Resources/dark/globe_magnifier@2x.png +0 -0
  433. psychopy/app/Resources/dark/globe_run.png +0 -0
  434. psychopy/app/Resources/dark/globe_run@2x.png +0 -0
  435. psychopy/app/Resources/dark/globe_user.png +0 -0
  436. psychopy/app/Resources/dark/globe_user@2x.png +0 -0
  437. psychopy/app/Resources/dark/goto.png +0 -0
  438. psychopy/app/Resources/dark/goto@2x.png +0 -0
  439. psychopy/app/Resources/dark/greendot.png +0 -0
  440. psychopy/app/Resources/dark/greendot@2x.png +0 -0
  441. psychopy/app/Resources/dark/greenglobe.png +0 -0
  442. psychopy/app/Resources/dark/greenglobe@2x.png +0 -0
  443. psychopy/app/Resources/dark/greenglobe_bug.png +0 -0
  444. psychopy/app/Resources/dark/greenglobe_bug@2x.png +0 -0
  445. psychopy/app/Resources/dark/greenglobe_greensync.png +0 -0
  446. psychopy/app/Resources/dark/greenglobe_greensync@2x.png +0 -0
  447. psychopy/app/Resources/dark/greenglobe_info.png +0 -0
  448. psychopy/app/Resources/dark/greenglobe_info@2x.png +0 -0
  449. psychopy/app/Resources/dark/greenglobe_magnifier.png +0 -0
  450. psychopy/app/Resources/dark/greenglobe_magnifier@2x.png +0 -0
  451. psychopy/app/Resources/dark/greenglobe_run.png +0 -0
  452. psychopy/app/Resources/dark/greenglobe_run@2x.png +0 -0
  453. psychopy/app/Resources/dark/greenglobe_user.png +0 -0
  454. psychopy/app/Resources/dark/greenglobe_user@2x.png +0 -0
  455. psychopy/app/Resources/dark/greydot.png +0 -0
  456. psychopy/app/Resources/dark/greydot@2x.png +0 -0
  457. psychopy/app/Resources/dark/greytick.png +0 -0
  458. psychopy/app/Resources/dark/greytick@2x.png +0 -0
  459. psychopy/app/Resources/dark/invalid_img.png +0 -0
  460. psychopy/app/Resources/dark/jsPilot.png +0 -0
  461. psychopy/app/Resources/dark/jsPilot@2x.png +0 -0
  462. psychopy/app/Resources/dark/jsRun.png +0 -0
  463. psychopy/app/Resources/dark/jsRun@2x.png +0 -0
  464. psychopy/app/Resources/dark/libroot16.png +0 -0
  465. psychopy/app/Resources/dark/libroot16@2x.png +0 -0
  466. psychopy/app/Resources/dark/monitor16.png +0 -0
  467. psychopy/app/Resources/dark/monitor16@2x.png +0 -0
  468. psychopy/app/Resources/dark/monitors.png +0 -0
  469. psychopy/app/Resources/dark/monitors32.png +0 -0
  470. psychopy/app/Resources/dark/monitors32@2x.png +0 -0
  471. psychopy/app/Resources/dark/monitors@2x.png +0 -0
  472. psychopy/app/Resources/dark/orangedot.png +0 -0
  473. psychopy/app/Resources/dark/orangedot@2x.png +0 -0
  474. psychopy/app/Resources/dark/pavlovia.png +0 -0
  475. psychopy/app/Resources/dark/pavlovia16.png +0 -0
  476. psychopy/app/Resources/dark/pavlovia16@2x.png +0 -0
  477. psychopy/app/Resources/dark/pavlovia@2x.png +0 -0
  478. psychopy/app/Resources/dark/pavsync.png +0 -0
  479. psychopy/app/Resources/dark/pavsync@2x.png +0 -0
  480. psychopy/app/Resources/dark/person_off.png +0 -0
  481. psychopy/app/Resources/dark/person_off@2x.png +0 -0
  482. psychopy/app/Resources/dark/person_on.png +0 -0
  483. psychopy/app/Resources/dark/person_on@2x.png +0 -0
  484. psychopy/app/Resources/dark/plugins32.png +0 -0
  485. psychopy/app/Resources/dark/plugins32@2x.png +0 -0
  486. psychopy/app/Resources/dark/plus.png +0 -0
  487. psychopy/app/Resources/dark/plus@2x.png +0 -0
  488. psychopy/app/Resources/dark/preferences-app.png +0 -0
  489. psychopy/app/Resources/dark/preferences-app48.png +0 -0
  490. psychopy/app/Resources/dark/preferences-app48@2x.png +0 -0
  491. psychopy/app/Resources/dark/preferences-app@2x.png +0 -0
  492. psychopy/app/Resources/dark/preferences-conn.png +0 -0
  493. psychopy/app/Resources/dark/preferences-conn48.png +0 -0
  494. psychopy/app/Resources/dark/preferences-conn48@2x.png +0 -0
  495. psychopy/app/Resources/dark/preferences-conn@2x.png +0 -0
  496. psychopy/app/Resources/dark/preferences-debug.png +0 -0
  497. psychopy/app/Resources/dark/preferences-debug@2x.png +0 -0
  498. psychopy/app/Resources/dark/preferences-general.png +0 -0
  499. psychopy/app/Resources/dark/preferences-general48.png +0 -0
  500. psychopy/app/Resources/dark/preferences-general48@2x.png +0 -0
  501. psychopy/app/Resources/dark/preferences-general@2x.png +0 -0
  502. psychopy/app/Resources/dark/preferences-hardware.png +0 -0
  503. psychopy/app/Resources/dark/preferences-hardware48.png +0 -0
  504. psychopy/app/Resources/dark/preferences-hardware48@2x.png +0 -0
  505. psychopy/app/Resources/dark/preferences-hardware@2x.png +0 -0
  506. psychopy/app/Resources/dark/preferences-keyboard.png +0 -0
  507. psychopy/app/Resources/dark/preferences-keyboard48.png +0 -0
  508. psychopy/app/Resources/dark/preferences-keyboard48@2x.png +0 -0
  509. psychopy/app/Resources/dark/preferences-keyboard@2x.png +0 -0
  510. psychopy/app/Resources/dark/preferences-pilot.png +0 -0
  511. psychopy/app/Resources/dark/preferences-pilot@2x.png +0 -0
  512. psychopy/app/Resources/dark/preferences32.png +0 -0
  513. psychopy/app/Resources/dark/preferences32@2x.png +0 -0
  514. psychopy/app/Resources/dark/pyPilot.png +0 -0
  515. psychopy/app/Resources/dark/pyPilot@2x.png +0 -0
  516. psychopy/app/Resources/dark/pyRun.png +0 -0
  517. psychopy/app/Resources/dark/pyRun@2x.png +0 -0
  518. psychopy/app/Resources/dark/reddot.png +0 -0
  519. psychopy/app/Resources/dark/reddot@2x.png +0 -0
  520. psychopy/app/Resources/dark/redglobe.png +0 -0
  521. psychopy/app/Resources/dark/redglobe@2x.png +0 -0
  522. psychopy/app/Resources/dark/redglobe_bug.png +0 -0
  523. psychopy/app/Resources/dark/redglobe_bug@2x.png +0 -0
  524. psychopy/app/Resources/dark/redglobe_greensync.png +0 -0
  525. psychopy/app/Resources/dark/redglobe_greensync@2x.png +0 -0
  526. psychopy/app/Resources/dark/redglobe_info.png +0 -0
  527. psychopy/app/Resources/dark/redglobe_info@2x.png +0 -0
  528. psychopy/app/Resources/dark/redglobe_magnifier.png +0 -0
  529. psychopy/app/Resources/dark/redglobe_magnifier@2x.png +0 -0
  530. psychopy/app/Resources/dark/redglobe_run.png +0 -0
  531. psychopy/app/Resources/dark/redglobe_run@2x.png +0 -0
  532. psychopy/app/Resources/dark/redglobe_user.png +0 -0
  533. psychopy/app/Resources/dark/redglobe_user@2x.png +0 -0
  534. psychopy/app/Resources/dark/redo.png +0 -0
  535. psychopy/app/Resources/dark/redo32.png +0 -0
  536. psychopy/app/Resources/dark/redo32@2x.png +0 -0
  537. psychopy/app/Resources/dark/redo@2x.png +0 -0
  538. psychopy/app/Resources/dark/removeExp32.png +0 -0
  539. psychopy/app/Resources/dark/removeExp32@2x.png +0 -0
  540. psychopy/app/Resources/dark/rename16.png +0 -0
  541. psychopy/app/Resources/dark/rename16@2x.png +0 -0
  542. psychopy/app/Resources/dark/restart.png +0 -0
  543. psychopy/app/Resources/dark/restart@2x.png +0 -0
  544. psychopy/app/Resources/dark/runner.png +0 -0
  545. psychopy/app/Resources/dark/runner@2x.png +0 -0
  546. psychopy/app/Resources/dark/runnerPilot.png +0 -0
  547. psychopy/app/Resources/dark/runnerPilot@2x.png +0 -0
  548. psychopy/app/Resources/dark/savebtn16.png +0 -0
  549. psychopy/app/Resources/dark/savebtn16@2x.png +0 -0
  550. psychopy/app/Resources/dark/search.png +0 -0
  551. psychopy/app/Resources/dark/search@2x.png +0 -0
  552. psychopy/app/Resources/dark/showBuilder.png +0 -0
  553. psychopy/app/Resources/dark/showBuilder@2x.png +0 -0
  554. psychopy/app/Resources/dark/showCoder.png +0 -0
  555. psychopy/app/Resources/dark/showCoder@2x.png +0 -0
  556. psychopy/app/Resources/dark/showRunner.png +0 -0
  557. psychopy/app/Resources/dark/showRunner@2x.png +0 -0
  558. psychopy/app/Resources/dark/starred.png +0 -0
  559. psychopy/app/Resources/dark/starred@2x.png +0 -0
  560. psychopy/app/Resources/dark/start.png +0 -0
  561. psychopy/app/Resources/dark/start@2x.png +0 -0
  562. psychopy/app/Resources/dark/stdout.png +0 -0
  563. psychopy/app/Resources/dark/stdout@2x.png +0 -0
  564. psychopy/app/Resources/dark/stop.png +0 -0
  565. psychopy/app/Resources/dark/stop32.png +0 -0
  566. psychopy/app/Resources/dark/stop32@2x.png +0 -0
  567. psychopy/app/Resources/dark/stop@2x.png +0 -0
  568. psychopy/app/Resources/dark/switchCtrlBot.png +0 -0
  569. psychopy/app/Resources/dark/switchCtrlBot@2x.png +0 -0
  570. psychopy/app/Resources/dark/switchCtrlLeft.png +0 -0
  571. psychopy/app/Resources/dark/switchCtrlLeft@2x.png +0 -0
  572. psychopy/app/Resources/dark/switchCtrlRight.png +0 -0
  573. psychopy/app/Resources/dark/switchCtrlRight@2x.png +0 -0
  574. psychopy/app/Resources/dark/switchCtrlTop.png +0 -0
  575. psychopy/app/Resources/dark/switchCtrlTop@2x.png +0 -0
  576. psychopy/app/Resources/dark/tick.png +0 -0
  577. psychopy/app/Resources/dark/tick@2x.png +0 -0
  578. psychopy/app/Resources/dark/undo.png +0 -0
  579. psychopy/app/Resources/dark/undo32.png +0 -0
  580. psychopy/app/Resources/dark/undo32@2x.png +0 -0
  581. psychopy/app/Resources/dark/undo@2x.png +0 -0
  582. psychopy/app/Resources/dark/unstarred.png +0 -0
  583. psychopy/app/Resources/dark/unstarred@2x.png +0 -0
  584. psychopy/app/Resources/dark/user_none.png +0 -0
  585. psychopy/app/Resources/dark/view-refresh16.png +0 -0
  586. psychopy/app/Resources/dark/view-refresh16@2x.png +0 -0
  587. psychopy/app/Resources/dark/viewbtn16.png +0 -0
  588. psychopy/app/Resources/dark/viewbtn16@2x.png +0 -0
  589. psychopy/app/Resources/dark/windows16.png +0 -0
  590. psychopy/app/Resources/dark/windows16@2x.png +0 -0
  591. psychopy/app/Resources/default.mp3 +0 -0
  592. psychopy/app/Resources/default.mp4 +0 -0
  593. psychopy/app/Resources/default.png +0 -0
  594. psychopy/app/Resources/fonts/Arvo-Bold.ttf +0 -0
  595. psychopy/app/Resources/fonts/Arvo-BoldItalic.ttf +0 -0
  596. psychopy/app/Resources/fonts/Arvo-Italic.ttf +0 -0
  597. psychopy/app/Resources/fonts/Arvo-Regular.ttf +0 -0
  598. psychopy/app/Resources/fonts/DejaVuSerif.ttf +0 -0
  599. psychopy/app/Resources/fonts/IndieFlower-Regular.ttf +0 -0
  600. psychopy/app/Resources/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  601. psychopy/app/Resources/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
  602. psychopy/app/Resources/fonts/OpenSans-Bold.ttf +0 -0
  603. psychopy/app/Resources/fonts/OpenSans-BoldItalic.ttf +0 -0
  604. psychopy/app/Resources/fonts/OpenSans-ExtraBold.ttf +0 -0
  605. psychopy/app/Resources/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
  606. psychopy/app/Resources/fonts/OpenSans-Italic.ttf +0 -0
  607. psychopy/app/Resources/fonts/OpenSans-Light.ttf +0 -0
  608. psychopy/app/Resources/fonts/OpenSans-LightItalic.ttf +0 -0
  609. psychopy/app/Resources/fonts/OpenSans-Regular.ttf +0 -0
  610. psychopy/app/Resources/fonts/OpenSans-SemiBold.ttf +0 -0
  611. psychopy/app/Resources/fonts/OpenSans-SemiBoldItalic.ttf +0 -0
  612. psychopy/app/Resources/instruct1.png +0 -0
  613. psychopy/app/Resources/instruct2.png +0 -0
  614. psychopy/app/Resources/light/FlowBottom_CompLeft.png +0 -0
  615. psychopy/app/Resources/light/FlowBottom_CompRight.png +0 -0
  616. psychopy/app/Resources/light/FlowTop_CompLeft.png +0 -0
  617. psychopy/app/Resources/light/FlowTop_CompRight.png +0 -0
  618. psychopy/app/Resources/light/README.txt +3 -0
  619. psychopy/app/Resources/light/__init__.py +0 -0
  620. psychopy/app/Resources/light/add.png +0 -0
  621. psychopy/app/Resources/light/add@2x.png +0 -0
  622. psychopy/app/Resources/light/addExp32.png +0 -0
  623. psychopy/app/Resources/light/addExp32@2x.png +0 -0
  624. psychopy/app/Resources/light/alerts.png +0 -0
  625. psychopy/app/Resources/light/alerts@2x.png +0 -0
  626. psychopy/app/Resources/light/beta.png +0 -0
  627. psychopy/app/Resources/light/beta@2x.png +0 -0
  628. psychopy/app/Resources/light/browser.png +0 -0
  629. psychopy/app/Resources/light/browser@2x.png +0 -0
  630. psychopy/app/Resources/light/bug16.png +0 -0
  631. psychopy/app/Resources/light/bug16@2x.png +0 -0
  632. psychopy/app/Resources/light/circle_mask.png +0 -0
  633. psychopy/app/Resources/light/circle_mask@2x.png +0 -0
  634. psychopy/app/Resources/light/clear.png +0 -0
  635. psychopy/app/Resources/light/clear@2x.png +0 -0
  636. psychopy/app/Resources/light/coderclass16.png +0 -0
  637. psychopy/app/Resources/light/coderclass16@2x.png +0 -0
  638. psychopy/app/Resources/light/coderfunc16.png +0 -0
  639. psychopy/app/Resources/light/coderfunc16@2x.png +0 -0
  640. psychopy/app/Resources/light/coderimport16.png +0 -0
  641. psychopy/app/Resources/light/coderimport16@2x.png +0 -0
  642. psychopy/app/Resources/light/coderjs.png +0 -0
  643. psychopy/app/Resources/light/coderjs@2x.png +0 -0
  644. psychopy/app/Resources/light/coderpython.png +0 -0
  645. psychopy/app/Resources/light/coderpython@2x.png +0 -0
  646. psychopy/app/Resources/light/codervar16.png +0 -0
  647. psychopy/app/Resources/light/codervar16@2x.png +0 -0
  648. psychopy/app/Resources/light/cogwindow32.png +0 -0
  649. psychopy/app/Resources/light/cogwindow32@2x.png +0 -0
  650. psychopy/app/Resources/light/color16.png +0 -0
  651. psychopy/app/Resources/light/color16@2x.png +0 -0
  652. psychopy/app/Resources/light/color32.png +0 -0
  653. psychopy/app/Resources/light/color32@2x.png +0 -0
  654. psychopy/app/Resources/light/compile_js.png +0 -0
  655. psychopy/app/Resources/light/compile_js@2x.png +0 -0
  656. psychopy/app/Resources/light/compile_py.png +0 -0
  657. psychopy/app/Resources/light/compile_py@2x.png +0 -0
  658. psychopy/app/Resources/light/copy16.png +0 -0
  659. psychopy/app/Resources/light/copy16@2x.png +0 -0
  660. psychopy/app/Resources/light/currentFile16.png +0 -0
  661. psychopy/app/Resources/light/currentFile16@2x.png +0 -0
  662. psychopy/app/Resources/light/delete16.png +0 -0
  663. psychopy/app/Resources/light/delete16@2x.png +0 -0
  664. psychopy/app/Resources/light/delete8.png +0 -0
  665. psychopy/app/Resources/light/desktop.png +0 -0
  666. psychopy/app/Resources/light/desktop@2x.png +0 -0
  667. psychopy/app/Resources/light/dirup16.png +0 -0
  668. psychopy/app/Resources/light/dirup16@2x.png +0 -0
  669. psychopy/app/Resources/light/docclose16.png +0 -0
  670. psychopy/app/Resources/light/docclose16@2x.png +0 -0
  671. psychopy/app/Resources/light/download.png +0 -0
  672. psychopy/app/Resources/light/download@2x.png +0 -0
  673. psychopy/app/Resources/light/edit.png +0 -0
  674. psychopy/app/Resources/light/edit@2x.png +0 -0
  675. psychopy/app/Resources/light/editbtn16.png +0 -0
  676. psychopy/app/Resources/light/editbtn16@2x.png +0 -0
  677. psychopy/app/Resources/light/email.png +0 -0
  678. psychopy/app/Resources/light/email@2x.png +0 -0
  679. psychopy/app/Resources/light/experiment.png +0 -0
  680. psychopy/app/Resources/light/experiment@2x.png +0 -0
  681. psychopy/app/Resources/light/expsettings.png +0 -0
  682. psychopy/app/Resources/light/expsettings@2x.png +0 -0
  683. psychopy/app/Resources/light/file.png +0 -0
  684. psychopy/app/Resources/light/file@2x.png +0 -0
  685. psychopy/app/Resources/light/filecsv16.png +0 -0
  686. psychopy/app/Resources/light/filecsv16@2x.png +0 -0
  687. psychopy/app/Resources/light/fileimage16.png +0 -0
  688. psychopy/app/Resources/light/fileimage16@2x.png +0 -0
  689. psychopy/app/Resources/light/filenew.png +0 -0
  690. psychopy/app/Resources/light/filenew32.png +0 -0
  691. psychopy/app/Resources/light/filenew32@2x.png +0 -0
  692. psychopy/app/Resources/light/filenew@2x.png +0 -0
  693. psychopy/app/Resources/light/fileopen.png +0 -0
  694. psychopy/app/Resources/light/fileopen32.png +0 -0
  695. psychopy/app/Resources/light/fileopen32@2x.png +0 -0
  696. psychopy/app/Resources/light/fileopen@2x.png +0 -0
  697. psychopy/app/Resources/light/filesave.png +0 -0
  698. psychopy/app/Resources/light/filesave32.png +0 -0
  699. psychopy/app/Resources/light/filesave32@2x.png +0 -0
  700. psychopy/app/Resources/light/filesave@2x.png +0 -0
  701. psychopy/app/Resources/light/filesaveas.png +0 -0
  702. psychopy/app/Resources/light/filesaveas32.png +0 -0
  703. psychopy/app/Resources/light/filesaveas32@2x.png +0 -0
  704. psychopy/app/Resources/light/filesaveas@2x.png +0 -0
  705. psychopy/app/Resources/light/fileunknown16.png +0 -0
  706. psychopy/app/Resources/light/fileunknown16@2x.png +0 -0
  707. psychopy/app/Resources/light/filter.png +0 -0
  708. psychopy/app/Resources/light/filter@2x.png +0 -0
  709. psychopy/app/Resources/light/folder-open16.png +0 -0
  710. psychopy/app/Resources/light/folder-open16@2x.png +0 -0
  711. psychopy/app/Resources/light/folder16.png +0 -0
  712. psychopy/app/Resources/light/folder16@2x.png +0 -0
  713. psychopy/app/Resources/light/foldernew16.png +0 -0
  714. psychopy/app/Resources/light/foldernew16@2x.png +0 -0
  715. psychopy/app/Resources/light/fork.png +0 -0
  716. psychopy/app/Resources/light/fork@2x.png +0 -0
  717. psychopy/app/Resources/light/github.png +0 -0
  718. psychopy/app/Resources/light/github@2x.png +0 -0
  719. psychopy/app/Resources/light/globe.png +0 -0
  720. psychopy/app/Resources/light/globe@2x.png +0 -0
  721. psychopy/app/Resources/light/globe_bug.png +0 -0
  722. psychopy/app/Resources/light/globe_bug@2x.png +0 -0
  723. psychopy/app/Resources/light/globe_greensync.png +0 -0
  724. psychopy/app/Resources/light/globe_greensync@2x.png +0 -0
  725. psychopy/app/Resources/light/globe_info.png +0 -0
  726. psychopy/app/Resources/light/globe_info@2x.png +0 -0
  727. psychopy/app/Resources/light/globe_magnifier.png +0 -0
  728. psychopy/app/Resources/light/globe_magnifier@2x.png +0 -0
  729. psychopy/app/Resources/light/globe_run.png +0 -0
  730. psychopy/app/Resources/light/globe_run@2x.png +0 -0
  731. psychopy/app/Resources/light/globe_user.png +0 -0
  732. psychopy/app/Resources/light/globe_user@2x.png +0 -0
  733. psychopy/app/Resources/light/goto16.png +0 -0
  734. psychopy/app/Resources/light/goto16@2x.png +0 -0
  735. psychopy/app/Resources/light/greendot.png +0 -0
  736. psychopy/app/Resources/light/greendot@2x.png +0 -0
  737. psychopy/app/Resources/light/greenglobe.png +0 -0
  738. psychopy/app/Resources/light/greenglobe@2x.png +0 -0
  739. psychopy/app/Resources/light/greenglobe_bug.png +0 -0
  740. psychopy/app/Resources/light/greenglobe_bug@2x.png +0 -0
  741. psychopy/app/Resources/light/greenglobe_greensync.png +0 -0
  742. psychopy/app/Resources/light/greenglobe_greensync@2x.png +0 -0
  743. psychopy/app/Resources/light/greenglobe_info.png +0 -0
  744. psychopy/app/Resources/light/greenglobe_info@2x.png +0 -0
  745. psychopy/app/Resources/light/greenglobe_magnifier.png +0 -0
  746. psychopy/app/Resources/light/greenglobe_magnifier@2x.png +0 -0
  747. psychopy/app/Resources/light/greenglobe_run.png +0 -0
  748. psychopy/app/Resources/light/greenglobe_run@2x.png +0 -0
  749. psychopy/app/Resources/light/greenglobe_user.png +0 -0
  750. psychopy/app/Resources/light/greenglobe_user@2x.png +0 -0
  751. psychopy/app/Resources/light/greydot.png +0 -0
  752. psychopy/app/Resources/light/greydot@2x.png +0 -0
  753. psychopy/app/Resources/light/greytick.png +0 -0
  754. psychopy/app/Resources/light/greytick@2x.png +0 -0
  755. psychopy/app/Resources/light/invalid_img.png +0 -0
  756. psychopy/app/Resources/light/jsPilot.png +0 -0
  757. psychopy/app/Resources/light/jsPilot@2x.png +0 -0
  758. psychopy/app/Resources/light/jsRun.png +0 -0
  759. psychopy/app/Resources/light/jsRun@2x.png +0 -0
  760. psychopy/app/Resources/light/libroot16.png +0 -0
  761. psychopy/app/Resources/light/libroot16@2x.png +0 -0
  762. psychopy/app/Resources/light/monitor16.png +0 -0
  763. psychopy/app/Resources/light/monitor16@2x.png +0 -0
  764. psychopy/app/Resources/light/monitors.png +0 -0
  765. psychopy/app/Resources/light/monitors32.png +0 -0
  766. psychopy/app/Resources/light/monitors32@2x.png +0 -0
  767. psychopy/app/Resources/light/monitors@2x.png +0 -0
  768. psychopy/app/Resources/light/orangedot.png +0 -0
  769. psychopy/app/Resources/light/orangedot@2x.png +0 -0
  770. psychopy/app/Resources/light/pavlovia.png +0 -0
  771. psychopy/app/Resources/light/pavlovia16.png +0 -0
  772. psychopy/app/Resources/light/pavlovia16@2x.png +0 -0
  773. psychopy/app/Resources/light/pavlovia@2x.png +0 -0
  774. psychopy/app/Resources/light/pavsync.png +0 -0
  775. psychopy/app/Resources/light/pavsync@2x.png +0 -0
  776. psychopy/app/Resources/light/person_off.png +0 -0
  777. psychopy/app/Resources/light/person_off@2x.png +0 -0
  778. psychopy/app/Resources/light/person_on.png +0 -0
  779. psychopy/app/Resources/light/person_on@2x.png +0 -0
  780. psychopy/app/Resources/light/plugins32.png +0 -0
  781. psychopy/app/Resources/light/plugins32@2x.png +0 -0
  782. psychopy/app/Resources/light/plus.png +0 -0
  783. psychopy/app/Resources/light/plus@2x.png +0 -0
  784. psychopy/app/Resources/light/preferences-app.png +0 -0
  785. psychopy/app/Resources/light/preferences-app48.png +0 -0
  786. psychopy/app/Resources/light/preferences-app48@2x.png +0 -0
  787. psychopy/app/Resources/light/preferences-app@2x.png +0 -0
  788. psychopy/app/Resources/light/preferences-conn.png +0 -0
  789. psychopy/app/Resources/light/preferences-conn48.png +0 -0
  790. psychopy/app/Resources/light/preferences-conn48@2x.png +0 -0
  791. psychopy/app/Resources/light/preferences-conn@2x.png +0 -0
  792. psychopy/app/Resources/light/preferences-general.png +0 -0
  793. psychopy/app/Resources/light/preferences-general48.png +0 -0
  794. psychopy/app/Resources/light/preferences-general48@2x.png +0 -0
  795. psychopy/app/Resources/light/preferences-general@2x.png +0 -0
  796. psychopy/app/Resources/light/preferences-hardware.png +0 -0
  797. psychopy/app/Resources/light/preferences-hardware48.png +0 -0
  798. psychopy/app/Resources/light/preferences-hardware48@2x.png +0 -0
  799. psychopy/app/Resources/light/preferences-hardware@2x.png +0 -0
  800. psychopy/app/Resources/light/preferences-keyboard.png +0 -0
  801. psychopy/app/Resources/light/preferences-keyboard48.png +0 -0
  802. psychopy/app/Resources/light/preferences-keyboard48@2x.png +0 -0
  803. psychopy/app/Resources/light/preferences-keyboard@2x.png +0 -0
  804. psychopy/app/Resources/light/preferences-pilot.png +0 -0
  805. psychopy/app/Resources/light/preferences-pilot@2x.png +0 -0
  806. psychopy/app/Resources/light/preferences32.png +0 -0
  807. psychopy/app/Resources/light/preferences32@2x.png +0 -0
  808. psychopy/app/Resources/light/pyPilot.png +0 -0
  809. psychopy/app/Resources/light/pyPilot@2x.png +0 -0
  810. psychopy/app/Resources/light/pyRun.png +0 -0
  811. psychopy/app/Resources/light/pyRun@2x.png +0 -0
  812. psychopy/app/Resources/light/reddot.png +0 -0
  813. psychopy/app/Resources/light/reddot@2x.png +0 -0
  814. psychopy/app/Resources/light/redglobe.png +0 -0
  815. psychopy/app/Resources/light/redglobe@2x.png +0 -0
  816. psychopy/app/Resources/light/redglobe_bug.png +0 -0
  817. psychopy/app/Resources/light/redglobe_bug@2x.png +0 -0
  818. psychopy/app/Resources/light/redglobe_greensync.png +0 -0
  819. psychopy/app/Resources/light/redglobe_greensync@2x.png +0 -0
  820. psychopy/app/Resources/light/redglobe_info.png +0 -0
  821. psychopy/app/Resources/light/redglobe_info@2x.png +0 -0
  822. psychopy/app/Resources/light/redglobe_magnifier.png +0 -0
  823. psychopy/app/Resources/light/redglobe_magnifier@2x.png +0 -0
  824. psychopy/app/Resources/light/redglobe_run.png +0 -0
  825. psychopy/app/Resources/light/redglobe_run@2x.png +0 -0
  826. psychopy/app/Resources/light/redglobe_user.png +0 -0
  827. psychopy/app/Resources/light/redglobe_user@2x.png +0 -0
  828. psychopy/app/Resources/light/redo.png +0 -0
  829. psychopy/app/Resources/light/redo32.png +0 -0
  830. psychopy/app/Resources/light/redo32@2x.png +0 -0
  831. psychopy/app/Resources/light/redo@2x.png +0 -0
  832. psychopy/app/Resources/light/removeExp32.png +0 -0
  833. psychopy/app/Resources/light/removeExp32@2x.png +0 -0
  834. psychopy/app/Resources/light/rename16.png +0 -0
  835. psychopy/app/Resources/light/rename16@2x.png +0 -0
  836. psychopy/app/Resources/light/restart.png +0 -0
  837. psychopy/app/Resources/light/restart@2x.png +0 -0
  838. psychopy/app/Resources/light/runner.png +0 -0
  839. psychopy/app/Resources/light/runner@2x.png +0 -0
  840. psychopy/app/Resources/light/runnerPilot.png +0 -0
  841. psychopy/app/Resources/light/runnerPilot@2x.png +0 -0
  842. psychopy/app/Resources/light/savebtn16.png +0 -0
  843. psychopy/app/Resources/light/savebtn16@2x.png +0 -0
  844. psychopy/app/Resources/light/search.png +0 -0
  845. psychopy/app/Resources/light/search@2x.png +0 -0
  846. psychopy/app/Resources/light/showBuilder.png +0 -0
  847. psychopy/app/Resources/light/showBuilder@2x.png +0 -0
  848. psychopy/app/Resources/light/showCoder.png +0 -0
  849. psychopy/app/Resources/light/showCoder@2x.png +0 -0
  850. psychopy/app/Resources/light/showRunner.png +0 -0
  851. psychopy/app/Resources/light/showRunner@2x.png +0 -0
  852. psychopy/app/Resources/light/starred.png +0 -0
  853. psychopy/app/Resources/light/starred@2x.png +0 -0
  854. psychopy/app/Resources/light/start.png +0 -0
  855. psychopy/app/Resources/light/start@2x.png +0 -0
  856. psychopy/app/Resources/light/stdout.png +0 -0
  857. psychopy/app/Resources/light/stdout@2x.png +0 -0
  858. psychopy/app/Resources/light/stop.png +0 -0
  859. psychopy/app/Resources/light/stop32.png +0 -0
  860. psychopy/app/Resources/light/stop32@2x.png +0 -0
  861. psychopy/app/Resources/light/stop@2x.png +0 -0
  862. psychopy/app/Resources/light/switchCtrlBot.png +0 -0
  863. psychopy/app/Resources/light/switchCtrlBot@2x.png +0 -0
  864. psychopy/app/Resources/light/switchCtrlLeft.png +0 -0
  865. psychopy/app/Resources/light/switchCtrlLeft@2x.png +0 -0
  866. psychopy/app/Resources/light/switchCtrlRight.png +0 -0
  867. psychopy/app/Resources/light/switchCtrlRight@2x.png +0 -0
  868. psychopy/app/Resources/light/switchCtrlTop.png +0 -0
  869. psychopy/app/Resources/light/switchCtrlTop@2x.png +0 -0
  870. psychopy/app/Resources/light/tick.png +0 -0
  871. psychopy/app/Resources/light/tick@2x.png +0 -0
  872. psychopy/app/Resources/light/undo.png +0 -0
  873. psychopy/app/Resources/light/undo32.png +0 -0
  874. psychopy/app/Resources/light/undo32@2x.png +0 -0
  875. psychopy/app/Resources/light/undo@2x.png +0 -0
  876. psychopy/app/Resources/light/unstarred.png +0 -0
  877. psychopy/app/Resources/light/unstarred@2x.png +0 -0
  878. psychopy/app/Resources/light/user_none.png +0 -0
  879. psychopy/app/Resources/light/view-refresh16.png +0 -0
  880. psychopy/app/Resources/light/view-refresh16@2x.png +0 -0
  881. psychopy/app/Resources/light/viewbtn16.png +0 -0
  882. psychopy/app/Resources/light/viewbtn16@2x.png +0 -0
  883. psychopy/app/Resources/light/windows16.png +0 -0
  884. psychopy/app/Resources/light/windows16@2x.png +0 -0
  885. psychopy/app/Resources/moveComponentIcons.py +50 -0
  886. psychopy/app/Resources/next.png +0 -0
  887. psychopy/app/Resources/psychopy.desktop +13 -0
  888. psychopy/app/Resources/psychopy.icns +0 -0
  889. psychopy/app/Resources/psychopy.ico +0 -0
  890. psychopy/app/Resources/psychopy.png +0 -0
  891. psychopy/app/Resources/psychopy.xml +9 -0
  892. psychopy/app/Resources/psychopy@2x.png +0 -0
  893. psychopy/app/Resources/psychopySplash.png +0 -0
  894. psychopy/app/Resources/psychopySplash@2x.png +0 -0
  895. psychopy/app/Resources/routine_templates/Basic.psyexp +345 -0
  896. psychopy/app/Resources/routine_templates/Misc.psyexp +229 -0
  897. psychopy/app/Resources/routine_templates/Online.psyexp +202 -0
  898. psychopy/app/Resources/routine_templates/Trials.psyexp +736 -0
  899. psychopy/app/Resources/routine_templates/readme.md +14 -0
  900. psychopy/app/Resources/runner.ico +0 -0
  901. psychopy/app/Resources/tips.txt +45 -0
  902. psychopy/app/Resources/tips_ar_001.txt +45 -0
  903. psychopy/app/Resources/tips_fr_FR.txt +45 -0
  904. psychopy/app/Resources/tips_ja_JP.txt +42 -0
  905. psychopy/app/Resources/tips_zh_CN.txt +45 -0
  906. psychopy/app/Resources/window.ico +0 -0
  907. psychopy/app/__init__.py +224 -0
  908. psychopy/app/_psychopyApp.py +1224 -0
  909. psychopy/app/appData.spec +58 -0
  910. psychopy/app/builder/__init__.py +5 -0
  911. psychopy/app/builder/builder.py +4658 -0
  912. psychopy/app/builder/dialogs/__init__.py +2014 -0
  913. psychopy/app/builder/dialogs/dlgsCode.py +610 -0
  914. psychopy/app/builder/dialogs/dlgsConditions.py +670 -0
  915. psychopy/app/builder/dialogs/findDlg.py +189 -0
  916. psychopy/app/builder/dialogs/paramCtrls.py +1213 -0
  917. psychopy/app/builder/localizedStrings.py +225 -0
  918. psychopy/app/builder/validators.py +581 -0
  919. psychopy/app/coder/__init__.py +8 -0
  920. psychopy/app/coder/codeEditorBase.py +460 -0
  921. psychopy/app/coder/coder.py +3086 -0
  922. psychopy/app/coder/fileBrowser.py +668 -0
  923. psychopy/app/coder/folding.py +128 -0
  924. psychopy/app/coder/psychoParser.py +139 -0
  925. psychopy/app/coder/repl.py +493 -0
  926. psychopy/app/coder/sourceTree.py +302 -0
  927. psychopy/app/colorpicker/__init__.py +594 -0
  928. psychopy/app/colorpicker/ui.py +336 -0
  929. psychopy/app/connections/__init__.py +10 -0
  930. psychopy/app/connections/news.py +104 -0
  931. psychopy/app/connections/sendusage.py +61 -0
  932. psychopy/app/connections/updates.py +642 -0
  933. psychopy/app/console.py +164 -0
  934. psychopy/app/dialogs.py +632 -0
  935. psychopy/app/errorDlg.py +238 -0
  936. psychopy/app/frametracker.py +8 -0
  937. psychopy/app/idle.py +143 -0
  938. psychopy/app/jobs.py +648 -0
  939. psychopy/app/linuxconfig/__init__.py +144 -0
  940. psychopy/app/linuxconfig/ui.py +88 -0
  941. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  942. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +9554 -0
  943. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  944. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +24 -0
  945. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  946. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +24 -0
  947. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  948. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +9712 -0
  949. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  950. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +25 -0
  951. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  952. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +25 -0
  953. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  954. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +18 -0
  955. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  956. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +9100 -0
  957. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  958. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +9094 -0
  959. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  960. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +9100 -0
  961. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  962. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +9569 -0
  963. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  964. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +5806 -0
  965. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  966. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +24 -0
  967. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  968. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +9577 -0
  969. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  970. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +9549 -0
  971. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  972. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +9559 -0
  973. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  974. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +25 -0
  975. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  976. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +9560 -0
  977. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  978. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +11687 -0
  979. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  980. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +24 -0
  981. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  982. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +8757 -0
  983. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  984. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +25 -0
  985. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  986. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +25 -0
  987. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  988. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +25 -0
  989. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  990. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +24 -0
  991. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  992. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +25 -0
  993. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  994. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +24 -0
  995. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  996. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +8747 -0
  997. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  998. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +9545 -0
  999. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  1000. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +8268 -0
  1001. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  1002. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +8022 -0
  1003. psychopy/app/pavlovia_ui/__init__.py +22 -0
  1004. psychopy/app/pavlovia_ui/_base.py +237 -0
  1005. psychopy/app/pavlovia_ui/functions.py +176 -0
  1006. psychopy/app/pavlovia_ui/menu.py +140 -0
  1007. psychopy/app/pavlovia_ui/project.py +943 -0
  1008. psychopy/app/pavlovia_ui/search.py +445 -0
  1009. psychopy/app/pavlovia_ui/sync.py +137 -0
  1010. psychopy/app/pavlovia_ui/user.py +264 -0
  1011. psychopy/app/plugin_manager/__init__.py +5 -0
  1012. psychopy/app/plugin_manager/dialog.py +400 -0
  1013. psychopy/app/plugin_manager/output.py +132 -0
  1014. psychopy/app/plugin_manager/packages.py +502 -0
  1015. psychopy/app/plugin_manager/plugins.py +1315 -0
  1016. psychopy/app/plugin_manager/utils.py +115 -0
  1017. psychopy/app/preferencesDlg.py +790 -0
  1018. psychopy/app/psychopyApp.py +103 -0
  1019. psychopy/app/ribbon.py +907 -0
  1020. psychopy/app/runner/__init__.py +1 -0
  1021. psychopy/app/runner/runner.py +1262 -0
  1022. psychopy/app/runner/scriptProcess.py +353 -0
  1023. psychopy/app/stdout/__init__.py +1 -0
  1024. psychopy/app/stdout/stdOutRich.py +380 -0
  1025. psychopy/app/sysInfoDlg.py +242 -0
  1026. psychopy/app/themes/__init__.py +78 -0
  1027. psychopy/app/themes/colors.py +201 -0
  1028. psychopy/app/themes/css/contrast_black.css +112 -0
  1029. psychopy/app/themes/css/contrast_white.css +115 -0
  1030. psychopy/app/themes/css/dark.css +112 -0
  1031. psychopy/app/themes/css/light.css +115 -0
  1032. psychopy/app/themes/fonts.py +629 -0
  1033. psychopy/app/themes/handlers.py +321 -0
  1034. psychopy/app/themes/icons.py +250 -0
  1035. psychopy/app/themes/spec/Classic.json +154 -0
  1036. psychopy/app/themes/spec/ClassicDark.json +154 -0
  1037. psychopy/app/themes/spec/GitHub.json +154 -0
  1038. psychopy/app/themes/spec/HiVisDark.json +153 -0
  1039. psychopy/app/themes/spec/HiVisLight.json +154 -0
  1040. psychopy/app/themes/spec/MinimalDark.json +154 -0
  1041. psychopy/app/themes/spec/MinimalLight.json +153 -0
  1042. psychopy/app/themes/spec/PsychopyDark.json +231 -0
  1043. psychopy/app/themes/spec/PsychopyLight.json +236 -0
  1044. psychopy/app/themes/ui.py +74 -0
  1045. psychopy/app/ui/__init__.py +184 -0
  1046. psychopy/app/urls.py +31 -0
  1047. psychopy/app/utils.py +1680 -0
  1048. psychopy/app/viewer/__init__.py +0 -0
  1049. psychopy/clock.py +612 -0
  1050. psychopy/colors.py +1050 -0
  1051. psychopy/compatibility.py +95 -0
  1052. psychopy/constants.py +87 -0
  1053. psychopy/contrib/__init__.py +0 -0
  1054. psychopy/contrib/configobj/LICENSE +39 -0
  1055. psychopy/contrib/configobj/__init__.py +2453 -0
  1056. psychopy/contrib/configobj/_version.py +2 -0
  1057. psychopy/contrib/configobj/validate.py +1458 -0
  1058. psychopy/contrib/lazy_import.py +402 -0
  1059. psychopy/contrib/mseq.py +273 -0
  1060. psychopy/contrib/mseqSearch.py +183 -0
  1061. psychopy/contrib/psi.py +100 -0
  1062. psychopy/contrib/quest.py +484 -0
  1063. psychopy/contrib/tesselate.py +188 -0
  1064. psychopy/core.py +167 -0
  1065. psychopy/data/__init__.py +44 -0
  1066. psychopy/data/base.py +566 -0
  1067. psychopy/data/counterbalance.py +210 -0
  1068. psychopy/data/experiment.py +708 -0
  1069. psychopy/data/fit.py +248 -0
  1070. psychopy/data/shelf.py +237 -0
  1071. psychopy/data/staircase.py +2247 -0
  1072. psychopy/data/trial.py +1978 -0
  1073. psychopy/data/utils.py +783 -0
  1074. psychopy/demos/__init__.py +0 -0
  1075. psychopy/demos/builder/Design Templates/branchedExperiment/README.md +5 -0
  1076. psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +239 -0
  1077. psychopy/demos/builder/Design Templates/branchedExperiment/trialTypes.xlsx +0 -0
  1078. psychopy/demos/builder/Design Templates/psychophysicsStaircase/README.md +20 -0
  1079. psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +264 -0
  1080. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/README.md +14 -0
  1081. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +201 -0
  1082. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/stairDefinitions.xlsx +0 -0
  1083. psychopy/demos/builder/Design Templates/randomisedBlocks/README.md +18 -0
  1084. psychopy/demos/builder/Design Templates/randomisedBlocks/chooseBlock.xlsx +0 -0
  1085. psychopy/demos/builder/Design Templates/randomisedBlocks/facesBlock.xlsx +0 -0
  1086. psychopy/demos/builder/Design Templates/randomisedBlocks/housesBlock.xlsx +0 -0
  1087. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +131 -0
  1088. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face01.jpg +0 -0
  1089. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face02.jpg +0 -0
  1090. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face03.jpg +0 -0
  1091. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house01.jpg +0 -0
  1092. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house02.jpg +0 -0
  1093. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house03.jpg +0 -0
  1094. psychopy/demos/builder/Experiments/BART/README.md +30 -0
  1095. psychopy/demos/builder/Experiments/BART/assets/background.png +0 -0
  1096. psychopy/demos/builder/Experiments/BART/assets/bang.mp3 +0 -0
  1097. psychopy/demos/builder/Experiments/BART/assets/bang.wav +0 -0
  1098. psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
  1099. psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
  1100. psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
  1101. psychopy/demos/builder/Experiments/BART/bart.psyexp +931 -0
  1102. psychopy/demos/builder/Experiments/BART/spreadsheets/conditions.xlsx +0 -0
  1103. psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +193 -0
  1104. psychopy/demos/builder/Experiments/BigFiveInventory/README.md +23 -0
  1105. psychopy/demos/builder/Experiments/BigFiveInventory/TIPI.xlsx +0 -0
  1106. psychopy/demos/builder/Experiments/BigFiveInventory/bigFiveItems.xlsx +0 -0
  1107. psychopy/demos/builder/Experiments/BigFiveInventory/demographics.xlsx +0 -0
  1108. psychopy/demos/builder/Experiments/BigFiveInventory/mini_IPIP.xlsx +0 -0
  1109. psychopy/demos/builder/Experiments/dragAndDrop/README.md +40 -0
  1110. psychopy/demos/builder/Experiments/dragAndDrop/archived_conditions.xlsx +0 -0
  1111. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +1221 -0
  1112. psychopy/demos/builder/Experiments/dragAndDrop/draw grid stim.py +61 -0
  1113. psychopy/demos/builder/Experiments/dragAndDrop/shapeMaker.psyexp +91 -0
  1114. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_1.png +0 -0
  1115. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_10.png +0 -0
  1116. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_2.png +0 -0
  1117. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_3.png +0 -0
  1118. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_4.png +0 -0
  1119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_5.png +0 -0
  1120. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_6.png +0 -0
  1121. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_7.png +0 -0
  1122. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_8.png +0 -0
  1123. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_9.png +0 -0
  1124. psychopy/demos/builder/Experiments/dragAndDrop/updated_conditions.xlsx +0 -0
  1125. psychopy/demos/builder/Experiments/goNoGo/conditions.xlsx +0 -0
  1126. psychopy/demos/builder/Experiments/goNoGo/gng.psyexp +449 -0
  1127. psychopy/demos/builder/Experiments/goNoGo/go.png +0 -0
  1128. psychopy/demos/builder/Experiments/goNoGo/nogo.png +0 -0
  1129. psychopy/demos/builder/Experiments/goNoGo/readme.md +14 -0
  1130. psychopy/demos/builder/Experiments/mentalRotation/F.png +0 -0
  1131. psychopy/demos/builder/Experiments/mentalRotation/FR.png +0 -0
  1132. psychopy/demos/builder/Experiments/mentalRotation/MentalRot.csv +33 -0
  1133. psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +605 -0
  1134. psychopy/demos/builder/Experiments/mentalRotation/README.md +23 -0
  1135. psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +478 -0
  1136. psychopy/demos/builder/Experiments/navon/README.md +26 -0
  1137. psychopy/demos/builder/Experiments/navon/bigHsmallH.png +0 -0
  1138. psychopy/demos/builder/Experiments/navon/bigHsmallS.png +0 -0
  1139. psychopy/demos/builder/Experiments/navon/bigSsmallH.png +0 -0
  1140. psychopy/demos/builder/Experiments/navon/bigSsmallS.png +0 -0
  1141. psychopy/demos/builder/Experiments/navon/mask.png +0 -0
  1142. psychopy/demos/builder/Experiments/navon/stimuli.pptx +0 -0
  1143. psychopy/demos/builder/Experiments/navon/trialTypes.xlsx +0 -0
  1144. psychopy/demos/builder/Experiments/sternberg/README.md +14 -0
  1145. psychopy/demos/builder/Experiments/sternberg/mainTrials.xlsx +0 -0
  1146. psychopy/demos/builder/Experiments/sternberg/pracTrials.xlsx +0 -0
  1147. psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +611 -0
  1148. psychopy/demos/builder/Experiments/stroop/README.md +15 -0
  1149. psychopy/demos/builder/Experiments/stroop/stroop.psyexp +225 -0
  1150. psychopy/demos/builder/Experiments/stroop/trialTypes.csv +7 -0
  1151. psychopy/demos/builder/Experiments/stroopExtended/README.md +14 -0
  1152. psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +243 -0
  1153. psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +243 -0
  1154. psychopy/demos/builder/Experiments/stroopExtended/trialTypes.xlsx +0 -0
  1155. psychopy/demos/builder/Experiments/stroopExtended/trialTypesReverse.xlsx +0 -0
  1156. psychopy/demos/builder/Experiments/stroopVoice/README.md +27 -0
  1157. psychopy/demos/builder/Experiments/stroopVoice/conditions.xlsx +0 -0
  1158. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +366 -0
  1159. psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +298 -0
  1160. psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +341 -0
  1161. psychopy/demos/builder/Feature Demos/gratings/readme.md +8 -0
  1162. psychopy/demos/builder/Feature Demos/noise/face.jpg +0 -0
  1163. psychopy/demos/builder/Feature Demos/noise/noise.psyexp +424 -0
  1164. psychopy/demos/builder/Feature Demos/noise/readme.md +3 -0
  1165. psychopy/demos/builder/Feature Demos/panorama/panImg.jpg +0 -0
  1166. psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +142 -0
  1167. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +423 -0
  1168. psychopy/demos/builder/Feature Demos/progress/readme.md +1 -0
  1169. psychopy/demos/builder/Feature Demos/sliders/README.md +8 -0
  1170. psychopy/demos/builder/Feature Demos/sliders/fruitConditions.xlsx +0 -0
  1171. psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +938 -0
  1172. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +621 -0
  1173. psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +404 -0
  1174. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +641 -0
  1175. psychopy/demos/builder/Hardware/EGI_netstation/README.md +17 -0
  1176. psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +254 -0
  1177. psychopy/demos/builder/Hardware/EGI_netstation/trialTypesEEG.csv +7 -0
  1178. psychopy/demos/builder/Hardware/Eyetracking_visual_search/readme.md +19 -0
  1179. psychopy/demos/builder/Hardware/Eyetracking_visual_search/trials_params.xlsx +0 -0
  1180. psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +691 -0
  1181. psychopy/demos/builder/Hardware/camera/camera.psyexp +263 -0
  1182. psychopy/demos/builder/Hardware/camera/readme.md +6 -0
  1183. psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +354 -0
  1184. psychopy/demos/builder/Hardware/eyetracking/readme.md +7 -0
  1185. psychopy/demos/builder/Hardware/eyetracking_custom_cal/Bullseye_grey.mov +0 -0
  1186. psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +349 -0
  1187. psychopy/demos/builder/Hardware/eyetracking_custom_cal/readme.md +7 -0
  1188. psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +202 -0
  1189. psychopy/demos/builder/Hardware/fMRI/readme.md +7 -0
  1190. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +349 -0
  1191. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +304 -0
  1192. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +451 -0
  1193. psychopy/demos/builder/Hardware/microphone/phrases.xlsx +0 -0
  1194. psychopy/demos/builder/Hardware/microphone/readme.md +9 -0
  1195. psychopy/demos/builder/Hardware/pump/README.md +19 -0
  1196. psychopy/demos/builder/Hardware/pump/pump.psyexp +382 -0
  1197. psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +288 -0
  1198. psychopy/demos/builder/Helper Tools/achorVSalignment/readme.md +5 -0
  1199. psychopy/demos/builder/Helper Tools/clockFace/README.md +3 -0
  1200. psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +160 -0
  1201. psychopy/demos/builder/Helper Tools/colors/README.md +5 -0
  1202. psychopy/demos/builder/Helper Tools/colors/colors.psyexp +300 -0
  1203. psychopy/demos/builder/Helper Tools/drawPolygon/README.md +5 -0
  1204. psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +591 -0
  1205. psychopy/demos/builder/Helper Tools/keyNameFinder/README.md +14 -0
  1206. psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +169 -0
  1207. psychopy/demos/builder/Helper Tools/spatialUnits/README.md +6 -0
  1208. psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +157 -0
  1209. psychopy/demos/builder/README.txt +43 -0
  1210. psychopy/demos/builder/__init__.py +0 -0
  1211. psychopy/demos/coder/__init__.py +0 -0
  1212. psychopy/demos/coder/basic/hello_world.py +37 -0
  1213. psychopy/demos/coder/csvFromPsydat.py +29 -0
  1214. psychopy/demos/coder/experiment control/JND_staircase_analysis.py +85 -0
  1215. psychopy/demos/coder/experiment control/JND_staircase_exp.py +110 -0
  1216. psychopy/demos/coder/experiment control/TrialHandler.py +64 -0
  1217. psychopy/demos/coder/experiment control/TrialHandler2.py +42 -0
  1218. psychopy/demos/coder/experiment control/__init__.py +0 -0
  1219. psychopy/demos/coder/experiment control/autoDraw_autoLog.py +50 -0
  1220. psychopy/demos/coder/experiment control/experimentHandler.py +57 -0
  1221. psychopy/demos/coder/experiment control/fMRI_launchScan.py +74 -0
  1222. psychopy/demos/coder/experiment control/gammaMotionAnalysis.py +68 -0
  1223. psychopy/demos/coder/experiment control/gammaMotionNull.py +169 -0
  1224. psychopy/demos/coder/experiment control/logFiles.py +55 -0
  1225. psychopy/demos/coder/experiment control/piloting.py +65 -0
  1226. psychopy/demos/coder/experiment control/runtimeInfo.py +79 -0
  1227. psychopy/demos/coder/hardware/CRS_BitsBox.py +75 -0
  1228. psychopy/demos/coder/hardware/CRS_BitsPlusPlus.py +73 -0
  1229. psychopy/demos/coder/hardware/RiftHeadTrackingExample.py +95 -0
  1230. psychopy/demos/coder/hardware/RiftMinimal.py +44 -0
  1231. psychopy/demos/coder/hardware/VSHD_Distortion.py +49 -0
  1232. psychopy/demos/coder/hardware/__init__.py +0 -0
  1233. psychopy/demos/coder/hardware/camera.py +90 -0
  1234. psychopy/demos/coder/hardware/cedrusRB730.py +38 -0
  1235. psychopy/demos/coder/hardware/crsBitsAdvancedDemo.py +780 -0
  1236. psychopy/demos/coder/hardware/egi_netstation.py +45 -0
  1237. psychopy/demos/coder/hardware/ioLab_bbox.py +62 -0
  1238. psychopy/demos/coder/hardware/labjack_u3.py +61 -0
  1239. psychopy/demos/coder/hardware/monitorDemo.py +28 -0
  1240. psychopy/demos/coder/hardware/parallelPortOutput.py +49 -0
  1241. psychopy/demos/coder/hardware/qmixPump.py +79 -0
  1242. psychopy/demos/coder/hardware/testSoundLatency.py +127 -0
  1243. psychopy/demos/coder/input/GUI.py +52 -0
  1244. psychopy/demos/coder/input/__init__.py +0 -0
  1245. psychopy/demos/coder/input/customMouse.py +51 -0
  1246. psychopy/demos/coder/input/joystick_universal.py +84 -0
  1247. psychopy/demos/coder/input/keyNameFinder.py +39 -0
  1248. psychopy/demos/coder/input/mic.png +0 -0
  1249. psychopy/demos/coder/input/mouse.py +66 -0
  1250. psychopy/demos/coder/iohub/delaytest.py +231 -0
  1251. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/canal.jpg +0 -0
  1252. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/fall.jpg +0 -0
  1253. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/lake.jpg +0 -0
  1254. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/party.jpg +0 -0
  1255. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/swimming.jpg +0 -0
  1256. psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByConditionVariables.py +67 -0
  1257. psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByMessages.py +34 -0
  1258. psychopy/demos/coder/iohub/eyetracking/gcCursor/run.py +323 -0
  1259. psychopy/demos/coder/iohub/eyetracking/gcCursor/trial_conditions.xlsx +0 -0
  1260. psychopy/demos/coder/iohub/eyetracking/simple.py +147 -0
  1261. psychopy/demos/coder/iohub/eyetracking/validation.py +251 -0
  1262. psychopy/demos/coder/iohub/iodatastore/saveEventReport.py +56 -0
  1263. psychopy/demos/coder/iohub/keyboard.py +189 -0
  1264. psychopy/demos/coder/iohub/keyboardreactiontime.py +88 -0
  1265. psychopy/demos/coder/iohub/launchHub.py +160 -0
  1266. psychopy/demos/coder/iohub/mouse.py +112 -0
  1267. psychopy/demos/coder/iohub/mouse_multi_window.py +124 -0
  1268. psychopy/demos/coder/iohub/serial/_parseserial.py +53 -0
  1269. psychopy/demos/coder/iohub/serial/customparser.py +84 -0
  1270. psychopy/demos/coder/iohub/serial/pstbox.py +166 -0
  1271. psychopy/demos/coder/iohub/wintab/_wintabgraphics.py +281 -0
  1272. psychopy/demos/coder/iohub/wintab/pen_demo.py +260 -0
  1273. psychopy/demos/coder/misc/encrypt_data.py +48 -0
  1274. psychopy/demos/coder/misc/makeMovie.py +28 -0
  1275. psychopy/demos/coder/misc/rigidBodyTransform.py +76 -0
  1276. psychopy/demos/coder/stimuli/Campaign.ttf +0 -0
  1277. psychopy/demos/coder/stimuli/MovieStim.py +65 -0
  1278. psychopy/demos/coder/stimuli/__init__.py +0 -0
  1279. psychopy/demos/coder/stimuli/aperture.py +37 -0
  1280. psychopy/demos/coder/stimuli/beach.jpg +0 -0
  1281. psychopy/demos/coder/stimuli/bufferImageStim.py +78 -0
  1282. psychopy/demos/coder/stimuli/clockface.py +43 -0
  1283. psychopy/demos/coder/stimuli/colorPalette.py +134 -0
  1284. psychopy/demos/coder/stimuli/compare_text_timing.py +193 -0
  1285. psychopy/demos/coder/stimuli/counterphase.py +43 -0
  1286. psychopy/demos/coder/stimuli/customTextures.py +57 -0
  1287. psychopy/demos/coder/stimuli/dot_gabors.py +35 -0
  1288. psychopy/demos/coder/stimuli/dots.py +35 -0
  1289. psychopy/demos/coder/stimuli/elementArrays.py +92 -0
  1290. psychopy/demos/coder/stimuli/embeddedOpenGL.py +35 -0
  1291. psychopy/demos/coder/stimuli/face.jpg +0 -0
  1292. psychopy/demos/coder/stimuli/face_jpg.py +48 -0
  1293. psychopy/demos/coder/stimuli/gabor.py +30 -0
  1294. psychopy/demos/coder/stimuli/imagesAndPatches.py +51 -0
  1295. psychopy/demos/coder/stimuli/jwpIntro.mp4 +0 -0
  1296. psychopy/demos/coder/stimuli/kanizsa.py +41 -0
  1297. psychopy/demos/coder/stimuli/maskReveal.py +63 -0
  1298. psychopy/demos/coder/stimuli/plaid.py +43 -0
  1299. psychopy/demos/coder/stimuli/ratingScale.py +183 -0
  1300. psychopy/demos/coder/stimuli/rotatingFlashingWedge.py +37 -0
  1301. psychopy/demos/coder/stimuli/screensAndWindows.py +58 -0
  1302. psychopy/demos/coder/stimuli/secondOrderGratings.py +66 -0
  1303. psychopy/demos/coder/stimuli/shapeContains.py +50 -0
  1304. psychopy/demos/coder/stimuli/shapes.py +63 -0
  1305. psychopy/demos/coder/stimuli/soundStimuli.py +49 -0
  1306. psychopy/demos/coder/stimuli/starField.py +48 -0
  1307. psychopy/demos/coder/stimuli/stim3d.py +107 -0
  1308. psychopy/demos/coder/stimuli/textBoxStim/textbox_glyph_placement.py +93 -0
  1309. psychopy/demos/coder/stimuli/textBoxStim/textbox_simple.py +71 -0
  1310. psychopy/demos/coder/stimuli/textStimuli.py +106 -0
  1311. psychopy/demos/coder/stimuli/textbox_editable.py +64 -0
  1312. psychopy/demos/coder/stimuli/variousVisualStims.py +47 -0
  1313. psychopy/demos/coder/stimuli/visual_noise.py +31 -0
  1314. psychopy/demos/coder/sysInfo.py +56 -0
  1315. psychopy/demos/coder/timing/__init__.py +0 -0
  1316. psychopy/demos/coder/timing/callOnFlip.py +39 -0
  1317. psychopy/demos/coder/timing/clocksAndTimers.py +46 -0
  1318. psychopy/demos/coder/timing/millikeyKeyboardTimingTest.py +323 -0
  1319. psychopy/demos/coder/timing/timeByFrames.py +74 -0
  1320. psychopy/demos/coder/timing/timeByFramesEx.py +107 -0
  1321. psychopy/demos/coder/understanding psychopy/colors.py +70 -0
  1322. psychopy/demos/coder/understanding psychopy/fontLayout.py +69 -0
  1323. psychopy/demos/demo_migration.py +214 -0
  1324. psychopy/demos/modernizeDemos.py +16 -0
  1325. psychopy/demos/test_demo_migration.py +155 -0
  1326. psychopy/devices/__init__.py +8 -0
  1327. psychopy/event.py +1333 -0
  1328. psychopy/exceptions.py +59 -0
  1329. psychopy/experiment/__init__.py +51 -0
  1330. psychopy/experiment/_experiment.py +1392 -0
  1331. psychopy/experiment/blankTemplate.xltx +0 -0
  1332. psychopy/experiment/components/__init__.py +411 -0
  1333. psychopy/experiment/components/_base.py +1351 -0
  1334. psychopy/experiment/components/aperture/__init__.py +149 -0
  1335. psychopy/experiment/components/aperture/classic/aperture.png +0 -0
  1336. psychopy/experiment/components/aperture/classic/aperture@2x.png +0 -0
  1337. psychopy/experiment/components/aperture/dark/aperture.png +0 -0
  1338. psychopy/experiment/components/aperture/dark/aperture@2x.png +0 -0
  1339. psychopy/experiment/components/aperture/light/aperture.png +0 -0
  1340. psychopy/experiment/components/aperture/light/aperture@2x.png +0 -0
  1341. psychopy/experiment/components/brush/__init__.py +163 -0
  1342. psychopy/experiment/components/brush/classic/brush.png +0 -0
  1343. psychopy/experiment/components/brush/classic/brush@2x.png +0 -0
  1344. psychopy/experiment/components/brush/dark/brush.png +0 -0
  1345. psychopy/experiment/components/brush/dark/brush@2x.png +0 -0
  1346. psychopy/experiment/components/brush/light/brush.png +0 -0
  1347. psychopy/experiment/components/brush/light/brush@2x.png +0 -0
  1348. psychopy/experiment/components/button/__init__.py +489 -0
  1349. psychopy/experiment/components/button/classic/button.png +0 -0
  1350. psychopy/experiment/components/button/classic/button@2x.png +0 -0
  1351. psychopy/experiment/components/button/dark/button.png +0 -0
  1352. psychopy/experiment/components/button/dark/button@2x.png +0 -0
  1353. psychopy/experiment/components/button/light/button.png +0 -0
  1354. psychopy/experiment/components/button/light/button@2x.png +0 -0
  1355. psychopy/experiment/components/buttonBox/__init__.py +316 -0
  1356. psychopy/experiment/components/buttonBox/classic/buttonBox.png +0 -0
  1357. psychopy/experiment/components/buttonBox/classic/buttonBox@2x.png +0 -0
  1358. psychopy/experiment/components/buttonBox/dark/buttonBox.png +0 -0
  1359. psychopy/experiment/components/buttonBox/dark/buttonBox@2x.png +0 -0
  1360. psychopy/experiment/components/buttonBox/light/buttonBox.png +0 -0
  1361. psychopy/experiment/components/buttonBox/light/buttonBox@2x.png +0 -0
  1362. psychopy/experiment/components/camera/__init__.py +591 -0
  1363. psychopy/experiment/components/camera/classic/webcam.png +0 -0
  1364. psychopy/experiment/components/camera/classic/webcam@2x.png +0 -0
  1365. psychopy/experiment/components/camera/dark/webcam.png +0 -0
  1366. psychopy/experiment/components/camera/dark/webcam@2x.png +0 -0
  1367. psychopy/experiment/components/camera/light/webcam.png +0 -0
  1368. psychopy/experiment/components/camera/light/webcam@2x.png +0 -0
  1369. psychopy/experiment/components/code/__init__.py +266 -0
  1370. psychopy/experiment/components/code/classic/code.png +0 -0
  1371. psychopy/experiment/components/code/classic/code@2x.png +0 -0
  1372. psychopy/experiment/components/code/dark/code.png +0 -0
  1373. psychopy/experiment/components/code/dark/code@2x.png +0 -0
  1374. psychopy/experiment/components/code/light/code.png +0 -0
  1375. psychopy/experiment/components/code/light/code@2x.png +0 -0
  1376. psychopy/experiment/components/dots/__init__.py +199 -0
  1377. psychopy/experiment/components/dots/classic/dots.png +0 -0
  1378. psychopy/experiment/components/dots/classic/dots@2x.png +0 -0
  1379. psychopy/experiment/components/dots/dark/dots.png +0 -0
  1380. psychopy/experiment/components/dots/dark/dots@2x.png +0 -0
  1381. psychopy/experiment/components/dots/dots.xcf +0 -0
  1382. psychopy/experiment/components/dots/light/dots.png +0 -0
  1383. psychopy/experiment/components/dots/light/dots@2x.png +0 -0
  1384. psychopy/experiment/components/eyetracker_record/__init__.py +128 -0
  1385. psychopy/experiment/components/eyetracker_record/classic/eyetracker_record.png +0 -0
  1386. psychopy/experiment/components/eyetracker_record/classic/eyetracker_record@2x.png +0 -0
  1387. psychopy/experiment/components/eyetracker_record/dark/eyetracker_record.png +0 -0
  1388. psychopy/experiment/components/eyetracker_record/dark/eyetracker_record@2x.png +0 -0
  1389. psychopy/experiment/components/eyetracker_record/light/eyetracker_record.png +0 -0
  1390. psychopy/experiment/components/eyetracker_record/light/eyetracker_record@2x.png +0 -0
  1391. psychopy/experiment/components/form/__init__.py +223 -0
  1392. psychopy/experiment/components/form/classic/form.png +0 -0
  1393. psychopy/experiment/components/form/classic/form@2x.png +0 -0
  1394. psychopy/experiment/components/form/dark/form.png +0 -0
  1395. psychopy/experiment/components/form/dark/form@2x.png +0 -0
  1396. psychopy/experiment/components/form/formItems.xltx +0 -0
  1397. psychopy/experiment/components/form/light/form.png +0 -0
  1398. psychopy/experiment/components/form/light/form@2x.png +0 -0
  1399. psychopy/experiment/components/grating/__init__.py +192 -0
  1400. psychopy/experiment/components/grating/classic/grating.png +0 -0
  1401. psychopy/experiment/components/grating/classic/grating@2x.png +0 -0
  1402. psychopy/experiment/components/grating/dark/grating.png +0 -0
  1403. psychopy/experiment/components/grating/dark/grating@2x.png +0 -0
  1404. psychopy/experiment/components/grating/light/grating.png +0 -0
  1405. psychopy/experiment/components/grating/light/grating@2x.png +0 -0
  1406. psychopy/experiment/components/image/__init__.py +184 -0
  1407. psychopy/experiment/components/image/classic/image.png +0 -0
  1408. psychopy/experiment/components/image/classic/image@2x.png +0 -0
  1409. psychopy/experiment/components/image/dark/image.png +0 -0
  1410. psychopy/experiment/components/image/dark/image@2x.png +0 -0
  1411. psychopy/experiment/components/image/light/image.png +0 -0
  1412. psychopy/experiment/components/image/light/image@2x.png +0 -0
  1413. psychopy/experiment/components/joyButtons/__init__.py +453 -0
  1414. psychopy/experiment/components/joyButtons/classic/joyButtons.png +0 -0
  1415. psychopy/experiment/components/joyButtons/classic/joybuttons@2x.png +0 -0
  1416. psychopy/experiment/components/joyButtons/dark/joyButtons.png +0 -0
  1417. psychopy/experiment/components/joyButtons/dark/joyButtons@2x.png +0 -0
  1418. psychopy/experiment/components/joyButtons/light/joyButtons.png +0 -0
  1419. psychopy/experiment/components/joyButtons/light/joyButtons@2x.png +0 -0
  1420. psychopy/experiment/components/joyButtons/virtualJoyButtons.py +34 -0
  1421. psychopy/experiment/components/joystick/__init__.py +572 -0
  1422. psychopy/experiment/components/joystick/classic/joystick.png +0 -0
  1423. psychopy/experiment/components/joystick/classic/joystick@2x.png +0 -0
  1424. psychopy/experiment/components/joystick/dark/joystick.png +0 -0
  1425. psychopy/experiment/components/joystick/dark/joystick@2x.png +0 -0
  1426. psychopy/experiment/components/joystick/light/joystick.png +0 -0
  1427. psychopy/experiment/components/joystick/light/joystick@2x.png +0 -0
  1428. psychopy/experiment/components/joystick/virtualJoystick.py +40 -0
  1429. psychopy/experiment/components/keyboard/__init__.py +587 -0
  1430. psychopy/experiment/components/keyboard/classic/keyboard.png +0 -0
  1431. psychopy/experiment/components/keyboard/classic/keyboard@2x.png +0 -0
  1432. psychopy/experiment/components/keyboard/dark/keyboard.png +0 -0
  1433. psychopy/experiment/components/keyboard/dark/keyboard@2x.png +0 -0
  1434. psychopy/experiment/components/keyboard/light/keyboard.png +0 -0
  1435. psychopy/experiment/components/keyboard/light/keyboard@2x.png +0 -0
  1436. psychopy/experiment/components/keyboard.xcf +0 -0
  1437. psychopy/experiment/components/microphone/__init__.py +664 -0
  1438. psychopy/experiment/components/microphone/classic/microphone.png +0 -0
  1439. psychopy/experiment/components/microphone/classic/microphone@2x.png +0 -0
  1440. psychopy/experiment/components/microphone/dark/microphone.png +0 -0
  1441. psychopy/experiment/components/microphone/dark/microphone@2x.png +0 -0
  1442. psychopy/experiment/components/microphone/light/microphone.png +0 -0
  1443. psychopy/experiment/components/microphone/light/microphone@2x.png +0 -0
  1444. psychopy/experiment/components/mouse/__init__.py +747 -0
  1445. psychopy/experiment/components/mouse/classic/mouse.png +0 -0
  1446. psychopy/experiment/components/mouse/classic/mouse@2x.png +0 -0
  1447. psychopy/experiment/components/mouse/dark/mouse.png +0 -0
  1448. psychopy/experiment/components/mouse/dark/mouse@2x.png +0 -0
  1449. psychopy/experiment/components/mouse/light/mouse.png +0 -0
  1450. psychopy/experiment/components/mouse/light/mouse@2x.png +0 -0
  1451. psychopy/experiment/components/movie/__init__.py +367 -0
  1452. psychopy/experiment/components/movie/classic/movie.png +0 -0
  1453. psychopy/experiment/components/movie/classic/movie@2x.png +0 -0
  1454. psychopy/experiment/components/movie/dark/movie.png +0 -0
  1455. psychopy/experiment/components/movie/dark/movie@2x.png +0 -0
  1456. psychopy/experiment/components/movie/light/movie.png +0 -0
  1457. psychopy/experiment/components/movie/light/movie@2x.png +0 -0
  1458. psychopy/experiment/components/panorama/__init__.py +456 -0
  1459. psychopy/experiment/components/panorama/classic/panorama.png +0 -0
  1460. psychopy/experiment/components/panorama/classic/panorama@2x.png +0 -0
  1461. psychopy/experiment/components/panorama/dark/panorama.png +0 -0
  1462. psychopy/experiment/components/panorama/dark/panorama@2x.png +0 -0
  1463. psychopy/experiment/components/panorama/light/panorama.png +0 -0
  1464. psychopy/experiment/components/panorama/light/panorama@2x.png +0 -0
  1465. psychopy/experiment/components/parallelOut/__init__.py +178 -0
  1466. psychopy/experiment/components/parallelOut/classic/parallel.png +0 -0
  1467. psychopy/experiment/components/parallelOut/classic/parallel@2x.png +0 -0
  1468. psychopy/experiment/components/parallelOut/dark/parallel.png +0 -0
  1469. psychopy/experiment/components/parallelOut/dark/parallel@2x.png +0 -0
  1470. psychopy/experiment/components/parallelOut/light/parallel.png +0 -0
  1471. psychopy/experiment/components/parallelOut/light/parallel@2x.png +0 -0
  1472. psychopy/experiment/components/patch/__init__.py +121 -0
  1473. psychopy/experiment/components/patch/classic/patch.png +0 -0
  1474. psychopy/experiment/components/patch/dark/patch.png +0 -0
  1475. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  1476. psychopy/experiment/components/patch/light/patch.png +0 -0
  1477. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  1478. psychopy/experiment/components/polygon/__init__.py +303 -0
  1479. psychopy/experiment/components/polygon/classic/polygon.png +0 -0
  1480. psychopy/experiment/components/polygon/classic/polygon@2x.png +0 -0
  1481. psychopy/experiment/components/polygon/dark/polygon.png +0 -0
  1482. psychopy/experiment/components/polygon/dark/polygon@2x.png +0 -0
  1483. psychopy/experiment/components/polygon/light/polygon.png +0 -0
  1484. psychopy/experiment/components/polygon/light/polygon@2x.png +0 -0
  1485. psychopy/experiment/components/progress/__init__.py +130 -0
  1486. psychopy/experiment/components/progress/classic/progress.png +0 -0
  1487. psychopy/experiment/components/progress/classic/progress@2x.png +0 -0
  1488. psychopy/experiment/components/progress/dark/progress.png +0 -0
  1489. psychopy/experiment/components/progress/dark/progress@2x.png +0 -0
  1490. psychopy/experiment/components/progress/light/progress.png +0 -0
  1491. psychopy/experiment/components/progress/light/progress@2x.png +0 -0
  1492. psychopy/experiment/components/ratingScale/__init__.py +337 -0
  1493. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  1494. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  1495. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  1496. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  1497. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  1498. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  1499. psychopy/experiment/components/resourceManager/__init__.py +176 -0
  1500. psychopy/experiment/components/resourceManager/classic/resource_manager.png +0 -0
  1501. psychopy/experiment/components/resourceManager/classic/resource_manager@2x.png +0 -0
  1502. psychopy/experiment/components/resourceManager/dark/resource_manager.png +0 -0
  1503. psychopy/experiment/components/resourceManager/dark/resource_manager@2x.png +0 -0
  1504. psychopy/experiment/components/resourceManager/light/resource_manager.png +0 -0
  1505. psychopy/experiment/components/resourceManager/light/resource_manager@2x.png +0 -0
  1506. psychopy/experiment/components/roi/__init__.py +311 -0
  1507. psychopy/experiment/components/roi/classic/eyetracker_roi.png +0 -0
  1508. psychopy/experiment/components/roi/classic/eyetracker_roi@2x.png +0 -0
  1509. psychopy/experiment/components/roi/dark/eyetracker_roi.png +0 -0
  1510. psychopy/experiment/components/roi/dark/eyetracker_roi@2X.png +0 -0
  1511. psychopy/experiment/components/roi/light/eyetracker_roi.png +0 -0
  1512. psychopy/experiment/components/roi/light/eyetracker_roi@2X.png +0 -0
  1513. psychopy/experiment/components/routineSettings/__init__.py +338 -0
  1514. psychopy/experiment/components/routineSettings/classic/routineSettings.png +0 -0
  1515. psychopy/experiment/components/routineSettings/classic/routineSettings@2x.png +0 -0
  1516. psychopy/experiment/components/routineSettings/dark/routineSettings.png +0 -0
  1517. psychopy/experiment/components/routineSettings/dark/routineSettings@2x.png +0 -0
  1518. psychopy/experiment/components/routineSettings/light/routineSettings.png +0 -0
  1519. psychopy/experiment/components/routineSettings/light/routineSettings@2x.png +0 -0
  1520. psychopy/experiment/components/serialOut/__init__.py +205 -0
  1521. psychopy/experiment/components/serialOut/classic/serial.png +0 -0
  1522. psychopy/experiment/components/serialOut/classic/serial@2x.png +0 -0
  1523. psychopy/experiment/components/serialOut/dark/serial.png +0 -0
  1524. psychopy/experiment/components/serialOut/dark/serial@2x.png +0 -0
  1525. psychopy/experiment/components/serialOut/light/serial.png +0 -0
  1526. psychopy/experiment/components/serialOut/light/serial@2x.png +0 -0
  1527. psychopy/experiment/components/settings/JS_htmlHeader.tmpl +23 -0
  1528. psychopy/experiment/components/settings/JS_setupExp.tmpl +28 -0
  1529. psychopy/experiment/components/settings/__init__.py +2155 -0
  1530. psychopy/experiment/components/settings/classic/settings.png +0 -0
  1531. psychopy/experiment/components/settings/classic/settings@2x.png +0 -0
  1532. psychopy/experiment/components/settings/dark/settings.png +0 -0
  1533. psychopy/experiment/components/settings/dark/settings@2x.png +0 -0
  1534. psychopy/experiment/components/settings/light/settings.png +0 -0
  1535. psychopy/experiment/components/settings/light/settings@2x.png +0 -0
  1536. psychopy/experiment/components/slider/__init__.py +410 -0
  1537. psychopy/experiment/components/slider/classic/slider.png +0 -0
  1538. psychopy/experiment/components/slider/classic/slider@2x.png +0 -0
  1539. psychopy/experiment/components/slider/dark/slider.png +0 -0
  1540. psychopy/experiment/components/slider/dark/slider@2x.png +0 -0
  1541. psychopy/experiment/components/slider/light/slider.png +0 -0
  1542. psychopy/experiment/components/slider/light/slider@2x.png +0 -0
  1543. psychopy/experiment/components/sound/__init__.py +307 -0
  1544. psychopy/experiment/components/sound/classic/sound.png +0 -0
  1545. psychopy/experiment/components/sound/classic/sound@2x.png +0 -0
  1546. psychopy/experiment/components/sound/dark/sound.png +0 -0
  1547. psychopy/experiment/components/sound/dark/sound@2x.png +0 -0
  1548. psychopy/experiment/components/sound/light/sound.png +0 -0
  1549. psychopy/experiment/components/sound/light/sound@2x.png +0 -0
  1550. psychopy/experiment/components/static/__init__.py +267 -0
  1551. psychopy/experiment/components/static/classic/static.png +0 -0
  1552. psychopy/experiment/components/static/classic/static@2x.png +0 -0
  1553. psychopy/experiment/components/static/dark/static.png +0 -0
  1554. psychopy/experiment/components/static/dark/static@2x.png +0 -0
  1555. psychopy/experiment/components/static/light/static.png +0 -0
  1556. psychopy/experiment/components/static/light/static@2x.png +0 -0
  1557. psychopy/experiment/components/text/__init__.py +169 -0
  1558. psychopy/experiment/components/text/classic/text.png +0 -0
  1559. psychopy/experiment/components/text/classic/text@2x.png +0 -0
  1560. psychopy/experiment/components/text/dark/text.png +0 -0
  1561. psychopy/experiment/components/text/dark/text@2x.png +0 -0
  1562. psychopy/experiment/components/text/light/text.png +0 -0
  1563. psychopy/experiment/components/text/light/text@2x.png +0 -0
  1564. psychopy/experiment/components/textbox/__init__.py +330 -0
  1565. psychopy/experiment/components/textbox/classic/textbox.png +0 -0
  1566. psychopy/experiment/components/textbox/classic/textbox@2x.png +0 -0
  1567. psychopy/experiment/components/textbox/dark/textbox.png +0 -0
  1568. psychopy/experiment/components/textbox/dark/textbox@2x.png +0 -0
  1569. psychopy/experiment/components/textbox/light/textbox.png +0 -0
  1570. psychopy/experiment/components/textbox/light/textbox@2x.png +0 -0
  1571. psychopy/experiment/components/unknown/__init__.py +86 -0
  1572. psychopy/experiment/components/unknown/classic/unknown.png +0 -0
  1573. psychopy/experiment/components/unknown/classic/unknown@2x.png +0 -0
  1574. psychopy/experiment/components/unknown/dark/unknown.png +0 -0
  1575. psychopy/experiment/components/unknown/dark/unknown@2x.png +0 -0
  1576. psychopy/experiment/components/unknown/light/unknown.png +0 -0
  1577. psychopy/experiment/components/unknown/light/unknown@2x.png +0 -0
  1578. psychopy/experiment/components/unknownPlugin/__init__.py +84 -0
  1579. psychopy/experiment/components/unknownPlugin/classic/unknownPlugin.png +0 -0
  1580. psychopy/experiment/components/unknownPlugin/classic/unknownPlugin@2x.png +0 -0
  1581. psychopy/experiment/components/unknownPlugin/dark/unknownPlugin.png +0 -0
  1582. psychopy/experiment/components/unknownPlugin/dark/unknownPlugin@2x.png +0 -0
  1583. psychopy/experiment/components/unknownPlugin/light/unknownPlugin.png +0 -0
  1584. psychopy/experiment/components/unknownPlugin/light/unknownPlugin@2x.png +0 -0
  1585. psychopy/experiment/components/utils.py +0 -0
  1586. psychopy/experiment/components/variable/__init__.py +184 -0
  1587. psychopy/experiment/components/variable/classic/variable.png +0 -0
  1588. psychopy/experiment/components/variable/classic/variable@2x.png +0 -0
  1589. psychopy/experiment/components/variable/dark/variable.png +0 -0
  1590. psychopy/experiment/components/variable/dark/variable@2x.png +0 -0
  1591. psychopy/experiment/components/variable/light/variable.png +0 -0
  1592. psychopy/experiment/components/variable/light/variable@2x.png +0 -0
  1593. psychopy/experiment/experiment.xsd +223 -0
  1594. psychopy/experiment/exports.py +388 -0
  1595. psychopy/experiment/flow.py +531 -0
  1596. psychopy/experiment/localization.py +11 -0
  1597. psychopy/experiment/loopTemplate.xltx +0 -0
  1598. psychopy/experiment/loops.py +952 -0
  1599. psychopy/experiment/params.py +437 -0
  1600. psychopy/experiment/plugins.py +115 -0
  1601. psychopy/experiment/py2js.py +183 -0
  1602. psychopy/experiment/py2js_transpiler.py +606 -0
  1603. psychopy/experiment/questPlusTemplate.xltx +0 -0
  1604. psychopy/experiment/questTemplate.xltx +0 -0
  1605. psychopy/experiment/routines/__init__.py +108 -0
  1606. psychopy/experiment/routines/_base.py +907 -0
  1607. psychopy/experiment/routines/counterbalance/__init__.py +315 -0
  1608. psychopy/experiment/routines/counterbalance/classic/counterbalance.png +0 -0
  1609. psychopy/experiment/routines/counterbalance/classic/counterbalance@2x.png +0 -0
  1610. psychopy/experiment/routines/counterbalance/counterbalanceItems.xltx +0 -0
  1611. psychopy/experiment/routines/counterbalance/dark/counterbalance.png +0 -0
  1612. psychopy/experiment/routines/counterbalance/dark/counterbalance@2x.png +0 -0
  1613. psychopy/experiment/routines/counterbalance/light/counterbalance.png +0 -0
  1614. psychopy/experiment/routines/counterbalance/light/counterbalance@2x.png +0 -0
  1615. psychopy/experiment/routines/eyetracker_calibrate/__init__.py +275 -0
  1616. psychopy/experiment/routines/eyetracker_calibrate/classic/eyetracker_calib.png +0 -0
  1617. psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib.png +0 -0
  1618. psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib@2x.png +0 -0
  1619. psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib.png +0 -0
  1620. psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib@2x.png +0 -0
  1621. psychopy/experiment/routines/eyetracker_validate/__init__.py +354 -0
  1622. psychopy/experiment/routines/eyetracker_validate/classic/eyetracker_valid.png +0 -0
  1623. psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid.png +0 -0
  1624. psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid@2x.png +0 -0
  1625. psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid.png +0 -0
  1626. psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid@2x.png +0 -0
  1627. psychopy/experiment/routines/pavlovia_survey/__init__.py +236 -0
  1628. psychopy/experiment/routines/pavlovia_survey/classic/survey.png +0 -0
  1629. psychopy/experiment/routines/pavlovia_survey/classic/survey@2x.png +0 -0
  1630. psychopy/experiment/routines/pavlovia_survey/dark/survey.png +0 -0
  1631. psychopy/experiment/routines/pavlovia_survey/dark/survey@2x.png +0 -0
  1632. psychopy/experiment/routines/pavlovia_survey/light/survey.png +0 -0
  1633. psychopy/experiment/routines/pavlovia_survey/light/survey@2x.png +0 -0
  1634. psychopy/experiment/routines/photodiodeValidator/__init__.py +429 -0
  1635. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator.png +0 -0
  1636. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator@2x.png +0 -0
  1637. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator.png +0 -0
  1638. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator@2x.png +0 -0
  1639. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator.png +0 -0
  1640. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator@2x.png +0 -0
  1641. psychopy/experiment/routines/unknown/__init__.py +28 -0
  1642. psychopy/experiment/routines/unknown/classic/eyetracker_record.png +0 -0
  1643. psychopy/experiment/routines/unknown/classic/unknown.png +0 -0
  1644. psychopy/experiment/routines/unknown/dark/eyetracker_record.png +0 -0
  1645. psychopy/experiment/routines/unknown/dark/eyetracker_record@2x.png +0 -0
  1646. psychopy/experiment/routines/unknown/dark/unknown.png +0 -0
  1647. psychopy/experiment/routines/unknown/dark/unknown@2x.png +0 -0
  1648. psychopy/experiment/routines/unknown/light/eyetracker_record.png +0 -0
  1649. psychopy/experiment/routines/unknown/light/eyetracker_record@2x.png +0 -0
  1650. psychopy/experiment/routines/unknown/light/unknown.png +0 -0
  1651. psychopy/experiment/routines/unknown/light/unknown@2x.png +0 -0
  1652. psychopy/experiment/routines/utils.py +0 -0
  1653. psychopy/experiment/staircaseTemplate.xltx +0 -0
  1654. psychopy/experiment/utils.py +44 -0
  1655. psychopy/filters.py +11 -0
  1656. psychopy/gamma.py +9 -0
  1657. psychopy/gui/__init__.py +47 -0
  1658. psychopy/gui/qtgui.py +866 -0
  1659. psychopy/gui/util.py +78 -0
  1660. psychopy/gui/wxgui.py +414 -0
  1661. psychopy/hardware/__init__.py +216 -0
  1662. psychopy/hardware/base.py +323 -0
  1663. psychopy/hardware/bbtk/__init__.py +30 -0
  1664. psychopy/hardware/brainproducts.py +31 -0
  1665. psychopy/hardware/button.py +205 -0
  1666. psychopy/hardware/buttonbox/__init__.py +122 -0
  1667. psychopy/hardware/camera/__init__.py +2809 -0
  1668. psychopy/hardware/cedrus.py +36 -0
  1669. psychopy/hardware/crs/__init__.py +46 -0
  1670. psychopy/hardware/crs/bits.py +34 -0
  1671. psychopy/hardware/crs/colorcal.py +30 -0
  1672. psychopy/hardware/crs/optical.py +46 -0
  1673. psychopy/hardware/crs/shaders.py +21 -0
  1674. psychopy/hardware/emotiv.py +33 -0
  1675. psychopy/hardware/emulator.py +37 -0
  1676. psychopy/hardware/eyetracker.py +208 -0
  1677. psychopy/hardware/forp.py +42 -0
  1678. psychopy/hardware/gammasci.py +32 -0
  1679. psychopy/hardware/iolab.py +35 -0
  1680. psychopy/hardware/joystick/__init__.py +597 -0
  1681. psychopy/hardware/keyboard.py +880 -0
  1682. psychopy/hardware/knownDevices.json +52 -0
  1683. psychopy/hardware/labhackers.py +27 -0
  1684. psychopy/hardware/labjacks.py +30 -0
  1685. psychopy/hardware/listener.py +323 -0
  1686. psychopy/hardware/manager.py +963 -0
  1687. psychopy/hardware/microphone.py +1074 -0
  1688. psychopy/hardware/minolta.py +30 -0
  1689. psychopy/hardware/mouse/__init__.py +885 -0
  1690. psychopy/hardware/photodiode.py +639 -0
  1691. psychopy/hardware/photometer/__init__.py +176 -0
  1692. psychopy/hardware/pr.py +30 -0
  1693. psychopy/hardware/qmix.py +41 -0
  1694. psychopy/hardware/serialdevice.py +342 -0
  1695. psychopy/hardware/speaker.py +54 -0
  1696. psychopy/hardware/triggerbox/__init__.py +56 -0
  1697. psychopy/hardware/triggerbox/base.py +170 -0
  1698. psychopy/hardware/triggerbox/parallel.py +362 -0
  1699. psychopy/info.py +790 -0
  1700. psychopy/iohub/__init__.py +45 -0
  1701. psychopy/iohub/client/__init__.py +1454 -0
  1702. psychopy/iohub/client/connect.py +260 -0
  1703. psychopy/iohub/client/eyetracker/__init__.py +4 -0
  1704. psychopy/iohub/client/eyetracker/validation/__init__.py +8 -0
  1705. psychopy/iohub/client/eyetracker/validation/posgrid.py +274 -0
  1706. psychopy/iohub/client/eyetracker/validation/procedure.py +1292 -0
  1707. psychopy/iohub/client/eyetracker/validation/trigger.py +240 -0
  1708. psychopy/iohub/client/keyboard.py +508 -0
  1709. psychopy/iohub/client/wintab.py +378 -0
  1710. psychopy/iohub/constants.py +1176 -0
  1711. psychopy/iohub/datastore/__init__.py +439 -0
  1712. psychopy/iohub/datastore/default_datastore.yaml +8 -0
  1713. psychopy/iohub/datastore/util.py +868 -0
  1714. psychopy/iohub/default_config.yaml +17 -0
  1715. psychopy/iohub/devices/__init__.py +960 -0
  1716. psychopy/iohub/devices/computer.py +607 -0
  1717. psychopy/iohub/devices/default_device.yaml +15 -0
  1718. psychopy/iohub/devices/deviceConfigValidation.py +517 -0
  1719. psychopy/iohub/devices/display/__init__.py +755 -0
  1720. psychopy/iohub/devices/display/default_display.yaml +118 -0
  1721. psychopy/iohub/devices/display/supported_config_settings.yaml +69 -0
  1722. psychopy/iohub/devices/eventfilters.py +3572 -0
  1723. psychopy/iohub/devices/experiment/__init__.py +316 -0
  1724. psychopy/iohub/devices/experiment/default_experiment.yaml +63 -0
  1725. psychopy/iohub/devices/experiment/supported_config_settings.yaml +22 -0
  1726. psychopy/iohub/devices/eyetracker/__init__.py +436 -0
  1727. psychopy/iohub/devices/eyetracker/calibration/__init__.py +1 -0
  1728. psychopy/iohub/devices/eyetracker/calibration/procedure.py +409 -0
  1729. psychopy/iohub/devices/eyetracker/default_eyetracker.yaml +18 -0
  1730. psychopy/iohub/devices/eyetracker/eye_events.py +1751 -0
  1731. psychopy/iohub/devices/eyetracker/filters/__init__.py +4 -0
  1732. psychopy/iohub/devices/eyetracker/filters/parser.py +913 -0
  1733. psychopy/iohub/devices/eyetracker/hw/__init__.py +4 -0
  1734. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +17 -0
  1735. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +28 -0
  1736. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +18 -0
  1737. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +21 -0
  1738. psychopy/iohub/devices/eyetracker/hw/mouse/__init__.py +8 -0
  1739. psychopy/iohub/devices/eyetracker/hw/mouse/calibration.py +10 -0
  1740. psychopy/iohub/devices/eyetracker/hw/mouse/default_eyetracker.yaml +102 -0
  1741. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +444 -0
  1742. psychopy/iohub/devices/eyetracker/hw/mouse/supported_config_settings.yaml +110 -0
  1743. psychopy/iohub/devices/eyetracker/hw/pupil_labs/__init__.py +4 -0
  1744. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +26 -0
  1745. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/eyetracker.py +18 -0
  1746. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/__init__.py +29 -0
  1747. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/bisector.py +20 -0
  1748. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/constants.py +19 -0
  1749. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/data_parse.py +22 -0
  1750. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/eyetracker.py +18 -0
  1751. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/pupil_remote.py +18 -0
  1752. psychopy/iohub/devices/eyetracker/hw/sr_research/__init__.py +4 -0
  1753. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/__init__.py +28 -0
  1754. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/calibration.py +22 -0
  1755. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/eyetracker.py +38 -0
  1756. psychopy/iohub/devices/eyetracker/hw/tobii/__init__.py +29 -0
  1757. psychopy/iohub/devices/eyetracker/hw/tobii/calibration.py +19 -0
  1758. psychopy/iohub/devices/eyetracker/hw/tobii/eyetracker.py +17 -0
  1759. psychopy/iohub/devices/eyetracker/hw/tobii/tobiiwrapper.py +19 -0
  1760. psychopy/iohub/devices/eyetracker/supported_config_settings.yaml +44 -0
  1761. psychopy/iohub/devices/keyboard/__init__.py +309 -0
  1762. psychopy/iohub/devices/keyboard/darwin.py +428 -0
  1763. psychopy/iohub/devices/keyboard/darwinkey.py +94 -0
  1764. psychopy/iohub/devices/keyboard/default_keyboard.yaml +94 -0
  1765. psychopy/iohub/devices/keyboard/linux2.py +102 -0
  1766. psychopy/iohub/devices/keyboard/supported_config_settings.yaml +39 -0
  1767. psychopy/iohub/devices/keyboard/win32.py +356 -0
  1768. psychopy/iohub/devices/mouse/__init__.py +709 -0
  1769. psychopy/iohub/devices/mouse/darwin.py +414 -0
  1770. psychopy/iohub/devices/mouse/default_mouse.yaml +92 -0
  1771. psychopy/iohub/devices/mouse/linux2.py +167 -0
  1772. psychopy/iohub/devices/mouse/supported_config_settings.yaml +39 -0
  1773. psychopy/iohub/devices/mouse/win32.py +279 -0
  1774. psychopy/iohub/devices/pyXHook.py +619 -0
  1775. psychopy/iohub/devices/serial/__init__.py +779 -0
  1776. psychopy/iohub/devices/serial/default_pstbox.yaml +108 -0
  1777. psychopy/iohub/devices/serial/default_serial.yaml +108 -0
  1778. psychopy/iohub/devices/serial/supported_config_settings.yaml +85 -0
  1779. psychopy/iohub/devices/serial/supported_config_settings_pstbox.yaml +85 -0
  1780. psychopy/iohub/devices/supported_config_settings.yaml +36 -0
  1781. psychopy/iohub/devices/wintab/__init__.py +580 -0
  1782. psychopy/iohub/devices/wintab/default_wintab.yaml +89 -0
  1783. psychopy/iohub/devices/wintab/supported_config_settings.yaml +42 -0
  1784. psychopy/iohub/devices/wintab/win32.py +722 -0
  1785. psychopy/iohub/devices/xlib.py +6795 -0
  1786. psychopy/iohub/errors.py +61 -0
  1787. psychopy/iohub/lazy_import.py +582 -0
  1788. psychopy/iohub/net.py +349 -0
  1789. psychopy/iohub/server.py +1075 -0
  1790. psychopy/iohub/start_iohub_process.py +118 -0
  1791. psychopy/iohub/util/__init__.py +841 -0
  1792. psychopy/iohub/util/visualangle.py +126 -0
  1793. psychopy/layout.py +916 -0
  1794. psychopy/liaison.py +419 -0
  1795. psychopy/locale_setup.py +31 -0
  1796. psychopy/localization/__init__.py +22 -0
  1797. psychopy/localization/_localization.py +155 -0
  1798. psychopy/localization/generateTranslationTemplate.py +139 -0
  1799. psychopy/localization/mappings.txt +170 -0
  1800. psychopy/localization/messages.pot +7516 -0
  1801. psychopy/localization/readme.txt +68 -0
  1802. psychopy/logging.py +406 -0
  1803. psychopy/microphone.py +54 -0
  1804. psychopy/misc.py +40 -0
  1805. psychopy/monitors/MonitorCenter.py +1254 -0
  1806. psychopy/monitors/__init__.py +30 -0
  1807. psychopy/monitors/calibData.py +78 -0
  1808. psychopy/monitors/calibTools.py +1300 -0
  1809. psychopy/monitors/getLumSeries.py +50 -0
  1810. psychopy/monitors/psychopy-icon.svg +395 -0
  1811. psychopy/monitors/psychopy.ico +0 -0
  1812. psychopy/parallel/__init__.py +206 -0
  1813. psychopy/parallel/_dlportio.py +150 -0
  1814. psychopy/parallel/_inpout.py +120 -0
  1815. psychopy/parallel/_linux.py +105 -0
  1816. psychopy/piloting.py +61 -0
  1817. psychopy/platform_specific/__init__.py +48 -0
  1818. psychopy/platform_specific/darwin.py +328 -0
  1819. psychopy/platform_specific/linux.py +79 -0
  1820. psychopy/platform_specific/posix.py +16 -0
  1821. psychopy/platform_specific/win32.py +99 -0
  1822. psychopy/plugins/__init__.py +1354 -0
  1823. psychopy/preferences/Darwin.spec +282 -0
  1824. psychopy/preferences/FreeBSD.spec +282 -0
  1825. psychopy/preferences/Linux.spec +282 -0
  1826. psychopy/preferences/Windows.spec +282 -0
  1827. psychopy/preferences/__init__.py +20 -0
  1828. psychopy/preferences/baseNoArch.spec +278 -0
  1829. psychopy/preferences/generateHints.py +91 -0
  1830. psychopy/preferences/generateSpec.py +80 -0
  1831. psychopy/preferences/hints.py +329 -0
  1832. psychopy/preferences/preferences.py +332 -0
  1833. psychopy/projects/__init__.py +11 -0
  1834. psychopy/projects/gitignore.py +45 -0
  1835. psychopy/projects/pavlovia.py +1566 -0
  1836. psychopy/projects/sshkeys.py +107 -0
  1837. psychopy/psychojs.zip +0 -0
  1838. psychopy/pylintrc +69 -0
  1839. psychopy/scripts/__init__.py +0 -0
  1840. psychopy/scripts/psyexpCompile.py +233 -0
  1841. psychopy/session.py +1450 -0
  1842. psychopy/sound/__init__.py +279 -0
  1843. psychopy/sound/_base.py +282 -0
  1844. psychopy/sound/audioclip.py +851 -0
  1845. psychopy/sound/audiodevice.py +857 -0
  1846. psychopy/sound/backend_ptb.py +660 -0
  1847. psychopy/sound/backend_pygame.py +309 -0
  1848. psychopy/sound/backend_pyo.py +33 -0
  1849. psychopy/sound/backend_pysound.py +341 -0
  1850. psychopy/sound/backend_sounddevice.py +32 -0
  1851. psychopy/sound/exceptions.py +115 -0
  1852. psychopy/sound/microphone.py +274 -0
  1853. psychopy/sound/transcribe.py +1248 -0
  1854. psychopy/tests/.DS_Store +0 -0
  1855. psychopy/tests/README.md +344 -0
  1856. psychopy/tests/__init__.py +47 -0
  1857. psychopy/tests/data/Electronic_Chime-KevanGC-495939803.wav +0 -0
  1858. psychopy/tests/data/TestCircle_w128h128_bottom_left.png +0 -0
  1859. psychopy/tests/data/TestCircle_w128h128_bottom_right.png +0 -0
  1860. psychopy/tests/data/TestCircle_w128h128_center_center.png +0 -0
  1861. psychopy/tests/data/TestCircle_w128h128_top_left.png +0 -0
  1862. psychopy/tests/data/TestCircle_w128h128_top_right.png +0 -0
  1863. psychopy/tests/data/TestCircle_w128h64_bottom_left.png +0 -0
  1864. psychopy/tests/data/TestCircle_w128h64_bottom_right.png +0 -0
  1865. psychopy/tests/data/TestCircle_w128h64_center_center.png +0 -0
  1866. psychopy/tests/data/TestCircle_w128h64_top_left.png +0 -0
  1867. psychopy/tests/data/TestCircle_w128h64_top_right.png +0 -0
  1868. psychopy/tests/data/TestCircle_w64h128_bottom_left.png +0 -0
  1869. psychopy/tests/data/TestCircle_w64h128_bottom_right.png +0 -0
  1870. psychopy/tests/data/TestCircle_w64h128_center_center.png +0 -0
  1871. psychopy/tests/data/TestCircle_w64h128_top_left.png +0 -0
  1872. psychopy/tests/data/TestCircle_w64h128_top_right.png +0 -0
  1873. psychopy/tests/data/TestCircle_w64h64_bottom_left.png +0 -0
  1874. psychopy/tests/data/TestCircle_w64h64_bottom_right.png +0 -0
  1875. psychopy/tests/data/TestCircle_w64h64_center_center.png +0 -0
  1876. psychopy/tests/data/TestCircle_w64h64_top_left.png +0 -0
  1877. psychopy/tests/data/TestCircle_w64h64_top_right.png +0 -0
  1878. psychopy/tests/data/TestForm_scrolling_nq10_s0.5.png +0 -0
  1879. psychopy/tests/data/TestForm_scrolling_nq10_s0.png +0 -0
  1880. psychopy/tests/data/TestForm_scrolling_nq10_s1.png +0 -0
  1881. psychopy/tests/data/TestForm_scrolling_nq1_s0.5.png +0 -0
  1882. psychopy/tests/data/TestForm_scrolling_nq1_s0.png +0 -0
  1883. psychopy/tests/data/TestForm_scrolling_nq1_s1.png +0 -0
  1884. psychopy/tests/data/TestForm_scrolling_nq3_s0.5.png +0 -0
  1885. psychopy/tests/data/TestForm_scrolling_nq3_s0.png +0 -0
  1886. psychopy/tests/data/TestForm_scrolling_nq3_s1.png +0 -0
  1887. psychopy/tests/data/TestImage_w128h128_bottom_left.png +0 -0
  1888. psychopy/tests/data/TestImage_w128h128_bottom_right.png +0 -0
  1889. psychopy/tests/data/TestImage_w128h128_center_center.png +0 -0
  1890. psychopy/tests/data/TestImage_w128h128_top_left.png +0 -0
  1891. psychopy/tests/data/TestImage_w128h128_top_right.png +0 -0
  1892. psychopy/tests/data/TestImage_w128h64_bottom_left.png +0 -0
  1893. psychopy/tests/data/TestImage_w128h64_bottom_right.png +0 -0
  1894. psychopy/tests/data/TestImage_w128h64_center_center.png +0 -0
  1895. psychopy/tests/data/TestImage_w128h64_top_left.png +0 -0
  1896. psychopy/tests/data/TestImage_w128h64_top_right.png +0 -0
  1897. psychopy/tests/data/TestImage_w64h128_bottom_left.png +0 -0
  1898. psychopy/tests/data/TestImage_w64h128_bottom_right.png +0 -0
  1899. psychopy/tests/data/TestImage_w64h128_center_center.png +0 -0
  1900. psychopy/tests/data/TestImage_w64h128_top_left.png +0 -0
  1901. psychopy/tests/data/TestImage_w64h128_top_right.png +0 -0
  1902. psychopy/tests/data/TestImage_w64h64_bottom_left.png +0 -0
  1903. psychopy/tests/data/TestImage_w64h64_bottom_right.png +0 -0
  1904. psychopy/tests/data/TestImage_w64h64_center_center.png +0 -0
  1905. psychopy/tests/data/TestImage_w64h64_top_left.png +0 -0
  1906. psychopy/tests/data/TestImage_w64h64_top_right.png +0 -0
  1907. psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.3.png +0 -0
  1908. psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.6.png +0 -0
  1909. psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.3.png +0 -0
  1910. psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.6.png +0 -0
  1911. psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.3.png +0 -0
  1912. psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.6.png +0 -0
  1913. psychopy/tests/data/TestProgress_testValue_minmax_0.png +0 -0
  1914. psychopy/tests/data/TestProgress_testValue_minmax_1.png +0 -0
  1915. psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.3.png +0 -0
  1916. psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.6.png +0 -0
  1917. psychopy/tests/data/TestProgress_testValue_vertical_center center_0.3.png +0 -0
  1918. psychopy/tests/data/TestProgress_testValue_vertical_center center_0.6.png +0 -0
  1919. psychopy/tests/data/TestProgress_testValue_vertical_top center_0.3.png +0 -0
  1920. psychopy/tests/data/TestProgress_testValue_vertical_top center_0.6.png +0 -0
  1921. psychopy/tests/data/TestProgress_w128h128_bottom_left.png +0 -0
  1922. psychopy/tests/data/TestProgress_w128h128_bottom_right.png +0 -0
  1923. psychopy/tests/data/TestProgress_w128h128_center_center.png +0 -0
  1924. psychopy/tests/data/TestProgress_w128h128_top_left.png +0 -0
  1925. psychopy/tests/data/TestProgress_w128h128_top_right.png +0 -0
  1926. psychopy/tests/data/TestProgress_w128h64_bottom_left.png +0 -0
  1927. psychopy/tests/data/TestProgress_w128h64_bottom_right.png +0 -0
  1928. psychopy/tests/data/TestProgress_w128h64_center_center.png +0 -0
  1929. psychopy/tests/data/TestProgress_w128h64_top_left.png +0 -0
  1930. psychopy/tests/data/TestProgress_w128h64_top_right.png +0 -0
  1931. psychopy/tests/data/TestProgress_w64h128_bottom_left.png +0 -0
  1932. psychopy/tests/data/TestProgress_w64h128_bottom_right.png +0 -0
  1933. psychopy/tests/data/TestProgress_w64h128_center_center.png +0 -0
  1934. psychopy/tests/data/TestProgress_w64h128_top_left.png +0 -0
  1935. psychopy/tests/data/TestProgress_w64h128_top_right.png +0 -0
  1936. psychopy/tests/data/TestProgress_w64h64_bottom_left.png +0 -0
  1937. psychopy/tests/data/TestProgress_w64h64_bottom_right.png +0 -0
  1938. psychopy/tests/data/TestProgress_w64h64_center_center.png +0 -0
  1939. psychopy/tests/data/TestProgress_w64h64_top_left.png +0 -0
  1940. psychopy/tests/data/TestProgress_w64h64_top_right.png +0 -0
  1941. psychopy/tests/data/TestROI_w128h128_bottom_left.png +0 -0
  1942. psychopy/tests/data/TestROI_w128h128_bottom_right.png +0 -0
  1943. psychopy/tests/data/TestROI_w128h128_center_center.png +0 -0
  1944. psychopy/tests/data/TestROI_w128h128_top_left.png +0 -0
  1945. psychopy/tests/data/TestROI_w128h128_top_right.png +0 -0
  1946. psychopy/tests/data/TestROI_w128h64_bottom_left.png +0 -0
  1947. psychopy/tests/data/TestROI_w128h64_bottom_right.png +0 -0
  1948. psychopy/tests/data/TestROI_w128h64_center_center.png +0 -0
  1949. psychopy/tests/data/TestROI_w128h64_top_left.png +0 -0
  1950. psychopy/tests/data/TestROI_w128h64_top_right.png +0 -0
  1951. psychopy/tests/data/TestROI_w64h128_bottom_left.png +0 -0
  1952. psychopy/tests/data/TestROI_w64h128_bottom_right.png +0 -0
  1953. psychopy/tests/data/TestROI_w64h128_center_center.png +0 -0
  1954. psychopy/tests/data/TestROI_w64h128_top_left.png +0 -0
  1955. psychopy/tests/data/TestROI_w64h128_top_right.png +0 -0
  1956. psychopy/tests/data/TestROI_w64h64_bottom_left.png +0 -0
  1957. psychopy/tests/data/TestROI_w64h64_bottom_right.png +0 -0
  1958. psychopy/tests/data/TestROI_w64h64_center_center.png +0 -0
  1959. psychopy/tests/data/TestROI_w64h64_top_left.png +0 -0
  1960. psychopy/tests/data/TestROI_w64h64_top_right.png +0 -0
  1961. psychopy/tests/data/TestSession_exp1.png +0 -0
  1962. psychopy/tests/data/TestSession_exp2.png +0 -0
  1963. psychopy/tests/data/TestShape_w128h128_bottom_left.png +0 -0
  1964. psychopy/tests/data/TestShape_w128h128_bottom_right.png +0 -0
  1965. psychopy/tests/data/TestShape_w128h128_center_center.png +0 -0
  1966. psychopy/tests/data/TestShape_w128h128_top_left.png +0 -0
  1967. psychopy/tests/data/TestShape_w128h128_top_right.png +0 -0
  1968. psychopy/tests/data/TestShape_w128h64_bottom_left.png +0 -0
  1969. psychopy/tests/data/TestShape_w128h64_bottom_right.png +0 -0
  1970. psychopy/tests/data/TestShape_w128h64_center_center.png +0 -0
  1971. psychopy/tests/data/TestShape_w128h64_top_left.png +0 -0
  1972. psychopy/tests/data/TestShape_w128h64_top_right.png +0 -0
  1973. psychopy/tests/data/TestShape_w64h128_bottom_left.png +0 -0
  1974. psychopy/tests/data/TestShape_w64h128_bottom_right.png +0 -0
  1975. psychopy/tests/data/TestShape_w64h128_center_center.png +0 -0
  1976. psychopy/tests/data/TestShape_w64h128_top_left.png +0 -0
  1977. psychopy/tests/data/TestShape_w64h128_top_right.png +0 -0
  1978. psychopy/tests/data/TestShape_w64h64_bottom_left.png +0 -0
  1979. psychopy/tests/data/TestShape_w64h64_bottom_right.png +0 -0
  1980. psychopy/tests/data/TestShape_w64h64_center_center.png +0 -0
  1981. psychopy/tests/data/TestShape_w64h64_top_left.png +0 -0
  1982. psychopy/tests/data/TestShape_w64h64_top_right.png +0 -0
  1983. psychopy/tests/data/TestTarget_w128h128_bottom_left.png +0 -0
  1984. psychopy/tests/data/TestTarget_w128h128_bottom_right.png +0 -0
  1985. psychopy/tests/data/TestTarget_w128h128_center_center.png +0 -0
  1986. psychopy/tests/data/TestTarget_w128h128_top_left.png +0 -0
  1987. psychopy/tests/data/TestTarget_w128h128_top_right.png +0 -0
  1988. psychopy/tests/data/TestTarget_w128h64_bottom_left.png +0 -0
  1989. psychopy/tests/data/TestTarget_w128h64_bottom_right.png +0 -0
  1990. psychopy/tests/data/TestTarget_w128h64_center_center.png +0 -0
  1991. psychopy/tests/data/TestTarget_w128h64_top_left.png +0 -0
  1992. psychopy/tests/data/TestTarget_w128h64_top_right.png +0 -0
  1993. psychopy/tests/data/TestTarget_w64h128_bottom_left.png +0 -0
  1994. psychopy/tests/data/TestTarget_w64h128_bottom_right.png +0 -0
  1995. psychopy/tests/data/TestTarget_w64h128_center_center.png +0 -0
  1996. psychopy/tests/data/TestTarget_w64h128_top_left.png +0 -0
  1997. psychopy/tests/data/TestTarget_w64h128_top_right.png +0 -0
  1998. psychopy/tests/data/TestTarget_w64h64_bottom_left.png +0 -0
  1999. psychopy/tests/data/TestTarget_w64h64_bottom_right.png +0 -0
  2000. psychopy/tests/data/TestTarget_w64h64_center_center.png +0 -0
  2001. psychopy/tests/data/TestTarget_w64h64_top_left.png +0 -0
  2002. psychopy/tests/data/TestTarget_w64h64_top_right.png +0 -0
  2003. psychopy/tests/data/TestTextbox_testLetterSpacing_0p6.png +0 -0
  2004. psychopy/tests/data/TestTextbox_testLetterSpacing_0p8.png +0 -0
  2005. psychopy/tests/data/TestTextbox_testLetterSpacing_1.png +0 -0
  2006. psychopy/tests/data/TestTextbox_testLetterSpacing_1p2.png +0 -0
  2007. psychopy/tests/data/TestTextbox_testLetterSpacing_1p4.png +0 -0
  2008. psychopy/tests/data/TestTextbox_testLetterSpacing_1p6.png +0 -0
  2009. psychopy/tests/data/TestTextbox_testLetterSpacing_1p8.png +0 -0
  2010. psychopy/tests/data/TestTextbox_testLetterSpacing_2p0.png +0 -0
  2011. psychopy/tests/data/TestTextbox_testLetterSpacing_None.png +0 -0
  2012. psychopy/tests/data/Test_textbox_testSpeechpoint_-3ovr8_0ovr8.png +0 -0
  2013. psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_-3ovr8.png +0 -0
  2014. psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_3ovr8.png +0 -0
  2015. psychopy/tests/data/Test_textbox_testSpeechpoint_3ovr8_0ovr8.png +0 -0
  2016. psychopy/tests/data/Test_textbox_w128h128_bottom_left.png +0 -0
  2017. psychopy/tests/data/Test_textbox_w128h128_bottom_right.png +0 -0
  2018. psychopy/tests/data/Test_textbox_w128h128_center_center.png +0 -0
  2019. psychopy/tests/data/Test_textbox_w128h128_top_left.png +0 -0
  2020. psychopy/tests/data/Test_textbox_w128h128_top_right.png +0 -0
  2021. psychopy/tests/data/Test_textbox_w128h64_bottom_left.png +0 -0
  2022. psychopy/tests/data/Test_textbox_w128h64_bottom_right.png +0 -0
  2023. psychopy/tests/data/Test_textbox_w128h64_center_center.png +0 -0
  2024. psychopy/tests/data/Test_textbox_w128h64_top_left.png +0 -0
  2025. psychopy/tests/data/Test_textbox_w128h64_top_right.png +0 -0
  2026. psychopy/tests/data/Test_textbox_w64h128_bottom_left.png +0 -0
  2027. psychopy/tests/data/Test_textbox_w64h128_bottom_right.png +0 -0
  2028. psychopy/tests/data/Test_textbox_w64h128_center_center.png +0 -0
  2029. psychopy/tests/data/Test_textbox_w64h128_top_left.png +0 -0
  2030. psychopy/tests/data/Test_textbox_w64h128_top_right.png +0 -0
  2031. psychopy/tests/data/Test_textbox_w64h64_bottom_left.png +0 -0
  2032. psychopy/tests/data/Test_textbox_w64h64_bottom_right.png +0 -0
  2033. psychopy/tests/data/Test_textbox_w64h64_center_center.png +0 -0
  2034. psychopy/tests/data/Test_textbox_w64h64_top_left.png +0 -0
  2035. psychopy/tests/data/Test_textbox_w64h64_top_right.png +0 -0
  2036. psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_left.png +0 -0
  2037. psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_right.png +0 -0
  2038. psychopy/tests/data/Test_uax14_textbox_w128h128_center_center.png +0 -0
  2039. psychopy/tests/data/Test_uax14_textbox_w128h128_top_left.png +0 -0
  2040. psychopy/tests/data/Test_uax14_textbox_w128h128_top_right.png +0 -0
  2041. psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_left.png +0 -0
  2042. psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_right.png +0 -0
  2043. psychopy/tests/data/Test_uax14_textbox_w128h64_center_center.png +0 -0
  2044. psychopy/tests/data/Test_uax14_textbox_w128h64_top_left.png +0 -0
  2045. psychopy/tests/data/Test_uax14_textbox_w128h64_top_right.png +0 -0
  2046. psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_left.png +0 -0
  2047. psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_right.png +0 -0
  2048. psychopy/tests/data/Test_uax14_textbox_w64h128_center_center.png +0 -0
  2049. psychopy/tests/data/Test_uax14_textbox_w64h128_top_left.png +0 -0
  2050. psychopy/tests/data/Test_uax14_textbox_w64h128_top_right.png +0 -0
  2051. psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_left.png +0 -0
  2052. psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_right.png +0 -0
  2053. psychopy/tests/data/Test_uax14_textbox_w64h64_center_center.png +0 -0
  2054. psychopy/tests/data/Test_uax14_textbox_w64h64_top_left.png +0 -0
  2055. psychopy/tests/data/Test_uax14_textbox_w64h64_top_right.png +0 -0
  2056. psychopy/tests/data/TextComponent_disabled.psyexp +63 -0
  2057. psychopy/tests/data/TextComponent_not_disabled.psyexp +63 -0
  2058. psychopy/tests/data/aperture1_deg.png +0 -0
  2059. psychopy/tests/data/aperture1_degFlat.png +0 -0
  2060. psychopy/tests/data/aperture1_degFlatPos.png +0 -0
  2061. psychopy/tests/data/aperture1_norm.png +0 -0
  2062. psychopy/tests/data/aperture1_normHexbackground.png +0 -0
  2063. psychopy/tests/data/aperture1_normNoShade.png +0 -0
  2064. psychopy/tests/data/aperture1_pix.png +0 -0
  2065. psychopy/tests/data/aperture1_stencil.png +0 -0
  2066. psychopy/tests/data/aperture2_deg.png +0 -0
  2067. psychopy/tests/data/aperture2_degFlat.png +0 -0
  2068. psychopy/tests/data/aperture2_degFlatPos.png +0 -0
  2069. psychopy/tests/data/aperture2_norm.png +0 -0
  2070. psychopy/tests/data/aperture2_normHexbackground.png +0 -0
  2071. psychopy/tests/data/aperture2_normNoShade.png +0 -0
  2072. psychopy/tests/data/aperture2_pix.png +0 -0
  2073. psychopy/tests/data/aperture2_stencil.png +0 -0
  2074. psychopy/tests/data/beatandrcos_cm.png +0 -0
  2075. psychopy/tests/data/beatandrcos_deg.png +0 -0
  2076. psychopy/tests/data/beatandrcos_degFlat.png +0 -0
  2077. psychopy/tests/data/beatandrcos_degFlatPos.png +0 -0
  2078. psychopy/tests/data/beatandrcos_height.png +0 -0
  2079. psychopy/tests/data/beatandrcos_norm.png +0 -0
  2080. psychopy/tests/data/beatandrcos_normAddBlend.png +0 -0
  2081. psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
  2082. psychopy/tests/data/beatandrcos_normHexbackground.png +0 -0
  2083. psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
  2084. psychopy/tests/data/beatandrcos_norm_local.png +0 -0
  2085. psychopy/tests/data/beatandrcos_pix.png +0 -0
  2086. psychopy/tests/data/beatandrcos_stencil.png +0 -0
  2087. psychopy/tests/data/blend_add_cm.png +0 -0
  2088. psychopy/tests/data/blend_add_deg.png +0 -0
  2089. psychopy/tests/data/blend_add_degFlat.png +0 -0
  2090. psychopy/tests/data/blend_add_degFlatPos.png +0 -0
  2091. psychopy/tests/data/blend_add_height.png +0 -0
  2092. psychopy/tests/data/blend_add_norm.png +0 -0
  2093. psychopy/tests/data/blend_add_normAddBlend.png +0 -0
  2094. psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
  2095. psychopy/tests/data/blend_add_normHexbackground.png +0 -0
  2096. psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
  2097. psychopy/tests/data/blend_add_normNoShade.png +0 -0
  2098. psychopy/tests/data/blend_add_norm_local.png +0 -0
  2099. psychopy/tests/data/blend_add_pix.png +0 -0
  2100. psychopy/tests/data/blend_add_stencil.png +0 -0
  2101. psychopy/tests/data/broken2020_2_5_resources/broken_resources.psyexp +84 -0
  2102. psychopy/tests/data/broken2020_2_5_resources/psychopy72.png +0 -0
  2103. psychopy/tests/data/bufferimg_gabor_cm.png +0 -0
  2104. psychopy/tests/data/bufferimg_gabor_deg.png +0 -0
  2105. psychopy/tests/data/bufferimg_gabor_degFlat.png +0 -0
  2106. psychopy/tests/data/bufferimg_gabor_degFlatPos.png +0 -0
  2107. psychopy/tests/data/bufferimg_gabor_height.png +0 -0
  2108. psychopy/tests/data/bufferimg_gabor_norm.png +0 -0
  2109. psychopy/tests/data/bufferimg_gabor_normAddBlend.png +0 -0
  2110. psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
  2111. psychopy/tests/data/bufferimg_gabor_normHexbackground.png +0 -0
  2112. psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
  2113. psychopy/tests/data/bufferimg_gabor_normNoShade.png +0 -0
  2114. psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
  2115. psychopy/tests/data/bufferimg_gabor_pix.png +0 -0
  2116. psychopy/tests/data/bufferimg_gabor_stencil.png +0 -0
  2117. psychopy/tests/data/circleHex_cm.png +0 -0
  2118. psychopy/tests/data/circleHex_deg.png +0 -0
  2119. psychopy/tests/data/circleHex_degFlat.png +0 -0
  2120. psychopy/tests/data/circleHex_degFlatPos.png +0 -0
  2121. psychopy/tests/data/circleHex_height.png +0 -0
  2122. psychopy/tests/data/circleHex_norm.png +0 -0
  2123. psychopy/tests/data/circleHex_normAddBlend.png +0 -0
  2124. psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
  2125. psychopy/tests/data/circleHex_normHexbackground.png +0 -0
  2126. psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
  2127. psychopy/tests/data/circleHex_normNoShade.png +0 -0
  2128. psychopy/tests/data/circleHex_norm_local.png +0 -0
  2129. psychopy/tests/data/circleHex_pix.png +0 -0
  2130. psychopy/tests/data/circleHex_stencil.png +0 -0
  2131. psychopy/tests/data/corrFullRandom.csv +16 -0
  2132. psychopy/tests/data/corrFullRandom.tsv +6 -0
  2133. psychopy/tests/data/corrFullRandomTH2.csv +16 -0
  2134. psychopy/tests/data/corrMultiKeyExpWide.csv +3 -0
  2135. psychopy/tests/data/corrMultiKeyTrials.csv +8 -0
  2136. psychopy/tests/data/corrMultiKeyTrials.xlsx +0 -0
  2137. psychopy/tests/data/corrRandom.csv +16 -0
  2138. psychopy/tests/data/corrRandom.tsv +6 -0
  2139. psychopy/tests/data/corrRandomTH2.csv +16 -0
  2140. psychopy/tests/data/corrXlsx.xlsx +0 -0
  2141. psychopy/tests/data/correctScript/js/correctCodeComponent.js +181 -0
  2142. psychopy/tests/data/correctScript/js/correctImageComponent.js +204 -0
  2143. psychopy/tests/data/correctScript/js/correctKeyboardComponent.js +225 -0
  2144. psychopy/tests/data/correctScript/js/correctMouseComponent.js +221 -0
  2145. psychopy/tests/data/correctScript/js/correctMovieComponent.js +210 -0
  2146. psychopy/tests/data/correctScript/js/correctPolygonComponent.js +205 -0
  2147. psychopy/tests/data/correctScript/js/correctSliderComponent.js +209 -0
  2148. psychopy/tests/data/correctScript/js/correctSoundComponent.js +205 -0
  2149. psychopy/tests/data/correctScript/js/correctTextComponent.js +207 -0
  2150. psychopy/tests/data/correctScript/python/correctApertureComponent.py +157 -0
  2151. psychopy/tests/data/correctScript/python/correctCodeComponent.py +135 -0
  2152. psychopy/tests/data/correctScript/python/correctDotsComponent.py +161 -0
  2153. psychopy/tests/data/correctScript/python/correctEnvGratingComponent.py +167 -0
  2154. psychopy/tests/data/correctScript/python/correctFormComponent.py +150 -0
  2155. psychopy/tests/data/correctScript/python/correctGratingComponent.py +158 -0
  2156. psychopy/tests/data/correctScript/python/correctImageComponent.py +160 -0
  2157. psychopy/tests/data/correctScript/python/correctJoyButtonsComponent.py +204 -0
  2158. psychopy/tests/data/correctScript/python/correctJoystickComponent.py +214 -0
  2159. psychopy/tests/data/correctScript/python/correctKeyboardComponent.py +169 -0
  2160. psychopy/tests/data/correctScript/python/correctMicrophoneComponent.py +163 -0
  2161. psychopy/tests/data/correctScript/python/correctMouseComponent.py +174 -0
  2162. psychopy/tests/data/correctScript/python/correctMovieComponent.py +160 -0
  2163. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +169 -0
  2164. psychopy/tests/data/correctScript/python/correctParallelOutComponent.py +156 -0
  2165. psychopy/tests/data/correctScript/python/correctPatchComponent.py +159 -0
  2166. psychopy/tests/data/correctScript/python/correctPolygonComponent.py +159 -0
  2167. psychopy/tests/data/correctScript/python/correctQmixPumpComponent.py +174 -0
  2168. psychopy/tests/data/correctScript/python/correctRatingScaleComponent.py +152 -0
  2169. psychopy/tests/data/correctScript/python/correctSliderComponent.py +159 -0
  2170. psychopy/tests/data/correctScript/python/correctSoundComponent.py +157 -0
  2171. psychopy/tests/data/correctScript/python/correctStaticComponent.py +176 -0
  2172. psychopy/tests/data/correctScript/python/correctTextComponent.py +159 -0
  2173. psychopy/tests/data/correctScript/python/correctVariableComponent.py +139 -0
  2174. psychopy/tests/data/correctScript/python/correctcedrusButtonBoxComponent.py +199 -0
  2175. psychopy/tests/data/correctScript/python/correctioLabsButtonBoxComponent.py +187 -0
  2176. psychopy/tests/data/dataTest.xlsx +0 -0
  2177. psychopy/tests/data/dots_cm.png +0 -0
  2178. psychopy/tests/data/dots_deg.png +0 -0
  2179. psychopy/tests/data/dots_degFlat.png +0 -0
  2180. psychopy/tests/data/dots_degFlatPos.png +0 -0
  2181. psychopy/tests/data/dots_height.png +0 -0
  2182. psychopy/tests/data/dots_norm.png +0 -0
  2183. psychopy/tests/data/dots_normAddBlend.png +0 -0
  2184. psychopy/tests/data/dots_normAddBlend_local.png +0 -0
  2185. psychopy/tests/data/dots_normHexbackground.png +0 -0
  2186. psychopy/tests/data/dots_normHexbackground_local.png +0 -0
  2187. psychopy/tests/data/dots_normNoShade.png +0 -0
  2188. psychopy/tests/data/dots_norm_local.png +0 -0
  2189. psychopy/tests/data/dots_pix.png +0 -0
  2190. psychopy/tests/data/dots_stencil.png +0 -0
  2191. psychopy/tests/data/elarray1_cm.png +0 -0
  2192. psychopy/tests/data/elarray1_deg.png +0 -0
  2193. psychopy/tests/data/elarray1_degFlat.png +0 -0
  2194. psychopy/tests/data/elarray1_degFlatPos.png +0 -0
  2195. psychopy/tests/data/elarray1_height.png +0 -0
  2196. psychopy/tests/data/elarray1_norm.png +0 -0
  2197. psychopy/tests/data/elarray1_normAddBlend.png +0 -0
  2198. psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
  2199. psychopy/tests/data/elarray1_normHexbackground.png +0 -0
  2200. psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
  2201. psychopy/tests/data/elarray1_norm_local.png +0 -0
  2202. psychopy/tests/data/elarray1_pix.png +0 -0
  2203. psychopy/tests/data/elarray1_stencil.png +0 -0
  2204. psychopy/tests/data/envelopeandrcos_cm.png +0 -0
  2205. psychopy/tests/data/envelopeandrcos_deg.png +0 -0
  2206. psychopy/tests/data/envelopeandrcos_degFlat.png +0 -0
  2207. psychopy/tests/data/envelopeandrcos_degFlatPos.png +0 -0
  2208. psychopy/tests/data/envelopeandrcos_height.png +0 -0
  2209. psychopy/tests/data/envelopeandrcos_norm.png +0 -0
  2210. psychopy/tests/data/envelopeandrcos_normAddBlend.png +0 -0
  2211. psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
  2212. psychopy/tests/data/envelopeandrcos_normHexbackground.png +0 -0
  2213. psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
  2214. psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
  2215. psychopy/tests/data/envelopeandrcos_pix.png +0 -0
  2216. psychopy/tests/data/envelopeandrcos_stencil.png +0 -0
  2217. psychopy/tests/data/envelopepowerandrcos_cm.png +0 -0
  2218. psychopy/tests/data/envelopepowerandrcos_deg.png +0 -0
  2219. psychopy/tests/data/envelopepowerandrcos_degFlat.png +0 -0
  2220. psychopy/tests/data/envelopepowerandrcos_degFlatPos.png +0 -0
  2221. psychopy/tests/data/envelopepowerandrcos_height.png +0 -0
  2222. psychopy/tests/data/envelopepowerandrcos_norm.png +0 -0
  2223. psychopy/tests/data/envelopepowerandrcos_normAddBlend.png +0 -0
  2224. psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
  2225. psychopy/tests/data/envelopepowerandrcos_normHexbackground.png +0 -0
  2226. psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
  2227. psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
  2228. psychopy/tests/data/envelopepowerandrcos_pix.png +0 -0
  2229. psychopy/tests/data/envelopepowerandrcos_stencil.png +0 -0
  2230. psychopy/tests/data/filltext.png +0 -0
  2231. psychopy/tests/data/form_font_demographics.png +0 -0
  2232. psychopy/tests/data/form_font_demographics.xlsx +0 -0
  2233. psychopy/tests/data/form_font_languages.png +0 -0
  2234. psychopy/tests/data/form_font_languages.xlsx +0 -0
  2235. psychopy/tests/data/form_font_nonstandard.png +0 -0
  2236. psychopy/tests/data/futureParams.psyexp +150 -0
  2237. psychopy/tests/data/gabor1_cm.png +0 -0
  2238. psychopy/tests/data/gabor1_deg.png +0 -0
  2239. psychopy/tests/data/gabor1_degFlat.png +0 -0
  2240. psychopy/tests/data/gabor1_degFlatPos.png +0 -0
  2241. psychopy/tests/data/gabor1_height.png +0 -0
  2242. psychopy/tests/data/gabor1_norm.png +0 -0
  2243. psychopy/tests/data/gabor1_normAddBlend.png +0 -0
  2244. psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
  2245. psychopy/tests/data/gabor1_normHexbackground.png +0 -0
  2246. psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
  2247. psychopy/tests/data/gabor1_normNoShade.png +0 -0
  2248. psychopy/tests/data/gabor1_norm_local.png +0 -0
  2249. psychopy/tests/data/gabor1_pix.png +0 -0
  2250. psychopy/tests/data/gabor1_stencil.png +0 -0
  2251. psychopy/tests/data/gabor2_cm.png +0 -0
  2252. psychopy/tests/data/gabor2_deg.png +0 -0
  2253. psychopy/tests/data/gabor2_degFlat.png +0 -0
  2254. psychopy/tests/data/gabor2_degFlatPos.png +0 -0
  2255. psychopy/tests/data/gabor2_height.png +0 -0
  2256. psychopy/tests/data/gabor2_norm.png +0 -0
  2257. psychopy/tests/data/gabor2_normAddBlend.png +0 -0
  2258. psychopy/tests/data/gabor2_normHexbackground.png +0 -0
  2259. psychopy/tests/data/gabor2_normNoShade.png +0 -0
  2260. psychopy/tests/data/gabor2_pix.png +0 -0
  2261. psychopy/tests/data/gabor2_stencil.png +0 -0
  2262. psychopy/tests/data/ghost_stroop.psyexp +131 -0
  2263. psychopy/tests/data/ghost_trialTypes.xlsx +0 -0
  2264. psychopy/tests/data/green_48000.flac.dist +0 -0
  2265. psychopy/tests/data/greyscale.jpg +0 -0
  2266. psychopy/tests/data/greyscale2.png +0 -0
  2267. psychopy/tests/data/greyscale2_cm.png +0 -0
  2268. psychopy/tests/data/greyscale2_deg.png +0 -0
  2269. psychopy/tests/data/greyscale2_degFlat.png +0 -0
  2270. psychopy/tests/data/greyscale2_degFlatPos.png +0 -0
  2271. psychopy/tests/data/greyscale2_height.png +0 -0
  2272. psychopy/tests/data/greyscale2_norm.png +0 -0
  2273. psychopy/tests/data/greyscale2_normAddBlend.png +0 -0
  2274. psychopy/tests/data/greyscale2_normHexbackground.png +0 -0
  2275. psychopy/tests/data/greyscale2_normNoShade.png +0 -0
  2276. psychopy/tests/data/greyscale2_pix.png +0 -0
  2277. psychopy/tests/data/greyscale2_stencil.png +0 -0
  2278. psychopy/tests/data/greyscaleLowContr_cm.png +0 -0
  2279. psychopy/tests/data/greyscaleLowContr_deg.png +0 -0
  2280. psychopy/tests/data/greyscaleLowContr_degFlat.png +0 -0
  2281. psychopy/tests/data/greyscaleLowContr_degFlatPos.png +0 -0
  2282. psychopy/tests/data/greyscaleLowContr_height.png +0 -0
  2283. psychopy/tests/data/greyscaleLowContr_norm.png +0 -0
  2284. psychopy/tests/data/greyscaleLowContr_normAddBlend.png +0 -0
  2285. psychopy/tests/data/greyscaleLowContr_normHexbackground.png +0 -0
  2286. psychopy/tests/data/greyscaleLowContr_normNoShade.png +0 -0
  2287. psychopy/tests/data/greyscaleLowContr_pix.png +0 -0
  2288. psychopy/tests/data/greyscaleLowContr_stencil.png +0 -0
  2289. psychopy/tests/data/greyscale_cm.png +0 -0
  2290. psychopy/tests/data/greyscale_deg.png +0 -0
  2291. psychopy/tests/data/greyscale_degFlat.png +0 -0
  2292. psychopy/tests/data/greyscale_degFlatPos.png +0 -0
  2293. psychopy/tests/data/greyscale_height.png +0 -0
  2294. psychopy/tests/data/greyscale_norm.png +0 -0
  2295. psychopy/tests/data/greyscale_normAddBlend.png +0 -0
  2296. psychopy/tests/data/greyscale_normHexbackground.png +0 -0
  2297. psychopy/tests/data/greyscale_normNoShade.png +0 -0
  2298. psychopy/tests/data/greyscale_pix.png +0 -0
  2299. psychopy/tests/data/greyscale_stencil.png +0 -0
  2300. psychopy/tests/data/imageAndGauss_cm.png +0 -0
  2301. psychopy/tests/data/imageAndGauss_deg.png +0 -0
  2302. psychopy/tests/data/imageAndGauss_degFlat.png +0 -0
  2303. psychopy/tests/data/imageAndGauss_degFlatPos.png +0 -0
  2304. psychopy/tests/data/imageAndGauss_height.png +0 -0
  2305. psychopy/tests/data/imageAndGauss_norm.png +0 -0
  2306. psychopy/tests/data/imageAndGauss_normAddBlend.png +0 -0
  2307. psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
  2308. psychopy/tests/data/imageAndGauss_normHexbackground.png +0 -0
  2309. psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
  2310. psychopy/tests/data/imageAndGauss_normNoShade.png +0 -0
  2311. psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
  2312. psychopy/tests/data/imageAndGauss_pix.png +0 -0
  2313. psychopy/tests/data/imageAndGauss_stencil.png +0 -0
  2314. psychopy/tests/data/movFrame1_cm.png +0 -0
  2315. psychopy/tests/data/movFrame1_deg.png +0 -0
  2316. psychopy/tests/data/movFrame1_degFlat.png +0 -0
  2317. psychopy/tests/data/movFrame1_degFlatPos.png +0 -0
  2318. psychopy/tests/data/movFrame1_height.png +0 -0
  2319. psychopy/tests/data/movFrame1_norm.png +0 -0
  2320. psychopy/tests/data/movFrame1_normAddBlend.png +0 -0
  2321. psychopy/tests/data/movFrame1_normHexbackground.png +0 -0
  2322. psychopy/tests/data/movFrame1_normNoShade.png +0 -0
  2323. psychopy/tests/data/movFrame1_pix.png +0 -0
  2324. psychopy/tests/data/movFrame1_stencil.png +0 -0
  2325. psychopy/tests/data/multiKeypressExperiment.psydat +357 -0
  2326. psychopy/tests/data/multiKeypressTrialhandler.psydat +252 -0
  2327. psychopy/tests/data/multiStairConds.xlsx +0 -0
  2328. psychopy/tests/data/multiStairQuestPlus.xlsx +0 -0
  2329. psychopy/tests/data/newstyle.psydat +6155 -0
  2330. psychopy/tests/data/noiseAndRcos_cm.png +0 -0
  2331. psychopy/tests/data/noiseAndRcos_deg.png +0 -0
  2332. psychopy/tests/data/noiseAndRcos_degFlat.png +0 -0
  2333. psychopy/tests/data/noiseAndRcos_degFlatPos.png +0 -0
  2334. psychopy/tests/data/noiseAndRcos_height.png +0 -0
  2335. psychopy/tests/data/noiseAndRcos_norm.png +0 -0
  2336. psychopy/tests/data/noiseAndRcos_normAddBlend.png +0 -0
  2337. psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
  2338. psychopy/tests/data/noiseAndRcos_normHexbackground.png +0 -0
  2339. psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
  2340. psychopy/tests/data/noiseAndRcos_normNoShade.png +0 -0
  2341. psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
  2342. psychopy/tests/data/noiseAndRcos_pix.png +0 -0
  2343. psychopy/tests/data/noiseAndRcos_stencil.png +0 -0
  2344. psychopy/tests/data/noiseFiltersAndRcos_cm.png +0 -0
  2345. psychopy/tests/data/noiseFiltersAndRcos_deg.png +0 -0
  2346. psychopy/tests/data/noiseFiltersAndRcos_degFlat.png +0 -0
  2347. psychopy/tests/data/noiseFiltersAndRcos_degFlatPos.png +0 -0
  2348. psychopy/tests/data/noiseFiltersAndRcos_height.png +0 -0
  2349. psychopy/tests/data/noiseFiltersAndRcos_norm.png +0 -0
  2350. psychopy/tests/data/noiseFiltersAndRcos_normAddBlend.png +0 -0
  2351. psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
  2352. psychopy/tests/data/noiseFiltersAndRcos_normHexbackground.png +0 -0
  2353. psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
  2354. psychopy/tests/data/noiseFiltersAndRcos_normNoShade.png +0 -0
  2355. psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
  2356. psychopy/tests/data/noiseFiltersAndRcos_pix.png +0 -0
  2357. psychopy/tests/data/noiseFiltersAndRcos_stencil.png +0 -0
  2358. psychopy/tests/data/numpyImage_cm.png +0 -0
  2359. psychopy/tests/data/numpyImage_deg.png +0 -0
  2360. psychopy/tests/data/numpyImage_degFlat.png +0 -0
  2361. psychopy/tests/data/numpyImage_degFlatPos.png +0 -0
  2362. psychopy/tests/data/numpyImage_height.png +0 -0
  2363. psychopy/tests/data/numpyImage_norm.png +0 -0
  2364. psychopy/tests/data/numpyImage_normAddBlend.png +0 -0
  2365. psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
  2366. psychopy/tests/data/numpyImage_normHexbackground.png +0 -0
  2367. psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
  2368. psychopy/tests/data/numpyImage_normNoShade.png +0 -0
  2369. psychopy/tests/data/numpyImage_norm_local.png +0 -0
  2370. psychopy/tests/data/numpyImage_pix.png +0 -0
  2371. psychopy/tests/data/numpyImage_stencil.png +0 -0
  2372. psychopy/tests/data/numpyLowContr_cm.png +0 -0
  2373. psychopy/tests/data/numpyLowContr_deg.png +0 -0
  2374. psychopy/tests/data/numpyLowContr_degFlat.png +0 -0
  2375. psychopy/tests/data/numpyLowContr_degFlatPos.png +0 -0
  2376. psychopy/tests/data/numpyLowContr_height.png +0 -0
  2377. psychopy/tests/data/numpyLowContr_norm.png +0 -0
  2378. psychopy/tests/data/numpyLowContr_normAddBlend.png +0 -0
  2379. psychopy/tests/data/numpyLowContr_normHexbackground.png +0 -0
  2380. psychopy/tests/data/numpyLowContr_normNoShade.png +0 -0
  2381. psychopy/tests/data/numpyLowContr_pix.png +0 -0
  2382. psychopy/tests/data/numpyLowContr_stencil.png +0 -0
  2383. psychopy/tests/data/oldstyle.psydat +1534 -0
  2384. psychopy/tests/data/oldstyle_stair.psydat +263 -0
  2385. psychopy/tests/data/ratingscale1_cm.png +0 -0
  2386. psychopy/tests/data/ratingscale1_deg.png +0 -0
  2387. psychopy/tests/data/ratingscale1_degFlat.png +0 -0
  2388. psychopy/tests/data/ratingscale1_degFlatPos.png +0 -0
  2389. psychopy/tests/data/ratingscale1_height.png +0 -0
  2390. psychopy/tests/data/ratingscale1_norm.png +0 -0
  2391. psychopy/tests/data/ratingscale1_normAddBlend.png +0 -0
  2392. psychopy/tests/data/ratingscale1_normHexbackground.png +0 -0
  2393. psychopy/tests/data/ratingscale1_normNoShade.png +0 -0
  2394. psychopy/tests/data/ratingscale1_pix.png +0 -0
  2395. psychopy/tests/data/ratingscale1_stencil.png +0 -0
  2396. psychopy/tests/data/red_16000.flac.dist +0 -0
  2397. psychopy/tests/data/retroListParam.psyexp +82 -0
  2398. psychopy/tests/data/right_to_left_unidcode.xlsx +0 -0
  2399. psychopy/tests/data/sample.meshwarp.data +11522 -0
  2400. psychopy/tests/data/shape2_1_cm.png +0 -0
  2401. psychopy/tests/data/shape2_1_deg.png +0 -0
  2402. psychopy/tests/data/shape2_1_degFlat.png +0 -0
  2403. psychopy/tests/data/shape2_1_degFlatPos.png +0 -0
  2404. psychopy/tests/data/shape2_1_height.png +0 -0
  2405. psychopy/tests/data/shape2_1_norm.png +0 -0
  2406. psychopy/tests/data/shape2_1_normAddBlend.png +0 -0
  2407. psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
  2408. psychopy/tests/data/shape2_1_normHexbackground.png +0 -0
  2409. psychopy/tests/data/shape2_1_normNoShade.png +0 -0
  2410. psychopy/tests/data/shape2_1_norm_local.png +0 -0
  2411. psychopy/tests/data/shape2_1_pix.png +0 -0
  2412. psychopy/tests/data/shape2_1_stencil.png +0 -0
  2413. psychopy/tests/data/shape2_2_cm.png +0 -0
  2414. psychopy/tests/data/shape2_2_deg.png +0 -0
  2415. psychopy/tests/data/shape2_2_degFlat.png +0 -0
  2416. psychopy/tests/data/shape2_2_degFlatPos.png +0 -0
  2417. psychopy/tests/data/shape2_2_height.png +0 -0
  2418. psychopy/tests/data/shape2_2_norm.png +0 -0
  2419. psychopy/tests/data/shape2_2_normAddBlend.png +0 -0
  2420. psychopy/tests/data/shape2_2_normHexbackground.png +0 -0
  2421. psychopy/tests/data/shape2_2_normNoShade.png +0 -0
  2422. psychopy/tests/data/shape2_2_pix.png +0 -0
  2423. psychopy/tests/data/shape2_2_stencil.png +0 -0
  2424. psychopy/tests/data/simpleimage1_cm.png +0 -0
  2425. psychopy/tests/data/simpleimage1_deg.png +0 -0
  2426. psychopy/tests/data/simpleimage1_degFlat.png +0 -0
  2427. psychopy/tests/data/simpleimage1_degFlatPos.png +0 -0
  2428. psychopy/tests/data/simpleimage1_height.png +0 -0
  2429. psychopy/tests/data/simpleimage1_norm.png +0 -0
  2430. psychopy/tests/data/simpleimage1_normAddBlend.png +0 -0
  2431. psychopy/tests/data/simpleimage1_normHexbackground.png +0 -0
  2432. psychopy/tests/data/simpleimage1_normNoShade.png +0 -0
  2433. psychopy/tests/data/simpleimage1_pix.png +0 -0
  2434. psychopy/tests/data/simpleimage1_stencil.png +0 -0
  2435. psychopy/tests/data/test001EntryImporting.psyexp +66 -0
  2436. psychopy/tests/data/testMovie.mp4 +0 -0
  2437. psychopy/tests/data/test_circle_radius_.1height.png +0 -0
  2438. psychopy/tests/data/test_circle_radius_.2height.png +0 -0
  2439. psychopy/tests/data/test_circle_radius_10pix.png +0 -0
  2440. psychopy/tests/data/test_circle_radius_20pix.png +0 -0
  2441. psychopy/tests/data/test_form_combinations_choice_bigItem.png +0 -0
  2442. psychopy/tests/data/test_form_combinations_choice_bigItemOverflow.png +0 -0
  2443. psychopy/tests/data/test_form_combinations_choice_bigResp.png +0 -0
  2444. psychopy/tests/data/test_form_combinations_choice_bigRespOverflow.png +0 -0
  2445. psychopy/tests/data/test_form_combinations_description_bigItem.png +0 -0
  2446. psychopy/tests/data/test_form_combinations_description_bigItemOverflow.png +0 -0
  2447. psychopy/tests/data/test_form_combinations_description_bigResp.png +0 -0
  2448. psychopy/tests/data/test_form_combinations_description_bigRespOverflow.png +0 -0
  2449. psychopy/tests/data/test_form_combinations_free text_bigItem.png +0 -0
  2450. psychopy/tests/data/test_form_combinations_free text_bigItemOverflow.png +0 -0
  2451. psychopy/tests/data/test_form_combinations_free text_bigResp.png +0 -0
  2452. psychopy/tests/data/test_form_combinations_free text_bigRespOverflow.png +0 -0
  2453. psychopy/tests/data/test_form_combinations_heading_bigItem.png +0 -0
  2454. psychopy/tests/data/test_form_combinations_heading_bigItemOverflow.png +0 -0
  2455. psychopy/tests/data/test_form_combinations_heading_bigResp.png +0 -0
  2456. psychopy/tests/data/test_form_combinations_heading_bigRespOverflow.png +0 -0
  2457. psychopy/tests/data/test_form_combinations_radio_bigItem.png +0 -0
  2458. psychopy/tests/data/test_form_combinations_radio_bigItemOverflow.png +0 -0
  2459. psychopy/tests/data/test_form_combinations_radio_bigResp.png +0 -0
  2460. psychopy/tests/data/test_form_combinations_radio_bigRespOverflow.png +0 -0
  2461. psychopy/tests/data/test_form_combinations_rating_bigItem.png +0 -0
  2462. psychopy/tests/data/test_form_combinations_rating_bigItemOverflow.png +0 -0
  2463. psychopy/tests/data/test_form_combinations_rating_bigResp.png +0 -0
  2464. psychopy/tests/data/test_form_combinations_rating_bigRespOverflow.png +0 -0
  2465. psychopy/tests/data/test_form_combinations_slider_bigItem.png +0 -0
  2466. psychopy/tests/data/test_form_combinations_slider_bigItemOverflow.png +0 -0
  2467. psychopy/tests/data/test_form_combinations_slider_bigResp.png +0 -0
  2468. psychopy/tests/data/test_form_combinations_slider_bigRespOverflow.png +0 -0
  2469. psychopy/tests/data/test_get_resources/blue.png +0 -0
  2470. psychopy/tests/data/test_get_resources/groupA.csv +3 -0
  2471. psychopy/tests/data/test_get_resources/groupB.csv +3 -0
  2472. psychopy/tests/data/test_get_resources/groups.csv +3 -0
  2473. psychopy/tests/data/test_get_resources/handledbyrm_constrloop.psyexp +143 -0
  2474. psychopy/tests/data/test_get_resources/handledbyrm_noloop.psyexp +114 -0
  2475. psychopy/tests/data/test_get_resources/handledbyrm_recurloop.psyexp +138 -0
  2476. psychopy/tests/data/test_get_resources/handledbyrm_strloop.psyexp +126 -0
  2477. psychopy/tests/data/test_get_resources/handledbystatic_constrloop.psyexp +141 -0
  2478. psychopy/tests/data/test_get_resources/handledbystatic_noloop.psyexp +112 -0
  2479. psychopy/tests/data/test_get_resources/handledbystatic_recurloop.psyexp +136 -0
  2480. psychopy/tests/data/test_get_resources/handledbystatic_strloop.psyexp +124 -0
  2481. psychopy/tests/data/test_get_resources/unhandled_constrloop.psyexp +128 -0
  2482. psychopy/tests/data/test_get_resources/unhandled_noloop.psyexp +99 -0
  2483. psychopy/tests/data/test_get_resources/unhandled_recurloop.psyexp +123 -0
  2484. psychopy/tests/data/test_get_resources/unhandled_strloop.psyexp +111 -0
  2485. psychopy/tests/data/test_get_resources/white.png +0 -0
  2486. psychopy/tests/data/test_get_resources/yellow.png +0 -0
  2487. psychopy/tests/data/test_image_aspect_default_None.png +0 -0
  2488. psychopy/tests/data/test_image_aspect_default_xFull_yNone.png +0 -0
  2489. psychopy/tests/data/test_image_aspect_default_xNone_yFull.png +0 -0
  2490. psychopy/tests/data/test_image_aspect_default_xNone_yNone.png +0 -0
  2491. psychopy/tests/data/test_image_flip_anchor_horiz.png +0 -0
  2492. psychopy/tests/data/test_image_flip_anchor_vert.png +0 -0
  2493. psychopy/tests/data/test_loops/testLoopsBlocks.psyexp +161 -0
  2494. psychopy/tests/data/test_loops/testStaircase.psyexp +105 -0
  2495. psychopy/tests/data/test_loops/test_current_loop_attr.psyexp +185 -0
  2496. psychopy/tests/data/test_panorama/panoramaTestImage.png +0 -0
  2497. psychopy/tests/data/test_panorama/panoramaTestImage.svg +1 -0
  2498. psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_-0.3.png +0 -0
  2499. psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_0.3.png +0 -0
  2500. psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_-0.3.png +0 -0
  2501. psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_0.3.png +0 -0
  2502. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_-1.0.png +0 -0
  2503. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_1.0.png +0 -0
  2504. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_-0.3.png +0 -0
  2505. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_0.3.png +0 -0
  2506. psychopy/tests/data/test_resources.psyexp +491 -0
  2507. psychopy/tests/data/test_session/outside_root/externalExp.psyexp +116 -0
  2508. psychopy/tests/data/test_session/root/annotation/annotation.psyexp +161 -0
  2509. psychopy/tests/data/test_session/root/error/error.psyexp +93 -0
  2510. psychopy/tests/data/test_session/root/exp1/exp1.psyexp +133 -0
  2511. psychopy/tests/data/test_session/root/exp2/exp2.psyexp +133 -0
  2512. psychopy/tests/data/test_session/root/frameRate/frameRate.psyexp +114 -0
  2513. psychopy/tests/data/test_session/root/invUseVersion/invUseVersion.psyexp +133 -0
  2514. psychopy/tests/data/test_session/root/testClockFormat/testClockFormat.psyexp +122 -0
  2515. psychopy/tests/data/test_session/root/testCtrls/testCtrls.psyexp +115 -0
  2516. psychopy/tests/data/test_session/root/testEditExpInfo/testEditExpInfo.psyexp +135 -0
  2517. psychopy/tests/data/test_session/root/testNamedButtonBox/testNamedButtonBox.psyexp +145 -0
  2518. psychopy/tests/data/test_slider_horiz_accute_horiz.png +0 -0
  2519. psychopy/tests/data/test_slider_horiz_accute_vert.png +0 -0
  2520. psychopy/tests/data/test_slider_horiz_horiz.png +0 -0
  2521. psychopy/tests/data/test_slider_horiz_obtuse_horiz.png +0 -0
  2522. psychopy/tests/data/test_slider_horiz_obtuse_vert.png +0 -0
  2523. psychopy/tests/data/test_slider_horiz_vert.png +0 -0
  2524. psychopy/tests/data/test_slider_ticklabelloc_blanks.png +0 -0
  2525. psychopy/tests/data/test_slider_ticklabelloc_clustered.png +0 -0
  2526. psychopy/tests/data/test_slider_ticklabelloc_morelabels.png +0 -0
  2527. psychopy/tests/data/test_slider_ticklabelloc_morelabelsclustered.png +0 -0
  2528. psychopy/tests/data/test_slider_ticklabelloc_moreticks.png +0 -0
  2529. psychopy/tests/data/test_slider_ticklabelloc_moreticksclustered.png +0 -0
  2530. psychopy/tests/data/test_slider_ticklabelloc_nolabels.png +0 -0
  2531. psychopy/tests/data/test_slider_ticklabelloc_noticks.png +0 -0
  2532. psychopy/tests/data/test_slider_ticklabelloc_simple.png +0 -0
  2533. psychopy/tests/data/test_slider_triangle_horiz_False_flip_False.png +0 -0
  2534. psychopy/tests/data/test_slider_triangle_horiz_False_flip_True.png +0 -0
  2535. psychopy/tests/data/test_slider_triangle_horiz_True_flip_False.png +0 -0
  2536. psychopy/tests/data/test_slider_triangle_horiz_True_flip_True.png +0 -0
  2537. psychopy/tests/data/test_static_component_script.psyexp +76 -0
  2538. psychopy/tests/data/test_win_bg_large_256_default.png +0 -0
  2539. psychopy/tests/data/test_win_bg_large_500_default.png +0 -0
  2540. psychopy/tests/data/test_win_bg_small_200_default.png +0 -0
  2541. psychopy/tests/data/test_win_bg_small_256_default.png +0 -0
  2542. psychopy/tests/data/test_win_bg_tall_200_default.png +0 -0
  2543. psychopy/tests/data/test_win_bg_tall_256_default.png +0 -0
  2544. psychopy/tests/data/test_win_bg_tall_500_default.png +0 -0
  2545. psychopy/tests/data/test_win_bg_tall_fill_default.png +0 -0
  2546. psychopy/tests/data/test_win_bg_wide_200_default.png +0 -0
  2547. psychopy/tests/data/test_win_bg_wide_256_default.png +0 -0
  2548. psychopy/tests/data/test_win_bg_wide_500_default.png +0 -0
  2549. psychopy/tests/data/test_win_bg_wide_fill_default.png +0 -0
  2550. psychopy/tests/data/test_win_bgcolor_blue.png +0 -0
  2551. psychopy/tests/data/test_win_bgcolor_green.png +0 -0
  2552. psychopy/tests/data/test_win_bgcolor_red.png +0 -0
  2553. psychopy/tests/data/testimage.jpg +0 -0
  2554. psychopy/tests/data/testimagegray.jpg +0 -0
  2555. psychopy/tests/data/testpixels.png +0 -0
  2556. psychopy/tests/data/testwedges.png +0 -0
  2557. psychopy/tests/data/text1_cm.png +0 -0
  2558. psychopy/tests/data/text1_deg.png +0 -0
  2559. psychopy/tests/data/text1_degFlat.png +0 -0
  2560. psychopy/tests/data/text1_degFlatPos.png +0 -0
  2561. psychopy/tests/data/text1_height.png +0 -0
  2562. psychopy/tests/data/text1_norm.png +0 -0
  2563. psychopy/tests/data/text1_normAddBlend.png +0 -0
  2564. psychopy/tests/data/text1_normAddBlend_local.png +0 -0
  2565. psychopy/tests/data/text1_normHexbackground.png +0 -0
  2566. psychopy/tests/data/text1_normHexbackground_local.png +0 -0
  2567. psychopy/tests/data/text1_normNoShade.png +0 -0
  2568. psychopy/tests/data/text1_norm_local.png +0 -0
  2569. psychopy/tests/data/text1_pix.png +0 -0
  2570. psychopy/tests/data/text1_stencil.png +0 -0
  2571. psychopy/tests/data/text2_cm.png +0 -0
  2572. psychopy/tests/data/text2_deg.png +0 -0
  2573. psychopy/tests/data/text2_degFlat.png +0 -0
  2574. psychopy/tests/data/text2_degFlatPos.png +0 -0
  2575. psychopy/tests/data/text2_height.png +0 -0
  2576. psychopy/tests/data/text2_norm.png +0 -0
  2577. psychopy/tests/data/text2_normNoShade.png +0 -0
  2578. psychopy/tests/data/text2_pix.png +0 -0
  2579. psychopy/tests/data/text2_stencil.png +0 -0
  2580. psychopy/tests/data/textbox_charcolors_default_roygbiv.png +0 -0
  2581. psychopy/tests/data/textbox_charcolors_default_white_hello_black_there.png +0 -0
  2582. psychopy/tests/data/textbox_default_align_bottom_center.png +0 -0
  2583. psychopy/tests/data/textbox_default_align_bottom_left.png +0 -0
  2584. psychopy/tests/data/textbox_default_align_bottom_right.png +0 -0
  2585. psychopy/tests/data/textbox_default_align_center.png +0 -0
  2586. psychopy/tests/data/textbox_default_align_center_center.png +0 -0
  2587. psychopy/tests/data/textbox_default_align_center_left.png +0 -0
  2588. psychopy/tests/data/textbox_default_align_center_right.png +0 -0
  2589. psychopy/tests/data/textbox_default_align_centre.png +0 -0
  2590. psychopy/tests/data/textbox_default_align_centre_centre.png +0 -0
  2591. psychopy/tests/data/textbox_default_align_more_than_two_words.png +0 -0
  2592. psychopy/tests/data/textbox_default_align_someword.png +0 -0
  2593. psychopy/tests/data/textbox_default_align_top_center.png +0 -0
  2594. psychopy/tests/data/textbox_default_align_top_left.png +0 -0
  2595. psychopy/tests/data/textbox_default_align_top_right.png +0 -0
  2596. psychopy/tests/data/textbox_default_colors_WOB.png +0 -0
  2597. psychopy/tests/data/textbox_default_colors_exemplar1.png +0 -0
  2598. psychopy/tests/data/textbox_default_colors_exemplar2.png +0 -0
  2599. psychopy/tests/data/textbox_default_colors_exemplar3.png +0 -0
  2600. psychopy/tests/data/textbox_default_colors_tyke1.png +0 -0
  2601. psychopy/tests/data/textbox_default_colors_tyke2.png +0 -0
  2602. psychopy/tests/data/textbox_default_colors_tyke3.png +0 -0
  2603. psychopy/tests/data/textbox_default_cutoff_top.png +0 -0
  2604. psychopy/tests/data/textbox_default_exemplar_1.png +0 -0
  2605. psychopy/tests/data/textbox_default_exemplar_2.png +0 -0
  2606. psychopy/tests/data/textbox_default_exemplar_3.png +0 -0
  2607. psychopy/tests/data/textbox_default_exemplar_4.png +0 -0
  2608. psychopy/tests/data/textbox_default_tyke_1.png +0 -0
  2609. psychopy/tests/data/textbox_default_tyke_2.png +0 -0
  2610. psychopy/tests/data/textbox_typing_blank.png +0 -0
  2611. psychopy/tests/data/textbox_typing_longKoeran.png +0 -0
  2612. psychopy/tests/data/textbox_typing_longWord.png +0 -0
  2613. psychopy/tests/data/textbox_typing_navDel.png +0 -0
  2614. psychopy/tests/data/textbox_typing_navLR.png +0 -0
  2615. psychopy/tests/data/textbox_typing_newline.png +0 -0
  2616. psychopy/tests/data/textbox_typing_pangram.png +0 -0
  2617. psychopy/tests/data/textbox_uax14_align_bottom_center.png +0 -0
  2618. psychopy/tests/data/textbox_uax14_align_bottom_left.png +0 -0
  2619. psychopy/tests/data/textbox_uax14_align_bottom_right.png +0 -0
  2620. psychopy/tests/data/textbox_uax14_align_center.png +0 -0
  2621. psychopy/tests/data/textbox_uax14_align_center_center.png +0 -0
  2622. psychopy/tests/data/textbox_uax14_align_center_left.png +0 -0
  2623. psychopy/tests/data/textbox_uax14_align_center_right.png +0 -0
  2624. psychopy/tests/data/textbox_uax14_align_centre.png +0 -0
  2625. psychopy/tests/data/textbox_uax14_align_centre_centre.png +0 -0
  2626. psychopy/tests/data/textbox_uax14_align_more_than_two_words.png +0 -0
  2627. psychopy/tests/data/textbox_uax14_align_someword.png +0 -0
  2628. psychopy/tests/data/textbox_uax14_align_top_center.png +0 -0
  2629. psychopy/tests/data/textbox_uax14_align_top_left.png +0 -0
  2630. psychopy/tests/data/textbox_uax14_align_top_right.png +0 -0
  2631. psychopy/tests/data/textbox_uax14_colors_WOB.png +0 -0
  2632. psychopy/tests/data/textbox_uax14_colors_exemplar1.png +0 -0
  2633. psychopy/tests/data/textbox_uax14_colors_exemplar2.png +0 -0
  2634. psychopy/tests/data/textbox_uax14_colors_exemplar3.png +0 -0
  2635. psychopy/tests/data/textbox_uax14_colors_tyke1.png +0 -0
  2636. psychopy/tests/data/textbox_uax14_colors_tyke2.png +0 -0
  2637. psychopy/tests/data/textbox_uax14_colors_tyke3.png +0 -0
  2638. psychopy/tests/data/textbox_uax14_cutoff_top.png +0 -0
  2639. psychopy/tests/data/textbox_uax14_exemplar_1.png +0 -0
  2640. psychopy/tests/data/textbox_uax14_exemplar_2.png +0 -0
  2641. psychopy/tests/data/textbox_uax14_exemplar_3.png +0 -0
  2642. psychopy/tests/data/textbox_uax14_exemplar_4.png +0 -0
  2643. psychopy/tests/data/textbox_uax14_tyke_1.png +0 -0
  2644. psychopy/tests/data/textbox_uax14_tyke_2.png +0 -0
  2645. psychopy/tests/data/trialTypes.csv +1 -0
  2646. psychopy/tests/data/trialTypes.docx +0 -0
  2647. psychopy/tests/data/trialTypes.pkl +0 -0
  2648. psychopy/tests/data/trialTypes.tsv +7 -0
  2649. psychopy/tests/data/trialTypes.xls +0 -0
  2650. psychopy/tests/data/trialTypes.xlsx +0 -0
  2651. psychopy/tests/data/trialTypes_eu.csv +7 -0
  2652. psychopy/tests/data/trialsBlankCols.xlsx +0 -0
  2653. psychopy/tests/data/wedge1_cm.png +0 -0
  2654. psychopy/tests/data/wedge1_deg.png +0 -0
  2655. psychopy/tests/data/wedge1_degFlat.png +0 -0
  2656. psychopy/tests/data/wedge1_degFlatPos.png +0 -0
  2657. psychopy/tests/data/wedge1_height.png +0 -0
  2658. psychopy/tests/data/wedge1_norm.png +0 -0
  2659. psychopy/tests/data/wedge1_normAddBlend.png +0 -0
  2660. psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
  2661. psychopy/tests/data/wedge1_normHexbackground.png +0 -0
  2662. psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
  2663. psychopy/tests/data/wedge1_normNoShade.png +0 -0
  2664. psychopy/tests/data/wedge1_norm_local.png +0 -0
  2665. psychopy/tests/data/wedge1_pix.png +0 -0
  2666. psychopy/tests/data/wedge1_stencil.png +0 -0
  2667. psychopy/tests/data/wedge2_cm.png +0 -0
  2668. psychopy/tests/data/wedge2_deg.png +0 -0
  2669. psychopy/tests/data/wedge2_degFlat.png +0 -0
  2670. psychopy/tests/data/wedge2_degFlatPos.png +0 -0
  2671. psychopy/tests/data/wedge2_height.png +0 -0
  2672. psychopy/tests/data/wedge2_norm.png +0 -0
  2673. psychopy/tests/data/wedge2_normAddBlend.png +0 -0
  2674. psychopy/tests/data/wedge2_normHexbackground.png +0 -0
  2675. psychopy/tests/data/wedge2_normNoShade.png +0 -0
  2676. psychopy/tests/data/wedge2_pix.png +0 -0
  2677. psychopy/tests/data/wedge2_stencil.png +0 -0
  2678. psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos-0.4_0.png +0 -0
  2679. psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos0_0.png +0 -0
  2680. psychopy/tests/data/winScalePos_ori0_scale-1_1_pos-0.4_0.png +0 -0
  2681. psychopy/tests/data/winScalePos_ori0_scale-1_1_pos0_0.png +0 -0
  2682. psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos-0.4_0.png +0 -0
  2683. psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos0_0.png +0 -0
  2684. psychopy/tests/data/winScalePos_ori0_scale-2_2_pos-0.4_0.png +0 -0
  2685. psychopy/tests/data/winScalePos_ori0_scale-2_2_pos0_0.png +0 -0
  2686. psychopy/tests/data/winScalePos_ori0_scale1_-1_pos-0.4_0.png +0 -0
  2687. psychopy/tests/data/winScalePos_ori0_scale1_-1_pos0_0.png +0 -0
  2688. psychopy/tests/data/winScalePos_ori0_scale1_1_pos-0.4_0.png +0 -0
  2689. psychopy/tests/data/winScalePos_ori0_scale1_1_pos0_0.png +0 -0
  2690. psychopy/tests/data/winScalePos_ori0_scale2_-2_pos-0.4_0.png +0 -0
  2691. psychopy/tests/data/winScalePos_ori0_scale2_-2_pos0_0.png +0 -0
  2692. psychopy/tests/data/winScalePos_ori0_scale2_2_pos-0.4_0.png +0 -0
  2693. psychopy/tests/data/winScalePos_ori0_scale2_2_pos0_0.png +0 -0
  2694. psychopy/tests/data/winScalePos_ori45_scale-1_-1_pos0_0.png +0 -0
  2695. psychopy/tests/data/winScalePos_ori45_scale-1_1_pos0_0.png +0 -0
  2696. psychopy/tests/data/winScalePos_ori45_scale-2_-2_pos0_0.png +0 -0
  2697. psychopy/tests/data/winScalePos_ori45_scale-2_2_pos0_0.png +0 -0
  2698. psychopy/tests/data/winScalePos_ori45_scale1_-1_pos0_0.png +0 -0
  2699. psychopy/tests/data/winScalePos_ori45_scale1_1_pos0_0.png +0 -0
  2700. psychopy/tests/data/winScalePos_ori45_scale2_-2_pos0_0.png +0 -0
  2701. psychopy/tests/data/winScalePos_ori45_scale2_2_pos0_0.png +0 -0
  2702. psychopy/tests/doc/run_btn.png +0 -0
  2703. psychopy/tests/doc/test-suite-bugs.jpg +0 -0
  2704. psychopy/tests/doc/user-bugs.jpg +0 -0
  2705. psychopy/tests/dummy_xorg.conf +21 -0
  2706. psychopy/tests/run.py +50 -0
  2707. psychopy/tests/test_Installation.py +38 -0
  2708. psychopy/tests/test_alerts/test_alerts.py +37 -0
  2709. psychopy/tests/test_alerts/test_alerttools.py +131 -0
  2710. psychopy/tests/test_app/__init__.py +0 -0
  2711. psychopy/tests/test_app/conftest.py +41 -0
  2712. psychopy/tests/test_app/test_builder/__init__.py +0 -0
  2713. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +151 -0
  2714. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +135 -0
  2715. psychopy/tests/test_app/test_builder/test_ComponentDialogs.py +98 -0
  2716. psychopy/tests/test_app/test_runner/__init__.py +0 -0
  2717. psychopy/tests/test_app/test_runner/test_RunnerFrame.py +166 -0
  2718. psychopy/tests/test_app/test_speed.py +144 -0
  2719. psychopy/tests/test_app/test_themes/test_icons.py +79 -0
  2720. psychopy/tests/test_codecov.py +29 -0
  2721. psychopy/tests/test_colors/test_Color.py +41 -0
  2722. psychopy/tests/test_data/dataPrev.xlsx +0 -0
  2723. psychopy/tests/test_data/test_ExperimentHandler.py +171 -0
  2724. psychopy/tests/test_data/test_MultiStairHandler.py +174 -0
  2725. psychopy/tests/test_data/test_StairHandlers.py +1241 -0
  2726. psychopy/tests/test_data/test_TrialHandler.py +335 -0
  2727. psychopy/tests/test_data/test_TrialHandler2.py +408 -0
  2728. psychopy/tests/test_data/test_TrialHandlerExt.py +359 -0
  2729. psychopy/tests/test_data/test_fitFunctions.py +101 -0
  2730. psychopy/tests/test_data/test_utils.py +169 -0
  2731. psychopy/tests/test_data/test_xlsx.py +92 -0
  2732. psychopy/tests/test_demos/test_builder_demos.py +52 -0
  2733. psychopy/tests/test_experiment/__init__.py +0 -0
  2734. psychopy/tests/test_experiment/known_py_diffs.txt +47 -0
  2735. psychopy/tests/test_experiment/needs_wx/__init__.py +0 -0
  2736. psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +13730 -0
  2737. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +130 -0
  2738. psychopy/tests/test_experiment/needs_wx/test_Experiment.py +406 -0
  2739. psychopy/tests/test_experiment/needs_wx/test_components.py +170 -0
  2740. psychopy/tests/test_experiment/test_component_compile_js.py +59 -0
  2741. psychopy/tests/test_experiment/test_component_compile_python.py +165 -0
  2742. psychopy/tests/test_experiment/test_components/__init__.py +1 -0
  2743. psychopy/tests/test_experiment/test_components/test_ButtonBox.py +216 -0
  2744. psychopy/tests/test_experiment/test_components/test_Code.py +82 -0
  2745. psychopy/tests/test_experiment/test_components/test_Image.py +24 -0
  2746. psychopy/tests/test_experiment/test_components/test_Mouse.py +149 -0
  2747. psychopy/tests/test_experiment/test_components/test_Polygon.py +81 -0
  2748. psychopy/tests/test_experiment/test_components/test_ResourceManager.py +60 -0
  2749. psychopy/tests/test_experiment/test_components/test_Settings.py +82 -0
  2750. psychopy/tests/test_experiment/test_components/test_Static.py +60 -0
  2751. psychopy/tests/test_experiment/test_components/test_all_components.py +162 -0
  2752. psychopy/tests/test_experiment/test_components/test_base_components.py +323 -0
  2753. psychopy/tests/test_experiment/test_experiment.py +103 -0
  2754. psychopy/tests/test_experiment/test_loops.py +95 -0
  2755. psychopy/tests/test_experiment/test_params.py +350 -0
  2756. psychopy/tests/test_experiment/test_py2js.py +230 -0
  2757. psychopy/tests/test_experiment/test_routines/__init__.py +1 -0
  2758. psychopy/tests/test_experiment/test_routines/test_EyetrackerCalibrationRoutine.py +9 -0
  2759. psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +9 -0
  2760. psychopy/tests/test_experiment/test_routines/test_all_routines.py +23 -0
  2761. psychopy/tests/test_experiment/test_routines/test_base_routine.py +154 -0
  2762. psychopy/tests/test_experiment/test_routines/test_standalone_routines.py +36 -0
  2763. psychopy/tests/test_gui/test_DlgFromDictQt.py +82 -0
  2764. psychopy/tests/test_gui/test_DlgFromDictWx.py +82 -0
  2765. psychopy/tests/test_hardware/__init__.py +0 -0
  2766. psychopy/tests/test_hardware/test_CRS_BitsSharp.py +67 -0
  2767. psychopy/tests/test_hardware/test_CRS_bitsShaders.py +110 -0
  2768. psychopy/tests/test_hardware/test_device_manager.py +36 -0
  2769. psychopy/tests/test_hardware/test_emulator.py +106 -0
  2770. psychopy/tests/test_hardware/test_gammasci.py +35 -0
  2771. psychopy/tests/test_hardware/test_keyboard.py +73 -0
  2772. psychopy/tests/test_hardware/test_keyboard_events.py +362 -0
  2773. psychopy/tests/test_hardware/test_ports.py +151 -0
  2774. psychopy/tests/test_iohub/__init__.py +1 -0
  2775. psychopy/tests/test_iohub/test_computer.py +124 -0
  2776. psychopy/tests/test_iohub/test_event_get_and_clear.py +94 -0
  2777. psychopy/tests/test_iohub/test_keyboard.py +91 -0
  2778. psychopy/tests/test_iohub/test_launch.py +19 -0
  2779. psychopy/tests/test_iohub/testutil.py +54 -0
  2780. psychopy/tests/test_liaison/test_Liaison.py +228 -0
  2781. psychopy/tests/test_misc/__init__.py +0 -0
  2782. psychopy/tests/test_misc/memory_usage.py +164 -0
  2783. psychopy/tests/test_misc/test_GammaFun.py +15 -0
  2784. psychopy/tests/test_misc/test_clock.py +61 -0
  2785. psychopy/tests/test_misc/test_color.py +203 -0
  2786. psychopy/tests/test_misc/test_core.py +446 -0
  2787. psychopy/tests/test_misc/test_event.py +307 -0
  2788. psychopy/tests/test_misc/test_info.py +18 -0
  2789. psychopy/tests/test_misc/test_layout.py +49 -0
  2790. psychopy/tests/test_misc/test_locale.py +38 -0
  2791. psychopy/tests/test_misc/test_web.py +22 -0
  2792. psychopy/tests/test_monitors/test_monitors.py +91 -0
  2793. psychopy/tests/test_preferences/__init__.py +0 -0
  2794. psychopy/tests/test_preferences/test_prefs.py +28 -0
  2795. psychopy/tests/test_session/test_Session.py +253 -0
  2796. psychopy/tests/test_sound/__init__.py +0 -0
  2797. psychopy/tests/test_sound/test_audioclip.py +331 -0
  2798. psychopy/tests/test_sound/test_hamming.py +45 -0
  2799. psychopy/tests/test_sound/test_microphone.py +217 -0
  2800. psychopy/tests/test_sound/test_sound.py +67 -0
  2801. psychopy/tests/test_sound/test_sound_pygame.py +67 -0
  2802. psychopy/tests/test_tools/test_animationtools.py +51 -0
  2803. psychopy/tests/test_tools/test_arraytools.py +130 -0
  2804. psychopy/tests/test_tools/test_attributetools.py +257 -0
  2805. psychopy/tests/test_tools/test_colorspacetools.py +149 -0
  2806. psychopy/tests/test_tools/test_environmenttools.py +52 -0
  2807. psychopy/tests/test_tools/test_fileerrortools.py +97 -0
  2808. psychopy/tests/test_tools/test_filetools.py +104 -0
  2809. psychopy/tests/test_tools/test_imagetools.py +57 -0
  2810. psychopy/tests/test_tools/test_mathtools.py +688 -0
  2811. psychopy/tests/test_tools/test_stringtools.py +143 -0
  2812. psychopy/tests/test_tools/test_versionchooser.py +178 -0
  2813. psychopy/tests/test_tools/test_viewtools.py +67 -0
  2814. psychopy/tests/test_visual/__init__.py +0 -0
  2815. psychopy/tests/test_visual/measure_parity.py +243 -0
  2816. psychopy/tests/test_visual/test_all_stimuli.py +752 -0
  2817. psychopy/tests/test_visual/test_basevisual.py +534 -0
  2818. psychopy/tests/test_visual/test_brush.py +68 -0
  2819. psychopy/tests/test_visual/test_button.py +15 -0
  2820. psychopy/tests/test_visual/test_circle.py +66 -0
  2821. psychopy/tests/test_visual/test_contains_overlaps.py +242 -0
  2822. psychopy/tests/test_visual/test_custommouse.py +47 -0
  2823. psychopy/tests/test_visual/test_dots.py +155 -0
  2824. psychopy/tests/test_visual/test_form.py +414 -0
  2825. psychopy/tests/test_visual/test_framepacking.py +41 -0
  2826. psychopy/tests/test_visual/test_gamma.py +152 -0
  2827. psychopy/tests/test_visual/test_glfw_backend.py +14 -0
  2828. psychopy/tests/test_visual/test_image.py +218 -0
  2829. psychopy/tests/test_visual/test_panorama.py +41 -0
  2830. psychopy/tests/test_visual/test_progress.py +96 -0
  2831. psychopy/tests/test_visual/test_projections.py +215 -0
  2832. psychopy/tests/test_visual/test_projections_interactive.py +163 -0
  2833. psychopy/tests/test_visual/test_ratingScale.py +299 -0
  2834. psychopy/tests/test_visual/test_roi.py +99 -0
  2835. psychopy/tests/test_visual/test_shape.py +23 -0
  2836. psychopy/tests/test_visual/test_slider.py +328 -0
  2837. psychopy/tests/test_visual/test_target.py +35 -0
  2838. psychopy/tests/test_visual/test_textbox.py +474 -0
  2839. psychopy/tests/test_visual/test_winFlipTiming.py +95 -0
  2840. psychopy/tests/test_visual/test_winScalePos.py +77 -0
  2841. psychopy/tests/test_visual/test_window.py +107 -0
  2842. psychopy/tests/utils.py +318 -0
  2843. psychopy/tools/LineBreak.txt +3597 -0
  2844. psychopy/tools/__init__.py +9 -0
  2845. psychopy/tools/animationtools.py +49 -0
  2846. psychopy/tools/apptools.py +32 -0
  2847. psychopy/tools/arraytools.py +514 -0
  2848. psychopy/tools/attributetools.py +205 -0
  2849. psychopy/tools/audiotools.py +358 -0
  2850. psychopy/tools/colorspacetools.py +715 -0
  2851. psychopy/tools/coordinatetools.py +104 -0
  2852. psychopy/tools/environmenttools.py +62 -0
  2853. psychopy/tools/fileerrortools.py +54 -0
  2854. psychopy/tools/filetools.py +406 -0
  2855. psychopy/tools/fontmanager.py +1041 -0
  2856. psychopy/tools/gltools.py +4959 -0
  2857. psychopy/tools/imagetools.py +65 -0
  2858. psychopy/tools/linebreak.py +322 -0
  2859. psychopy/tools/mathtools.py +4040 -0
  2860. psychopy/tools/monitorunittools.py +270 -0
  2861. psychopy/tools/movietools.py +1071 -0
  2862. psychopy/tools/pkgtools.py +601 -0
  2863. psychopy/tools/plottools.py +25 -0
  2864. psychopy/tools/rifttools.py +76 -0
  2865. psychopy/tools/stereotools.py +9 -0
  2866. psychopy/tools/stimulustools.py +28 -0
  2867. psychopy/tools/stringtools.py +421 -0
  2868. psychopy/tools/systemtools.py +1331 -0
  2869. psychopy/tools/typetools.py +52 -0
  2870. psychopy/tools/unittools.py +16 -0
  2871. psychopy/tools/versionchooser.py +602 -0
  2872. psychopy/tools/viewtools.py +1084 -0
  2873. psychopy/tools/wizard.py +804 -0
  2874. psychopy/visual/__init__.py +124 -0
  2875. psychopy/visual/aperture.py +330 -0
  2876. psychopy/visual/backends/__init__.py +77 -0
  2877. psychopy/visual/backends/_base.py +481 -0
  2878. psychopy/visual/backends/gamma.py +349 -0
  2879. psychopy/visual/backends/glfwbackend.py +28 -0
  2880. psychopy/visual/backends/pygamebackend.py +343 -0
  2881. psychopy/visual/backends/pygletbackend.py +920 -0
  2882. psychopy/visual/basevisual.py +1952 -0
  2883. psychopy/visual/brush.py +204 -0
  2884. psychopy/visual/bufferimage.py +231 -0
  2885. psychopy/visual/button.py +206 -0
  2886. psychopy/visual/circle.py +173 -0
  2887. psychopy/visual/custommouse.py +263 -0
  2888. psychopy/visual/dot.py +668 -0
  2889. psychopy/visual/dropdown.py +165 -0
  2890. psychopy/visual/elementarray.py +728 -0
  2891. psychopy/visual/filters.py +419 -0
  2892. psychopy/visual/form.py +1180 -0
  2893. psychopy/visual/globalVars.py +24 -0
  2894. psychopy/visual/grating.py +501 -0
  2895. psychopy/visual/helpers.py +334 -0
  2896. psychopy/visual/image.py +440 -0
  2897. psychopy/visual/line.py +238 -0
  2898. psychopy/visual/movie.py +12 -0
  2899. psychopy/visual/movie2.py +829 -0
  2900. psychopy/visual/movie3.py +599 -0
  2901. psychopy/visual/movies/__init__.py +850 -0
  2902. psychopy/visual/movies/frame.py +255 -0
  2903. psychopy/visual/movies/metadata.py +242 -0
  2904. psychopy/visual/movies/players/__init__.py +62 -0
  2905. psychopy/visual/movies/players/_base.py +364 -0
  2906. psychopy/visual/movies/players/ffpyplayer_player.py +1399 -0
  2907. psychopy/visual/nnlvs.py +827 -0
  2908. psychopy/visual/noise.py +34 -0
  2909. psychopy/visual/panorama.py +313 -0
  2910. psychopy/visual/patch.py +29 -0
  2911. psychopy/visual/pie.py +248 -0
  2912. psychopy/visual/polygon.py +229 -0
  2913. psychopy/visual/progress.py +313 -0
  2914. psychopy/visual/radial.py +33 -0
  2915. psychopy/visual/ratingscale.py +1424 -0
  2916. psychopy/visual/rect.py +204 -0
  2917. psychopy/visual/rift.py +2654 -0
  2918. psychopy/visual/roi.py +141 -0
  2919. psychopy/visual/secondorder.py +29 -0
  2920. psychopy/visual/shaders.py +453 -0
  2921. psychopy/visual/shape.py +701 -0
  2922. psychopy/visual/simpleimage.py +303 -0
  2923. psychopy/visual/slider.py +1169 -0
  2924. psychopy/visual/stim3d.py +2712 -0
  2925. psychopy/visual/target.py +278 -0
  2926. psychopy/visual/text.py +759 -0
  2927. psychopy/visual/textbox/__init__.py +1280 -0
  2928. psychopy/visual/textbox/fontmanager.py +574 -0
  2929. psychopy/visual/textbox/parsedtext.py +317 -0
  2930. psychopy/visual/textbox/textgrid.py +278 -0
  2931. psychopy/visual/textbox/textureatlas.py +248 -0
  2932. psychopy/visual/textbox2/__init__.py +4 -0
  2933. psychopy/visual/textbox2/textbox2.py +1817 -0
  2934. psychopy/visual/vlcmoviestim.py +1294 -0
  2935. psychopy/visual/window.py +3706 -0
  2936. psychopy/visual/windowframepack.py +86 -0
  2937. psychopy/visual/windowwarp.py +457 -0
  2938. psychopy/voicekey/__init__.py +684 -0
  2939. psychopy/voicekey/demo_vks.py +12 -0
  2940. psychopy/voicekey/labjack_vks.py +50 -0
  2941. psychopy/voicekey/parallel_vks.py +44 -0
  2942. psychopy/voicekey/signal.py +42 -0
  2943. psychopy/voicekey/vk_tools.py +280 -0
  2944. psychopy/web.py +286 -0
  2945. psychopy-2024.1.0.dist-info/METADATA +154 -0
  2946. psychopy-2024.1.0.dist-info/RECORD +2950 -0
  2947. psychopy-2024.1.0.dist-info/WHEEL +4 -0
  2948. psychopy-2024.1.0.dist-info/entry_points.txt +3 -0
  2949. psychopy-2024.1.0.dist-info/licenses/AUTHORS.md +138 -0
  2950. psychopy-2024.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,4658 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Defines the behavior of Psychopy's Builder view window
6
+ Part of the PsychoPy library
7
+ Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
8
+ Distributed under the terms of the GNU General Public License (GPL).
9
+ """
10
+ import collections
11
+ import os, sys
12
+ import subprocess
13
+ import webbrowser
14
+ from collections import OrderedDict
15
+ from pathlib import Path
16
+ import glob
17
+ import copy
18
+ import traceback
19
+ import codecs
20
+ from types import SimpleNamespace
21
+
22
+ import numpy
23
+ import requests
24
+ import io
25
+
26
+ from pkg_resources import parse_version
27
+ import wx.stc
28
+ from wx.lib import scrolledpanel
29
+ from wx.lib import platebtn
30
+ from wx.html import HtmlWindow
31
+
32
+ import psychopy.app.plugin_manager.dialog
33
+ from .validators import WarningManager
34
+ from ..pavlovia_ui import sync, PavloviaMiniBrowser
35
+ from ..pavlovia_ui.project import ProjectFrame
36
+ from ..pavlovia_ui.search import SearchFrame
37
+ from ..pavlovia_ui.user import UserFrame
38
+ from ..pavlovia_ui.functions import logInPavlovia
39
+ from ...experiment import getAllElements, getAllCategories
40
+ from ...experiment.routines import Routine, BaseStandaloneRoutine
41
+ from psychopy.tools.versionchooser import parseVersionSafely, psychopyVersion
42
+
43
+ try:
44
+ import markdown_it as md
45
+ except ImportError:
46
+ md = None
47
+ import wx.lib.agw.aui as aui # some versions of phoenix
48
+ try:
49
+ from wx.adv import PseudoDC
50
+ except ImportError:
51
+ from wx import PseudoDC
52
+
53
+ if parse_version(wx.__version__) < parse_version('4.0.3'):
54
+ wx.NewIdRef = wx.NewId
55
+
56
+ from psychopy.localization import _translate
57
+ from ... import experiment, prefs
58
+ from .. import dialogs, utils, ribbon
59
+ from ..themes import icons, colors, handlers
60
+ from ..themes.ui import ThemeSwitcher
61
+ from ..ui import BaseAuiFrame
62
+ from psychopy import logging, data
63
+ from psychopy.tools.filetools import mergeFolder
64
+ from .dialogs import (DlgComponentProperties, DlgExperimentProperties,
65
+ DlgCodeComponentProperties, DlgLoopProperties,
66
+ ParamNotebook, DlgNewRoutine, BuilderFindDlg)
67
+ from ..utils import (BasePsychopyToolbar, HoverButton, WindowFrozen,
68
+ FileDropTarget, FrameSwitcher, updateDemosMenu,
69
+ ToggleButtonArray, HoverMixin)
70
+
71
+ from psychopy.experiment import getAllStandaloneRoutines
72
+ from psychopy.app import pavlovia_ui
73
+ from psychopy.projects import pavlovia
74
+ from psychopy.tools import stringtools as st
75
+ from psychopy.scripts.psyexpCompile import generateScript
76
+
77
+ # Components which are always hidden
78
+ alwaysHidden = [
79
+ 'SettingsComponent', 'RoutineSettingsComponent', 'UnknownComponent', 'UnknownRoutine',
80
+ 'UnknownStandaloneRoutine', 'UnknownPluginComponent', 'BaseComponent', 'BaseStandaloneRoutine',
81
+ 'BaseValidatorRoutine'
82
+ ]
83
+
84
+
85
+ class TemplateManager(dict):
86
+ mainFolder = Path(prefs.paths['resources']).absolute() / 'routine_templates'
87
+ userFolder = Path(prefs.paths['userPrefsDir']).absolute() / 'routine_templates'
88
+ experimentFiles = {}
89
+
90
+ def __init__(self):
91
+ dict.__init__(self)
92
+ self.updateTemplates()
93
+
94
+ def updateTemplates(self, ):
95
+ """Search and import templates in the standard files"""
96
+ for folder in [TemplateManager.mainFolder, TemplateManager.userFolder]:
97
+ categs = folder.glob("*.psyexp")
98
+ for filePath in categs:
99
+ thisExp = experiment.Experiment()
100
+ thisExp.loadFromXML(filePath)
101
+ categName = filePath.stem
102
+ self[categName]={}
103
+ for routineName in thisExp.routines:
104
+ self[categName][routineName] = copy.copy(thisExp.routines[routineName])
105
+
106
+
107
+ class BuilderFrame(BaseAuiFrame, handlers.ThemeMixin):
108
+ """Defines construction of the Psychopy Builder Frame"""
109
+
110
+ routineTemplates = TemplateManager()
111
+
112
+ def __init__(self, parent, id=-1, title='PsychoPy (Experiment Builder)',
113
+ pos=wx.DefaultPosition, fileName=None, frameData=None,
114
+ style=wx.DEFAULT_FRAME_STYLE, app=None):
115
+
116
+ if (fileName is not None) and (type(fileName) == bytes):
117
+ fileName = fileName.decode(sys.getfilesystemencoding())
118
+
119
+ self.app = app
120
+ self.dpi = self.app.dpi
121
+ # things the user doesn't set like winsize etc:
122
+ self.appData = self.app.prefs.appData['builder']
123
+ # things about the builder that the user can set:
124
+ self.prefs = self.app.prefs.builder
125
+ self.appPrefs = self.app.prefs.app
126
+ self.paths = self.app.prefs.paths
127
+ self.frameType = 'builder'
128
+ self.fileExists = False
129
+ self.filename = fileName
130
+ self.htmlPath = None
131
+ self.scriptProcess = None
132
+ self.stdoutBuffer = None
133
+ self.readmeFrame = None
134
+ self.generateScript = generateScript
135
+
136
+ # default window title
137
+ self.winTitle = 'PsychoPy Builder (v{})'.format(self.app.version)
138
+
139
+ if fileName in self.appData['frames']:
140
+ self.frameData = self.appData['frames'][fileName]
141
+ else: # work out a new frame size/location
142
+ dispW, dispH = self.app.getPrimaryDisplaySize()
143
+ default = self.appData['defaultFrame']
144
+ default['winW'] = int(dispW * 0.75)
145
+ default['winH'] = int(dispH * 0.75)
146
+ if default['winX'] + default['winW'] > dispW:
147
+ default['winX'] = 5
148
+ if default['winY'] + default['winH'] > dispH:
149
+ default['winY'] = 5
150
+ self.frameData = dict(self.appData['defaultFrame']) # copy
151
+ # increment default for next frame
152
+ default['winX'] += 10
153
+ default['winY'] += 10
154
+
155
+ # we didn't have the key or the win was minimized / invalid
156
+ if self.frameData['winH'] == 0 or self.frameData['winW'] == 0:
157
+ self.frameData['winX'], self.frameData['winY'] = (0, 0)
158
+ if self.frameData['winY'] < 20:
159
+ self.frameData['winY'] = 20
160
+
161
+ BaseAuiFrame.__init__(self, parent=parent, id=id, title=title,
162
+ pos=(int(self.frameData['winX']),
163
+ int(self.frameData['winY'])),
164
+ size=(int(self.frameData['winW']),
165
+ int(self.frameData['winH'])),
166
+ style=style)
167
+
168
+ # detect retina displays (then don't use double-buffering)
169
+ self.isRetina = \
170
+ self.GetContentScaleFactor() != 1 and wx.Platform == '__WXMAC__'
171
+
172
+ # create icon
173
+ if sys.platform != 'darwin':
174
+ # doesn't work on darwin and not necessary: handled by app bundle
175
+ iconFile = os.path.join(self.paths['resources'], 'builder.ico')
176
+ if os.path.isfile(iconFile):
177
+ self.SetIcon(wx.Icon(iconFile, wx.BITMAP_TYPE_ICO))
178
+
179
+ # create our panels
180
+ self.flowPanel = FlowPanel(frame=self)
181
+ self.flowCanvas = self.flowPanel.canvas
182
+ self.routinePanel = RoutinesNotebook(self)
183
+ self.componentButtons = ComponentsPanel(self)
184
+ self.ribbon = BuilderRibbon(self)
185
+ # menus and toolbars
186
+ self.menuIDs = SimpleNamespace()
187
+ self.makeMenus()
188
+ self.CreateStatusBar()
189
+ self.SetStatusText("")
190
+
191
+ # setup universal shortcuts
192
+ accelTable = self.app.makeAccelTable()
193
+ self.SetAcceleratorTable(accelTable)
194
+
195
+ # setup a default exp
196
+ if self.filename.is_file():
197
+ self.fileOpen(filename=fileName, closeCurrent=False)
198
+ else:
199
+ self.lastSavedCopy = None
200
+ # don't try to close before opening
201
+ self.fileNew(closeCurrent=False)
202
+
203
+ self.updateReadme() # check/create frame as needed
204
+
205
+ # control the panes using aui manager
206
+ self._mgr = self.getAuiManager()
207
+
208
+ #self._mgr.SetArtProvider(PsychopyDockArt())
209
+ #self._art = self._mgr.GetArtProvider()
210
+ # Create panels
211
+ self._mgr.AddPane(self.ribbon,
212
+ aui.AuiPaneInfo().
213
+ Name("Ribbon").
214
+ DockFixed(True).
215
+ CloseButton(False).MaximizeButton(True).PaneBorder(False).CaptionVisible(False).
216
+ Top()
217
+ )
218
+ self._mgr.AddPane(self.routinePanel,
219
+ aui.AuiPaneInfo().
220
+ Name("Routines").Caption("Routines").CaptionVisible(True).
221
+ Floatable(False).
222
+ Movable(False).
223
+ CloseButton(False).MaximizeButton(True).PaneBorder(False).
224
+ Center()) # 'center panes' expand
225
+ rtPane = self._mgr.GetPane('Routines')
226
+ self._mgr.AddPane(self.componentButtons,
227
+ aui.AuiPaneInfo().
228
+ Name("Components").Caption("Components").CaptionVisible(True).
229
+ Floatable(False).
230
+ RightDockable(True).LeftDockable(True).
231
+ CloseButton(False).PaneBorder(False))
232
+ compPane = self._mgr.GetPane('Components')
233
+ self._mgr.AddPane(self.flowPanel,
234
+ aui.AuiPaneInfo().
235
+ Name("Flow").Caption("Flow").CaptionVisible(True).
236
+ BestSize((8 * self.dpi, 2 * self.dpi)).
237
+ Floatable(False).
238
+ RightDockable(True).LeftDockable(True).
239
+ CloseButton(False).PaneBorder(False))
240
+ flowPane = self._mgr.GetPane('Flow')
241
+ self.layoutPanes()
242
+ rtPane.CaptionVisible(True)
243
+ # tell the manager to 'commit' all the changes just made
244
+ self._mgr.Update()
245
+ # self.SetSizer(self.mainSizer) # not necessary for aui type controls
246
+ if self.frameData['auiPerspective']:
247
+ self._mgr.LoadPerspective(self.frameData['auiPerspective'])
248
+ self.SetMinSize(wx.Size(600, 400)) # min size for the whole window
249
+ self.SetSize(
250
+ (int(self.frameData['winW']), int(self.frameData['winH'])))
251
+ self.SendSizeEvent()
252
+ self._mgr.GetPane("Ribbon").Show()
253
+ self._mgr.Update()
254
+
255
+ # self.SetAutoLayout(True)
256
+ self.Bind(wx.EVT_CLOSE, self.closeFrame)
257
+ self.Bind(wx.EVT_SIZE, self.onResize)
258
+ self.Bind(wx.EVT_SHOW, self.onShow)
259
+
260
+ self.app.trackFrame(self)
261
+ self.SetDropTarget(FileDropTarget(targetFrame=self))
262
+
263
+ self.theme = colors.theme
264
+
265
+ @property
266
+ def session(self):
267
+ """
268
+ Current Pavlovia session
269
+ """
270
+ return pavlovia.getCurrentSession()
271
+
272
+ # Synonymise Aui manager for use with theme mixin
273
+ def GetAuiManager(self):
274
+ return self._mgr
275
+
276
+ def makeMenus(self):
277
+ """
278
+ Produces Menus for the Builder Frame
279
+ """
280
+
281
+ # ---Menus---#000000#FFFFFF-------------------------------------------
282
+ menuBar = wx.MenuBar()
283
+ # ---_file---#000000#FFFFFF-------------------------------------------
284
+ self.fileMenu = wx.Menu()
285
+ menuBar.Append(self.fileMenu, _translate('&File'))
286
+
287
+ # create a file history submenu
288
+ self.fileHistoryMaxFiles = 10
289
+ self.fileHistory = wx.FileHistory(maxFiles=self.fileHistoryMaxFiles)
290
+ self.recentFilesMenu = wx.Menu()
291
+ self.fileHistory.UseMenu(self.recentFilesMenu)
292
+ for filename in self.appData['fileHistory']:
293
+ if os.path.exists(filename):
294
+ self.fileHistory.AddFileToHistory(filename)
295
+ self.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory,
296
+ id=wx.ID_FILE1, id2=wx.ID_FILE9)
297
+ keys = self.app.keys
298
+ menu = self.fileMenu
299
+ menu.Append(
300
+ wx.ID_NEW,
301
+ _translate("&New\t%s") % keys['new'])
302
+ menu.Append(
303
+ wx.ID_OPEN,
304
+ _translate("&Open...\t%s") % keys['open'])
305
+ menu.AppendSubMenu(
306
+ self.recentFilesMenu,
307
+ _translate("Open &Recent"))
308
+ menu.Append(
309
+ wx.ID_SAVE,
310
+ _translate("&Save\t%s") % keys['save'],
311
+ _translate("Save current experiment file"))
312
+ menu.Append(
313
+ wx.ID_SAVEAS,
314
+ _translate("Save &as...\t%s") % keys['saveAs'],
315
+ _translate("Save current experiment file as..."))
316
+ # export html
317
+ self.menuIDs.ID_EXPORT_HTML = wx.NewId()
318
+ menu.Append(
319
+ self.menuIDs.ID_EXPORT_HTML,
320
+ _translate("Export HTML...\t%s") % keys['exportHTML'],
321
+ _translate("Export experiment to html/javascript file")
322
+ )
323
+ self.Bind(wx.EVT_MENU, self.fileExport, id=self.menuIDs.ID_EXPORT_HTML)
324
+ # reveal folder
325
+ self.menuIDs.ID_REVEAL = wx.NewId()
326
+ menu.Append(
327
+ self.menuIDs.ID_REVEAL,
328
+ _translate("Reveal in file explorer..."),
329
+ _translate("Open the folder containing this experiment in your system's file explorer")
330
+ )
331
+ self.Bind(wx.EVT_MENU, self.fileReveal, id=self.menuIDs.ID_REVEAL)
332
+ menu.Append(
333
+ wx.ID_CLOSE,
334
+ _translate("&Close file\t%s") % keys['close'],
335
+ _translate("Close current experiment"))
336
+ self.Bind(wx.EVT_MENU, self.app.newBuilderFrame, id=wx.ID_NEW)
337
+ self.Bind(wx.EVT_MENU, self.fileSave, id=wx.ID_SAVE)
338
+ menu.Enable(wx.ID_SAVE, False)
339
+ self.Bind(wx.EVT_MENU, self.fileSaveAs, id=wx.ID_SAVEAS)
340
+ self.Bind(wx.EVT_MENU, self.fileOpen, id=wx.ID_OPEN)
341
+ self.Bind(wx.EVT_MENU, self.commandCloseFrame, id=wx.ID_CLOSE)
342
+ self.fileMenu.AppendSeparator()
343
+ item = menu.Append(
344
+ wx.ID_PREFERENCES,
345
+ _translate("&Preferences\t%s") % keys['preferences'])
346
+ self.Bind(wx.EVT_MENU, self.app.showPrefs, item)
347
+ item = menu.Append(
348
+ wx.ID_ANY, _translate("Reset preferences...")
349
+ )
350
+ self.Bind(wx.EVT_MENU, self.resetPrefs, item)
351
+ # item = menu.Append(wx.NewId(), "Plug&ins")
352
+ # self.Bind(wx.EVT_MENU, self.pluginManager, item)
353
+ self.fileMenu.AppendSeparator()
354
+ self.fileMenu.Append(wx.ID_EXIT,
355
+ _translate("&Quit\t%s") % keys['quit'],
356
+ _translate("Terminate the program"))
357
+ self.Bind(wx.EVT_MENU, self.quit, id=wx.ID_EXIT)
358
+
359
+ # ------------- edit ------------------------------------
360
+ self.editMenu = wx.Menu()
361
+ menuBar.Append(self.editMenu, _translate('&Edit'))
362
+ menu = self.editMenu
363
+ self._undoLabel = menu.Append(wx.ID_UNDO,
364
+ _translate("Undo\t%s") % keys['undo'],
365
+ _translate("Undo last action"),
366
+ wx.ITEM_NORMAL)
367
+ self.Bind(wx.EVT_MENU, self.undo, id=wx.ID_UNDO)
368
+ self._redoLabel = menu.Append(wx.ID_REDO,
369
+ _translate("Redo\t%s") % keys['redo'],
370
+ _translate("Redo last action"),
371
+ wx.ITEM_NORMAL)
372
+ self.Bind(wx.EVT_MENU, self.redo, id=wx.ID_REDO)
373
+ menu.Append(wx.ID_PASTE, _translate("&Paste\t%s") % keys['paste'])
374
+ self.Bind(wx.EVT_MENU, self.paste, id=wx.ID_PASTE)
375
+
376
+ # ---_view---#000000#FFFFFF-------------------------------------------
377
+ self.viewMenu = wx.Menu()
378
+ menuBar.Append(self.viewMenu, _translate('&View'))
379
+ menu = self.viewMenu
380
+
381
+ # item = menu.Append(wx.ID_ANY,
382
+ # _translate("Open Coder view"),
383
+ # _translate("Open a new Coder view"))
384
+ # self.Bind(wx.EVT_MENU, self.app.showCoder, item)
385
+ #
386
+ # item = menu.Append(wx.ID_ANY,
387
+ # _translate("Open Runner view"),
388
+ # _translate("Open the Runner view"))
389
+ # self.Bind(wx.EVT_MENU, self.app.showRunner, item)
390
+ # menu.AppendSeparator()
391
+
392
+ item = menu.Append(wx.ID_ANY,
393
+ _translate("&Toggle readme\t%s") % self.app.keys[
394
+ 'toggleReadme'],
395
+ _translate("Toggle Readme"))
396
+ self.Bind(wx.EVT_MENU, self.toggleReadme, item)
397
+ item = menu.Append(wx.ID_ANY,
398
+ _translate("&Flow Larger\t%s") % self.app.keys[
399
+ 'largerFlow'],
400
+ _translate("Larger flow items"))
401
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.increaseSize, item)
402
+ item = menu.Append(wx.ID_ANY,
403
+ _translate("&Flow Smaller\t%s") % self.app.keys[
404
+ 'smallerFlow'],
405
+ _translate("Smaller flow items"))
406
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.decreaseSize, item)
407
+ item = menu.Append(wx.ID_ANY,
408
+ _translate("&Routine Larger\t%s") % keys[
409
+ 'largerRoutine'],
410
+ _translate("Larger routine items"))
411
+ self.Bind(wx.EVT_MENU, self.routinePanel.increaseSize, item)
412
+ item = menu.Append(wx.ID_ANY,
413
+ _translate("&Routine Smaller\t%s") % keys[
414
+ 'smallerRoutine'],
415
+ _translate("Smaller routine items"))
416
+ self.Bind(wx.EVT_MENU, self.routinePanel.decreaseSize, item)
417
+ menu.AppendSeparator()
418
+
419
+ # Frame switcher
420
+ FrameSwitcher.makeViewSwitcherButtons(menu, frame=self, app=self.app)
421
+
422
+ # Theme switcher
423
+ self.themesMenu = ThemeSwitcher(app=self.app)
424
+ menu.AppendSubMenu(self.themesMenu, _translate("&Themes"))
425
+
426
+ # ---_tools ---#000000#FFFFFF-----------------------------------------
427
+ self.toolsMenu = wx.Menu()
428
+ menuBar.Append(self.toolsMenu, _translate('&Tools'))
429
+ menu = self.toolsMenu
430
+ item = menu.Append(wx.ID_ANY,
431
+ _translate("Monitor Center"),
432
+ _translate("To set information about your monitor"))
433
+ self.Bind(wx.EVT_MENU, self.app.openMonitorCenter, item)
434
+
435
+ item = menu.Append(wx.ID_ANY,
436
+ _translate("Compile\t%s") % keys['compileScript'],
437
+ _translate("Compile the exp to a script"))
438
+ self.Bind(wx.EVT_MENU, self.compileScript, item)
439
+ self.bldrRun = menu.Append(wx.ID_ANY,
440
+ _translate("Run/pilot\t%s") % keys['runScript'],
441
+ _translate("Run the current script"))
442
+ self.Bind(wx.EVT_MENU, self.onRunShortcut, self.bldrRun, id=self.bldrRun)
443
+ item = menu.Append(wx.ID_ANY,
444
+ _translate("Send to runner\t%s") % keys['runnerScript'],
445
+ _translate("Send current script to runner"))
446
+ self.Bind(wx.EVT_MENU, self.runFile, item)
447
+ menu.AppendSeparator()
448
+ item = menu.Append(wx.ID_ANY,
449
+ _translate("PsychoPy updates..."),
450
+ _translate("Update PsychoPy to the latest, or a "
451
+ "specific, version"))
452
+ self.Bind(wx.EVT_MENU, self.app.openUpdater, item)
453
+ item = menu.Append(wx.ID_ANY,
454
+ _translate("Plugin/packages manager..."),
455
+ _translate("Manage Python packages and optional plugins for PsychoPy"))
456
+ self.Bind(wx.EVT_MENU, self.openPluginManager, item)
457
+ if hasattr(self.app, 'benchmarkWizard'):
458
+ item = menu.Append(wx.ID_ANY,
459
+ _translate("Benchmark wizard"),
460
+ _translate("Check software & hardware, generate "
461
+ "report"))
462
+ self.Bind(wx.EVT_MENU, self.app.benchmarkWizard, item)
463
+
464
+ # ---_experiment---#000000#FFFFFF-------------------------------------
465
+ self.expMenu = wx.Menu()
466
+ menuBar.Append(self.expMenu, _translate('E&xperiment'))
467
+ menu = self.expMenu
468
+ item = menu.Append(wx.ID_ANY,
469
+ _translate("&New Routine\t%s") % keys['newRoutine'],
470
+ _translate("Create a new routine (e.g. the trial "
471
+ "definition)"))
472
+ self.Bind(wx.EVT_MENU, self.addRoutine, item)
473
+ item = menu.Append(wx.ID_ANY,
474
+ _translate("&Copy Routine\t%s") % keys[
475
+ 'copyRoutine'],
476
+ _translate("Copy the current routine so it can be "
477
+ "used in another exp"),
478
+ wx.ITEM_NORMAL)
479
+ self.Bind(wx.EVT_MENU, self.onCopyRoutine, item)
480
+ item = menu.Append(wx.ID_ANY,
481
+ _translate("&Paste Routine\t%s") % keys[
482
+ 'pasteRoutine'],
483
+ _translate("Paste the Routine into the current "
484
+ "experiment"),
485
+ wx.ITEM_NORMAL)
486
+ self.Bind(wx.EVT_MENU, self.onPasteRoutine, item)
487
+ item = menu.Append(wx.ID_ANY,
488
+ _translate("&Rename Routine\t%s") % keys[
489
+ 'renameRoutine'],
490
+ _translate("Change the name of this routine"))
491
+ self.Bind(wx.EVT_MENU, self.renameRoutine, item)
492
+ item = menu.Append(wx.ID_ANY,
493
+ _translate("Paste Component\t%s") % keys[
494
+ 'pasteCompon'],
495
+ _translate(
496
+ "Paste the Component at bottom of the current "
497
+ "Routine"),
498
+ wx.ITEM_NORMAL)
499
+ self.Bind(wx.EVT_MENU, self.onPasteCompon, item)
500
+ menu.AppendSeparator()
501
+
502
+ item = menu.Append(wx.ID_ANY,
503
+ _translate("Insert Routine in Flow"),
504
+ _translate(
505
+ "Select one of your routines to be inserted"
506
+ " into the experiment flow"))
507
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.onInsertRoutine, item)
508
+ item = menu.Append(wx.ID_ANY,
509
+ _translate("Insert Loop in Flow"),
510
+ _translate("Create a new loop in your flow window"))
511
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.insertLoop, item)
512
+ menu.AppendSeparator()
513
+
514
+ item = menu.Append(wx.ID_ANY,
515
+ _translate("&Find in experiment...\t%s") % keys['builderFind'],
516
+ _translate("Search the whole experiment for a specific term"))
517
+ self.Bind(wx.EVT_MENU, self.onFindInExperiment, item)
518
+
519
+ item = menu.Append(wx.ID_ANY,
520
+ _translate("README..."),
521
+ _translate("Add or edit the text shown when your experiment is opened"))
522
+ self.Bind(wx.EVT_MENU, self.editREADME, item)
523
+
524
+ # ---_demos---#000000#FFFFFF------------------------------------------
525
+ # for demos we need a dict where the event ID will correspond to a
526
+ # filename
527
+
528
+ self.demosMenu = wx.Menu()
529
+ # unpack demos option
530
+ menu = self.demosMenu
531
+ item = menu.Append(wx.ID_ANY,
532
+ _translate("&Unpack Demos..."),
533
+ _translate(
534
+ "Unpack demos to a writable location (so that"
535
+ " they can be run)"))
536
+ self.Bind(wx.EVT_MENU, self.demosUnpack, item)
537
+ item = menu.Append(wx.ID_ANY,
538
+ _translate("Browse on Pavlovia"),
539
+ _translate("Get more demos from the online demos "
540
+ "repository on Pavlovia")
541
+ )
542
+ self.Bind(wx.EVT_MENU, self.openPavloviaDemos, item)
543
+ item = menu.Append(wx.ID_ANY,
544
+ _translate("Open demos folder"),
545
+ _translate("Open the local folder where demos are stored")
546
+ )
547
+ self.Bind(wx.EVT_MENU, self.openLocalDemos, item)
548
+ menu.AppendSeparator()
549
+ # add any demos that are found in the prefs['demosUnpacked'] folder
550
+ updateDemosMenu(self, self.demosMenu, self.prefs['unpackedDemosDir'], ext=".psyexp")
551
+ menuBar.Append(self.demosMenu, _translate('&Demos'))
552
+
553
+ # ---_onlineStudies---#000000#FFFFFF-------------------------------------------
554
+ self.pavloviaMenu = pavlovia_ui.menu.PavloviaMenu(parent=self)
555
+ menuBar.Append(self.pavloviaMenu, _translate("&Pavlovia.org"))
556
+
557
+ # ---_window---#000000#FFFFFF-----------------------------------------
558
+ self.windowMenu = FrameSwitcher(self)
559
+ menuBar.Append(self.windowMenu, _translate("&Window"))
560
+
561
+ # ---_help---#000000#FFFFFF-------------------------------------------
562
+ self.helpMenu = wx.Menu()
563
+ menuBar.Append(self.helpMenu, _translate('&Help'))
564
+ menu = self.helpMenu
565
+
566
+ item = menu.Append(wx.ID_ANY,
567
+ _translate("&PsychoPy Homepage"),
568
+ _translate("Go to the PsychoPy homepage"))
569
+ self.Bind(wx.EVT_MENU, self.app.followLink, item)
570
+ self.app.urls[item.GetId()] = self.app.urls['psychopyHome']
571
+ item = menu.Append(wx.ID_ANY,
572
+ _translate("&PsychoPy Builder Help"),
573
+ _translate(
574
+ "Go to the online documentation for PsychoPy"
575
+ " Builder"))
576
+ self.Bind(wx.EVT_MENU, self.app.followLink, item)
577
+ self.app.urls[item.GetId()] = self.app.urls['builderHelp']
578
+
579
+ menu.AppendSeparator()
580
+ item = menu.Append(wx.ID_ANY,
581
+ _translate("&System Info..."),
582
+ _translate("Get system information."))
583
+ self.Bind(wx.EVT_MENU, self.app.showSystemInfo, id=item.GetId())
584
+
585
+ menu.AppendSeparator()
586
+ menu.Append(wx.ID_ABOUT, _translate(
587
+ "&About..."), _translate("About PsychoPy"))
588
+ self.Bind(wx.EVT_MENU, self.app.showAbout, id=wx.ID_ABOUT)
589
+ item = menu.Append(wx.ID_ANY,
590
+ _translate("&News..."),
591
+ _translate("News"))
592
+ self.Bind(wx.EVT_MENU, self.app.showNews, id=item.GetId())
593
+
594
+ self.SetMenuBar(menuBar)
595
+
596
+ def commandCloseFrame(self, event):
597
+ """Defines Builder Frame Closing Event"""
598
+ self.Close()
599
+
600
+ def closeFrame(self, event=None, checkSave=True):
601
+ """Defines Frame closing behavior, such as checking for file
602
+ saving"""
603
+ # close file first (check for save) but no need to update view
604
+ okToClose = self.fileClose(updateViews=False, checkSave=checkSave)
605
+
606
+ if not okToClose:
607
+ if hasattr(event, 'Veto'):
608
+ event.Veto()
609
+ return
610
+ else:
611
+ # as of wx3.0 the AUI manager needs to be uninitialised explicitly
612
+ self._mgr.UnInit()
613
+ # is it the last frame?
614
+ lastFrame = len(self.app.getAllFrames()) == 1
615
+ quitting = self.app.quitting
616
+ if lastFrame and sys.platform != 'darwin' and not quitting:
617
+ self.app.quit(event)
618
+ else:
619
+ self.app.forgetFrame(self)
620
+ self.Destroy() # required
621
+
622
+ # Show Runner if hidden
623
+ if self.app.runner is not None:
624
+ self.app.showRunner()
625
+ self.app.updateWindowMenu()
626
+
627
+ def quit(self, event=None):
628
+ """quit the app
629
+ """
630
+ self.app.quit(event)
631
+
632
+ def onResize(self, event):
633
+ """Called when the frame is resized."""
634
+ self.componentButtons.Refresh()
635
+ self.flowPanel.canvas.Refresh()
636
+ event.Skip()
637
+
638
+ def onShow(self, event):
639
+ """Called when the frame is shown"""
640
+ event.Skip()
641
+ # if README was updated when frame wasn't shown, it won't be show either - so update again
642
+ self.updateReadme()
643
+
644
+ @property
645
+ def filename(self):
646
+ """Name of the currently open file"""
647
+ return self._filename
648
+
649
+ @filename.setter
650
+ def filename(self, value):
651
+ if value is None:
652
+ # mark nonexistant
653
+ self.fileExists = False
654
+ # keep placeholder name for labels and etc.
655
+ self._filename = Path("untitled.psyexp")
656
+ else:
657
+ # path-ise and set
658
+ self._filename = Path(value)
659
+ # mark existant
660
+ self.fileExists = Path(value).is_file()
661
+ # enable/disable reveal button
662
+ if hasattr(self, "menuIDs"):
663
+ self.fileMenu.Enable(self.menuIDs.ID_REVEAL, self.fileExists)
664
+ # skip if there's no ribbon
665
+ if not hasattr(self, "ribbon"):
666
+ return
667
+ # enable/disable compile buttons
668
+ for key in ('compile_py', 'compile_js'):
669
+ if key in self.ribbon.buttons:
670
+ self.ribbon.buttons[key].Enable(
671
+ self._filename.is_file()
672
+ )
673
+
674
+ def fileNew(self, event=None, closeCurrent=True):
675
+ """Create a default experiment (maybe an empty one instead)
676
+ """
677
+ # Note: this is NOT the method called by the File>New menu item.
678
+ # That calls app.newBuilderFrame() instead
679
+ if closeCurrent: # if no exp exists then don't try to close it
680
+ if not self.fileClose(updateViews=False):
681
+ # close the existing (and prompt for save if necess)
682
+ return False
683
+ self.filename = None
684
+ self.exp = experiment.Experiment(prefs=self.app.prefs)
685
+ defaultName = 'trial'
686
+ # create the trial routine as an example
687
+ self.exp.addRoutine(defaultName)
688
+ self.exp.flow.addRoutine(
689
+ self.exp.routines[defaultName], pos=1) # add it to flow
690
+ # add it to user's namespace
691
+ self.exp.namespace.add(defaultName, self.exp.namespace.user)
692
+ routine = self.exp.routines[defaultName]
693
+ ## add an ISI component by default
694
+ # components = self.componentButtons.components
695
+ # Static = components['StaticComponent']
696
+ # ISI = Static(self.exp, parentName=defaultName, name='ISI',
697
+ # startType='time (s)', startVal=0.0,
698
+ # stopType='duration (s)', stopVal=0.5)
699
+ # routine.addComponent(ISI)
700
+ # set run mode silently and update icons
701
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode, silent=True)
702
+ self.updateRunModeIcons()
703
+ # update undo stack
704
+ self.resetUndoStack()
705
+ self.setIsModified(False)
706
+ self.updateAllViews()
707
+ self.app.updateWindowMenu()
708
+
709
+ def fileOpen(self, event=None, filename=None, closeCurrent=True):
710
+ """Open a FileDialog, then load the file if possible.
711
+ """
712
+ if filename is None:
713
+ # Set wildcard
714
+ if sys.platform != 'darwin':
715
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*.*")
716
+ else:
717
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*")
718
+ # get path of current file (or home dir to avoid temp)
719
+ initPath = str(self.filename.parent)
720
+ if self.fileExists:
721
+ dlg = wx.FileDialog(self, message=_translate("Open file ..."),
722
+ defaultDir=initPath,
723
+ style=wx.FD_OPEN,
724
+ wildcard=wildcard)
725
+ else:
726
+ dlg = wx.FileDialog(self, message=_translate("Open file ..."),
727
+ style=wx.FD_OPEN,
728
+ wildcard=wildcard)
729
+ if dlg.ShowModal() != wx.ID_OK:
730
+ return 0
731
+ filename = dlg.GetPath()
732
+
733
+ filename = str(filename)
734
+ # did user try to open a script in Builder?
735
+ if filename.endswith('.py'):
736
+ self.app.showCoder() # ensures that a coder window exists
737
+ self.app.coder.setCurrentDoc(filename)
738
+ self.app.coder.setFileModified(False)
739
+ return
740
+
741
+ with WindowFrozen(ctrl=self):
742
+ # try to pause rendering until all panels updated
743
+ if closeCurrent:
744
+ if not self.fileClose(updateViews=False):
745
+ # close the existing (and prompt for save if necess)
746
+ return False
747
+ self.exp = experiment.Experiment(prefs=self.app.prefs)
748
+ try:
749
+ self.exp.loadFromXML(filename)
750
+ # set run mode silently and update buttons accordingly
751
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode, silent=True)
752
+ self.updateRunModeIcons()
753
+ except Exception:
754
+ print(u"Failed to load {}. Please send the following to"
755
+ u" the PsychoPy user list".format(filename))
756
+ traceback.print_exc()
757
+ logging.flush()
758
+ self.resetUndoStack()
759
+ self.setIsModified(False)
760
+ self.filename = filename
761
+ # routinePanel.addRoutinePage() is done in
762
+ # routinePanel.redrawRoutines(), called by self.updateAllViews()
763
+ # update the views
764
+ self.updateAllViews() # if frozen effect will be visible on thaw
765
+
766
+ # Show README
767
+ if self.prefs['alwaysShowReadme']:
768
+ # If prefs are to always show README, show if populated
769
+ self.updateReadme()
770
+ else:
771
+ # Otherwise update so we have the object, but don't show until asked
772
+ self.updateReadme(show=False)
773
+
774
+ self.fileHistory.AddFileToHistory(filename)
775
+ self.htmlPath = None # so we won't accidentally save to other html exp
776
+
777
+ if self.app.runner:
778
+ self.app.runner.addTask(fileName=self.filename) # Add to Runner
779
+
780
+ self.project = pavlovia.getProject(filename)
781
+ self.app.updateWindowMenu()
782
+
783
+ def fileReveal(self, evt=None):
784
+ """
785
+ Reveal the current file in the system file explorer.
786
+ """
787
+ # get current dir
788
+ if self.fileExists:
789
+ folder = Path(self.filename).parent
790
+ else:
791
+ folder = Path().home()
792
+ # choose a command according to OS
793
+ if sys.platform in ['win32']:
794
+ comm = "explorer"
795
+ elif sys.platform in ['darwin']:
796
+ comm = "open"
797
+ elif sys.platform in ['linux', 'linux2']:
798
+ comm = "dolphin"
799
+ # use command to open folder
800
+ subprocess.call(f"{comm} {folder}", shell=True)
801
+
802
+ def fileSave(self, event=None, filename=None):
803
+ """Save file, revert to SaveAs if the file hasn't yet been saved
804
+ """
805
+ if filename is None:
806
+ filename = self.filename
807
+ else:
808
+ filename = Path(filename)
809
+
810
+ if not self.fileExists:
811
+ if not self.fileSaveAs(filename):
812
+ return False # the user cancelled during saveAs
813
+ else:
814
+ filename = self.exp.saveToXML(filename)
815
+ self.fileHistory.AddFileToHistory(filename)
816
+ self.setIsModified(False)
817
+ # if export on save then we should have an html file to update
818
+ if self._getExportPref('on save') and os.path.split(filename)[0]:
819
+ self.filename = filename
820
+ self.fileExport(htmlPath=self.htmlPath)
821
+ return True
822
+
823
+ def fileSaveAs(self, event=None, filename=None):
824
+ """Defines Save File as Behavior
825
+ """
826
+ shortFilename = self.getShortFilename()
827
+ expName = self.exp.getExpName()
828
+ if (not expName) or (shortFilename == expName):
829
+ usingDefaultName = True
830
+ else:
831
+ usingDefaultName = False
832
+ # force filename to Path
833
+ if filename is None:
834
+ filename = self.filename
835
+ else:
836
+ filename = Path(filename)
837
+ # get parent and filename
838
+ initPath = filename.parent
839
+ filename = filename.name
840
+ # substitute temp dir for home
841
+ if not self.fileExists:
842
+ initPath = Path.home()
843
+
844
+ if sys.platform != 'darwin':
845
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*.*")
846
+ else:
847
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*")
848
+ returnVal = False
849
+ dlg = wx.FileDialog(
850
+ self, message=_translate("Save file as ..."), defaultDir=str(initPath),
851
+ defaultFile=filename, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
852
+ wildcard=wildcard)
853
+
854
+ if dlg.ShowModal() == wx.ID_OK:
855
+ newPath = dlg.GetPath()
856
+ # update exp name
857
+ # if user has not manually renamed experiment
858
+ if usingDefaultName:
859
+ newShortName = os.path.splitext(
860
+ os.path.split(newPath)[1])[0]
861
+ self.exp.setExpName(newShortName)
862
+ # actually save
863
+ self.filename = newPath
864
+ self.fileExists = True
865
+ self.fileSave(event=None, filename=newPath)
866
+ self.project = pavlovia.getProject(filename)
867
+ returnVal = 1
868
+ dlg.Destroy()
869
+
870
+ self.updateWindowTitle()
871
+ # update README in case the file path has changed
872
+ if self.prefs['alwaysShowReadme']:
873
+ # if prefs are to always show README, show if populated
874
+ self.updateReadme()
875
+ else:
876
+ # otherwise update so we have the object, but don't show until asked
877
+ self.updateReadme(show=False)
878
+ return returnVal
879
+
880
+ def fileExport(self, event=None, htmlPath=None):
881
+ """Exports the script as an HTML file (PsychoJS library)
882
+ """
883
+ # get path if not given one
884
+ if htmlPath is None:
885
+ htmlPath = self._getHtmlPath(self.filename)
886
+ if not htmlPath:
887
+ return
888
+
889
+ exportPath = os.path.join(htmlPath, self.exp.name + '.js')
890
+ exportPath = self.generateScript(
891
+ outfile=exportPath,
892
+ exp=self.exp,
893
+ target="PsychoJS"
894
+ )
895
+ # Open exported files
896
+ self.app.showCoder(fileList=[exportPath])
897
+ self.app.coder.fileReload(event=None, filename=exportPath)
898
+
899
+ def editREADME(self, event):
900
+ if self.filename is None:
901
+ dlg = wx.MessageDialog(
902
+ self,
903
+ _translate("Please save experiment before editing the README file"),
904
+ _translate("No readme file"),
905
+ wx.OK | wx.ICON_WARNING | wx.CENTRE)
906
+ dlg.ShowModal()
907
+ else:
908
+ self.updateReadme(show=True)
909
+
910
+ def getShortFilename(self, withExt=False):
911
+ """
912
+ Returns the filename without path
913
+
914
+ Parameters
915
+ ----------
916
+ withExt : bool
917
+ Should the returned filename include the file extension? False by default.
918
+ """
919
+ # get file stem
920
+ shortName = self.filename.stem
921
+ ext = self.filename.suffix
922
+ # append extension if requested
923
+ if withExt:
924
+ shortName += ext
925
+
926
+ return shortName
927
+
928
+ # def pluginManager(self, evt=None, value=True):
929
+ # """Show the plugin manager frame."""
930
+ # PluginManagerFrame(self).ShowModal()
931
+
932
+ def onFindInExperiment(self, evt=None):
933
+ dlg = BuilderFindDlg(frame=self, exp=self.exp)
934
+ dlg.Show()
935
+
936
+ def updateReadme(self, show=None):
937
+ """Check whether there is a readme file in this folder and try to show
938
+
939
+ Parameters
940
+ ==========
941
+ show : bool or None
942
+ If True, always show Readme frame.
943
+ If False, never show Readme frame.
944
+ If None, show only when there is content.
945
+ """
946
+ # Make sure we have a file
947
+ dirname = self.filename.parent
948
+ possibles = list(dirname.glob('readme*'))
949
+ if len(possibles) == 0:
950
+ possibles = list(dirname.glob('Readme*'))
951
+ possibles.extend(dirname.glob('README*'))
952
+
953
+ # still haven't found a file so use default name
954
+ if len(possibles) == 0:
955
+ self.readmeFilename = str(dirname / 'readme.md') # use this as our default
956
+ else:
957
+ self.readmeFilename = str(possibles[0]) # take the first one found
958
+
959
+ # Make sure we have a frame
960
+ if self.readmeFrame is None:
961
+ self.readmeFrame = ReadmeFrame(
962
+ parent=self, filename=self.readmeFilename
963
+ )
964
+
965
+ # Set file
966
+ if self.fileExists:
967
+ self.readmeFrame.setFile(self.readmeFilename)
968
+ else:
969
+ self.readmeFrame.setFile(None)
970
+ show = False
971
+ self.readmeFrame.ctrl.load()
972
+
973
+ # Show/hide frame as appropriate
974
+ if show is None:
975
+ show = len(self.readmeFrame.ctrl.getValue()) > 0
976
+ show = show and self.IsShown()
977
+ self.readmeFrame.show(show)
978
+
979
+ def showReadme(self, evt=None, value=True):
980
+ """Shows Readme file
981
+ """
982
+ if not self.readmeFrame.IsShown():
983
+ self.readmeFrame.show(value)
984
+
985
+ def toggleReadme(self, evt=None):
986
+ """Toggles visibility of Readme file
987
+ """
988
+ if self.readmeFrame is None:
989
+ self.showReadme()
990
+ else:
991
+ self.readmeFrame.toggleVisible()
992
+
993
+ def OnFileHistory(self, evt=None):
994
+ """get the file based on the menu ID
995
+ """
996
+ fileNum = evt.GetId() - wx.ID_FILE1
997
+ path = self.fileHistory.GetHistoryFile(fileNum)
998
+ self.fileOpen(filename=path)
999
+ # add it back to the history so it will be moved up the list
1000
+ self.fileHistory.AddFileToHistory(path)
1001
+
1002
+ def checkSave(self):
1003
+ """Check whether we need to save before quitting
1004
+ """
1005
+ if hasattr(self, 'isModified') and self.isModified and not self.app.testMode:
1006
+ self.Show(True)
1007
+ self.Raise()
1008
+ self.app.SetTopWindow(self)
1009
+ msg = _translate('Experiment %s has changed. Save before '
1010
+ 'quitting?') % self.filename
1011
+ dlg = dialogs.MessageDialog(self, msg, type='Warning')
1012
+ resp = dlg.ShowModal()
1013
+ if resp == wx.ID_CANCEL:
1014
+ return False # return, don't quit
1015
+ elif resp == wx.ID_YES:
1016
+ if not self.fileSave():
1017
+ return False # user might cancel during save
1018
+ elif resp == wx.ID_NO:
1019
+ pass # don't save just quit
1020
+ return True
1021
+
1022
+ def fileClose(self, event=None, checkSave=True, updateViews=True):
1023
+ """This is typically only called when the user x
1024
+ """
1025
+ if checkSave:
1026
+ ok = self.checkSave()
1027
+ if not ok:
1028
+ return False # user cancelled
1029
+ if self.filename is None:
1030
+ frameData = self.appData['defaultFrame']
1031
+ else:
1032
+ frameData = dict(self.appData['defaultFrame'])
1033
+ self.appData['prevFiles'].append(self.filename)
1034
+
1035
+ # get size and window layout info
1036
+ if self.IsIconized():
1037
+ self.Iconize(False) # will return to normal mode to get size info
1038
+ frameData['state'] = 'normal'
1039
+ elif self.IsMaximized():
1040
+ # will briefly return to normal mode to get size info
1041
+ self.Maximize(False)
1042
+ frameData['state'] = 'maxim'
1043
+ else:
1044
+ frameData['state'] = 'normal'
1045
+ frameData['auiPerspective'] = self._mgr.SavePerspective()
1046
+ frameData['winW'], frameData['winH'] = self.GetSize()
1047
+ frameData['winX'], frameData['winY'] = self.GetPosition()
1048
+
1049
+ # truncate history to the recent-most last N unique files, where
1050
+ # N = self.fileHistoryMaxFiles, as defined in makeMenus()
1051
+ for ii in range(self.fileHistory.GetCount()):
1052
+ self.appData['fileHistory'].append(
1053
+ self.fileHistory.GetHistoryFile(ii))
1054
+ # fileClose gets calls multiple times, so remove redundancy
1055
+ # while preserving order; end of the list is recent-most:
1056
+ tmp = []
1057
+ fhMax = self.fileHistoryMaxFiles
1058
+ for f in self.appData['fileHistory'][-3 * fhMax:]:
1059
+ if f not in tmp:
1060
+ tmp.append(f)
1061
+ self.appData['fileHistory'] = copy.copy(tmp[-fhMax:])
1062
+
1063
+ # assign the data to this filename
1064
+ self.appData['frames'][str(self.filename)] = frameData
1065
+ # save the display data only for those frames in the history:
1066
+ tmp2 = {}
1067
+ for f in self.appData['frames']:
1068
+ if f in self.appData['fileHistory']:
1069
+ tmp2[f] = self.appData['frames'][f]
1070
+ self.appData['frames'] = copy.copy(tmp2)
1071
+
1072
+ # close self
1073
+ self.routinePanel.removePages()
1074
+ self.filename = None
1075
+ # add the current exp as the start point for undo:
1076
+ self.resetUndoStack()
1077
+ if updateViews:
1078
+ self.updateAllViews()
1079
+ return 1
1080
+
1081
+ def updateAllViews(self):
1082
+ """Updates Flow Panel, Routine Panel, and Window Title simultaneously
1083
+ """
1084
+ self.flowPanel.canvas.draw()
1085
+ self.routinePanel.redrawRoutines()
1086
+ self.componentButtons.Refresh()
1087
+ self.updateWindowTitle()
1088
+
1089
+ def layoutPanes(self):
1090
+ # Get panes
1091
+ flowPane = self._mgr.GetPane('Flow')
1092
+ compPane = self._mgr.GetPane('Components')
1093
+ rtPane = self._mgr.GetPane('Routines')
1094
+ # Arrange panes according to prefs
1095
+ if 'FlowBottom' in self.prefs['builderLayout']:
1096
+ flowPane.Bottom()
1097
+ elif 'FlowTop' in self.prefs['builderLayout']:
1098
+ flowPane.Top()
1099
+ if 'CompRight' in self.prefs['builderLayout']:
1100
+ compPane.Right()
1101
+ if 'CompLeft' in self.prefs['builderLayout']:
1102
+ compPane.Left()
1103
+ rtPane.Center()
1104
+ # Commit
1105
+ self._mgr.Update()
1106
+
1107
+ def resetPrefs(self, event):
1108
+ """Reset preferences to default"""
1109
+ # Present "are you sure" dialog
1110
+ dlg = wx.MessageDialog(
1111
+ self, _translate(
1112
+ "Are you sure you want to reset your preferences? This cannot "
1113
+ "be undone."),
1114
+ caption="Reset Preferences...",
1115
+ style=wx.ICON_WARNING | wx.CANCEL)
1116
+ dlg.SetOKCancelLabels(
1117
+ _translate("I'm sure"),
1118
+ _translate("Wait, go back!")
1119
+ )
1120
+ if dlg.ShowModal() == wx.ID_OK:
1121
+ # If okay is pressed, remove prefs file (meaning a new one will be
1122
+ # created on next restart)
1123
+ os.remove(prefs.paths['userPrefsFile'])
1124
+ # Show confirmation
1125
+ dlg = wx.MessageDialog(
1126
+ self, _translate(
1127
+ "Done! Your preferences have been reset. Changes will be "
1128
+ "applied when you next open PsychoPy."))
1129
+ dlg.ShowModal()
1130
+ else:
1131
+ pass
1132
+
1133
+ def updateWindowTitle(self, newTitle=None):
1134
+ """Defines behavior to update window Title
1135
+ """
1136
+ if newTitle is None:
1137
+ newTitle = self.getShortFilename(withExt=True)
1138
+ self.setTitle(title=self.winTitle, document=newTitle)
1139
+
1140
+ def setIsModified(self, newVal=None):
1141
+ """Sets current modified status and updates save icon accordingly.
1142
+
1143
+ This method is called by the methods fileSave, undo, redo,
1144
+ addToUndoStack and it is usually preferably to call those
1145
+ than to call this directly.
1146
+
1147
+ Call with ``newVal=None``, to only update the save icon(s)
1148
+ """
1149
+ if newVal is None:
1150
+ newVal = self.getIsModified()
1151
+ else:
1152
+ self.isModified = newVal
1153
+ # get ribbon buttons
1154
+ if 'save' in self.ribbon.buttons:
1155
+ self.ribbon.buttons['save'].Enable(newVal)
1156
+ self.fileMenu.Enable(wx.ID_SAVE, newVal)
1157
+
1158
+ def getIsModified(self):
1159
+ """Checks if changes were made"""
1160
+ return self.isModified
1161
+
1162
+ def resetUndoStack(self):
1163
+ """Reset the undo stack. do *immediately after* creating a new exp.
1164
+
1165
+ Implicitly calls addToUndoStack() using the current exp as the state
1166
+ """
1167
+ self.currentUndoLevel = 1 # 1 is current, 2 is back one setp...
1168
+ self.currentUndoStack = []
1169
+ self.addToUndoStack()
1170
+ self.updateUndoRedo()
1171
+ self.setIsModified(newVal=False) # update save icon if needed
1172
+
1173
+ def addToUndoStack(self, action="", state=None):
1174
+ """Add the given ``action`` to the currentUndoStack, associated
1175
+ with the @state@. ``state`` should be a copy of the exp
1176
+ from *immediately after* the action was taken.
1177
+ If no ``state`` is given the current state of the experiment is used.
1178
+
1179
+ If we are at end of stack already then simply append the action. If
1180
+ not (user has done an undo) then remove orphan actions and append.
1181
+ """
1182
+ if state is None:
1183
+ state = copy.deepcopy(self.exp)
1184
+ # remove actions from after the current level
1185
+ if self.currentUndoLevel > 1:
1186
+ self.currentUndoStack = self.currentUndoStack[
1187
+ :-(self.currentUndoLevel - 1)]
1188
+ self.currentUndoLevel = 1
1189
+ # append this action
1190
+ self.currentUndoStack.append({'action': action, 'state': state})
1191
+ self.setIsModified(newVal=True) # update save icon if needed
1192
+ self.updateUndoRedo()
1193
+
1194
+ def undo(self, event=None):
1195
+ """Step the exp back one level in the @currentUndoStack@ if possible,
1196
+ and update the windows.
1197
+
1198
+ Returns the final undo level (1=current, >1 for further in past)
1199
+ or -1 if redo failed (probably can't undo)
1200
+ """
1201
+ if self.currentUndoLevel >= len(self.currentUndoStack):
1202
+ return -1 # can't undo
1203
+ self.currentUndoLevel += 1
1204
+ state = self.currentUndoStack[-self.currentUndoLevel]['state']
1205
+ self.exp = copy.deepcopy(state)
1206
+ self.updateAllViews()
1207
+ self.setIsModified(newVal=True) # update save icon if needed
1208
+ self.updateUndoRedo()
1209
+
1210
+ return self.currentUndoLevel
1211
+
1212
+ def redo(self, event=None):
1213
+ """Step the exp up one level in the @currentUndoStack@ if possible,
1214
+ and update the windows.
1215
+
1216
+ Returns the final undo level (0=current, >0 for further in past)
1217
+ or -1 if redo failed (probably can't redo)
1218
+ """
1219
+ if self.currentUndoLevel <= 1:
1220
+ return -1 # can't redo, we're already at latest state
1221
+ self.currentUndoLevel -= 1
1222
+ self.exp = copy.deepcopy(
1223
+ self.currentUndoStack[-self.currentUndoLevel]['state'])
1224
+ self.updateUndoRedo()
1225
+ self.updateAllViews()
1226
+ self.setIsModified(newVal=True) # update save icon if needed
1227
+ return self.currentUndoLevel
1228
+
1229
+ def paste(self, event=None):
1230
+ """This receives paste commands for all child dialog boxes as well
1231
+ """
1232
+ foc = self.FindFocus()
1233
+ if hasattr(foc, 'Paste'):
1234
+ foc.Paste()
1235
+
1236
+ def updateUndoRedo(self):
1237
+ """Defines Undo and Redo commands for the window
1238
+ """
1239
+ undoLevel = self.currentUndoLevel
1240
+ # check undo
1241
+ if undoLevel >= len(self.currentUndoStack):
1242
+ # can't undo if we're at top of undo stack
1243
+ label = _translate("Undo\t%s") % self.app.keys['undo']
1244
+ enable = False
1245
+ else:
1246
+ action = self.currentUndoStack[-undoLevel]['action']
1247
+ txt = _translate("Undo %(action)s\t%(key)s")
1248
+ fmt = {'action': action, 'key': self.app.keys['undo']}
1249
+ label = txt % fmt
1250
+ enable = True
1251
+ self._undoLabel.SetItemLabel(label)
1252
+ if 'undo' in self.ribbon.buttons:
1253
+ self.ribbon.buttons['undo'].Enable(enable)
1254
+ self.editMenu.Enable(wx.ID_UNDO, enable)
1255
+
1256
+ # check redo
1257
+ if undoLevel == 1:
1258
+ label = _translate("Redo\t%s") % self.app.keys['redo']
1259
+ enable = False
1260
+ else:
1261
+ action = self.currentUndoStack[-undoLevel + 1]['action']
1262
+ txt = _translate("Redo %(action)s\t%(key)s")
1263
+ fmt = {'action': action, 'key': self.app.keys['redo']}
1264
+ label = txt % fmt
1265
+ enable = True
1266
+ self._redoLabel.SetItemLabel(label)
1267
+ if 'redo' in self.ribbon.buttons:
1268
+ self.ribbon.buttons['redo'].Enable(enable)
1269
+ self.editMenu.Enable(wx.ID_REDO, enable)
1270
+
1271
+ def demosUnpack(self, event=None):
1272
+ """Get a folder location from the user and unpack demos into it."""
1273
+ # choose a dir to unpack in
1274
+ dlg = wx.DirDialog(parent=self, message=_translate(
1275
+ "Location to unpack demos"))
1276
+ if dlg.ShowModal() == wx.ID_OK:
1277
+ unpackFolder = dlg.GetPath()
1278
+ else:
1279
+ return -1 # user cancelled
1280
+ # ensure it's an empty dir:
1281
+ if os.listdir(unpackFolder) != []:
1282
+ unpackFolder = os.path.join(unpackFolder, 'PsychoPy3 Demos')
1283
+ if not os.path.isdir(unpackFolder):
1284
+ os.mkdir(unpackFolder)
1285
+ mergeFolder(os.path.join(self.paths['demos'], 'builder'),
1286
+ unpackFolder)
1287
+ self.prefs['unpackedDemosDir'] = unpackFolder
1288
+ self.app.prefs.saveUserPrefs()
1289
+ updateDemosMenu(self, self.demosMenu, self.prefs['unpackedDemosDir'],
1290
+ ext=".psyexp")
1291
+
1292
+ def demoLoad(self, event=None):
1293
+ """Defines Demo Loading Event."""
1294
+ fileDir = self.demos[event.GetId()]
1295
+ files = glob.glob(os.path.join(fileDir, '*.psyexp'))
1296
+ if len(files) == 0:
1297
+ print("Found no psyexp files in %s" % fileDir)
1298
+ else:
1299
+ self.fileOpen(event=None, filename=files[0], closeCurrent=True)
1300
+
1301
+ def openLocalDemos(self, event=None):
1302
+ # Choose a command according to OS
1303
+ if sys.platform in ['win32']:
1304
+ comm = "explorer"
1305
+ elif sys.platform in ['darwin']:
1306
+ comm = "open"
1307
+ elif sys.platform in ['linux', 'linux2']:
1308
+ comm = "dolphin"
1309
+ # Use command to open themes folder
1310
+ subprocess.call(f"{comm} {prefs.builder['unpackedDemosDir']}", shell=True)
1311
+
1312
+ def openPavloviaDemos(self, event=None):
1313
+ webbrowser.open("https://pavlovia.org/explore")
1314
+
1315
+ def sendToRunner(self, evt=None):
1316
+ """
1317
+ Send the current file to the Runner.
1318
+ """
1319
+ # Check whether file is truly untitled (not just saved as untitled)
1320
+ if not self.fileExists:
1321
+ ok = self.fileSave(self.filename)
1322
+ if not ok:
1323
+ return False # save file before compiling script
1324
+
1325
+ if self.getIsModified():
1326
+ ok = self.fileSave(self.filename)
1327
+ if not ok:
1328
+ return False # save file before compiling script
1329
+ self.app.showRunner()
1330
+ self.app.runner.addTask(fileName=self.filename)
1331
+ self.app.runner.Raise()
1332
+ self.app.showRunner()
1333
+
1334
+ return True
1335
+
1336
+ def updateRunModeIcons(self, evt=None):
1337
+ """
1338
+ Function to update run/pilot icons according to run mode
1339
+ """
1340
+ mode = self.ribbon.buttons['pyswitch'].mode
1341
+ # show/hide run buttons
1342
+ for key in ("pyrun", "jsrun", "sendRunner"):
1343
+ self.ribbon.buttons[key].Show(mode)
1344
+ # hide/show pilot buttons
1345
+ for key in ("pypilot", "jspilot", "pilotRunner"):
1346
+ self.ribbon.buttons[key].Show(not mode)
1347
+ # update
1348
+ self.ribbon.Layout()
1349
+
1350
+ def onRunModeToggle(self, evt):
1351
+ """
1352
+ Function to execute when switching between pilot and run modes
1353
+ """
1354
+ mode = evt.GetInt()
1355
+ # update icons
1356
+ self.updateRunModeIcons()
1357
+ # update experiment mode
1358
+ if self.exp is not None:
1359
+ self.exp.runMode = mode
1360
+ # mark as modified
1361
+ self.setIsModified(True)
1362
+ # update
1363
+ self.ribbon.Update()
1364
+ self.ribbon.Refresh()
1365
+ self.ribbon.Layout()
1366
+
1367
+ def onRunShortcut(self, evt=None):
1368
+ """
1369
+ Callback for when the run shortcut is pressed - will either run or pilot depending on run mode
1370
+ """
1371
+ # do nothing if we have no experiment
1372
+ if self.exp is None:
1373
+ return
1374
+ # run/pilot according to mode
1375
+ if self.exp.runMode:
1376
+ self.runFile(evt)
1377
+ else:
1378
+ self.pilotFile(evt)
1379
+
1380
+ def runFile(self, event=None):
1381
+ """
1382
+ Send the current file to the Runner and run it.
1383
+ """
1384
+ if self.sendToRunner(event):
1385
+ self.app.runner.panel.runLocal(event)
1386
+
1387
+ def pilotFile(self, event=None):
1388
+ """
1389
+ Send the current file to the Runner and run it in pilot mode.
1390
+ """
1391
+ if self.sendToRunner(event):
1392
+ self.app.runner.panel.pilotLocal(event)
1393
+
1394
+ def onCopyRoutine(self, event=None):
1395
+ """copy the current routine from self.routinePanel
1396
+ to self.app.copiedRoutine.
1397
+ """
1398
+ r = self.routinePanel.getCurrentRoutine().copy()
1399
+ if r is not None:
1400
+ self.app.copiedRoutine = r
1401
+
1402
+ def onPasteRoutine(self, event=None):
1403
+ """Paste the current routine from self.app.copiedRoutine to a new page
1404
+ in self.routinePanel after prompting for a new name.
1405
+ """
1406
+ if self.app.copiedRoutine is None:
1407
+ return -1
1408
+ origName = self.app.copiedRoutine.name
1409
+ defaultName = self.exp.namespace.makeValid(origName)
1410
+ msg = _translate('New name for copy of "%(copied)s"? [%(default)s]')
1411
+ vals = {'copied': origName, 'default': defaultName}
1412
+ message = msg % vals
1413
+ dlg = wx.TextEntryDialog(self, message=message,
1414
+ caption=_translate('Paste Routine'))
1415
+ if dlg.ShowModal() == wx.ID_OK:
1416
+ routineName = dlg.GetValue()
1417
+ if not routineName:
1418
+ routineName = defaultName
1419
+ newRoutine = self.app.copiedRoutine.copy()
1420
+ self.pasteRoutine(newRoutine, routineName)
1421
+ dlg.Destroy()
1422
+
1423
+ def pasteRoutine(self, newRoutine, routineName):
1424
+ """
1425
+ Paste a copied Routine into the current Experiment. Returns a copy of that Routine
1426
+ """
1427
+ newRoutine.name = self.exp.namespace.makeValid(routineName, prefix="routine")
1428
+ newRoutine.exp = self.exp
1429
+ # add to the experiment
1430
+ self.exp.addRoutine(newRoutine.name, newRoutine)
1431
+ for newComp in newRoutine: # routine == list of components
1432
+ newName = self.exp.namespace.makeValid(newComp.params['name'])
1433
+ self.exp.namespace.add(newName)
1434
+ newComp.params['name'].val = newName
1435
+ newComp.exp = self.exp
1436
+ # could do redrawRoutines but would be slower?
1437
+ self.routinePanel.addRoutinePage(newRoutine.name, newRoutine)
1438
+ self.routinePanel.setCurrentRoutine(newRoutine)
1439
+ return newRoutine
1440
+
1441
+ def onPasteCompon(self, event=None):
1442
+ """
1443
+ Paste the copied Component (if there is one) into the current
1444
+ Routine
1445
+ """
1446
+ routinePage = self.routinePanel.getCurrentPage()
1447
+ routinePage.pasteCompon()
1448
+
1449
+ def onURL(self, evt):
1450
+ """decompose the URL of a file and line number"""
1451
+ # "C:\Program Files\wxPython...\samples\hangman\hangman.py"
1452
+ filename = evt.GetString().split('"')[1]
1453
+ lineNumber = int(evt.GetString().split(',')[1][5:])
1454
+ self.app.showCoder()
1455
+ self.app.coder.gotoLine(filename, lineNumber)
1456
+
1457
+ def setExperimentSettings(self, event=None, timeout=None):
1458
+ """Defines ability to save experiment settings
1459
+ """
1460
+ component = self.exp.settings
1461
+ # does this component have a help page?
1462
+ if hasattr(component, 'url'):
1463
+ helpUrl = component.url
1464
+ else:
1465
+ helpUrl = None
1466
+ title = '%s Properties' % self.exp.getExpName()
1467
+ dlg = DlgExperimentProperties(
1468
+ frame=self, element=component, experiment=self.exp, timeout=timeout)
1469
+
1470
+ if dlg.OK:
1471
+ # add to undo stack
1472
+ self.addToUndoStack("EDIT experiment settings")
1473
+ # update run mode
1474
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode)
1475
+ # mark modified
1476
+ self.setIsModified(True)
1477
+
1478
+ def addRoutine(self, event=None):
1479
+ """Defines ability to add routine in the routine panel
1480
+ """
1481
+ self.routinePanel.createNewRoutine()
1482
+
1483
+ def renameRoutine(self, name, event=None):
1484
+ """Defines ability to rename routine in the routine panel
1485
+ """
1486
+ # get notebook details
1487
+ routine = self.routinePanel.GetPage(
1488
+ self.routinePanel.GetSelection()).routine
1489
+ oldName = routine.name
1490
+ msg = _translate("What is the new name for the Routine?")
1491
+ dlg = wx.TextEntryDialog(self, message=msg, value=oldName,
1492
+ caption=_translate('Rename'))
1493
+ if dlg.ShowModal() == wx.ID_OK:
1494
+ name = dlg.GetValue()
1495
+ self._doRenameRoutine(oldName=oldName, newName=name)
1496
+ dlg.Destroy()
1497
+
1498
+ def _doRenameRoutine(self, oldName, newName):
1499
+ # silently auto-adjust the name to be valid, and register in the
1500
+ # namespace:
1501
+ name = self.exp.namespace.makeValid(newName, prefix='routine')
1502
+ if oldName in self.exp.routines:
1503
+ # Swap old with new names
1504
+ self.exp.routines[oldName].name = name
1505
+ self.exp.routines[name] = self.exp.routines.pop(oldName)
1506
+ self.exp.namespace.rename(oldName, name)
1507
+ currentRoutine = self.routinePanel.getCurrentPage()
1508
+ currentRoutineIndex = self.routinePanel.GetPageIndex(currentRoutine)
1509
+ self.routinePanel.renameRoutinePage(currentRoutineIndex, name)
1510
+ self.addToUndoStack("`RENAME Routine `%s`" % oldName)
1511
+ self.flowPanel.canvas.draw()
1512
+
1513
+ def compileScript(self, event=None):
1514
+ """Defines compile script button behavior"""
1515
+ # save so we have a file to work off
1516
+ saved = self.fileSave()
1517
+ # if save cancelled, return now
1518
+ if not saved:
1519
+ return
1520
+ # construct filename for py file
1521
+ fullPath = self.filename.parent / (self.exp.name + '.py')
1522
+ # write script
1523
+ fullPath = self.generateScript(
1524
+ outfile=str(fullPath),
1525
+ exp=self.exp
1526
+ )
1527
+ # show it in Coder
1528
+ self.app.showCoder(fileList=[fullPath]) # make sure coder is visible
1529
+ self.app.coder.fileReload(event=None, filename=fullPath)
1530
+
1531
+ @property
1532
+ def stdoutFrame(self):
1533
+ """
1534
+ Gets Experiment Runner stdout.
1535
+ """
1536
+ if not self.app.runner:
1537
+ self.app.runner = self.app.showRunner()
1538
+ return self.app.runner
1539
+
1540
+ def _getHtmlPath(self, filename):
1541
+ expPath = os.path.split(filename)[0]
1542
+ if not os.path.isdir(expPath):
1543
+ retVal = self.fileSave()
1544
+ if retVal:
1545
+ return self._getHtmlPath(self.filename)
1546
+ else:
1547
+ return False
1548
+
1549
+ htmlPath = os.path.join(expPath, self.exp.htmlFolder)
1550
+ return htmlPath
1551
+
1552
+ def _getExportPref(self, pref):
1553
+ """Returns True if pref matches exportHTML preference"""
1554
+ if pref.lower() not in [prefs.lower() for prefs in self.exp.settings.params['exportHTML'].allowedVals]:
1555
+ raise ValueError("'{}' is not an allowed value for {}".format(pref, 'exportHTML'))
1556
+ exportHtml = str(self.exp.settings.params['exportHTML'].val).lower()
1557
+ if exportHtml == pref.lower():
1558
+ return True
1559
+
1560
+ def openPluginManager(self, evt=None):
1561
+ dlg = psychopy.app.plugin_manager.dialog.EnvironmentManagerDlg(self)
1562
+ dlg.Show()
1563
+ # Do post-close checks
1564
+ dlg.onClose()
1565
+
1566
+ def onPavloviaCreate(self, evt=None):
1567
+ if Path(self.filename).is_file():
1568
+ # Save file
1569
+ self.fileSave(self.filename)
1570
+ # If allowed by prefs, export html and js files
1571
+ if self._getExportPref('on sync'):
1572
+ htmlPath = self._getHtmlPath(self.filename)
1573
+ if htmlPath:
1574
+ self.fileExport(htmlPath=htmlPath)
1575
+ else:
1576
+ return
1577
+ # Get start path and name from builder/coder if possible
1578
+ if self.filename:
1579
+ file = Path(self.filename)
1580
+ name = file.stem
1581
+ path = file.parent
1582
+ else:
1583
+ name = path = ""
1584
+ # Open dlg to create new project
1585
+ createDlg = sync.CreateDlg(self,
1586
+ user=pavlovia.getCurrentSession().user,
1587
+ name=name,
1588
+ path=path)
1589
+ if createDlg.ShowModal() == wx.ID_OK and createDlg.project is not None:
1590
+ self.project = createDlg.project
1591
+ else:
1592
+ return
1593
+ # Do first sync
1594
+ self.onPavloviaSync()
1595
+
1596
+ def onPavloviaSync(self, evt=None):
1597
+ if Path(self.filename).is_file():
1598
+ # Save file
1599
+ self.fileSave(self.filename)
1600
+ # If allowed by prefs, export html and js files
1601
+ if self._getExportPref('on sync'):
1602
+ htmlPath = self._getHtmlPath(self.filename)
1603
+ if htmlPath:
1604
+ self.fileExport(htmlPath=htmlPath)
1605
+ else:
1606
+ return
1607
+ # Sync
1608
+ pavlovia_ui.syncProject(parent=self, file=self.filename, project=self.project)
1609
+
1610
+ def onPavloviaRun(self, evt=None):
1611
+ # Sync project
1612
+ self.onPavloviaSync()
1613
+
1614
+ if self.project is not None:
1615
+ # Update project status
1616
+ self.project.pavloviaStatus = 'ACTIVATED'
1617
+ # Run
1618
+ url = "https://pavlovia.org/run/{}".format(self.project['path_with_namespace'])
1619
+ wx.LaunchDefaultBrowser(url)
1620
+
1621
+ def onPavloviaDebug(self, evt=None):
1622
+ # Open runner
1623
+ self.app.showRunner()
1624
+ runner = self.app.runner
1625
+ # Make sure we have a current file
1626
+ if self.getIsModified() or not Path(self.filename).is_file():
1627
+ saved = self.fileSave()
1628
+ if not saved:
1629
+ return
1630
+ # Send current file to runner
1631
+ runner.addTask(fileName=self.filename)
1632
+ # Run debug function from runner
1633
+ self.app.runner.panel.runOnlineDebug(evt=evt)
1634
+
1635
+ def setPavloviaUser(self, user):
1636
+ # TODO: update user icon on button to user avatar
1637
+ pass
1638
+
1639
+ @property
1640
+ def project(self):
1641
+ """A PavloviaProject object if one is known for this experiment
1642
+ """
1643
+ if hasattr(self, "_project"):
1644
+ return self._project
1645
+ elif self.fileExists:
1646
+ return pavlovia.getProject(self.filename)
1647
+ else:
1648
+ return None
1649
+
1650
+ @project.setter
1651
+ def project(self, project):
1652
+ self._project = project
1653
+
1654
+ self.ribbon.buttons['pavproject'].updateInfo()
1655
+
1656
+
1657
+ class RoutinesNotebook(aui.AuiNotebook, handlers.ThemeMixin):
1658
+ """A notebook that stores one or more routines
1659
+ """
1660
+
1661
+ def __init__(self, frame, id=-1):
1662
+ self.frame = frame
1663
+ self.app = frame.app
1664
+ self.routineMaxSize = 2
1665
+ self.appData = self.app.prefs.appData
1666
+ aui.AuiNotebook.__init__(self, frame, id,
1667
+ agwStyle=aui.AUI_NB_TAB_MOVE | aui.AUI_NB_CLOSE_ON_ACTIVE_TAB | aui.AUI_NB_WINDOWLIST_BUTTON)
1668
+ self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.onClosePane)
1669
+ self.Bind(aui.EVT_AUINOTEBOOK_END_DRAG, self.onMoveTab)
1670
+
1671
+ # double buffered better rendering except if retina
1672
+
1673
+ self.SetDoubleBuffered(not self.frame.isRetina)
1674
+
1675
+ # This needs to be done on init, otherwise it gets an outline
1676
+ self.GetAuiManager().SetArtProvider(handlers.PsychopyDockArt())
1677
+
1678
+ if not hasattr(self.frame, 'exp'):
1679
+ return # we haven't yet added an exp
1680
+
1681
+ def getCurrentRoutine(self):
1682
+ routinePage = self.getCurrentPage()
1683
+ if routinePage:
1684
+ return routinePage.routine # no routine page
1685
+ return None
1686
+
1687
+ def setCurrentRoutine(self, routine):
1688
+ for ii in range(self.GetPageCount()):
1689
+ if routine is self.GetPage(ii).routine:
1690
+ self.SetSelection(ii)
1691
+ self.frame.flowPanel.canvas.draw()
1692
+
1693
+ def SetSelection(self, index, force=False):
1694
+ aui.AuiNotebook.SetSelection(self, index, force=force)
1695
+ self.frame.componentButtons.enableComponents(
1696
+ not isinstance(self.GetPage(index).routine, BaseStandaloneRoutine)
1697
+ )
1698
+
1699
+ def getCurrentPage(self):
1700
+ if self.GetSelection() >= 0:
1701
+ return self.GetPage(self.GetSelection())
1702
+ return None
1703
+
1704
+ def addRoutinePage(self, routineName, routine):
1705
+ # Make page
1706
+ routinePage = None
1707
+ if isinstance(routine, Routine):
1708
+ routinePage = RoutineCanvas(notebook=self, routine=routine)
1709
+ elif isinstance(routine, BaseStandaloneRoutine):
1710
+ routinePage = StandaloneRoutineCanvas(parent=self, routine=routine)
1711
+ # Add page
1712
+ if routinePage:
1713
+ self.AddPage(routinePage, routineName)
1714
+
1715
+ def renameRoutinePage(self, index, newName, ):
1716
+ self.SetPageText(index, newName)
1717
+
1718
+ def removePages(self):
1719
+ for ii in range(self.GetPageCount()):
1720
+ currId = self.GetSelection()
1721
+ self.DeletePage(currId)
1722
+
1723
+ def createNewRoutine(self, template=None):
1724
+ msg = _translate("What is the name for the new Routine? "
1725
+ "(e.g. instr, trial, feedback)")
1726
+ dlg = DlgNewRoutine(self)
1727
+ routineName = None
1728
+ if dlg.ShowModal() == wx.ID_OK:
1729
+ routineName = dlg.nameCtrl.GetValue()
1730
+ routineName = self.frame.exp.namespace.makeValid(routineName, prefix="routine")
1731
+ template = copy.deepcopy(dlg.selectedTemplate)
1732
+ self.frame.pasteRoutine(template, routineName)
1733
+ self.frame.addToUndoStack("NEW Routine `%s`" % routineName)
1734
+ dlg.Destroy()
1735
+ return routineName
1736
+
1737
+ def onClosePane(self, event=None):
1738
+ """Close the pane and remove the routine from the exp.
1739
+ """
1740
+ currentPage = self.GetPage(event.GetSelection())
1741
+ routine = currentPage.routine
1742
+ name = routine.name
1743
+
1744
+ # name is not valid for some reason
1745
+ if name not in self.frame.exp.routines:
1746
+ event.Skip()
1747
+ return
1748
+
1749
+ # check if the user wants a prompt
1750
+ showDlg = self.app.prefs.builder.get('confirmRoutineClose', False)
1751
+ if showDlg:
1752
+ # message to display
1753
+ msg = _translate(
1754
+ "Do you want to remove routine '{}' from the experiment?")
1755
+
1756
+ # dialog asking if the user wants to remove the routine
1757
+ dlg = wx.MessageDialog(
1758
+ self,
1759
+ _translate(msg).format(name),
1760
+ _translate('Remove routine?'),
1761
+ wx.YES_NO | wx.NO_DEFAULT | wx.CENTRE | wx.STAY_ON_TOP)
1762
+
1763
+ # show the dialog and get the response
1764
+ dlgResult = dlg.ShowModal()
1765
+ dlg.Destroy()
1766
+
1767
+ if dlgResult == wx.ID_NO: # if NO, stop the tab from closing
1768
+ event.Veto()
1769
+ return
1770
+
1771
+ # remove names of the routine and its components from namespace
1772
+ _nsp = self.frame.exp.namespace
1773
+ for c in self.frame.exp.routines[name]:
1774
+ _nsp.remove(c.params['name'].val)
1775
+ _nsp.remove(self.frame.exp.routines[name].name)
1776
+ del self.frame.exp.routines[name]
1777
+
1778
+ if routine in self.frame.exp.flow:
1779
+ self.frame.exp.flow.removeComponent(routine)
1780
+ self.frame.flowPanel.canvas.draw()
1781
+ self.frame.addToUndoStack("REMOVE Routine `%s`" % (name))
1782
+
1783
+ def onMoveTab(self, evt=None):
1784
+ """
1785
+ After moving tabs around, sorts Routines in the Experiment accordingly
1786
+ and marks experiment as changed.
1787
+
1788
+ Parameters
1789
+ ----------
1790
+ evt : wx.aui.AUI_NB_TAB_MOVE
1791
+ Event generated by moving the tab (not used)
1792
+ """
1793
+
1794
+ # Get tab names in order
1795
+ names = []
1796
+ for i in range(self.GetPageCount()):
1797
+ names.append(self.GetPageText(i))
1798
+ # Reorder routines in experiment to match tab order
1799
+ routines = collections.OrderedDict()
1800
+ for name in names:
1801
+ routines[name] = self.frame.exp.routines[name]
1802
+ self.frame.exp.routines = routines
1803
+ # Set modified
1804
+ self.frame.setIsModified(True)
1805
+
1806
+ def increaseSize(self, event=None):
1807
+ self.appData['routineSize'] = min(
1808
+ self.routineMaxSize, self.appData['routineSize'] + 1)
1809
+ with WindowFrozen(self):
1810
+ self.redrawRoutines()
1811
+
1812
+ def decreaseSize(self, event=None):
1813
+ self.appData['routineSize'] = max(0, self.appData['routineSize'] - 1)
1814
+ with WindowFrozen(self):
1815
+ self.redrawRoutines()
1816
+
1817
+ def redrawRoutines(self):
1818
+ """Removes all the routines, adds them back (alphabetical order),
1819
+ sets current back to orig
1820
+ """
1821
+ currPage = self.GetSelection()
1822
+ self.removePages()
1823
+ for routineName in self.frame.exp.routines:
1824
+ if isinstance(self.frame.exp.routines[routineName], (Routine, BaseStandaloneRoutine)):
1825
+ self.addRoutinePage(
1826
+ routineName, self.frame.exp.routines[routineName])
1827
+ if currPage > -1:
1828
+ self.SetSelection(currPage)
1829
+
1830
+
1831
+ class RoutineCanvas(wx.ScrolledWindow, handlers.ThemeMixin):
1832
+ """Represents a single routine (used as page in RoutinesNotebook)"""
1833
+
1834
+ def __init__(self, notebook, id=wx.ID_ANY, routine=None):
1835
+ """This window is based heavily on the PseudoDC demo of wxPython
1836
+ """
1837
+ wx.ScrolledWindow.__init__(
1838
+ self, notebook, id, (0, 0), style=wx.BORDER_NONE | wx.VSCROLL)
1839
+
1840
+ self.frame = notebook.frame
1841
+ self.app = self.frame.app
1842
+ self.dpi = self.app.dpi
1843
+ self.lines = []
1844
+ self.maxWidth = self.GetSize().GetWidth()
1845
+ self.maxHeight = 15 * self.dpi
1846
+ self.x = self.y = 0
1847
+ self.curLine = []
1848
+ self.drawing = False
1849
+ self.drawSize = self.app.prefs.appData['routineSize']
1850
+ # dict in which to store rectangles to aid layout (populated in updateLayoutRects)
1851
+ self.rects = {}
1852
+ # auto-rescale based on number of components and window size is jumpy
1853
+ # when switch between routines of diff drawing sizes
1854
+ self.iconSize = (24, 24, 48)[self.drawSize] # only 24, 48 so far
1855
+ self.fontBaseSize = (1100, 1200, 1300)[self.drawSize] # depends on OS?
1856
+ #self.scroller = PsychopyScrollbar(self, wx.VERTICAL)
1857
+ self.SetVirtualSize((self.maxWidth, self.maxHeight))
1858
+ self.SetScrollRate(self.dpi // 16, self.dpi // 16)
1859
+
1860
+ self.routine = routine
1861
+ self.yPositions = None
1862
+ self.yPosTop = (25, 40, 60)[self.drawSize]
1863
+ # the step in Y between each component
1864
+ self.componentStep = (25, 32, 50)[self.drawSize]
1865
+ self.timeXposStart = (150, 150, 200)[self.drawSize]
1866
+ # the left hand edge of the icons:
1867
+ _scale = (1.3, 1.5, 1.5)[self.drawSize]
1868
+ self.iconXpos = self.timeXposStart - self.iconSize * _scale
1869
+ self.timeXposEnd = self.timeXposStart + 400 # onResize() overrides
1870
+
1871
+ # create a PseudoDC to record our drawing
1872
+ self.pdc = PseudoDC()
1873
+ self.pen_cache = {}
1874
+ self.brush_cache = {}
1875
+ # vars for handling mouse clicks
1876
+ self.dragid = -1
1877
+ self.lastpos = (0, 0)
1878
+ # use the ID of the drawn icon to retrieve component name:
1879
+ self.componentFromID = {}
1880
+ # define context menu items and labels
1881
+ self.contextMenuLabels = {
1882
+ 'copy': _translate("Copy"),
1883
+ 'paste above': _translate("Paste above"),
1884
+ 'paste below': _translate("Paste below"),
1885
+ 'edit': _translate("Edit"),
1886
+ 'remove': _translate("Remove"),
1887
+ 'move to top': _translate("Move to top"),
1888
+ 'move up': _translate("Move up"),
1889
+ 'move down': _translate("Move down"),
1890
+ 'move to bottom': _translate("Move to bottom"),
1891
+ }
1892
+ self.contextMenuItems = list(self.contextMenuLabels)
1893
+
1894
+ self.contextItemFromID = {}
1895
+ self.contextIDFromItem = {}
1896
+ for item in self.contextMenuItems:
1897
+ id = wx.NewIdRef()
1898
+ self.contextItemFromID[id] = item
1899
+ self.contextIDFromItem[item] = id
1900
+
1901
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
1902
+ self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
1903
+ self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1904
+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
1905
+ self.Bind(wx.EVT_SIZE, self.onResize)
1906
+ # crashes if drop on OSX:
1907
+ # self.SetDropTarget(FileDropTarget(builder = self.frame))
1908
+
1909
+ def _applyAppTheme(self, target=None):
1910
+ """Synonymise app theme method with redraw method"""
1911
+ return self.redrawRoutine()
1912
+
1913
+ def onResize(self, event):
1914
+ self.sizePix = event.GetSize()
1915
+ self.timeXposStart = (150, 150, 200)[self.drawSize]
1916
+ self.timeXposEnd = self.sizePix[0] - (60, 80, 100)[self.drawSize]
1917
+ self.redrawRoutine() # then redraw visible
1918
+
1919
+ def ConvertEventCoords(self, event):
1920
+ xView, yView = self.GetViewStart()
1921
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
1922
+ return (event.GetX() + (xView * xDelta),
1923
+ event.GetY() + (yView * yDelta))
1924
+
1925
+ def OffsetRect(self, r):
1926
+ """Offset the rectangle, r, to appear in the given pos in the window
1927
+ """
1928
+ xView, yView = self.GetViewStart()
1929
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
1930
+ r.OffsetXY(-(xView * xDelta), -(yView * yDelta))
1931
+
1932
+ def OnMouse(self, event):
1933
+ if event.LeftDown():
1934
+ x, y = self.ConvertEventCoords(event)
1935
+ icons = self.pdc.FindObjectsByBBox(x, y)
1936
+ if len(icons):
1937
+ self.editComponentProperties(
1938
+ component=self.componentFromID[icons[0]])
1939
+ elif event.RightDown():
1940
+ x, y = self.ConvertEventCoords(event)
1941
+ icons = self.pdc.FindObjectsByBBox(x, y)
1942
+ menuPos = event.GetPosition()
1943
+ if 'flowTop' in self.app.prefs.builder['builderLayout']:
1944
+ # width of components panel
1945
+ menuPos[0] += self.frame.componentButtons.GetSize()[0]
1946
+ # height of flow panel
1947
+ menuPos[1] += self.frame.flowPanel.canvas.GetSize()[1]
1948
+ if len(icons):
1949
+ self._menuComponent = self.componentFromID[icons[0]]
1950
+ self.showContextMenu(self._menuComponent, xy=menuPos)
1951
+ else: # no context
1952
+ self.showContextMenu(None, xy=menuPos)
1953
+
1954
+ elif event.Dragging() or event.LeftUp():
1955
+ if self.dragid != -1:
1956
+ pass
1957
+ if event.LeftUp():
1958
+ pass
1959
+ elif event.Moving():
1960
+ try:
1961
+ x, y = self.ConvertEventCoords(event)
1962
+ id = self.pdc.FindObjectsByBBox(x, y)[0]
1963
+ component = self.componentFromID[id]
1964
+ # Indicate hover target in the bottom bar
1965
+ if component == self.routine.settings:
1966
+ self.frame.SetStatusText("Routine settings: " + component.params['name'].val)
1967
+ else:
1968
+ self.frame.SetStatusText("Component: "+component.params['name'].val)
1969
+ except IndexError:
1970
+ self.frame.SetStatusText("")
1971
+
1972
+ def OnScroll(self, event):
1973
+ xy = self.GetViewStart()
1974
+ delta = int(event.WheelRotation * self.dpi / 1600)
1975
+ self.Scroll(xy[0], xy[1]-delta)
1976
+
1977
+ def showContextMenu(self, component, xy):
1978
+ """Show a context menu in the routine view.
1979
+ """
1980
+ menu = wx.Menu()
1981
+ if component not in (None, self.routine.settings):
1982
+ for item in self.contextMenuItems:
1983
+ id = self.contextIDFromItem[item]
1984
+ # don't show paste option unless something is copied
1985
+ if item.startswith('paste'):
1986
+ if not self.app.copiedCompon: # skip paste options
1987
+ continue
1988
+ itemLabel = " ".join(
1989
+ (self.contextMenuLabels[item],
1990
+ "({})".format(
1991
+ self.app.copiedCompon.params['name'].val)))
1992
+ elif any([item.startswith(op) for op in ('copy', 'remove', 'edit')]):
1993
+ itemLabel = " ".join(
1994
+ (self.contextMenuLabels[item],
1995
+ "({})".format(component.params['name'].val)))
1996
+ else:
1997
+ itemLabel = self.contextMenuLabels[item]
1998
+
1999
+ menu.Append(id, itemLabel)
2000
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
2001
+
2002
+ self.frame.PopupMenu(menu, xy)
2003
+ menu.Destroy() # destroy to avoid mem leak
2004
+ else:
2005
+ # anywhere but a hotspot is clicked, show this menu
2006
+ if self.app.copiedCompon:
2007
+ itemLabel = " ".join(
2008
+ (_translate('paste'),
2009
+ "({})".format(
2010
+ self.app.copiedCompon.params['name'].val)))
2011
+ menu.Append(wx.ID_ANY, itemLabel)
2012
+ menu.Bind(wx.EVT_MENU, self.pasteCompon, id=wx.ID_ANY)
2013
+
2014
+ self.frame.PopupMenu(menu, xy)
2015
+ menu.Destroy()
2016
+
2017
+ def onContextSelect(self, event):
2018
+ """Perform a given action on the component chosen
2019
+ """
2020
+ op = self.contextItemFromID[event.GetId()]
2021
+ component = self._menuComponent
2022
+ r = self.routine
2023
+ if op == 'edit':
2024
+ self.editComponentProperties(component=component)
2025
+ elif op == 'copy':
2026
+ self.copyCompon(component=component)
2027
+ elif op == 'paste above':
2028
+ self.pasteCompon(index=r.index(component))
2029
+ elif op == 'paste below':
2030
+ self.pasteCompon(index=r.index(component) + 1)
2031
+ elif op == 'remove':
2032
+ r.removeComponent(component)
2033
+ self.frame.addToUndoStack(
2034
+ "REMOVE `%s` from Routine" % component.params['name'].val)
2035
+ self.frame.exp.namespace.remove(component.params['name'].val)
2036
+ elif op.startswith('move'):
2037
+ lastLoc = r.index(component)
2038
+ r.remove(component)
2039
+ if op == 'move to top':
2040
+ r.insert(0, component)
2041
+ if op == 'move up':
2042
+ r.insert(lastLoc - 1, component)
2043
+ if op == 'move down':
2044
+ r.insert(lastLoc + 1, component)
2045
+ if op == 'move to bottom':
2046
+ r.append(component)
2047
+ self.frame.addToUndoStack("MOVED `%s`" %
2048
+ component.params['name'].val)
2049
+ self.redrawRoutine()
2050
+ self._menuComponent = None
2051
+
2052
+ def OnPaint(self, event):
2053
+ # Create a buffered paint DC. It will create the real
2054
+ # wx.PaintDC and then blit the bitmap to it when dc is
2055
+ # deleted.
2056
+ dc = wx.GCDC(wx.BufferedPaintDC(self))
2057
+ # we need to clear the dc BEFORE calling PrepareDC
2058
+ bg = wx.Brush(self.GetBackgroundColour())
2059
+ dc.SetBackground(bg)
2060
+ dc.Clear()
2061
+ # use PrepareDC to set position correctly
2062
+ self.PrepareDC(dc)
2063
+ # create a clipping rect from our position and size
2064
+ # and the Update Region
2065
+ xv, yv = self.GetViewStart()
2066
+ dx, dy = self.GetScrollPixelsPerUnit()
2067
+ x, y = (xv * dx, yv * dy)
2068
+ rgn = self.GetUpdateRegion()
2069
+ rgn.Offset(x, y)
2070
+ r = rgn.GetBox()
2071
+ # draw to the dc using the calculated clipping rect
2072
+ self.pdc.DrawToDCClipped(dc, r)
2073
+
2074
+ def redrawRoutine(self):
2075
+ # clear everything
2076
+ self.pdc.Clear()
2077
+ self.pdc.RemoveAll()
2078
+ # set font size
2079
+ self.setFontSize(self.fontBaseSize // self.dpi, self.pdc)
2080
+
2081
+ # update rects with which to layout
2082
+ self.updateLayoutRects()
2083
+ # # if debugging, draw all the rects
2084
+ # self.pdc.SetPen(wx.Pen("Red"))
2085
+ # for rect in self.rects.values():
2086
+ # self.pdc.DrawRectangle(rect)
2087
+
2088
+ self.SetBackgroundColour(colors.app['tab_bg'])
2089
+
2090
+ # separate components according to whether they are drawn in separate
2091
+ # row
2092
+ rowComponents = []
2093
+ staticCompons = []
2094
+ for n, component in enumerate(self.routine):
2095
+ if component.type == 'Static':
2096
+ staticCompons.append(component)
2097
+ elif component == self.routine.settings:
2098
+ pass
2099
+ else:
2100
+ rowComponents.append(component)
2101
+
2102
+ # draw settings button
2103
+ settingsBtnExtent = self.drawSettingsBtn(self.pdc, self.routine.settings)
2104
+
2105
+ # draw static, time grid, normal (row) comp:
2106
+ yPos = self.rects['grid'].Top
2107
+ yPosBottom = self.rects['grid'].Bottom
2108
+ # draw any Static Components first (below the grid)
2109
+ for component in staticCompons:
2110
+ bottom = max(yPosBottom, self.GetSize()[1])
2111
+ self.drawStatic(self.pdc, component, yPos, bottom)
2112
+ self.drawTimeGrid(self.pdc, yPos, yPosBottom)
2113
+ # normal components, one per row
2114
+ for component in rowComponents:
2115
+ self.drawComponent(self.pdc, component, yPos)
2116
+ yPos += self.componentStep
2117
+ # draw end line (if there is one)
2118
+ self.drawForceEndLine(self.pdc, yPosBottom)
2119
+
2120
+ # the 50 allows space for labels below the time axis
2121
+ self.SetVirtualSize((int(self.maxWidth), yPos + 50))
2122
+ self.Refresh() # refresh the visible window after drawing (OnPaint)
2123
+ #self.scroller.Resize()
2124
+
2125
+ def updateLayoutRects(self):
2126
+ """
2127
+ Recalculate the positions and sizes of the wx.Rect objects which determine
2128
+ how the canvas is laid out.
2129
+ """
2130
+ self.rects = {}
2131
+ self.setFontSize(self.fontBaseSize // self.dpi, self.pdc)
2132
+
2133
+ # --- Whole area ---
2134
+ canvas = self.rects['canvas'] = wx.Rect(
2135
+ x=15,
2136
+ y=15,
2137
+ width=self.sizePix[0] - 30,
2138
+ height=self.sizePix[1] - 30
2139
+ )
2140
+
2141
+ # --- Time grid ---
2142
+ # filter Components for just those included in the time grid
2143
+ trueComponents = []
2144
+ for comp in self.routine:
2145
+ if type(comp).__name__ in ("StaticComponent", "RoutineSettingsComponent"):
2146
+ continue
2147
+ else:
2148
+ trueComponents.append(comp)
2149
+ # note: will be modified as things are added around it
2150
+ grid = self.rects['grid'] = wx.Rect(
2151
+ x=canvas.Left,
2152
+ y=canvas.Top,
2153
+ width=canvas.Width,
2154
+ height=self.componentStep * len(trueComponents)
2155
+ )
2156
+
2157
+ # --- Top bar ---
2158
+ # this is where the Settings button lives
2159
+ topBar = self.rects['topBar'] = wx.Rect(
2160
+ x=canvas.Left,
2161
+ y=canvas.Top,
2162
+ width=canvas.Width,
2163
+ height=int(self.iconSize/3) + 24
2164
+ )
2165
+ # shift grid down
2166
+ grid.Top += topBar.Height
2167
+
2168
+ # --- Time labels ---
2169
+ # note: will be modified as things are added around it
2170
+ timeLbls = self.rects['timeLbls'] = wx.Rect(
2171
+ x=grid.Left,
2172
+ y=topBar.Bottom,
2173
+ width=grid.Width,
2174
+ height=int(self.componentStep/2)
2175
+ )
2176
+ # shift grid down
2177
+ grid.Top += timeLbls.Height
2178
+
2179
+ # --- Component names ---
2180
+ # get width of component names column
2181
+ compNameWidths = [120]
2182
+ if not prefs.builder['abbreviateLongCompNames']:
2183
+ # get width of longest name if we're not elipsizing
2184
+ for comp in self.routine:
2185
+ w = self.GetFullTextExtent(comp.name)[0] + 12
2186
+ compNameWidths.append(w)
2187
+ componentLabelWidth = max(compNameWidths)
2188
+ # create rect
2189
+ compLbls = self.rects['compLbls'] = wx.Rect(
2190
+ x=canvas.Left,
2191
+ y=grid.Top,
2192
+ width=componentLabelWidth,
2193
+ height=grid.Height
2194
+ )
2195
+ # shift grid and time labels right (and cut to size)
2196
+ grid.Left += compLbls.Width
2197
+ grid.Width -= compLbls.Width
2198
+ timeLbls.Left += compLbls.Width
2199
+ timeLbls.Width -= compLbls.Width
2200
+
2201
+ # --- Component icons ---
2202
+ icons = self.rects['icons'] = wx.Rect(
2203
+ x=compLbls.Right,
2204
+ y=grid.Top,
2205
+ width=self.iconSize + 12,
2206
+ height=grid.Height
2207
+ )
2208
+ # shift grid and time labels right (and cut to size)
2209
+ grid.Left += icons.Width + 12
2210
+ grid.Width -= icons.Width + 12
2211
+ timeLbls.Left += icons.Width + 12
2212
+ timeLbls.Width -= icons.Width + 12
2213
+
2214
+ # --- Time units label ---
2215
+ timeUnitsLbl = self.rects['timeUnitsLbl'] = wx.Rect(
2216
+ x=grid.Right,
2217
+ y=grid.Top,
2218
+ width=self.GetFullTextExtent("t (sec)")[0] + 12,
2219
+ height=int(self.componentStep/2)
2220
+ )
2221
+ # align self by right edge
2222
+ timeUnitsLbl.Left -= timeUnitsLbl.Width
2223
+ # shift grid and time labels left (and cut to size)
2224
+ grid.Width -= timeUnitsLbl.Width
2225
+ timeLbls.Width -= timeUnitsLbl.Width
2226
+
2227
+ # update references from rects
2228
+ self.timeXposStart = grid.Left
2229
+ self.timeXposEnd = grid.Right
2230
+ self.iconXpos = self.rects['icons'].Left
2231
+
2232
+ def getMaxTime(self):
2233
+ """Return the max time to be drawn in the window
2234
+ """
2235
+ maxTime, nonSlip = self.routine.getMaxTime()
2236
+ if self.routine.hasOnlyStaticComp():
2237
+ maxTime = int(maxTime) + 1.0
2238
+
2239
+ # if max came from routine settings, mark as hard stop
2240
+ rtMax, rtMaxIsNum = self.routine.settings.getDuration()
2241
+ hardStop = rtMaxIsNum and rtMax == maxTime
2242
+
2243
+ return maxTime, hardStop
2244
+
2245
+ def drawTimeGrid(self, dc, yPosTop, yPosBottom, labelAbove=True):
2246
+ """Draws the grid of lines and labels the time axes
2247
+ """
2248
+ yPosTop = int(yPosTop) # explicit type conversion to `int`
2249
+ yPosBottom = int(yPosBottom)
2250
+
2251
+ tMax, hardStop = self.getMaxTime()
2252
+ tMax *= 1.1
2253
+ xScale = self.getSecsPerPixel()
2254
+ xSt = self.timeXposStart
2255
+ xEnd = self.timeXposEnd
2256
+
2257
+ # dc.SetId(wx.NewIdRef())
2258
+ dc.SetPen(wx.Pen(colors.app['rt_timegrid']))
2259
+ dc.SetTextForeground(wx.Colour(colors.app['rt_timegrid']))
2260
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2261
+
2262
+ id = wx.NewIdRef()
2263
+ dc.SetId(id)
2264
+
2265
+ # draw horizontal lines on top and bottom
2266
+ dc.DrawLine(
2267
+ x1=int(xSt),
2268
+ y1=yPosTop,
2269
+ x2=int(xEnd),
2270
+ y2=yPosTop)
2271
+ dc.DrawLine(
2272
+ x1=int(xSt),
2273
+ y1=yPosBottom,
2274
+ x2=int(xEnd),
2275
+ y2=yPosBottom)
2276
+
2277
+ # draw vertical time points
2278
+ # gives roughly 1/10 the width, but in rounded to base 10 of
2279
+ # 0.1,1,10...
2280
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 10.0
2281
+ if tMax / unitSize < 3:
2282
+ # gives units of 2 (0.2,2,20)
2283
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 50.0
2284
+ elif tMax / unitSize < 6:
2285
+ # gives units of 5 (0.5,5,50)
2286
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 20.0
2287
+ for lineN in range(int(numpy.floor((tMax / unitSize)))):
2288
+ # vertical line:
2289
+ dc.DrawLine(int(xSt + lineN * unitSize / xScale),
2290
+ yPosTop - 4,
2291
+ int(xSt + lineN * unitSize / xScale),
2292
+ yPosBottom + 4)
2293
+ # label above:
2294
+ dc.DrawText('%.2g' % (lineN * unitSize),
2295
+ int(xSt + lineN * unitSize / xScale - 4),
2296
+ yPosTop - 30)
2297
+ if yPosBottom > 300:
2298
+ # if bottom of grid is far away then draw labels here too
2299
+ dc.DrawText('%.2g' % (lineN * unitSize),
2300
+ int(xSt + lineN * unitSize / xScale - 4),
2301
+ yPosBottom + 10)
2302
+ # add a label
2303
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2304
+ # y is y-half height of text
2305
+ dc.DrawText('t (sec)',
2306
+ self.rects['timeUnitsLbl'].Left + 6,
2307
+ self.rects['timeUnitsLbl'].Top)
2308
+ # or draw bottom labels only if scrolling is turned on, virtual size >
2309
+ # available size?
2310
+ if yPosBottom > 300:
2311
+ # if bottom of grid is far away then draw labels there too
2312
+ # y is y-half height of text
2313
+ dc.DrawText('t (sec)',
2314
+ int(xEnd + 5),
2315
+ yPosBottom - self.GetFullTextExtent('t')[1] // 2)
2316
+ dc.SetTextForeground(colors.app['text'])
2317
+
2318
+ def drawForceEndLine(self, dc, yPosBottom):
2319
+ id = wx.NewIdRef()
2320
+ dc.SetId(id)
2321
+ # get max time & check if we have a hard stop
2322
+ tMax, hardStop = self.getMaxTime()
2323
+ # if routine has an estimated stop, it's not a hard stop but we shold draw the line anyway
2324
+ if self.routine.settings.params.get("durationEstim", False):
2325
+ hardStop = True
2326
+
2327
+ if hardStop:
2328
+ # if hard stop, draw orange final line
2329
+ dc.SetPen(
2330
+ wx.Pen(colors.app['rt_comp_force'], width=4)
2331
+ )
2332
+ dc.SetTextForeground(
2333
+ wx.Colour(colors.app['rt_comp_force'])
2334
+ )
2335
+ # vertical line:
2336
+ dc.DrawLine(self.timeXposEnd,
2337
+ self.rects['grid'].Top - 4,
2338
+ self.timeXposEnd,
2339
+ yPosBottom + 4)
2340
+ # label above:
2341
+ dc.DrawText('%.2g' % tMax,
2342
+ int(self.timeXposEnd - 4),
2343
+ self.rects['grid'].Top - 30)
2344
+
2345
+ def setFontSize(self, size, dc):
2346
+ font = self.GetFont()
2347
+ font.SetPointSize(size)
2348
+ dc.SetFont(font)
2349
+ self.SetFont(font)
2350
+
2351
+ def drawStatic(self, dc, component, yPosTop, yPosBottom):
2352
+ """draw a static (ISI) component box"""
2353
+
2354
+ # type conversion to `int`
2355
+ yPosTop = int(yPosTop)
2356
+ yPosBottom = int(yPosBottom)
2357
+
2358
+ # set an id for the region of this component (so it can
2359
+ # act as a button). see if we created this already.
2360
+ id = None
2361
+ for key in self.componentFromID:
2362
+ if self.componentFromID[key] == component:
2363
+ id = key
2364
+ if not id: # then create one and add to the dict
2365
+ id = wx.NewIdRef()
2366
+ self.componentFromID[id] = component
2367
+ dc.SetId(id)
2368
+ # deduce start and stop times if possible
2369
+ startTime, duration, nonSlipSafe = component.getStartAndDuration()
2370
+ # ensure static comps are clickable (even if $code start or duration)
2371
+ unknownTiming = False
2372
+ if startTime is None:
2373
+ startTime = 0
2374
+ unknownTiming = True
2375
+ if duration is None:
2376
+ duration = 0 # minimal extent ensured below
2377
+ unknownTiming = True
2378
+ # calculate rectangle for component
2379
+ xScale = self.getSecsPerPixel()
2380
+
2381
+ if component.params['disabled'].val:
2382
+ dc.SetBrush(wx.Brush(colors.app['rt_static_disabled']))
2383
+ dc.SetPen(wx.Pen(colors.app['rt_static_disabled']))
2384
+
2385
+ else:
2386
+ dc.SetBrush(wx.Brush(colors.app['rt_static']))
2387
+ dc.SetPen(wx.Pen(colors.app['rt_static']))
2388
+
2389
+ xSt = self.timeXposStart + startTime // xScale
2390
+ w = duration // xScale + 1 # +1 b/c border alpha=0 in dc.SetPen
2391
+ w = max(min(w, 10000), 2) # ensure 2..10000 pixels
2392
+ h = yPosBottom - yPosTop
2393
+ # name label, position:
2394
+ name = component.params['name'].val # "ISI"
2395
+ if unknownTiming:
2396
+ # flag it as not literally represented in time, e.g., $code
2397
+ # duration
2398
+ name += ' ???'
2399
+ nameW, nameH = self.GetFullTextExtent(name)[0:2]
2400
+ x = xSt + w // 2
2401
+ staticLabelTop = (0, 50, 60)[self.drawSize]
2402
+ y = staticLabelTop - nameH * 3
2403
+ fullRect = wx.Rect(int(x - 20), int(y), int(nameW), int(nameH))
2404
+ # draw the rectangle, draw text on top:
2405
+ dc.DrawRectangle(
2406
+ int(xSt), int(yPosTop - nameH * 4), int(w), int(h + nameH * 5))
2407
+ dc.DrawText(name, int(x - nameW // 2), y)
2408
+ # update bounds to include time bar
2409
+ fullRect.Union(wx.Rect(int(xSt), int(yPosTop), int(w), int(h)))
2410
+ dc.SetIdBounds(id, fullRect)
2411
+
2412
+ def drawComponent(self, dc, component, yPos):
2413
+ """Draw the timing of one component on the timeline"""
2414
+ # set an id for the region of this component (so it
2415
+ # can act as a button). see if we created this already
2416
+
2417
+ yPos = int(yPos) # explicit type conversion
2418
+
2419
+ id = None
2420
+ for key in self.componentFromID:
2421
+ if self.componentFromID[key] == component:
2422
+ id = key
2423
+ if not id: # then create one and add to the dict
2424
+ id = wx.NewIdRef()
2425
+ self.componentFromID[id] = component
2426
+ dc.SetId(id)
2427
+
2428
+ iconYOffset = (6, 6, 0)[self.drawSize]
2429
+ # get default icon and bar color
2430
+ thisIcon = icons.ComponentIcon(component, size=self.iconSize).bitmap
2431
+ thisColor = colors.app['rt_comp']
2432
+ thisStyle = wx.BRUSHSTYLE_SOLID
2433
+
2434
+ # check True/False on ForceEndRoutine
2435
+ if 'forceEndRoutine' in component.params:
2436
+ if component.params['forceEndRoutine'].val:
2437
+ thisColor = colors.app['rt_comp_force']
2438
+ # check True/False on ForceEndRoutineOnPress
2439
+ if 'forceEndRoutineOnPress' in component.params:
2440
+ if component.params['forceEndRoutineOnPress'].val in ['any click', 'correct click', 'valid click']:
2441
+ thisColor = colors.app['rt_comp_force']
2442
+ # check True aliases on EndRoutineOn
2443
+ if 'endRoutineOn' in component.params:
2444
+ if component.params['endRoutineOn'].val in ['look at', 'look away']:
2445
+ thisColor = colors.app['rt_comp_force']
2446
+ # grey bar if comp is disabled
2447
+ if component.params['disabled'].val:
2448
+ thisIcon = thisIcon.ConvertToDisabled()
2449
+ thisColor = colors.app['rt_comp_disabled']
2450
+
2451
+ dc.DrawBitmap(thisIcon, int(self.iconXpos) + 6, int(yPos + iconYOffset), True)
2452
+ fullRect = wx.Rect(
2453
+ int(self.iconXpos),
2454
+ yPos,
2455
+ thisIcon.GetWidth(),
2456
+ thisIcon.GetHeight())
2457
+
2458
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2459
+
2460
+ name = component.params['name'].val
2461
+ # elipsize name if it's too long
2462
+ if self.GetFullTextExtent(name)[0] > self.rects['compLbls'].Width:
2463
+ name = name[:6] + "..." + name[-6:]
2464
+ # get size based on text
2465
+ w = self.rects['compLbls'].Width
2466
+ h = self.GetFullTextExtent(name)[1]
2467
+
2468
+ # draw text
2469
+ # + x position of icon (left side)
2470
+ # - half width of icon (including whitespace around it)
2471
+ # - FULL width of text
2472
+ # + slight adjustment for whitespace
2473
+ x = self.rects['compLbls'].Right - 6 - self.GetFullTextExtent(name)[0]
2474
+ _adjust = (5, 5, -2)[self.drawSize]
2475
+ y = yPos + thisIcon.GetHeight() // 2 - h // 2 + _adjust
2476
+ dc.DrawText(name, int(x), y)
2477
+ fullRect.Union(
2478
+ wx.Rect(int(x - 20), int(y), int(w), int(h)))
2479
+
2480
+ # deduce start and stop times if possible
2481
+ startTime, duration, nonSlipSafe = component.getStartAndDuration()
2482
+ # draw entries on timeline (if they have some time definition)
2483
+ if duration is not None:
2484
+ yOffset = (3.5, 3.5, 0.5)[self.drawSize]
2485
+ h = self.componentStep // (4, 3.25, 2.5)[self.drawSize]
2486
+ xScale = self.getSecsPerPixel()
2487
+ # then we can draw a sensible time bar!
2488
+ thisPen = wx.Pen(thisColor, style=wx.TRANSPARENT)
2489
+ thisBrush = wx.Brush(thisColor, style=thisStyle)
2490
+ dc.SetPen(thisPen)
2491
+ dc.SetBrush(thisBrush)
2492
+ # cap duration if routine has a max
2493
+ maxDur, useMax = self.routine.settings.getDuration()
2494
+ overspill = 0
2495
+ if useMax:
2496
+ overspill = max(duration - maxDur, 0)
2497
+ duration = min(maxDur, duration)
2498
+ # If there's a fixed end time and no start time, start 20px before 0
2499
+ if ('stopType' in component.params) and ('startType' in component.params) and (
2500
+ component.params['stopType'].val in ('time (s)', 'duration (s)')
2501
+ and component.params['startType'].val in ('time (s)')
2502
+ and startTime is None
2503
+ ):
2504
+ startTime = -20 * self.getSecsPerPixel()
2505
+ duration += 20 * self.getSecsPerPixel()
2506
+ # thisBrush.SetStyle(wx.BRUSHSTYLE_BDIAGONAL_HATCH)
2507
+ # dc.SetBrush(thisBrush)
2508
+
2509
+ if startTime is not None:
2510
+ xSt = self.timeXposStart + startTime // xScale
2511
+ w = duration // xScale + 1
2512
+ if w > 10000:
2513
+ w = 10000 # limit width to 10000 pixels!
2514
+ if w < 2:
2515
+ w = 2 # make sure at least one pixel shows
2516
+ dc.DrawRectangle(int(xSt), int(y + yOffset), int(w), int(h))
2517
+ # update bounds to include time bar
2518
+ fullRect.Union(wx.Rect(int(xSt), int(y + yOffset), int(w), int(h)))
2519
+ # draw greyed out bar for any overspill (if routine has a max dur)
2520
+ if useMax and overspill > 0:
2521
+ # use disabled color
2522
+ dc.SetBrush(
2523
+ wx.Brush(colors.app['rt_comp_disabled'], style=thisStyle)
2524
+ )
2525
+ dc.SetPen(
2526
+ wx.Pen(colors.app['rt_comp_disabled'], style=wx.TRANSPARENT)
2527
+ )
2528
+ # draw rest of bar
2529
+ w = overspill // xScale + 1
2530
+ if w > 10000:
2531
+ w = 10000 # limit width to 10000 pixels!
2532
+ if w < 2:
2533
+ w = 2 # make sure at least one pixel shows
2534
+ dc.DrawRectangle(self.timeXposEnd, int(y + yOffset), int(w), int(h))
2535
+ dc.SetIdBounds(id, fullRect)
2536
+
2537
+ def drawSettingsBtn(self, dc, component):
2538
+ # Setup ID
2539
+ id = None
2540
+ for key in self.componentFromID:
2541
+ if self.componentFromID[key] == component:
2542
+ id = key
2543
+ if not id: # then create one and add to the dict
2544
+ id = wx.NewIdRef()
2545
+ self.componentFromID[id] = component
2546
+ dc.SetId(id)
2547
+ # Get settings icon
2548
+ sz = int(self.iconSize/3)
2549
+ thisIcon = icons.ComponentIcon(component, size=sz).bitmap
2550
+ # Some parameters
2551
+ lbl = _translate("Routine settings")
2552
+ padding = 12
2553
+ # Set font
2554
+ fontSize = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT).GetPointSize()
2555
+ self.setFontSize(fontSize, dc)
2556
+ # Calculate extent
2557
+ extent = wx.Rect(
2558
+ x=self.rects['topBar'].Left,
2559
+ y=self.rects['topBar'].Top,
2560
+ width=padding + sz + 6 + self.GetTextExtent(lbl)[0] + padding,
2561
+ height=padding + sz + padding
2562
+ )
2563
+ extent = extent.CenterIn(self.rects['topBar'], dir=wx.VERTICAL)
2564
+ # Get content rect
2565
+ rect = wx.Rect(extent.TopLeft, extent.BottomRight)
2566
+ rect.Deflate(padding)
2567
+ # Draw rect
2568
+ dc.SetPen(wx.Pen(colors.app['panel_bg']))
2569
+ dc.SetBrush(wx.Brush(colors.app['tab_bg']))
2570
+ dc.DrawRoundedRectangle(extent, 6)
2571
+ # Draw button
2572
+ dc.SetTextForeground(
2573
+ wx.Colour(colors.app['text'])
2574
+ )
2575
+ dc.DrawLabel(
2576
+ lbl,
2577
+ image=thisIcon,
2578
+ rect=rect,
2579
+ alignment=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL
2580
+ )
2581
+ # Bind to ID bounds
2582
+ dc.SetIdBounds(id, extent)
2583
+
2584
+ return extent
2585
+
2586
+ def copyCompon(self, event=None, component=None):
2587
+ """This is easy - just take a copy of the component into memory
2588
+ """
2589
+ self.app.copiedCompon = component.copy()
2590
+
2591
+ def pasteCompon(self, event=None, component=None, index=None):
2592
+ # Alias None for component stored in app
2593
+ if component is None and self.app.copiedCompon:
2594
+ component = self.app.copiedCompon
2595
+ # Fail if nothing copied
2596
+ if component is None:
2597
+ return -1
2598
+ exp = self.frame.exp
2599
+ origName = component.params['name'].val
2600
+ defaultName = exp.namespace.makeValid(origName)
2601
+ msg = _translate('New name for copy of "%(copied)s"? [%(default)s]')
2602
+ vals = {'copied': origName, 'default': defaultName}
2603
+ message = msg % vals
2604
+ dlg = wx.TextEntryDialog(self, message=message,
2605
+ caption=_translate('Paste Component'))
2606
+ if dlg.ShowModal() == wx.ID_OK:
2607
+ # Get new name
2608
+ newName = dlg.GetValue()
2609
+ if not newName:
2610
+ newName = defaultName
2611
+ newName = exp.namespace.makeValid(newName)
2612
+ # Create copy of component with new references
2613
+ newCompon = component.copy(
2614
+ exp=exp,
2615
+ parentName=self.routine.name,
2616
+ name=newName
2617
+ )
2618
+ # Add to routine
2619
+ if index is None:
2620
+ self.routine.addComponent(newCompon)
2621
+ else:
2622
+ self.routine.insertComponent(index, newCompon)
2623
+ self.frame.exp.namespace.user.append(newName)
2624
+ # could do redrawRoutines but would be slower?
2625
+ self.redrawRoutine()
2626
+ self.frame.addToUndoStack("PASTE Component `%s`" % newName)
2627
+ dlg.Destroy()
2628
+
2629
+ def editComponentProperties(self, event=None, component=None, openToPage=None):
2630
+ # we got here from a wx.button press (rather than our own drawn icons)
2631
+ if event:
2632
+ componentName = event.EventObject.GetName()
2633
+ component = self.routine.getComponentFromName(componentName)
2634
+ # does this component have a help page?
2635
+ if hasattr(component, 'url'):
2636
+ helpUrl = component.url
2637
+ else:
2638
+ helpUrl = None
2639
+ old_name = component.params['name'].val
2640
+ old_disabled = component.params['disabled'].val
2641
+ # check current timing settings of component (if it changes we
2642
+ # need to update views)
2643
+ initialTimings = component.getStartAndDuration()
2644
+ if 'forceEndRoutine' in component.params \
2645
+ or 'forceEndRoutineOnPress' in component.params:
2646
+ # If component can force end routine, check if it did before
2647
+ initialForce = [component.params[key].val
2648
+ for key in ['forceEndRoutine', 'forceEndRoutineOnPress']
2649
+ if key in component.params]
2650
+ else:
2651
+ initialForce = False
2652
+ # create the dialog
2653
+ if hasattr(component, 'type') and component.type.lower() == 'code':
2654
+ _Dlg = DlgCodeComponentProperties
2655
+ else:
2656
+ _Dlg = DlgComponentProperties
2657
+ dlg = _Dlg(frame=self.frame,
2658
+ element=component,
2659
+ experiment=self.frame.exp, editing=True,
2660
+ openToPage=openToPage)
2661
+ if dlg.OK:
2662
+ # Redraw if force end routine has changed
2663
+ if any(key in component.params for key in ['forceEndRoutine', 'forceEndRoutineOnPress', 'endRoutineOn']):
2664
+ newForce = [component.params[key].val
2665
+ for key in ['forceEndRoutine', 'forceEndRoutineOnPress', 'endRoutineOn']
2666
+ if key in component.params]
2667
+ if initialForce != newForce:
2668
+ self.redrawRoutine() # need to refresh timings section
2669
+ self.Refresh() # then redraw visible
2670
+ self.frame.flowPanel.canvas.draw()
2671
+ # Redraw if timings have changed (or always, if comp was RoutineSettings)
2672
+ if (
2673
+ component.getStartAndDuration() != initialTimings
2674
+ or component.type == "RoutineSettingsComponent"
2675
+ ):
2676
+ self.redrawRoutine() # need to refresh timings section
2677
+ self.Refresh() # then redraw visible
2678
+ self.frame.flowPanel.canvas.draw()
2679
+ # self.frame.flowPanel.Refresh()
2680
+ elif component.name != old_name:
2681
+ if component == self.routine.settings:
2682
+ self.frame.flowPanel.canvas.draw()
2683
+ self.frame._doRenameRoutine(oldName=old_name, newName=component.name)
2684
+ self.redrawRoutine() # need to refresh name
2685
+ elif component.params['disabled'].val != old_disabled:
2686
+ self.redrawRoutine() # need to refresh color
2687
+ self.frame.exp.namespace.remove(old_name)
2688
+ self.frame.exp.namespace.add(component.params['name'].val)
2689
+ self.frame.addToUndoStack("EDIT `%s`" %
2690
+ component.params['name'].val)
2691
+
2692
+ def getSecsPerPixel(self):
2693
+ pixels = float(self.timeXposEnd - self.timeXposStart)
2694
+ return self.getMaxTime()[0] / pixels
2695
+
2696
+
2697
+ class StandaloneRoutineCanvas(scrolledpanel.ScrolledPanel):
2698
+ def __init__(self, parent, routine=None):
2699
+ # Init super
2700
+ scrolledpanel.ScrolledPanel.__init__(
2701
+ self, parent,
2702
+ style=wx.BORDER_NONE)
2703
+ # Store basics
2704
+ self.frame = parent.frame
2705
+ self.app = self.frame.app
2706
+ self.dpi = self.app.dpi
2707
+ self.routine = routine
2708
+ self.helpUrl = self.routine.url
2709
+ self.params = routine.params
2710
+ # Setup sizer
2711
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
2712
+ self.SetSizer(self.sizer)
2713
+ # Setup categ notebook
2714
+ self.ctrls = ParamNotebook(self, experiment=self.frame.exp, element=routine)
2715
+ self.paramCtrls = self.ctrls.paramCtrls
2716
+ self.sizer.Add(self.ctrls, border=12, proportion=1, flag=wx.ALIGN_CENTER | wx.TOP)
2717
+ # Make buttons
2718
+ self.btnsSizer = wx.BoxSizer(wx.HORIZONTAL)
2719
+ self.helpBtn = utils.HoverButton(self, id=wx.ID_HELP, label=_translate("Help"))
2720
+ self.helpBtn.Bind(wx.EVT_BUTTON, self.onHelp)
2721
+ self.btnsSizer.Add(self.helpBtn, border=6, flag=wx.ALL | wx.EXPAND)
2722
+ self.btnsSizer.AddStretchSpacer(1)
2723
+ # Add validator stuff
2724
+ self.warnings = WarningManager(self)
2725
+ self.sizer.Add(self.warnings.output, border=3, flag=wx.EXPAND | wx.ALL)
2726
+ # Add buttons to sizer
2727
+ self.sizer.Add(self.btnsSizer, border=3, proportion=0, flag=wx.EXPAND | wx.ALL)
2728
+ # Style
2729
+ self.SetupScrolling(scroll_y=True)
2730
+
2731
+ def _applyAppTheme(self):
2732
+ self.SetBackgroundColour(colors.app['tab_bg'])
2733
+ self.helpBtn._applyAppTheme()
2734
+ self.Refresh()
2735
+ self.Update()
2736
+
2737
+ def updateExperiment(self, evt=None):
2738
+ """Update this routine's saved parameters to what is currently entered"""
2739
+ # Get params in correct formats
2740
+ self.routine.params = self.ctrls.getParams()
2741
+ # Duplicate routine list and iterate through to find this one
2742
+ routines = self.frame.exp.routines.copy()
2743
+ for name, routine in routines.items():
2744
+ if routine == self.routine:
2745
+ # Update the routine dict keys to use the current name for this routine
2746
+ self.frame.exp.routines[self.routine.params['name'].val] = self.frame.exp.routines.pop(name)
2747
+ # Redraw the flow panel
2748
+ self.frame.flowPanel.canvas.draw()
2749
+ # Rename this page
2750
+ page = self.frame.routinePanel.GetPageIndex(self)
2751
+ self.frame.routinePanel.SetPageText(page, self.routine.params['name'].val)
2752
+ # Update save button
2753
+ self.frame.setIsModified(True)
2754
+
2755
+ def onHelp(self, event=None):
2756
+ """Uses self.app.followLink() to self.helpUrl
2757
+ """
2758
+ self.app.followLink(url=self.helpUrl)
2759
+
2760
+ def Validate(self, *args, **kwargs):
2761
+ return self.ctrls.Validate()
2762
+
2763
+
2764
+ class ComponentsPanel(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
2765
+ """Panel containing buttons for each component, sorted by category"""
2766
+
2767
+ class CategoryButton(wx.ToggleButton, handlers.ThemeMixin, HoverMixin):
2768
+ """Button to show/hide a category of components"""
2769
+ def __init__(self, parent, name, cat):
2770
+ if sys.platform == 'darwin':
2771
+ label = name # on macOS the wx.BU_LEFT flag has no effect
2772
+ else:
2773
+ label = " "+name
2774
+ # Initialise button
2775
+ wx.ToggleButton.__init__(self, parent,
2776
+ label=label, size=(-1, 24),
2777
+ style= wx.BORDER_NONE | wx.BU_LEFT)
2778
+ self.parent = parent
2779
+ # Link to category of buttons
2780
+ self.menu = self.parent.catSizers[cat]
2781
+ # # Set own sizer
2782
+ # self.sizer = wx.GridSizer(wx.HORIZONTAL)
2783
+ # self.SetSizer(self.sizer)
2784
+ # # Add icon
2785
+ # self.icon = wx.StaticText(parent=self, label="DOWN")
2786
+ # self.sizer.Add(self.icon, border=5, flag=wx.ALL | wx.ALIGN_RIGHT)
2787
+ # Default states to false
2788
+ self.state = False
2789
+ self.hover = False
2790
+ # Bind toggle function
2791
+ self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleMenu)
2792
+ # Bind hover functions
2793
+ self.SetupHover()
2794
+
2795
+ def ToggleMenu(self, event):
2796
+ # If triggered manually with a bool, treat that as a substitute for event selection
2797
+ if isinstance(event, bool):
2798
+ state = event
2799
+ else:
2800
+ state = event.GetSelection()
2801
+ # Set state
2802
+ self.SetValue(state)
2803
+ # Refresh view (which will show/hide according to this button's state)
2804
+ self.parent.refreshView()
2805
+ # Restyle
2806
+ self.OnHover()
2807
+
2808
+ def _applyAppTheme(self):
2809
+ """Apply app theme to this button"""
2810
+ self.OnHover()
2811
+
2812
+ class ComponentButton(wx.Button, handlers.ThemeMixin):
2813
+ """Button to open component parameters dialog"""
2814
+ def __init__(self, parent, name, comp, cat):
2815
+ self.parent = parent
2816
+ self.component = comp
2817
+ self.category = cat
2818
+ # construct label
2819
+ label = name
2820
+ # remove "Component" from the end
2821
+ for redundant in ['component', 'Component']:
2822
+ label = label.replace(redundant, "")
2823
+ # convert to title case
2824
+ label = st.CaseSwitcher.pascal2title(label)
2825
+ # wrap
2826
+ label = st.wrap(label, 10)
2827
+
2828
+ # Make button
2829
+ wx.Button.__init__(self, parent, wx.ID_ANY,
2830
+ label=label, name=name,
2831
+ size=(68, 68+12*label.count("\n")),
2832
+ style=wx.NO_BORDER)
2833
+ self.SetToolTip(wx.ToolTip(comp.tooltip or name))
2834
+ # Style
2835
+ self._applyAppTheme()
2836
+ # Bind to functions
2837
+ self.Bind(wx.EVT_BUTTON, self.onClick)
2838
+ self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)
2839
+
2840
+ @property
2841
+ def element(self):
2842
+ return self.component
2843
+
2844
+ def onClick(self, evt=None, timeout=None):
2845
+ """Called when a component button is clicked on.
2846
+ """
2847
+ routine = self.parent.frame.routinePanel.getCurrentRoutine()
2848
+ if routine is None:
2849
+ if timeout is not None: # just return, we're testing the UI
2850
+ return
2851
+ # Show a message telling the user there is no routine in the
2852
+ # experiment, making adding a component pointless until they do
2853
+ # so.
2854
+ dlg = wx.MessageDialog(
2855
+ self,
2856
+ _translate(
2857
+ "Cannot add component, experiment has no routines."),
2858
+ _translate("Error"),
2859
+ wx.OK | wx.ICON_ERROR | wx.CENTRE)
2860
+ dlg.ShowModal()
2861
+ dlg.Destroy()
2862
+ return
2863
+
2864
+ page = self.parent.frame.routinePanel.getCurrentPage()
2865
+ comp = self.component(
2866
+ parentName=routine.name,
2867
+ exp=self.parent.frame.exp)
2868
+
2869
+ # does this component have a help page?
2870
+ if hasattr(comp, 'url'):
2871
+ helpUrl = comp.url
2872
+ else:
2873
+ helpUrl = None
2874
+ # create component template
2875
+ if comp.type == 'Code':
2876
+ _Dlg = DlgCodeComponentProperties
2877
+ else:
2878
+ _Dlg = DlgComponentProperties
2879
+ dlg = _Dlg(frame=self.parent.frame,
2880
+ element=comp,
2881
+ experiment=self.parent.frame.exp,
2882
+ timeout=timeout)
2883
+
2884
+ if dlg.OK:
2885
+ # Add to the actual routine
2886
+ routine.addComponent(comp)
2887
+ namespace = self.parent.frame.exp.namespace
2888
+ desiredName = comp.params['name'].val
2889
+ name = comp.params['name'].val = namespace.makeValid(desiredName)
2890
+ namespace.add(name)
2891
+ # update the routine's view with the new component too
2892
+ page.redrawRoutine()
2893
+ self.parent.frame.addToUndoStack(
2894
+ "ADD `%s` to `%s`" % (name, routine.name))
2895
+ return True
2896
+
2897
+ def onRightClick(self, evt):
2898
+ """
2899
+ Defines rightclick behavior within builder view's
2900
+ components panel
2901
+ """
2902
+ # Get fave levels
2903
+ faveLevels = prefs.appDataCfg['builder']['favComponents']
2904
+ # Make menu
2905
+ menu = wx.Menu()
2906
+ if faveLevels[self.component.__name__] > ComponentsPanel.faveThreshold:
2907
+ # If is in favs
2908
+ msg = _translate("Remove from favorites")
2909
+ fun = self.removeFromFavorites
2910
+ else:
2911
+ # If is not in favs
2912
+ msg = _translate("Add to favorites")
2913
+ fun = self.addToFavorites
2914
+ btn = menu.Append(wx.ID_ANY, msg)
2915
+ menu.Bind(wx.EVT_MENU, fun, btn)
2916
+ # Show as popup
2917
+ self.PopupMenu(menu, evt.GetPosition())
2918
+ # Destroy to avoid mem leak
2919
+ menu.Destroy()
2920
+
2921
+ def addToFavorites(self, evt):
2922
+ self.parent.addToFavorites(self.component)
2923
+
2924
+ def removeFromFavorites(self, evt):
2925
+ self.parent.removeFromFavorites(self)
2926
+
2927
+ def _applyAppTheme(self):
2928
+ # Set colors
2929
+ self.SetForegroundColour(colors.app['text'])
2930
+ self.SetBackgroundColour(colors.app['panel_bg'])
2931
+ # Set bitmap
2932
+ icon = icons.ComponentIcon(self.component, size=48)
2933
+ if hasattr(self.component, "beta") and self.component.beta:
2934
+ icon = icon.beta
2935
+ else:
2936
+ icon = icon.bitmap
2937
+ self.SetBitmap(icon)
2938
+ self.SetBitmapCurrent(icon)
2939
+ self.SetBitmapPressed(icon)
2940
+ self.SetBitmapFocus(icon)
2941
+ self.SetBitmapPosition(wx.TOP)
2942
+ # Refresh
2943
+ self.Refresh()
2944
+
2945
+ class RoutineButton(wx.Button, handlers.ThemeMixin):
2946
+ """Button to open component parameters dialog"""
2947
+ def __init__(self, parent, name, rt, cat):
2948
+ self.parent = parent
2949
+ self.routine = rt
2950
+ self.category = cat
2951
+ # construct label
2952
+ label = name
2953
+ # remove "Routine" from the end
2954
+ for redundant in ['routine', 'Routine', "ButtonBox"]:
2955
+ label = label.replace(redundant, "")
2956
+ # convert to title case
2957
+ label = st.CaseSwitcher.pascal2title(label)
2958
+ # wrap
2959
+ label = st.wrap(label, 10)
2960
+ # Make button
2961
+ wx.Button.__init__(self, parent, wx.ID_ANY,
2962
+ label=label, name=name,
2963
+ size=(68, 68+12*label.count("\n")),
2964
+ style=wx.NO_BORDER)
2965
+ self.SetToolTip(wx.ToolTip(rt.tooltip or name))
2966
+ # Style
2967
+ self._applyAppTheme()
2968
+ # Bind to functions
2969
+ self.Bind(wx.EVT_BUTTON, self.onClick)
2970
+ self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)
2971
+
2972
+ @property
2973
+ def element(self):
2974
+ return self.routine
2975
+
2976
+ def onClick(self, evt=None, timeout=None):
2977
+ # Make a routine instance
2978
+ comp = self.routine(exp=self.parent.frame.exp)
2979
+ # Add to the actual routine
2980
+ exp = self.parent.frame.exp
2981
+ namespace = exp.namespace
2982
+ name = comp.params['name'].val = namespace.makeValid(
2983
+ comp.params['name'].val)
2984
+ namespace.add(name)
2985
+ exp.addStandaloneRoutine(name, comp)
2986
+ # update the routine's view with the new routine too
2987
+ self.parent.frame.addToUndoStack(
2988
+ "ADD `%s` to `%s`" % (name, exp.name))
2989
+ # Add a routine page
2990
+ notebook = self.parent.frame.routinePanel
2991
+ notebook.addRoutinePage(name, comp)
2992
+ notebook.setCurrentRoutine(comp)
2993
+
2994
+ def onRightClick(self, evt):
2995
+ """
2996
+ Defines rightclick behavior within builder view's
2997
+ routines panel
2998
+ """
2999
+ return
3000
+
3001
+ def addToFavorites(self, evt):
3002
+ self.parent.addToFavorites(self.routine)
3003
+
3004
+ def removeFromFavorites(self, evt):
3005
+ self.parent.removeFromFavorites(self)
3006
+
3007
+ def _applyAppTheme(self):
3008
+ # Set colors
3009
+ self.SetForegroundColour(colors.app['text'])
3010
+ self.SetBackgroundColour(colors.app['panel_bg'])
3011
+ # Set bitmap
3012
+ icon = icons.ComponentIcon(self.routine, size=48)
3013
+ if hasattr(self.routine, "beta") and self.routine.beta:
3014
+ icon = icon.beta
3015
+ else:
3016
+ icon = icon.bitmap
3017
+ self.SetBitmap(icon)
3018
+ self.SetBitmapCurrent(icon)
3019
+ self.SetBitmapPressed(icon)
3020
+ self.SetBitmapFocus(icon)
3021
+ self.SetBitmapPosition(wx.TOP)
3022
+ # Refresh
3023
+ self.Refresh()
3024
+
3025
+ class FilterDialog(wx.Dialog, handlers.ThemeMixin):
3026
+ def __init__(self, parent, size=(200, 300)):
3027
+ wx.Dialog.__init__(self, parent, size=size)
3028
+ self.parent = parent
3029
+ # Setup sizer
3030
+ self.border = wx.BoxSizer(wx.VERTICAL)
3031
+ self.SetSizer(self.border)
3032
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
3033
+ self.border.Add(self.sizer, border=6, proportion=1, flag=wx.ALL | wx.EXPAND)
3034
+ # Label
3035
+ self.label = wx.StaticText(self, label="Show components which \nwork with...")
3036
+ self.sizer.Add(self.label, border=6, flag=wx.ALL | wx.EXPAND)
3037
+ # Control
3038
+ self.viewCtrl = ToggleButtonArray(self,
3039
+ labels=("PsychoPy (local)", "PsychoJS (online)", "Both", "Any"),
3040
+ values=("PsychoPy", "PsychoJS", "Both", "Any"),
3041
+ multi=False, ori=wx.VERTICAL)
3042
+ self.viewCtrl.Bind(wx.EVT_CHOICE, self.onChange)
3043
+ self.sizer.Add(self.viewCtrl, border=6, flag=wx.ALL | wx.EXPAND)
3044
+ self.viewCtrl.SetValue(prefs.builder['componentFilter'])
3045
+ # OK
3046
+ self.OKbtn = wx.Button(self, id=wx.ID_OK, label=_translate("OK"))
3047
+ self.SetAffirmativeId(wx.ID_OK)
3048
+ self.border.Add(self.OKbtn, border=6, flag=wx.ALL | wx.ALIGN_RIGHT)
3049
+
3050
+ self.Layout()
3051
+ self._applyAppTheme()
3052
+
3053
+ def _applyAppTheme(self):
3054
+ self.SetBackgroundColour(colors.app['panel_bg'])
3055
+ self.label.SetForegroundColour(colors.app['text'])
3056
+
3057
+ def GetValue(self):
3058
+ return self.viewCtrl.GetValue()
3059
+
3060
+ def onChange(self, evt=None):
3061
+ self.parent.filter = prefs.builder['componentFilter'] = self.GetValue()
3062
+ prefs.saveUserPrefs()
3063
+ self.parent.refreshView()
3064
+
3065
+ faveThreshold = 20
3066
+
3067
+ def __init__(self, frame, id=-1):
3068
+ """A panel that displays available components.
3069
+ """
3070
+ self.frame = frame
3071
+ self.app = frame.app
3072
+ self.dpi = self.app.dpi
3073
+ self.prefs = self.app.prefs
3074
+ panelWidth = 3 * (68 + 12) + 12 + 12
3075
+ scrolledpanel.ScrolledPanel.__init__(self,
3076
+ frame,
3077
+ id,
3078
+ size=(panelWidth, 10 * self.dpi),
3079
+ style=wx.BORDER_NONE)
3080
+ # Get filter from prefs
3081
+ self.filter = prefs.builder['componentFilter']
3082
+ # Setup sizer
3083
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
3084
+ self.SetSizer(self.sizer)
3085
+ # Top bar
3086
+ self.topBarSizer = wx.BoxSizer(wx.HORIZONTAL)
3087
+ self.sizer.Add(self.topBarSizer, border=0, flag=wx.ALL | wx.EXPAND)
3088
+ # Add plugins button
3089
+ self.pluginBtn = wx.Button(self, label=_translate("Get more..."), style=wx.BU_EXACTFIT | wx.BORDER_NONE)
3090
+ self.pluginBtn.SetToolTip(_translate("Add new components and features via plugins."))
3091
+ self.topBarSizer.Add(self.pluginBtn, border=3, flag=wx.ALL)
3092
+ self.pluginBtn.Bind(wx.EVT_BUTTON, self.onPluginBtn)
3093
+ # Add filter button
3094
+ self.topBarSizer.AddStretchSpacer(1)
3095
+ self.filterBtn = wx.Button(self, style=wx.BU_EXACTFIT | wx.BORDER_NONE)
3096
+ self.filterBtn.SetToolTip(_translate("Filter components by whether they work with PsychoJS, PsychoPy or both."))
3097
+ self.topBarSizer.Add(self.filterBtn, border=3, flag=wx.ALL)
3098
+ self.filterBtn.Bind(wx.EVT_BUTTON, self.onFilterBtn)
3099
+
3100
+ # Attributes to store handles in
3101
+ self.catLabels = {}
3102
+ self.catSizers = {}
3103
+ self.compButtons = []
3104
+ self.rtButtons = []
3105
+ self.objectHandles = {}
3106
+ # Create buttons
3107
+ self.populate()
3108
+ # Apply filter
3109
+ self.refreshView()
3110
+ # Do sizing
3111
+ self.Fit()
3112
+ # double buffered better rendering except if retina
3113
+ self.SetDoubleBuffered(not self.frame.isRetina)
3114
+
3115
+ def getSortedElements(self):
3116
+ allElements = getAllElements()
3117
+ if "SettingsComponent" in allElements:
3118
+ del allElements['SettingsComponent']
3119
+
3120
+ # Create array to store elements in
3121
+ elements = OrderedDict()
3122
+ # Specify which categories are fixed to start/end
3123
+ firstCats = ['Favorites', 'Stimuli', 'Responses', 'Custom']
3124
+ lastCats = ['I/O', 'Other']
3125
+ # Add categories which are fixed to start
3126
+ for cat in firstCats:
3127
+ elements[cat] = {}
3128
+ # Add unfixed categories
3129
+ for cat in getAllCategories():
3130
+ if cat not in lastCats + firstCats:
3131
+ elements[cat] = {}
3132
+ # Add categories which are fixed to end
3133
+ for cat in lastCats:
3134
+ elements[cat] = {}
3135
+ # Get elements and sort by category
3136
+ for name, emt in allElements.items():
3137
+ for cat in emt.categories:
3138
+ if cat in elements:
3139
+ elements[cat][name] = emt
3140
+ # Assign favorites
3141
+ self.faveLevels = prefs.appDataCfg['builder']['favComponents']
3142
+ for name, emt in allElements.items():
3143
+ # Make sure element has a level
3144
+ if name not in self.faveLevels:
3145
+ self.faveLevels[name] = 0
3146
+ # If it exceeds the threshold, add to favorites
3147
+ if self.faveLevels[name] > self.faveThreshold:
3148
+ elements['Favorites'][name] = emt
3149
+ # Fill in gaps in favorites with defaults
3150
+ faveDefaults = [
3151
+ ('ImageComponent', allElements['ImageComponent']),
3152
+ ('KeyboardComponent', allElements['KeyboardComponent']),
3153
+ ('SoundComponent', allElements['SoundComponent']),
3154
+ ('TextComponent', allElements['TextComponent']),
3155
+ ('MouseComponent', allElements['MouseComponent']),
3156
+ ('SliderComponent', allElements['SliderComponent']),
3157
+ ]
3158
+ while len(elements['Favorites']) < 6:
3159
+ name, emt = faveDefaults.pop(0)
3160
+ if name not in elements['Favorites']:
3161
+ elements['Favorites'][name] = emt
3162
+ self.faveLevels[name] = self.faveThreshold + 1
3163
+
3164
+ return elements
3165
+
3166
+ def populate(self):
3167
+ """
3168
+ Find all component/standalone routine classes and create buttons for each, sorted by category.
3169
+
3170
+ This *can* be called multiple times - already existing buttons are simply detached from their sizer and
3171
+ reattached in the correct place given any changes since last called.
3172
+ """
3173
+ elements = self.getSortedElements()
3174
+
3175
+ # Detach any extant category labels and sizers from main sizer
3176
+ for cat in self.objectHandles:
3177
+ self.sizer.Detach(self.catLabels[cat])
3178
+ self.sizer.Detach(self.catSizers[cat])
3179
+ # Add each category
3180
+ for cat, emts in elements.items():
3181
+ if cat not in self.objectHandles:
3182
+ # Make category sizer
3183
+ self.catSizers[cat] = wx.WrapSizer(orient=wx.HORIZONTAL)
3184
+ # Make category button
3185
+ self.catLabels[cat] = self.CategoryButton(self, name=_translate(cat), cat=cat)
3186
+ # Store category reference
3187
+ self.objectHandles[cat] = {}
3188
+ # Add to sizer
3189
+ self.sizer.Add(self.catLabels[cat], border=3, flag=wx.BOTTOM | wx.EXPAND)
3190
+ self.sizer.Add(self.catSizers[cat], border=6, flag=wx.ALL | wx.ALIGN_CENTER)
3191
+
3192
+ # Detach any extant buttons from sizer
3193
+ for btn in self.objectHandles[cat].values():
3194
+ self.catSizers[cat].Detach(btn)
3195
+ # Add each element
3196
+ for name, emt in emts.items():
3197
+ if name not in self.objectHandles[cat]:
3198
+ # Make appropriate button
3199
+ if issubclass(emt, BaseStandaloneRoutine):
3200
+ emtBtn = self.RoutineButton(self, name=name, rt=emt, cat=cat)
3201
+ self.rtButtons.append(emtBtn)
3202
+ else:
3203
+ emtBtn = self.ComponentButton(self, name=name, comp=emt, cat=cat)
3204
+ self.compButtons.append(emtBtn)
3205
+ # If we're in standalone routine view, disable new component button
3206
+ rtPage = self.frame.routinePanel.getCurrentPage()
3207
+ if rtPage:
3208
+ emtBtn.Enable(
3209
+ not isinstance(rtPage.routine, BaseStandaloneRoutine)
3210
+ )
3211
+ # Store reference by category
3212
+ self.objectHandles[cat][name] = emtBtn
3213
+ # Add to category sizer
3214
+ self.catSizers[cat].Add(self.objectHandles[cat][name], border=3, flag=wx.ALL)
3215
+
3216
+ # Show favourites on startup
3217
+ self.catLabels['Favorites'].ToggleMenu(True)
3218
+
3219
+ def refreshView(self):
3220
+ # Get view value(s)
3221
+ if prefs.builder['componentFilter'] == "Both":
3222
+ view = ["PsychoPy", "PsychoJS"]
3223
+ elif prefs.builder['componentFilter'] == "Any":
3224
+ view = []
3225
+ else:
3226
+ view = [prefs.builder['componentFilter']]
3227
+
3228
+ # Iterate through categories and buttons
3229
+ for cat in self.objectHandles:
3230
+ anyShown = False
3231
+ for name, btn in self.objectHandles[cat].items():
3232
+ shown = True
3233
+ # Get element button refers to
3234
+ if isinstance(btn, self.ComponentButton):
3235
+ emt = btn.component
3236
+ elif isinstance(btn, self.RoutineButton):
3237
+ emt = btn.routine
3238
+ else:
3239
+ emt = None
3240
+ # Check whether button is hidden by filter
3241
+ for v in view:
3242
+ if v not in btn.element.targets:
3243
+ shown = False
3244
+ # Check whether button is hidden by prefs
3245
+ if name in prefs.builder['hiddenComponents'] + alwaysHidden:
3246
+ shown = False
3247
+ # Check whether button refers to a future comp/rt
3248
+ if hasattr(emt, "version"):
3249
+ ver = parseVersionSafely(emt.version)
3250
+ if ver > psychopyVersion:
3251
+ shown = False
3252
+ # Show/hide button
3253
+ btn.Show(shown)
3254
+ # Count state towards category
3255
+ anyShown = anyShown or shown
3256
+ # Only show category button if there are some buttons
3257
+ self.catLabels[cat].Show(anyShown)
3258
+ # If comp button is set to hide, hide all regardless
3259
+ if not self.catLabels[cat].GetValue():
3260
+ self.catSizers[cat].ShowItems(False)
3261
+
3262
+ # Do sizing
3263
+ self.Layout()
3264
+ self.SetupScrolling(scrollToTop=False)
3265
+
3266
+ def _applyAppTheme(self, target=None):
3267
+ # Style component panel
3268
+ self.SetForegroundColour(colors.app['text'])
3269
+ self.SetBackgroundColour(colors.app['panel_bg'])
3270
+ # Style category labels
3271
+ for lbl in self.catLabels:
3272
+ self.catLabels[lbl].SetForegroundColour(colors.app['text'])
3273
+ # Style filter button
3274
+ self.filterBtn.SetBackgroundColour(colors.app['panel_bg'])
3275
+ icon = icons.ButtonIcon("filter", size=16).bitmap
3276
+ self.filterBtn.SetBitmap(icon)
3277
+ self.filterBtn.SetBitmapCurrent(icon)
3278
+ self.filterBtn.SetBitmapPressed(icon)
3279
+ self.filterBtn.SetBitmapFocus(icon)
3280
+ # Style plugin button
3281
+ self.pluginBtn.SetBackgroundColour(colors.app['panel_bg'])
3282
+ self.pluginBtn.SetForegroundColour(colors.app['text'])
3283
+ icon = icons.ButtonIcon("plus", size=16).bitmap
3284
+ self.pluginBtn.SetBitmap(icon)
3285
+ self.pluginBtn.SetBitmapCurrent(icon)
3286
+ self.pluginBtn.SetBitmapPressed(icon)
3287
+ self.pluginBtn.SetBitmapFocus(icon)
3288
+
3289
+ def addToFavorites(self, comp):
3290
+ name = comp.__name__
3291
+ # Mark component as a favorite
3292
+ self.faveLevels[name] = self.faveThreshold + 1
3293
+ # Repopulate
3294
+ self.populate()
3295
+ # Do sizing
3296
+ self.Layout()
3297
+
3298
+ def removeFromFavorites(self, button):
3299
+ comp = button.component
3300
+ name = comp.__name__
3301
+ # Unmark component as favorite
3302
+ self.faveLevels[name] = 0
3303
+ # Remove button from favorites menu
3304
+ button.Destroy()
3305
+ del self.objectHandles["Favorites"][name]
3306
+ # Do sizing
3307
+ self.Layout()
3308
+
3309
+ def enableComponents(self, enable=True):
3310
+ for button in self.compButtons:
3311
+ button.Enable(enable)
3312
+ self.Update()
3313
+
3314
+ def onFilterBtn(self, evt=None):
3315
+ dlg = self.FilterDialog(self)
3316
+ dlg.ShowModal()
3317
+
3318
+ def onPluginBtn(self, evt=None):
3319
+ dlg = psychopy.app.plugin_manager.dialog.EnvironmentManagerDlg(self)
3320
+ dlg.Show()
3321
+ # Do post-close checks
3322
+ dlg.onClose()
3323
+
3324
+
3325
+ class ReadmeFrame(wx.Frame, handlers.ThemeMixin):
3326
+ """Defines construction of the Readme Frame"""
3327
+
3328
+ def __init__(self, parent, filename=None):
3329
+ """
3330
+ A frame for presenting/loading/saving readme files
3331
+ """
3332
+ self.parent = parent
3333
+ try:
3334
+ title = "%s readme" % (parent.exp.name)
3335
+ except AttributeError:
3336
+ title = "readme"
3337
+ self._fileLastModTime = None
3338
+ pos = wx.Point(parent.Position[0] + 80, parent.Position[1] + 80)
3339
+ _style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT
3340
+ wx.Frame.__init__(self, parent, title=title,
3341
+ size=(600, 500), pos=pos, style=_style)
3342
+ # Setup sizer
3343
+ self.sizer = wx.BoxSizer()
3344
+ self.SetSizer(self.sizer)
3345
+
3346
+ self.Bind(wx.EVT_CLOSE, self.onClose)
3347
+ self.Hide()
3348
+ # create icon
3349
+ if sys.platform == 'darwin':
3350
+ pass # doesn't work and not necessary - handled by app bundle
3351
+ else:
3352
+ iconFile = os.path.join(parent.paths['resources'], 'coder.ico')
3353
+ if os.path.isfile(iconFile):
3354
+ self.SetIcon(wx.Icon(iconFile, wx.BITMAP_TYPE_ICO))
3355
+ self.ctrl = utils.MarkdownCtrl(self, file=filename, style=wx.BOTTOM)
3356
+ self.sizer.Add(self.ctrl, border=6, proportion=1, flag=wx.ALL | wx.EXPAND)
3357
+
3358
+ def show(self, value=True):
3359
+ self.Show(value)
3360
+ if value:
3361
+ self._applyAppTheme()
3362
+
3363
+ def _applyAppTheme(self):
3364
+ from psychopy.app.themes import fonts
3365
+ self.SetBackgroundColour(fonts.coderTheme.base.backColor)
3366
+ self.ctrl.SetBackgroundColour(fonts.coderTheme.base.backColor)
3367
+ self.Update()
3368
+ self.Refresh()
3369
+
3370
+ def onClose(self, evt=None):
3371
+ """
3372
+ Defines behavior on close of the Readme Frame
3373
+ """
3374
+ self.parent.readmeFrame = None
3375
+ self.Destroy()
3376
+
3377
+ def makeMenus(self):
3378
+ """Produces menus for the Readme Frame"""
3379
+
3380
+ # ---Menus---#000000#FFFFFF-------------------------------------------
3381
+ menuBar = wx.MenuBar()
3382
+ # ---_file---#000000#FFFFFF-------------------------------------------
3383
+ self.fileMenu = wx.Menu()
3384
+ menuBar.Append(self.fileMenu, _translate('&File'))
3385
+ menu = self.fileMenu
3386
+ keys = self.parent.app.keys
3387
+ menu.Append(wx.ID_EDIT, _translate("Edit"))
3388
+ self.Bind(wx.EVT_MENU, self.fileEdit, id=wx.ID_EDIT)
3389
+ menu.Append(wx.ID_CLOSE,
3390
+ _translate("&Close readme\t%s") % keys['close'])
3391
+ item = self.Bind(wx.EVT_MENU, self.toggleVisible, id=wx.ID_CLOSE)
3392
+ item = menu.Append(-1,
3393
+ _translate("&Toggle readme\t%s") % keys[
3394
+ 'toggleReadme'],
3395
+ _translate("Toggle Readme"))
3396
+ self.Bind(wx.EVT_MENU, self.toggleVisible, item)
3397
+ self.SetMenuBar(menuBar)
3398
+
3399
+ def setFile(self, filename):
3400
+ """Sets the readme file found with current builder experiment"""
3401
+ self.filename = self.ctrl.file = filename
3402
+ self.expName = self.parent.exp.getExpName()
3403
+ if not self.expName:
3404
+ self.expName = "untitled"
3405
+ # check we can read
3406
+ if filename is None: # check if we can write to the directory
3407
+ return False
3408
+ # Path-ise file
3409
+ filename = Path(filename)
3410
+ if not filename.is_file():
3411
+ filename.write_text("")
3412
+ self.filename = filename
3413
+ return False
3414
+ elif not os.access(filename, os.R_OK):
3415
+ msg = "Found readme file (%s) no read permissions"
3416
+ logging.warning(msg % filename)
3417
+ return False
3418
+ # attempt to open
3419
+ try:
3420
+ f = codecs.open(filename, 'r', 'utf-8-sig')
3421
+ except IOError as err:
3422
+ msg = ("Found readme file for %s and appear to have"
3423
+ " permissions, but can't open")
3424
+ logging.warning(msg % self.expName)
3425
+ logging.warning(err)
3426
+ return False
3427
+ # attempt to read
3428
+ try:
3429
+ readmeText = f.read().replace("\r\n", "\n")
3430
+ except Exception:
3431
+ msg = ("Opened readme file for %s it but failed to read it "
3432
+ "(not text/unicode?)")
3433
+ logging.error(msg % self.expName)
3434
+ return False
3435
+ f.close()
3436
+ self._fileLastModTime = os.path.getmtime(filename)
3437
+ self.ctrl.setValue(readmeText)
3438
+ self.SetTitle("readme (%s)" % self.expName)
3439
+
3440
+ def refresh(self, evt=None):
3441
+ if hasattr(self, 'filename'):
3442
+ self.setFile(self.filename)
3443
+
3444
+ def fileEdit(self, evt=None):
3445
+ self.parent.app.showCoder()
3446
+ coder = self.parent.app.coder
3447
+ if not self.filename:
3448
+ self.parent.updateReadme()
3449
+ coder.fileOpen(filename=self.filename)
3450
+ # Close README window
3451
+ self.Close()
3452
+
3453
+ def fileSave(self, evt=None):
3454
+ """Defines save behavior for readme frame"""
3455
+ mtime = os.path.getmtime(self.filename)
3456
+ if self._fileLastModTime and mtime > self._fileLastModTime:
3457
+ logging.warning(
3458
+ 'readme file has been changed by another program?')
3459
+ txt = self.rawText
3460
+ with codecs.open(self.filename, 'w', 'utf-8-sig') as f:
3461
+ f.write(txt)
3462
+
3463
+ def toggleVisible(self, evt=None):
3464
+ """Defines visibility toggle for readme frame"""
3465
+ if self.IsShown():
3466
+ self.Hide()
3467
+ else:
3468
+ self.Show()
3469
+
3470
+
3471
+ class FlowPanel(wx.Panel, handlers.ThemeMixin):
3472
+ def __init__(self, frame, id=-1):
3473
+ wx.Panel.__init__(self, parent=frame)
3474
+ # setup sizer
3475
+ self.sizer = wx.BoxSizer(wx.HORIZONTAL)
3476
+ self.SetSizer(self.sizer)
3477
+ # buttons panel
3478
+ self.btnPanel = wx.Panel(self)
3479
+ self.btnPanel.sizer = wx.BoxSizer(wx.VERTICAL)
3480
+ self.btnPanel.SetSizer(self.btnPanel.sizer)
3481
+ self.sizer.Add(self.btnPanel, border=6, flag=wx.EXPAND | wx.ALL)
3482
+ # canvas
3483
+ self.canvas = FlowCanvas(parent=self, frame=frame)
3484
+ self.sizer.Add(self.canvas, border=0, proportion=1, flag=wx.EXPAND | wx.ALL)
3485
+ # add routine button
3486
+ self.btnInsertRoutine = self.canvas.btnInsertRoutine = HoverButton(
3487
+ self.btnPanel, -1, _translate('Insert Routine'), size=(120, 50),
3488
+ style=wx.BORDER_NONE
3489
+ )
3490
+ self.btnInsertRoutine.Bind(wx.EVT_BUTTON, self.canvas.onInsertRoutine)
3491
+ self.btnPanel.sizer.Add(self.btnInsertRoutine, border=6, flag=wx.EXPAND | wx.ALL)
3492
+ # add loop button
3493
+ self.btnInsertLoop = self.canvas.btnInsertLoop = HoverButton(
3494
+ self.btnPanel, -1, _translate('Insert Loop'), size=(120, 50),
3495
+ style=wx.BORDER_NONE
3496
+ )
3497
+ self.btnInsertLoop.Bind(wx.EVT_BUTTON, self.canvas.setLoopPoint1)
3498
+ self.btnPanel.sizer.Add(self.btnInsertLoop, border=6, flag=wx.EXPAND | wx.ALL)
3499
+ # align buttons to top
3500
+ self.btnPanel.sizer.AddStretchSpacer(1)
3501
+
3502
+ self.Layout()
3503
+
3504
+ def _applyAppTheme(self):
3505
+ self.SetBackgroundColour(colors.app['panel_bg'])
3506
+ self.btnPanel.SetBackgroundColour(colors.app['panel_bg'])
3507
+
3508
+ self.Refresh()
3509
+
3510
+
3511
+ class FlowCanvas(wx.ScrolledWindow, handlers.ThemeMixin):
3512
+
3513
+ def __init__(self, parent, frame, id=-1):
3514
+ """A panel that shows how the routines will fit together
3515
+ """
3516
+ self.frame = frame
3517
+ self.parent = parent
3518
+ self.app = frame.app
3519
+ self.dpi = self.app.dpi
3520
+ wx.ScrolledWindow.__init__(self, parent, id,
3521
+ style=wx.HSCROLL | wx.VSCROLL | wx.BORDER_NONE)
3522
+ self.needUpdate = True
3523
+ self.maxWidth = 50 * self.dpi
3524
+ self.maxHeight = 2 * self.dpi
3525
+ self.mousePos = None
3526
+ # if we're adding a loop or routine then add spots to timeline
3527
+ # self.drawNearestRoutinePoint = True
3528
+ # self.drawNearestLoopPoint = False
3529
+ # lists the x-vals of points to draw, eg loop locations:
3530
+ self.pointsToDraw = []
3531
+ # for flowSize, showLoopInfoInFlow:
3532
+ self.appData = self.app.prefs.appData
3533
+
3534
+ # self.SetAutoLayout(True)
3535
+ self.SetScrollRate(self.dpi // 16, self.dpi // 16)
3536
+
3537
+ # create a PseudoDC to record our drawing
3538
+ self.pdc = PseudoDC()
3539
+ if parse_version(wx.__version__) < parse_version('4.0.0a1'):
3540
+ self.pdc.DrawRoundedRectangle = self.pdc.DrawRoundedRectangleRect
3541
+ self.pen_cache = {}
3542
+ self.brush_cache = {}
3543
+ # vars for handling mouse clicks
3544
+ self.hitradius = 5
3545
+ self.dragid = -1
3546
+ self.entryPointPosList = []
3547
+ self.entryPointIDlist = []
3548
+ self.gapsExcluded = []
3549
+ # mode can also be 'loopPoint1','loopPoint2','routinePoint'
3550
+ self.mode = 'normal'
3551
+ self.insertingRoutine = ""
3552
+
3553
+ # for the context menu use the ID of the drawn icon to retrieve
3554
+ # the component (loop or routine)
3555
+ self.componentFromID = {}
3556
+ self.contextMenuLabels = {
3557
+ 'remove': _translate('remove'),
3558
+ 'rename': _translate('rename')}
3559
+ self.contextMenuItems = ['remove', 'rename']
3560
+ self.contextItemFromID = {}
3561
+ self.contextIDFromItem = {}
3562
+ for item in self.contextMenuItems:
3563
+ id = wx.NewIdRef()
3564
+ self.contextItemFromID[id] = item
3565
+ self.contextIDFromItem[item] = id
3566
+
3567
+ # self.btnInsertRoutine = wx.Button(self,-1,
3568
+ # 'Insert Routine', pos=(10,10))
3569
+ # self.btnInsertLoop = wx.Button(self,-1,'Insert Loop', pos=(10,30))
3570
+
3571
+ # use self.appData['flowSize'] to index a tuple to get a specific
3572
+ # value, eg: (4,6,8)[self.appData['flowSize']]
3573
+ self.flowMaxSize = 2 # upper limit on increaseSize
3574
+
3575
+ # bind events
3576
+ self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
3577
+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
3578
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
3579
+
3580
+ idClear = wx.NewIdRef()
3581
+ self.Bind(wx.EVT_MENU, self.clearMode, id=idClear)
3582
+ aTable = wx.AcceleratorTable([
3583
+ (wx.ACCEL_NORMAL, wx.WXK_ESCAPE, idClear)
3584
+ ])
3585
+ self.SetAcceleratorTable(aTable)
3586
+
3587
+ # double buffered better rendering except if retina
3588
+ self.SetDoubleBuffered(not self.frame.isRetina)
3589
+
3590
+ def _applyAppTheme(self, target=None):
3591
+ """Apply any changes which have been made to the theme since panel was last loaded"""
3592
+ # Set background
3593
+ self.SetBackgroundColour(colors.app['panel_bg'])
3594
+
3595
+ self.draw()
3596
+
3597
+ def clearMode(self, event=None):
3598
+ """If we were in middle of doing something (like inserting routine)
3599
+ then end it, allowing user to cancel
3600
+ """
3601
+ self.mode = 'normal'
3602
+ self.insertingRoutine = None
3603
+ for id in self.entryPointIDlist:
3604
+ self.pdc.RemoveId(id)
3605
+ self.entryPointPosList = []
3606
+ self.entryPointIDlist = []
3607
+ self.gapsExcluded = []
3608
+ self.draw()
3609
+ self.frame.SetStatusText("")
3610
+ self.btnInsertRoutine.SetLabel(_translate('Insert Routine'))
3611
+ self.btnInsertRoutine.Update()
3612
+ self.btnInsertLoop.SetLabel(_translate('Insert Loop'))
3613
+ self.btnInsertRoutine.Update()
3614
+
3615
+ def ConvertEventCoords(self, event):
3616
+ xView, yView = self.GetViewStart()
3617
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
3618
+ return (event.GetX() + (xView * xDelta),
3619
+ event.GetY() + (yView * yDelta))
3620
+
3621
+ def OffsetRect(self, r):
3622
+ """Offset the rectangle, r, to appear in the given position
3623
+ in the window
3624
+ """
3625
+ xView, yView = self.GetViewStart()
3626
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
3627
+ r.Offset((-(xView * xDelta), -(yView * yDelta)))
3628
+
3629
+ def onInsertRoutine(self, evt):
3630
+ """For when the insert Routine button is pressed - bring up
3631
+ dialog and present insertion point on flow line.
3632
+ see self.insertRoutine() for further info
3633
+ """
3634
+ if self.mode.startswith('loopPoint'):
3635
+ self.clearMode()
3636
+ elif self.mode == 'routine':
3637
+ # clicked again with label now being "Cancel..."
3638
+ self.clearMode()
3639
+ return
3640
+ self.frame.SetStatusText(_translate(
3641
+ "Select a Routine to insert (Esc to exit)"))
3642
+ menu = wx.Menu()
3643
+ self.routinesFromID = {}
3644
+ id = wx.NewIdRef()
3645
+ menu.Append(id, '(new)')
3646
+ self.routinesFromID[id] = '(new)'
3647
+ menu.Bind(wx.EVT_MENU, self.insertNewRoutine, id=id)
3648
+ flow = self.frame.exp.flow
3649
+ for name, routine in self.frame.exp.routines.items():
3650
+ id = wx.NewIdRef()
3651
+ item = menu.Append(id, name)
3652
+ # Enable / disable each routine's button according to limits
3653
+ if hasattr(routine, "limit"):
3654
+ limitProgress = 0
3655
+ for rt in flow:
3656
+ limitProgress += int(isinstance(rt, type(routine)))
3657
+ item.Enable(limitProgress < routine.limit or routine in flow)
3658
+ self.routinesFromID[id] = name
3659
+ menu.Bind(wx.EVT_MENU, self.onInsertRoutineSelect, id=id)
3660
+ self.PopupMenu(menu)
3661
+ menu.Bind(wx.EVT_MENU_CLOSE, self.clearMode)
3662
+ menu.Destroy() # destroy to avoid mem leak
3663
+
3664
+ def insertNewRoutine(self, event):
3665
+ """selecting (new) is a short-cut for:
3666
+ make new routine, insert it into the flow
3667
+ """
3668
+ newRoutine = self.frame.routinePanel.createNewRoutine()
3669
+ if newRoutine:
3670
+ self.routinesFromID[event.GetId()] = newRoutine
3671
+ self.onInsertRoutineSelect(event)
3672
+ else:
3673
+ self.clearMode()
3674
+
3675
+ def onInsertRoutineSelect(self, event):
3676
+ """User has selected a routine to be entered so bring up the
3677
+ entrypoint marker and await mouse button press.
3678
+ see self.insertRoutine() for further info
3679
+ """
3680
+ self.mode = 'routine'
3681
+ self.btnInsertRoutine.SetLabel(_translate('CANCEL Insert'))
3682
+ self.frame.SetStatusText(_translate(
3683
+ 'Click where you want to insert the Routine, or CANCEL insert.'))
3684
+ self.insertingRoutine = self.routinesFromID[event.GetId()]
3685
+ x = self.getNearestGapPoint(0)
3686
+ self.drawEntryPoints([x])
3687
+
3688
+ def insertRoutine(self, ii):
3689
+ """Insert a routine into the Flow knowing its name and location
3690
+
3691
+ onInsertRoutine() the button has been pressed so present menu
3692
+ onInsertRoutineSelect() user selected the name so present entry points
3693
+ OnMouse() user has selected a point on the timeline to insert entry
3694
+
3695
+ """
3696
+ rtn = self.frame.exp.routines[self.insertingRoutine]
3697
+ self.frame.exp.flow.addRoutine(rtn, ii)
3698
+ self.frame.addToUndoStack("ADD Routine `%s`" % rtn.name)
3699
+ # reset flow drawing (remove entry point)
3700
+ self.clearMode()
3701
+ # enable/disable add loop button
3702
+ self.btnInsertLoop.Enable(bool(len(self.frame.exp.flow)))
3703
+
3704
+ def setLoopPoint1(self, evt=None):
3705
+ """Someone pushed the insert loop button.
3706
+ Fetch the dialog
3707
+ """
3708
+ if self.mode == 'routine':
3709
+ self.clearMode()
3710
+ # clicked again, label is "Cancel..."
3711
+ elif self.mode.startswith('loopPoint'):
3712
+ self.clearMode()
3713
+ return
3714
+ self.btnInsertLoop.SetLabel(_translate('CANCEL insert'))
3715
+ self.mode = 'loopPoint1'
3716
+ self.frame.SetStatusText(_translate(
3717
+ 'Click where you want the loop to start/end, or CANCEL insert.'))
3718
+ x = self.getNearestGapPoint(0)
3719
+ self.drawEntryPoints([x])
3720
+
3721
+ def setLoopPoint2(self, evt=None):
3722
+ """We have the location of the first point, waiting to get the second
3723
+ """
3724
+ self.mode = 'loopPoint2'
3725
+ self.frame.SetStatusText(_translate(
3726
+ 'Click the other end for the loop'))
3727
+ thisPos = self.entryPointPosList[0]
3728
+ self.gapsExcluded = [thisPos]
3729
+ self.gapsExcluded.extend(self.getGapPointsCrossingStreams(thisPos))
3730
+ # is there more than one available point
3731
+ diff = wx.GetMousePosition()[0] - self.GetScreenPosition()[0]
3732
+ x = self.getNearestGapPoint(diff, exclude=self.gapsExcluded)
3733
+ self.drawEntryPoints([self.entryPointPosList[0], x])
3734
+ nAvailableGaps = len(self.gapMidPoints) - len(self.gapsExcluded)
3735
+ if nAvailableGaps == 1:
3736
+ self.insertLoop() # there's only one place - use it
3737
+
3738
+ def insertLoop(self, evt=None):
3739
+ # bring up listbox to choose the routine to add, and / or a new one
3740
+ loopDlg = DlgLoopProperties(frame=self.frame,
3741
+ helpUrl=self.app.urls['builder.loops'])
3742
+ startII = self.gapMidPoints.index(min(self.entryPointPosList))
3743
+ endII = self.gapMidPoints.index(max(self.entryPointPosList))
3744
+ if loopDlg.OK:
3745
+ handler = loopDlg.currentHandler
3746
+ self.frame.exp.flow.addLoop(handler,
3747
+ startPos=startII, endPos=endII)
3748
+ action = "ADD Loop `%s` to Flow" % handler.params['name'].val
3749
+ self.frame.addToUndoStack(action)
3750
+ self.clearMode()
3751
+ self.draw()
3752
+
3753
+ def increaseSize(self, event=None):
3754
+ if self.appData['flowSize'] == self.flowMaxSize:
3755
+ self.appData['showLoopInfoInFlow'] = True
3756
+ self.appData['flowSize'] = min(
3757
+ self.flowMaxSize, self.appData['flowSize'] + 1)
3758
+ self.clearMode() # redraws
3759
+
3760
+ def decreaseSize(self, event=None):
3761
+ if self.appData['flowSize'] == 0:
3762
+ self.appData['showLoopInfoInFlow'] = False
3763
+ self.appData['flowSize'] = max(0, self.appData['flowSize'] - 1)
3764
+ self.clearMode() # redraws
3765
+
3766
+ def editLoopProperties(self, event=None, loop=None):
3767
+ # add routine points to the timeline
3768
+ self.setDrawPoints('loops')
3769
+ self.draw()
3770
+ if 'conditions' in loop.params:
3771
+ condOrig = loop.params['conditions'].val
3772
+ condFileOrig = loop.params['conditionsFile'].val
3773
+ title = loop.params['name'].val + ' Properties'
3774
+ loopDlg = DlgLoopProperties(frame=self.frame,
3775
+ helpUrl=self.app.urls['builder.loops'],
3776
+ title=title, loop=loop)
3777
+ if loopDlg.OK:
3778
+ prevLoop = loop
3779
+ if loopDlg.params['loopType'].val == 'staircase':
3780
+ loop = loopDlg.stairHandler
3781
+ elif loopDlg.params['loopType'].val == 'interleaved staircases':
3782
+ loop = loopDlg.multiStairHandler
3783
+ else:
3784
+ # ['random','sequential', 'fullRandom', ]
3785
+ loop = loopDlg.trialHandler
3786
+ # if the loop is a whole new class then we can't just update the
3787
+ # params
3788
+ if loop.getType() != prevLoop.getType():
3789
+ # get indices for start and stop points of prev loop
3790
+ flow = self.frame.exp.flow
3791
+ # find the index of the initiator
3792
+ startII = flow.index(prevLoop.initiator)
3793
+ # minus one because initiator will have been deleted
3794
+ endII = flow.index(prevLoop.terminator) - 1
3795
+ # remove old loop completely
3796
+ flow.removeComponent(prevLoop)
3797
+ # finally insert the new loop
3798
+ flow.addLoop(loop, startII, endII)
3799
+ self.frame.addToUndoStack("EDIT Loop `%s`" %
3800
+ (loop.params['name'].val))
3801
+ elif 'conditions' in loop.params:
3802
+ loop.params['conditions'].val = condOrig
3803
+ loop.params['conditionsFile'].val = condFileOrig
3804
+ # remove the points from the timeline
3805
+ self.setDrawPoints(None)
3806
+ self.draw()
3807
+
3808
+ def OnMouse(self, event):
3809
+ x, y = self.ConvertEventCoords(event)
3810
+ handlerTypes = ('StairHandler', 'TrialHandler', 'MultiStairHandler')
3811
+ if self.mode == 'normal':
3812
+ if event.LeftDown():
3813
+ icons = self.pdc.FindObjectsByBBox(x, y)
3814
+ for thisIcon in icons:
3815
+ # might intersect several and only one has a callback
3816
+ if thisIcon in self.componentFromID:
3817
+ comp = self.componentFromID[thisIcon]
3818
+ if comp.getType() in handlerTypes:
3819
+ self.editLoopProperties(loop=comp)
3820
+ if comp.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
3821
+ self.frame.routinePanel.setCurrentRoutine(
3822
+ routine=comp)
3823
+ elif event.RightDown():
3824
+ icons = self.pdc.FindObjectsByBBox(x, y)
3825
+ # todo: clean-up remove `comp`, its unused
3826
+ comp = None
3827
+ for thisIcon in icons:
3828
+ # might intersect several and only one has a callback
3829
+ if thisIcon in self.componentFromID:
3830
+ # loop through comps looking for Routine, or a Loop if
3831
+ # no routine
3832
+ thisComp = self.componentFromID[thisIcon]
3833
+ if thisComp.getType() in handlerTypes:
3834
+ comp = thisComp # unused
3835
+ icon = thisIcon
3836
+ if thisComp.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
3837
+ comp = thisComp
3838
+ icon = thisIcon
3839
+ break # we've found a Routine so stop looking
3840
+ self.frame.routinePanel.setCurrentRoutine(comp)
3841
+ try:
3842
+ self._menuComponentID = icon
3843
+ xy = wx.Point(event.X + self.parent.GetPosition()[0],
3844
+ event.Y + self.parent.GetPosition()[1])
3845
+ self.showContextMenu(self._menuComponentID, xy=xy)
3846
+ except UnboundLocalError:
3847
+ # right click but not on an icon
3848
+ # might as well do something
3849
+ self.Refresh()
3850
+ elif event.Moving():
3851
+ icons = self.pdc.FindObjectsByBBox(x, y)
3852
+ if not icons:
3853
+ self.frame.SetStatusText("")
3854
+ for thisIcon in icons:
3855
+ # might intersect several and only one has a callback
3856
+ if thisIcon in self.componentFromID:
3857
+ comp = self.componentFromID[thisIcon]
3858
+ # indicate hover target in bottom bar
3859
+ if comp.getType() in handlerTypes:
3860
+ self.frame.SetStatusText(f"Loop ({comp.getType()}): {comp.name}")
3861
+ else:
3862
+ self.frame.SetStatusText(f"{comp.getType()}: {comp.name}")
3863
+ elif self.mode == 'routine':
3864
+ if event.LeftDown():
3865
+ pt = self.entryPointPosList[0]
3866
+ self.insertRoutine(ii=self.gapMidPoints.index(pt))
3867
+ else: # move spot if needed
3868
+ point = self.getNearestGapPoint(mouseX=x)
3869
+ self.drawEntryPoints([point])
3870
+ elif self.mode == 'loopPoint1':
3871
+ if event.LeftDown():
3872
+ self.setLoopPoint2()
3873
+ else: # move spot if needed
3874
+ point = self.getNearestGapPoint(mouseX=x)
3875
+ self.drawEntryPoints([point])
3876
+ elif self.mode == 'loopPoint2':
3877
+ if event.LeftDown():
3878
+ self.insertLoop()
3879
+ else: # move spot if needed
3880
+ point = self.getNearestGapPoint(mouseX=x,
3881
+ exclude=self.gapsExcluded)
3882
+ self.drawEntryPoints([self.entryPointPosList[0], point])
3883
+
3884
+ def OnScroll(self, evt):
3885
+ xy = self.GetViewStart()
3886
+ delta = int(evt.WheelRotation * self.dpi / 1600)
3887
+ if evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:
3888
+ # scroll vertically
3889
+ self.Scroll(xy[0], xy[1] - delta)
3890
+ if evt.GetWheelAxis() == wx.MOUSE_WHEEL_HORIZONTAL:
3891
+ # scroll horizontally
3892
+ self.Scroll(xy[0] + delta, xy[1])
3893
+
3894
+ def getNearestGapPoint(self, mouseX, exclude=()):
3895
+ """Get gap that is nearest to a particular mouse location
3896
+ """
3897
+ d = 1000000000
3898
+ nearest = None
3899
+ for point in self.gapMidPoints:
3900
+ if point in exclude:
3901
+ continue
3902
+ if (point - mouseX) ** 2 < d:
3903
+ d = (point - mouseX) ** 2
3904
+ nearest = point
3905
+ return nearest
3906
+
3907
+ def getGapPointsCrossingStreams(self, gapPoint):
3908
+ """For a given gap point, identify the gap points that are
3909
+ excluded by crossing a loop line
3910
+ """
3911
+ gapArray = numpy.array(self.gapMidPoints)
3912
+ nestLevels = numpy.array(self.gapNestLevels)
3913
+ thisLevel = nestLevels[gapArray == gapPoint]
3914
+ invalidGaps = (gapArray[nestLevels != thisLevel]).tolist()
3915
+ return invalidGaps
3916
+
3917
+ def showContextMenu(self, component, xy):
3918
+ menu = wx.Menu()
3919
+ # get ID
3920
+ # the ID is also the index to the element in the flow list
3921
+ compID = self._menuComponentID
3922
+ flow = self.frame.exp.flow
3923
+ component = flow[compID]
3924
+ compType = component.getType()
3925
+ if compType == 'Routine':
3926
+ for item in self.contextMenuItems:
3927
+ id = self.contextIDFromItem[item]
3928
+ menu.Append(id, self.contextMenuLabels[item])
3929
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
3930
+ self.frame.PopupMenu(menu, xy)
3931
+ # destroy to avoid mem leak:
3932
+ menu.Destroy()
3933
+ else:
3934
+ for item in self.contextMenuItems:
3935
+ if item == 'rename':
3936
+ continue
3937
+ id = self.contextIDFromItem[item]
3938
+ menu.Append(id, self.contextMenuLabels[item])
3939
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
3940
+ self.frame.PopupMenu(menu, xy)
3941
+ # destroy to avoid mem leak:
3942
+ menu.Destroy()
3943
+
3944
+ def onContextSelect(self, event):
3945
+ """Perform a given action on the component chosen
3946
+ """
3947
+ # get ID
3948
+ op = self.contextItemFromID[event.GetId()]
3949
+ # the ID is also the index to the element in the flow list
3950
+ compID = self._menuComponentID
3951
+ flow = self.frame.exp.flow
3952
+ component = flow[compID]
3953
+ # if we have a Loop Initiator, remove the whole loop
3954
+ if component.getType() == 'LoopInitiator':
3955
+ component = component.loop
3956
+ if op == 'remove':
3957
+ self.removeComponent(component, compID)
3958
+ self.frame.addToUndoStack(
3959
+ "REMOVE `%s` from Flow" % component.params['name'])
3960
+ if op == 'rename':
3961
+ self.frame.renameRoutine(component)
3962
+
3963
+ def removeComponent(self, component, compID):
3964
+ """Remove either a Routine or a Loop from the Flow
3965
+ """
3966
+ flow = self.frame.exp.flow
3967
+ if component.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
3968
+ # check whether this will cause a collapsed loop
3969
+ # prev and next elements on flow are a loop init/end
3970
+ prevIsLoop = nextIsLoop = False
3971
+ if compID > 0: # there is at least one preceding
3972
+ prevIsLoop = (flow[compID - 1]).getType() == 'LoopInitiator'
3973
+ if len(flow) > (compID + 1): # there is at least one more compon
3974
+ nextIsLoop = (flow[compID + 1]).getType() == 'LoopTerminator'
3975
+ if prevIsLoop and nextIsLoop:
3976
+ # because flow[compID+1] is a terminator
3977
+ loop = flow[compID + 1].loop
3978
+ msg = _translate('The "%s" Loop is about to be deleted as '
3979
+ 'well (by collapsing). OK to proceed?')
3980
+ title = _translate('Impending Loop collapse')
3981
+ warnDlg = dialogs.MessageDialog(
3982
+ parent=self.frame, message=msg % loop.params['name'],
3983
+ type='Warning', title=title)
3984
+ resp = warnDlg.ShowModal()
3985
+ if resp in [wx.ID_CANCEL, wx.ID_NO]:
3986
+ return # abort
3987
+ elif resp == wx.ID_YES:
3988
+ # make recursive calls to this same method until success
3989
+ # remove the loop first
3990
+ self.removeComponent(loop, compID)
3991
+ # because the loop has been removed ID is now one less
3992
+ self.removeComponent(component, compID - 1)
3993
+ return # have done the removal in final successful call
3994
+ # remove name from namespace only if it's a loop;
3995
+ # loops exist only in the flow
3996
+ elif 'conditionsFile' in component.params:
3997
+ conditionsFile = component.params['conditionsFile'].val
3998
+ if conditionsFile and conditionsFile not in ['None', '']:
3999
+ try:
4000
+ trialList, fieldNames = data.importConditions(
4001
+ conditionsFile, returnFieldNames=True)
4002
+ for fname in fieldNames:
4003
+ self.frame.exp.namespace.remove(fname)
4004
+ except Exception:
4005
+ msg = ("Conditions file %s couldn't be found so names not"
4006
+ " removed from namespace")
4007
+ logging.debug(msg % conditionsFile)
4008
+ self.frame.exp.namespace.remove(component.params['name'].val)
4009
+ # perform the actual removal
4010
+ flow.removeComponent(component, id=compID)
4011
+ self.draw()
4012
+ # enable/disable add loop button
4013
+ self.btnInsertLoop.Enable(bool(len(flow)))
4014
+
4015
+ def OnPaint(self, event):
4016
+ # Create a buffered paint DC. It will create the real
4017
+ # wx.PaintDC and then blit the bitmap to it when dc is
4018
+ # deleted.
4019
+ dc = wx.GCDC(wx.BufferedPaintDC(self))
4020
+ # use PrepareDC to set position correctly
4021
+ self.PrepareDC(dc)
4022
+ # we need to clear the dc BEFORE calling PrepareDC
4023
+ bg = wx.Brush(self.GetBackgroundColour())
4024
+ dc.SetBackground(bg)
4025
+ dc.Clear()
4026
+ # create a clipping rect from our position and size
4027
+ # and the Update Region
4028
+ xv, yv = self.GetViewStart()
4029
+ dx, dy = self.GetScrollPixelsPerUnit()
4030
+ x, y = (xv * dx, yv * dy)
4031
+ rgn = self.GetUpdateRegion()
4032
+ rgn.Offset(x, y)
4033
+ r = rgn.GetBox()
4034
+ # draw to the dc using the calculated clipping rect
4035
+ self.pdc.DrawToDCClipped(dc, r)
4036
+
4037
+ def draw(self, evt=None):
4038
+ """This is the main function for drawing the Flow panel.
4039
+ It should be called whenever something changes in the exp.
4040
+
4041
+ This then makes calls to other drawing functions,
4042
+ like drawEntryPoints...
4043
+ """
4044
+ if not hasattr(self.frame, 'exp'):
4045
+ # we haven't yet added an exp
4046
+ return
4047
+ # retrieve the current flow from the experiment
4048
+ expFlow = self.frame.exp.flow
4049
+ pdc = self.pdc
4050
+
4051
+ # use the ID of the drawn icon to retrieve component (loop or routine)
4052
+ self.componentFromID = {}
4053
+
4054
+ pdc.Clear() # clear the screen
4055
+ pdc.RemoveAll() # clear all objects (icon buttons)
4056
+
4057
+ font = self.GetFont()
4058
+
4059
+ # draw the main time line
4060
+ self.linePos = (1 * self.dpi, 0.5 * self.dpi) # x,y of start
4061
+ gap = self.dpi // (6, 4, 2)[self.appData['flowSize']]
4062
+ dLoopToBaseLine = (15, 25, 43)[self.appData['flowSize']]
4063
+ dBetweenLoops = (20, 24, 30)[self.appData['flowSize']]
4064
+
4065
+ # guess virtual size; nRoutines wide by nLoops high
4066
+ # make bigger than needed and shrink later
4067
+ nRoutines = len(expFlow)
4068
+ nLoops = 0
4069
+ for entry in expFlow:
4070
+ if entry.getType() == 'LoopInitiator':
4071
+ nLoops += 1
4072
+ sizeX = nRoutines * self.dpi * 2
4073
+ sizeY = nLoops * dBetweenLoops + dLoopToBaseLine * 3
4074
+ self.SetVirtualSize(size=(int(sizeX), int(sizeY)))
4075
+
4076
+ # this has type `float` for values, needs to be `int`
4077
+ linePosX, linePosY = [int(x) for x in self.linePos]
4078
+
4079
+ # step through components in flow, get spacing from text size, etc
4080
+ currX = self.linePos[0] # float
4081
+ lineId = wx.NewIdRef()
4082
+ pdc.SetPen(wx.Pen(colour=colors.app['fl_flowline_bg']))
4083
+ pdc.DrawLine(
4084
+ x1=linePosX - gap,
4085
+ y1=linePosY,
4086
+ x2=linePosX,
4087
+ y2=linePosY)
4088
+
4089
+ # NB the loop is itself the key, value is further info about it
4090
+ self.loops = {}
4091
+ nestLevel = 0
4092
+ maxNestLevel = 0
4093
+ self.gapMidPoints = [currX - gap // 2]
4094
+ self.gapNestLevels = [0]
4095
+ for ii, entry in enumerate(expFlow):
4096
+ if entry.getType() == 'LoopInitiator':
4097
+ # NB the loop is itself the dict key!?
4098
+ self.loops[entry.loop] = {
4099
+ 'init': currX, 'nest': nestLevel, 'id': ii}
4100
+ nestLevel += 1 # start of loop so increment level of nesting
4101
+ maxNestLevel = max(nestLevel, maxNestLevel)
4102
+ elif entry.getType() == 'LoopTerminator':
4103
+ # NB the loop is itself the dict key!
4104
+ self.loops[entry.loop]['term'] = currX
4105
+ nestLevel -= 1 # end of loop so decrement level of nesting
4106
+ elif entry.getType() == 'Routine' or entry.getType() in getAllStandaloneRoutines():
4107
+ # just get currX based on text size, don't draw anything yet:
4108
+ currX = self.drawFlowRoutine(pdc, entry, id=ii,
4109
+ pos=[currX, linePosY - 10],
4110
+ draw=False)
4111
+ self.gapMidPoints.append(currX + gap // 2)
4112
+ self.gapNestLevels.append(nestLevel)
4113
+ pdc.SetId(lineId)
4114
+ pdc.SetPen(wx.Pen(colour=colors.app['fl_flowline_bg']))
4115
+ pdc.DrawLine(
4116
+ x1=int(currX),
4117
+ y1=linePosY,
4118
+ x2=int(currX + gap),
4119
+ y2=linePosY)
4120
+ currX += gap
4121
+
4122
+ lineRect = wx.Rect(
4123
+ linePosX - 2,
4124
+ linePosY - 2,
4125
+ int(currX) - linePosX + 2,
4126
+ 4)
4127
+ pdc.SetIdBounds(lineId, lineRect)
4128
+
4129
+ # draw the loops first:
4130
+ maxHeight = 0
4131
+ for thisLoop in self.loops:
4132
+ thisInit = self.loops[thisLoop]['init']
4133
+ thisTerm = self.loops[thisLoop]['term']
4134
+ thisNest = maxNestLevel - self.loops[thisLoop]['nest'] - 1
4135
+ thisId = self.loops[thisLoop]['id']
4136
+ height = (linePosY + dLoopToBaseLine +
4137
+ thisNest * dBetweenLoops)
4138
+ self.drawLoop(pdc, thisLoop, id=thisId,
4139
+ startX=thisInit, endX=thisTerm,
4140
+ base=linePosY, height=height)
4141
+ self.drawLoopStart(pdc, pos=[thisInit, linePosY])
4142
+ self.drawLoopEnd(pdc, pos=[thisTerm, linePosY])
4143
+ if height > maxHeight:
4144
+ maxHeight = height
4145
+
4146
+ # draw routines second (over loop lines):
4147
+ currX = int(self.linePos[0])
4148
+ for ii, entry in enumerate(expFlow):
4149
+ if entry.getType() == 'Routine' or entry.getType() in getAllStandaloneRoutines():
4150
+ currX = self.drawFlowRoutine(
4151
+ pdc, entry, id=ii, pos=[currX, linePosY - 10])
4152
+ pdc.SetPen(wx.Pen(wx.Pen(colour=colors.app['fl_flowline_bg'])))
4153
+ pdc.DrawLine(
4154
+ x1=int(currX),
4155
+ y1=linePosY,
4156
+ x2=int(currX + gap),
4157
+ y2=linePosY)
4158
+ currX += gap
4159
+
4160
+ self.SetVirtualSize(size=(currX + 100, maxHeight + 50))
4161
+
4162
+ self.drawLineStart(pdc, (linePosX - gap, linePosY))
4163
+ self.drawLineEnd(pdc, (currX, linePosY))
4164
+
4165
+ # refresh the visible window after drawing (using OnPaint)
4166
+ self.Refresh()
4167
+
4168
+ def drawEntryPoints(self, posList):
4169
+ ptSize = (3, 4, 5)[self.appData['flowSize']]
4170
+ for n, pos in enumerate(posList):
4171
+ pos = int(pos)
4172
+ if n >= len(self.entryPointPosList):
4173
+ # draw for first time
4174
+ id = wx.NewIdRef()
4175
+ self.entryPointIDlist.append(id)
4176
+ self.pdc.SetId(id)
4177
+ self.pdc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4178
+ self.pdc.DrawCircle(pos, int(self.linePos[1]), ptSize)
4179
+ r = self.pdc.GetIdBounds(id)
4180
+ self.OffsetRect(r)
4181
+ self.RefreshRect(r, False)
4182
+ elif pos == self.entryPointPosList[n]:
4183
+ pass # nothing to see here, move along please :-)
4184
+ else:
4185
+ # move to new position
4186
+ dx = pos - self.entryPointPosList[n]
4187
+ dy = 0
4188
+ r = self.pdc.GetIdBounds(self.entryPointIDlist[n])
4189
+ self.pdc.TranslateId(self.entryPointIDlist[n], int(dx), int(dy))
4190
+ r2 = self.pdc.GetIdBounds(self.entryPointIDlist[n])
4191
+ # combine old and new locations to get redraw area
4192
+ rectToRedraw = r.Union(r2)
4193
+ rectToRedraw.Inflate(4, 4)
4194
+ self.OffsetRect(rectToRedraw)
4195
+ self.RefreshRect(rectToRedraw, False)
4196
+
4197
+ self.entryPointPosList = posList
4198
+ # refresh the visible window after drawing (using OnPaint)
4199
+ self.Refresh()
4200
+
4201
+ def setDrawPoints(self, ptType, startPoint=None):
4202
+ """Set the points of 'routines', 'loops', or None
4203
+ """
4204
+ if ptType == 'routines':
4205
+ self.pointsToDraw = self.gapMidPoints
4206
+ elif ptType == 'loops':
4207
+ self.pointsToDraw = self.gapMidPoints
4208
+ else:
4209
+ self.pointsToDraw = []
4210
+
4211
+ def drawLineStart(self, dc, pos):
4212
+ # draw bar at start of timeline; circle looked bad, offset vertically
4213
+ tmpId = wx.NewId()
4214
+ dc.SetId(tmpId)
4215
+ ptSize = (9, 9, 12)[self.appData['flowSize']]
4216
+ thic = (1, 1, 2)[self.appData['flowSize']]
4217
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4218
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4219
+
4220
+ posX, posY = pos
4221
+ dc.DrawPolygon(
4222
+ [[0, -ptSize], [thic, -ptSize], [thic, ptSize], [0, ptSize]],
4223
+ int(posX), int(posY))
4224
+
4225
+ def drawLineEnd(self, dc, pos):
4226
+ # draws arrow at end of timeline
4227
+ tmpId = wx.NewId()
4228
+ dc.SetId(tmpId)
4229
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4230
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4231
+
4232
+ posX, posY = pos
4233
+ dc.DrawPolygon([[0, -3], [5, 0], [0, 3]], int(posX), int(posY))
4234
+ # dc.SetIdBounds(tmpId,wx.Rect(pos[0],pos[1]+3,5,6))
4235
+
4236
+ def drawLoopEnd(self, dc, pos, downwards=True):
4237
+ # define the right side of a loop but draw nothing
4238
+ # idea: might want an ID for grabbing and relocating the loop endpoint
4239
+ tmpId = wx.NewIdRef()
4240
+ dc.SetId(tmpId)
4241
+ # dc.SetBrush(wx.Brush(wx.Colour(0,0,0, 250)))
4242
+ # dc.SetPen(wx.Pen(wx.Colour(0,0,0, 255)))
4243
+ size = (3, 4, 5)[self.appData['flowSize']]
4244
+ # if downwards:
4245
+ # dc.DrawPolygon([[size, 0], [0, size], [-size, 0]],
4246
+ # pos[0], pos[1] + 2 * size) # points down
4247
+ # else:
4248
+ # dc.DrawPolygon([[size, size], [0, 0], [-size, size]],
4249
+ # pos[0], pos[1]-3*size) # points up
4250
+
4251
+ posX, posY = pos
4252
+ doubleSize = int(2 * size)
4253
+ dc.SetIdBounds(tmpId, wx.Rect(
4254
+ int(posX) - size, int(posY) - size,
4255
+ doubleSize,
4256
+ doubleSize))
4257
+
4258
+ return
4259
+
4260
+ def drawLoopStart(self, dc, pos, downwards=True):
4261
+ # draws direction arrow on left side of a loop
4262
+ tmpId = wx.NewIdRef()
4263
+ dc.SetId(tmpId)
4264
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4265
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4266
+
4267
+ size = (3, 4, 5)[self.appData['flowSize']]
4268
+ offset = (3, 2, 0)[self.appData['flowSize']]
4269
+ posX, posY = [int(x) for x in pos]
4270
+ if downwards:
4271
+ dc.DrawPolygon(
4272
+ [[size, size], [0, 0], [-size, size]],
4273
+ posX, posY + 3 * size - offset) # points up
4274
+ else:
4275
+ dc.DrawPolygon(
4276
+ [[size, 0], [0, size], [-size, 0]],
4277
+ posX,
4278
+ posY - 4 * size) # points down
4279
+
4280
+ doubleSize = int(2 * size)
4281
+ dc.SetIdBounds(tmpId, wx.Rect(
4282
+ posX - size,
4283
+ posY - size,
4284
+ doubleSize,
4285
+ doubleSize))
4286
+
4287
+ def drawFlowRoutine(self, dc, routine, id, pos=(0, 0), draw=True):
4288
+ """Draw a box to show a routine on the timeline
4289
+ draw=False is for a dry-run, esp to compute and return size
4290
+ without drawing or setting a pdc ID
4291
+ """
4292
+ name = routine.name
4293
+ if self.appData['flowSize'] == 0 and len(name) > 5:
4294
+ name = ' ' + name[:4] + '..'
4295
+ else:
4296
+ name = ' ' + name + ' '
4297
+ if draw:
4298
+ dc.SetId(id)
4299
+ font = self.GetFont()
4300
+ if sys.platform == 'darwin':
4301
+ fontSizeDelta = (9, 6, 0)[self.appData['flowSize']]
4302
+ font.SetPointSize(1400 // self.dpi - fontSizeDelta)
4303
+ elif sys.platform.startswith('linux'):
4304
+ fontSizeDelta = (6, 4, 0)[self.appData['flowSize']]
4305
+ font.SetPointSize(1400 // self.dpi - fontSizeDelta)
4306
+ else:
4307
+ fontSizeDelta = (8, 4, 0)[self.appData['flowSize']]
4308
+ font.SetPointSize(1000 // self.dpi - fontSizeDelta)
4309
+ # if selected, bold text
4310
+ if routine == self.frame.routinePanel.getCurrentRoutine():
4311
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
4312
+ else:
4313
+ font.SetWeight(wx.FONTWEIGHT_NORMAL)
4314
+
4315
+ maxTime, nonSlip = routine.getMaxTime()
4316
+ if hasattr(routine, "disabled") and routine.disabled:
4317
+ rtFill = colors.app['rt_comp_disabled']
4318
+ rtEdge = colors.app['rt_comp_disabled']
4319
+ rtText = colors.app['fl_routine_fg']
4320
+ elif nonSlip:
4321
+ rtFill = colors.app['fl_routine_bg_nonslip']
4322
+ rtEdge = colors.app['fl_routine_bg_nonslip']
4323
+ rtText = colors.app['fl_routine_fg']
4324
+ else:
4325
+ rtFill = colors.app['fl_routine_bg_slip']
4326
+ rtEdge = colors.app['fl_routine_bg_slip']
4327
+ rtText = colors.app['fl_routine_fg']
4328
+
4329
+ # get size based on text
4330
+ self.SetFont(font)
4331
+ if draw:
4332
+ dc.SetFont(font)
4333
+ w, h = self.GetFullTextExtent(name)[0:2]
4334
+ pos = [int(x) for x in pos] # explicit type conversion for position
4335
+ pad = (5, 10, 20)[self.appData['flowSize']]
4336
+ # draw box
4337
+ rect = wx.Rect(pos[0], pos[1] + 2 - self.appData['flowSize'],
4338
+ w + pad, h + pad)
4339
+ endX = pos[0] + w + pad
4340
+ # the edge should match the text, unless selected
4341
+ if draw:
4342
+ dc.SetPen(wx.Pen(wx.Colour(rtEdge[0], rtEdge[1],
4343
+ rtEdge[2], wx.ALPHA_OPAQUE)))
4344
+ dc.SetBrush(wx.Brush(rtFill))
4345
+ dc.DrawRoundedRectangle(
4346
+ rect, (4, 6, 8)[self.appData['flowSize']])
4347
+ # draw text
4348
+ dc.SetTextForeground(rtText)
4349
+ dc.DrawLabel(name, rect, alignment=wx.ALIGN_CENTRE)
4350
+ if nonSlip and self.appData['flowSize'] != 0:
4351
+ font.SetPointSize(int(font.GetPointSize() * 0.6))
4352
+ dc.SetFont(font)
4353
+ _align = wx.ALIGN_CENTRE | wx.ALIGN_BOTTOM
4354
+ timeRect = wx.Rect(rect.Left, rect.Top, rect.Width, rect.Height-2)
4355
+ dc.DrawLabel("(%.2fs)" % maxTime, timeRect, alignment=_align)
4356
+
4357
+ self.componentFromID[id] = routine
4358
+ # set the area for this component
4359
+ dc.SetIdBounds(id, rect)
4360
+
4361
+ return endX
4362
+
4363
+ def drawLoop(self, dc, loop, id, startX, endX, base, height,
4364
+ downwards=True):
4365
+ if downwards:
4366
+ up = -1
4367
+ else:
4368
+ up = +1
4369
+
4370
+ # draw loop itself, as transparent rect with curved corners
4371
+ tmpId = wx.NewIdRef()
4372
+ dc.SetId(tmpId)
4373
+ # extra distance, in both h and w for curve
4374
+ curve = (6, 11, 15)[self.appData['flowSize']]
4375
+ # convert args types to `int`
4376
+ startX, endX, base, height = [
4377
+ int(x) for x in (startX, endX, base, height)]
4378
+
4379
+ yy = [base, height + curve * up, height +
4380
+ curve * up // 2, height] # for area
4381
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4382
+ vertOffset = 0 # 1 is interesting too
4383
+ area = wx.Rect(startX, base + vertOffset,
4384
+ endX - startX, max(yy) - min(yy))
4385
+ dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 0), style=wx.TRANSPARENT))
4386
+ # draws outline:
4387
+ dc.DrawRoundedRectangle(area, curve)
4388
+ dc.SetIdBounds(tmpId, area)
4389
+
4390
+ flowsize = self.appData['flowSize'] # 0, 1, or 2
4391
+
4392
+ # add a name label, loop info, except at smallest size
4393
+ name = loop.params['name'].val
4394
+ _show = self.appData['showLoopInfoInFlow']
4395
+ if _show and flowsize:
4396
+ _cond = 'conditions' in list(loop.params)
4397
+ if _cond and loop.params['conditions'].val:
4398
+ xnumTrials = 'x' + str(len(loop.params['conditions'].val))
4399
+ else:
4400
+ xnumTrials = ''
4401
+ name += ' (' + str(loop.params['nReps'].val) + xnumTrials
4402
+ abbrev = ['', # for flowsize == 0
4403
+ {'random': 'rand.',
4404
+ 'sequential': 'sequ.',
4405
+ 'fullRandom': 'f-ran.',
4406
+ 'staircase': 'stair.',
4407
+ 'interleaved staircases': "int-str."},
4408
+ {'random': 'random',
4409
+ 'sequential': 'sequential',
4410
+ 'fullRandom': 'fullRandom',
4411
+ 'staircase': 'staircase',
4412
+ 'interleaved staircases': "interl'vd stairs"}]
4413
+ name += ' ' + abbrev[flowsize][loop.params['loopType'].val] + ')'
4414
+ if flowsize == 0:
4415
+ if len(name) > 9:
4416
+ name = ' ' + name[:8] + '..'
4417
+ else:
4418
+ name = ' ' + name[:9]
4419
+ else:
4420
+ name = ' ' + name + ' '
4421
+
4422
+ dc.SetId(id)
4423
+ font = self.GetFont()
4424
+ font.SetWeight(wx.FONTWEIGHT_NORMAL)
4425
+ if sys.platform == 'darwin':
4426
+ basePtSize = (650, 750, 900)[flowsize]
4427
+ elif sys.platform.startswith('linux'):
4428
+ basePtSize = (750, 850, 1000)[flowsize]
4429
+ else:
4430
+ basePtSize = (700, 750, 800)[flowsize]
4431
+ font.SetPointSize(basePtSize // self.dpi)
4432
+ self.SetFont(font)
4433
+ dc.SetFont(font)
4434
+
4435
+ # get size based on text
4436
+ pad = (5, 8, 10)[self.appData['flowSize']]
4437
+ w, h = self.GetFullTextExtent(name)[0:2]
4438
+ x = startX + (endX - startX) // 2 - w // 2 - pad // 2
4439
+ y = (height - h // 2)
4440
+
4441
+ # draw box
4442
+ rect = wx.Rect(int(x), int(y), int(w + pad), int(h + pad))
4443
+ # the edge should match the text
4444
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4445
+ # try to make the loop fill brighter than the background canvas:
4446
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4447
+
4448
+ dc.DrawRoundedRectangle(rect, (4, 6, 8)[flowsize])
4449
+ # draw text
4450
+ dc.SetTextForeground(colors.app['fl_flowline_fg'])
4451
+ dc.DrawText(name, x + pad // 2, y + pad // 2)
4452
+
4453
+ self.componentFromID[id] = loop
4454
+ # set the area for this component
4455
+ dc.SetIdBounds(id, rect)
4456
+
4457
+
4458
+ class BuilderRibbon(ribbon.FrameRibbon):
4459
+ def __init__(self, parent):
4460
+ # initialize
4461
+ ribbon.FrameRibbon.__init__(self, parent)
4462
+
4463
+ # --- File ---
4464
+ self.addSection(
4465
+ "file", label=_translate("File"), icon="file"
4466
+ )
4467
+ # file new
4468
+ self.addButton(
4469
+ section="file", name="new", label=_translate("New"), icon="filenew",
4470
+ tooltip=_translate("Create new experiment file"),
4471
+ callback=parent.app.newBuilderFrame
4472
+ )
4473
+ # file open
4474
+ self.addButton(
4475
+ section="file", name="open", label=_translate("Open"), icon="fileopen",
4476
+ tooltip=_translate("Open an existing experiment file"),
4477
+ callback=parent.fileOpen
4478
+ )
4479
+ # file save
4480
+ self.addButton(
4481
+ section="file", name="save", label=_translate("Save"), icon="filesave",
4482
+ tooltip=_translate("Save current experiment file"),
4483
+ callback=parent.fileSave
4484
+ )
4485
+ # file save as
4486
+ self.addButton(
4487
+ section="file", name="saveas", label=_translate("Save as..."), icon="filesaveas",
4488
+ tooltip=_translate("Save current experiment file as..."),
4489
+ callback=parent.fileSaveAs
4490
+ )
4491
+
4492
+ self.addSeparator()
4493
+
4494
+ # --- Edit ---
4495
+ self.addSection(
4496
+ "edit", label=_translate("Edit"), icon="edit"
4497
+ )
4498
+ # undo
4499
+ self.addButton(
4500
+ section="edit", name="undo", label=_translate("Undo"), icon="undo",
4501
+ tooltip=_translate("Undo last action"),
4502
+ callback=parent.undo
4503
+ )
4504
+ # redo
4505
+ self.addButton(
4506
+ section="edit", name="redo", label=_translate("Redo"), icon="redo",
4507
+ tooltip=_translate("Redo last action"),
4508
+ callback=parent.redo
4509
+ )
4510
+
4511
+ self.addSeparator()
4512
+
4513
+ # --- Tools ---
4514
+ self.addSection(
4515
+ "experiment", label=_translate("Experiment"), icon="experiment"
4516
+ )
4517
+ # monitor center
4518
+ self.addButton(
4519
+ section="experiment", name='monitor', label=_translate('Monitor center'),
4520
+ icon="monitors",
4521
+ tooltip=_translate("Monitor settings and calibration"),
4522
+ callback=parent.app.openMonitorCenter
4523
+ )
4524
+ # settings
4525
+ self.addButton(
4526
+ section="experiment", name='expsettings', label=_translate('Experiment settings'), icon="expsettings",
4527
+ tooltip=_translate("Edit experiment settings"),
4528
+ callback=parent.setExperimentSettings
4529
+ )
4530
+ # switch run/pilot
4531
+ self.addSwitchCtrl(
4532
+ section="experiment", name="pyswitch",
4533
+ labels=(_translate("Pilot"), _translate("Run")),
4534
+ startMode=0, callback=parent.onRunModeToggle,
4535
+ style=wx.HORIZONTAL
4536
+ )
4537
+ # send to runner
4538
+ self.addButton(
4539
+ section="experiment", name='sendRunner', label=_translate('Runner'), icon="runner",
4540
+ tooltip=_translate("Send experiment to Runner"),
4541
+ callback=parent.sendToRunner
4542
+ )
4543
+ # send to runner (pilot icon)
4544
+ self.addButton(
4545
+ section="experiment", name='pilotRunner', label=_translate('Runner'),
4546
+ icon="runnerPilot",
4547
+ tooltip=_translate("Send experiment to Runner"),
4548
+ callback=parent.sendToRunner
4549
+ )
4550
+
4551
+ self.addSeparator()
4552
+
4553
+ # --- Python ---
4554
+ self.addSection(
4555
+ "py", label=_translate("Desktop"), icon="desktop"
4556
+ )
4557
+ # compile python
4558
+ self.addButton(
4559
+ section="py", name="pycompile", label=_translate('Write Python'), icon='compile_py',
4560
+ tooltip=_translate("Write experiment as a Python script"),
4561
+ callback=parent.compileScript
4562
+ )
4563
+ # pilot Py
4564
+ self.addButton(
4565
+ section="py", name="pypilot", label=_translate("Pilot"), icon='pyPilot',
4566
+ tooltip=_translate("Run the current script in Python with piloting features on"),
4567
+ callback=parent.pilotFile
4568
+ )
4569
+ # run Py
4570
+ self.addButton(
4571
+ section="py", name="pyrun", label=_translate("Run"), icon='pyRun',
4572
+ tooltip=_translate("Run the current script in Python"),
4573
+ callback=parent.runFile
4574
+ )
4575
+
4576
+ self.addSeparator()
4577
+
4578
+ # --- JS ---
4579
+ self.addSection(
4580
+ "browser", label=_translate("Browser"), icon="browser"
4581
+ )
4582
+ # compile JS
4583
+ self.addButton(
4584
+ section="browser", name="jscompile", label=_translate('Write JS'), icon='compile_js',
4585
+ tooltip=_translate("Write experiment as a JavaScript (JS) script"),
4586
+ callback=parent.fileExport
4587
+ )
4588
+ # pilot JS
4589
+ self.addButton(
4590
+ section="browser", name="jspilot", label=_translate("Pilot in browser"),
4591
+ icon='jsPilot',
4592
+ tooltip=_translate("Pilot experiment locally in your browser"),
4593
+ callback=parent.onPavloviaDebug
4594
+ )
4595
+ # run JS
4596
+ self.addButton(
4597
+ section="browser", name="jsrun", label=_translate("Run on Pavlovia"), icon='jsRun',
4598
+ tooltip=_translate("Run experiment on Pavlovia"),
4599
+ callback=parent.onPavloviaRun
4600
+ )
4601
+ # sync project
4602
+ self.addButton(
4603
+ section="browser", name="pavsync", label=_translate("Sync"), icon='pavsync',
4604
+ tooltip=_translate("Sync project with Pavlovia"),
4605
+ callback=parent.onPavloviaSync
4606
+ )
4607
+
4608
+ self.addSeparator()
4609
+
4610
+ # --- JS ---
4611
+ self.addSection(
4612
+ "pavlovia", label=_translate("Pavlovia"), icon="pavlovia"
4613
+ )
4614
+ # pavlovia user
4615
+ self.addPavloviaUserCtrl(
4616
+ section="pavlovia", name="pavuser", frame=parent
4617
+ )
4618
+ # pavlovia project
4619
+ self.addPavloviaProjectCtrl(
4620
+ section="pavlovia", name="pavproject", frame=parent
4621
+ )
4622
+
4623
+ self.addSeparator()
4624
+
4625
+ # --- Views ---
4626
+ self.addStretchSpacer()
4627
+ self.addSeparator()
4628
+
4629
+ self.addSection(
4630
+ "views", label=_translate("Views"), icon="windows"
4631
+ )
4632
+ # show Builder
4633
+ self.addButton(
4634
+ section="views", name="builder", label=_translate("Show Builder"), icon="showBuilder",
4635
+ tooltip=_translate("Switch to Builder view"),
4636
+ callback=parent.app.showBuilder
4637
+ ).Disable()
4638
+ # show Coder
4639
+ self.addButton(
4640
+ section="views", name="coder", label=_translate("Show Coder"), icon="showCoder",
4641
+ tooltip=_translate("Switch to Coder view"),
4642
+ callback=parent.app.showCoder
4643
+ )
4644
+ # show Runner
4645
+ self.addButton(
4646
+ section="views", name="runner", label=_translate("Show Runner"), icon="showRunner",
4647
+ tooltip=_translate("Switch to Runner view"),
4648
+ callback=parent.app.showRunner
4649
+ )
4650
+
4651
+
4652
+ def extractText(stream):
4653
+ """Take a byte stream (or any file object of type b?) and return
4654
+
4655
+ :param stream: stream from wx.Process or any byte stream from a file
4656
+ :return: text converted to unicode ready for appending to wx text view
4657
+ """
4658
+ return stream.read().decode('utf-8')