psychopy 2025.2.4__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.
Files changed (3127) hide show
  1. psychopy/CHANGELOG.txt +3981 -0
  2. psychopy/GIT_SHA +1 -0
  3. psychopy/LICENSE.txt +11 -0
  4. psychopy/LICENSES.txt +55 -0
  5. psychopy/VERSION +1 -0
  6. psychopy/__init__.py +148 -0
  7. psychopy/alerts/__init__.py +65 -0
  8. psychopy/alerts/_alerts.py +224 -0
  9. psychopy/alerts/_errorHandler.py +50 -0
  10. psychopy/alerts/alertsCatalogue/1000.yaml +0 -0
  11. psychopy/alerts/alertsCatalogue/1100.yaml +0 -0
  12. psychopy/alerts/alertsCatalogue/1500.yaml +0 -0
  13. psychopy/alerts/alertsCatalogue/2000.yaml +0 -0
  14. psychopy/alerts/alertsCatalogue/2100.yaml +0 -0
  15. psychopy/alerts/alertsCatalogue/2110.yaml +0 -0
  16. psychopy/alerts/alertsCatalogue/2115.yaml +21 -0
  17. psychopy/alerts/alertsCatalogue/2120.yaml +22 -0
  18. psychopy/alerts/alertsCatalogue/2150.yaml +0 -0
  19. psychopy/alerts/alertsCatalogue/2155.yaml +21 -0
  20. psychopy/alerts/alertsCatalogue/2300.yaml +0 -0
  21. psychopy/alerts/alertsCatalogue/2500.yaml +0 -0
  22. psychopy/alerts/alertsCatalogue/3000.yaml +0 -0
  23. psychopy/alerts/alertsCatalogue/3100.yaml +0 -0
  24. psychopy/alerts/alertsCatalogue/3110.yaml +25 -0
  25. psychopy/alerts/alertsCatalogue/3115.yaml +24 -0
  26. psychopy/alerts/alertsCatalogue/3200.yaml +0 -0
  27. psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
  28. psychopy/alerts/alertsCatalogue/3400.yaml +0 -0
  29. psychopy/alerts/alertsCatalogue/3600.yaml +0 -0
  30. psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
  31. psychopy/alerts/alertsCatalogue/4000.yaml +0 -0
  32. psychopy/alerts/alertsCatalogue/4051.yaml +19 -0
  33. psychopy/alerts/alertsCatalogue/4052.yaml +19 -0
  34. psychopy/alerts/alertsCatalogue/4100.yaml +0 -0
  35. psychopy/alerts/alertsCatalogue/4105.yaml +18 -0
  36. psychopy/alerts/alertsCatalogue/4115.yaml +21 -0
  37. psychopy/alerts/alertsCatalogue/4120.yaml +19 -0
  38. psychopy/alerts/alertsCatalogue/4125.yaml +23 -0
  39. psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
  40. psychopy/alerts/alertsCatalogue/4160.yaml +25 -0
  41. psychopy/alerts/alertsCatalogue/4200.yaml +0 -0
  42. psychopy/alerts/alertsCatalogue/4205.yaml +19 -0
  43. psychopy/alerts/alertsCatalogue/4210.yaml +19 -0
  44. psychopy/alerts/alertsCatalogue/4305.yaml +21 -0
  45. psychopy/alerts/alertsCatalogue/4310.yaml +21 -0
  46. psychopy/alerts/alertsCatalogue/4315.yaml +21 -0
  47. psychopy/alerts/alertsCatalogue/4320.yaml +19 -0
  48. psychopy/alerts/alertsCatalogue/4325.yaml +23 -0
  49. psychopy/alerts/alertsCatalogue/4330.yaml +19 -0
  50. psychopy/alerts/alertsCatalogue/4335.yaml +19 -0
  51. psychopy/alerts/alertsCatalogue/4340.yaml +19 -0
  52. psychopy/alerts/alertsCatalogue/4405.yaml +24 -0
  53. psychopy/alerts/alertsCatalogue/4505.yaml +22 -0
  54. psychopy/alerts/alertsCatalogue/4510.yaml +25 -0
  55. psychopy/alerts/alertsCatalogue/4520.yaml +26 -0
  56. psychopy/alerts/alertsCatalogue/4530.yaml +23 -0
  57. psychopy/alerts/alertsCatalogue/4540.yaml +19 -0
  58. psychopy/alerts/alertsCatalogue/4545.yaml +22 -0
  59. psychopy/alerts/alertsCatalogue/4550.yaml +19 -0
  60. psychopy/alerts/alertsCatalogue/4605.yaml +22 -0
  61. psychopy/alerts/alertsCatalogue/4610.yaml +22 -0
  62. psychopy/alerts/alertsCatalogue/4615.yaml +19 -0
  63. psychopy/alerts/alertsCatalogue/4705.yaml +19 -0
  64. psychopy/alerts/alertsCatalogue/4710.yaml +19 -0
  65. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  66. psychopy/alerts/alertsCatalogue/5000.yaml +0 -0
  67. psychopy/alerts/alertsCatalogue/5055.yaml +25 -0
  68. psychopy/alerts/alertsCatalogue/6000.yaml +0 -0
  69. psychopy/alerts/alertsCatalogue/6105.yaml +18 -0
  70. psychopy/alerts/alertsCatalogue/7105.yaml +19 -0
  71. psychopy/alerts/alertsCatalogue/8105.yaml +21 -0
  72. psychopy/alerts/alertsCatalogue/8110.yaml +21 -0
  73. psychopy/alerts/alertsCatalogue/9998.yaml +19 -0
  74. psychopy/alerts/alertsCatalogue/9999.yaml +19 -0
  75. psychopy/alerts/alertsCatalogue/alertCategories.yaml +123 -0
  76. psychopy/alerts/alertsCatalogue/alertTemplate.yaml +19 -0
  77. psychopy/alerts/alertsCatalogue/alertmsg.py +173 -0
  78. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +34 -0
  79. psychopy/alerts/alerttools.py +322 -0
  80. psychopy/app/Resources/README.md +12 -0
  81. psychopy/app/Resources/__init__.py +0 -0
  82. psychopy/app/Resources/betasplash.png +0 -0
  83. psychopy/app/Resources/betasplash@2x.png +0 -0
  84. psychopy/app/Resources/builder.ico +0 -0
  85. psychopy/app/Resources/classic/FlowBottom_CompLeft.png +0 -0
  86. psychopy/app/Resources/classic/FlowBottom_CompRight.png +0 -0
  87. psychopy/app/Resources/classic/FlowTop_CompLeft.png +0 -0
  88. psychopy/app/Resources/classic/FlowTop_CompRight.png +0 -0
  89. psychopy/app/Resources/classic/README.txt +9 -0
  90. psychopy/app/Resources/classic/__init__.py +0 -0
  91. psychopy/app/Resources/classic/_layouts.ai +1138 -1
  92. psychopy/app/Resources/classic/add.png +0 -0
  93. psychopy/app/Resources/classic/add@2x.png +0 -0
  94. psychopy/app/Resources/classic/addExp32.png +0 -0
  95. psychopy/app/Resources/classic/add_many.png +0 -0
  96. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  97. psychopy/app/Resources/classic/alerts.png +0 -0
  98. psychopy/app/Resources/classic/beta.png +0 -0
  99. psychopy/app/Resources/classic/browser.png +0 -0
  100. psychopy/app/Resources/classic/browser@2x.png +0 -0
  101. psychopy/app/Resources/classic/bug16.png +0 -0
  102. psychopy/app/Resources/classic/case.png +0 -0
  103. psychopy/app/Resources/classic/case@2x.png +0 -0
  104. psychopy/app/Resources/classic/circle_mask.png +0 -0
  105. psychopy/app/Resources/classic/circle_mask@2x.png +0 -0
  106. psychopy/app/Resources/classic/clear.png +0 -0
  107. psychopy/app/Resources/classic/clear@2x.png +0 -0
  108. psychopy/app/Resources/classic/coderclass16.png +0 -0
  109. psychopy/app/Resources/classic/coderfunc16.png +0 -0
  110. psychopy/app/Resources/classic/coderimport16.png +0 -0
  111. psychopy/app/Resources/classic/coderjs16.png +0 -0
  112. psychopy/app/Resources/classic/coderpython16.png +0 -0
  113. psychopy/app/Resources/classic/codervar16.png +0 -0
  114. psychopy/app/Resources/classic/cogwindow32.png +0 -0
  115. psychopy/app/Resources/classic/color32.png +0 -0
  116. psychopy/app/Resources/classic/compile32.png +0 -0
  117. psychopy/app/Resources/classic/compile_js.png +0 -0
  118. psychopy/app/Resources/classic/compile_js@2x.png +0 -0
  119. psychopy/app/Resources/classic/compile_py.png +0 -0
  120. psychopy/app/Resources/classic/compile_py@2x.png +0 -0
  121. psychopy/app/Resources/classic/copy16.png +0 -0
  122. psychopy/app/Resources/classic/currentFile.png +0 -0
  123. psychopy/app/Resources/classic/currentFile@2x.png +0 -0
  124. psychopy/app/Resources/classic/delete16.png +0 -0
  125. psychopy/app/Resources/classic/desktop.png +0 -0
  126. psychopy/app/Resources/classic/desktop@2x.png +0 -0
  127. psychopy/app/Resources/classic/devices.png +0 -0
  128. psychopy/app/Resources/classic/devices@2x.png +0 -0
  129. psychopy/app/Resources/classic/dirup16.png +0 -0
  130. psychopy/app/Resources/classic/docclose16.png +0 -0
  131. psychopy/app/Resources/classic/download.png +0 -0
  132. psychopy/app/Resources/classic/edit.png +0 -0
  133. psychopy/app/Resources/classic/edit@2x.png +0 -0
  134. psychopy/app/Resources/classic/editbtn.png +0 -0
  135. psychopy/app/Resources/classic/editbtn16.png +0 -0
  136. psychopy/app/Resources/classic/editbtn16@2x.png +0 -0
  137. psychopy/app/Resources/classic/editbtn@2x.png +0 -0
  138. psychopy/app/Resources/classic/email.png +0 -0
  139. psychopy/app/Resources/classic/email@2x.png +0 -0
  140. psychopy/app/Resources/classic/experiment.png +0 -0
  141. psychopy/app/Resources/classic/experiment@2x.png +0 -0
  142. psychopy/app/Resources/classic/expsettings.png +0 -0
  143. psychopy/app/Resources/classic/expsettings@2x.png +0 -0
  144. psychopy/app/Resources/classic/file.png +0 -0
  145. psychopy/app/Resources/classic/file@2x.png +0 -0
  146. psychopy/app/Resources/classic/fileaudio.png +0 -0
  147. psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
  148. psychopy/app/Resources/classic/filecss.png +0 -0
  149. psychopy/app/Resources/classic/filecss@2x.png +0 -0
  150. psychopy/app/Resources/classic/filecsv.png +0 -0
  151. psychopy/app/Resources/classic/filecsv@2x.png +0 -0
  152. psychopy/app/Resources/classic/filedesign.png +0 -0
  153. psychopy/app/Resources/classic/filedesign@2x.png +0 -0
  154. psychopy/app/Resources/classic/filefont.png +0 -0
  155. psychopy/app/Resources/classic/filefont@2x.png +0 -0
  156. psychopy/app/Resources/classic/filegit.png +0 -0
  157. psychopy/app/Resources/classic/filegit@2x.png +0 -0
  158. psychopy/app/Resources/classic/filehtml.png +0 -0
  159. psychopy/app/Resources/classic/filehtml@2x.png +0 -0
  160. psychopy/app/Resources/classic/fileimage.png +0 -0
  161. psychopy/app/Resources/classic/fileimage@2x.png +0 -0
  162. psychopy/app/Resources/classic/fileinfo.png +0 -0
  163. psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
  164. psychopy/app/Resources/classic/filejs.png +0 -0
  165. psychopy/app/Resources/classic/filejs@2x.png +0 -0
  166. psychopy/app/Resources/classic/filejson.png +0 -0
  167. psychopy/app/Resources/classic/filejson@2x.png +0 -0
  168. psychopy/app/Resources/classic/filenew.png +0 -0
  169. psychopy/app/Resources/classic/filenew32.png +0 -0
  170. psychopy/app/Resources/classic/filenew@2x.png +0 -0
  171. psychopy/app/Resources/classic/fileopen.png +0 -0
  172. psychopy/app/Resources/classic/fileopen32.png +0 -0
  173. psychopy/app/Resources/classic/fileopen@2x.png +0 -0
  174. psychopy/app/Resources/classic/filepkg.png +0 -0
  175. psychopy/app/Resources/classic/filepkg@2x.png +0 -0
  176. psychopy/app/Resources/classic/filepsyexp.png +0 -0
  177. psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
  178. psychopy/app/Resources/classic/filepy.png +0 -0
  179. psychopy/app/Resources/classic/filepy@2x.png +0 -0
  180. psychopy/app/Resources/classic/filesave.png +0 -0
  181. psychopy/app/Resources/classic/filesave32.png +0 -0
  182. psychopy/app/Resources/classic/filesave@2x.png +0 -0
  183. psychopy/app/Resources/classic/filesaveas.png +0 -0
  184. psychopy/app/Resources/classic/filesaveas32.png +0 -0
  185. psychopy/app/Resources/classic/filesaveas@2x.png +0 -0
  186. psychopy/app/Resources/classic/filetxt.png +0 -0
  187. psychopy/app/Resources/classic/filetxt@2x.png +0 -0
  188. psychopy/app/Resources/classic/fileunknown.png +0 -0
  189. psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
  190. psychopy/app/Resources/classic/filevideo.png +0 -0
  191. psychopy/app/Resources/classic/filevideo@2x.png +0 -0
  192. psychopy/app/Resources/classic/filter.png +0 -0
  193. psychopy/app/Resources/classic/filter@2x.png +0 -0
  194. psychopy/app/Resources/classic/find.png +0 -0
  195. psychopy/app/Resources/classic/find@2x.png +0 -0
  196. psychopy/app/Resources/classic/folder-open16.png +0 -0
  197. psychopy/app/Resources/classic/folder16.png +0 -0
  198. psychopy/app/Resources/classic/foldernew16.png +0 -0
  199. psychopy/app/Resources/classic/fork.png +0 -0
  200. psychopy/app/Resources/classic/fork@2x.png +0 -0
  201. psychopy/app/Resources/classic/github.png +0 -0
  202. psychopy/app/Resources/classic/github@2x.png +0 -0
  203. psychopy/app/Resources/classic/globe.png +0 -0
  204. psychopy/app/Resources/classic/globe@2x.png +0 -0
  205. psychopy/app/Resources/classic/globe_bug.png +0 -0
  206. psychopy/app/Resources/classic/globe_bug@2x.png +0 -0
  207. psychopy/app/Resources/classic/globe_greensync.png +0 -0
  208. psychopy/app/Resources/classic/globe_greensync@2x.png +0 -0
  209. psychopy/app/Resources/classic/globe_info.png +0 -0
  210. psychopy/app/Resources/classic/globe_info@2x.png +0 -0
  211. psychopy/app/Resources/classic/globe_magnifier.png +0 -0
  212. psychopy/app/Resources/classic/globe_magnifier@2x.png +0 -0
  213. psychopy/app/Resources/classic/globe_run.png +0 -0
  214. psychopy/app/Resources/classic/globe_run@2x.png +0 -0
  215. psychopy/app/Resources/classic/globe_user.png +0 -0
  216. psychopy/app/Resources/classic/globe_user@2x.png +0 -0
  217. psychopy/app/Resources/classic/goto16.png +0 -0
  218. psychopy/app/Resources/classic/greendot.png +0 -0
  219. psychopy/app/Resources/classic/greendot@2x.png +0 -0
  220. psychopy/app/Resources/classic/greenglobe.png +0 -0
  221. psychopy/app/Resources/classic/greenglobe@2x.png +0 -0
  222. psychopy/app/Resources/classic/greenglobe_bug.png +0 -0
  223. psychopy/app/Resources/classic/greenglobe_bug@2x.png +0 -0
  224. psychopy/app/Resources/classic/greenglobe_greensync.png +0 -0
  225. psychopy/app/Resources/classic/greenglobe_greensync@2x.png +0 -0
  226. psychopy/app/Resources/classic/greenglobe_info.png +0 -0
  227. psychopy/app/Resources/classic/greenglobe_info@2x.png +0 -0
  228. psychopy/app/Resources/classic/greenglobe_magnifier.png +0 -0
  229. psychopy/app/Resources/classic/greenglobe_magnifier@2x.png +0 -0
  230. psychopy/app/Resources/classic/greenglobe_run.png +0 -0
  231. psychopy/app/Resources/classic/greenglobe_run@2x.png +0 -0
  232. psychopy/app/Resources/classic/greenglobe_user.png +0 -0
  233. psychopy/app/Resources/classic/greenglobe_user@2x.png +0 -0
  234. psychopy/app/Resources/classic/greydot.png +0 -0
  235. psychopy/app/Resources/classic/greydot@2x.png +0 -0
  236. psychopy/app/Resources/classic/greytick.png +0 -0
  237. psychopy/app/Resources/classic/greytick@2x.png +0 -0
  238. psychopy/app/Resources/classic/invalid_img.png +0 -0
  239. psychopy/app/Resources/classic/jsPilot.png +0 -0
  240. psychopy/app/Resources/classic/jsPilot@2x.png +0 -0
  241. psychopy/app/Resources/classic/jsRun.png +0 -0
  242. psychopy/app/Resources/classic/jsRun@2x.png +0 -0
  243. psychopy/app/Resources/classic/libroot.png +0 -0
  244. psychopy/app/Resources/classic/libroot@2x.png +0 -0
  245. psychopy/app/Resources/classic/loop.png +0 -0
  246. psychopy/app/Resources/classic/loop@2x.png +0 -0
  247. psychopy/app/Resources/classic/monitor16.png +0 -0
  248. psychopy/app/Resources/classic/monitors.png +0 -0
  249. psychopy/app/Resources/classic/monitors16.png +0 -0
  250. psychopy/app/Resources/classic/monitors32.png +0 -0
  251. psychopy/app/Resources/classic/monitors@2x.png +0 -0
  252. psychopy/app/Resources/classic/orangedot.png +0 -0
  253. psychopy/app/Resources/classic/orangedot@2x.png +0 -0
  254. psychopy/app/Resources/classic/pavlovia.png +0 -0
  255. psychopy/app/Resources/classic/pavlovia16.png +0 -0
  256. psychopy/app/Resources/classic/pavlovia16@2x.png +0 -0
  257. psychopy/app/Resources/classic/pavlovia@2x.png +0 -0
  258. psychopy/app/Resources/classic/pavsync.png +0 -0
  259. psychopy/app/Resources/classic/pavsync@2x.png +0 -0
  260. psychopy/app/Resources/classic/person_off.png +0 -0
  261. psychopy/app/Resources/classic/person_off@2x.png +0 -0
  262. psychopy/app/Resources/classic/person_on.png +0 -0
  263. psychopy/app/Resources/classic/person_on@2x.png +0 -0
  264. psychopy/app/Resources/classic/photometer.png +0 -0
  265. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  266. psychopy/app/Resources/classic/plugin16.png +0 -0
  267. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  268. psychopy/app/Resources/classic/plugins32.png +0 -0
  269. psychopy/app/Resources/classic/plus.png +0 -0
  270. psychopy/app/Resources/classic/plus@2x.png +0 -0
  271. psychopy/app/Resources/classic/preferences-app.png +0 -0
  272. psychopy/app/Resources/classic/preferences-app48.png +0 -0
  273. psychopy/app/Resources/classic/preferences-app@2x.png +0 -0
  274. psychopy/app/Resources/classic/preferences-conn.png +0 -0
  275. psychopy/app/Resources/classic/preferences-conn48.png +0 -0
  276. psychopy/app/Resources/classic/preferences-conn@2x.png +0 -0
  277. psychopy/app/Resources/classic/preferences-debug.png +0 -0
  278. psychopy/app/Resources/classic/preferences-debug@2x.png +0 -0
  279. psychopy/app/Resources/classic/preferences-general.png +0 -0
  280. psychopy/app/Resources/classic/preferences-general48.png +0 -0
  281. psychopy/app/Resources/classic/preferences-general@2x.png +0 -0
  282. psychopy/app/Resources/classic/preferences-hardware.png +0 -0
  283. psychopy/app/Resources/classic/preferences-hardware48.png +0 -0
  284. psychopy/app/Resources/classic/preferences-hardware@2x.png +0 -0
  285. psychopy/app/Resources/classic/preferences-keyboard.png +0 -0
  286. psychopy/app/Resources/classic/preferences-keyboard48.png +0 -0
  287. psychopy/app/Resources/classic/preferences-keyboard@2x.png +0 -0
  288. psychopy/app/Resources/classic/preferences-pilot.png +0 -0
  289. psychopy/app/Resources/classic/preferences-pilot@2x.png +0 -0
  290. psychopy/app/Resources/classic/preferences32.png +0 -0
  291. psychopy/app/Resources/classic/pyPilot.png +0 -0
  292. psychopy/app/Resources/classic/pyPilot@2x.png +0 -0
  293. psychopy/app/Resources/classic/pyRun.png +0 -0
  294. psychopy/app/Resources/classic/pyRun@2x.png +0 -0
  295. psychopy/app/Resources/classic/reddot.png +0 -0
  296. psychopy/app/Resources/classic/reddot@2x.png +0 -0
  297. psychopy/app/Resources/classic/redglobe.png +0 -0
  298. psychopy/app/Resources/classic/redglobe@2x.png +0 -0
  299. psychopy/app/Resources/classic/redglobe_bug.png +0 -0
  300. psychopy/app/Resources/classic/redglobe_bug@2x.png +0 -0
  301. psychopy/app/Resources/classic/redglobe_greensync.png +0 -0
  302. psychopy/app/Resources/classic/redglobe_greensync@2x.png +0 -0
  303. psychopy/app/Resources/classic/redglobe_info.png +0 -0
  304. psychopy/app/Resources/classic/redglobe_info@2x.png +0 -0
  305. psychopy/app/Resources/classic/redglobe_magnifier.png +0 -0
  306. psychopy/app/Resources/classic/redglobe_magnifier@2x.png +0 -0
  307. psychopy/app/Resources/classic/redglobe_run.png +0 -0
  308. psychopy/app/Resources/classic/redglobe_run@2x.png +0 -0
  309. psychopy/app/Resources/classic/redglobe_user.png +0 -0
  310. psychopy/app/Resources/classic/redglobe_user@2x.png +0 -0
  311. psychopy/app/Resources/classic/redo.png +0 -0
  312. psychopy/app/Resources/classic/redo32.png +0 -0
  313. psychopy/app/Resources/classic/redo@2x.png +0 -0
  314. psychopy/app/Resources/classic/regex.png +0 -0
  315. psychopy/app/Resources/classic/regex@2x.png +0 -0
  316. psychopy/app/Resources/classic/removeExp32.png +0 -0
  317. psychopy/app/Resources/classic/rename16.png +0 -0
  318. psychopy/app/Resources/classic/restart.png +0 -0
  319. psychopy/app/Resources/classic/restart@2x.png +0 -0
  320. psychopy/app/Resources/classic/run.png +0 -0
  321. psychopy/app/Resources/classic/run@2x.png +0 -0
  322. psychopy/app/Resources/classic/runner.png +0 -0
  323. psychopy/app/Resources/classic/runner@2x.png +0 -0
  324. psychopy/app/Resources/classic/runnerPilot.png +0 -0
  325. psychopy/app/Resources/classic/runnerPilot@2x.png +0 -0
  326. psychopy/app/Resources/classic/savebtn.png +0 -0
  327. psychopy/app/Resources/classic/savebtn16.png +0 -0
  328. psychopy/app/Resources/classic/savebtn16@2x.png +0 -0
  329. psychopy/app/Resources/classic/savebtn@2x.png +0 -0
  330. psychopy/app/Resources/classic/search.png +0 -0
  331. psychopy/app/Resources/classic/search@2x.png +0 -0
  332. psychopy/app/Resources/classic/showBuilder.png +0 -0
  333. psychopy/app/Resources/classic/showBuilder@2x.png +0 -0
  334. psychopy/app/Resources/classic/showCoder.png +0 -0
  335. psychopy/app/Resources/classic/showCoder@2x.png +0 -0
  336. psychopy/app/Resources/classic/showRunner.png +0 -0
  337. psychopy/app/Resources/classic/showRunner@2x.png +0 -0
  338. psychopy/app/Resources/classic/starred.png +0 -0
  339. psychopy/app/Resources/classic/starred@2x.png +0 -0
  340. psychopy/app/Resources/classic/start.png +0 -0
  341. psychopy/app/Resources/classic/start@2x.png +0 -0
  342. psychopy/app/Resources/classic/stdout.png +0 -0
  343. psychopy/app/Resources/classic/stop.png +0 -0
  344. psychopy/app/Resources/classic/stop32.png +0 -0
  345. psychopy/app/Resources/classic/stop@2x.png +0 -0
  346. psychopy/app/Resources/classic/switchCtrlBot.png +0 -0
  347. psychopy/app/Resources/classic/switchCtrlBot@2x.png +0 -0
  348. psychopy/app/Resources/classic/switchCtrlLeft.png +0 -0
  349. psychopy/app/Resources/classic/switchCtrlLeft@2x.png +0 -0
  350. psychopy/app/Resources/classic/switchCtrlRight.png +0 -0
  351. psychopy/app/Resources/classic/switchCtrlRight@2x.png +0 -0
  352. psychopy/app/Resources/classic/switchCtrlTop.png +0 -0
  353. psychopy/app/Resources/classic/switchCtrlTop@2x.png +0 -0
  354. psychopy/app/Resources/classic/tick.png +0 -0
  355. psychopy/app/Resources/classic/tick@2x.png +0 -0
  356. psychopy/app/Resources/classic/undo.png +0 -0
  357. psychopy/app/Resources/classic/undo32.png +0 -0
  358. psychopy/app/Resources/classic/undo@2x.png +0 -0
  359. psychopy/app/Resources/classic/unstarred.png +0 -0
  360. psychopy/app/Resources/classic/unstarred@2x.png +0 -0
  361. psychopy/app/Resources/classic/user_none.png +0 -0
  362. psychopy/app/Resources/classic/view-refresh16.png +0 -0
  363. psychopy/app/Resources/classic/viewbtn.png +0 -0
  364. psychopy/app/Resources/classic/viewbtn16.png +0 -0
  365. psychopy/app/Resources/classic/viewbtn16@2x.png +0 -0
  366. psychopy/app/Resources/classic/viewbtn@2x.png +0 -0
  367. psychopy/app/Resources/classic/windows16.png +0 -0
  368. psychopy/app/Resources/classic/windows16@2x.png +0 -0
  369. psychopy/app/Resources/coder.ico +0 -0
  370. psychopy/app/Resources/dark/FlowBottom_CompLeft.png +0 -0
  371. psychopy/app/Resources/dark/FlowBottom_CompRight.png +0 -0
  372. psychopy/app/Resources/dark/FlowTop_CompLeft.png +0 -0
  373. psychopy/app/Resources/dark/FlowTop_CompRight.png +0 -0
  374. psychopy/app/Resources/dark/README.txt +3 -0
  375. psychopy/app/Resources/dark/__init__.py +0 -0
  376. psychopy/app/Resources/dark/add.png +0 -0
  377. psychopy/app/Resources/dark/add@2x.png +0 -0
  378. psychopy/app/Resources/dark/addExp32.png +0 -0
  379. psychopy/app/Resources/dark/addExp32@2x.png +0 -0
  380. psychopy/app/Resources/dark/add_many.png +0 -0
  381. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  382. psychopy/app/Resources/dark/alerts.png +0 -0
  383. psychopy/app/Resources/dark/alerts@2x.png +0 -0
  384. psychopy/app/Resources/dark/beta.png +0 -0
  385. psychopy/app/Resources/dark/beta@2x.png +0 -0
  386. psychopy/app/Resources/dark/browser.png +0 -0
  387. psychopy/app/Resources/dark/browser@2x.png +0 -0
  388. psychopy/app/Resources/dark/bug16.png +0 -0
  389. psychopy/app/Resources/dark/bug16@2x.png +0 -0
  390. psychopy/app/Resources/dark/case.png +0 -0
  391. psychopy/app/Resources/dark/case@2x.png +0 -0
  392. psychopy/app/Resources/dark/circle_mask.png +0 -0
  393. psychopy/app/Resources/dark/circle_mask@2x.png +0 -0
  394. psychopy/app/Resources/dark/clear.png +0 -0
  395. psychopy/app/Resources/dark/clear@2x.png +0 -0
  396. psychopy/app/Resources/dark/coderclass16.png +0 -0
  397. psychopy/app/Resources/dark/coderclass16@2x.png +0 -0
  398. psychopy/app/Resources/dark/coderfunc16.png +0 -0
  399. psychopy/app/Resources/dark/coderfunc16@2x.png +0 -0
  400. psychopy/app/Resources/dark/coderimport16.png +0 -0
  401. psychopy/app/Resources/dark/coderimport16@2x.png +0 -0
  402. psychopy/app/Resources/dark/coderjs.png +0 -0
  403. psychopy/app/Resources/dark/coderjs@2x.png +0 -0
  404. psychopy/app/Resources/dark/coderpython.png +0 -0
  405. psychopy/app/Resources/dark/coderpython@2x.png +0 -0
  406. psychopy/app/Resources/dark/codervar16.png +0 -0
  407. psychopy/app/Resources/dark/codervar16@2x.png +0 -0
  408. psychopy/app/Resources/dark/cogwindow32.png +0 -0
  409. psychopy/app/Resources/dark/cogwindow32@2x.png +0 -0
  410. psychopy/app/Resources/dark/color32.png +0 -0
  411. psychopy/app/Resources/dark/color32@2x.png +0 -0
  412. psychopy/app/Resources/dark/compile_js.png +0 -0
  413. psychopy/app/Resources/dark/compile_js@2x.png +0 -0
  414. psychopy/app/Resources/dark/compile_py.png +0 -0
  415. psychopy/app/Resources/dark/compile_py@2x.png +0 -0
  416. psychopy/app/Resources/dark/copy16.png +0 -0
  417. psychopy/app/Resources/dark/copy16@2x.png +0 -0
  418. psychopy/app/Resources/dark/currentFile16.png +0 -0
  419. psychopy/app/Resources/dark/currentFile16@2x.png +0 -0
  420. psychopy/app/Resources/dark/delete16.png +0 -0
  421. psychopy/app/Resources/dark/delete16@2x.png +0 -0
  422. psychopy/app/Resources/dark/desktop.png +0 -0
  423. psychopy/app/Resources/dark/desktop@2x.png +0 -0
  424. psychopy/app/Resources/dark/devices.png +0 -0
  425. psychopy/app/Resources/dark/devices@2x.png +0 -0
  426. psychopy/app/Resources/dark/dirup16.png +0 -0
  427. psychopy/app/Resources/dark/dirup16@2x.png +0 -0
  428. psychopy/app/Resources/dark/docclose16.png +0 -0
  429. psychopy/app/Resources/dark/docclose16@2x.png +0 -0
  430. psychopy/app/Resources/dark/download.png +0 -0
  431. psychopy/app/Resources/dark/download@2x.png +0 -0
  432. psychopy/app/Resources/dark/edit.png +0 -0
  433. psychopy/app/Resources/dark/edit@2x.png +0 -0
  434. psychopy/app/Resources/dark/editbtn16.png +0 -0
  435. psychopy/app/Resources/dark/editbtn16@2x.png +0 -0
  436. psychopy/app/Resources/dark/email.png +0 -0
  437. psychopy/app/Resources/dark/email@2x.png +0 -0
  438. psychopy/app/Resources/dark/experiment.png +0 -0
  439. psychopy/app/Resources/dark/experiment@2x.png +0 -0
  440. psychopy/app/Resources/dark/expsettings.png +0 -0
  441. psychopy/app/Resources/dark/expsettings@2x.png +0 -0
  442. psychopy/app/Resources/dark/file.png +0 -0
  443. psychopy/app/Resources/dark/file@16w.png +0 -0
  444. psychopy/app/Resources/dark/file@16w@2x.png +0 -0
  445. psychopy/app/Resources/dark/file@2x.png +0 -0
  446. psychopy/app/Resources/dark/fileaudio.png +0 -0
  447. psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
  448. psychopy/app/Resources/dark/filecss.png +0 -0
  449. psychopy/app/Resources/dark/filecss@2x.png +0 -0
  450. psychopy/app/Resources/dark/filecsv.png +0 -0
  451. psychopy/app/Resources/dark/filecsv@2x.png +0 -0
  452. psychopy/app/Resources/dark/filedesign.png +0 -0
  453. psychopy/app/Resources/dark/filedesign@2x.png +0 -0
  454. psychopy/app/Resources/dark/filefont.png +0 -0
  455. psychopy/app/Resources/dark/filefont@2x.png +0 -0
  456. psychopy/app/Resources/dark/filegit.png +0 -0
  457. psychopy/app/Resources/dark/filegit@2x.png +0 -0
  458. psychopy/app/Resources/dark/filehtml.png +0 -0
  459. psychopy/app/Resources/dark/filehtml@2x.png +0 -0
  460. psychopy/app/Resources/dark/fileimage.png +0 -0
  461. psychopy/app/Resources/dark/fileimage@2x.png +0 -0
  462. psychopy/app/Resources/dark/fileinfo.png +0 -0
  463. psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
  464. psychopy/app/Resources/dark/filejs.png +0 -0
  465. psychopy/app/Resources/dark/filejs@2x.png +0 -0
  466. psychopy/app/Resources/dark/filejson.png +0 -0
  467. psychopy/app/Resources/dark/filejson@2x.png +0 -0
  468. psychopy/app/Resources/dark/filenew.png +0 -0
  469. psychopy/app/Resources/dark/filenew32.png +0 -0
  470. psychopy/app/Resources/dark/filenew32@2x.png +0 -0
  471. psychopy/app/Resources/dark/filenew@2x.png +0 -0
  472. psychopy/app/Resources/dark/fileopen.png +0 -0
  473. psychopy/app/Resources/dark/fileopen32.png +0 -0
  474. psychopy/app/Resources/dark/fileopen32@2x.png +0 -0
  475. psychopy/app/Resources/dark/fileopen@2x.png +0 -0
  476. psychopy/app/Resources/dark/filepkg.png +0 -0
  477. psychopy/app/Resources/dark/filepkg@2x.png +0 -0
  478. psychopy/app/Resources/dark/filepsyexp.png +0 -0
  479. psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
  480. psychopy/app/Resources/dark/filepy.png +0 -0
  481. psychopy/app/Resources/dark/filepy@2x.png +0 -0
  482. psychopy/app/Resources/dark/filesave.png +0 -0
  483. psychopy/app/Resources/dark/filesave32.png +0 -0
  484. psychopy/app/Resources/dark/filesave32@2x.png +0 -0
  485. psychopy/app/Resources/dark/filesave@2x.png +0 -0
  486. psychopy/app/Resources/dark/filesaveas.png +0 -0
  487. psychopy/app/Resources/dark/filesaveas32.png +0 -0
  488. psychopy/app/Resources/dark/filesaveas32@2x.png +0 -0
  489. psychopy/app/Resources/dark/filesaveas@2x.png +0 -0
  490. psychopy/app/Resources/dark/filetxt.png +0 -0
  491. psychopy/app/Resources/dark/filetxt@2x.png +0 -0
  492. psychopy/app/Resources/dark/fileunknown.png +0 -0
  493. psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
  494. psychopy/app/Resources/dark/filevideo.png +0 -0
  495. psychopy/app/Resources/dark/filevideo@2x.png +0 -0
  496. psychopy/app/Resources/dark/filter.png +0 -0
  497. psychopy/app/Resources/dark/filter@2x.png +0 -0
  498. psychopy/app/Resources/dark/find.png +0 -0
  499. psychopy/app/Resources/dark/find@2x.png +0 -0
  500. psychopy/app/Resources/dark/folder-open16.png +0 -0
  501. psychopy/app/Resources/dark/folder-open16@2x.png +0 -0
  502. psychopy/app/Resources/dark/folder16.png +0 -0
  503. psychopy/app/Resources/dark/folder16@2x.png +0 -0
  504. psychopy/app/Resources/dark/foldernew16.png +0 -0
  505. psychopy/app/Resources/dark/foldernew16@2x.png +0 -0
  506. psychopy/app/Resources/dark/fork.png +0 -0
  507. psychopy/app/Resources/dark/fork@2x.png +0 -0
  508. psychopy/app/Resources/dark/github.png +0 -0
  509. psychopy/app/Resources/dark/github@2x.png +0 -0
  510. psychopy/app/Resources/dark/globe.png +0 -0
  511. psychopy/app/Resources/dark/globe@2x.png +0 -0
  512. psychopy/app/Resources/dark/globe_bug.png +0 -0
  513. psychopy/app/Resources/dark/globe_bug@2x.png +0 -0
  514. psychopy/app/Resources/dark/globe_greensync.png +0 -0
  515. psychopy/app/Resources/dark/globe_greensync@2x.png +0 -0
  516. psychopy/app/Resources/dark/globe_info.png +0 -0
  517. psychopy/app/Resources/dark/globe_info@2x.png +0 -0
  518. psychopy/app/Resources/dark/globe_magnifier.png +0 -0
  519. psychopy/app/Resources/dark/globe_magnifier@2x.png +0 -0
  520. psychopy/app/Resources/dark/globe_run.png +0 -0
  521. psychopy/app/Resources/dark/globe_run@2x.png +0 -0
  522. psychopy/app/Resources/dark/globe_user.png +0 -0
  523. psychopy/app/Resources/dark/globe_user@2x.png +0 -0
  524. psychopy/app/Resources/dark/goto.png +0 -0
  525. psychopy/app/Resources/dark/goto@2x.png +0 -0
  526. psychopy/app/Resources/dark/greendot.png +0 -0
  527. psychopy/app/Resources/dark/greendot@2x.png +0 -0
  528. psychopy/app/Resources/dark/greenglobe.png +0 -0
  529. psychopy/app/Resources/dark/greenglobe@2x.png +0 -0
  530. psychopy/app/Resources/dark/greenglobe_bug.png +0 -0
  531. psychopy/app/Resources/dark/greenglobe_bug@2x.png +0 -0
  532. psychopy/app/Resources/dark/greenglobe_greensync.png +0 -0
  533. psychopy/app/Resources/dark/greenglobe_greensync@2x.png +0 -0
  534. psychopy/app/Resources/dark/greenglobe_info.png +0 -0
  535. psychopy/app/Resources/dark/greenglobe_info@2x.png +0 -0
  536. psychopy/app/Resources/dark/greenglobe_magnifier.png +0 -0
  537. psychopy/app/Resources/dark/greenglobe_magnifier@2x.png +0 -0
  538. psychopy/app/Resources/dark/greenglobe_run.png +0 -0
  539. psychopy/app/Resources/dark/greenglobe_run@2x.png +0 -0
  540. psychopy/app/Resources/dark/greenglobe_user.png +0 -0
  541. psychopy/app/Resources/dark/greenglobe_user@2x.png +0 -0
  542. psychopy/app/Resources/dark/greydot.png +0 -0
  543. psychopy/app/Resources/dark/greydot@2x.png +0 -0
  544. psychopy/app/Resources/dark/greytick.png +0 -0
  545. psychopy/app/Resources/dark/greytick@2x.png +0 -0
  546. psychopy/app/Resources/dark/invalid_img.png +0 -0
  547. psychopy/app/Resources/dark/jsPilot.png +0 -0
  548. psychopy/app/Resources/dark/jsPilot@2x.png +0 -0
  549. psychopy/app/Resources/dark/jsRun.png +0 -0
  550. psychopy/app/Resources/dark/jsRun@2x.png +0 -0
  551. psychopy/app/Resources/dark/libroot16.png +0 -0
  552. psychopy/app/Resources/dark/libroot16@2x.png +0 -0
  553. psychopy/app/Resources/dark/loop.png +0 -0
  554. psychopy/app/Resources/dark/loop@2x.png +0 -0
  555. psychopy/app/Resources/dark/monitor16.png +0 -0
  556. psychopy/app/Resources/dark/monitor16@2x.png +0 -0
  557. psychopy/app/Resources/dark/monitors.png +0 -0
  558. psychopy/app/Resources/dark/monitors32.png +0 -0
  559. psychopy/app/Resources/dark/monitors32@2x.png +0 -0
  560. psychopy/app/Resources/dark/monitors@2x.png +0 -0
  561. psychopy/app/Resources/dark/orangedot.png +0 -0
  562. psychopy/app/Resources/dark/orangedot@2x.png +0 -0
  563. psychopy/app/Resources/dark/pavlovia.png +0 -0
  564. psychopy/app/Resources/dark/pavlovia16.png +0 -0
  565. psychopy/app/Resources/dark/pavlovia16@2x.png +0 -0
  566. psychopy/app/Resources/dark/pavlovia@2x.png +0 -0
  567. psychopy/app/Resources/dark/pavsync.png +0 -0
  568. psychopy/app/Resources/dark/pavsync@2x.png +0 -0
  569. psychopy/app/Resources/dark/person_off.png +0 -0
  570. psychopy/app/Resources/dark/person_off@2x.png +0 -0
  571. psychopy/app/Resources/dark/person_on.png +0 -0
  572. psychopy/app/Resources/dark/person_on@2x.png +0 -0
  573. psychopy/app/Resources/dark/photometer.png +0 -0
  574. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  575. psychopy/app/Resources/dark/plugin16.png +0 -0
  576. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  577. psychopy/app/Resources/dark/plugins32.png +0 -0
  578. psychopy/app/Resources/dark/plugins32@2x.png +0 -0
  579. psychopy/app/Resources/dark/plus.png +0 -0
  580. psychopy/app/Resources/dark/plus@2x.png +0 -0
  581. psychopy/app/Resources/dark/preferences-app.png +0 -0
  582. psychopy/app/Resources/dark/preferences-app48.png +0 -0
  583. psychopy/app/Resources/dark/preferences-app48@2x.png +0 -0
  584. psychopy/app/Resources/dark/preferences-app@2x.png +0 -0
  585. psychopy/app/Resources/dark/preferences-conn.png +0 -0
  586. psychopy/app/Resources/dark/preferences-conn48.png +0 -0
  587. psychopy/app/Resources/dark/preferences-conn48@2x.png +0 -0
  588. psychopy/app/Resources/dark/preferences-conn@2x.png +0 -0
  589. psychopy/app/Resources/dark/preferences-debug.png +0 -0
  590. psychopy/app/Resources/dark/preferences-debug@2x.png +0 -0
  591. psychopy/app/Resources/dark/preferences-general.png +0 -0
  592. psychopy/app/Resources/dark/preferences-general48.png +0 -0
  593. psychopy/app/Resources/dark/preferences-general48@2x.png +0 -0
  594. psychopy/app/Resources/dark/preferences-general@2x.png +0 -0
  595. psychopy/app/Resources/dark/preferences-hardware.png +0 -0
  596. psychopy/app/Resources/dark/preferences-hardware48.png +0 -0
  597. psychopy/app/Resources/dark/preferences-hardware48@2x.png +0 -0
  598. psychopy/app/Resources/dark/preferences-hardware@2x.png +0 -0
  599. psychopy/app/Resources/dark/preferences-keyboard.png +0 -0
  600. psychopy/app/Resources/dark/preferences-keyboard48.png +0 -0
  601. psychopy/app/Resources/dark/preferences-keyboard48@2x.png +0 -0
  602. psychopy/app/Resources/dark/preferences-keyboard@2x.png +0 -0
  603. psychopy/app/Resources/dark/preferences-pilot.png +0 -0
  604. psychopy/app/Resources/dark/preferences-pilot@2x.png +0 -0
  605. psychopy/app/Resources/dark/preferences32.png +0 -0
  606. psychopy/app/Resources/dark/preferences32@2x.png +0 -0
  607. psychopy/app/Resources/dark/pyPilot.png +0 -0
  608. psychopy/app/Resources/dark/pyPilot@2x.png +0 -0
  609. psychopy/app/Resources/dark/pyRun.png +0 -0
  610. psychopy/app/Resources/dark/pyRun@2x.png +0 -0
  611. psychopy/app/Resources/dark/reddot.png +0 -0
  612. psychopy/app/Resources/dark/reddot@2x.png +0 -0
  613. psychopy/app/Resources/dark/redglobe.png +0 -0
  614. psychopy/app/Resources/dark/redglobe@2x.png +0 -0
  615. psychopy/app/Resources/dark/redglobe_bug.png +0 -0
  616. psychopy/app/Resources/dark/redglobe_bug@2x.png +0 -0
  617. psychopy/app/Resources/dark/redglobe_greensync.png +0 -0
  618. psychopy/app/Resources/dark/redglobe_greensync@2x.png +0 -0
  619. psychopy/app/Resources/dark/redglobe_info.png +0 -0
  620. psychopy/app/Resources/dark/redglobe_info@2x.png +0 -0
  621. psychopy/app/Resources/dark/redglobe_magnifier.png +0 -0
  622. psychopy/app/Resources/dark/redglobe_magnifier@2x.png +0 -0
  623. psychopy/app/Resources/dark/redglobe_run.png +0 -0
  624. psychopy/app/Resources/dark/redglobe_run@2x.png +0 -0
  625. psychopy/app/Resources/dark/redglobe_user.png +0 -0
  626. psychopy/app/Resources/dark/redglobe_user@2x.png +0 -0
  627. psychopy/app/Resources/dark/redo.png +0 -0
  628. psychopy/app/Resources/dark/redo32.png +0 -0
  629. psychopy/app/Resources/dark/redo32@2x.png +0 -0
  630. psychopy/app/Resources/dark/redo@2x.png +0 -0
  631. psychopy/app/Resources/dark/regex.png +0 -0
  632. psychopy/app/Resources/dark/regex@2x.png +0 -0
  633. psychopy/app/Resources/dark/removeExp32.png +0 -0
  634. psychopy/app/Resources/dark/removeExp32@2x.png +0 -0
  635. psychopy/app/Resources/dark/rename16.png +0 -0
  636. psychopy/app/Resources/dark/rename16@2x.png +0 -0
  637. psychopy/app/Resources/dark/restart.png +0 -0
  638. psychopy/app/Resources/dark/restart@2x.png +0 -0
  639. psychopy/app/Resources/dark/runner.png +0 -0
  640. psychopy/app/Resources/dark/runner@2x.png +0 -0
  641. psychopy/app/Resources/dark/runnerPilot.png +0 -0
  642. psychopy/app/Resources/dark/runnerPilot@2x.png +0 -0
  643. psychopy/app/Resources/dark/savebtn16.png +0 -0
  644. psychopy/app/Resources/dark/savebtn16@2x.png +0 -0
  645. psychopy/app/Resources/dark/search.png +0 -0
  646. psychopy/app/Resources/dark/search@2x.png +0 -0
  647. psychopy/app/Resources/dark/showBuilder.png +0 -0
  648. psychopy/app/Resources/dark/showBuilder@2x.png +0 -0
  649. psychopy/app/Resources/dark/showCoder.png +0 -0
  650. psychopy/app/Resources/dark/showCoder@2x.png +0 -0
  651. psychopy/app/Resources/dark/showRunner.png +0 -0
  652. psychopy/app/Resources/dark/showRunner@2x.png +0 -0
  653. psychopy/app/Resources/dark/starred.png +0 -0
  654. psychopy/app/Resources/dark/starred@2x.png +0 -0
  655. psychopy/app/Resources/dark/start.png +0 -0
  656. psychopy/app/Resources/dark/start@2x.png +0 -0
  657. psychopy/app/Resources/dark/stdout.png +0 -0
  658. psychopy/app/Resources/dark/stdout@2x.png +0 -0
  659. psychopy/app/Resources/dark/stop.png +0 -0
  660. psychopy/app/Resources/dark/stop32.png +0 -0
  661. psychopy/app/Resources/dark/stop32@2x.png +0 -0
  662. psychopy/app/Resources/dark/stop@2x.png +0 -0
  663. psychopy/app/Resources/dark/switchCtrlBot.png +0 -0
  664. psychopy/app/Resources/dark/switchCtrlBot@2x.png +0 -0
  665. psychopy/app/Resources/dark/switchCtrlLeft.png +0 -0
  666. psychopy/app/Resources/dark/switchCtrlLeft@2x.png +0 -0
  667. psychopy/app/Resources/dark/switchCtrlRight.png +0 -0
  668. psychopy/app/Resources/dark/switchCtrlRight@2x.png +0 -0
  669. psychopy/app/Resources/dark/switchCtrlTop.png +0 -0
  670. psychopy/app/Resources/dark/switchCtrlTop@2x.png +0 -0
  671. psychopy/app/Resources/dark/tick.png +0 -0
  672. psychopy/app/Resources/dark/tick@2x.png +0 -0
  673. psychopy/app/Resources/dark/undo.png +0 -0
  674. psychopy/app/Resources/dark/undo32.png +0 -0
  675. psychopy/app/Resources/dark/undo32@2x.png +0 -0
  676. psychopy/app/Resources/dark/undo@2x.png +0 -0
  677. psychopy/app/Resources/dark/unstarred.png +0 -0
  678. psychopy/app/Resources/dark/unstarred@2x.png +0 -0
  679. psychopy/app/Resources/dark/user_none.png +0 -0
  680. psychopy/app/Resources/dark/view-refresh16.png +0 -0
  681. psychopy/app/Resources/dark/view-refresh16@2x.png +0 -0
  682. psychopy/app/Resources/dark/viewbtn16.png +0 -0
  683. psychopy/app/Resources/dark/viewbtn16@2x.png +0 -0
  684. psychopy/app/Resources/dark/windows16.png +0 -0
  685. psychopy/app/Resources/dark/windows16@2x.png +0 -0
  686. psychopy/app/Resources/light/FlowBottom_CompLeft.png +0 -0
  687. psychopy/app/Resources/light/FlowBottom_CompRight.png +0 -0
  688. psychopy/app/Resources/light/FlowTop_CompLeft.png +0 -0
  689. psychopy/app/Resources/light/FlowTop_CompRight.png +0 -0
  690. psychopy/app/Resources/light/README.txt +3 -0
  691. psychopy/app/Resources/light/__init__.py +0 -0
  692. psychopy/app/Resources/light/add.png +0 -0
  693. psychopy/app/Resources/light/add@2x.png +0 -0
  694. psychopy/app/Resources/light/addExp32.png +0 -0
  695. psychopy/app/Resources/light/addExp32@2x.png +0 -0
  696. psychopy/app/Resources/light/add_many.png +0 -0
  697. psychopy/app/Resources/light/add_many@2x.png +0 -0
  698. psychopy/app/Resources/light/alerts.png +0 -0
  699. psychopy/app/Resources/light/alerts@2x.png +0 -0
  700. psychopy/app/Resources/light/beta.png +0 -0
  701. psychopy/app/Resources/light/beta@2x.png +0 -0
  702. psychopy/app/Resources/light/browser.png +0 -0
  703. psychopy/app/Resources/light/browser@2x.png +0 -0
  704. psychopy/app/Resources/light/bug16.png +0 -0
  705. psychopy/app/Resources/light/bug16@2x.png +0 -0
  706. psychopy/app/Resources/light/case.png +0 -0
  707. psychopy/app/Resources/light/case@2x.png +0 -0
  708. psychopy/app/Resources/light/circle_mask.png +0 -0
  709. psychopy/app/Resources/light/circle_mask@2x.png +0 -0
  710. psychopy/app/Resources/light/clear.png +0 -0
  711. psychopy/app/Resources/light/clear@2x.png +0 -0
  712. psychopy/app/Resources/light/coderclass16.png +0 -0
  713. psychopy/app/Resources/light/coderclass16@2x.png +0 -0
  714. psychopy/app/Resources/light/coderfunc16.png +0 -0
  715. psychopy/app/Resources/light/coderfunc16@2x.png +0 -0
  716. psychopy/app/Resources/light/coderimport16.png +0 -0
  717. psychopy/app/Resources/light/coderimport16@2x.png +0 -0
  718. psychopy/app/Resources/light/coderjs.png +0 -0
  719. psychopy/app/Resources/light/coderjs@2x.png +0 -0
  720. psychopy/app/Resources/light/coderpython.png +0 -0
  721. psychopy/app/Resources/light/coderpython@2x.png +0 -0
  722. psychopy/app/Resources/light/codervar16.png +0 -0
  723. psychopy/app/Resources/light/codervar16@2x.png +0 -0
  724. psychopy/app/Resources/light/cogwindow32.png +0 -0
  725. psychopy/app/Resources/light/cogwindow32@2x.png +0 -0
  726. psychopy/app/Resources/light/color16.png +0 -0
  727. psychopy/app/Resources/light/color16@2x.png +0 -0
  728. psychopy/app/Resources/light/color32.png +0 -0
  729. psychopy/app/Resources/light/color32@2x.png +0 -0
  730. psychopy/app/Resources/light/compile_js.png +0 -0
  731. psychopy/app/Resources/light/compile_js@2x.png +0 -0
  732. psychopy/app/Resources/light/compile_py.png +0 -0
  733. psychopy/app/Resources/light/compile_py@2x.png +0 -0
  734. psychopy/app/Resources/light/copy16.png +0 -0
  735. psychopy/app/Resources/light/copy16@2x.png +0 -0
  736. psychopy/app/Resources/light/currentFile16.png +0 -0
  737. psychopy/app/Resources/light/currentFile16@2x.png +0 -0
  738. psychopy/app/Resources/light/delete16.png +0 -0
  739. psychopy/app/Resources/light/delete16@2x.png +0 -0
  740. psychopy/app/Resources/light/delete8.png +0 -0
  741. psychopy/app/Resources/light/desktop.png +0 -0
  742. psychopy/app/Resources/light/desktop@2x.png +0 -0
  743. psychopy/app/Resources/light/devices.png +0 -0
  744. psychopy/app/Resources/light/devices@2x.png +0 -0
  745. psychopy/app/Resources/light/dirup16.png +0 -0
  746. psychopy/app/Resources/light/dirup16@2x.png +0 -0
  747. psychopy/app/Resources/light/docclose16.png +0 -0
  748. psychopy/app/Resources/light/docclose16@2x.png +0 -0
  749. psychopy/app/Resources/light/download.png +0 -0
  750. psychopy/app/Resources/light/download@2x.png +0 -0
  751. psychopy/app/Resources/light/edit.png +0 -0
  752. psychopy/app/Resources/light/edit@2x.png +0 -0
  753. psychopy/app/Resources/light/editbtn16.png +0 -0
  754. psychopy/app/Resources/light/editbtn16@2x.png +0 -0
  755. psychopy/app/Resources/light/email.png +0 -0
  756. psychopy/app/Resources/light/email@2x.png +0 -0
  757. psychopy/app/Resources/light/experiment.png +0 -0
  758. psychopy/app/Resources/light/experiment@2x.png +0 -0
  759. psychopy/app/Resources/light/expsettings.png +0 -0
  760. psychopy/app/Resources/light/expsettings@2x.png +0 -0
  761. psychopy/app/Resources/light/file.png +0 -0
  762. psychopy/app/Resources/light/file@2x.png +0 -0
  763. psychopy/app/Resources/light/fileaudio.png +0 -0
  764. psychopy/app/Resources/light/fileaudio@2x.png +0 -0
  765. psychopy/app/Resources/light/filecss.png +0 -0
  766. psychopy/app/Resources/light/filecss@2x.png +0 -0
  767. psychopy/app/Resources/light/filecsv.png +0 -0
  768. psychopy/app/Resources/light/filecsv@2x.png +0 -0
  769. psychopy/app/Resources/light/filedesign.png +0 -0
  770. psychopy/app/Resources/light/filedesign@2x.png +0 -0
  771. psychopy/app/Resources/light/filefont.png +0 -0
  772. psychopy/app/Resources/light/filefont@2x.png +0 -0
  773. psychopy/app/Resources/light/filegit.png +0 -0
  774. psychopy/app/Resources/light/filegit@2x.png +0 -0
  775. psychopy/app/Resources/light/filehtml.png +0 -0
  776. psychopy/app/Resources/light/filehtml@2x.png +0 -0
  777. psychopy/app/Resources/light/fileimage.png +0 -0
  778. psychopy/app/Resources/light/fileimage@2x.png +0 -0
  779. psychopy/app/Resources/light/fileinfo.png +0 -0
  780. psychopy/app/Resources/light/fileinfo@2x.png +0 -0
  781. psychopy/app/Resources/light/filejs.png +0 -0
  782. psychopy/app/Resources/light/filejs@2x.png +0 -0
  783. psychopy/app/Resources/light/filejson.png +0 -0
  784. psychopy/app/Resources/light/filejson@2x.png +0 -0
  785. psychopy/app/Resources/light/filenew.png +0 -0
  786. psychopy/app/Resources/light/filenew32.png +0 -0
  787. psychopy/app/Resources/light/filenew32@2x.png +0 -0
  788. psychopy/app/Resources/light/filenew@2x.png +0 -0
  789. psychopy/app/Resources/light/fileopen.png +0 -0
  790. psychopy/app/Resources/light/fileopen32.png +0 -0
  791. psychopy/app/Resources/light/fileopen32@2x.png +0 -0
  792. psychopy/app/Resources/light/fileopen@2x.png +0 -0
  793. psychopy/app/Resources/light/filepkg.png +0 -0
  794. psychopy/app/Resources/light/filepkg@2x.png +0 -0
  795. psychopy/app/Resources/light/filepsyexp.png +0 -0
  796. psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
  797. psychopy/app/Resources/light/filepy.png +0 -0
  798. psychopy/app/Resources/light/filepy@2x.png +0 -0
  799. psychopy/app/Resources/light/filesave.png +0 -0
  800. psychopy/app/Resources/light/filesave32.png +0 -0
  801. psychopy/app/Resources/light/filesave32@2x.png +0 -0
  802. psychopy/app/Resources/light/filesave@2x.png +0 -0
  803. psychopy/app/Resources/light/filesaveas.png +0 -0
  804. psychopy/app/Resources/light/filesaveas32.png +0 -0
  805. psychopy/app/Resources/light/filesaveas32@2x.png +0 -0
  806. psychopy/app/Resources/light/filesaveas@2x.png +0 -0
  807. psychopy/app/Resources/light/filetxt.png +0 -0
  808. psychopy/app/Resources/light/filetxt@2x.png +0 -0
  809. psychopy/app/Resources/light/fileunknown.png +0 -0
  810. psychopy/app/Resources/light/fileunknown@2x.png +0 -0
  811. psychopy/app/Resources/light/filevideo.png +0 -0
  812. psychopy/app/Resources/light/filevideo@2x.png +0 -0
  813. psychopy/app/Resources/light/filter.png +0 -0
  814. psychopy/app/Resources/light/filter@2x.png +0 -0
  815. psychopy/app/Resources/light/find.png +0 -0
  816. psychopy/app/Resources/light/find@2x.png +0 -0
  817. psychopy/app/Resources/light/folder-open16.png +0 -0
  818. psychopy/app/Resources/light/folder-open16@2x.png +0 -0
  819. psychopy/app/Resources/light/folder16.png +0 -0
  820. psychopy/app/Resources/light/folder16@2x.png +0 -0
  821. psychopy/app/Resources/light/foldernew16.png +0 -0
  822. psychopy/app/Resources/light/foldernew16@2x.png +0 -0
  823. psychopy/app/Resources/light/fork.png +0 -0
  824. psychopy/app/Resources/light/fork@2x.png +0 -0
  825. psychopy/app/Resources/light/github.png +0 -0
  826. psychopy/app/Resources/light/github@2x.png +0 -0
  827. psychopy/app/Resources/light/globe.png +0 -0
  828. psychopy/app/Resources/light/globe@2x.png +0 -0
  829. psychopy/app/Resources/light/globe_bug.png +0 -0
  830. psychopy/app/Resources/light/globe_bug@2x.png +0 -0
  831. psychopy/app/Resources/light/globe_greensync.png +0 -0
  832. psychopy/app/Resources/light/globe_greensync@2x.png +0 -0
  833. psychopy/app/Resources/light/globe_info.png +0 -0
  834. psychopy/app/Resources/light/globe_info@2x.png +0 -0
  835. psychopy/app/Resources/light/globe_magnifier.png +0 -0
  836. psychopy/app/Resources/light/globe_magnifier@2x.png +0 -0
  837. psychopy/app/Resources/light/globe_run.png +0 -0
  838. psychopy/app/Resources/light/globe_run@2x.png +0 -0
  839. psychopy/app/Resources/light/globe_user.png +0 -0
  840. psychopy/app/Resources/light/globe_user@2x.png +0 -0
  841. psychopy/app/Resources/light/goto16.png +0 -0
  842. psychopy/app/Resources/light/goto16@2x.png +0 -0
  843. psychopy/app/Resources/light/greendot.png +0 -0
  844. psychopy/app/Resources/light/greendot@2x.png +0 -0
  845. psychopy/app/Resources/light/greenglobe.png +0 -0
  846. psychopy/app/Resources/light/greenglobe@2x.png +0 -0
  847. psychopy/app/Resources/light/greenglobe_bug.png +0 -0
  848. psychopy/app/Resources/light/greenglobe_bug@2x.png +0 -0
  849. psychopy/app/Resources/light/greenglobe_greensync.png +0 -0
  850. psychopy/app/Resources/light/greenglobe_greensync@2x.png +0 -0
  851. psychopy/app/Resources/light/greenglobe_info.png +0 -0
  852. psychopy/app/Resources/light/greenglobe_info@2x.png +0 -0
  853. psychopy/app/Resources/light/greenglobe_magnifier.png +0 -0
  854. psychopy/app/Resources/light/greenglobe_magnifier@2x.png +0 -0
  855. psychopy/app/Resources/light/greenglobe_run.png +0 -0
  856. psychopy/app/Resources/light/greenglobe_run@2x.png +0 -0
  857. psychopy/app/Resources/light/greenglobe_user.png +0 -0
  858. psychopy/app/Resources/light/greenglobe_user@2x.png +0 -0
  859. psychopy/app/Resources/light/greydot.png +0 -0
  860. psychopy/app/Resources/light/greydot@2x.png +0 -0
  861. psychopy/app/Resources/light/greytick.png +0 -0
  862. psychopy/app/Resources/light/greytick@2x.png +0 -0
  863. psychopy/app/Resources/light/invalid_img.png +0 -0
  864. psychopy/app/Resources/light/jsPilot.png +0 -0
  865. psychopy/app/Resources/light/jsPilot@2x.png +0 -0
  866. psychopy/app/Resources/light/jsRun.png +0 -0
  867. psychopy/app/Resources/light/jsRun@2x.png +0 -0
  868. psychopy/app/Resources/light/libroot16.png +0 -0
  869. psychopy/app/Resources/light/libroot16@2x.png +0 -0
  870. psychopy/app/Resources/light/loop.png +0 -0
  871. psychopy/app/Resources/light/loop@2x.png +0 -0
  872. psychopy/app/Resources/light/monitor16.png +0 -0
  873. psychopy/app/Resources/light/monitor16@2x.png +0 -0
  874. psychopy/app/Resources/light/monitors.png +0 -0
  875. psychopy/app/Resources/light/monitors32.png +0 -0
  876. psychopy/app/Resources/light/monitors32@2x.png +0 -0
  877. psychopy/app/Resources/light/monitors@2x.png +0 -0
  878. psychopy/app/Resources/light/orangedot.png +0 -0
  879. psychopy/app/Resources/light/orangedot@2x.png +0 -0
  880. psychopy/app/Resources/light/pavlovia.png +0 -0
  881. psychopy/app/Resources/light/pavlovia16.png +0 -0
  882. psychopy/app/Resources/light/pavlovia16@2x.png +0 -0
  883. psychopy/app/Resources/light/pavlovia@2x.png +0 -0
  884. psychopy/app/Resources/light/pavsync.png +0 -0
  885. psychopy/app/Resources/light/pavsync@2x.png +0 -0
  886. psychopy/app/Resources/light/person_off.png +0 -0
  887. psychopy/app/Resources/light/person_off@2x.png +0 -0
  888. psychopy/app/Resources/light/person_on.png +0 -0
  889. psychopy/app/Resources/light/person_on@2x.png +0 -0
  890. psychopy/app/Resources/light/photometer.png +0 -0
  891. psychopy/app/Resources/light/photometer@2x.png +0 -0
  892. psychopy/app/Resources/light/plugin16.png +0 -0
  893. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  894. psychopy/app/Resources/light/plugins32.png +0 -0
  895. psychopy/app/Resources/light/plugins32@2x.png +0 -0
  896. psychopy/app/Resources/light/plus.png +0 -0
  897. psychopy/app/Resources/light/plus@2x.png +0 -0
  898. psychopy/app/Resources/light/preferences-app.png +0 -0
  899. psychopy/app/Resources/light/preferences-app48.png +0 -0
  900. psychopy/app/Resources/light/preferences-app48@2x.png +0 -0
  901. psychopy/app/Resources/light/preferences-app@2x.png +0 -0
  902. psychopy/app/Resources/light/preferences-conn.png +0 -0
  903. psychopy/app/Resources/light/preferences-conn48.png +0 -0
  904. psychopy/app/Resources/light/preferences-conn48@2x.png +0 -0
  905. psychopy/app/Resources/light/preferences-conn@2x.png +0 -0
  906. psychopy/app/Resources/light/preferences-general.png +0 -0
  907. psychopy/app/Resources/light/preferences-general48.png +0 -0
  908. psychopy/app/Resources/light/preferences-general48@2x.png +0 -0
  909. psychopy/app/Resources/light/preferences-general@2x.png +0 -0
  910. psychopy/app/Resources/light/preferences-hardware.png +0 -0
  911. psychopy/app/Resources/light/preferences-hardware48.png +0 -0
  912. psychopy/app/Resources/light/preferences-hardware48@2x.png +0 -0
  913. psychopy/app/Resources/light/preferences-hardware@2x.png +0 -0
  914. psychopy/app/Resources/light/preferences-keyboard.png +0 -0
  915. psychopy/app/Resources/light/preferences-keyboard48.png +0 -0
  916. psychopy/app/Resources/light/preferences-keyboard48@2x.png +0 -0
  917. psychopy/app/Resources/light/preferences-keyboard@2x.png +0 -0
  918. psychopy/app/Resources/light/preferences-pilot.png +0 -0
  919. psychopy/app/Resources/light/preferences-pilot@2x.png +0 -0
  920. psychopy/app/Resources/light/preferences32.png +0 -0
  921. psychopy/app/Resources/light/preferences32@2x.png +0 -0
  922. psychopy/app/Resources/light/pyPilot.png +0 -0
  923. psychopy/app/Resources/light/pyPilot@2x.png +0 -0
  924. psychopy/app/Resources/light/pyRun.png +0 -0
  925. psychopy/app/Resources/light/pyRun@2x.png +0 -0
  926. psychopy/app/Resources/light/reddot.png +0 -0
  927. psychopy/app/Resources/light/reddot@2x.png +0 -0
  928. psychopy/app/Resources/light/redglobe.png +0 -0
  929. psychopy/app/Resources/light/redglobe@2x.png +0 -0
  930. psychopy/app/Resources/light/redglobe_bug.png +0 -0
  931. psychopy/app/Resources/light/redglobe_bug@2x.png +0 -0
  932. psychopy/app/Resources/light/redglobe_greensync.png +0 -0
  933. psychopy/app/Resources/light/redglobe_greensync@2x.png +0 -0
  934. psychopy/app/Resources/light/redglobe_info.png +0 -0
  935. psychopy/app/Resources/light/redglobe_info@2x.png +0 -0
  936. psychopy/app/Resources/light/redglobe_magnifier.png +0 -0
  937. psychopy/app/Resources/light/redglobe_magnifier@2x.png +0 -0
  938. psychopy/app/Resources/light/redglobe_run.png +0 -0
  939. psychopy/app/Resources/light/redglobe_run@2x.png +0 -0
  940. psychopy/app/Resources/light/redglobe_user.png +0 -0
  941. psychopy/app/Resources/light/redglobe_user@2x.png +0 -0
  942. psychopy/app/Resources/light/redo.png +0 -0
  943. psychopy/app/Resources/light/redo32.png +0 -0
  944. psychopy/app/Resources/light/redo32@2x.png +0 -0
  945. psychopy/app/Resources/light/redo@2x.png +0 -0
  946. psychopy/app/Resources/light/regex.png +0 -0
  947. psychopy/app/Resources/light/regex@2x.png +0 -0
  948. psychopy/app/Resources/light/removeExp32.png +0 -0
  949. psychopy/app/Resources/light/removeExp32@2x.png +0 -0
  950. psychopy/app/Resources/light/rename16.png +0 -0
  951. psychopy/app/Resources/light/rename16@2x.png +0 -0
  952. psychopy/app/Resources/light/restart.png +0 -0
  953. psychopy/app/Resources/light/restart@2x.png +0 -0
  954. psychopy/app/Resources/light/runner.png +0 -0
  955. psychopy/app/Resources/light/runner@2x.png +0 -0
  956. psychopy/app/Resources/light/runnerPilot.png +0 -0
  957. psychopy/app/Resources/light/runnerPilot@2x.png +0 -0
  958. psychopy/app/Resources/light/savebtn16.png +0 -0
  959. psychopy/app/Resources/light/savebtn16@2x.png +0 -0
  960. psychopy/app/Resources/light/search.png +0 -0
  961. psychopy/app/Resources/light/search@2x.png +0 -0
  962. psychopy/app/Resources/light/showBuilder.png +0 -0
  963. psychopy/app/Resources/light/showBuilder@2x.png +0 -0
  964. psychopy/app/Resources/light/showCoder.png +0 -0
  965. psychopy/app/Resources/light/showCoder@2x.png +0 -0
  966. psychopy/app/Resources/light/showRunner.png +0 -0
  967. psychopy/app/Resources/light/showRunner@2x.png +0 -0
  968. psychopy/app/Resources/light/starred.png +0 -0
  969. psychopy/app/Resources/light/starred@2x.png +0 -0
  970. psychopy/app/Resources/light/start.png +0 -0
  971. psychopy/app/Resources/light/start@2x.png +0 -0
  972. psychopy/app/Resources/light/stdout.png +0 -0
  973. psychopy/app/Resources/light/stdout@2x.png +0 -0
  974. psychopy/app/Resources/light/stop.png +0 -0
  975. psychopy/app/Resources/light/stop32.png +0 -0
  976. psychopy/app/Resources/light/stop32@2x.png +0 -0
  977. psychopy/app/Resources/light/stop@2x.png +0 -0
  978. psychopy/app/Resources/light/switchCtrlBot.png +0 -0
  979. psychopy/app/Resources/light/switchCtrlBot@2x.png +0 -0
  980. psychopy/app/Resources/light/switchCtrlLeft.png +0 -0
  981. psychopy/app/Resources/light/switchCtrlLeft@2x.png +0 -0
  982. psychopy/app/Resources/light/switchCtrlRight.png +0 -0
  983. psychopy/app/Resources/light/switchCtrlRight@2x.png +0 -0
  984. psychopy/app/Resources/light/switchCtrlTop.png +0 -0
  985. psychopy/app/Resources/light/switchCtrlTop@2x.png +0 -0
  986. psychopy/app/Resources/light/tick.png +0 -0
  987. psychopy/app/Resources/light/tick@2x.png +0 -0
  988. psychopy/app/Resources/light/undo.png +0 -0
  989. psychopy/app/Resources/light/undo32.png +0 -0
  990. psychopy/app/Resources/light/undo32@2x.png +0 -0
  991. psychopy/app/Resources/light/undo@2x.png +0 -0
  992. psychopy/app/Resources/light/unstarred.png +0 -0
  993. psychopy/app/Resources/light/unstarred@2x.png +0 -0
  994. psychopy/app/Resources/light/user_none.png +0 -0
  995. psychopy/app/Resources/light/view-refresh16.png +0 -0
  996. psychopy/app/Resources/light/view-refresh16@2x.png +0 -0
  997. psychopy/app/Resources/light/viewbtn16.png +0 -0
  998. psychopy/app/Resources/light/viewbtn16@2x.png +0 -0
  999. psychopy/app/Resources/light/windows16.png +0 -0
  1000. psychopy/app/Resources/light/windows16@2x.png +0 -0
  1001. psychopy/app/Resources/moveComponentIcons.py +50 -0
  1002. psychopy/app/Resources/psychopy.desktop +13 -0
  1003. psychopy/app/Resources/psychopy.icns +0 -0
  1004. psychopy/app/Resources/psychopy.ico +0 -0
  1005. psychopy/app/Resources/psychopy.png +0 -0
  1006. psychopy/app/Resources/psychopy.xml +9 -0
  1007. psychopy/app/Resources/psychopy@2x.png +0 -0
  1008. psychopy/app/Resources/routine_templates/Basic.psyexp +344 -0
  1009. psychopy/app/Resources/routine_templates/Misc.psyexp +228 -0
  1010. psychopy/app/Resources/routine_templates/Online.psyexp +200 -0
  1011. psychopy/app/Resources/routine_templates/Trials.psyexp +735 -0
  1012. psychopy/app/Resources/routine_templates/readme.md +14 -0
  1013. psychopy/app/Resources/runner.ico +0 -0
  1014. psychopy/app/Resources/splash.png +0 -0
  1015. psychopy/app/Resources/splash@2x.png +0 -0
  1016. psychopy/app/Resources/tips.txt +45 -0
  1017. psychopy/app/Resources/tips_ar_001.txt +45 -0
  1018. psychopy/app/Resources/tips_fr_FR.txt +45 -0
  1019. psychopy/app/Resources/tips_ja_JP.txt +42 -0
  1020. psychopy/app/Resources/tips_zh_CN.txt +45 -0
  1021. psychopy/app/Resources/window.ico +0 -0
  1022. psychopy/app/__init__.py +339 -0
  1023. psychopy/app/__main__.py +3 -0
  1024. psychopy/app/_psychopyApp.py +1298 -0
  1025. psychopy/app/appData.spec +58 -0
  1026. psychopy/app/builder/__init__.py +5 -0
  1027. psychopy/app/builder/builder.py +4738 -0
  1028. psychopy/app/builder/dialogs/__init__.py +1910 -0
  1029. psychopy/app/builder/dialogs/dlgsCode.py +631 -0
  1030. psychopy/app/builder/dialogs/dlgsConditions.py +669 -0
  1031. psychopy/app/builder/dialogs/findDlg.py +275 -0
  1032. psychopy/app/builder/dialogs/paramCtrls.py +1802 -0
  1033. psychopy/app/builder/validators.py +589 -0
  1034. psychopy/app/coder/__init__.py +8 -0
  1035. psychopy/app/coder/codeEditorBase.py +460 -0
  1036. psychopy/app/coder/coder.py +3100 -0
  1037. psychopy/app/coder/fileBrowser.py +714 -0
  1038. psychopy/app/coder/folding.py +128 -0
  1039. psychopy/app/coder/psychoParser.py +139 -0
  1040. psychopy/app/coder/repl.py +496 -0
  1041. psychopy/app/coder/sourceTree.py +302 -0
  1042. psychopy/app/colorpicker/__init__.py +594 -0
  1043. psychopy/app/colorpicker/ui.py +336 -0
  1044. psychopy/app/connections/__init__.py +10 -0
  1045. psychopy/app/connections/news.py +104 -0
  1046. psychopy/app/connections/sendusage.py +61 -0
  1047. psychopy/app/connections/updates.py +642 -0
  1048. psychopy/app/console.py +164 -0
  1049. psychopy/app/deviceManager/__init__.py +1 -0
  1050. psychopy/app/deviceManager/addDialog.py +218 -0
  1051. psychopy/app/deviceManager/dialog.py +185 -0
  1052. psychopy/app/deviceManager/panel.py +191 -0
  1053. psychopy/app/deviceManager/utils.py +60 -0
  1054. psychopy/app/dialogs.py +664 -0
  1055. psychopy/app/errorDlg.py +238 -0
  1056. psychopy/app/frametracker.py +8 -0
  1057. psychopy/app/idle.py +181 -0
  1058. psychopy/app/jobs.py +676 -0
  1059. psychopy/app/linuxconfig/__init__.py +153 -0
  1060. psychopy/app/linuxconfig/ui.py +88 -0
  1061. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  1062. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -0
  1063. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  1064. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -0
  1065. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  1066. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -0
  1067. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  1068. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -0
  1069. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  1070. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -0
  1071. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  1072. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -0
  1073. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  1074. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -0
  1075. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  1076. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -0
  1077. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  1078. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -0
  1079. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  1080. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -0
  1081. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  1082. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -0
  1083. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  1084. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -0
  1085. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  1086. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -0
  1087. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  1088. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -0
  1089. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  1090. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -0
  1091. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  1092. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -0
  1093. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  1094. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -0
  1095. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  1096. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -0
  1097. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  1098. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +11268 -0
  1099. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  1100. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -0
  1101. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  1102. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -0
  1103. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  1104. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -0
  1105. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  1106. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -0
  1107. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  1108. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -0
  1109. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  1110. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -0
  1111. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  1112. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -0
  1113. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  1114. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -0
  1115. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  1116. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -0
  1117. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  1118. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -0
  1119. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  1120. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -0
  1121. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  1122. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -0
  1123. psychopy/app/localizedStrings.py +386 -0
  1124. psychopy/app/pavlovia_ui/__init__.py +22 -0
  1125. psychopy/app/pavlovia_ui/_base.py +268 -0
  1126. psychopy/app/pavlovia_ui/functions.py +176 -0
  1127. psychopy/app/pavlovia_ui/menu.py +140 -0
  1128. psychopy/app/pavlovia_ui/project.py +943 -0
  1129. psychopy/app/pavlovia_ui/search.py +444 -0
  1130. psychopy/app/pavlovia_ui/sync.py +137 -0
  1131. psychopy/app/pavlovia_ui/user.py +264 -0
  1132. psychopy/app/plugin_manager/__init__.py +5 -0
  1133. psychopy/app/plugin_manager/dialog.py +402 -0
  1134. psychopy/app/plugin_manager/output.py +132 -0
  1135. psychopy/app/plugin_manager/packageIndex.py +303 -0
  1136. psychopy/app/plugin_manager/packages.py +643 -0
  1137. psychopy/app/plugin_manager/plugins.py +1363 -0
  1138. psychopy/app/plugin_manager/utils.py +115 -0
  1139. psychopy/app/preferencesDlg.py +771 -0
  1140. psychopy/app/psychopyApp.py +207 -0
  1141. psychopy/app/ribbon.py +1019 -0
  1142. psychopy/app/runner/__init__.py +1 -0
  1143. psychopy/app/runner/runner.py +1299 -0
  1144. psychopy/app/runner/scriptProcess.py +363 -0
  1145. psychopy/app/stdout/__init__.py +1 -0
  1146. psychopy/app/stdout/stdOutRich.py +410 -0
  1147. psychopy/app/sysInfoDlg.py +242 -0
  1148. psychopy/app/themes/__init__.py +78 -0
  1149. psychopy/app/themes/colors.py +201 -0
  1150. psychopy/app/themes/css/contrast_black.css +112 -0
  1151. psychopy/app/themes/css/contrast_white.css +115 -0
  1152. psychopy/app/themes/css/dark.css +112 -0
  1153. psychopy/app/themes/css/light.css +115 -0
  1154. psychopy/app/themes/fonts.py +629 -0
  1155. psychopy/app/themes/handlers.py +321 -0
  1156. psychopy/app/themes/icons.py +267 -0
  1157. psychopy/app/themes/spec/Classic.json +154 -0
  1158. psychopy/app/themes/spec/ClassicDark.json +154 -0
  1159. psychopy/app/themes/spec/GitHub.json +154 -0
  1160. psychopy/app/themes/spec/HiVisDark.json +153 -0
  1161. psychopy/app/themes/spec/HiVisLight.json +154 -0
  1162. psychopy/app/themes/spec/MinimalDark.json +154 -0
  1163. psychopy/app/themes/spec/MinimalLight.json +153 -0
  1164. psychopy/app/themes/spec/PsychopyDark.json +231 -0
  1165. psychopy/app/themes/spec/PsychopyLight.json +236 -0
  1166. psychopy/app/themes/ui.py +74 -0
  1167. psychopy/app/ui/__init__.py +184 -0
  1168. psychopy/app/urls.py +31 -0
  1169. psychopy/app/utils.py +1741 -0
  1170. psychopy/app/viewer/__init__.py +0 -0
  1171. psychopy/assets/Psychopy Window Favicon@16w.png +0 -0
  1172. psychopy/assets/Psychopy Window Favicon@32w.png +0 -0
  1173. psychopy/assets/USB-C.png +0 -0
  1174. psychopy/assets/USB.png +0 -0
  1175. psychopy/assets/__init__.py +0 -0
  1176. psychopy/assets/click.png +0 -0
  1177. psychopy/assets/clicknext.png +0 -0
  1178. psychopy/assets/creditCard.png +0 -0
  1179. psychopy/assets/default.mp3 +0 -0
  1180. psychopy/assets/default.mp4 +0 -0
  1181. psychopy/assets/default.png +0 -0
  1182. psychopy/assets/fonts/Arvo-Bold.ttf +0 -0
  1183. psychopy/assets/fonts/Arvo-BoldItalic.ttf +0 -0
  1184. psychopy/assets/fonts/Arvo-Italic.ttf +0 -0
  1185. psychopy/assets/fonts/Arvo-Regular.ttf +0 -0
  1186. psychopy/assets/fonts/DejaVuSerif.ttf +0 -0
  1187. psychopy/assets/fonts/IndieFlower-Regular.ttf +0 -0
  1188. psychopy/assets/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  1189. psychopy/assets/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
  1190. psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
  1191. psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
  1192. psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
  1193. psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
  1194. psychopy/assets/next.png +0 -0
  1195. psychopy/assets/psychopy.ico +0 -0
  1196. psychopy/assets/psychopy.png +0 -0
  1197. psychopy/assets/templates/__init__.py +0 -0
  1198. psychopy/assets/templates/instruct1.png +0 -0
  1199. psychopy/assets/templates/instruct2.png +0 -0
  1200. psychopy/assets/touch.png +0 -0
  1201. psychopy/assets/touchnext.png +0 -0
  1202. psychopy/assets/voicekeyThresholdStim.wav +0 -0
  1203. psychopy/assets/window.ico +0 -0
  1204. psychopy/changes/2023.1.0.md +9 -0
  1205. psychopy/changes/2024.1.0.md +16 -0
  1206. psychopy/changes/__init__.py +0 -0
  1207. psychopy/clock.py +619 -0
  1208. psychopy/colors.py +1058 -0
  1209. psychopy/compatibility.py +147 -0
  1210. psychopy/constants.py +87 -0
  1211. psychopy/contrib/__init__.py +0 -0
  1212. psychopy/contrib/configobj/LICENSE +39 -0
  1213. psychopy/contrib/configobj/__init__.py +2455 -0
  1214. psychopy/contrib/configobj/_version.py +2 -0
  1215. psychopy/contrib/configobj/validate.py +1458 -0
  1216. psychopy/contrib/lazy_import.py +402 -0
  1217. psychopy/contrib/mseq.py +273 -0
  1218. psychopy/contrib/mseqSearch.py +183 -0
  1219. psychopy/contrib/psi.py +100 -0
  1220. psychopy/contrib/quest.py +484 -0
  1221. psychopy/contrib/tesselate.py +188 -0
  1222. psychopy/core.py +167 -0
  1223. psychopy/data/__init__.py +45 -0
  1224. psychopy/data/base.py +566 -0
  1225. psychopy/data/counterbalance.py +210 -0
  1226. psychopy/data/experiment.py +1049 -0
  1227. psychopy/data/fit.py +248 -0
  1228. psychopy/data/routine.py +94 -0
  1229. psychopy/data/shelf.py +237 -0
  1230. psychopy/data/staircase.py +2270 -0
  1231. psychopy/data/trial.py +2505 -0
  1232. psychopy/data/utils.py +858 -0
  1233. psychopy/demos/__init__.py +0 -0
  1234. psychopy/demos/builder/Design Templates/branchedExperiment/README.md +5 -0
  1235. psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +354 -0
  1236. psychopy/demos/builder/Design Templates/branchedExperiment/trialTypes.xlsx +0 -0
  1237. psychopy/demos/builder/Design Templates/psychophysicsStaircase/README.md +20 -0
  1238. psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +286 -0
  1239. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/README.md +14 -0
  1240. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +340 -0
  1241. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/stairDefinitions.xlsx +0 -0
  1242. psychopy/demos/builder/Design Templates/randomisedBlocks/README.md +18 -0
  1243. psychopy/demos/builder/Design Templates/randomisedBlocks/chooseBlock.xlsx +0 -0
  1244. psychopy/demos/builder/Design Templates/randomisedBlocks/facesBlock.xlsx +0 -0
  1245. psychopy/demos/builder/Design Templates/randomisedBlocks/housesBlock.xlsx +0 -0
  1246. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +219 -0
  1247. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face01.jpg +0 -0
  1248. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face02.jpg +0 -0
  1249. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face03.jpg +0 -0
  1250. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house01.jpg +0 -0
  1251. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house02.jpg +0 -0
  1252. psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house03.jpg +0 -0
  1253. psychopy/demos/builder/Experiments/BART/README.md +30 -0
  1254. psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
  1255. psychopy/demos/builder/Experiments/BART/assets/background.png +0 -0
  1256. psychopy/demos/builder/Experiments/BART/assets/bang.mp3 +0 -0
  1257. psychopy/demos/builder/Experiments/BART/assets/bang.wav +0 -0
  1258. psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
  1259. psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
  1260. psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
  1261. psychopy/demos/builder/Experiments/BART/bart.psyexp +844 -0
  1262. psychopy/demos/builder/Experiments/BART/spreadsheets/conditions.xlsx +0 -0
  1263. psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +255 -0
  1264. psychopy/demos/builder/Experiments/BigFiveInventory/README.md +23 -0
  1265. psychopy/demos/builder/Experiments/BigFiveInventory/TIPI.xlsx +0 -0
  1266. psychopy/demos/builder/Experiments/BigFiveInventory/bigFiveItems.xlsx +0 -0
  1267. psychopy/demos/builder/Experiments/BigFiveInventory/demographics.xlsx +0 -0
  1268. psychopy/demos/builder/Experiments/BigFiveInventory/mini_IPIP.xlsx +0 -0
  1269. psychopy/demos/builder/Experiments/GoNoGo/conditions.xlsx +0 -0
  1270. psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +462 -0
  1271. psychopy/demos/builder/Experiments/GoNoGo/go.png +0 -0
  1272. psychopy/demos/builder/Experiments/GoNoGo/nogo.png +0 -0
  1273. psychopy/demos/builder/Experiments/dragAndDrop/README.md +5 -0
  1274. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +494 -0
  1275. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
  1276. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
  1277. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
  1278. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
  1279. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
  1280. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
  1281. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
  1282. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
  1283. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
  1284. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
  1285. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
  1286. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
  1287. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
  1288. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  1289. psychopy/demos/builder/Experiments/goNoGo/readme.md +14 -0
  1290. psychopy/demos/builder/Experiments/mentalRotation/F.png +0 -0
  1291. psychopy/demos/builder/Experiments/mentalRotation/FR.png +0 -0
  1292. psychopy/demos/builder/Experiments/mentalRotation/MentalRot.csv +33 -0
  1293. psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +646 -0
  1294. psychopy/demos/builder/Experiments/mentalRotation/README.md +23 -0
  1295. psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +509 -0
  1296. psychopy/demos/builder/Experiments/navon/README.md +26 -0
  1297. psychopy/demos/builder/Experiments/navon/bigHsmallH.png +0 -0
  1298. psychopy/demos/builder/Experiments/navon/bigHsmallS.png +0 -0
  1299. psychopy/demos/builder/Experiments/navon/bigSsmallH.png +0 -0
  1300. psychopy/demos/builder/Experiments/navon/bigSsmallS.png +0 -0
  1301. psychopy/demos/builder/Experiments/navon/mask.png +0 -0
  1302. psychopy/demos/builder/Experiments/navon/stimuli.pptx +0 -0
  1303. psychopy/demos/builder/Experiments/navon/trialTypes.xlsx +0 -0
  1304. psychopy/demos/builder/Experiments/sternberg/README.md +14 -0
  1305. psychopy/demos/builder/Experiments/sternberg/mainTrials.xlsx +0 -0
  1306. psychopy/demos/builder/Experiments/sternberg/pracTrials.xlsx +0 -0
  1307. psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +649 -0
  1308. psychopy/demos/builder/Experiments/stroop/README.md +15 -0
  1309. psychopy/demos/builder/Experiments/stroop/stroop.psyexp +321 -0
  1310. psychopy/demos/builder/Experiments/stroop/trialTypes.csv +7 -0
  1311. psychopy/demos/builder/Experiments/stroopExtended/README.md +14 -0
  1312. psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +418 -0
  1313. psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +418 -0
  1314. psychopy/demos/builder/Experiments/stroopExtended/trialTypes.xlsx +0 -0
  1315. psychopy/demos/builder/Experiments/stroopExtended/trialTypesReverse.xlsx +0 -0
  1316. psychopy/demos/builder/Experiments/stroopVoice/README.md +27 -0
  1317. psychopy/demos/builder/Experiments/stroopVoice/conditions.xlsx +0 -0
  1318. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +382 -0
  1319. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +371 -0
  1320. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  1321. psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +308 -0
  1322. psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +391 -0
  1323. psychopy/demos/builder/Feature Demos/gratings/readme.md +8 -0
  1324. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  1325. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  1326. psychopy/demos/builder/Feature Demos/noise/face.jpg +0 -0
  1327. psychopy/demos/builder/Feature Demos/noise/noise.psyexp +477 -0
  1328. psychopy/demos/builder/Feature Demos/noise/readme.md +3 -0
  1329. psychopy/demos/builder/Feature Demos/panorama/panImg.jpg +0 -0
  1330. psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +177 -0
  1331. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +434 -0
  1332. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  1333. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +451 -0
  1334. psychopy/demos/builder/Feature Demos/progress/readme.md +1 -0
  1335. psychopy/demos/builder/Feature Demos/sliders/README.md +8 -0
  1336. psychopy/demos/builder/Feature Demos/sliders/fruitConditions.xlsx +0 -0
  1337. psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +984 -0
  1338. psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
  1339. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +199 -0
  1340. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +639 -0
  1341. psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +415 -0
  1342. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +659 -0
  1343. psychopy/demos/builder/Hardware/EGI_netstation/README.md +17 -0
  1344. psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +339 -0
  1345. psychopy/demos/builder/Hardware/EGI_netstation/trialTypesEEG.csv +7 -0
  1346. psychopy/demos/builder/Hardware/Eyetracking_visual_search/readme.md +19 -0
  1347. psychopy/demos/builder/Hardware/Eyetracking_visual_search/trials_params.xlsx +0 -0
  1348. psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +830 -0
  1349. psychopy/demos/builder/Hardware/camera/camera.psyexp +330 -0
  1350. psychopy/demos/builder/Hardware/camera/readme.md +6 -0
  1351. psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +459 -0
  1352. psychopy/demos/builder/Hardware/eyetracking/readme.md +7 -0
  1353. psychopy/demos/builder/Hardware/eyetracking_custom_cal/Bullseye_grey.mov +0 -0
  1354. psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +468 -0
  1355. psychopy/demos/builder/Hardware/eyetracking_custom_cal/readme.md +7 -0
  1356. psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +211 -0
  1357. psychopy/demos/builder/Hardware/fMRI/readme.md +7 -0
  1358. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +360 -0
  1359. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
  1360. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
  1361. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +360 -0
  1362. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
  1363. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
  1364. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +484 -0
  1365. psychopy/demos/builder/Hardware/microphone/phrases.xlsx +0 -0
  1366. psychopy/demos/builder/Hardware/microphone/readme.md +9 -0
  1367. psychopy/demos/builder/Hardware/pump/README.md +19 -0
  1368. psychopy/demos/builder/Hardware/pump/pump.psyexp +646 -0
  1369. psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
  1370. psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +350 -0
  1371. psychopy/demos/builder/Helper Tools/achorVSalignment/readme.md +5 -0
  1372. psychopy/demos/builder/Helper Tools/clockFace/README.md +3 -0
  1373. psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +211 -0
  1374. psychopy/demos/builder/Helper Tools/colors/README.md +5 -0
  1375. psychopy/demos/builder/Helper Tools/colors/colors.psyexp +321 -0
  1376. psychopy/demos/builder/Helper Tools/drawPolygon/README.md +5 -0
  1377. psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +704 -0
  1378. psychopy/demos/builder/Helper Tools/keyNameFinder/README.md +14 -0
  1379. psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +225 -0
  1380. psychopy/demos/builder/Helper Tools/spatialUnits/README.md +6 -0
  1381. psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +206 -0
  1382. psychopy/demos/builder/README.txt +43 -0
  1383. psychopy/demos/builder/__init__.py +0 -0
  1384. psychopy/demos/coder/__init__.py +0 -0
  1385. psychopy/demos/coder/basic/hello_world.py +37 -0
  1386. psychopy/demos/coder/csvFromPsydat.py +29 -0
  1387. psychopy/demos/coder/experiment control/JND_staircase_analysis.py +85 -0
  1388. psychopy/demos/coder/experiment control/JND_staircase_exp.py +110 -0
  1389. psychopy/demos/coder/experiment control/TrialHandler.py +64 -0
  1390. psychopy/demos/coder/experiment control/TrialHandler2.py +42 -0
  1391. psychopy/demos/coder/experiment control/__init__.py +0 -0
  1392. psychopy/demos/coder/experiment control/autoDraw_autoLog.py +50 -0
  1393. psychopy/demos/coder/experiment control/experimentHandler.py +57 -0
  1394. psychopy/demos/coder/experiment control/fMRI_launchScan.py +74 -0
  1395. psychopy/demos/coder/experiment control/gammaMotionAnalysis.py +68 -0
  1396. psychopy/demos/coder/experiment control/gammaMotionNull.py +169 -0
  1397. psychopy/demos/coder/experiment control/logFiles.py +55 -0
  1398. psychopy/demos/coder/experiment control/piloting.py +65 -0
  1399. psychopy/demos/coder/experiment control/runtimeInfo.py +79 -0
  1400. psychopy/demos/coder/hardware/CRS_BitsBox.py +75 -0
  1401. psychopy/demos/coder/hardware/CRS_BitsPlusPlus.py +73 -0
  1402. psychopy/demos/coder/hardware/RiftHeadTrackingExample.py +95 -0
  1403. psychopy/demos/coder/hardware/RiftMinimal.py +44 -0
  1404. psychopy/demos/coder/hardware/VSHD_Distortion.py +49 -0
  1405. psychopy/demos/coder/hardware/__init__.py +0 -0
  1406. psychopy/demos/coder/hardware/camera.py +90 -0
  1407. psychopy/demos/coder/hardware/cedrusRB730.py +38 -0
  1408. psychopy/demos/coder/hardware/crsBitsAdvancedDemo.py +780 -0
  1409. psychopy/demos/coder/hardware/egi_netstation.py +45 -0
  1410. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  1411. psychopy/demos/coder/hardware/ioLab_bbox.py +62 -0
  1412. psychopy/demos/coder/hardware/labjack_u3.py +61 -0
  1413. psychopy/demos/coder/hardware/monitorDemo.py +28 -0
  1414. psychopy/demos/coder/hardware/parallelPortOutput.py +49 -0
  1415. psychopy/demos/coder/hardware/qmixPump.py +79 -0
  1416. psychopy/demos/coder/hardware/testSoundLatency.py +127 -0
  1417. psychopy/demos/coder/input/GUI.py +52 -0
  1418. psychopy/demos/coder/input/__init__.py +0 -0
  1419. psychopy/demos/coder/input/customMouse.py +51 -0
  1420. psychopy/demos/coder/input/joystick_universal.py +84 -0
  1421. psychopy/demos/coder/input/keyNameFinder.py +39 -0
  1422. psychopy/demos/coder/input/mic.png +0 -0
  1423. psychopy/demos/coder/input/mouse.py +66 -0
  1424. psychopy/demos/coder/iohub/delaytest.py +231 -0
  1425. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/canal.jpg +0 -0
  1426. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/fall.jpg +0 -0
  1427. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/lake.jpg +0 -0
  1428. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/party.jpg +0 -0
  1429. psychopy/demos/coder/iohub/eyetracking/gcCursor/images/swimming.jpg +0 -0
  1430. psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByConditionVariables.py +67 -0
  1431. psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByMessages.py +34 -0
  1432. psychopy/demos/coder/iohub/eyetracking/gcCursor/run.py +323 -0
  1433. psychopy/demos/coder/iohub/eyetracking/gcCursor/trial_conditions.xlsx +0 -0
  1434. psychopy/demos/coder/iohub/eyetracking/simple.py +147 -0
  1435. psychopy/demos/coder/iohub/eyetracking/validation.py +251 -0
  1436. psychopy/demos/coder/iohub/iodatastore/saveEventReport.py +56 -0
  1437. psychopy/demos/coder/iohub/keyboard.py +189 -0
  1438. psychopy/demos/coder/iohub/keyboardreactiontime.py +88 -0
  1439. psychopy/demos/coder/iohub/launchHub.py +160 -0
  1440. psychopy/demos/coder/iohub/mouse.py +112 -0
  1441. psychopy/demos/coder/iohub/mouse_multi_window.py +124 -0
  1442. psychopy/demos/coder/iohub/serial/_parseserial.py +53 -0
  1443. psychopy/demos/coder/iohub/serial/customparser.py +84 -0
  1444. psychopy/demos/coder/iohub/serial/pstbox.py +166 -0
  1445. psychopy/demos/coder/iohub/wintab/_wintabgraphics.py +281 -0
  1446. psychopy/demos/coder/iohub/wintab/pen_demo.py +260 -0
  1447. psychopy/demos/coder/misc/encrypt_data.py +48 -0
  1448. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  1449. psychopy/demos/coder/misc/makeMovie.py +28 -0
  1450. psychopy/demos/coder/misc/rigidBodyTransform.py +76 -0
  1451. psychopy/demos/coder/stimuli/Campaign.ttf +0 -0
  1452. psychopy/demos/coder/stimuli/MovieStim.py +65 -0
  1453. psychopy/demos/coder/stimuli/__init__.py +0 -0
  1454. psychopy/demos/coder/stimuli/aperture.py +37 -0
  1455. psychopy/demos/coder/stimuli/beach.jpg +0 -0
  1456. psychopy/demos/coder/stimuli/bufferImageStim.py +78 -0
  1457. psychopy/demos/coder/stimuli/clockface.py +43 -0
  1458. psychopy/demos/coder/stimuli/colorPalette.py +134 -0
  1459. psychopy/demos/coder/stimuli/compare_text_timing.py +193 -0
  1460. psychopy/demos/coder/stimuli/counterphase.py +43 -0
  1461. psychopy/demos/coder/stimuli/customTextures.py +57 -0
  1462. psychopy/demos/coder/stimuli/dot_gabors.py +35 -0
  1463. psychopy/demos/coder/stimuli/dots.py +35 -0
  1464. psychopy/demos/coder/stimuli/elementArrays.py +92 -0
  1465. psychopy/demos/coder/stimuli/embeddedOpenGL.py +35 -0
  1466. psychopy/demos/coder/stimuli/face.jpg +0 -0
  1467. psychopy/demos/coder/stimuli/face_jpg.py +48 -0
  1468. psychopy/demos/coder/stimuli/gabor.py +30 -0
  1469. psychopy/demos/coder/stimuli/imagesAndPatches.py +51 -0
  1470. psychopy/demos/coder/stimuli/jwpIntro.mp4 +0 -0
  1471. psychopy/demos/coder/stimuli/kanizsa.py +41 -0
  1472. psychopy/demos/coder/stimuli/maskReveal.py +63 -0
  1473. psychopy/demos/coder/stimuli/plaid.py +43 -0
  1474. psychopy/demos/coder/stimuli/ratingScale.py +183 -0
  1475. psychopy/demos/coder/stimuli/rotatingFlashingWedge.py +37 -0
  1476. psychopy/demos/coder/stimuli/screensAndWindows.py +58 -0
  1477. psychopy/demos/coder/stimuli/secondOrderGratings.py +66 -0
  1478. psychopy/demos/coder/stimuli/shapeContains.py +50 -0
  1479. psychopy/demos/coder/stimuli/shapes.py +63 -0
  1480. psychopy/demos/coder/stimuli/soundStimuli.py +49 -0
  1481. psychopy/demos/coder/stimuli/starField.py +48 -0
  1482. psychopy/demos/coder/stimuli/stim3d.py +107 -0
  1483. psychopy/demos/coder/stimuli/textBoxStim/textbox_glyph_placement.py +93 -0
  1484. psychopy/demos/coder/stimuli/textBoxStim/textbox_simple.py +71 -0
  1485. psychopy/demos/coder/stimuli/textStimuli.py +106 -0
  1486. psychopy/demos/coder/stimuli/textbox_editable.py +64 -0
  1487. psychopy/demos/coder/stimuli/variousVisualStims.py +47 -0
  1488. psychopy/demos/coder/stimuli/visual_noise.py +31 -0
  1489. psychopy/demos/coder/sysInfo.py +56 -0
  1490. psychopy/demos/coder/timing/__init__.py +0 -0
  1491. psychopy/demos/coder/timing/callOnFlip.py +39 -0
  1492. psychopy/demos/coder/timing/clocksAndTimers.py +46 -0
  1493. psychopy/demos/coder/timing/millikeyKeyboardTimingTest.py +323 -0
  1494. psychopy/demos/coder/timing/timeByFrames.py +80 -0
  1495. psychopy/demos/coder/timing/timeByFramesEx.py +107 -0
  1496. psychopy/demos/coder/understanding psychopy/colors.py +70 -0
  1497. psychopy/demos/coder/understanding psychopy/fontLayout.py +69 -0
  1498. psychopy/demos/demo_migration.py +214 -0
  1499. psychopy/demos/modernizeDemos.py +16 -0
  1500. psychopy/demos/test_demo_migration.py +155 -0
  1501. psychopy/devices/__init__.py +8 -0
  1502. psychopy/event.py +1339 -0
  1503. psychopy/exceptions.py +59 -0
  1504. psychopy/experiment/__init__.py +51 -0
  1505. psychopy/experiment/_experiment.py +1514 -0
  1506. psychopy/experiment/blankTemplate.xltx +0 -0
  1507. psychopy/experiment/components/__init__.py +402 -0
  1508. psychopy/experiment/components/_base.py +1689 -0
  1509. psychopy/experiment/components/aperture/__init__.py +153 -0
  1510. psychopy/experiment/components/aperture/classic/aperture.png +0 -0
  1511. psychopy/experiment/components/aperture/classic/aperture@2x.png +0 -0
  1512. psychopy/experiment/components/aperture/dark/aperture.png +0 -0
  1513. psychopy/experiment/components/aperture/dark/aperture@2x.png +0 -0
  1514. psychopy/experiment/components/aperture/light/aperture.png +0 -0
  1515. psychopy/experiment/components/aperture/light/aperture@2x.png +0 -0
  1516. psychopy/experiment/components/brush/__init__.py +165 -0
  1517. psychopy/experiment/components/brush/classic/brush.png +0 -0
  1518. psychopy/experiment/components/brush/classic/brush@2x.png +0 -0
  1519. psychopy/experiment/components/brush/dark/brush.png +0 -0
  1520. psychopy/experiment/components/brush/dark/brush@2x.png +0 -0
  1521. psychopy/experiment/components/brush/light/brush.png +0 -0
  1522. psychopy/experiment/components/brush/light/brush@2x.png +0 -0
  1523. psychopy/experiment/components/button/__init__.py +493 -0
  1524. psychopy/experiment/components/button/classic/button.png +0 -0
  1525. psychopy/experiment/components/button/classic/button@2x.png +0 -0
  1526. psychopy/experiment/components/button/dark/button.png +0 -0
  1527. psychopy/experiment/components/button/dark/button@2x.png +0 -0
  1528. psychopy/experiment/components/button/light/button.png +0 -0
  1529. psychopy/experiment/components/button/light/button@2x.png +0 -0
  1530. psychopy/experiment/components/buttonBox/__init__.py +322 -0
  1531. psychopy/experiment/components/buttonBox/classic/buttonBox.png +0 -0
  1532. psychopy/experiment/components/buttonBox/classic/buttonBox@2x.png +0 -0
  1533. psychopy/experiment/components/buttonBox/dark/buttonBox.png +0 -0
  1534. psychopy/experiment/components/buttonBox/dark/buttonBox@2x.png +0 -0
  1535. psychopy/experiment/components/buttonBox/light/buttonBox.png +0 -0
  1536. psychopy/experiment/components/buttonBox/light/buttonBox@2x.png +0 -0
  1537. psychopy/experiment/components/camera/__init__.py +381 -0
  1538. psychopy/experiment/components/camera/classic/webcam.png +0 -0
  1539. psychopy/experiment/components/camera/classic/webcam@2x.png +0 -0
  1540. psychopy/experiment/components/camera/dark/webcam.png +0 -0
  1541. psychopy/experiment/components/camera/dark/webcam@2x.png +0 -0
  1542. psychopy/experiment/components/camera/light/webcam.png +0 -0
  1543. psychopy/experiment/components/camera/light/webcam@2x.png +0 -0
  1544. psychopy/experiment/components/code/__init__.py +299 -0
  1545. psychopy/experiment/components/code/classic/code.png +0 -0
  1546. psychopy/experiment/components/code/classic/code@2x.png +0 -0
  1547. psychopy/experiment/components/code/dark/code.png +0 -0
  1548. psychopy/experiment/components/code/dark/code@2x.png +0 -0
  1549. psychopy/experiment/components/code/light/code.png +0 -0
  1550. psychopy/experiment/components/code/light/code@2x.png +0 -0
  1551. psychopy/experiment/components/dots/__init__.py +199 -0
  1552. psychopy/experiment/components/dots/classic/dots.png +0 -0
  1553. psychopy/experiment/components/dots/classic/dots@2x.png +0 -0
  1554. psychopy/experiment/components/dots/dark/dots.png +0 -0
  1555. psychopy/experiment/components/dots/dark/dots@2x.png +0 -0
  1556. psychopy/experiment/components/dots/dots.xcf +0 -0
  1557. psychopy/experiment/components/dots/light/dots.png +0 -0
  1558. psychopy/experiment/components/dots/light/dots@2x.png +0 -0
  1559. psychopy/experiment/components/eyetracker_record/__init__.py +194 -0
  1560. psychopy/experiment/components/eyetracker_record/classic/eyetracker_record.png +0 -0
  1561. psychopy/experiment/components/eyetracker_record/classic/eyetracker_record@2x.png +0 -0
  1562. psychopy/experiment/components/eyetracker_record/dark/eyetracker_record.png +0 -0
  1563. psychopy/experiment/components/eyetracker_record/dark/eyetracker_record@2x.png +0 -0
  1564. psychopy/experiment/components/eyetracker_record/light/eyetracker_record.png +0 -0
  1565. psychopy/experiment/components/eyetracker_record/light/eyetracker_record@2x.png +0 -0
  1566. psychopy/experiment/components/form/__init__.py +227 -0
  1567. psychopy/experiment/components/form/classic/form.png +0 -0
  1568. psychopy/experiment/components/form/classic/form@2x.png +0 -0
  1569. psychopy/experiment/components/form/dark/form.png +0 -0
  1570. psychopy/experiment/components/form/dark/form@2x.png +0 -0
  1571. psychopy/experiment/components/form/formItems.xltx +0 -0
  1572. psychopy/experiment/components/form/light/form.png +0 -0
  1573. psychopy/experiment/components/form/light/form@2x.png +0 -0
  1574. psychopy/experiment/components/grating/__init__.py +203 -0
  1575. psychopy/experiment/components/grating/classic/grating.png +0 -0
  1576. psychopy/experiment/components/grating/classic/grating@2x.png +0 -0
  1577. psychopy/experiment/components/grating/dark/grating.png +0 -0
  1578. psychopy/experiment/components/grating/dark/grating@2x.png +0 -0
  1579. psychopy/experiment/components/grating/light/grating.png +0 -0
  1580. psychopy/experiment/components/grating/light/grating@2x.png +0 -0
  1581. psychopy/experiment/components/image/__init__.py +195 -0
  1582. psychopy/experiment/components/image/classic/image.png +0 -0
  1583. psychopy/experiment/components/image/classic/image@2x.png +0 -0
  1584. psychopy/experiment/components/image/dark/image.png +0 -0
  1585. psychopy/experiment/components/image/dark/image@2x.png +0 -0
  1586. psychopy/experiment/components/image/light/image.png +0 -0
  1587. psychopy/experiment/components/image/light/image@2x.png +0 -0
  1588. psychopy/experiment/components/joyButtons/__init__.py +453 -0
  1589. psychopy/experiment/components/joyButtons/classic/joyButtons.png +0 -0
  1590. psychopy/experiment/components/joyButtons/classic/joybuttons@2x.png +0 -0
  1591. psychopy/experiment/components/joyButtons/dark/joyButtons.png +0 -0
  1592. psychopy/experiment/components/joyButtons/dark/joyButtons@2x.png +0 -0
  1593. psychopy/experiment/components/joyButtons/light/joyButtons.png +0 -0
  1594. psychopy/experiment/components/joyButtons/light/joyButtons@2x.png +0 -0
  1595. psychopy/experiment/components/joyButtons/virtualJoyButtons.py +34 -0
  1596. psychopy/experiment/components/joystick/__init__.py +572 -0
  1597. psychopy/experiment/components/joystick/classic/joystick.png +0 -0
  1598. psychopy/experiment/components/joystick/classic/joystick@2x.png +0 -0
  1599. psychopy/experiment/components/joystick/dark/joystick.png +0 -0
  1600. psychopy/experiment/components/joystick/dark/joystick@2x.png +0 -0
  1601. psychopy/experiment/components/joystick/light/joystick.png +0 -0
  1602. psychopy/experiment/components/joystick/light/joystick@2x.png +0 -0
  1603. psychopy/experiment/components/joystick/virtualJoystick.py +40 -0
  1604. psychopy/experiment/components/keyboard/__init__.py +568 -0
  1605. psychopy/experiment/components/keyboard/classic/keyboard.png +0 -0
  1606. psychopy/experiment/components/keyboard/classic/keyboard@2x.png +0 -0
  1607. psychopy/experiment/components/keyboard/dark/keyboard.png +0 -0
  1608. psychopy/experiment/components/keyboard/dark/keyboard@2x.png +0 -0
  1609. psychopy/experiment/components/keyboard/light/keyboard.png +0 -0
  1610. psychopy/experiment/components/keyboard/light/keyboard@2x.png +0 -0
  1611. psychopy/experiment/components/keyboard.xcf +0 -0
  1612. psychopy/experiment/components/microphone/__init__.py +600 -0
  1613. psychopy/experiment/components/microphone/classic/microphone.png +0 -0
  1614. psychopy/experiment/components/microphone/classic/microphone@2x.png +0 -0
  1615. psychopy/experiment/components/microphone/dark/microphone.png +0 -0
  1616. psychopy/experiment/components/microphone/dark/microphone@2x.png +0 -0
  1617. psychopy/experiment/components/microphone/light/microphone.png +0 -0
  1618. psychopy/experiment/components/microphone/light/microphone@2x.png +0 -0
  1619. psychopy/experiment/components/mouse/__init__.py +773 -0
  1620. psychopy/experiment/components/mouse/classic/mouse.png +0 -0
  1621. psychopy/experiment/components/mouse/classic/mouse@2x.png +0 -0
  1622. psychopy/experiment/components/mouse/dark/mouse.png +0 -0
  1623. psychopy/experiment/components/mouse/dark/mouse@2x.png +0 -0
  1624. psychopy/experiment/components/mouse/light/mouse.png +0 -0
  1625. psychopy/experiment/components/mouse/light/mouse@2x.png +0 -0
  1626. psychopy/experiment/components/movie/__init__.py +348 -0
  1627. psychopy/experiment/components/movie/classic/movie.png +0 -0
  1628. psychopy/experiment/components/movie/classic/movie@2x.png +0 -0
  1629. psychopy/experiment/components/movie/dark/movie.png +0 -0
  1630. psychopy/experiment/components/movie/dark/movie@2x.png +0 -0
  1631. psychopy/experiment/components/movie/light/movie.png +0 -0
  1632. psychopy/experiment/components/movie/light/movie@2x.png +0 -0
  1633. psychopy/experiment/components/panorama/__init__.py +456 -0
  1634. psychopy/experiment/components/panorama/classic/panorama.png +0 -0
  1635. psychopy/experiment/components/panorama/classic/panorama@2x.png +0 -0
  1636. psychopy/experiment/components/panorama/dark/panorama.png +0 -0
  1637. psychopy/experiment/components/panorama/dark/panorama@2x.png +0 -0
  1638. psychopy/experiment/components/panorama/light/panorama.png +0 -0
  1639. psychopy/experiment/components/panorama/light/panorama@2x.png +0 -0
  1640. psychopy/experiment/components/parallelOut/__init__.py +178 -0
  1641. psychopy/experiment/components/parallelOut/classic/parallel.png +0 -0
  1642. psychopy/experiment/components/parallelOut/classic/parallel@2x.png +0 -0
  1643. psychopy/experiment/components/parallelOut/dark/parallel.png +0 -0
  1644. psychopy/experiment/components/parallelOut/dark/parallel@2x.png +0 -0
  1645. psychopy/experiment/components/parallelOut/light/parallel.png +0 -0
  1646. psychopy/experiment/components/parallelOut/light/parallel@2x.png +0 -0
  1647. psychopy/experiment/components/polygon/__init__.py +332 -0
  1648. psychopy/experiment/components/polygon/classic/polygon.png +0 -0
  1649. psychopy/experiment/components/polygon/classic/polygon@2x.png +0 -0
  1650. psychopy/experiment/components/polygon/dark/polygon.png +0 -0
  1651. psychopy/experiment/components/polygon/dark/polygon@2x.png +0 -0
  1652. psychopy/experiment/components/polygon/light/polygon.png +0 -0
  1653. psychopy/experiment/components/polygon/light/polygon@2x.png +0 -0
  1654. psychopy/experiment/components/progress/__init__.py +130 -0
  1655. psychopy/experiment/components/progress/classic/progress.png +0 -0
  1656. psychopy/experiment/components/progress/classic/progress@2x.png +0 -0
  1657. psychopy/experiment/components/progress/dark/progress.png +0 -0
  1658. psychopy/experiment/components/progress/dark/progress@2x.png +0 -0
  1659. psychopy/experiment/components/progress/light/progress.png +0 -0
  1660. psychopy/experiment/components/progress/light/progress@2x.png +0 -0
  1661. psychopy/experiment/components/resourceManager/__init__.py +176 -0
  1662. psychopy/experiment/components/resourceManager/classic/resource_manager.png +0 -0
  1663. psychopy/experiment/components/resourceManager/classic/resource_manager@2x.png +0 -0
  1664. psychopy/experiment/components/resourceManager/dark/resource_manager.png +0 -0
  1665. psychopy/experiment/components/resourceManager/dark/resource_manager@2x.png +0 -0
  1666. psychopy/experiment/components/resourceManager/light/resource_manager.png +0 -0
  1667. psychopy/experiment/components/resourceManager/light/resource_manager@2x.png +0 -0
  1668. psychopy/experiment/components/roi/__init__.py +316 -0
  1669. psychopy/experiment/components/roi/classic/eyetracker_roi.png +0 -0
  1670. psychopy/experiment/components/roi/classic/eyetracker_roi@2x.png +0 -0
  1671. psychopy/experiment/components/roi/dark/eyetracker_roi.png +0 -0
  1672. psychopy/experiment/components/roi/dark/eyetracker_roi@2X.png +0 -0
  1673. psychopy/experiment/components/roi/light/eyetracker_roi.png +0 -0
  1674. psychopy/experiment/components/roi/light/eyetracker_roi@2X.png +0 -0
  1675. psychopy/experiment/components/routineSettings/__init__.py +387 -0
  1676. psychopy/experiment/components/routineSettings/classic/routineSettings.png +0 -0
  1677. psychopy/experiment/components/routineSettings/classic/routineSettings@2x.png +0 -0
  1678. psychopy/experiment/components/routineSettings/dark/routineSettings.png +0 -0
  1679. psychopy/experiment/components/routineSettings/dark/routineSettings@2x.png +0 -0
  1680. psychopy/experiment/components/routineSettings/light/routineSettings.png +0 -0
  1681. psychopy/experiment/components/routineSettings/light/routineSettings@2x.png +0 -0
  1682. psychopy/experiment/components/serialOut/__init__.py +304 -0
  1683. psychopy/experiment/components/serialOut/classic/serial.png +0 -0
  1684. psychopy/experiment/components/serialOut/classic/serial@2x.png +0 -0
  1685. psychopy/experiment/components/serialOut/dark/serial.png +0 -0
  1686. psychopy/experiment/components/serialOut/dark/serial@2x.png +0 -0
  1687. psychopy/experiment/components/serialOut/light/serial.png +0 -0
  1688. psychopy/experiment/components/serialOut/light/serial@2x.png +0 -0
  1689. psychopy/experiment/components/settings/JS_htmlHeader.tmpl +23 -0
  1690. psychopy/experiment/components/settings/JS_setupExp.tmpl +28 -0
  1691. psychopy/experiment/components/settings/__init__.py +2254 -0
  1692. psychopy/experiment/components/settings/classic/settings.png +0 -0
  1693. psychopy/experiment/components/settings/classic/settings@2x.png +0 -0
  1694. psychopy/experiment/components/settings/dark/settings.png +0 -0
  1695. psychopy/experiment/components/settings/dark/settings@2x.png +0 -0
  1696. psychopy/experiment/components/settings/eyetracking.py +108 -0
  1697. psychopy/experiment/components/settings/light/settings.png +0 -0
  1698. psychopy/experiment/components/settings/light/settings@2x.png +0 -0
  1699. psychopy/experiment/components/slider/__init__.py +410 -0
  1700. psychopy/experiment/components/slider/classic/slider.png +0 -0
  1701. psychopy/experiment/components/slider/classic/slider@2x.png +0 -0
  1702. psychopy/experiment/components/slider/dark/slider.png +0 -0
  1703. psychopy/experiment/components/slider/dark/slider@2x.png +0 -0
  1704. psychopy/experiment/components/slider/light/slider.png +0 -0
  1705. psychopy/experiment/components/slider/light/slider@2x.png +0 -0
  1706. psychopy/experiment/components/sound/__init__.py +441 -0
  1707. psychopy/experiment/components/sound/classic/sound.png +0 -0
  1708. psychopy/experiment/components/sound/classic/sound@2x.png +0 -0
  1709. psychopy/experiment/components/sound/dark/sound.png +0 -0
  1710. psychopy/experiment/components/sound/dark/sound@2x.png +0 -0
  1711. psychopy/experiment/components/sound/light/sound.png +0 -0
  1712. psychopy/experiment/components/sound/light/sound@2x.png +0 -0
  1713. psychopy/experiment/components/soundsensor/__init__.py +324 -0
  1714. psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
  1715. psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
  1716. psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
  1717. psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
  1718. psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
  1719. psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
  1720. psychopy/experiment/components/static/__init__.py +293 -0
  1721. psychopy/experiment/components/static/classic/static.png +0 -0
  1722. psychopy/experiment/components/static/classic/static@2x.png +0 -0
  1723. psychopy/experiment/components/static/dark/static.png +0 -0
  1724. psychopy/experiment/components/static/dark/static@2x.png +0 -0
  1725. psychopy/experiment/components/static/light/static.png +0 -0
  1726. psychopy/experiment/components/static/light/static@2x.png +0 -0
  1727. psychopy/experiment/components/text/__init__.py +190 -0
  1728. psychopy/experiment/components/text/classic/text.png +0 -0
  1729. psychopy/experiment/components/text/classic/text@2x.png +0 -0
  1730. psychopy/experiment/components/text/dark/text.png +0 -0
  1731. psychopy/experiment/components/text/dark/text@2x.png +0 -0
  1732. psychopy/experiment/components/text/light/text.png +0 -0
  1733. psychopy/experiment/components/text/light/text@2x.png +0 -0
  1734. psychopy/experiment/components/textbox/__init__.py +336 -0
  1735. psychopy/experiment/components/textbox/classic/textbox.png +0 -0
  1736. psychopy/experiment/components/textbox/classic/textbox@2x.png +0 -0
  1737. psychopy/experiment/components/textbox/dark/textbox.png +0 -0
  1738. psychopy/experiment/components/textbox/dark/textbox@2x.png +0 -0
  1739. psychopy/experiment/components/textbox/light/textbox.png +0 -0
  1740. psychopy/experiment/components/textbox/light/textbox@2x.png +0 -0
  1741. psychopy/experiment/components/unknown/__init__.py +87 -0
  1742. psychopy/experiment/components/unknown/classic/unknown.png +0 -0
  1743. psychopy/experiment/components/unknown/classic/unknown@2x.png +0 -0
  1744. psychopy/experiment/components/unknown/dark/unknown.png +0 -0
  1745. psychopy/experiment/components/unknown/dark/unknown@2x.png +0 -0
  1746. psychopy/experiment/components/unknown/light/unknown.png +0 -0
  1747. psychopy/experiment/components/unknown/light/unknown@2x.png +0 -0
  1748. psychopy/experiment/components/unknownPlugin/__init__.py +86 -0
  1749. psychopy/experiment/components/unknownPlugin/classic/unknownPlugin.png +0 -0
  1750. psychopy/experiment/components/unknownPlugin/classic/unknownPlugin@2x.png +0 -0
  1751. psychopy/experiment/components/unknownPlugin/dark/unknownPlugin.png +0 -0
  1752. psychopy/experiment/components/unknownPlugin/dark/unknownPlugin@2x.png +0 -0
  1753. psychopy/experiment/components/unknownPlugin/light/unknownPlugin.png +0 -0
  1754. psychopy/experiment/components/unknownPlugin/light/unknownPlugin@2x.png +0 -0
  1755. psychopy/experiment/components/utils.py +0 -0
  1756. psychopy/experiment/components/variable/__init__.py +184 -0
  1757. psychopy/experiment/components/variable/classic/variable.png +0 -0
  1758. psychopy/experiment/components/variable/classic/variable@2x.png +0 -0
  1759. psychopy/experiment/components/variable/dark/variable.png +0 -0
  1760. psychopy/experiment/components/variable/dark/variable@2x.png +0 -0
  1761. psychopy/experiment/components/variable/light/variable.png +0 -0
  1762. psychopy/experiment/components/variable/light/variable@2x.png +0 -0
  1763. psychopy/experiment/devices.py +303 -0
  1764. psychopy/experiment/experiment.xsd +223 -0
  1765. psychopy/experiment/exports.py +390 -0
  1766. psychopy/experiment/flow.py +547 -0
  1767. psychopy/experiment/localization.py +11 -0
  1768. psychopy/experiment/loopTemplate.xltx +0 -0
  1769. psychopy/experiment/loops.py +1051 -0
  1770. psychopy/experiment/monitor.py +74 -0
  1771. psychopy/experiment/params.py +541 -0
  1772. psychopy/experiment/plugins.py +42 -0
  1773. psychopy/experiment/py2js.py +183 -0
  1774. psychopy/experiment/py2js_transpiler.py +613 -0
  1775. psychopy/experiment/questPlusTemplate.xltx +0 -0
  1776. psychopy/experiment/questTemplate.xltx +0 -0
  1777. psychopy/experiment/routines/__init__.py +108 -0
  1778. psychopy/experiment/routines/_base.py +1172 -0
  1779. psychopy/experiment/routines/audioValidator/__init__.py +229 -0
  1780. psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
  1781. psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
  1782. psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
  1783. psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
  1784. psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
  1785. psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
  1786. psychopy/experiment/routines/counterbalance/__init__.py +320 -0
  1787. psychopy/experiment/routines/counterbalance/classic/counterbalance.png +0 -0
  1788. psychopy/experiment/routines/counterbalance/classic/counterbalance@2x.png +0 -0
  1789. psychopy/experiment/routines/counterbalance/counterbalanceItems.xltx +0 -0
  1790. psychopy/experiment/routines/counterbalance/dark/counterbalance.png +0 -0
  1791. psychopy/experiment/routines/counterbalance/dark/counterbalance@2x.png +0 -0
  1792. psychopy/experiment/routines/counterbalance/light/counterbalance.png +0 -0
  1793. psychopy/experiment/routines/counterbalance/light/counterbalance@2x.png +0 -0
  1794. psychopy/experiment/routines/eyetracker_calibrate/__init__.py +334 -0
  1795. psychopy/experiment/routines/eyetracker_calibrate/classic/eyetracker_calib.png +0 -0
  1796. psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib.png +0 -0
  1797. psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib@2x.png +0 -0
  1798. psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib.png +0 -0
  1799. psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib@2x.png +0 -0
  1800. psychopy/experiment/routines/eyetracker_validate/__init__.py +354 -0
  1801. psychopy/experiment/routines/eyetracker_validate/classic/eyetracker_valid.png +0 -0
  1802. psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid.png +0 -0
  1803. psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid@2x.png +0 -0
  1804. psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid.png +0 -0
  1805. psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid@2x.png +0 -0
  1806. psychopy/experiment/routines/pavlovia_survey/__init__.py +236 -0
  1807. psychopy/experiment/routines/pavlovia_survey/classic/survey.png +0 -0
  1808. psychopy/experiment/routines/pavlovia_survey/classic/survey@2x.png +0 -0
  1809. psychopy/experiment/routines/pavlovia_survey/dark/survey.png +0 -0
  1810. psychopy/experiment/routines/pavlovia_survey/dark/survey@2x.png +0 -0
  1811. psychopy/experiment/routines/pavlovia_survey/light/survey.png +0 -0
  1812. psychopy/experiment/routines/pavlovia_survey/light/survey@2x.png +0 -0
  1813. psychopy/experiment/routines/unknown/__init__.py +30 -0
  1814. psychopy/experiment/routines/unknown/classic/eyetracker_record.png +0 -0
  1815. psychopy/experiment/routines/unknown/classic/unknown.png +0 -0
  1816. psychopy/experiment/routines/unknown/dark/eyetracker_record.png +0 -0
  1817. psychopy/experiment/routines/unknown/dark/eyetracker_record@2x.png +0 -0
  1818. psychopy/experiment/routines/unknown/dark/unknown.png +0 -0
  1819. psychopy/experiment/routines/unknown/dark/unknown@2x.png +0 -0
  1820. psychopy/experiment/routines/unknown/light/eyetracker_record.png +0 -0
  1821. psychopy/experiment/routines/unknown/light/eyetracker_record@2x.png +0 -0
  1822. psychopy/experiment/routines/unknown/light/unknown.png +0 -0
  1823. psychopy/experiment/routines/unknown/light/unknown@2x.png +0 -0
  1824. psychopy/experiment/routines/utils.py +0 -0
  1825. psychopy/experiment/routines/visualValidator/__init__.py +395 -0
  1826. psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
  1827. psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
  1828. psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
  1829. psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
  1830. psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
  1831. psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
  1832. psychopy/experiment/staircaseTemplate.xltx +0 -0
  1833. psychopy/experiment/utils.py +44 -0
  1834. psychopy/filters.py +11 -0
  1835. psychopy/gamma.py +9 -0
  1836. psychopy/gui/__init__.py +47 -0
  1837. psychopy/gui/qtgui.py +882 -0
  1838. psychopy/gui/util.py +74 -0
  1839. psychopy/gui/wxgui.py +420 -0
  1840. psychopy/hardware/__init__.py +178 -0
  1841. psychopy/hardware/base.py +466 -0
  1842. psychopy/hardware/bbtk/__init__.py +24 -0
  1843. psychopy/hardware/brainproducts.py +25 -0
  1844. psychopy/hardware/button.py +238 -0
  1845. psychopy/hardware/buttonbox/__init__.py +122 -0
  1846. psychopy/hardware/camera/__init__.py +3657 -0
  1847. psychopy/hardware/cedrus.py +24 -0
  1848. psychopy/hardware/crs/__init__.py +46 -0
  1849. psychopy/hardware/crs/bits.py +37 -0
  1850. psychopy/hardware/crs/colorcal.py +18 -0
  1851. psychopy/hardware/crs/optical.py +33 -0
  1852. psychopy/hardware/crs/shaders.py +34 -0
  1853. psychopy/hardware/emotiv.py +33 -0
  1854. psychopy/hardware/emulator.py +46 -0
  1855. psychopy/hardware/exceptions.py +86 -0
  1856. psychopy/hardware/eyetracker.py +132 -0
  1857. psychopy/hardware/forp.py +37 -0
  1858. psychopy/hardware/gammasci.py +26 -0
  1859. psychopy/hardware/iolab.py +24 -0
  1860. psychopy/hardware/joystick/__init__.py +1196 -0
  1861. psychopy/hardware/joystick/_base.py +251 -0
  1862. psychopy/hardware/joystick/backend_glfw.py +306 -0
  1863. psychopy/hardware/joystick/backend_pyglet.py +309 -0
  1864. psychopy/hardware/joystick/mappings.py +287 -0
  1865. psychopy/hardware/keyboard.py +975 -0
  1866. psychopy/hardware/knownDevices.json +52 -0
  1867. psychopy/hardware/labhackers.py +27 -0
  1868. psychopy/hardware/labjacks.py +26 -0
  1869. psychopy/hardware/lightsensor.py +789 -0
  1870. psychopy/hardware/listener.py +330 -0
  1871. psychopy/hardware/manager.py +992 -0
  1872. psychopy/hardware/microphone.py +1630 -0
  1873. psychopy/hardware/minolta.py +33 -0
  1874. psychopy/hardware/monitor.py +144 -0
  1875. psychopy/hardware/mouse/__init__.py +885 -0
  1876. psychopy/hardware/photometer/__init__.py +207 -0
  1877. psychopy/hardware/pr.py +33 -0
  1878. psychopy/hardware/qmix.py +32 -0
  1879. psychopy/hardware/serialdevice.py +387 -0
  1880. psychopy/hardware/soundsensor.py +476 -0
  1881. psychopy/hardware/spatial/__init__.py +231 -0
  1882. psychopy/hardware/speaker.py +351 -0
  1883. psychopy/hardware/triggerbox/__init__.py +56 -0
  1884. psychopy/hardware/triggerbox/base.py +170 -0
  1885. psychopy/hardware/triggerbox/parallel.py +362 -0
  1886. psychopy/info.py +719 -0
  1887. psychopy/iohub/__init__.py +50 -0
  1888. psychopy/iohub/client/__init__.py +1481 -0
  1889. psychopy/iohub/client/connect.py +260 -0
  1890. psychopy/iohub/client/eyetracker/__init__.py +4 -0
  1891. psychopy/iohub/client/eyetracker/validation/__init__.py +8 -0
  1892. psychopy/iohub/client/eyetracker/validation/posgrid.py +274 -0
  1893. psychopy/iohub/client/eyetracker/validation/procedure.py +1292 -0
  1894. psychopy/iohub/client/eyetracker/validation/trigger.py +240 -0
  1895. psychopy/iohub/client/keyboard.py +513 -0
  1896. psychopy/iohub/client/wintab.py +378 -0
  1897. psychopy/iohub/constants.py +1176 -0
  1898. psychopy/iohub/datastore/__init__.py +439 -0
  1899. psychopy/iohub/datastore/default_datastore.yaml +8 -0
  1900. psychopy/iohub/datastore/util.py +868 -0
  1901. psychopy/iohub/default_config.yaml +17 -0
  1902. psychopy/iohub/devices/__init__.py +1047 -0
  1903. psychopy/iohub/devices/computer.py +607 -0
  1904. psychopy/iohub/devices/default_device.yaml +15 -0
  1905. psychopy/iohub/devices/deviceConfigValidation.py +519 -0
  1906. psychopy/iohub/devices/display/__init__.py +755 -0
  1907. psychopy/iohub/devices/display/default_display.yaml +118 -0
  1908. psychopy/iohub/devices/display/supported_config_settings.yaml +69 -0
  1909. psychopy/iohub/devices/eventfilters.py +3572 -0
  1910. psychopy/iohub/devices/experiment/__init__.py +316 -0
  1911. psychopy/iohub/devices/experiment/default_experiment.yaml +74 -0
  1912. psychopy/iohub/devices/experiment/supported_config_settings.yaml +26 -0
  1913. psychopy/iohub/devices/eyetracker/__init__.py +474 -0
  1914. psychopy/iohub/devices/eyetracker/calibration/__init__.py +1 -0
  1915. psychopy/iohub/devices/eyetracker/calibration/procedure.py +391 -0
  1916. psychopy/iohub/devices/eyetracker/default_eyetracker.yaml +18 -0
  1917. psychopy/iohub/devices/eyetracker/eye_events.py +1751 -0
  1918. psychopy/iohub/devices/eyetracker/filters/__init__.py +4 -0
  1919. psychopy/iohub/devices/eyetracker/filters/parser.py +913 -0
  1920. psychopy/iohub/devices/eyetracker/hw/__init__.py +4 -0
  1921. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +29 -0
  1922. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +29 -0
  1923. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +18 -0
  1924. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +31 -0
  1925. psychopy/iohub/devices/eyetracker/hw/mouse/__init__.py +8 -0
  1926. psychopy/iohub/devices/eyetracker/hw/mouse/calibration.py +10 -0
  1927. psychopy/iohub/devices/eyetracker/hw/mouse/default_eyetracker.yaml +102 -0
  1928. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +443 -0
  1929. psychopy/iohub/devices/eyetracker/hw/mouse/supported_config_settings.yaml +110 -0
  1930. psychopy/iohub/devices/eyetracker/hw/pupil_labs/__init__.py +4 -0
  1931. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +27 -0
  1932. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/eyetracker.py +18 -0
  1933. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/__init__.py +29 -0
  1934. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/bisector.py +20 -0
  1935. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/constants.py +19 -0
  1936. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/data_parse.py +22 -0
  1937. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/eyetracker.py +18 -0
  1938. psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/pupil_remote.py +18 -0
  1939. psychopy/iohub/devices/eyetracker/hw/sr_research/__init__.py +4 -0
  1940. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/__init__.py +28 -0
  1941. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/calibration.py +22 -0
  1942. psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/eyetracker.py +38 -0
  1943. psychopy/iohub/devices/eyetracker/hw/tobii/__init__.py +29 -0
  1944. psychopy/iohub/devices/eyetracker/hw/tobii/calibration.py +19 -0
  1945. psychopy/iohub/devices/eyetracker/hw/tobii/eyetracker.py +17 -0
  1946. psychopy/iohub/devices/eyetracker/hw/tobii/tobiiwrapper.py +19 -0
  1947. psychopy/iohub/devices/eyetracker/supported_config_settings.yaml +44 -0
  1948. psychopy/iohub/devices/keyboard/__init__.py +309 -0
  1949. psychopy/iohub/devices/keyboard/darwin.py +431 -0
  1950. psychopy/iohub/devices/keyboard/darwinkey.py +94 -0
  1951. psychopy/iohub/devices/keyboard/default_keyboard.yaml +94 -0
  1952. psychopy/iohub/devices/keyboard/linux2.py +102 -0
  1953. psychopy/iohub/devices/keyboard/supported_config_settings.yaml +39 -0
  1954. psychopy/iohub/devices/keyboard/win32.py +356 -0
  1955. psychopy/iohub/devices/mouse/__init__.py +709 -0
  1956. psychopy/iohub/devices/mouse/darwin.py +414 -0
  1957. psychopy/iohub/devices/mouse/default_mouse.yaml +92 -0
  1958. psychopy/iohub/devices/mouse/linux2.py +172 -0
  1959. psychopy/iohub/devices/mouse/supported_config_settings.yaml +39 -0
  1960. psychopy/iohub/devices/mouse/win32.py +284 -0
  1961. psychopy/iohub/devices/pyXHook.py +619 -0
  1962. psychopy/iohub/devices/serial/__init__.py +779 -0
  1963. psychopy/iohub/devices/serial/default_pstbox.yaml +108 -0
  1964. psychopy/iohub/devices/serial/default_serial.yaml +108 -0
  1965. psychopy/iohub/devices/serial/supported_config_settings.yaml +85 -0
  1966. psychopy/iohub/devices/serial/supported_config_settings_pstbox.yaml +85 -0
  1967. psychopy/iohub/devices/supported_config_settings.yaml +36 -0
  1968. psychopy/iohub/devices/wintab/__init__.py +580 -0
  1969. psychopy/iohub/devices/wintab/default_wintab.yaml +89 -0
  1970. psychopy/iohub/devices/wintab/supported_config_settings.yaml +42 -0
  1971. psychopy/iohub/devices/wintab/win32.py +722 -0
  1972. psychopy/iohub/devices/xlib.py +6795 -0
  1973. psychopy/iohub/errors.py +61 -0
  1974. psychopy/iohub/lazy_import.py +582 -0
  1975. psychopy/iohub/net.py +349 -0
  1976. psychopy/iohub/server.py +1075 -0
  1977. psychopy/iohub/start_iohub_process.py +121 -0
  1978. psychopy/iohub/util/__init__.py +832 -0
  1979. psychopy/iohub/util/visualangle.py +126 -0
  1980. psychopy/layout.py +916 -0
  1981. psychopy/liaison.py +471 -0
  1982. psychopy/locale_setup.py +41 -0
  1983. psychopy/localization/__init__.py +22 -0
  1984. psychopy/localization/_localization.py +155 -0
  1985. psychopy/localization/generateTranslationTemplate.py +257 -0
  1986. psychopy/localization/mappings.txt +170 -0
  1987. psychopy/localization/messages.pot +9950 -0
  1988. psychopy/localization/readme.txt +68 -0
  1989. psychopy/logging.py +415 -0
  1990. psychopy/microphone.py +28 -0
  1991. psychopy/misc.py +40 -0
  1992. psychopy/monitors/MonitorCenter.py +1354 -0
  1993. psychopy/monitors/__init__.py +30 -0
  1994. psychopy/monitors/calibData.py +78 -0
  1995. psychopy/monitors/calibTools.py +1301 -0
  1996. psychopy/monitors/getLumSeries.py +50 -0
  1997. psychopy/monitors/psychopy-icon.svg +395 -0
  1998. psychopy/monitors/psychopy.ico +0 -0
  1999. psychopy/parallel/__init__.py +206 -0
  2000. psychopy/parallel/_dlportio.py +150 -0
  2001. psychopy/parallel/_inpout.py +120 -0
  2002. psychopy/parallel/_linux.py +105 -0
  2003. psychopy/piloting.py +61 -0
  2004. psychopy/platform_specific/__init__.py +46 -0
  2005. psychopy/platform_specific/darwin.py +326 -0
  2006. psychopy/platform_specific/linux.py +77 -0
  2007. psychopy/platform_specific/win32.py +124 -0
  2008. psychopy/plugins/__init__.py +1397 -0
  2009. psychopy/plugins/util.py +104 -0
  2010. psychopy/preferences/Darwin.spec +294 -0
  2011. psychopy/preferences/FreeBSD.spec +294 -0
  2012. psychopy/preferences/Linux.spec +294 -0
  2013. psychopy/preferences/Windows.spec +294 -0
  2014. psychopy/preferences/__init__.py +20 -0
  2015. psychopy/preferences/baseNoArch.spec +290 -0
  2016. psychopy/preferences/devices.py +80 -0
  2017. psychopy/preferences/generateHints.py +93 -0
  2018. psychopy/preferences/generateSpec.py +80 -0
  2019. psychopy/preferences/hints.py +367 -0
  2020. psychopy/preferences/preferences.py +448 -0
  2021. psychopy/projects/__init__.py +11 -0
  2022. psychopy/projects/gitignore.py +45 -0
  2023. psychopy/projects/pavlovia.py +1674 -0
  2024. psychopy/projects/sshkeys.py +107 -0
  2025. psychopy/psychojs.zip +0 -0
  2026. psychopy/pylintrc +69 -0
  2027. psychopy/scripts/__init__.py +0 -0
  2028. psychopy/scripts/psychopy-pkgutil.py +969 -0
  2029. psychopy/scripts/psyexpCompile.py +229 -0
  2030. psychopy/session.py +1651 -0
  2031. psychopy/sound/__init__.py +50 -0
  2032. psychopy/sound/_base.py +399 -0
  2033. psychopy/sound/audioclip.py +1325 -0
  2034. psychopy/sound/audiodevice.py +857 -0
  2035. psychopy/sound/backend_ptb.py +406 -0
  2036. psychopy/sound/backend_pygame.py +323 -0
  2037. psychopy/sound/backend_pyo.py +64 -0
  2038. psychopy/sound/backend_pysound.py +324 -0
  2039. psychopy/sound/backend_sounddevice.py +44 -0
  2040. psychopy/sound/backends/__init__.py +0 -0
  2041. psychopy/sound/exceptions.py +115 -0
  2042. psychopy/sound/microphone.py +357 -0
  2043. psychopy/sound/sound.py +58 -0
  2044. psychopy/sound/transcribe.py +1321 -0
  2045. psychopy/tests/README.md +344 -0
  2046. psychopy/tests/__init__.py +47 -0
  2047. psychopy/tests/data/Electronic_Chime-KevanGC-495939803.wav +0 -0
  2048. psychopy/tests/data/TestCircle_w128h128_bottom_left.png +0 -0
  2049. psychopy/tests/data/TestCircle_w128h128_bottom_right.png +0 -0
  2050. psychopy/tests/data/TestCircle_w128h128_center_center.png +0 -0
  2051. psychopy/tests/data/TestCircle_w128h128_top_left.png +0 -0
  2052. psychopy/tests/data/TestCircle_w128h128_top_right.png +0 -0
  2053. psychopy/tests/data/TestCircle_w128h64_bottom_left.png +0 -0
  2054. psychopy/tests/data/TestCircle_w128h64_bottom_right.png +0 -0
  2055. psychopy/tests/data/TestCircle_w128h64_center_center.png +0 -0
  2056. psychopy/tests/data/TestCircle_w128h64_top_left.png +0 -0
  2057. psychopy/tests/data/TestCircle_w128h64_top_right.png +0 -0
  2058. psychopy/tests/data/TestCircle_w64h128_bottom_left.png +0 -0
  2059. psychopy/tests/data/TestCircle_w64h128_bottom_right.png +0 -0
  2060. psychopy/tests/data/TestCircle_w64h128_center_center.png +0 -0
  2061. psychopy/tests/data/TestCircle_w64h128_top_left.png +0 -0
  2062. psychopy/tests/data/TestCircle_w64h128_top_right.png +0 -0
  2063. psychopy/tests/data/TestCircle_w64h64_bottom_left.png +0 -0
  2064. psychopy/tests/data/TestCircle_w64h64_bottom_right.png +0 -0
  2065. psychopy/tests/data/TestCircle_w64h64_center_center.png +0 -0
  2066. psychopy/tests/data/TestCircle_w64h64_top_left.png +0 -0
  2067. psychopy/tests/data/TestCircle_w64h64_top_right.png +0 -0
  2068. psychopy/tests/data/TestForm_scrolling_nq10_s0.5.png +0 -0
  2069. psychopy/tests/data/TestForm_scrolling_nq10_s0.png +0 -0
  2070. psychopy/tests/data/TestForm_scrolling_nq10_s1.png +0 -0
  2071. psychopy/tests/data/TestForm_scrolling_nq1_s0.5.png +0 -0
  2072. psychopy/tests/data/TestForm_scrolling_nq1_s0.png +0 -0
  2073. psychopy/tests/data/TestForm_scrolling_nq1_s1.png +0 -0
  2074. psychopy/tests/data/TestForm_scrolling_nq3_s0.5.png +0 -0
  2075. psychopy/tests/data/TestForm_scrolling_nq3_s0.png +0 -0
  2076. psychopy/tests/data/TestForm_scrolling_nq3_s1.png +0 -0
  2077. psychopy/tests/data/TestImage_w128h128_bottom_left.png +0 -0
  2078. psychopy/tests/data/TestImage_w128h128_bottom_right.png +0 -0
  2079. psychopy/tests/data/TestImage_w128h128_center_center.png +0 -0
  2080. psychopy/tests/data/TestImage_w128h128_top_left.png +0 -0
  2081. psychopy/tests/data/TestImage_w128h128_top_right.png +0 -0
  2082. psychopy/tests/data/TestImage_w128h64_bottom_left.png +0 -0
  2083. psychopy/tests/data/TestImage_w128h64_bottom_right.png +0 -0
  2084. psychopy/tests/data/TestImage_w128h64_center_center.png +0 -0
  2085. psychopy/tests/data/TestImage_w128h64_top_left.png +0 -0
  2086. psychopy/tests/data/TestImage_w128h64_top_right.png +0 -0
  2087. psychopy/tests/data/TestImage_w64h128_bottom_left.png +0 -0
  2088. psychopy/tests/data/TestImage_w64h128_bottom_right.png +0 -0
  2089. psychopy/tests/data/TestImage_w64h128_center_center.png +0 -0
  2090. psychopy/tests/data/TestImage_w64h128_top_left.png +0 -0
  2091. psychopy/tests/data/TestImage_w64h128_top_right.png +0 -0
  2092. psychopy/tests/data/TestImage_w64h64_bottom_left.png +0 -0
  2093. psychopy/tests/data/TestImage_w64h64_bottom_right.png +0 -0
  2094. psychopy/tests/data/TestImage_w64h64_center_center.png +0 -0
  2095. psychopy/tests/data/TestImage_w64h64_top_left.png +0 -0
  2096. psychopy/tests/data/TestImage_w64h64_top_right.png +0 -0
  2097. psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.3.png +0 -0
  2098. psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.6.png +0 -0
  2099. psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.3.png +0 -0
  2100. psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.6.png +0 -0
  2101. psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.3.png +0 -0
  2102. psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.6.png +0 -0
  2103. psychopy/tests/data/TestProgress_testValue_minmax_0.png +0 -0
  2104. psychopy/tests/data/TestProgress_testValue_minmax_1.png +0 -0
  2105. psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.3.png +0 -0
  2106. psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.6.png +0 -0
  2107. psychopy/tests/data/TestProgress_testValue_vertical_center center_0.3.png +0 -0
  2108. psychopy/tests/data/TestProgress_testValue_vertical_center center_0.6.png +0 -0
  2109. psychopy/tests/data/TestProgress_testValue_vertical_top center_0.3.png +0 -0
  2110. psychopy/tests/data/TestProgress_testValue_vertical_top center_0.6.png +0 -0
  2111. psychopy/tests/data/TestProgress_w128h128_bottom_left.png +0 -0
  2112. psychopy/tests/data/TestProgress_w128h128_bottom_right.png +0 -0
  2113. psychopy/tests/data/TestProgress_w128h128_center_center.png +0 -0
  2114. psychopy/tests/data/TestProgress_w128h128_top_left.png +0 -0
  2115. psychopy/tests/data/TestProgress_w128h128_top_right.png +0 -0
  2116. psychopy/tests/data/TestProgress_w128h64_bottom_left.png +0 -0
  2117. psychopy/tests/data/TestProgress_w128h64_bottom_right.png +0 -0
  2118. psychopy/tests/data/TestProgress_w128h64_center_center.png +0 -0
  2119. psychopy/tests/data/TestProgress_w128h64_top_left.png +0 -0
  2120. psychopy/tests/data/TestProgress_w128h64_top_right.png +0 -0
  2121. psychopy/tests/data/TestProgress_w64h128_bottom_left.png +0 -0
  2122. psychopy/tests/data/TestProgress_w64h128_bottom_right.png +0 -0
  2123. psychopy/tests/data/TestProgress_w64h128_center_center.png +0 -0
  2124. psychopy/tests/data/TestProgress_w64h128_top_left.png +0 -0
  2125. psychopy/tests/data/TestProgress_w64h128_top_right.png +0 -0
  2126. psychopy/tests/data/TestProgress_w64h64_bottom_left.png +0 -0
  2127. psychopy/tests/data/TestProgress_w64h64_bottom_right.png +0 -0
  2128. psychopy/tests/data/TestProgress_w64h64_center_center.png +0 -0
  2129. psychopy/tests/data/TestProgress_w64h64_top_left.png +0 -0
  2130. psychopy/tests/data/TestProgress_w64h64_top_right.png +0 -0
  2131. psychopy/tests/data/TestROI_w128h128_bottom_left.png +0 -0
  2132. psychopy/tests/data/TestROI_w128h128_bottom_right.png +0 -0
  2133. psychopy/tests/data/TestROI_w128h128_center_center.png +0 -0
  2134. psychopy/tests/data/TestROI_w128h128_top_left.png +0 -0
  2135. psychopy/tests/data/TestROI_w128h128_top_right.png +0 -0
  2136. psychopy/tests/data/TestROI_w128h64_bottom_left.png +0 -0
  2137. psychopy/tests/data/TestROI_w128h64_bottom_right.png +0 -0
  2138. psychopy/tests/data/TestROI_w128h64_center_center.png +0 -0
  2139. psychopy/tests/data/TestROI_w128h64_top_left.png +0 -0
  2140. psychopy/tests/data/TestROI_w128h64_top_right.png +0 -0
  2141. psychopy/tests/data/TestROI_w64h128_bottom_left.png +0 -0
  2142. psychopy/tests/data/TestROI_w64h128_bottom_right.png +0 -0
  2143. psychopy/tests/data/TestROI_w64h128_center_center.png +0 -0
  2144. psychopy/tests/data/TestROI_w64h128_top_left.png +0 -0
  2145. psychopy/tests/data/TestROI_w64h128_top_right.png +0 -0
  2146. psychopy/tests/data/TestROI_w64h64_bottom_left.png +0 -0
  2147. psychopy/tests/data/TestROI_w64h64_bottom_right.png +0 -0
  2148. psychopy/tests/data/TestROI_w64h64_center_center.png +0 -0
  2149. psychopy/tests/data/TestROI_w64h64_top_left.png +0 -0
  2150. psychopy/tests/data/TestROI_w64h64_top_right.png +0 -0
  2151. psychopy/tests/data/TestSession_exp1.png +0 -0
  2152. psychopy/tests/data/TestSession_exp2.png +0 -0
  2153. psychopy/tests/data/TestShape_w128h128_bottom_left.png +0 -0
  2154. psychopy/tests/data/TestShape_w128h128_bottom_right.png +0 -0
  2155. psychopy/tests/data/TestShape_w128h128_center_center.png +0 -0
  2156. psychopy/tests/data/TestShape_w128h128_top_left.png +0 -0
  2157. psychopy/tests/data/TestShape_w128h128_top_right.png +0 -0
  2158. psychopy/tests/data/TestShape_w128h64_bottom_left.png +0 -0
  2159. psychopy/tests/data/TestShape_w128h64_bottom_right.png +0 -0
  2160. psychopy/tests/data/TestShape_w128h64_center_center.png +0 -0
  2161. psychopy/tests/data/TestShape_w128h64_top_left.png +0 -0
  2162. psychopy/tests/data/TestShape_w128h64_top_right.png +0 -0
  2163. psychopy/tests/data/TestShape_w64h128_bottom_left.png +0 -0
  2164. psychopy/tests/data/TestShape_w64h128_bottom_right.png +0 -0
  2165. psychopy/tests/data/TestShape_w64h128_center_center.png +0 -0
  2166. psychopy/tests/data/TestShape_w64h128_top_left.png +0 -0
  2167. psychopy/tests/data/TestShape_w64h128_top_right.png +0 -0
  2168. psychopy/tests/data/TestShape_w64h64_bottom_left.png +0 -0
  2169. psychopy/tests/data/TestShape_w64h64_bottom_right.png +0 -0
  2170. psychopy/tests/data/TestShape_w64h64_center_center.png +0 -0
  2171. psychopy/tests/data/TestShape_w64h64_top_left.png +0 -0
  2172. psychopy/tests/data/TestShape_w64h64_top_right.png +0 -0
  2173. psychopy/tests/data/TestTarget_w128h128_bottom_left.png +0 -0
  2174. psychopy/tests/data/TestTarget_w128h128_bottom_right.png +0 -0
  2175. psychopy/tests/data/TestTarget_w128h128_center_center.png +0 -0
  2176. psychopy/tests/data/TestTarget_w128h128_top_left.png +0 -0
  2177. psychopy/tests/data/TestTarget_w128h128_top_right.png +0 -0
  2178. psychopy/tests/data/TestTarget_w128h64_bottom_left.png +0 -0
  2179. psychopy/tests/data/TestTarget_w128h64_bottom_right.png +0 -0
  2180. psychopy/tests/data/TestTarget_w128h64_center_center.png +0 -0
  2181. psychopy/tests/data/TestTarget_w128h64_top_left.png +0 -0
  2182. psychopy/tests/data/TestTarget_w128h64_top_right.png +0 -0
  2183. psychopy/tests/data/TestTarget_w64h128_bottom_left.png +0 -0
  2184. psychopy/tests/data/TestTarget_w64h128_bottom_right.png +0 -0
  2185. psychopy/tests/data/TestTarget_w64h128_center_center.png +0 -0
  2186. psychopy/tests/data/TestTarget_w64h128_top_left.png +0 -0
  2187. psychopy/tests/data/TestTarget_w64h128_top_right.png +0 -0
  2188. psychopy/tests/data/TestTarget_w64h64_bottom_left.png +0 -0
  2189. psychopy/tests/data/TestTarget_w64h64_bottom_right.png +0 -0
  2190. psychopy/tests/data/TestTarget_w64h64_center_center.png +0 -0
  2191. psychopy/tests/data/TestTarget_w64h64_top_left.png +0 -0
  2192. psychopy/tests/data/TestTarget_w64h64_top_right.png +0 -0
  2193. psychopy/tests/data/TestTextbox_testLetterSpacing_0p6.png +0 -0
  2194. psychopy/tests/data/TestTextbox_testLetterSpacing_0p8.png +0 -0
  2195. psychopy/tests/data/TestTextbox_testLetterSpacing_1.png +0 -0
  2196. psychopy/tests/data/TestTextbox_testLetterSpacing_1p2.png +0 -0
  2197. psychopy/tests/data/TestTextbox_testLetterSpacing_1p4.png +0 -0
  2198. psychopy/tests/data/TestTextbox_testLetterSpacing_1p6.png +0 -0
  2199. psychopy/tests/data/TestTextbox_testLetterSpacing_1p8.png +0 -0
  2200. psychopy/tests/data/TestTextbox_testLetterSpacing_2p0.png +0 -0
  2201. psychopy/tests/data/TestTextbox_testLetterSpacing_None.png +0 -0
  2202. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  2203. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  2204. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  2205. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  2206. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  2207. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  2208. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  2209. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  2210. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  2211. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  2212. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  2213. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  2214. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  2215. psychopy/tests/data/Test_textbox_testSpeechpoint_-3ovr8_0ovr8.png +0 -0
  2216. psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_-3ovr8.png +0 -0
  2217. psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_3ovr8.png +0 -0
  2218. psychopy/tests/data/Test_textbox_testSpeechpoint_3ovr8_0ovr8.png +0 -0
  2219. psychopy/tests/data/Test_textbox_w128h128_bottom_left.png +0 -0
  2220. psychopy/tests/data/Test_textbox_w128h128_bottom_right.png +0 -0
  2221. psychopy/tests/data/Test_textbox_w128h128_center_center.png +0 -0
  2222. psychopy/tests/data/Test_textbox_w128h128_top_left.png +0 -0
  2223. psychopy/tests/data/Test_textbox_w128h128_top_right.png +0 -0
  2224. psychopy/tests/data/Test_textbox_w128h64_bottom_left.png +0 -0
  2225. psychopy/tests/data/Test_textbox_w128h64_bottom_right.png +0 -0
  2226. psychopy/tests/data/Test_textbox_w128h64_center_center.png +0 -0
  2227. psychopy/tests/data/Test_textbox_w128h64_top_left.png +0 -0
  2228. psychopy/tests/data/Test_textbox_w128h64_top_right.png +0 -0
  2229. psychopy/tests/data/Test_textbox_w64h128_bottom_left.png +0 -0
  2230. psychopy/tests/data/Test_textbox_w64h128_bottom_right.png +0 -0
  2231. psychopy/tests/data/Test_textbox_w64h128_center_center.png +0 -0
  2232. psychopy/tests/data/Test_textbox_w64h128_top_left.png +0 -0
  2233. psychopy/tests/data/Test_textbox_w64h128_top_right.png +0 -0
  2234. psychopy/tests/data/Test_textbox_w64h64_bottom_left.png +0 -0
  2235. psychopy/tests/data/Test_textbox_w64h64_bottom_right.png +0 -0
  2236. psychopy/tests/data/Test_textbox_w64h64_center_center.png +0 -0
  2237. psychopy/tests/data/Test_textbox_w64h64_top_left.png +0 -0
  2238. psychopy/tests/data/Test_textbox_w64h64_top_right.png +0 -0
  2239. psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_left.png +0 -0
  2240. psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_right.png +0 -0
  2241. psychopy/tests/data/Test_uax14_textbox_w128h128_center_center.png +0 -0
  2242. psychopy/tests/data/Test_uax14_textbox_w128h128_top_left.png +0 -0
  2243. psychopy/tests/data/Test_uax14_textbox_w128h128_top_right.png +0 -0
  2244. psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_left.png +0 -0
  2245. psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_right.png +0 -0
  2246. psychopy/tests/data/Test_uax14_textbox_w128h64_center_center.png +0 -0
  2247. psychopy/tests/data/Test_uax14_textbox_w128h64_top_left.png +0 -0
  2248. psychopy/tests/data/Test_uax14_textbox_w128h64_top_right.png +0 -0
  2249. psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_left.png +0 -0
  2250. psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_right.png +0 -0
  2251. psychopy/tests/data/Test_uax14_textbox_w64h128_center_center.png +0 -0
  2252. psychopy/tests/data/Test_uax14_textbox_w64h128_top_left.png +0 -0
  2253. psychopy/tests/data/Test_uax14_textbox_w64h128_top_right.png +0 -0
  2254. psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_left.png +0 -0
  2255. psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_right.png +0 -0
  2256. psychopy/tests/data/Test_uax14_textbox_w64h64_center_center.png +0 -0
  2257. psychopy/tests/data/Test_uax14_textbox_w64h64_top_left.png +0 -0
  2258. psychopy/tests/data/Test_uax14_textbox_w64h64_top_right.png +0 -0
  2259. psychopy/tests/data/TextComponent_disabled.psyexp +63 -0
  2260. psychopy/tests/data/TextComponent_not_disabled.psyexp +63 -0
  2261. psychopy/tests/data/aperture1_deg.png +0 -0
  2262. psychopy/tests/data/aperture1_degFlat.png +0 -0
  2263. psychopy/tests/data/aperture1_degFlatPos.png +0 -0
  2264. psychopy/tests/data/aperture1_norm.png +0 -0
  2265. psychopy/tests/data/aperture1_normHexbackground.png +0 -0
  2266. psychopy/tests/data/aperture1_normNoShade.png +0 -0
  2267. psychopy/tests/data/aperture1_pix.png +0 -0
  2268. psychopy/tests/data/aperture1_stencil.png +0 -0
  2269. psychopy/tests/data/aperture2_deg.png +0 -0
  2270. psychopy/tests/data/aperture2_degFlat.png +0 -0
  2271. psychopy/tests/data/aperture2_degFlatPos.png +0 -0
  2272. psychopy/tests/data/aperture2_norm.png +0 -0
  2273. psychopy/tests/data/aperture2_normHexbackground.png +0 -0
  2274. psychopy/tests/data/aperture2_normNoShade.png +0 -0
  2275. psychopy/tests/data/aperture2_pix.png +0 -0
  2276. psychopy/tests/data/aperture2_stencil.png +0 -0
  2277. psychopy/tests/data/beatandrcos_cm.png +0 -0
  2278. psychopy/tests/data/beatandrcos_deg.png +0 -0
  2279. psychopy/tests/data/beatandrcos_degFlat.png +0 -0
  2280. psychopy/tests/data/beatandrcos_degFlatPos.png +0 -0
  2281. psychopy/tests/data/beatandrcos_height.png +0 -0
  2282. psychopy/tests/data/beatandrcos_norm.png +0 -0
  2283. psychopy/tests/data/beatandrcos_normAddBlend.png +0 -0
  2284. psychopy/tests/data/beatandrcos_normHexbackground.png +0 -0
  2285. psychopy/tests/data/beatandrcos_pix.png +0 -0
  2286. psychopy/tests/data/beatandrcos_stencil.png +0 -0
  2287. psychopy/tests/data/blend_add_cm.png +0 -0
  2288. psychopy/tests/data/blend_add_deg.png +0 -0
  2289. psychopy/tests/data/blend_add_degFlat.png +0 -0
  2290. psychopy/tests/data/blend_add_degFlatPos.png +0 -0
  2291. psychopy/tests/data/blend_add_height.png +0 -0
  2292. psychopy/tests/data/blend_add_norm.png +0 -0
  2293. psychopy/tests/data/blend_add_normAddBlend.png +0 -0
  2294. psychopy/tests/data/blend_add_normHexbackground.png +0 -0
  2295. psychopy/tests/data/blend_add_normNoShade.png +0 -0
  2296. psychopy/tests/data/blend_add_pix.png +0 -0
  2297. psychopy/tests/data/blend_add_stencil.png +0 -0
  2298. psychopy/tests/data/broken2020_2_5_resources/broken_resources.psyexp +84 -0
  2299. psychopy/tests/data/broken2020_2_5_resources/psychopy72.png +0 -0
  2300. psychopy/tests/data/bufferimg_gabor_cm.png +0 -0
  2301. psychopy/tests/data/bufferimg_gabor_deg.png +0 -0
  2302. psychopy/tests/data/bufferimg_gabor_degFlat.png +0 -0
  2303. psychopy/tests/data/bufferimg_gabor_degFlatPos.png +0 -0
  2304. psychopy/tests/data/bufferimg_gabor_height.png +0 -0
  2305. psychopy/tests/data/bufferimg_gabor_norm.png +0 -0
  2306. psychopy/tests/data/bufferimg_gabor_normAddBlend.png +0 -0
  2307. psychopy/tests/data/bufferimg_gabor_normHexbackground.png +0 -0
  2308. psychopy/tests/data/bufferimg_gabor_normNoShade.png +0 -0
  2309. psychopy/tests/data/bufferimg_gabor_pix.png +0 -0
  2310. psychopy/tests/data/bufferimg_gabor_stencil.png +0 -0
  2311. psychopy/tests/data/circleHex_cm.png +0 -0
  2312. psychopy/tests/data/circleHex_deg.png +0 -0
  2313. psychopy/tests/data/circleHex_degFlat.png +0 -0
  2314. psychopy/tests/data/circleHex_degFlatPos.png +0 -0
  2315. psychopy/tests/data/circleHex_height.png +0 -0
  2316. psychopy/tests/data/circleHex_norm.png +0 -0
  2317. psychopy/tests/data/circleHex_normAddBlend.png +0 -0
  2318. psychopy/tests/data/circleHex_normHexbackground.png +0 -0
  2319. psychopy/tests/data/circleHex_normNoShade.png +0 -0
  2320. psychopy/tests/data/circleHex_pix.png +0 -0
  2321. psychopy/tests/data/circleHex_stencil.png +0 -0
  2322. psychopy/tests/data/corrFullRandom.csv +16 -0
  2323. psychopy/tests/data/corrFullRandom.tsv +6 -0
  2324. psychopy/tests/data/corrFullRandomTH2.csv +16 -0
  2325. psychopy/tests/data/corrMultiKeyExpWide.csv +3 -0
  2326. psychopy/tests/data/corrMultiKeyTrials.csv +8 -0
  2327. psychopy/tests/data/corrMultiKeyTrials.xlsx +0 -0
  2328. psychopy/tests/data/corrRandom.csv +16 -0
  2329. psychopy/tests/data/corrRandom.tsv +6 -0
  2330. psychopy/tests/data/corrRandomTH2.csv +16 -0
  2331. psychopy/tests/data/corrXlsx.xlsx +0 -0
  2332. psychopy/tests/data/correctScript/js/correctCodeComponent.js +181 -0
  2333. psychopy/tests/data/correctScript/js/correctImageComponent.js +204 -0
  2334. psychopy/tests/data/correctScript/js/correctKeyboardComponent.js +225 -0
  2335. psychopy/tests/data/correctScript/js/correctMouseComponent.js +221 -0
  2336. psychopy/tests/data/correctScript/js/correctMovieComponent.js +210 -0
  2337. psychopy/tests/data/correctScript/js/correctPolygonComponent.js +205 -0
  2338. psychopy/tests/data/correctScript/js/correctSliderComponent.js +209 -0
  2339. psychopy/tests/data/correctScript/js/correctSoundComponent.js +205 -0
  2340. psychopy/tests/data/correctScript/js/correctTextComponent.js +207 -0
  2341. psychopy/tests/data/correctScript/python/correctApertureComponent.py +157 -0
  2342. psychopy/tests/data/correctScript/python/correctCodeComponent.py +135 -0
  2343. psychopy/tests/data/correctScript/python/correctDotsComponent.py +161 -0
  2344. psychopy/tests/data/correctScript/python/correctEnvGratingComponent.py +167 -0
  2345. psychopy/tests/data/correctScript/python/correctFormComponent.py +150 -0
  2346. psychopy/tests/data/correctScript/python/correctGratingComponent.py +158 -0
  2347. psychopy/tests/data/correctScript/python/correctImageComponent.py +160 -0
  2348. psychopy/tests/data/correctScript/python/correctJoyButtonsComponent.py +204 -0
  2349. psychopy/tests/data/correctScript/python/correctJoystickComponent.py +214 -0
  2350. psychopy/tests/data/correctScript/python/correctKeyboardComponent.py +169 -0
  2351. psychopy/tests/data/correctScript/python/correctMicrophoneComponent.py +163 -0
  2352. psychopy/tests/data/correctScript/python/correctMouseComponent.py +174 -0
  2353. psychopy/tests/data/correctScript/python/correctMovieComponent.py +160 -0
  2354. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +169 -0
  2355. psychopy/tests/data/correctScript/python/correctParallelOutComponent.py +156 -0
  2356. psychopy/tests/data/correctScript/python/correctPatchComponent.py +159 -0
  2357. psychopy/tests/data/correctScript/python/correctPolygonComponent.py +159 -0
  2358. psychopy/tests/data/correctScript/python/correctQmixPumpComponent.py +174 -0
  2359. psychopy/tests/data/correctScript/python/correctRatingScaleComponent.py +152 -0
  2360. psychopy/tests/data/correctScript/python/correctSliderComponent.py +159 -0
  2361. psychopy/tests/data/correctScript/python/correctSoundComponent.py +157 -0
  2362. psychopy/tests/data/correctScript/python/correctStaticComponent.py +176 -0
  2363. psychopy/tests/data/correctScript/python/correctTextComponent.py +159 -0
  2364. psychopy/tests/data/correctScript/python/correctVariableComponent.py +139 -0
  2365. psychopy/tests/data/correctScript/python/correctcedrusButtonBoxComponent.py +199 -0
  2366. psychopy/tests/data/correctScript/python/correctioLabsButtonBoxComponent.py +187 -0
  2367. psychopy/tests/data/dataTest.xlsx +0 -0
  2368. psychopy/tests/data/dots_cm.png +0 -0
  2369. psychopy/tests/data/dots_deg.png +0 -0
  2370. psychopy/tests/data/dots_degFlat.png +0 -0
  2371. psychopy/tests/data/dots_degFlatPos.png +0 -0
  2372. psychopy/tests/data/dots_height.png +0 -0
  2373. psychopy/tests/data/dots_norm.png +0 -0
  2374. psychopy/tests/data/dots_normAddBlend.png +0 -0
  2375. psychopy/tests/data/dots_normHexbackground.png +0 -0
  2376. psychopy/tests/data/dots_normNoShade.png +0 -0
  2377. psychopy/tests/data/dots_pix.png +0 -0
  2378. psychopy/tests/data/dots_stencil.png +0 -0
  2379. psychopy/tests/data/duplicateHeaders.csv +2 -0
  2380. psychopy/tests/data/elarray1_cm.png +0 -0
  2381. psychopy/tests/data/elarray1_deg.png +0 -0
  2382. psychopy/tests/data/elarray1_degFlat.png +0 -0
  2383. psychopy/tests/data/elarray1_degFlatPos.png +0 -0
  2384. psychopy/tests/data/elarray1_height.png +0 -0
  2385. psychopy/tests/data/elarray1_norm.png +0 -0
  2386. psychopy/tests/data/elarray1_normAddBlend.png +0 -0
  2387. psychopy/tests/data/elarray1_normHexbackground.png +0 -0
  2388. psychopy/tests/data/elarray1_pix.png +0 -0
  2389. psychopy/tests/data/elarray1_stencil.png +0 -0
  2390. psychopy/tests/data/envelopeandrcos_cm.png +0 -0
  2391. psychopy/tests/data/envelopeandrcos_deg.png +0 -0
  2392. psychopy/tests/data/envelopeandrcos_degFlat.png +0 -0
  2393. psychopy/tests/data/envelopeandrcos_degFlatPos.png +0 -0
  2394. psychopy/tests/data/envelopeandrcos_height.png +0 -0
  2395. psychopy/tests/data/envelopeandrcos_norm.png +0 -0
  2396. psychopy/tests/data/envelopeandrcos_normAddBlend.png +0 -0
  2397. psychopy/tests/data/envelopeandrcos_normHexbackground.png +0 -0
  2398. psychopy/tests/data/envelopeandrcos_pix.png +0 -0
  2399. psychopy/tests/data/envelopeandrcos_stencil.png +0 -0
  2400. psychopy/tests/data/envelopepowerandrcos_cm.png +0 -0
  2401. psychopy/tests/data/envelopepowerandrcos_deg.png +0 -0
  2402. psychopy/tests/data/envelopepowerandrcos_degFlat.png +0 -0
  2403. psychopy/tests/data/envelopepowerandrcos_degFlatPos.png +0 -0
  2404. psychopy/tests/data/envelopepowerandrcos_height.png +0 -0
  2405. psychopy/tests/data/envelopepowerandrcos_norm.png +0 -0
  2406. psychopy/tests/data/envelopepowerandrcos_normAddBlend.png +0 -0
  2407. psychopy/tests/data/envelopepowerandrcos_normHexbackground.png +0 -0
  2408. psychopy/tests/data/envelopepowerandrcos_pix.png +0 -0
  2409. psychopy/tests/data/envelopepowerandrcos_stencil.png +0 -0
  2410. psychopy/tests/data/filltext.png +0 -0
  2411. psychopy/tests/data/form_font_demographics.png +0 -0
  2412. psychopy/tests/data/form_font_demographics.xlsx +0 -0
  2413. psychopy/tests/data/form_font_languages.png +0 -0
  2414. psychopy/tests/data/form_font_languages.xlsx +0 -0
  2415. psychopy/tests/data/form_font_nonstandard.png +0 -0
  2416. psychopy/tests/data/futureParams.psyexp +150 -0
  2417. psychopy/tests/data/gabor1_cm.png +0 -0
  2418. psychopy/tests/data/gabor1_deg.png +0 -0
  2419. psychopy/tests/data/gabor1_degFlat.png +0 -0
  2420. psychopy/tests/data/gabor1_degFlatPos.png +0 -0
  2421. psychopy/tests/data/gabor1_height.png +0 -0
  2422. psychopy/tests/data/gabor1_norm.png +0 -0
  2423. psychopy/tests/data/gabor1_normAddBlend.png +0 -0
  2424. psychopy/tests/data/gabor1_normHexbackground.png +0 -0
  2425. psychopy/tests/data/gabor1_normNoShade.png +0 -0
  2426. psychopy/tests/data/gabor1_pix.png +0 -0
  2427. psychopy/tests/data/gabor1_stencil.png +0 -0
  2428. psychopy/tests/data/gabor2_cm.png +0 -0
  2429. psychopy/tests/data/gabor2_deg.png +0 -0
  2430. psychopy/tests/data/gabor2_degFlat.png +0 -0
  2431. psychopy/tests/data/gabor2_degFlatPos.png +0 -0
  2432. psychopy/tests/data/gabor2_height.png +0 -0
  2433. psychopy/tests/data/gabor2_norm.png +0 -0
  2434. psychopy/tests/data/gabor2_normAddBlend.png +0 -0
  2435. psychopy/tests/data/gabor2_normHexbackground.png +0 -0
  2436. psychopy/tests/data/gabor2_normNoShade.png +0 -0
  2437. psychopy/tests/data/gabor2_pix.png +0 -0
  2438. psychopy/tests/data/gabor2_stencil.png +0 -0
  2439. psychopy/tests/data/ghost_stroop.psyexp +131 -0
  2440. psychopy/tests/data/ghost_trialTypes.xlsx +0 -0
  2441. psychopy/tests/data/green_48000.flac.dist +0 -0
  2442. psychopy/tests/data/greyscale.jpg +0 -0
  2443. psychopy/tests/data/greyscale2.png +0 -0
  2444. psychopy/tests/data/greyscale2_cm.png +0 -0
  2445. psychopy/tests/data/greyscale2_deg.png +0 -0
  2446. psychopy/tests/data/greyscale2_degFlat.png +0 -0
  2447. psychopy/tests/data/greyscale2_degFlatPos.png +0 -0
  2448. psychopy/tests/data/greyscale2_height.png +0 -0
  2449. psychopy/tests/data/greyscale2_norm.png +0 -0
  2450. psychopy/tests/data/greyscale2_normAddBlend.png +0 -0
  2451. psychopy/tests/data/greyscale2_normHexbackground.png +0 -0
  2452. psychopy/tests/data/greyscale2_normNoShade.png +0 -0
  2453. psychopy/tests/data/greyscale2_pix.png +0 -0
  2454. psychopy/tests/data/greyscale2_stencil.png +0 -0
  2455. psychopy/tests/data/greyscaleLowContr_cm.png +0 -0
  2456. psychopy/tests/data/greyscaleLowContr_deg.png +0 -0
  2457. psychopy/tests/data/greyscaleLowContr_degFlat.png +0 -0
  2458. psychopy/tests/data/greyscaleLowContr_degFlatPos.png +0 -0
  2459. psychopy/tests/data/greyscaleLowContr_height.png +0 -0
  2460. psychopy/tests/data/greyscaleLowContr_norm.png +0 -0
  2461. psychopy/tests/data/greyscaleLowContr_normAddBlend.png +0 -0
  2462. psychopy/tests/data/greyscaleLowContr_normHexbackground.png +0 -0
  2463. psychopy/tests/data/greyscaleLowContr_normNoShade.png +0 -0
  2464. psychopy/tests/data/greyscaleLowContr_pix.png +0 -0
  2465. psychopy/tests/data/greyscaleLowContr_stencil.png +0 -0
  2466. psychopy/tests/data/greyscale_cm.png +0 -0
  2467. psychopy/tests/data/greyscale_deg.png +0 -0
  2468. psychopy/tests/data/greyscale_degFlat.png +0 -0
  2469. psychopy/tests/data/greyscale_degFlatPos.png +0 -0
  2470. psychopy/tests/data/greyscale_height.png +0 -0
  2471. psychopy/tests/data/greyscale_norm.png +0 -0
  2472. psychopy/tests/data/greyscale_normAddBlend.png +0 -0
  2473. psychopy/tests/data/greyscale_normHexbackground.png +0 -0
  2474. psychopy/tests/data/greyscale_normNoShade.png +0 -0
  2475. psychopy/tests/data/greyscale_pix.png +0 -0
  2476. psychopy/tests/data/greyscale_stencil.png +0 -0
  2477. psychopy/tests/data/imageAndGauss_cm.png +0 -0
  2478. psychopy/tests/data/imageAndGauss_deg.png +0 -0
  2479. psychopy/tests/data/imageAndGauss_degFlat.png +0 -0
  2480. psychopy/tests/data/imageAndGauss_degFlatPos.png +0 -0
  2481. psychopy/tests/data/imageAndGauss_height.png +0 -0
  2482. psychopy/tests/data/imageAndGauss_norm.png +0 -0
  2483. psychopy/tests/data/imageAndGauss_normAddBlend.png +0 -0
  2484. psychopy/tests/data/imageAndGauss_normHexbackground.png +0 -0
  2485. psychopy/tests/data/imageAndGauss_normNoShade.png +0 -0
  2486. psychopy/tests/data/imageAndGauss_pix.png +0 -0
  2487. psychopy/tests/data/imageAndGauss_stencil.png +0 -0
  2488. psychopy/tests/data/movFrame1_cm.png +0 -0
  2489. psychopy/tests/data/movFrame1_deg.png +0 -0
  2490. psychopy/tests/data/movFrame1_degFlat.png +0 -0
  2491. psychopy/tests/data/movFrame1_degFlatPos.png +0 -0
  2492. psychopy/tests/data/movFrame1_height.png +0 -0
  2493. psychopy/tests/data/movFrame1_norm.png +0 -0
  2494. psychopy/tests/data/movFrame1_normAddBlend.png +0 -0
  2495. psychopy/tests/data/movFrame1_normHexbackground.png +0 -0
  2496. psychopy/tests/data/movFrame1_normNoShade.png +0 -0
  2497. psychopy/tests/data/movFrame1_pix.png +0 -0
  2498. psychopy/tests/data/movFrame1_stencil.png +0 -0
  2499. psychopy/tests/data/multiKeypressExperiment.psydat +357 -0
  2500. psychopy/tests/data/multiKeypressTrialhandler.psydat +252 -0
  2501. psychopy/tests/data/multiStairConds.xlsx +0 -0
  2502. psychopy/tests/data/multiStairQuestPlus.xlsx +0 -0
  2503. psychopy/tests/data/newstyle.psydat +6155 -0
  2504. psychopy/tests/data/noiseAndRcos_cm.png +0 -0
  2505. psychopy/tests/data/noiseAndRcos_deg.png +0 -0
  2506. psychopy/tests/data/noiseAndRcos_degFlat.png +0 -0
  2507. psychopy/tests/data/noiseAndRcos_degFlatPos.png +0 -0
  2508. psychopy/tests/data/noiseAndRcos_height.png +0 -0
  2509. psychopy/tests/data/noiseAndRcos_norm.png +0 -0
  2510. psychopy/tests/data/noiseAndRcos_normAddBlend.png +0 -0
  2511. psychopy/tests/data/noiseAndRcos_normHexbackground.png +0 -0
  2512. psychopy/tests/data/noiseAndRcos_normNoShade.png +0 -0
  2513. psychopy/tests/data/noiseAndRcos_pix.png +0 -0
  2514. psychopy/tests/data/noiseAndRcos_stencil.png +0 -0
  2515. psychopy/tests/data/noiseFiltersAndRcos_cm.png +0 -0
  2516. psychopy/tests/data/noiseFiltersAndRcos_deg.png +0 -0
  2517. psychopy/tests/data/noiseFiltersAndRcos_degFlat.png +0 -0
  2518. psychopy/tests/data/noiseFiltersAndRcos_degFlatPos.png +0 -0
  2519. psychopy/tests/data/noiseFiltersAndRcos_height.png +0 -0
  2520. psychopy/tests/data/noiseFiltersAndRcos_norm.png +0 -0
  2521. psychopy/tests/data/noiseFiltersAndRcos_normAddBlend.png +0 -0
  2522. psychopy/tests/data/noiseFiltersAndRcos_normHexbackground.png +0 -0
  2523. psychopy/tests/data/noiseFiltersAndRcos_normNoShade.png +0 -0
  2524. psychopy/tests/data/noiseFiltersAndRcos_pix.png +0 -0
  2525. psychopy/tests/data/noiseFiltersAndRcos_stencil.png +0 -0
  2526. psychopy/tests/data/numpyImage_cm.png +0 -0
  2527. psychopy/tests/data/numpyImage_deg.png +0 -0
  2528. psychopy/tests/data/numpyImage_degFlat.png +0 -0
  2529. psychopy/tests/data/numpyImage_degFlatPos.png +0 -0
  2530. psychopy/tests/data/numpyImage_height.png +0 -0
  2531. psychopy/tests/data/numpyImage_norm.png +0 -0
  2532. psychopy/tests/data/numpyImage_normAddBlend.png +0 -0
  2533. psychopy/tests/data/numpyImage_normHexbackground.png +0 -0
  2534. psychopy/tests/data/numpyImage_normNoShade.png +0 -0
  2535. psychopy/tests/data/numpyImage_pix.png +0 -0
  2536. psychopy/tests/data/numpyImage_stencil.png +0 -0
  2537. psychopy/tests/data/numpyLowContr_cm.png +0 -0
  2538. psychopy/tests/data/numpyLowContr_deg.png +0 -0
  2539. psychopy/tests/data/numpyLowContr_degFlat.png +0 -0
  2540. psychopy/tests/data/numpyLowContr_degFlatPos.png +0 -0
  2541. psychopy/tests/data/numpyLowContr_height.png +0 -0
  2542. psychopy/tests/data/numpyLowContr_norm.png +0 -0
  2543. psychopy/tests/data/numpyLowContr_normAddBlend.png +0 -0
  2544. psychopy/tests/data/numpyLowContr_normHexbackground.png +0 -0
  2545. psychopy/tests/data/numpyLowContr_normNoShade.png +0 -0
  2546. psychopy/tests/data/numpyLowContr_pix.png +0 -0
  2547. psychopy/tests/data/numpyLowContr_stencil.png +0 -0
  2548. psychopy/tests/data/oldstyle.psydat +1534 -0
  2549. psychopy/tests/data/oldstyle_stair.psydat +263 -0
  2550. psychopy/tests/data/ratingscale1_cm.png +0 -0
  2551. psychopy/tests/data/ratingscale1_deg.png +0 -0
  2552. psychopy/tests/data/ratingscale1_degFlat.png +0 -0
  2553. psychopy/tests/data/ratingscale1_degFlatPos.png +0 -0
  2554. psychopy/tests/data/ratingscale1_height.png +0 -0
  2555. psychopy/tests/data/ratingscale1_norm.png +0 -0
  2556. psychopy/tests/data/ratingscale1_normAddBlend.png +0 -0
  2557. psychopy/tests/data/ratingscale1_normHexbackground.png +0 -0
  2558. psychopy/tests/data/ratingscale1_normNoShade.png +0 -0
  2559. psychopy/tests/data/ratingscale1_pix.png +0 -0
  2560. psychopy/tests/data/ratingscale1_stencil.png +0 -0
  2561. psychopy/tests/data/red_16000.flac.dist +0 -0
  2562. psychopy/tests/data/retroListParam.psyexp +82 -0
  2563. psychopy/tests/data/right_to_left_unidcode.xlsx +0 -0
  2564. psychopy/tests/data/sample.meshwarp.data +11522 -0
  2565. psychopy/tests/data/shape2_1_cm.png +0 -0
  2566. psychopy/tests/data/shape2_1_deg.png +0 -0
  2567. psychopy/tests/data/shape2_1_degFlat.png +0 -0
  2568. psychopy/tests/data/shape2_1_degFlatPos.png +0 -0
  2569. psychopy/tests/data/shape2_1_height.png +0 -0
  2570. psychopy/tests/data/shape2_1_norm.png +0 -0
  2571. psychopy/tests/data/shape2_1_normAddBlend.png +0 -0
  2572. psychopy/tests/data/shape2_1_normHexbackground.png +0 -0
  2573. psychopy/tests/data/shape2_1_normNoShade.png +0 -0
  2574. psychopy/tests/data/shape2_1_pix.png +0 -0
  2575. psychopy/tests/data/shape2_1_stencil.png +0 -0
  2576. psychopy/tests/data/shape2_2_cm.png +0 -0
  2577. psychopy/tests/data/shape2_2_deg.png +0 -0
  2578. psychopy/tests/data/shape2_2_degFlat.png +0 -0
  2579. psychopy/tests/data/shape2_2_degFlatPos.png +0 -0
  2580. psychopy/tests/data/shape2_2_height.png +0 -0
  2581. psychopy/tests/data/shape2_2_norm.png +0 -0
  2582. psychopy/tests/data/shape2_2_normAddBlend.png +0 -0
  2583. psychopy/tests/data/shape2_2_normHexbackground.png +0 -0
  2584. psychopy/tests/data/shape2_2_normNoShade.png +0 -0
  2585. psychopy/tests/data/shape2_2_pix.png +0 -0
  2586. psychopy/tests/data/shape2_2_stencil.png +0 -0
  2587. psychopy/tests/data/simpleimage1_cm.png +0 -0
  2588. psychopy/tests/data/simpleimage1_deg.png +0 -0
  2589. psychopy/tests/data/simpleimage1_degFlat.png +0 -0
  2590. psychopy/tests/data/simpleimage1_degFlatPos.png +0 -0
  2591. psychopy/tests/data/simpleimage1_height.png +0 -0
  2592. psychopy/tests/data/simpleimage1_norm.png +0 -0
  2593. psychopy/tests/data/simpleimage1_normAddBlend.png +0 -0
  2594. psychopy/tests/data/simpleimage1_normHexbackground.png +0 -0
  2595. psychopy/tests/data/simpleimage1_normNoShade.png +0 -0
  2596. psychopy/tests/data/simpleimage1_pix.png +0 -0
  2597. psychopy/tests/data/simpleimage1_stencil.png +0 -0
  2598. psychopy/tests/data/test001EntryImporting.psyexp +66 -0
  2599. psychopy/tests/data/testMovie.mp4 +0 -0
  2600. psychopy/tests/data/test_basic_run.py +1 -0
  2601. psychopy/tests/data/test_circle_radius_.1height.png +0 -0
  2602. psychopy/tests/data/test_circle_radius_.2height.png +0 -0
  2603. psychopy/tests/data/test_circle_radius_10pix.png +0 -0
  2604. psychopy/tests/data/test_circle_radius_20pix.png +0 -0
  2605. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  2606. psychopy/tests/data/test_form_combinations_choice_bigItem.png +0 -0
  2607. psychopy/tests/data/test_form_combinations_choice_bigItemOverflow.png +0 -0
  2608. psychopy/tests/data/test_form_combinations_choice_bigResp.png +0 -0
  2609. psychopy/tests/data/test_form_combinations_choice_bigRespOverflow.png +0 -0
  2610. psychopy/tests/data/test_form_combinations_description_bigItem.png +0 -0
  2611. psychopy/tests/data/test_form_combinations_description_bigItemOverflow.png +0 -0
  2612. psychopy/tests/data/test_form_combinations_description_bigResp.png +0 -0
  2613. psychopy/tests/data/test_form_combinations_description_bigRespOverflow.png +0 -0
  2614. psychopy/tests/data/test_form_combinations_free text_bigItem.png +0 -0
  2615. psychopy/tests/data/test_form_combinations_free text_bigItemOverflow.png +0 -0
  2616. psychopy/tests/data/test_form_combinations_free text_bigResp.png +0 -0
  2617. psychopy/tests/data/test_form_combinations_free text_bigRespOverflow.png +0 -0
  2618. psychopy/tests/data/test_form_combinations_heading_bigItem.png +0 -0
  2619. psychopy/tests/data/test_form_combinations_heading_bigItemOverflow.png +0 -0
  2620. psychopy/tests/data/test_form_combinations_heading_bigResp.png +0 -0
  2621. psychopy/tests/data/test_form_combinations_heading_bigRespOverflow.png +0 -0
  2622. psychopy/tests/data/test_form_combinations_radio_bigItem.png +0 -0
  2623. psychopy/tests/data/test_form_combinations_radio_bigItemOverflow.png +0 -0
  2624. psychopy/tests/data/test_form_combinations_radio_bigResp.png +0 -0
  2625. psychopy/tests/data/test_form_combinations_radio_bigRespOverflow.png +0 -0
  2626. psychopy/tests/data/test_form_combinations_rating_bigItem.png +0 -0
  2627. psychopy/tests/data/test_form_combinations_rating_bigItemOverflow.png +0 -0
  2628. psychopy/tests/data/test_form_combinations_rating_bigResp.png +0 -0
  2629. psychopy/tests/data/test_form_combinations_rating_bigRespOverflow.png +0 -0
  2630. psychopy/tests/data/test_form_combinations_slider_bigItem.png +0 -0
  2631. psychopy/tests/data/test_form_combinations_slider_bigItemOverflow.png +0 -0
  2632. psychopy/tests/data/test_form_combinations_slider_bigResp.png +0 -0
  2633. psychopy/tests/data/test_form_combinations_slider_bigRespOverflow.png +0 -0
  2634. psychopy/tests/data/test_get_resources/blue.png +0 -0
  2635. psychopy/tests/data/test_get_resources/groupA.csv +3 -0
  2636. psychopy/tests/data/test_get_resources/groupB.csv +3 -0
  2637. psychopy/tests/data/test_get_resources/groups.csv +3 -0
  2638. psychopy/tests/data/test_get_resources/handledbyrm_constrloop.psyexp +143 -0
  2639. psychopy/tests/data/test_get_resources/handledbyrm_noloop.psyexp +114 -0
  2640. psychopy/tests/data/test_get_resources/handledbyrm_recurloop.psyexp +138 -0
  2641. psychopy/tests/data/test_get_resources/handledbyrm_strloop.psyexp +126 -0
  2642. psychopy/tests/data/test_get_resources/handledbystatic_constrloop.psyexp +141 -0
  2643. psychopy/tests/data/test_get_resources/handledbystatic_noloop.psyexp +112 -0
  2644. psychopy/tests/data/test_get_resources/handledbystatic_recurloop.psyexp +136 -0
  2645. psychopy/tests/data/test_get_resources/handledbystatic_strloop.psyexp +124 -0
  2646. psychopy/tests/data/test_get_resources/unhandled_constrloop.psyexp +128 -0
  2647. psychopy/tests/data/test_get_resources/unhandled_noloop.psyexp +99 -0
  2648. psychopy/tests/data/test_get_resources/unhandled_recurloop.psyexp +123 -0
  2649. psychopy/tests/data/test_get_resources/unhandled_strloop.psyexp +111 -0
  2650. psychopy/tests/data/test_get_resources/white.png +0 -0
  2651. psychopy/tests/data/test_get_resources/yellow.png +0 -0
  2652. psychopy/tests/data/test_image_aspect_default_None.png +0 -0
  2653. psychopy/tests/data/test_image_aspect_default_xFull_yNone.png +0 -0
  2654. psychopy/tests/data/test_image_aspect_default_xNone_yFull.png +0 -0
  2655. psychopy/tests/data/test_image_aspect_default_xNone_yNone.png +0 -0
  2656. psychopy/tests/data/test_image_flip_anchor_horiz.png +0 -0
  2657. psychopy/tests/data/test_image_flip_anchor_vert.png +0 -0
  2658. psychopy/tests/data/test_loaded_namespace/test_counterbalance.psyexp +142 -0
  2659. psychopy/tests/data/test_loaded_namespace/test_custom_missing.psyexp +129 -0
  2660. psychopy/tests/data/test_loaded_namespace/test_missing_counterbalance.psyexp +116 -0
  2661. psychopy/tests/data/test_loaded_namespace/test_mix_exp.psyexp +181 -0
  2662. psychopy/tests/data/test_loaded_namespace/test_mix_missing.psyexp +140 -0
  2663. psychopy/tests/data/test_loaded_namespace/test_mix_name_calibration.psyexp +164 -0
  2664. psychopy/tests/data/test_loops/testLoopsBlocks.psyexp +161 -0
  2665. psychopy/tests/data/test_loops/testStaircase.psyexp +105 -0
  2666. psychopy/tests/data/test_loops/test_current_loop_attr.psyexp +185 -0
  2667. psychopy/tests/data/test_panorama/panoramaTestImage.png +0 -0
  2668. psychopy/tests/data/test_panorama/panoramaTestImage.svg +1 -0
  2669. psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_-0.3.png +0 -0
  2670. psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_0.3.png +0 -0
  2671. psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_-0.3.png +0 -0
  2672. psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_0.3.png +0 -0
  2673. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_-1.0.png +0 -0
  2674. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_1.0.png +0 -0
  2675. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_-0.3.png +0 -0
  2676. psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_0.3.png +0 -0
  2677. psychopy/tests/data/test_resources.psyexp +491 -0
  2678. psychopy/tests/data/test_session/outside_root/externalExp.psyexp +116 -0
  2679. psychopy/tests/data/test_session/root/annotation/annotation.psyexp +161 -0
  2680. psychopy/tests/data/test_session/root/error/error.psyexp +93 -0
  2681. psychopy/tests/data/test_session/root/exp1/exp1.psyexp +133 -0
  2682. psychopy/tests/data/test_session/root/exp2/exp2.psyexp +133 -0
  2683. psychopy/tests/data/test_session/root/frameRate/frameRate.psyexp +114 -0
  2684. psychopy/tests/data/test_session/root/invUseVersion/invUseVersion.psyexp +133 -0
  2685. psychopy/tests/data/test_session/root/testClockFormat/testClockFormat.psyexp +122 -0
  2686. psychopy/tests/data/test_session/root/testCtrls/testCtrls.psyexp +115 -0
  2687. psychopy/tests/data/test_session/root/testEditExpInfo/testEditExpInfo.psyexp +135 -0
  2688. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  2689. psychopy/tests/data/test_session/root/testNamedButtonBox/testNamedButtonBox.psyexp +145 -0
  2690. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  2691. psychopy/tests/data/test_slider_horiz_accute_horiz.png +0 -0
  2692. psychopy/tests/data/test_slider_horiz_accute_vert.png +0 -0
  2693. psychopy/tests/data/test_slider_horiz_horiz.png +0 -0
  2694. psychopy/tests/data/test_slider_horiz_obtuse_horiz.png +0 -0
  2695. psychopy/tests/data/test_slider_horiz_obtuse_vert.png +0 -0
  2696. psychopy/tests/data/test_slider_horiz_vert.png +0 -0
  2697. psychopy/tests/data/test_slider_ticklabelloc_blanks.png +0 -0
  2698. psychopy/tests/data/test_slider_ticklabelloc_clustered.png +0 -0
  2699. psychopy/tests/data/test_slider_ticklabelloc_morelabels.png +0 -0
  2700. psychopy/tests/data/test_slider_ticklabelloc_morelabelsclustered.png +0 -0
  2701. psychopy/tests/data/test_slider_ticklabelloc_moreticks.png +0 -0
  2702. psychopy/tests/data/test_slider_ticklabelloc_moreticksclustered.png +0 -0
  2703. psychopy/tests/data/test_slider_ticklabelloc_nolabels.png +0 -0
  2704. psychopy/tests/data/test_slider_ticklabelloc_noticks.png +0 -0
  2705. psychopy/tests/data/test_slider_ticklabelloc_simple.png +0 -0
  2706. psychopy/tests/data/test_slider_triangle_horiz_False_flip_False.png +0 -0
  2707. psychopy/tests/data/test_slider_triangle_horiz_False_flip_True.png +0 -0
  2708. psychopy/tests/data/test_slider_triangle_horiz_True_flip_False.png +0 -0
  2709. psychopy/tests/data/test_slider_triangle_horiz_True_flip_True.png +0 -0
  2710. psychopy/tests/data/test_sounds/default_16000.wav +0 -0
  2711. psychopy/tests/data/test_sounds/default_192000.wav +0 -0
  2712. psychopy/tests/data/test_sounds/default_22050.wav +0 -0
  2713. psychopy/tests/data/test_sounds/default_32000.wav +0 -0
  2714. psychopy/tests/data/test_sounds/default_44100.wav +0 -0
  2715. psychopy/tests/data/test_sounds/default_48000.wav +0 -0
  2716. psychopy/tests/data/test_sounds/default_8000.wav +0 -0
  2717. psychopy/tests/data/test_sounds/default_96000.wav +0 -0
  2718. psychopy/tests/data/test_static_component_script.psyexp +76 -0
  2719. psychopy/tests/data/test_win_bg_large_256_default.png +0 -0
  2720. psychopy/tests/data/test_win_bg_large_500_default.png +0 -0
  2721. psychopy/tests/data/test_win_bg_small_200_default.png +0 -0
  2722. psychopy/tests/data/test_win_bg_small_256_default.png +0 -0
  2723. psychopy/tests/data/test_win_bg_tall_200_default.png +0 -0
  2724. psychopy/tests/data/test_win_bg_tall_256_default.png +0 -0
  2725. psychopy/tests/data/test_win_bg_tall_500_default.png +0 -0
  2726. psychopy/tests/data/test_win_bg_tall_fill_default.png +0 -0
  2727. psychopy/tests/data/test_win_bg_wide_200_default.png +0 -0
  2728. psychopy/tests/data/test_win_bg_wide_256_default.png +0 -0
  2729. psychopy/tests/data/test_win_bg_wide_500_default.png +0 -0
  2730. psychopy/tests/data/test_win_bg_wide_fill_default.png +0 -0
  2731. psychopy/tests/data/test_win_bgcolor_blue.png +0 -0
  2732. psychopy/tests/data/test_win_bgcolor_green.png +0 -0
  2733. psychopy/tests/data/test_win_bgcolor_red.png +0 -0
  2734. psychopy/tests/data/testimage.jpg +0 -0
  2735. psychopy/tests/data/testimagegray.jpg +0 -0
  2736. psychopy/tests/data/testpixels.png +0 -0
  2737. psychopy/tests/data/testwedges.png +0 -0
  2738. psychopy/tests/data/text1_cm.png +0 -0
  2739. psychopy/tests/data/text1_deg.png +0 -0
  2740. psychopy/tests/data/text1_degFlat.png +0 -0
  2741. psychopy/tests/data/text1_degFlatPos.png +0 -0
  2742. psychopy/tests/data/text1_height.png +0 -0
  2743. psychopy/tests/data/text1_norm.png +0 -0
  2744. psychopy/tests/data/text1_normAddBlend.png +0 -0
  2745. psychopy/tests/data/text1_normHexbackground.png +0 -0
  2746. psychopy/tests/data/text1_normNoShade.png +0 -0
  2747. psychopy/tests/data/text1_pix.png +0 -0
  2748. psychopy/tests/data/text1_stencil.png +0 -0
  2749. psychopy/tests/data/text2_cm.png +0 -0
  2750. psychopy/tests/data/text2_deg.png +0 -0
  2751. psychopy/tests/data/text2_degFlat.png +0 -0
  2752. psychopy/tests/data/text2_degFlatPos.png +0 -0
  2753. psychopy/tests/data/text2_norm.png +0 -0
  2754. psychopy/tests/data/text2_normNoShade.png +0 -0
  2755. psychopy/tests/data/text2_pix.png +0 -0
  2756. psychopy/tests/data/textbox_charcolors_default_roygbiv.png +0 -0
  2757. psychopy/tests/data/textbox_charcolors_default_white_hello_black_there.png +0 -0
  2758. psychopy/tests/data/textbox_default_align_bottom_center.png +0 -0
  2759. psychopy/tests/data/textbox_default_align_bottom_left.png +0 -0
  2760. psychopy/tests/data/textbox_default_align_bottom_right.png +0 -0
  2761. psychopy/tests/data/textbox_default_align_center.png +0 -0
  2762. psychopy/tests/data/textbox_default_align_center_center.png +0 -0
  2763. psychopy/tests/data/textbox_default_align_center_left.png +0 -0
  2764. psychopy/tests/data/textbox_default_align_center_right.png +0 -0
  2765. psychopy/tests/data/textbox_default_align_centre.png +0 -0
  2766. psychopy/tests/data/textbox_default_align_centre_centre.png +0 -0
  2767. psychopy/tests/data/textbox_default_align_more_than_two_words.png +0 -0
  2768. psychopy/tests/data/textbox_default_align_someword.png +0 -0
  2769. psychopy/tests/data/textbox_default_align_top_center.png +0 -0
  2770. psychopy/tests/data/textbox_default_align_top_left.png +0 -0
  2771. psychopy/tests/data/textbox_default_align_top_right.png +0 -0
  2772. psychopy/tests/data/textbox_default_colors_WOB.png +0 -0
  2773. psychopy/tests/data/textbox_default_colors_exemplar1.png +0 -0
  2774. psychopy/tests/data/textbox_default_colors_exemplar2.png +0 -0
  2775. psychopy/tests/data/textbox_default_colors_exemplar3.png +0 -0
  2776. psychopy/tests/data/textbox_default_colors_tyke1.png +0 -0
  2777. psychopy/tests/data/textbox_default_colors_tyke2.png +0 -0
  2778. psychopy/tests/data/textbox_default_colors_tyke3.png +0 -0
  2779. psychopy/tests/data/textbox_default_cutoff_top.png +0 -0
  2780. psychopy/tests/data/textbox_default_exemplar_1.png +0 -0
  2781. psychopy/tests/data/textbox_default_exemplar_2.png +0 -0
  2782. psychopy/tests/data/textbox_default_exemplar_3.png +0 -0
  2783. psychopy/tests/data/textbox_default_exemplar_4.png +0 -0
  2784. psychopy/tests/data/textbox_default_tyke_1.png +0 -0
  2785. psychopy/tests/data/textbox_default_tyke_2.png +0 -0
  2786. psychopy/tests/data/textbox_typing_blank.png +0 -0
  2787. psychopy/tests/data/textbox_typing_longKoeran.png +0 -0
  2788. psychopy/tests/data/textbox_typing_longWord.png +0 -0
  2789. psychopy/tests/data/textbox_typing_navDel.png +0 -0
  2790. psychopy/tests/data/textbox_typing_navLR.png +0 -0
  2791. psychopy/tests/data/textbox_typing_newline.png +0 -0
  2792. psychopy/tests/data/textbox_typing_pangram.png +0 -0
  2793. psychopy/tests/data/textbox_uax14_align_bottom_center.png +0 -0
  2794. psychopy/tests/data/textbox_uax14_align_bottom_left.png +0 -0
  2795. psychopy/tests/data/textbox_uax14_align_bottom_right.png +0 -0
  2796. psychopy/tests/data/textbox_uax14_align_center.png +0 -0
  2797. psychopy/tests/data/textbox_uax14_align_center_center.png +0 -0
  2798. psychopy/tests/data/textbox_uax14_align_center_left.png +0 -0
  2799. psychopy/tests/data/textbox_uax14_align_center_right.png +0 -0
  2800. psychopy/tests/data/textbox_uax14_align_centre.png +0 -0
  2801. psychopy/tests/data/textbox_uax14_align_centre_centre.png +0 -0
  2802. psychopy/tests/data/textbox_uax14_align_more_than_two_words.png +0 -0
  2803. psychopy/tests/data/textbox_uax14_align_someword.png +0 -0
  2804. psychopy/tests/data/textbox_uax14_align_top_center.png +0 -0
  2805. psychopy/tests/data/textbox_uax14_align_top_left.png +0 -0
  2806. psychopy/tests/data/textbox_uax14_align_top_right.png +0 -0
  2807. psychopy/tests/data/textbox_uax14_colors_WOB.png +0 -0
  2808. psychopy/tests/data/textbox_uax14_colors_exemplar1.png +0 -0
  2809. psychopy/tests/data/textbox_uax14_colors_exemplar2.png +0 -0
  2810. psychopy/tests/data/textbox_uax14_colors_exemplar3.png +0 -0
  2811. psychopy/tests/data/textbox_uax14_colors_tyke1.png +0 -0
  2812. psychopy/tests/data/textbox_uax14_colors_tyke2.png +0 -0
  2813. psychopy/tests/data/textbox_uax14_colors_tyke3.png +0 -0
  2814. psychopy/tests/data/textbox_uax14_cutoff_top.png +0 -0
  2815. psychopy/tests/data/textbox_uax14_exemplar_1.png +0 -0
  2816. psychopy/tests/data/textbox_uax14_exemplar_2.png +0 -0
  2817. psychopy/tests/data/textbox_uax14_exemplar_3.png +0 -0
  2818. psychopy/tests/data/textbox_uax14_exemplar_4.png +0 -0
  2819. psychopy/tests/data/textbox_uax14_tyke_1.png +0 -0
  2820. psychopy/tests/data/textbox_uax14_tyke_2.png +0 -0
  2821. psychopy/tests/data/trialTypes.csv +1 -0
  2822. psychopy/tests/data/trialTypes.docx +0 -0
  2823. psychopy/tests/data/trialTypes.pkl +0 -0
  2824. psychopy/tests/data/trialTypes.tsv +7 -0
  2825. psychopy/tests/data/trialTypes.xls +0 -0
  2826. psychopy/tests/data/trialTypes.xlsx +0 -0
  2827. psychopy/tests/data/trialTypes_eu.csv +7 -0
  2828. psychopy/tests/data/trialsBlankCols.xlsx +0 -0
  2829. psychopy/tests/data/wedge1_cm.png +0 -0
  2830. psychopy/tests/data/wedge1_deg.png +0 -0
  2831. psychopy/tests/data/wedge1_degFlat.png +0 -0
  2832. psychopy/tests/data/wedge1_degFlatPos.png +0 -0
  2833. psychopy/tests/data/wedge1_height.png +0 -0
  2834. psychopy/tests/data/wedge1_norm.png +0 -0
  2835. psychopy/tests/data/wedge1_normAddBlend.png +0 -0
  2836. psychopy/tests/data/wedge1_normHexbackground.png +0 -0
  2837. psychopy/tests/data/wedge1_normNoShade.png +0 -0
  2838. psychopy/tests/data/wedge1_pix.png +0 -0
  2839. psychopy/tests/data/wedge1_stencil.png +0 -0
  2840. psychopy/tests/data/wedge2_cm.png +0 -0
  2841. psychopy/tests/data/wedge2_deg.png +0 -0
  2842. psychopy/tests/data/wedge2_degFlat.png +0 -0
  2843. psychopy/tests/data/wedge2_degFlatPos.png +0 -0
  2844. psychopy/tests/data/wedge2_height.png +0 -0
  2845. psychopy/tests/data/wedge2_norm.png +0 -0
  2846. psychopy/tests/data/wedge2_normAddBlend.png +0 -0
  2847. psychopy/tests/data/wedge2_normHexbackground.png +0 -0
  2848. psychopy/tests/data/wedge2_normNoShade.png +0 -0
  2849. psychopy/tests/data/wedge2_pix.png +0 -0
  2850. psychopy/tests/data/wedge2_stencil.png +0 -0
  2851. psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos-0.4_0.png +0 -0
  2852. psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos0_0.png +0 -0
  2853. psychopy/tests/data/winScalePos_ori0_scale-1_1_pos-0.4_0.png +0 -0
  2854. psychopy/tests/data/winScalePos_ori0_scale-1_1_pos0_0.png +0 -0
  2855. psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos-0.4_0.png +0 -0
  2856. psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos0_0.png +0 -0
  2857. psychopy/tests/data/winScalePos_ori0_scale-2_2_pos-0.4_0.png +0 -0
  2858. psychopy/tests/data/winScalePos_ori0_scale-2_2_pos0_0.png +0 -0
  2859. psychopy/tests/data/winScalePos_ori0_scale1_-1_pos-0.4_0.png +0 -0
  2860. psychopy/tests/data/winScalePos_ori0_scale1_-1_pos0_0.png +0 -0
  2861. psychopy/tests/data/winScalePos_ori0_scale1_1_pos-0.4_0.png +0 -0
  2862. psychopy/tests/data/winScalePos_ori0_scale1_1_pos0_0.png +0 -0
  2863. psychopy/tests/data/winScalePos_ori0_scale2_-2_pos-0.4_0.png +0 -0
  2864. psychopy/tests/data/winScalePos_ori0_scale2_-2_pos0_0.png +0 -0
  2865. psychopy/tests/data/winScalePos_ori0_scale2_2_pos-0.4_0.png +0 -0
  2866. psychopy/tests/data/winScalePos_ori0_scale2_2_pos0_0.png +0 -0
  2867. psychopy/tests/data/winScalePos_ori45_scale-1_-1_pos0_0.png +0 -0
  2868. psychopy/tests/data/winScalePos_ori45_scale-1_1_pos0_0.png +0 -0
  2869. psychopy/tests/data/winScalePos_ori45_scale-2_-2_pos0_0.png +0 -0
  2870. psychopy/tests/data/winScalePos_ori45_scale-2_2_pos0_0.png +0 -0
  2871. psychopy/tests/data/winScalePos_ori45_scale1_-1_pos0_0.png +0 -0
  2872. psychopy/tests/data/winScalePos_ori45_scale1_1_pos0_0.png +0 -0
  2873. psychopy/tests/data/winScalePos_ori45_scale2_-2_pos0_0.png +0 -0
  2874. psychopy/tests/data/winScalePos_ori45_scale2_2_pos0_0.png +0 -0
  2875. psychopy/tests/doc/run_btn.png +0 -0
  2876. psychopy/tests/doc/test-suite-bugs.jpg +0 -0
  2877. psychopy/tests/doc/user-bugs.jpg +0 -0
  2878. psychopy/tests/dummy_xorg.conf +21 -0
  2879. psychopy/tests/run.py +50 -0
  2880. psychopy/tests/test_Installation.py +38 -0
  2881. psychopy/tests/test_alerts/test_alerts.py +37 -0
  2882. psychopy/tests/test_alerts/test_alerttools.py +132 -0
  2883. psychopy/tests/test_app/__init__.py +0 -0
  2884. psychopy/tests/test_app/conftest.py +40 -0
  2885. psychopy/tests/test_app/test_builder/__init__.py +0 -0
  2886. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +166 -0
  2887. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +133 -0
  2888. psychopy/tests/test_app/test_builder/test_ComponentDialogs.py +98 -0
  2889. psychopy/tests/test_app/test_command_line.py +65 -0
  2890. psychopy/tests/test_app/test_runner/__init__.py +0 -0
  2891. psychopy/tests/test_app/test_runner/test_RunnerFrame.py +166 -0
  2892. psychopy/tests/test_app/test_speed.py +147 -0
  2893. psychopy/tests/test_app/test_themes/test_icons.py +79 -0
  2894. psychopy/tests/test_codecov.py +29 -0
  2895. psychopy/tests/test_colors/test_Color.py +41 -0
  2896. psychopy/tests/test_data/dataPrev.xlsx +0 -0
  2897. psychopy/tests/test_data/test_ExperimentHandler.py +171 -0
  2898. psychopy/tests/test_data/test_MultiStairHandler.py +174 -0
  2899. psychopy/tests/test_data/test_StairHandlers.py +1241 -0
  2900. psychopy/tests/test_data/test_TrialHandler.py +335 -0
  2901. psychopy/tests/test_data/test_TrialHandler2.py +584 -0
  2902. psychopy/tests/test_data/test_TrialHandlerExt.py +359 -0
  2903. psychopy/tests/test_data/test_fitFunctions.py +101 -0
  2904. psychopy/tests/test_data/test_utils.py +173 -0
  2905. psychopy/tests/test_data/test_xlsx.py +92 -0
  2906. psychopy/tests/test_demos/test_builder_demos.py +52 -0
  2907. psychopy/tests/test_experiment/__init__.py +0 -0
  2908. psychopy/tests/test_experiment/known_py_diffs.txt +47 -0
  2909. psychopy/tests/test_experiment/needs_wx/__init__.py +0 -0
  2910. psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +14780 -0
  2911. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +130 -0
  2912. psychopy/tests/test_experiment/needs_wx/test_Experiment.py +406 -0
  2913. psychopy/tests/test_experiment/needs_wx/test_components.py +170 -0
  2914. psychopy/tests/test_experiment/test_component_compile_js.py +59 -0
  2915. psychopy/tests/test_experiment/test_component_compile_python.py +165 -0
  2916. psychopy/tests/test_experiment/test_components/__init__.py +1 -0
  2917. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +218 -0
  2918. psychopy/tests/test_experiment/test_components/test_CodeComponent.py +70 -0
  2919. psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
  2920. psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
  2921. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  2922. psychopy/tests/test_experiment/test_components/test_MouseComponent.py +136 -0
  2923. psychopy/tests/test_experiment/test_components/test_PolygonComponent.py +65 -0
  2924. psychopy/tests/test_experiment/test_components/test_ResourceManagerComponent.py +50 -0
  2925. psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
  2926. psychopy/tests/test_experiment/test_components/test_SettingsComponent.py +80 -0
  2927. psychopy/tests/test_experiment/test_components/test_StaticComponent.py +51 -0
  2928. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  2929. psychopy/tests/test_experiment/test_components/test_all_components.py +104 -0
  2930. psychopy/tests/test_experiment/test_components/test_base_components.py +477 -0
  2931. psychopy/tests/test_experiment/test_experiment.py +199 -0
  2932. psychopy/tests/test_experiment/test_loops.py +103 -0
  2933. psychopy/tests/test_experiment/test_params.py +363 -0
  2934. psychopy/tests/test_experiment/test_py2js.py +230 -0
  2935. psychopy/tests/test_experiment/test_routines/__init__.py +1 -0
  2936. psychopy/tests/test_experiment/test_routines/test_EyetrackerCalibrationRoutine.py +9 -0
  2937. psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +9 -0
  2938. psychopy/tests/test_experiment/test_routines/test_all_routines.py +23 -0
  2939. psychopy/tests/test_experiment/test_routines/test_base_routine.py +154 -0
  2940. psychopy/tests/test_experiment/test_routines/test_standalone_routines.py +36 -0
  2941. psychopy/tests/test_gui/test_DlgFromDictQt.py +82 -0
  2942. psychopy/tests/test_gui/test_DlgFromDictWx.py +82 -0
  2943. psychopy/tests/test_hardware/__init__.py +0 -0
  2944. psychopy/tests/test_hardware/test_device_manager.py +36 -0
  2945. psychopy/tests/test_hardware/test_emulator.py +106 -0
  2946. psychopy/tests/test_hardware/test_gammasci.py +35 -0
  2947. psychopy/tests/test_hardware/test_keyboard.py +241 -0
  2948. psychopy/tests/test_hardware/test_keyboard_events.py +362 -0
  2949. psychopy/tests/test_hardware/test_photodiode.py +66 -0
  2950. psychopy/tests/test_hardware/test_ports.py +129 -0
  2951. psychopy/tests/test_iohub/__init__.py +1 -0
  2952. psychopy/tests/test_iohub/test_computer.py +124 -0
  2953. psychopy/tests/test_iohub/test_event_get_and_clear.py +94 -0
  2954. psychopy/tests/test_iohub/test_keyboard.py +91 -0
  2955. psychopy/tests/test_iohub/test_launch.py +19 -0
  2956. psychopy/tests/test_iohub/testutil.py +54 -0
  2957. psychopy/tests/test_liaison/test_Liaison.py +275 -0
  2958. psychopy/tests/test_misc/__init__.py +0 -0
  2959. psychopy/tests/test_misc/memory_usage.py +164 -0
  2960. psychopy/tests/test_misc/test_GammaFun.py +15 -0
  2961. psychopy/tests/test_misc/test_clock.py +63 -0
  2962. psychopy/tests/test_misc/test_color.py +205 -0
  2963. psychopy/tests/test_misc/test_core.py +451 -0
  2964. psychopy/tests/test_misc/test_event.py +308 -0
  2965. psychopy/tests/test_misc/test_info.py +18 -0
  2966. psychopy/tests/test_misc/test_layout.py +49 -0
  2967. psychopy/tests/test_misc/test_locale.py +38 -0
  2968. psychopy/tests/test_misc/test_web.py +22 -0
  2969. psychopy/tests/test_monitors/test_monitors.py +91 -0
  2970. psychopy/tests/test_plugins/__init__.py +0 -0
  2971. psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
  2972. psychopy/tests/test_preferences/__init__.py +0 -0
  2973. psychopy/tests/test_preferences/test_prefs.py +28 -0
  2974. psychopy/tests/test_session/test_Session.py +257 -0
  2975. psychopy/tests/test_sound/__init__.py +0 -0
  2976. psychopy/tests/test_sound/test_audioclip.py +331 -0
  2977. psychopy/tests/test_sound/test_hamming.py +45 -0
  2978. psychopy/tests/test_sound/test_sound.py +138 -0
  2979. psychopy/tests/test_sound/test_sound_pygame.py +67 -0
  2980. psychopy/tests/test_tools/test_animationtools.py +51 -0
  2981. psychopy/tests/test_tools/test_arraytools.py +242 -0
  2982. psychopy/tests/test_tools/test_attributetools.py +257 -0
  2983. psychopy/tests/test_tools/test_colorspacetools.py +150 -0
  2984. psychopy/tests/test_tools/test_environmenttools.py +52 -0
  2985. psychopy/tests/test_tools/test_fileerrortools.py +97 -0
  2986. psychopy/tests/test_tools/test_filetools.py +104 -0
  2987. psychopy/tests/test_tools/test_imagetools.py +57 -0
  2988. psychopy/tests/test_tools/test_mathtools.py +688 -0
  2989. psychopy/tests/test_tools/test_stringtools.py +143 -0
  2990. psychopy/tests/test_tools/test_versionchooser.py +209 -0
  2991. psychopy/tests/test_tools/test_viewtools.py +168 -0
  2992. psychopy/tests/test_validators/__init__.py +0 -0
  2993. psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
  2994. psychopy/tests/test_visual/__init__.py +0 -0
  2995. psychopy/tests/test_visual/measure_parity.py +243 -0
  2996. psychopy/tests/test_visual/test_all_stimuli.py +655 -0
  2997. psychopy/tests/test_visual/test_basevisual.py +564 -0
  2998. psychopy/tests/test_visual/test_brush.py +68 -0
  2999. psychopy/tests/test_visual/test_button.py +15 -0
  3000. psychopy/tests/test_visual/test_circle.py +74 -0
  3001. psychopy/tests/test_visual/test_contains_overlaps.py +242 -0
  3002. psychopy/tests/test_visual/test_custommouse.py +47 -0
  3003. psychopy/tests/test_visual/test_dots.py +155 -0
  3004. psychopy/tests/test_visual/test_form.py +414 -0
  3005. psychopy/tests/test_visual/test_framepacking.py +41 -0
  3006. psychopy/tests/test_visual/test_gamma.py +152 -0
  3007. psychopy/tests/test_visual/test_glfw_backend.py +14 -0
  3008. psychopy/tests/test_visual/test_image.py +219 -0
  3009. psychopy/tests/test_visual/test_panorama.py +41 -0
  3010. psychopy/tests/test_visual/test_progress.py +96 -0
  3011. psychopy/tests/test_visual/test_projections.py +215 -0
  3012. psychopy/tests/test_visual/test_projections_interactive.py +163 -0
  3013. psychopy/tests/test_visual/test_roi.py +105 -0
  3014. psychopy/tests/test_visual/test_shape.py +23 -0
  3015. psychopy/tests/test_visual/test_slider.py +328 -0
  3016. psychopy/tests/test_visual/test_target.py +35 -0
  3017. psychopy/tests/test_visual/test_textbox.py +508 -0
  3018. psychopy/tests/test_visual/test_winFlipTiming.py +95 -0
  3019. psychopy/tests/test_visual/test_winScalePos.py +78 -0
  3020. psychopy/tests/test_visual/test_window.py +124 -0
  3021. psychopy/tests/utils.py +372 -0
  3022. psychopy/tools/LineBreak.txt +3597 -0
  3023. psychopy/tools/__init__.py +9 -0
  3024. psychopy/tools/animationtools.py +49 -0
  3025. psychopy/tools/apptools.py +32 -0
  3026. psychopy/tools/arraytools.py +561 -0
  3027. psychopy/tools/attributetools.py +263 -0
  3028. psychopy/tools/audiotools.py +361 -0
  3029. psychopy/tools/colorspacetools.py +717 -0
  3030. psychopy/tools/coordinatetools.py +104 -0
  3031. psychopy/tools/environmenttools.py +62 -0
  3032. psychopy/tools/fileerrortools.py +66 -0
  3033. psychopy/tools/filetools.py +406 -0
  3034. psychopy/tools/fontmanager.py +1065 -0
  3035. psychopy/tools/gltools.py +7367 -0
  3036. psychopy/tools/imagetools.py +65 -0
  3037. psychopy/tools/linebreak.py +322 -0
  3038. psychopy/tools/mathtools.py +4910 -0
  3039. psychopy/tools/monitorunittools.py +276 -0
  3040. psychopy/tools/movietools.py +1111 -0
  3041. psychopy/tools/pkgtools.py +654 -0
  3042. psychopy/tools/plottools.py +25 -0
  3043. psychopy/tools/rifttools.py +76 -0
  3044. psychopy/tools/stereotools.py +9 -0
  3045. psychopy/tools/stimulustools.py +198 -0
  3046. psychopy/tools/stringtools.py +466 -0
  3047. psychopy/tools/systemtools.py +1331 -0
  3048. psychopy/tools/typetools.py +52 -0
  3049. psychopy/tools/unittools.py +16 -0
  3050. psychopy/tools/versionchooser.py +604 -0
  3051. psychopy/tools/viewtools.py +1068 -0
  3052. psychopy/tools/wizard.py +804 -0
  3053. psychopy/validation/__init__.py +6 -0
  3054. psychopy/validation/audio.py +78 -0
  3055. psychopy/validation/visual.py +119 -0
  3056. psychopy/visual/__init__.py +121 -0
  3057. psychopy/visual/aperture.py +333 -0
  3058. psychopy/visual/backends/__init__.py +77 -0
  3059. psychopy/visual/backends/_base.py +481 -0
  3060. psychopy/visual/backends/gamma.py +349 -0
  3061. psychopy/visual/backends/glfwbackend.py +24 -0
  3062. psychopy/visual/backends/pygamebackend.py +343 -0
  3063. psychopy/visual/backends/pygletbackend.py +970 -0
  3064. psychopy/visual/basevisual.py +2027 -0
  3065. psychopy/visual/brush.py +204 -0
  3066. psychopy/visual/bufferimage.py +311 -0
  3067. psychopy/visual/button.py +194 -0
  3068. psychopy/visual/circle.py +165 -0
  3069. psychopy/visual/custommouse.py +263 -0
  3070. psychopy/visual/dot.py +734 -0
  3071. psychopy/visual/dropdown.py +165 -0
  3072. psychopy/visual/elementarray.py +802 -0
  3073. psychopy/visual/filters.py +419 -0
  3074. psychopy/visual/form.py +1195 -0
  3075. psychopy/visual/globalVars.py +24 -0
  3076. psychopy/visual/grating.py +581 -0
  3077. psychopy/visual/helpers.py +336 -0
  3078. psychopy/visual/image.py +438 -0
  3079. psychopy/visual/line.py +227 -0
  3080. psychopy/visual/movie.py +12 -0
  3081. psychopy/visual/movie2.py +21 -0
  3082. psychopy/visual/movie3.py +18 -0
  3083. psychopy/visual/movies/__init__.py +2334 -0
  3084. psychopy/visual/movies/frame.py +258 -0
  3085. psychopy/visual/movies/metadata.py +242 -0
  3086. psychopy/visual/movies/players/_base.py +364 -0
  3087. psychopy/visual/nnlvs.py +830 -0
  3088. psychopy/visual/noise.py +32 -0
  3089. psychopy/visual/panorama.py +313 -0
  3090. psychopy/visual/patch.py +21 -0
  3091. psychopy/visual/pie.py +237 -0
  3092. psychopy/visual/polygon.py +226 -0
  3093. psychopy/visual/progress.py +313 -0
  3094. psychopy/visual/radial.py +29 -0
  3095. psychopy/visual/ratingscale.py +18 -0
  3096. psychopy/visual/rect.py +195 -0
  3097. psychopy/visual/rift.py +2660 -0
  3098. psychopy/visual/roi.py +141 -0
  3099. psychopy/visual/secondorder.py +27 -0
  3100. psychopy/visual/shaders.py +787 -0
  3101. psychopy/visual/shape.py +897 -0
  3102. psychopy/visual/simpleimage.py +303 -0
  3103. psychopy/visual/slider.py +1222 -0
  3104. psychopy/visual/stim3d.py +2109 -0
  3105. psychopy/visual/target.py +278 -0
  3106. psychopy/visual/text.py +769 -0
  3107. psychopy/visual/textbox/__init__.py +1280 -0
  3108. psychopy/visual/textbox/fontmanager.py +574 -0
  3109. psychopy/visual/textbox/parsedtext.py +317 -0
  3110. psychopy/visual/textbox/textgrid.py +278 -0
  3111. psychopy/visual/textbox/textureatlas.py +248 -0
  3112. psychopy/visual/textbox2/__init__.py +4 -0
  3113. psychopy/visual/textbox2/textbox2.py +1970 -0
  3114. psychopy/visual/vlcmoviestim.py +1294 -0
  3115. psychopy/visual/window.py +3910 -0
  3116. psychopy/visual/windowframepack.py +86 -0
  3117. psychopy/visual/windowwarp.py +457 -0
  3118. psychopy/voicekey/__init__.py +56 -0
  3119. psychopy/voicekey/labjack_vks.py +9 -0
  3120. psychopy/voicekey/parallel_vks.py +9 -0
  3121. psychopy/voicekey/vk_tools.py +131 -0
  3122. psychopy/web.py +289 -0
  3123. psychopy-2025.2.4.dist-info/METADATA +160 -0
  3124. psychopy-2025.2.4.dist-info/RECORD +3127 -0
  3125. psychopy-2025.2.4.dist-info/WHEEL +4 -0
  3126. psychopy-2025.2.4.dist-info/entry_points.txt +5 -0
  3127. psychopy-2025.2.4.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,4738 @@
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-2025 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 packaging.version import 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 .dialogs.paramCtrls import EVT_PARAM_CHANGED
34
+ from .validators import WarningManager
35
+ from ..pavlovia_ui import sync, PavloviaMiniBrowser
36
+ from ..pavlovia_ui.project import ProjectFrame
37
+ from ..pavlovia_ui.search import SearchFrame
38
+ from ..pavlovia_ui.user import UserFrame
39
+ from ..pavlovia_ui.functions import logInPavlovia
40
+ from ..deviceManager import DeviceManagerDlg
41
+ from ...experiment import getAllElements, getAllCategories
42
+ from ...experiment.routines import Routine, BaseStandaloneRoutine
43
+ from psychopy.tools.versionchooser import parseVersionSafely, psychopyVersion
44
+
45
+ try:
46
+ import markdown_it as md
47
+ except ImportError:
48
+ md = None
49
+ import wx.lib.agw.aui as aui # some versions of phoenix
50
+ try:
51
+ from wx.adv import PseudoDC
52
+ except ImportError:
53
+ from wx import PseudoDC
54
+
55
+ if Version(wx.__version__) < Version('4.0.3'):
56
+ wx.NewIdRef = wx.NewId
57
+
58
+ from psychopy.localization import _translate
59
+ from ... import experiment, prefs
60
+ from .. import dialogs, utils, ribbon
61
+ from ..themes import icons, colors, handlers
62
+ from ..themes.ui import ThemeSwitcher
63
+ from ..ui import BaseAuiFrame
64
+ from psychopy import logging, data
65
+ from psychopy.tools.filetools import mergeFolder
66
+ from .dialogs import (DlgComponentProperties, DlgExperimentProperties,
67
+ DlgCodeComponentProperties, DlgLoopProperties,
68
+ ParamNotebook, DlgNewRoutine, BuilderFindDlg)
69
+ from ..utils import (BasePsychopyToolbar, HoverButton, ThemedPanel, WindowFrozen,
70
+ FileDropTarget, FrameSwitcher, updateDemosMenu,
71
+ ToggleButtonArray, HoverMixin)
72
+
73
+ from psychopy.experiment import getAllStandaloneRoutines
74
+ from psychopy.app import pavlovia_ui
75
+ from psychopy.projects import pavlovia
76
+ from psychopy.tools import stringtools as st
77
+ from psychopy.scripts.psyexpCompile import generateScript
78
+
79
+ # Components which are always hidden
80
+ alwaysHidden = [
81
+ 'BaseComponent',
82
+ 'BaseDeviceComponent',
83
+ 'BaseStandaloneRoutine',
84
+ 'BaseDeviceRoutine',
85
+ 'BaseValidatorRoutine',
86
+ ]
87
+
88
+
89
+ class TemplateManager(dict):
90
+ mainFolder = Path(prefs.paths['resources']).absolute() / 'routine_templates'
91
+ userFolder = Path(prefs.paths['userPrefsDir']).absolute() / 'routine_templates'
92
+ experimentFiles = {}
93
+
94
+ def __init__(self):
95
+ dict.__init__(self)
96
+ self.updateTemplates()
97
+
98
+ def updateTemplates(self, ):
99
+ """Search and import templates in the standard files"""
100
+ for folder in [TemplateManager.mainFolder, TemplateManager.userFolder]:
101
+ categs = folder.glob("*.psyexp")
102
+ for filePath in categs:
103
+ thisExp = experiment.Experiment()
104
+ thisExp.loadFromXML(filePath)
105
+ categName = filePath.stem
106
+ self[categName]={}
107
+ for routineName in thisExp.routines:
108
+ self[categName][routineName] = copy.copy(thisExp.routines[routineName])
109
+
110
+
111
+ class BuilderFrame(BaseAuiFrame, handlers.ThemeMixin):
112
+ """Defines construction of the Psychopy Builder Frame"""
113
+
114
+ routineTemplates = TemplateManager()
115
+
116
+ def __init__(self, parent, id=-1, title='PsychoPy (Experiment Builder)',
117
+ pos=wx.DefaultPosition, fileName=None, frameData=None,
118
+ style=wx.DEFAULT_FRAME_STYLE, app=None):
119
+
120
+ if (fileName is not None) and (type(fileName) == bytes):
121
+ fileName = fileName.decode(sys.getfilesystemencoding())
122
+
123
+ self.app = app
124
+ self.dpi = self.app.dpi
125
+ # things the user doesn't set like winsize etc:
126
+ self.appData = self.app.prefs.appData['builder']
127
+ # things about the builder that the user can set:
128
+ self.prefs = self.app.prefs.builder
129
+ self.appPrefs = self.app.prefs.app
130
+ self.paths = self.app.prefs.paths
131
+ self.frameType = 'builder'
132
+ self.fileExists = False
133
+ self.filename = fileName
134
+ self.htmlPath = None
135
+ self.scriptProcess = None
136
+ self.stdoutBuffer = None
137
+ self.readmeFrame = None
138
+ self.generateScript = generateScript
139
+
140
+ # default window title
141
+ self.winTitle = title
142
+
143
+ # get last frame information, or default
144
+ if fileName in self.appData['frames']:
145
+ self.frameData = self.appData['frames'][fileName]
146
+ else:
147
+ self.frameData = dict(self.appData['defaultFrame'])
148
+ # work out best default size from screen size
149
+ i = wx.Display.GetFromPoint(wx.Point(
150
+ int(self.frameData['winX']), int(self.frameData['winY'])
151
+ ))
152
+ try:
153
+ disp = wx.Display(max(i, 0))
154
+ except:
155
+ disp = wx.Display(0)
156
+ dispW, dispH = list(disp.GetGeometry())[2:]
157
+ # set in frameData array
158
+ self.frameData['winW'] = dispW * 0.75
159
+ self.frameData['winH'] = dispH * 0.75
160
+ # increment default for next frame
161
+ self.frameData['winX'] += 10
162
+ self.frameData['winY'] += 10
163
+ # handle when last open frame was minimised or invalid
164
+ for key in ("winH", "winW", "winX", "winY"):
165
+ self.frameData[key] = max(self.frameData[key], 20)
166
+ # initialise
167
+ BaseAuiFrame.__init__(
168
+ self,
169
+ parent=parent,
170
+ id=id,
171
+ size=(
172
+ int(self.frameData['winW']), int(self.frameData['winH'])
173
+ ),
174
+ pos=(
175
+ int(self.frameData['winX']), int(self.frameData['winY'])
176
+ ),
177
+ title=title,
178
+ style=style
179
+ )
180
+ # detect retina displays (then don't use double-buffering)
181
+ self.isRetina = \
182
+ self.GetContentScaleFactor() != 1 and wx.Platform == '__WXMAC__'
183
+
184
+ # create icon
185
+ if sys.platform != 'darwin':
186
+ # doesn't work on darwin and not necessary: handled by app bundle
187
+ iconFile = os.path.join(self.paths['resources'], 'builder.ico')
188
+ if os.path.isfile(iconFile):
189
+ self.SetIcon(wx.Icon(iconFile, wx.BITMAP_TYPE_ICO))
190
+
191
+ # create our panels
192
+ self.flowPanel = FlowPanel(frame=self)
193
+ self.flowCanvas = self.flowPanel.canvas
194
+ self.routinePanel = RoutinesNotebook(self)
195
+ self.componentButtons = ComponentsPanel(self)
196
+ self.ribbon = BuilderRibbon(self)
197
+ # menus and toolbars
198
+ self.menuIDs = SimpleNamespace()
199
+ self.makeMenus()
200
+ self.CreateStatusBar()
201
+ self.SetStatusText("")
202
+
203
+ # setup universal shortcuts
204
+ accelTable = self.app.makeAccelTable()
205
+ self.SetAcceleratorTable(accelTable)
206
+
207
+ # setup a default exp
208
+ if fileName is not None and self.filename.is_file():
209
+ self.fileOpen(filename=fileName, closeCurrent=False)
210
+ else:
211
+ self.lastSavedCopy = None
212
+ # don't try to close before opening
213
+ self.fileNew(closeCurrent=False)
214
+
215
+ self.updateReadme() # check/create frame as needed
216
+
217
+ # control the panes using aui manager
218
+ self._mgr = self.getAuiManager()
219
+
220
+ #self._mgr.SetArtProvider(PsychopyDockArt())
221
+ #self._art = self._mgr.GetArtProvider()
222
+ # Create panels
223
+ self._mgr.AddPane(self.ribbon,
224
+ aui.AuiPaneInfo().
225
+ Name("Ribbon").
226
+ DockFixed(True).
227
+ CloseButton(False).MaximizeButton(True).PaneBorder(False).CaptionVisible(False).
228
+ Top()
229
+ )
230
+ self._mgr.AddPane(self.routinePanel,
231
+ aui.AuiPaneInfo().
232
+ Name("Routines").Caption("Routines").CaptionVisible(True).
233
+ Floatable(False).
234
+ Movable(False).
235
+ CloseButton(False).MaximizeButton(True).PaneBorder(False).
236
+ Center()) # 'center panes' expand
237
+ rtPane = self._mgr.GetPane('Routines')
238
+ self._mgr.AddPane(self.componentButtons,
239
+ aui.AuiPaneInfo().
240
+ Name("Components").Caption("Components").CaptionVisible(True).
241
+ Floatable(False).
242
+ RightDockable(True).LeftDockable(True).
243
+ CloseButton(False).PaneBorder(False))
244
+ compPane = self._mgr.GetPane('Components')
245
+ self._mgr.AddPane(self.flowPanel,
246
+ aui.AuiPaneInfo().
247
+ Name("Flow").Caption("Flow").CaptionVisible(True).
248
+ BestSize((8 * self.dpi, 2 * self.dpi)).
249
+ Floatable(False).
250
+ RightDockable(True).LeftDockable(True).
251
+ CloseButton(False).PaneBorder(False))
252
+ flowPane = self._mgr.GetPane('Flow')
253
+ self.layoutPanes()
254
+ rtPane.CaptionVisible(True)
255
+ # tell the manager to 'commit' all the changes just made
256
+ self._mgr.Update()
257
+ # self.SetSizer(self.mainSizer) # not necessary for aui type controls
258
+ if self.frameData['auiPerspective']:
259
+ backup = self._mgr.SavePerspective()
260
+ try:
261
+ self._mgr.LoadPerspective(self.frameData['auiPerspective'])
262
+ except:
263
+ self._mgr.LoadPerspective(backup)
264
+ self.SetMinSize(wx.Size(600, 400)) # min size for the whole window
265
+ self.SetSize(
266
+ (int(self.frameData['winW']), int(self.frameData['winH'])))
267
+ self.SendSizeEvent()
268
+ self._mgr.GetPane("Ribbon").Show()
269
+ self._mgr.Update()
270
+
271
+ # self.SetAutoLayout(True)
272
+ self.Bind(wx.EVT_CLOSE, self.closeFrame)
273
+ self.Bind(wx.EVT_SIZE, self.onResize)
274
+ self.Bind(wx.EVT_SHOW, self.onShow)
275
+
276
+ self.app.trackFrame(self)
277
+ self.SetDropTarget(FileDropTarget(targetFrame=self))
278
+
279
+ self.theme = colors.theme
280
+
281
+ @property
282
+ def session(self):
283
+ """
284
+ Current Pavlovia session
285
+ """
286
+ return pavlovia.getCurrentSession()
287
+
288
+ # Synonymise Aui manager for use with theme mixin
289
+ def GetAuiManager(self):
290
+ return self._mgr
291
+
292
+ def makeMenus(self):
293
+ """
294
+ Produces Menus for the Builder Frame
295
+ """
296
+
297
+ # ---Menus---#000000#FFFFFF-------------------------------------------
298
+ menuBar = wx.MenuBar()
299
+ # ---_file---#000000#FFFFFF-------------------------------------------
300
+ self.fileMenu = wx.Menu()
301
+ menuBar.Append(self.fileMenu, _translate('&File'))
302
+
303
+ # create a file history submenu
304
+ self.fileHistoryMaxFiles = 10
305
+ self.fileHistory = wx.FileHistory(maxFiles=self.fileHistoryMaxFiles)
306
+ self.recentFilesMenu = wx.Menu()
307
+ self.fileHistory.UseMenu(self.recentFilesMenu)
308
+ for filename in self.appData['fileHistory']:
309
+ if os.path.exists(filename):
310
+ self.fileHistory.AddFileToHistory(filename)
311
+ self.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory,
312
+ id=wx.ID_FILE1, id2=wx.ID_FILE9)
313
+ keys = self.app.keys
314
+ menu = self.fileMenu
315
+ menu.Append(
316
+ wx.ID_NEW,
317
+ _translate("&New\t%s") % keys['new'])
318
+ menu.Append(
319
+ wx.ID_OPEN,
320
+ _translate("&Open...\t%s") % keys['open'])
321
+ menu.AppendSubMenu(
322
+ self.recentFilesMenu,
323
+ _translate("Open &Recent"))
324
+ menu.Append(
325
+ wx.ID_SAVE,
326
+ _translate("&Save\t%s") % keys['save'],
327
+ _translate("Save current experiment file"))
328
+ menu.Append(
329
+ wx.ID_SAVEAS,
330
+ _translate("Save &as...\t%s") % keys['saveAs'],
331
+ _translate("Save current experiment file as..."))
332
+ # export html
333
+ self.menuIDs.ID_EXPORT_HTML = wx.NewIdRef(count=1)
334
+ menu.Append(
335
+ self.menuIDs.ID_EXPORT_HTML,
336
+ _translate("Export HTML...\t%s") % keys['exportHTML'],
337
+ _translate("Export experiment to html/javascript file")
338
+ )
339
+ self.Bind(wx.EVT_MENU, self.fileExport, id=self.menuIDs.ID_EXPORT_HTML)
340
+ # reveal folder
341
+ self.menuIDs.ID_REVEAL = wx.NewIdRef(count=1)
342
+ menu.Append(
343
+ self.menuIDs.ID_REVEAL,
344
+ _translate("Reveal in file explorer...\t%s") % keys['revealFolder'],
345
+ _translate("Open the folder containing this experiment in your system's file explorer")
346
+ )
347
+ self.Bind(wx.EVT_MENU, self.fileReveal, id=self.menuIDs.ID_REVEAL)
348
+ menu.Append(
349
+ wx.ID_CLOSE,
350
+ _translate("&Close file\t%s") % keys['close'],
351
+ _translate("Close current experiment"))
352
+ self.Bind(wx.EVT_MENU, self.app.newBuilderFrame, id=wx.ID_NEW)
353
+ self.Bind(wx.EVT_MENU, self.fileSave, id=wx.ID_SAVE)
354
+ menu.Enable(wx.ID_SAVE, False)
355
+ self.Bind(wx.EVT_MENU, self.fileSaveAs, id=wx.ID_SAVEAS)
356
+ self.Bind(wx.EVT_MENU, self.fileOpen, id=wx.ID_OPEN)
357
+ self.Bind(wx.EVT_MENU, self.commandCloseFrame, id=wx.ID_CLOSE)
358
+ self.fileMenu.AppendSeparator()
359
+ item = menu.Append(
360
+ wx.ID_PREFERENCES,
361
+ _translate("&Preferences\t%s") % keys['preferences'])
362
+ self.Bind(wx.EVT_MENU, self.app.showPrefs, item)
363
+ item = menu.Append(
364
+ wx.ID_ANY, _translate("Reset preferences...")
365
+ )
366
+ self.Bind(wx.EVT_MENU, self.resetPrefs, item)
367
+ # item = menu.Append(wx.NewIdRef(count=1), "Plug&ins")
368
+ # self.Bind(wx.EVT_MENU, self.pluginManager, item)
369
+ self.fileMenu.AppendSeparator()
370
+ self.fileMenu.Append(wx.ID_EXIT,
371
+ _translate("&Quit\t%s") % keys['quit'],
372
+ _translate("Terminate the program"))
373
+ self.Bind(wx.EVT_MENU, self.quit, id=wx.ID_EXIT)
374
+
375
+ # ------------- edit ------------------------------------
376
+ self.editMenu = wx.Menu()
377
+ menuBar.Append(self.editMenu, _translate('&Edit'))
378
+ menu = self.editMenu
379
+ self._undoLabel = menu.Append(wx.ID_UNDO,
380
+ _translate("Undo\t%s") % keys['undo'],
381
+ _translate("Undo last action"),
382
+ wx.ITEM_NORMAL)
383
+ self.Bind(wx.EVT_MENU, self.undo, id=wx.ID_UNDO)
384
+ self._redoLabel = menu.Append(wx.ID_REDO,
385
+ _translate("Redo\t%s") % keys['redo'],
386
+ _translate("Redo last action"),
387
+ wx.ITEM_NORMAL)
388
+ self.Bind(wx.EVT_MENU, self.redo, id=wx.ID_REDO)
389
+ menu.Append(wx.ID_PASTE, _translate("&Paste\t%s") % keys['paste'])
390
+ self.Bind(wx.EVT_MENU, self.paste, id=wx.ID_PASTE)
391
+
392
+ item = menu.Append(
393
+ wx.ID_ANY,
394
+ _translate("&Find in experiment...\t%s") % keys['builderFind'],
395
+ _translate("Search the whole experiment for a specific term")
396
+ )
397
+ self.Bind(wx.EVT_MENU, self.onFindInExperiment, item)
398
+
399
+ # ---_view---#000000#FFFFFF-------------------------------------------
400
+ self.viewMenu = wx.Menu()
401
+ menuBar.Append(self.viewMenu, _translate('&View'))
402
+ menu = self.viewMenu
403
+
404
+ # item = menu.Append(wx.ID_ANY,
405
+ # _translate("Open Coder view"),
406
+ # _translate("Open a new Coder view"))
407
+ # self.Bind(wx.EVT_MENU, self.app.showCoder, item)
408
+ #
409
+ # item = menu.Append(wx.ID_ANY,
410
+ # _translate("Open Runner view"),
411
+ # _translate("Open the Runner view"))
412
+ # self.Bind(wx.EVT_MENU, self.app.showRunner, item)
413
+ # menu.AppendSeparator()
414
+
415
+ item = menu.Append(wx.ID_ANY,
416
+ _translate("&Toggle readme\t%s") % self.app.keys[
417
+ 'toggleReadme'],
418
+ _translate("Toggle Readme"))
419
+ self.Bind(wx.EVT_MENU, self.toggleReadme, item)
420
+ item = menu.Append(wx.ID_ANY,
421
+ _translate("&Flow Larger\t%s") % self.app.keys[
422
+ 'largerFlow'],
423
+ _translate("Larger flow items"))
424
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.increaseSize, item)
425
+ item = menu.Append(wx.ID_ANY,
426
+ _translate("&Flow Smaller\t%s") % self.app.keys[
427
+ 'smallerFlow'],
428
+ _translate("Smaller flow items"))
429
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.decreaseSize, item)
430
+ item = menu.Append(wx.ID_ANY,
431
+ _translate("&Routine Larger\t%s") % keys[
432
+ 'largerRoutine'],
433
+ _translate("Larger routine items"))
434
+ self.Bind(wx.EVT_MENU, self.routinePanel.increaseSize, item)
435
+ item = menu.Append(wx.ID_ANY,
436
+ _translate("&Routine Smaller\t%s") % keys[
437
+ 'smallerRoutine'],
438
+ _translate("Smaller routine items"))
439
+ self.Bind(wx.EVT_MENU, self.routinePanel.decreaseSize, item)
440
+ menu.AppendSeparator()
441
+
442
+ # Frame switcher
443
+ FrameSwitcher.makeViewSwitcherButtons(menu, frame=self, app=self.app)
444
+
445
+ # Theme switcher
446
+ self.themesMenu = ThemeSwitcher(app=self.app)
447
+ menu.AppendSubMenu(self.themesMenu, _translate("&Themes"))
448
+
449
+ # ---_tools ---#000000#FFFFFF-----------------------------------------
450
+ self.toolsMenu = wx.Menu()
451
+ menuBar.Append(self.toolsMenu, _translate('&Tools'))
452
+ menu = self.toolsMenu
453
+ item = menu.Append(wx.ID_ANY,
454
+ _translate("Monitor Center"),
455
+ _translate("To set information about your monitor"))
456
+ self.Bind(wx.EVT_MENU, self.app.openMonitorCenter, item)
457
+
458
+ item = menu.Append(wx.ID_ANY,
459
+ _translate("Device Manager"),
460
+ _translate("Setup named devices for your Components to refer to"))
461
+ self.Bind(wx.EVT_MENU, self.openDeviceManager, item)
462
+
463
+ item = menu.Append(wx.ID_ANY,
464
+ _translate("Compile\t%s") % keys['compileScript'],
465
+ _translate("Compile the exp to a script"))
466
+ self.Bind(wx.EVT_MENU, self.compileScript, item)
467
+ self.bldrRun = menu.Append(wx.ID_ANY,
468
+ _translate("Run/pilot\t%s") % keys['runScript'],
469
+ _translate("Run the current script"))
470
+ self.Bind(wx.EVT_MENU, self.onRunShortcut, self.bldrRun, id=self.bldrRun)
471
+ item = menu.Append(wx.ID_ANY,
472
+ _translate("Send to runner\t%s") % keys['runnerScript'],
473
+ _translate("Send current script to runner"))
474
+ self.Bind(wx.EVT_MENU, self.runFile, item)
475
+ menu.AppendSeparator()
476
+ item = menu.Append(wx.ID_ANY,
477
+ _translate("PsychoPy updates..."),
478
+ _translate("Update PsychoPy to the latest, or a "
479
+ "specific, version"))
480
+ self.Bind(wx.EVT_MENU, self.app.openUpdater, item)
481
+ item = menu.Append(wx.ID_ANY,
482
+ _translate("Plugin/packages manager..."),
483
+ _translate("Manage Python packages and optional plugins for PsychoPy"))
484
+ self.Bind(wx.EVT_MENU, self.openPluginManager, item)
485
+ if hasattr(self.app, 'benchmarkWizard'):
486
+ item = menu.Append(wx.ID_ANY,
487
+ _translate("Benchmark wizard"),
488
+ _translate("Check software & hardware, generate "
489
+ "report"))
490
+ self.Bind(wx.EVT_MENU, self.app.benchmarkWizard, item)
491
+
492
+ # ---_experiment---#000000#FFFFFF-------------------------------------
493
+ self.expMenu = wx.Menu()
494
+ menuBar.Append(self.expMenu, _translate('E&xperiment'))
495
+ menu = self.expMenu
496
+
497
+ item = menu.Append(wx.ID_ANY,
498
+ _translate("Experiment &Settings\t%s") % keys['expSettings'],
499
+ _translate("Edit experiment settings"))
500
+ self.Bind(wx.EVT_MENU, self.setExperimentSettings, item)
501
+ menu.AppendSeparator()
502
+
503
+ item = menu.Append(wx.ID_ANY,
504
+ _translate("&New Routine\t%s") % keys['newRoutine'],
505
+ _translate("Create a new routine (e.g. the trial "
506
+ "definition)"))
507
+ self.Bind(wx.EVT_MENU, self.addRoutine, item)
508
+ item = menu.Append(wx.ID_ANY,
509
+ _translate("&Copy Routine\t%s") % keys[
510
+ 'copyRoutine'],
511
+ _translate("Copy the current routine so it can be "
512
+ "used in another exp"),
513
+ wx.ITEM_NORMAL)
514
+ self.Bind(wx.EVT_MENU, self.onCopyRoutine, item)
515
+ item = menu.Append(wx.ID_ANY,
516
+ _translate("&Paste Routine\t%s") % keys[
517
+ 'pasteRoutine'],
518
+ _translate("Paste the Routine into the current "
519
+ "experiment"),
520
+ wx.ITEM_NORMAL)
521
+ self.Bind(wx.EVT_MENU, self.onPasteRoutine, item)
522
+ item = menu.Append(wx.ID_ANY,
523
+ _translate("&Rename Routine\t%s") % keys[
524
+ 'renameRoutine'],
525
+ _translate("Change the name of this routine"))
526
+ self.Bind(wx.EVT_MENU, self.renameRoutine, item)
527
+ item = menu.Append(wx.ID_ANY,
528
+ _translate("Paste Component\t%s") % keys[
529
+ 'pasteCompon'],
530
+ _translate(
531
+ "Paste the Component at bottom of the current "
532
+ "Routine"),
533
+ wx.ITEM_NORMAL)
534
+ self.Bind(wx.EVT_MENU, self.onPasteCompon, item)
535
+ menu.AppendSeparator()
536
+
537
+ item = menu.Append(wx.ID_ANY,
538
+ _translate("Insert Routine in Flow"),
539
+ _translate(
540
+ "Select one of your routines to be inserted"
541
+ " into the experiment flow"))
542
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.onInsertRoutine, item)
543
+ item = menu.Append(wx.ID_ANY,
544
+ _translate("Insert Loop in Flow"),
545
+ _translate("Create a new loop in your flow window"))
546
+ self.Bind(wx.EVT_MENU, self.flowPanel.canvas.insertLoop, item)
547
+ menu.AppendSeparator()
548
+
549
+ item = menu.Append(wx.ID_ANY,
550
+ _translate("README..."),
551
+ _translate("Add or edit the text shown when your experiment is opened"))
552
+ self.Bind(wx.EVT_MENU, self.editREADME, item)
553
+
554
+ # ---_demos---#000000#FFFFFF------------------------------------------
555
+ # for demos we need a dict where the event ID will correspond to a
556
+ # filename
557
+
558
+ self.demosMenu = wx.Menu()
559
+ # unpack demos option
560
+ menu = self.demosMenu
561
+ item = menu.Append(wx.ID_ANY,
562
+ _translate("&Unpack Demos..."),
563
+ _translate(
564
+ "Unpack demos to a writable location (so that"
565
+ " they can be run)"))
566
+ self.Bind(wx.EVT_MENU, self.demosUnpack, item)
567
+ item = menu.Append(wx.ID_ANY,
568
+ _translate("Browse on Pavlovia"),
569
+ _translate("Get more demos from the online demos "
570
+ "repository on Pavlovia")
571
+ )
572
+ self.Bind(wx.EVT_MENU, self.openPavloviaDemos, item)
573
+ item = menu.Append(wx.ID_ANY,
574
+ _translate("Open demos folder"),
575
+ _translate("Open the local folder where demos are stored")
576
+ )
577
+ self.Bind(wx.EVT_MENU, self.openLocalDemos, item)
578
+ menu.AppendSeparator()
579
+ # add any demos that are found in the prefs['demosUnpacked'] folder
580
+ updateDemosMenu(self, self.demosMenu, self.prefs['unpackedDemosDir'], ext=".psyexp")
581
+ menuBar.Append(self.demosMenu, _translate('&Demos'))
582
+
583
+ # ---_onlineStudies---#000000#FFFFFF-------------------------------------------
584
+ self.pavloviaMenu = pavlovia_ui.menu.PavloviaMenu(parent=self)
585
+ menuBar.Append(self.pavloviaMenu, _translate("&Pavlovia.org"))
586
+
587
+ # ---_window---#000000#FFFFFF-----------------------------------------
588
+ self.windowMenu = FrameSwitcher(self)
589
+ menuBar.Append(self.windowMenu, _translate("&Window"))
590
+
591
+ # ---_help---#000000#FFFFFF-------------------------------------------
592
+ self.helpMenu = wx.Menu()
593
+ menuBar.Append(self.helpMenu, _translate('&Help'))
594
+ menu = self.helpMenu
595
+
596
+ item = menu.Append(wx.ID_ANY,
597
+ _translate("&PsychoPy Homepage"),
598
+ _translate("Go to the PsychoPy homepage"))
599
+ self.Bind(wx.EVT_MENU, self.app.followLink, item)
600
+ self.app.urls[item.GetId()] = self.app.urls['psychopyHome']
601
+ item = menu.Append(wx.ID_ANY,
602
+ _translate("&PsychoPy Builder Help"),
603
+ _translate(
604
+ "Go to the online documentation for PsychoPy"
605
+ " Builder"))
606
+ self.Bind(wx.EVT_MENU, self.app.followLink, item)
607
+ self.app.urls[item.GetId()] = self.app.urls['builderHelp']
608
+
609
+ menu.AppendSeparator()
610
+ item = menu.Append(wx.ID_ANY,
611
+ _translate("&System Info..."),
612
+ _translate("Get system information."))
613
+ self.Bind(wx.EVT_MENU, self.app.showSystemInfo, id=item.GetId())
614
+
615
+ menu.AppendSeparator()
616
+ menu.Append(wx.ID_ABOUT, _translate(
617
+ "&About..."), _translate("About PsychoPy"))
618
+ self.Bind(wx.EVT_MENU, self.app.showAbout, id=wx.ID_ABOUT)
619
+ item = menu.Append(wx.ID_ANY,
620
+ _translate("&News..."),
621
+ _translate("News"))
622
+ self.Bind(wx.EVT_MENU, self.app.showNews, id=item.GetId())
623
+
624
+ self.SetMenuBar(menuBar)
625
+
626
+ def openDeviceManager(self, evt=None):
627
+ # create a device manager dialog
628
+ dlg = DeviceManagerDlg(self)
629
+ # show it modal to this window
630
+ dlg.ShowModal()
631
+
632
+ def commandCloseFrame(self, event):
633
+ """Defines Builder Frame Closing Event"""
634
+ self.Close()
635
+
636
+ def closeFrame(self, event=None, checkSave=True):
637
+ """Defines Frame closing behavior, such as checking for file
638
+ saving"""
639
+ # close file first (check for save) but no need to update view
640
+ okToClose = self.fileClose(updateViews=False, checkSave=checkSave)
641
+
642
+ if not okToClose:
643
+ if hasattr(event, 'Veto'):
644
+ event.Veto()
645
+ return
646
+ else:
647
+ # as of wx3.0 the AUI manager needs to be uninitialised explicitly
648
+ self._mgr.UnInit()
649
+ # is it the last frame?
650
+ lastFrame = len(self.app.getAllFrames()) == 1
651
+ quitting = self.app.quitting
652
+ if lastFrame and sys.platform != 'darwin' and not quitting:
653
+ self.app.quit(event)
654
+ else:
655
+ self.app.forgetFrame(self)
656
+ self.Destroy() # required
657
+
658
+ # Show Runner if hidden
659
+ if self.app.runner is not None:
660
+ self.app.showRunner()
661
+ self.app.updateWindowMenu()
662
+
663
+ def quit(self, event=None):
664
+ """quit the app
665
+ """
666
+ self.app.quit(event)
667
+
668
+ def onResize(self, event):
669
+ """Called when the frame is resized."""
670
+ self.componentButtons.Refresh()
671
+ self.flowPanel.canvas.Refresh()
672
+ event.Skip()
673
+
674
+ def onShow(self, event):
675
+ """Called when the frame is shown"""
676
+ event.Skip()
677
+ # if README was updated when frame wasn't shown, it won't be show either - so update again
678
+ self.updateReadme()
679
+
680
+ @property
681
+ def filename(self):
682
+ """Name of the currently open file"""
683
+ return self._filename
684
+
685
+ @filename.setter
686
+ def filename(self, value):
687
+ if value is None:
688
+ # mark nonexistant
689
+ self.fileExists = False
690
+ # keep placeholder name for labels and etc.
691
+ self._filename = Path("untitled.psyexp")
692
+ else:
693
+ # path-ise and set
694
+ self._filename = Path(value)
695
+ # mark existant
696
+ self.fileExists = Path(value).is_file()
697
+ # enable/disable reveal button
698
+ if hasattr(self, "menuIDs"):
699
+ self.fileMenu.Enable(self.menuIDs.ID_REVEAL, self.fileExists)
700
+ # skip if there's no ribbon
701
+ if not hasattr(self, "ribbon"):
702
+ return
703
+ # enable/disable compile buttons
704
+ for key in ('compile_py', 'compile_js'):
705
+ if key in self.ribbon.buttons:
706
+ self.ribbon.buttons[key].Enable(
707
+ self._filename.is_file()
708
+ )
709
+
710
+ def fileNew(self, event=None, closeCurrent=True):
711
+ """Create a default experiment (maybe an empty one instead)
712
+ """
713
+ # Note: this is NOT the method called by the File>New menu item.
714
+ # That calls app.newBuilderFrame() instead
715
+ if closeCurrent: # if no exp exists then don't try to close it
716
+ if not self.fileClose(updateViews=False):
717
+ # close the existing (and prompt for save if necess)
718
+ return False
719
+ self.filename = None
720
+ self.exp = experiment.Experiment(prefs=self.app.prefs)
721
+ defaultName = 'trial'
722
+ # create the trial routine as an example
723
+ self.exp.addRoutine(defaultName)
724
+ self.exp.flow.addRoutine(
725
+ self.exp.routines[defaultName], pos=1) # add it to flow
726
+ # add it to user's namespace
727
+ self.exp.namespace.add(defaultName, self.exp.namespace.user)
728
+ routine = self.exp.routines[defaultName]
729
+ ## add an ISI component by default
730
+ # components = self.componentButtons.components
731
+ # Static = components['StaticComponent']
732
+ # ISI = Static(self.exp, parentName=defaultName, name='ISI',
733
+ # startType='time (s)', startVal=0.0,
734
+ # stopType='duration (s)', stopVal=0.5)
735
+ # routine.addComponent(ISI)
736
+ # set run mode silently and update icons
737
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode, silent=True)
738
+ self.updateRunModeIcons()
739
+ # update undo stack
740
+ self.resetUndoStack()
741
+ self.setIsModified(False)
742
+ self.updateAllViews()
743
+ self.app.updateWindowMenu()
744
+
745
+ def fileOpen(self, event=None, filename=None, closeCurrent=True):
746
+ """Open a FileDialog, then load the file if possible.
747
+ """
748
+ if filename is None:
749
+ # Set wildcard
750
+ if sys.platform != 'darwin':
751
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*.*")
752
+ else:
753
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*")
754
+ # get path of current file (or home dir to avoid temp)
755
+ initPath = str(self.filename.parent)
756
+ if self.fileExists:
757
+ dlg = wx.FileDialog(self, message=_translate("Open file ..."),
758
+ defaultDir=initPath,
759
+ style=wx.FD_OPEN,
760
+ wildcard=wildcard)
761
+ else:
762
+ dlg = wx.FileDialog(self, message=_translate("Open file ..."),
763
+ style=wx.FD_OPEN,
764
+ wildcard=wildcard)
765
+ if dlg.ShowModal() != wx.ID_OK:
766
+ return 0
767
+ filename = dlg.GetPath()
768
+
769
+ filename = str(filename)
770
+ # did user try to open a script in Builder?
771
+ if filename.endswith('.py'):
772
+ self.app.showCoder() # ensures that a coder window exists
773
+ self.app.coder.setCurrentDoc(filename)
774
+ self.app.coder.setFileModified(False)
775
+ return
776
+
777
+ with WindowFrozen(ctrl=self):
778
+ # try to pause rendering until all panels updated
779
+ if closeCurrent:
780
+ if not self.fileClose(updateViews=False):
781
+ # close the existing (and prompt for save if necess)
782
+ return False
783
+ self.exp = experiment.Experiment(prefs=self.app.prefs)
784
+ try:
785
+ self.exp.loadFromXML(filename)
786
+ # set run mode silently and update buttons accordingly
787
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode, silent=True)
788
+ self.updateRunModeIcons()
789
+ except Exception:
790
+ print(u"Failed to load {}. Please send the following to"
791
+ u" the PsychoPy user list".format(filename))
792
+ traceback.print_exc()
793
+ logging.flush()
794
+ self.resetUndoStack()
795
+ self.setIsModified(False)
796
+ self.filename = filename
797
+ # routinePanel.addRoutinePage() is done in
798
+ # routinePanel.redrawRoutines(), called by self.updateAllViews()
799
+ # update the views
800
+ self.updateAllViews() # if frozen effect will be visible on thaw
801
+
802
+ # Show README
803
+ if self.prefs['alwaysShowReadme']:
804
+ # If prefs are to always show README, show if populated
805
+ self.updateReadme()
806
+ else:
807
+ # Otherwise update so we have the object, but don't show until asked
808
+ self.updateReadme(show=False)
809
+
810
+ self.fileHistory.AddFileToHistory(filename)
811
+ self.htmlPath = None # so we won't accidentally save to other html exp
812
+
813
+ if self.app.runner:
814
+ self.app.runner.addTask(fileName=self.filename) # Add to Runner
815
+
816
+ self.project = pavlovia.getProject(filename)
817
+ self.app.updateWindowMenu()
818
+
819
+ def fileReveal(self, evt=None):
820
+ """
821
+ Reveal the current file in the system file explorer.
822
+ """
823
+ # get current dir
824
+ if self.fileExists:
825
+ folder = Path(self.filename).parent
826
+ else:
827
+ folder = Path().home()
828
+ # choose a command according to OS
829
+ if sys.platform in ['win32']:
830
+ comm = "explorer"
831
+ elif sys.platform in ['darwin']:
832
+ comm = "open"
833
+ elif sys.platform in ['linux', 'linux2']:
834
+ comm = "dolphin"
835
+ # use command to open folder
836
+ subprocess.call(f"{comm} {folder}", shell=True)
837
+
838
+ def fileSave(self, event=None, filename=None):
839
+ """Save file, revert to SaveAs if the file hasn't yet been saved
840
+ """
841
+ if filename is None:
842
+ filename = self.filename
843
+ else:
844
+ filename = Path(filename)
845
+
846
+ if not self.fileExists:
847
+ if not self.fileSaveAs(filename):
848
+ return False # the user cancelled during saveAs
849
+ else:
850
+ filename = self.exp.saveToXML(filename)
851
+ self.fileHistory.AddFileToHistory(filename)
852
+ self.setIsModified(False)
853
+ # if export on save then we should have an html file to update
854
+ if self._getExportPref('on save') and os.path.split(filename)[0]:
855
+ self.filename = filename
856
+ self.fileExport(htmlPath=self.htmlPath)
857
+ return True
858
+
859
+ def fileSaveAs(self, event=None, filename=None):
860
+ """Defines Save File as Behavior
861
+ """
862
+ shortFilename = self.getShortFilename()
863
+ expName = self.exp.getExpName()
864
+ if (not expName) or (shortFilename == expName):
865
+ usingDefaultName = True
866
+ else:
867
+ usingDefaultName = False
868
+ # force filename to Path
869
+ if filename is None:
870
+ filename = self.filename
871
+ else:
872
+ filename = Path(filename)
873
+ # get parent and filename
874
+ initPath = filename.parent
875
+ filename = filename.name
876
+ # substitute temp dir for home
877
+ if not self.fileExists:
878
+ initPath = Path.home()
879
+
880
+ if sys.platform != 'darwin':
881
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*.*")
882
+ else:
883
+ wildcard = _translate("PsychoPy experiments (*.psyexp)|*.psyexp|Any file (*.*)|*")
884
+ returnVal = False
885
+ dlg = wx.FileDialog(
886
+ self, message=_translate("Save file as ..."), defaultDir=str(initPath),
887
+ defaultFile=filename, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
888
+ wildcard=wildcard)
889
+
890
+ if dlg.ShowModal() == wx.ID_OK:
891
+ newPath = dlg.GetPath()
892
+ # update exp name
893
+ # if user has not manually renamed experiment
894
+ if usingDefaultName:
895
+ newShortName = os.path.splitext(
896
+ os.path.split(newPath)[1])[0]
897
+ self.exp.setExpName(newShortName)
898
+ # actually save
899
+ self.filename = newPath
900
+ self.fileExists = True
901
+ self.fileSave(event=None, filename=newPath)
902
+ # enable/disable reveal button
903
+ if hasattr(self, "menuIDs"):
904
+ self.fileMenu.Enable(self.menuIDs.ID_REVEAL, True)
905
+ # update pavlovia project
906
+ self.project = pavlovia.getProject(filename)
907
+ returnVal = 1
908
+ dlg.Destroy()
909
+
910
+ self.updateWindowTitle()
911
+ # update README in case the file path has changed
912
+ if self.prefs['alwaysShowReadme']:
913
+ # if prefs are to always show README, show if populated
914
+ self.updateReadme()
915
+ else:
916
+ # otherwise update so we have the object, but don't show until asked
917
+ self.updateReadme(show=False)
918
+ return returnVal
919
+
920
+ def fileExport(self, event=None, htmlPath=None):
921
+ """Exports the script as an HTML file (PsychoJS library)
922
+ """
923
+ # get path if not given one
924
+ if htmlPath is None:
925
+ htmlPath = self._getHtmlPath(self.filename)
926
+ if not htmlPath:
927
+ return
928
+
929
+ exportPath = os.path.join(htmlPath, self.exp.name + '.js')
930
+ exportPath = self.generateScript(
931
+ outfile=exportPath,
932
+ exp=self.exp,
933
+ target="PsychoJS"
934
+ )
935
+ # Open exported files
936
+ self.app.showCoder(fileList=[exportPath])
937
+ self.app.coder.fileReload(event=None, filename=exportPath)
938
+
939
+ def editREADME(self, event):
940
+ if self.filename is None:
941
+ dlg = wx.MessageDialog(
942
+ self,
943
+ _translate("Please save experiment before editing the README file"),
944
+ _translate("No readme file"),
945
+ wx.OK | wx.ICON_WARNING | wx.CENTRE)
946
+ dlg.ShowModal()
947
+ else:
948
+ self.updateReadme(show=True)
949
+
950
+ def getShortFilename(self, withExt=False):
951
+ """
952
+ Returns the filename without path
953
+
954
+ Parameters
955
+ ----------
956
+ withExt : bool
957
+ Should the returned filename include the file extension? False by default.
958
+ """
959
+ # get file stem
960
+ shortName = self.filename.stem
961
+ ext = self.filename.suffix
962
+ # append extension if requested
963
+ if withExt:
964
+ shortName += ext
965
+
966
+ return shortName
967
+
968
+ # def pluginManager(self, evt=None, value=True):
969
+ # """Show the plugin manager frame."""
970
+ # PluginManagerFrame(self).ShowModal()
971
+
972
+ def onFindInExperiment(self, evt=None):
973
+ dlg = BuilderFindDlg(frame=self, exp=self.exp)
974
+ dlg.Show()
975
+
976
+ def updateReadme(self, show=None):
977
+ """Check whether there is a readme file in this folder and try to show
978
+
979
+ Parameters
980
+ ==========
981
+ show : bool or None
982
+ If True, always show Readme frame.
983
+ If False, never show Readme frame.
984
+ If None, show only when there is content.
985
+ """
986
+ # Make sure we have a file
987
+ dirname = self.filename.parent
988
+ possibles = list(dirname.glob('readme*'))
989
+ if len(possibles) == 0:
990
+ possibles = list(dirname.glob('Readme*'))
991
+ possibles.extend(dirname.glob('README*'))
992
+
993
+ # still haven't found a file so use default name
994
+ if len(possibles) == 0:
995
+ self.readmeFilename = str(dirname / 'readme.md') # use this as our default
996
+ else:
997
+ self.readmeFilename = str(possibles[0]) # take the first one found
998
+
999
+ # Make sure we have a frame
1000
+ if self.readmeFrame is None:
1001
+ self.readmeFrame = ReadmeFrame(
1002
+ parent=self, filename=self.readmeFilename
1003
+ )
1004
+
1005
+ # Set file
1006
+ if self.fileExists:
1007
+ self.readmeFrame.setFile(self.readmeFilename)
1008
+ else:
1009
+ self.readmeFrame.setFile(None)
1010
+ show = False
1011
+ self.readmeFrame.ctrl.load()
1012
+
1013
+ # Show/hide frame as appropriate
1014
+ if show is None:
1015
+ show = len(self.readmeFrame.ctrl.getValue()) > 0
1016
+ show = show and self.IsShown()
1017
+ self.readmeFrame.show(show)
1018
+
1019
+ def showReadme(self, evt=None, value=True):
1020
+ """Shows Readme file
1021
+ """
1022
+ if not self.readmeFrame.IsShown():
1023
+ self.readmeFrame.show(value)
1024
+
1025
+ def toggleReadme(self, evt=None):
1026
+ """Toggles visibility of Readme file
1027
+ """
1028
+ if self.readmeFrame is None:
1029
+ self.showReadme()
1030
+ else:
1031
+ self.readmeFrame.toggleVisible()
1032
+
1033
+ def OnFileHistory(self, evt=None):
1034
+ """get the file based on the menu ID
1035
+ """
1036
+ fileNum = evt.GetId() - wx.ID_FILE1
1037
+ path = self.fileHistory.GetHistoryFile(fileNum)
1038
+ self.fileOpen(filename=path)
1039
+ # add it back to the history so it will be moved up the list
1040
+ self.fileHistory.AddFileToHistory(path)
1041
+
1042
+ def checkSave(self):
1043
+ """Check whether we need to save before quitting
1044
+ """
1045
+ if hasattr(self, 'isModified') and self.isModified and not self.app.testMode:
1046
+ self.Show(True)
1047
+ self.Raise()
1048
+ self.app.SetTopWindow(self)
1049
+ msg = _translate('Experiment %s has changed. Save before '
1050
+ 'quitting?') % self.filename
1051
+ dlg = dialogs.MessageDialog(self, msg, type='Warning')
1052
+ resp = dlg.ShowModal()
1053
+ if resp == wx.ID_CANCEL:
1054
+ return False # return, don't quit
1055
+ elif resp == wx.ID_YES:
1056
+ if not self.fileSave():
1057
+ return False # user might cancel during save
1058
+ elif resp == wx.ID_NO:
1059
+ pass # don't save just quit
1060
+ return True
1061
+
1062
+ def fileClose(self, event=None, checkSave=True, updateViews=True):
1063
+ """This is typically only called when the user x
1064
+ """
1065
+ if checkSave:
1066
+ ok = self.checkSave()
1067
+ if not ok:
1068
+ return False # user cancelled
1069
+ frameData = self.appData['defaultFrame']
1070
+ if self.fileExists:
1071
+ self.appData['prevFiles'].append(self.filename)
1072
+
1073
+ # get size and window layout info
1074
+ if self.IsIconized():
1075
+ self.Iconize(False) # will return to normal mode to get size info
1076
+ frameData['state'] = 'normal'
1077
+ elif self.IsMaximized():
1078
+ # will briefly return to normal mode to get size info
1079
+ self.Maximize(False)
1080
+ frameData['state'] = 'maxim'
1081
+ else:
1082
+ frameData['state'] = 'normal'
1083
+ frameData['auiPerspective'] = self._mgr.SavePerspective()
1084
+ frameData['winW'], frameData['winH'] = self.GetSize()
1085
+ frameData['winX'], frameData['winY'] = self.GetPosition()
1086
+
1087
+ # truncate history to the recent-most last N unique files, where
1088
+ # N = self.fileHistoryMaxFiles, as defined in makeMenus()
1089
+ for ii in range(self.fileHistory.GetCount()):
1090
+ self.appData['fileHistory'].append(
1091
+ self.fileHistory.GetHistoryFile(ii))
1092
+ # fileClose gets calls multiple times, so remove redundancy
1093
+ # while preserving order; end of the list is recent-most:
1094
+ tmp = []
1095
+ fhMax = self.fileHistoryMaxFiles
1096
+ for f in self.appData['fileHistory'][-3 * fhMax:]:
1097
+ if f not in tmp:
1098
+ tmp.append(f)
1099
+ self.appData['fileHistory'] = copy.copy(tmp[-fhMax:])
1100
+
1101
+ # assign the data to this filename
1102
+ if (
1103
+ str(self.filename) not in self.appData['frames']
1104
+ and str(self.filename) not in self.appData
1105
+ ):
1106
+ self.appData['frames'][str(self.filename)] = frameData
1107
+ # save the display data only for those frames in the history:
1108
+ tmp2 = {}
1109
+ for f in self.appData['frames']:
1110
+ if f in self.appData['fileHistory']:
1111
+ tmp2[f] = self.appData['frames'][f]
1112
+ self.appData['frames'] = copy.copy(tmp2)
1113
+
1114
+ # close self
1115
+ self.routinePanel.removePages()
1116
+ self.filename = None
1117
+ # add the current exp as the start point for undo:
1118
+ self.resetUndoStack()
1119
+ if updateViews:
1120
+ self.updateAllViews()
1121
+ return 1
1122
+
1123
+ def updateAllViews(self):
1124
+ """Updates Flow Panel, Routine Panel, and Window Title simultaneously
1125
+ """
1126
+ self.flowPanel.canvas.draw()
1127
+ self.routinePanel.redrawRoutines()
1128
+ self.componentButtons.Refresh()
1129
+ self.updateWindowTitle()
1130
+
1131
+ def layoutPanes(self):
1132
+ # Get panes
1133
+ flowPane = self._mgr.GetPane('Flow')
1134
+ compPane = self._mgr.GetPane('Components')
1135
+ rtPane = self._mgr.GetPane('Routines')
1136
+ # Arrange panes according to prefs
1137
+ if 'FlowBottom' in self.prefs['builderLayout']:
1138
+ flowPane.Bottom()
1139
+ elif 'FlowTop' in self.prefs['builderLayout']:
1140
+ flowPane.Top()
1141
+ if 'CompRight' in self.prefs['builderLayout']:
1142
+ compPane.Right()
1143
+ if 'CompLeft' in self.prefs['builderLayout']:
1144
+ compPane.Left()
1145
+ rtPane.Center()
1146
+ # Commit
1147
+ self._mgr.Update()
1148
+
1149
+ def resetPrefs(self, event):
1150
+ """Reset preferences to default"""
1151
+ # Present "are you sure" dialog
1152
+ dlg = wx.MessageDialog(
1153
+ self, _translate(
1154
+ "Are you sure you want to reset your preferences? This cannot "
1155
+ "be undone."),
1156
+ caption="Reset Preferences...",
1157
+ style=wx.ICON_WARNING | wx.CANCEL)
1158
+ dlg.SetOKCancelLabels(
1159
+ _translate("I'm sure"),
1160
+ _translate("Wait, go back!")
1161
+ )
1162
+ if dlg.ShowModal() == wx.ID_OK:
1163
+ # If okay is pressed, remove prefs file (meaning a new one will be
1164
+ # created on next restart)
1165
+ os.remove(prefs.paths['userPrefsFile'])
1166
+ # Show confirmation
1167
+ dlg = wx.MessageDialog(
1168
+ self, _translate(
1169
+ "Done! Your preferences have been reset. Changes will be "
1170
+ "applied when you next open PsychoPy."))
1171
+ dlg.ShowModal()
1172
+ else:
1173
+ pass
1174
+
1175
+ def updateWindowTitle(self, newTitle=None):
1176
+ """Defines behavior to update window Title
1177
+ """
1178
+ if newTitle is None:
1179
+ newTitle = self.getShortFilename(withExt=True)
1180
+ self.setTitle(title=self.winTitle, document=newTitle)
1181
+
1182
+ def setIsModified(self, newVal=None):
1183
+ """Sets current modified status and updates save icon accordingly.
1184
+
1185
+ This method is called by the methods fileSave, undo, redo,
1186
+ addToUndoStack and it is usually preferably to call those
1187
+ than to call this directly.
1188
+
1189
+ Call with ``newVal=None``, to only update the save icon(s)
1190
+ """
1191
+ if newVal is None:
1192
+ newVal = self.getIsModified()
1193
+ else:
1194
+ self.isModified = newVal
1195
+ # get ribbon buttons
1196
+ if 'save' in self.ribbon.buttons:
1197
+ self.ribbon.buttons['save'].Enable(newVal)
1198
+ self.fileMenu.Enable(wx.ID_SAVE, newVal)
1199
+
1200
+ def getIsModified(self):
1201
+ """Checks if changes were made"""
1202
+ return self.isModified
1203
+
1204
+ def resetUndoStack(self):
1205
+ """Reset the undo stack. do *immediately after* creating a new exp.
1206
+
1207
+ Implicitly calls addToUndoStack() using the current exp as the state
1208
+ """
1209
+ self.currentUndoLevel = 1 # 1 is current, 2 is back one setp...
1210
+ self.currentUndoStack = []
1211
+ self.addToUndoStack()
1212
+ self.updateUndoRedo()
1213
+ self.setIsModified(newVal=False) # update save icon if needed
1214
+
1215
+ def addToUndoStack(self, action="", state=None):
1216
+ """Add the given ``action`` to the currentUndoStack, associated
1217
+ with the @state@. ``state`` should be a copy of the exp
1218
+ from *immediately after* the action was taken.
1219
+ If no ``state`` is given the current state of the experiment is used.
1220
+
1221
+ If we are at end of stack already then simply append the action. If
1222
+ not (user has done an undo) then remove orphan actions and append.
1223
+ """
1224
+ if state is None:
1225
+ state = copy.deepcopy(self.exp)
1226
+ # remove actions from after the current level
1227
+ if self.currentUndoLevel > 1:
1228
+ self.currentUndoStack = self.currentUndoStack[
1229
+ :-(self.currentUndoLevel - 1)]
1230
+ self.currentUndoLevel = 1
1231
+ # append this action
1232
+ self.currentUndoStack.append({'action': action, 'state': state})
1233
+ self.setIsModified(newVal=True) # update save icon if needed
1234
+ self.updateUndoRedo()
1235
+
1236
+ def undo(self, event=None):
1237
+ """Step the exp back one level in the @currentUndoStack@ if possible,
1238
+ and update the windows.
1239
+
1240
+ Returns the final undo level (1=current, >1 for further in past)
1241
+ or -1 if redo failed (probably can't undo)
1242
+ """
1243
+ if self.currentUndoLevel >= len(self.currentUndoStack):
1244
+ return -1 # can't undo
1245
+ self.currentUndoLevel += 1
1246
+ state = self.currentUndoStack[-self.currentUndoLevel]['state']
1247
+ self.exp = copy.deepcopy(state)
1248
+ self.updateAllViews()
1249
+ self.setIsModified(newVal=True) # update save icon if needed
1250
+ self.updateUndoRedo()
1251
+
1252
+ return self.currentUndoLevel
1253
+
1254
+ def redo(self, event=None):
1255
+ """Step the exp up one level in the @currentUndoStack@ if possible,
1256
+ and update the windows.
1257
+
1258
+ Returns the final undo level (0=current, >0 for further in past)
1259
+ or -1 if redo failed (probably can't redo)
1260
+ """
1261
+ if self.currentUndoLevel <= 1:
1262
+ return -1 # can't redo, we're already at latest state
1263
+ self.currentUndoLevel -= 1
1264
+ self.exp = copy.deepcopy(
1265
+ self.currentUndoStack[-self.currentUndoLevel]['state'])
1266
+ self.updateUndoRedo()
1267
+ self.updateAllViews()
1268
+ self.setIsModified(newVal=True) # update save icon if needed
1269
+ return self.currentUndoLevel
1270
+
1271
+ def paste(self, event=None):
1272
+ """This receives paste commands for all child dialog boxes as well
1273
+ """
1274
+ foc = self.FindFocus()
1275
+ if hasattr(foc, 'Paste'):
1276
+ foc.Paste()
1277
+
1278
+ def updateUndoRedo(self):
1279
+ """Defines Undo and Redo commands for the window
1280
+ """
1281
+ undoLevel = self.currentUndoLevel
1282
+ # check undo
1283
+ if undoLevel >= len(self.currentUndoStack):
1284
+ # can't undo if we're at top of undo stack
1285
+ label = _translate("Undo\t%s") % self.app.keys['undo']
1286
+ enable = False
1287
+ else:
1288
+ action = self.currentUndoStack[-undoLevel]['action']
1289
+ txt = _translate("Undo %(action)s\t%(key)s")
1290
+ fmt = {'action': action, 'key': self.app.keys['undo']}
1291
+ label = txt % fmt
1292
+ enable = True
1293
+ self._undoLabel.SetItemLabel(label)
1294
+ if 'undo' in self.ribbon.buttons:
1295
+ self.ribbon.buttons['undo'].Enable(enable)
1296
+ self.editMenu.Enable(wx.ID_UNDO, enable)
1297
+
1298
+ # check redo
1299
+ if undoLevel == 1:
1300
+ label = _translate("Redo\t%s") % self.app.keys['redo']
1301
+ enable = False
1302
+ else:
1303
+ action = self.currentUndoStack[-undoLevel + 1]['action']
1304
+ txt = _translate("Redo %(action)s\t%(key)s")
1305
+ fmt = {'action': action, 'key': self.app.keys['redo']}
1306
+ label = txt % fmt
1307
+ enable = True
1308
+ self._redoLabel.SetItemLabel(label)
1309
+ if 'redo' in self.ribbon.buttons:
1310
+ self.ribbon.buttons['redo'].Enable(enable)
1311
+ self.editMenu.Enable(wx.ID_REDO, enable)
1312
+
1313
+ def demosUnpack(self, event=None):
1314
+ """Get a folder location from the user and unpack demos into it."""
1315
+ # choose a dir to unpack in
1316
+ dlg = wx.DirDialog(parent=self, message=_translate(
1317
+ "Location to unpack demos"))
1318
+ if dlg.ShowModal() == wx.ID_OK:
1319
+ unpackFolder = dlg.GetPath()
1320
+ else:
1321
+ return -1 # user cancelled
1322
+ # ensure it's an empty dir:
1323
+ if os.listdir(unpackFolder) != []:
1324
+ unpackFolder = os.path.join(unpackFolder, 'PsychoPy3 Demos')
1325
+ if not os.path.isdir(unpackFolder):
1326
+ os.mkdir(unpackFolder)
1327
+ mergeFolder(os.path.join(self.paths['demos'], 'builder'),
1328
+ unpackFolder)
1329
+ self.prefs['unpackedDemosDir'] = unpackFolder
1330
+ self.app.prefs.saveUserPrefs()
1331
+ updateDemosMenu(self, self.demosMenu, self.prefs['unpackedDemosDir'],
1332
+ ext=".psyexp")
1333
+
1334
+ def demoLoad(self, event=None):
1335
+ """Defines Demo Loading Event."""
1336
+ fileDir = self.demos[event.GetId()]
1337
+ files = glob.glob(os.path.join(fileDir, '*.psyexp'))
1338
+ if len(files) == 0:
1339
+ print("Found no psyexp files in %s" % fileDir)
1340
+ else:
1341
+ self.fileOpen(event=None, filename=files[0], closeCurrent=True)
1342
+
1343
+ def openLocalDemos(self, event=None):
1344
+ # Choose a command according to OS
1345
+ if sys.platform in ['win32']:
1346
+ comm = "explorer"
1347
+ elif sys.platform in ['darwin']:
1348
+ comm = "open"
1349
+ elif sys.platform in ['linux', 'linux2']:
1350
+ comm = "dolphin"
1351
+ # Use command to open themes folder
1352
+ subprocess.call(f"{comm} {prefs.builder['unpackedDemosDir']}", shell=True)
1353
+
1354
+ def openPavloviaDemos(self, event=None):
1355
+ webbrowser.open("https://pavlovia.org/explore")
1356
+
1357
+ def sendToRunner(self, evt=None):
1358
+ """
1359
+ Send the current file to the Runner.
1360
+ """
1361
+ # Check whether file is truly untitled (not just saved as untitled)
1362
+ if not self.fileExists:
1363
+ ok = self.fileSave(self.filename)
1364
+ if not ok:
1365
+ return False # save file before compiling script
1366
+
1367
+ if self.getIsModified():
1368
+ ok = self.fileSave(self.filename)
1369
+ if not ok:
1370
+ return False # save file before compiling script
1371
+ self.app.showRunner()
1372
+ self.app.runner.addTask(fileName=self.filename)
1373
+ self.app.runner.Raise()
1374
+ self.app.showRunner()
1375
+
1376
+ return True
1377
+
1378
+ def updateRunModeIcons(self, evt=None):
1379
+ """
1380
+ Function to update run/pilot icons according to run mode
1381
+ """
1382
+ mode = self.ribbon.buttons['pyswitch'].mode
1383
+ # show/hide run buttons
1384
+ for key in ("pyrun", "jsrun", "sendRunner"):
1385
+ self.ribbon.buttons[key].Show(mode)
1386
+ # hide/show pilot buttons
1387
+ for key in ("pypilot", "jspilot", "pilotRunner"):
1388
+ self.ribbon.buttons[key].Show(not mode)
1389
+ # update
1390
+ self.ribbon.Layout()
1391
+
1392
+ def onRunModeToggle(self, evt):
1393
+ """
1394
+ Function to execute when switching between pilot and run modes
1395
+ """
1396
+ mode = evt.GetInt()
1397
+ # update icons
1398
+ self.updateRunModeIcons()
1399
+ # update experiment mode
1400
+ if self.exp is not None and self.exp.runMode != mode:
1401
+ self.exp.runMode = mode
1402
+ # mark as modified
1403
+ self.setIsModified(True)
1404
+ # update
1405
+ self.ribbon.Update()
1406
+ self.ribbon.Refresh()
1407
+ self.ribbon.Layout()
1408
+
1409
+ def onRunShortcut(self, evt=None):
1410
+ """
1411
+ Callback for when the run shortcut is pressed - will either run or pilot depending on run mode
1412
+ """
1413
+ # do nothing if we have no experiment
1414
+ if self.exp is None:
1415
+ return
1416
+ # run/pilot according to mode
1417
+ if self.exp.runMode:
1418
+ self.runFile(evt)
1419
+ else:
1420
+ self.pilotFile(evt)
1421
+
1422
+ def runFile(self, event=None):
1423
+ """
1424
+ Send the current file to the Runner and run it.
1425
+ """
1426
+ if self.sendToRunner(event):
1427
+ self.app.runner.panel.runLocal(event)
1428
+
1429
+ def pilotFile(self, event=None):
1430
+ """
1431
+ Send the current file to the Runner and run it in pilot mode.
1432
+ """
1433
+ if self.sendToRunner(event):
1434
+ self.app.runner.panel.pilotLocal(event)
1435
+
1436
+ def onCopyRoutine(self, event=None):
1437
+ """copy the current routine from self.routinePanel
1438
+ to self.app.copiedRoutine.
1439
+ """
1440
+ r = self.routinePanel.getCurrentRoutine().copy()
1441
+ if r is not None:
1442
+ self.app.copiedRoutine = r
1443
+
1444
+ def onPasteRoutine(self, event=None):
1445
+ """Paste the current routine from self.app.copiedRoutine to a new page
1446
+ in self.routinePanel after prompting for a new name.
1447
+ """
1448
+ if self.app.copiedRoutine is None:
1449
+ return -1
1450
+ origName = self.app.copiedRoutine.name
1451
+ defaultName = self.exp.namespace.makeValid(origName)
1452
+ msg = _translate('New name for copy of "%(copied)s"? [%(default)s]')
1453
+ vals = {'copied': origName, 'default': defaultName}
1454
+ message = msg % vals
1455
+ dlg = wx.TextEntryDialog(self, message=message,
1456
+ caption=_translate('Paste Routine'))
1457
+ if dlg.ShowModal() == wx.ID_OK:
1458
+ routineName = dlg.GetValue()
1459
+ if not routineName:
1460
+ routineName = defaultName
1461
+ newRoutine = self.app.copiedRoutine.copy()
1462
+ self.pasteRoutine(newRoutine, routineName)
1463
+ dlg.Destroy()
1464
+
1465
+ def pasteRoutine(self, newRoutine, routineName):
1466
+ """
1467
+ Paste a copied Routine into the current Experiment. Returns a copy of that Routine
1468
+ """
1469
+ newRoutine.name = self.exp.namespace.makeValid(routineName, prefix="routine")
1470
+ newRoutine.exp = self.exp
1471
+ # add to the experiment
1472
+ self.exp.addRoutine(newRoutine.name, newRoutine)
1473
+ for newComp in newRoutine: # routine == list of components
1474
+ newName = self.exp.namespace.makeValid(newComp.params['name'])
1475
+ self.exp.namespace.add(newName)
1476
+ newComp.params['name'].val = newName
1477
+ newComp.exp = self.exp
1478
+ # could do redrawRoutines but would be slower?
1479
+ self.routinePanel.addRoutinePage(newRoutine.name, newRoutine)
1480
+ self.routinePanel.setCurrentRoutine(newRoutine)
1481
+ return newRoutine
1482
+
1483
+ def onPasteCompon(self, event=None):
1484
+ """
1485
+ Paste the copied Component (if there is one) into the current
1486
+ Routine
1487
+ """
1488
+ routinePage = self.routinePanel.getCurrentPage()
1489
+ routinePage.pasteCompon()
1490
+
1491
+ def onURL(self, evt):
1492
+ """decompose the URL of a file and line number"""
1493
+ # "C:\Program Files\wxPython...\samples\hangman\hangman.py"
1494
+ filename = evt.GetString().split('"')[1]
1495
+ lineNumber = int(evt.GetString().split(',')[1][5:])
1496
+ self.app.showCoder()
1497
+ self.app.coder.gotoLine(filename, lineNumber)
1498
+
1499
+ def setExperimentSettings(self, event=None, timeout=None):
1500
+ """Defines ability to save experiment settings
1501
+ """
1502
+ component = self.exp.settings
1503
+ # does this component have a help page?
1504
+ if hasattr(component, 'url'):
1505
+ helpUrl = component.url
1506
+ else:
1507
+ helpUrl = None
1508
+ title = '%s Properties' % self.exp.getExpName()
1509
+ dlg = DlgExperimentProperties(
1510
+ frame=self, element=component, experiment=self.exp, timeout=timeout)
1511
+
1512
+ if dlg.OK:
1513
+ # add to undo stack
1514
+ self.addToUndoStack("EDIT experiment settings")
1515
+ # update run mode
1516
+ self.ribbon.buttons['pyswitch'].setMode(self.exp.runMode)
1517
+ # mark modified
1518
+ self.setIsModified(True)
1519
+
1520
+ def addRoutine(self, event=None):
1521
+ """Defines ability to add routine in the routine panel
1522
+ """
1523
+ self.routinePanel.createNewRoutine()
1524
+
1525
+ def renameRoutine(self, name, event=None):
1526
+ """Defines ability to rename routine in the routine panel
1527
+ """
1528
+ # get notebook details
1529
+ routine = self.routinePanel.GetPage(
1530
+ self.routinePanel.GetSelection()).routine
1531
+ oldName = routine.name
1532
+ msg = _translate("What is the new name for the Routine?")
1533
+ dlg = wx.TextEntryDialog(self, message=msg, value=oldName,
1534
+ caption=_translate('Rename'))
1535
+ if dlg.ShowModal() == wx.ID_OK:
1536
+ name = dlg.GetValue()
1537
+ self._doRenameRoutine(oldName=oldName, newName=name)
1538
+ dlg.Destroy()
1539
+
1540
+ def _doRenameRoutine(self, oldName, newName):
1541
+ # silently auto-adjust the name to be valid, and register in the
1542
+ # namespace:
1543
+ name = self.exp.namespace.makeValid(newName, prefix='routine')
1544
+ if oldName in self.exp.routines:
1545
+ # Swap old with new names
1546
+ self.exp.routines[oldName].name = name
1547
+ self.exp.routines[name] = self.exp.routines.pop(oldName)
1548
+ self.exp.namespace.rename(oldName, name)
1549
+ currentRoutine = self.routinePanel.getCurrentPage()
1550
+ currentRoutineIndex = self.routinePanel.GetPageIndex(currentRoutine)
1551
+ self.routinePanel.renameRoutinePage(currentRoutineIndex, name)
1552
+ self.addToUndoStack("`RENAME Routine `%s`" % oldName)
1553
+ self.flowPanel.canvas.draw()
1554
+
1555
+ def compileScript(self, event=None):
1556
+ """Defines compile script button behavior"""
1557
+ # save so we have a file to work off
1558
+ saved = self.fileSave()
1559
+ # if save cancelled, return now
1560
+ if not saved:
1561
+ return
1562
+ # construct filename for py file
1563
+ fullPath = self.filename.parent / (self.exp.name + '.py')
1564
+ # write script
1565
+ fullPath = self.generateScript(
1566
+ outfile=str(fullPath),
1567
+ exp=self.exp
1568
+ )
1569
+ # show it in Coder
1570
+ self.app.showCoder(fileList=[fullPath]) # make sure coder is visible
1571
+ self.app.coder.fileReload(event=None, filename=fullPath)
1572
+
1573
+ @property
1574
+ def stdoutFrame(self):
1575
+ """
1576
+ Gets Experiment Runner stdout.
1577
+ """
1578
+ if not self.app.runner:
1579
+ self.app.runner = self.app.showRunner()
1580
+ return self.app.runner
1581
+
1582
+ def _getHtmlPath(self, filename):
1583
+ expPath = os.path.split(filename)[0]
1584
+ if not os.path.isdir(expPath):
1585
+ retVal = self.fileSave()
1586
+ if retVal:
1587
+ return self._getHtmlPath(self.filename)
1588
+ else:
1589
+ return False
1590
+
1591
+ htmlPath = os.path.join(expPath, self.exp.htmlFolder)
1592
+ return htmlPath
1593
+
1594
+ def _getExportPref(self, pref):
1595
+ """Returns True if pref matches exportHTML preference"""
1596
+ if pref.lower() not in [prefs.lower() for prefs in self.exp.settings.params['exportHTML'].allowedVals]:
1597
+ raise ValueError("'{}' is not an allowed value for {}".format(pref, 'exportHTML'))
1598
+ exportHtml = str(self.exp.settings.params['exportHTML'].val).lower()
1599
+ if exportHtml == pref.lower():
1600
+ return True
1601
+
1602
+ def openPluginManager(self, evt=None):
1603
+ # check if the package index is currently being updated, show a message
1604
+ # to tell the user to wait before opening the plugin manager
1605
+ import psychopy.app.plugin_manager.packageIndex as packageIndex
1606
+ if packageIndex.isIndexing():
1607
+ msg = _translate("The package index is currently being updated. "
1608
+ "Please try again later.")
1609
+ wx.MessageBox(
1610
+ msg,
1611
+ _translate("Package indexing in progress"),
1612
+ style=wx.OK | wx.ICON_INFORMATION
1613
+ )
1614
+ return
1615
+
1616
+ dlg = psychopy.app.plugin_manager.dialog.EnvironmentManagerDlg(self)
1617
+ dlg.Show()
1618
+
1619
+ return dlg
1620
+
1621
+ def onPavloviaCreate(self, evt=None):
1622
+ if Path(self.filename).is_file():
1623
+ # Save file
1624
+ self.fileSave(self.filename)
1625
+ # If allowed by prefs, export html and js files
1626
+ if self._getExportPref('on sync'):
1627
+ htmlPath = self._getHtmlPath(self.filename)
1628
+ if htmlPath:
1629
+ self.fileExport(htmlPath=htmlPath)
1630
+ else:
1631
+ return
1632
+ # Get start path and name from builder/coder if possible
1633
+ if self.filename:
1634
+ file = Path(self.filename)
1635
+ name = file.stem
1636
+ path = file.parent
1637
+ else:
1638
+ name = path = ""
1639
+ # Open dlg to create new project
1640
+ createDlg = sync.CreateDlg(self,
1641
+ user=pavlovia.getCurrentSession().user,
1642
+ name=name,
1643
+ path=path)
1644
+ if createDlg.ShowModal() == wx.ID_OK and createDlg.project is not None:
1645
+ self.project = createDlg.project
1646
+ else:
1647
+ return
1648
+ # Do first sync
1649
+ self.onPavloviaSync()
1650
+
1651
+ def onPavloviaSync(self, evt=None):
1652
+ if Path(self.filename).is_file():
1653
+ # Save file
1654
+ self.fileSave(self.filename)
1655
+ # If allowed by prefs, export html and js files
1656
+ if self._getExportPref('on sync'):
1657
+ htmlPath = self._getHtmlPath(self.filename)
1658
+ if htmlPath:
1659
+ self.fileExport(htmlPath=htmlPath)
1660
+ else:
1661
+ return
1662
+ # Sync
1663
+ pavlovia_ui.syncProject(parent=self, file=self.filename, project=self.project)
1664
+
1665
+ def onPavloviaRun(self, evt=None):
1666
+ # Sync project
1667
+ self.onPavloviaSync()
1668
+
1669
+ if self.project is not None:
1670
+ # Update project status
1671
+ self.project.pavloviaStatus = 'ACTIVATED'
1672
+ # Run
1673
+ url = "https://pavlovia.org/run/{}".format(self.project['path_with_namespace'])
1674
+ wx.LaunchDefaultBrowser(url)
1675
+
1676
+ def onPavloviaDebug(self, evt=None):
1677
+ # Open runner
1678
+ self.app.showRunner()
1679
+ runner = self.app.runner
1680
+ # Make sure we have a current file
1681
+ if self.getIsModified() or not Path(self.filename).is_file():
1682
+ saved = self.fileSave()
1683
+ if not saved:
1684
+ return
1685
+ # Send current file to runner
1686
+ runner.addTask(fileName=self.filename)
1687
+ # Run debug function from runner
1688
+ self.app.runner.panel.runOnlineDebug(evt=evt)
1689
+
1690
+ @property
1691
+ def project(self):
1692
+ """A PavloviaProject object if one is known for this experiment
1693
+ """
1694
+ if hasattr(self, "_project"):
1695
+ return self._project
1696
+ elif self.fileExists:
1697
+ return pavlovia.getProject(self.filename)
1698
+ else:
1699
+ return None
1700
+
1701
+ @project.setter
1702
+ def project(self, project):
1703
+ self._project = project
1704
+
1705
+ self.ribbon.buttons['pavproject'].updateInfo()
1706
+
1707
+
1708
+ class RoutinesNotebook(aui.AuiNotebook, handlers.ThemeMixin):
1709
+ """A notebook that stores one or more routines
1710
+ """
1711
+
1712
+ def __init__(self, frame, id=-1):
1713
+ self.frame = frame
1714
+ self.app = frame.app
1715
+ self.routineMaxSize = 2
1716
+ self.appData = self.app.prefs.appData
1717
+ aui.AuiNotebook.__init__(self, frame, id,
1718
+ agwStyle=aui.AUI_NB_TAB_MOVE | aui.AUI_NB_CLOSE_ON_ACTIVE_TAB | aui.AUI_NB_WINDOWLIST_BUTTON)
1719
+ self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.onClosePane)
1720
+ self.Bind(aui.EVT_AUINOTEBOOK_END_DRAG, self.onMoveTab)
1721
+
1722
+ # double buffered better rendering except if retina
1723
+
1724
+ self.SetDoubleBuffered(not self.frame.isRetina)
1725
+
1726
+ # This needs to be done on init, otherwise it gets an outline
1727
+ self.GetAuiManager().SetArtProvider(handlers.PsychopyDockArt())
1728
+
1729
+ if not hasattr(self.frame, 'exp'):
1730
+ return # we haven't yet added an exp
1731
+
1732
+ def getCurrentRoutine(self):
1733
+ routinePage = self.getCurrentPage()
1734
+ if routinePage:
1735
+ return routinePage.routine # no routine page
1736
+ return None
1737
+
1738
+ def setCurrentRoutine(self, routine):
1739
+ for ii in range(self.GetPageCount()):
1740
+ if routine is self.GetPage(ii).routine:
1741
+ self.SetSelection(ii)
1742
+ self.frame.flowPanel.canvas.draw()
1743
+
1744
+ def SetSelection(self, index, force=False):
1745
+ aui.AuiNotebook.SetSelection(self, index, force=force)
1746
+ self.frame.componentButtons.enableComponents(
1747
+ not isinstance(self.GetPage(index).routine, BaseStandaloneRoutine)
1748
+ )
1749
+
1750
+ def getCurrentPage(self):
1751
+ if self.GetSelection() >= 0:
1752
+ return self.GetPage(self.GetSelection())
1753
+ return None
1754
+
1755
+ def addRoutinePage(self, routineName, routine):
1756
+ # Make page
1757
+ routinePage = None
1758
+ if isinstance(routine, Routine):
1759
+ routinePage = RoutineCanvas(notebook=self, routine=routine)
1760
+ elif isinstance(routine, BaseStandaloneRoutine):
1761
+ routinePage = StandaloneRoutineCanvas(parent=self, routine=routine)
1762
+ # Add page
1763
+ if routinePage:
1764
+ self.AddPage(routinePage, routineName)
1765
+
1766
+ def renameRoutinePage(self, index, newName, ):
1767
+ self.SetPageText(index, newName)
1768
+
1769
+ def removePages(self):
1770
+ for ii in range(self.GetPageCount()):
1771
+ currId = self.GetSelection()
1772
+ self.DeletePage(currId)
1773
+
1774
+ def createNewRoutine(self, template=None):
1775
+ msg = _translate("What is the name for the new Routine? "
1776
+ "(e.g. instr, trial, feedback)")
1777
+ dlg = DlgNewRoutine(self)
1778
+ routineName = None
1779
+ if dlg.ShowModal() == wx.ID_OK:
1780
+ routineName = dlg.nameCtrl.GetValue()
1781
+ routineName = self.frame.exp.namespace.makeValid(routineName, prefix="routine")
1782
+ template = copy.deepcopy(dlg.selectedTemplate)
1783
+ self.frame.pasteRoutine(template, routineName)
1784
+ self.frame.addToUndoStack("NEW Routine `%s`" % routineName)
1785
+ dlg.Destroy()
1786
+ return routineName
1787
+
1788
+ def onClosePane(self, event=None):
1789
+ """Close the pane and remove the routine from the exp.
1790
+ """
1791
+ currentPage = self.GetPage(event.GetSelection())
1792
+ routine = currentPage.routine
1793
+ name = routine.name
1794
+
1795
+ # name is not valid for some reason
1796
+ if name not in self.frame.exp.routines:
1797
+ event.Skip()
1798
+ return
1799
+
1800
+ # check if the user wants a prompt
1801
+ showDlg = self.app.prefs.builder.get('confirmRoutineClose', False)
1802
+ if showDlg:
1803
+ # message to display
1804
+ msg = _translate(
1805
+ "Do you want to remove routine '{}' from the experiment?")
1806
+
1807
+ # dialog asking if the user wants to remove the routine
1808
+ dlg = wx.MessageDialog(
1809
+ self,
1810
+ _translate(msg).format(name),
1811
+ _translate('Remove routine?'),
1812
+ wx.YES_NO | wx.NO_DEFAULT | wx.CENTRE | wx.STAY_ON_TOP)
1813
+
1814
+ # show the dialog and get the response
1815
+ dlgResult = dlg.ShowModal()
1816
+ dlg.Destroy()
1817
+
1818
+ if dlgResult == wx.ID_NO: # if NO, stop the tab from closing
1819
+ event.Veto()
1820
+ return
1821
+
1822
+ # remove names of the routine and its components from namespace
1823
+ _nsp = self.frame.exp.namespace
1824
+ for c in self.frame.exp.routines[name]:
1825
+ _nsp.remove(c.params['name'].val)
1826
+ _nsp.remove(self.frame.exp.routines[name].name)
1827
+ del self.frame.exp.routines[name]
1828
+
1829
+ if routine in self.frame.exp.flow:
1830
+ self.frame.exp.flow.removeComponent(routine)
1831
+ self.frame.flowPanel.canvas.draw()
1832
+ self.frame.addToUndoStack("REMOVE Routine `%s`" % (name))
1833
+
1834
+ def onMoveTab(self, evt=None):
1835
+ """
1836
+ After moving tabs around, sorts Routines in the Experiment accordingly
1837
+ and marks experiment as changed.
1838
+
1839
+ Parameters
1840
+ ----------
1841
+ evt : wx.aui.AUI_NB_TAB_MOVE
1842
+ Event generated by moving the tab (not used)
1843
+ """
1844
+
1845
+ # Get tab names in order
1846
+ names = []
1847
+ for i in range(self.GetPageCount()):
1848
+ names.append(self.GetPageText(i))
1849
+ # Reorder routines in experiment to match tab order
1850
+ routines = collections.OrderedDict()
1851
+ for name in names:
1852
+ routines[name] = self.frame.exp.routines[name]
1853
+ self.frame.exp.routines = routines
1854
+ # Set modified
1855
+ self.frame.setIsModified(True)
1856
+
1857
+ def increaseSize(self, event=None):
1858
+ self.appData['routineSize'] = min(
1859
+ self.routineMaxSize, self.appData['routineSize'] + 1)
1860
+ with WindowFrozen(self):
1861
+ self.redrawRoutines()
1862
+
1863
+ def decreaseSize(self, event=None):
1864
+ self.appData['routineSize'] = max(0, self.appData['routineSize'] - 1)
1865
+ with WindowFrozen(self):
1866
+ self.redrawRoutines()
1867
+
1868
+ def redrawRoutines(self):
1869
+ """Removes all the routines, adds them back (alphabetical order),
1870
+ sets current back to orig
1871
+ """
1872
+ currPage = self.GetSelection()
1873
+ self.removePages()
1874
+ for routineName in self.frame.exp.routines:
1875
+ if isinstance(self.frame.exp.routines[routineName], (Routine, BaseStandaloneRoutine)):
1876
+ self.addRoutinePage(
1877
+ routineName, self.frame.exp.routines[routineName])
1878
+ if currPage > -1:
1879
+ self.SetSelection(currPage)
1880
+
1881
+
1882
+ class RoutineCanvas(wx.ScrolledWindow, handlers.ThemeMixin):
1883
+ """Represents a single routine (used as page in RoutinesNotebook)"""
1884
+
1885
+ def __init__(self, notebook, id=wx.ID_ANY, routine=None):
1886
+ """This window is based heavily on the PseudoDC demo of wxPython
1887
+ """
1888
+ wx.ScrolledWindow.__init__(
1889
+ self, notebook, id, (0, 0), style=wx.BORDER_NONE | wx.VSCROLL)
1890
+
1891
+ self.frame = notebook.frame
1892
+ self.app = self.frame.app
1893
+ self.dpi = self.app.dpi
1894
+ self.lines = []
1895
+ self.maxWidth = self.GetSize().GetWidth()
1896
+ self.maxHeight = 15 * self.dpi
1897
+ self.x = self.y = 0
1898
+ self.curLine = []
1899
+ self.drawing = False
1900
+ self.drawSize = self.app.prefs.appData['routineSize']
1901
+ # dict in which to store rectangles to aid layout (populated in updateLayoutRects)
1902
+ self.rects = {}
1903
+ # auto-rescale based on number of components and window size is jumpy
1904
+ # when switch between routines of diff drawing sizes
1905
+ self.iconSize = (24, 24, 48)[self.drawSize] # only 24, 48 so far
1906
+ self.fontBaseSize = (1100, 1200, 1300)[self.drawSize] # depends on OS?
1907
+ #self.scroller = PsychopyScrollbar(self, wx.VERTICAL)
1908
+ self.SetVirtualSize((self.maxWidth, self.maxHeight))
1909
+ self.SetScrollRate(self.dpi // 16, self.dpi // 16)
1910
+
1911
+ self.routine = routine
1912
+ self.yPositions = None
1913
+ self.yPosTop = (25, 40, 60)[self.drawSize]
1914
+ # the step in Y between each component
1915
+ self.componentStep = (25, 32, 50)[self.drawSize]
1916
+ self.timeXposStart = (150, 150, 200)[self.drawSize]
1917
+ # the left hand edge of the icons:
1918
+ _scale = (1.3, 1.5, 1.5)[self.drawSize]
1919
+ self.iconXpos = self.timeXposStart - self.iconSize * _scale
1920
+ self.timeXposEnd = self.timeXposStart + 400 # onResize() overrides
1921
+
1922
+ # create a PseudoDC to record our drawing
1923
+ self.pdc = PseudoDC()
1924
+ self.pen_cache = {}
1925
+ self.brush_cache = {}
1926
+ # vars for handling mouse clicks
1927
+ self.dragid = -1
1928
+ self.lastpos = (0, 0)
1929
+ # use the ID of the drawn icon to retrieve component name:
1930
+ self.componentFromID = {}
1931
+ # define context menu items and labels
1932
+ self.contextMenuLabels = {
1933
+ 'copy': _translate("Copy"),
1934
+ 'paste above': _translate("Paste above"),
1935
+ 'paste below': _translate("Paste below"),
1936
+ 'edit': _translate("Edit"),
1937
+ 'remove': _translate("Remove"),
1938
+ 'move to top': _translate("Move to top"),
1939
+ 'move up': _translate("Move up"),
1940
+ 'move down': _translate("Move down"),
1941
+ 'move to bottom': _translate("Move to bottom"),
1942
+ }
1943
+ self.contextMenuItems = list(self.contextMenuLabels)
1944
+
1945
+ self.contextItemFromID = {}
1946
+ self.contextIDFromItem = {}
1947
+ for item in self.contextMenuItems:
1948
+ id = wx.NewIdRef()
1949
+ self.contextItemFromID[id] = item
1950
+ self.contextIDFromItem[item] = id
1951
+
1952
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
1953
+ self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
1954
+ self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1955
+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
1956
+ self.Bind(wx.EVT_SIZE, self.onResize)
1957
+ # crashes if drop on OSX:
1958
+ # self.SetDropTarget(FileDropTarget(builder = self.frame))
1959
+
1960
+ def _applyAppTheme(self, target=None):
1961
+ """Synonymise app theme method with redraw method"""
1962
+ return self.redrawRoutine()
1963
+
1964
+ def onResize(self, event):
1965
+ self.sizePix = event.GetSize()
1966
+ self.timeXposStart = (150, 150, 200)[self.drawSize]
1967
+ self.timeXposEnd = self.sizePix[0] - (60, 80, 100)[self.drawSize]
1968
+ self.redrawRoutine() # then redraw visible
1969
+
1970
+ def ConvertEventCoords(self, event):
1971
+ xView, yView = self.GetViewStart()
1972
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
1973
+ return (event.GetX() + (xView * xDelta),
1974
+ event.GetY() + (yView * yDelta))
1975
+
1976
+ def OffsetRect(self, r):
1977
+ """Offset the rectangle, r, to appear in the given pos in the window
1978
+ """
1979
+ xView, yView = self.GetViewStart()
1980
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
1981
+ r.OffsetXY(-(xView * xDelta), -(yView * yDelta))
1982
+
1983
+ def OnMouse(self, event):
1984
+ if event.LeftDown():
1985
+ x, y = self.ConvertEventCoords(event)
1986
+ icons = self.pdc.FindObjectsByBBox(x, y)
1987
+ if len(icons):
1988
+ self.editComponentProperties(
1989
+ component=self.componentFromID[icons[0]])
1990
+ elif event.RightDown():
1991
+ x, y = self.ConvertEventCoords(event)
1992
+ icons = self.pdc.FindObjectsByBBox(x, y)
1993
+ menuPos = event.GetPosition()
1994
+ if 'flowTop' in self.app.prefs.builder['builderLayout']:
1995
+ # width of components panel
1996
+ menuPos[0] += self.frame.componentButtons.GetSize()[0]
1997
+ # height of flow panel
1998
+ menuPos[1] += self.frame.flowPanel.canvas.GetSize()[1]
1999
+ if len(icons):
2000
+ self._menuComponent = self.componentFromID[icons[0]]
2001
+ self.showContextMenu(self._menuComponent, xy=menuPos)
2002
+ else: # no context
2003
+ self.showContextMenu(None, xy=menuPos)
2004
+
2005
+ elif event.Dragging() or event.LeftUp():
2006
+ if self.dragid != -1:
2007
+ pass
2008
+ if event.LeftUp():
2009
+ pass
2010
+ elif event.Moving():
2011
+ try:
2012
+ x, y = self.ConvertEventCoords(event)
2013
+ id = self.pdc.FindObjectsByBBox(x, y)[0]
2014
+ component = self.componentFromID[id]
2015
+ # Indicate hover target in the bottom bar
2016
+ if component == self.routine.settings:
2017
+ self.frame.SetStatusText("Routine settings: " + component.params['name'].val)
2018
+ else:
2019
+ self.frame.SetStatusText("Component: "+component.params['name'].val)
2020
+ except IndexError:
2021
+ self.frame.SetStatusText("")
2022
+
2023
+ def OnScroll(self, event):
2024
+ xy = self.GetViewStart()
2025
+ delta = int(event.WheelRotation * self.dpi / 1600)
2026
+ self.Scroll(xy[0], xy[1]-delta)
2027
+
2028
+ def showContextMenu(self, component, xy):
2029
+ """Show a context menu in the routine view.
2030
+ """
2031
+ menu = wx.Menu()
2032
+ if component not in (None, self.routine.settings):
2033
+ for item in self.contextMenuItems:
2034
+ id = self.contextIDFromItem[item]
2035
+ # don't show paste option unless something is copied
2036
+ if item.startswith('paste'):
2037
+ if not self.app.copiedCompon: # skip paste options
2038
+ continue
2039
+ itemLabel = " ".join(
2040
+ (self.contextMenuLabels[item],
2041
+ "({})".format(
2042
+ self.app.copiedCompon.params['name'].val)))
2043
+ elif any([item.startswith(op) for op in ('copy', 'remove', 'edit')]):
2044
+ itemLabel = " ".join(
2045
+ (self.contextMenuLabels[item],
2046
+ "({})".format(component.params['name'].val)))
2047
+ else:
2048
+ itemLabel = self.contextMenuLabels[item]
2049
+
2050
+ menu.Append(id, itemLabel)
2051
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
2052
+
2053
+ self.frame.PopupMenu(menu, xy)
2054
+ menu.Destroy() # destroy to avoid mem leak
2055
+ else:
2056
+ # anywhere but a hotspot is clicked, show this menu
2057
+ if self.app.copiedCompon:
2058
+ itemLabel = " ".join(
2059
+ (_translate('paste'),
2060
+ "({})".format(
2061
+ self.app.copiedCompon.params['name'].val)))
2062
+ menu.Append(wx.ID_ANY, itemLabel)
2063
+ menu.Bind(wx.EVT_MENU, self.pasteCompon, id=wx.ID_ANY)
2064
+
2065
+ self.frame.PopupMenu(menu, xy)
2066
+ menu.Destroy()
2067
+
2068
+ def onContextSelect(self, event):
2069
+ """Perform a given action on the component chosen
2070
+ """
2071
+ op = self.contextItemFromID[event.GetId()]
2072
+ component = self._menuComponent
2073
+ r = self.routine
2074
+ if op == 'edit':
2075
+ self.editComponentProperties(component=component)
2076
+ elif op == 'copy':
2077
+ self.copyCompon(component=component)
2078
+ elif op == 'paste above':
2079
+ self.pasteCompon(index=r.index(component))
2080
+ elif op == 'paste below':
2081
+ self.pasteCompon(index=r.index(component) + 1)
2082
+ elif op == 'remove':
2083
+ r.removeComponent(component)
2084
+ self.frame.addToUndoStack(
2085
+ "REMOVE `%s` from Routine" % component.params['name'].val)
2086
+ self.frame.exp.namespace.remove(component.params['name'].val)
2087
+ elif op.startswith('move'):
2088
+ lastLoc = r.index(component)
2089
+ r.remove(component)
2090
+ if op == 'move to top':
2091
+ r.insert(0, component)
2092
+ if op == 'move up':
2093
+ r.insert(lastLoc - 1, component)
2094
+ if op == 'move down':
2095
+ r.insert(lastLoc + 1, component)
2096
+ if op == 'move to bottom':
2097
+ r.append(component)
2098
+ self.frame.addToUndoStack("MOVED `%s`" %
2099
+ component.params['name'].val)
2100
+ self.redrawRoutine()
2101
+ self._menuComponent = None
2102
+
2103
+ def OnPaint(self, event):
2104
+ # Create a buffered paint DC. It will create the real
2105
+ # wx.PaintDC and then blit the bitmap to it when dc is
2106
+ # deleted.
2107
+ dc = wx.GCDC(wx.BufferedPaintDC(self))
2108
+ # we need to clear the dc BEFORE calling PrepareDC
2109
+ bg = wx.Brush(self.GetBackgroundColour())
2110
+ dc.SetBackground(bg)
2111
+ dc.Clear()
2112
+ # use PrepareDC to set position correctly
2113
+ self.PrepareDC(dc)
2114
+ # create a clipping rect from our position and size
2115
+ # and the Update Region
2116
+ xv, yv = self.GetViewStart()
2117
+ dx, dy = self.GetScrollPixelsPerUnit()
2118
+ x, y = (xv * dx, yv * dy)
2119
+ rgn = self.GetUpdateRegion()
2120
+ rgn.Offset(x, y)
2121
+ r = rgn.GetBox()
2122
+ # draw to the dc using the calculated clipping rect
2123
+ self.pdc.DrawToDCClipped(dc, r)
2124
+
2125
+ def redrawRoutine(self):
2126
+ # clear everything
2127
+ self.pdc.Clear()
2128
+ self.pdc.RemoveAll()
2129
+ # set font size
2130
+ self.setFontSize(self.fontBaseSize // self.dpi, self.pdc)
2131
+
2132
+ # update rects with which to layout
2133
+ self.updateLayoutRects()
2134
+ # # if debugging, draw all the rects
2135
+ # self.pdc.SetPen(wx.Pen("Red"))
2136
+ # for rect in self.rects.values():
2137
+ # self.pdc.DrawRectangle(rect)
2138
+
2139
+ self.SetBackgroundColour(colors.app['tab_bg'])
2140
+
2141
+ # separate components according to whether they are drawn in separate
2142
+ # row
2143
+ rowComponents = []
2144
+ staticCompons = []
2145
+ for n, component in enumerate(self.routine):
2146
+ if component.type == 'Static':
2147
+ staticCompons.append(component)
2148
+ elif component == self.routine.settings:
2149
+ pass
2150
+ else:
2151
+ rowComponents.append(component)
2152
+
2153
+ # draw settings button
2154
+ settingsBtnExtent = self.drawSettingsBtn(self.pdc, self.routine.settings)
2155
+
2156
+ # draw static, time grid, normal (row) comp:
2157
+ yPos = self.rects['grid'].Top
2158
+ yPosBottom = self.rects['grid'].Bottom
2159
+ # draw any Static Components first (below the grid)
2160
+ for component in staticCompons:
2161
+ bottom = max(yPosBottom, self.GetSize()[1])
2162
+ self.drawStatic(self.pdc, component, yPos, bottom)
2163
+ self.drawTimeGrid(self.pdc, yPos, yPosBottom)
2164
+ # normal components, one per row
2165
+ for component in rowComponents:
2166
+ self.drawComponent(self.pdc, component, yPos)
2167
+ yPos += self.componentStep
2168
+ # draw end line (if there is one)
2169
+ self.drawForceEndLine(self.pdc, yPosBottom)
2170
+
2171
+ # the 50 allows space for labels below the time axis
2172
+ self.SetVirtualSize((int(self.maxWidth), yPos + 50))
2173
+ self.Refresh() # refresh the visible window after drawing (OnPaint)
2174
+ #self.scroller.Resize()
2175
+
2176
+ def updateLayoutRects(self):
2177
+ """
2178
+ Recalculate the positions and sizes of the wx.Rect objects which determine
2179
+ how the canvas is laid out.
2180
+ """
2181
+ self.rects = {}
2182
+ self.setFontSize(self.fontBaseSize // self.dpi, self.pdc)
2183
+
2184
+ # --- Whole area ---
2185
+ canvas = self.rects['canvas'] = wx.Rect(
2186
+ x=15,
2187
+ y=15,
2188
+ width=self.sizePix[0] - 30,
2189
+ height=self.sizePix[1] - 30
2190
+ )
2191
+
2192
+ # --- Time grid ---
2193
+ # filter Components for just those included in the time grid
2194
+ trueComponents = []
2195
+ for comp in self.routine:
2196
+ if type(comp).__name__ in ("StaticComponent", "RoutineSettingsComponent"):
2197
+ continue
2198
+ else:
2199
+ trueComponents.append(comp)
2200
+ # note: will be modified as things are added around it
2201
+ grid = self.rects['grid'] = wx.Rect(
2202
+ x=canvas.Left,
2203
+ y=canvas.Top,
2204
+ width=canvas.Width,
2205
+ height=self.componentStep * len(trueComponents)
2206
+ )
2207
+
2208
+ # --- Top bar ---
2209
+ # this is where the Settings button lives
2210
+ topBar = self.rects['topBar'] = wx.Rect(
2211
+ x=canvas.Left,
2212
+ y=canvas.Top,
2213
+ width=canvas.Width,
2214
+ height=int(self.iconSize/3) + 24
2215
+ )
2216
+ # shift grid down
2217
+ grid.Top += topBar.Height
2218
+
2219
+ # --- Time labels ---
2220
+ # note: will be modified as things are added around it
2221
+ timeLbls = self.rects['timeLbls'] = wx.Rect(
2222
+ x=grid.Left,
2223
+ y=topBar.Bottom,
2224
+ width=grid.Width,
2225
+ height=int(self.componentStep/2)
2226
+ )
2227
+ # shift grid down
2228
+ grid.Top += timeLbls.Height
2229
+
2230
+ # --- Component names ---
2231
+ # get width of component names column
2232
+ compNameWidths = [120]
2233
+ if not prefs.builder['abbreviateLongCompNames']:
2234
+ # get width of longest name if we're not elipsizing
2235
+ for comp in self.routine:
2236
+ w = self.GetFullTextExtent(comp.name)[0] + 12
2237
+ compNameWidths.append(w)
2238
+ componentLabelWidth = max(compNameWidths)
2239
+ # create rect
2240
+ compLbls = self.rects['compLbls'] = wx.Rect(
2241
+ x=canvas.Left,
2242
+ y=grid.Top,
2243
+ width=componentLabelWidth,
2244
+ height=grid.Height
2245
+ )
2246
+ # shift grid and time labels right (and cut to size)
2247
+ grid.Left += compLbls.Width
2248
+ grid.Width -= compLbls.Width
2249
+ timeLbls.Left += compLbls.Width
2250
+ timeLbls.Width -= compLbls.Width
2251
+
2252
+ # --- Component icons ---
2253
+ icons = self.rects['icons'] = wx.Rect(
2254
+ x=compLbls.Right,
2255
+ y=grid.Top,
2256
+ width=self.iconSize + 12,
2257
+ height=grid.Height
2258
+ )
2259
+ # shift grid and time labels right (and cut to size)
2260
+ grid.Left += icons.Width + 12
2261
+ grid.Width -= icons.Width + 12
2262
+ timeLbls.Left += icons.Width + 12
2263
+ timeLbls.Width -= icons.Width + 12
2264
+
2265
+ # --- Time units label ---
2266
+ timeUnitsLbl = self.rects['timeUnitsLbl'] = wx.Rect(
2267
+ x=grid.Right,
2268
+ y=grid.Top,
2269
+ width=self.GetFullTextExtent("t (sec)")[0] + 12,
2270
+ height=int(self.componentStep/2)
2271
+ )
2272
+ # align self by right edge
2273
+ timeUnitsLbl.Left -= timeUnitsLbl.Width
2274
+ # shift grid and time labels left (and cut to size)
2275
+ grid.Width -= timeUnitsLbl.Width
2276
+ timeLbls.Width -= timeUnitsLbl.Width
2277
+
2278
+ # update references from rects
2279
+ self.timeXposStart = grid.Left
2280
+ self.timeXposEnd = grid.Right
2281
+ self.iconXpos = self.rects['icons'].Left
2282
+
2283
+ def getMaxTime(self):
2284
+ """Return the max time to be drawn in the window
2285
+ """
2286
+ maxTime, nonSlip = self.routine.getMaxTime()
2287
+ if self.routine.hasOnlyStaticComp():
2288
+ maxTime = int(maxTime) + 1.0
2289
+
2290
+ # if max came from routine settings, mark as hard stop
2291
+ rtMax, rtMaxIsNum = self.routine.settings.getDuration()
2292
+ hardStop = rtMaxIsNum and rtMax == maxTime
2293
+ # handle no max
2294
+ if maxTime is None:
2295
+ maxTime = 10
2296
+
2297
+ return maxTime, hardStop
2298
+
2299
+ def drawTimeGrid(self, dc, yPosTop, yPosBottom, labelAbove=True):
2300
+ """Draws the grid of lines and labels the time axes
2301
+ """
2302
+ yPosTop = int(yPosTop) # explicit type conversion to `int`
2303
+ yPosBottom = int(yPosBottom)
2304
+
2305
+ tMax, hardStop = self.getMaxTime()
2306
+ tMax *= 1.1
2307
+ xScale = self.getSecsPerPixel()
2308
+ xSt = self.timeXposStart
2309
+ xEnd = self.timeXposEnd
2310
+
2311
+ # dc.SetId(wx.NewIdRef())
2312
+ dc.SetPen(wx.Pen(colors.app['rt_timegrid']))
2313
+ dc.SetTextForeground(wx.Colour(colors.app['rt_timegrid']))
2314
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2315
+
2316
+ id = wx.NewIdRef()
2317
+ dc.SetId(id)
2318
+
2319
+ # draw horizontal lines on top and bottom
2320
+ dc.DrawLine(
2321
+ x1=int(xSt),
2322
+ y1=yPosTop,
2323
+ x2=int(xEnd),
2324
+ y2=yPosTop)
2325
+ dc.DrawLine(
2326
+ x1=int(xSt),
2327
+ y1=yPosBottom,
2328
+ x2=int(xEnd),
2329
+ y2=yPosBottom)
2330
+
2331
+ # draw vertical time points
2332
+ # gives roughly 1/10 the width, but in rounded to base 10 of
2333
+ # 0.1,1,10...
2334
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 10.0
2335
+ if tMax / unitSize < 3:
2336
+ # gives units of 2 (0.2,2,20)
2337
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 50.0
2338
+ elif tMax / unitSize < 6:
2339
+ # gives units of 5 (0.5,5,50)
2340
+ unitSize = 10 ** numpy.ceil(numpy.log10(tMax * 0.8)) / 20.0
2341
+ for lineN in range(int(numpy.floor((tMax / unitSize)))):
2342
+ # vertical line:
2343
+ dc.DrawLine(int(xSt + lineN * unitSize / xScale),
2344
+ yPosTop - 4,
2345
+ int(xSt + lineN * unitSize / xScale),
2346
+ yPosBottom + 4)
2347
+ # label above:
2348
+ dc.DrawText('%.2g' % (lineN * unitSize),
2349
+ int(xSt + lineN * unitSize / xScale - 4),
2350
+ yPosTop - 30)
2351
+ if yPosBottom > 300:
2352
+ # if bottom of grid is far away then draw labels here too
2353
+ dc.DrawText('%.2g' % (lineN * unitSize),
2354
+ int(xSt + lineN * unitSize / xScale - 4),
2355
+ yPosBottom + 10)
2356
+ # add a label
2357
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2358
+ # y is y-half height of text
2359
+ dc.DrawText('t (sec)',
2360
+ self.rects['timeUnitsLbl'].Left + 6,
2361
+ self.rects['timeUnitsLbl'].Top)
2362
+ # or draw bottom labels only if scrolling is turned on, virtual size >
2363
+ # available size?
2364
+ if yPosBottom > 300:
2365
+ # if bottom of grid is far away then draw labels there too
2366
+ # y is y-half height of text
2367
+ dc.DrawText('t (sec)',
2368
+ int(xEnd + 5),
2369
+ yPosBottom - self.GetFullTextExtent('t')[1] // 2)
2370
+ dc.SetTextForeground(colors.app['text'])
2371
+
2372
+ def drawForceEndLine(self, dc, yPosBottom):
2373
+ id = wx.NewIdRef()
2374
+ dc.SetId(id)
2375
+ # get max time & check if we have a hard stop
2376
+ tMax, hardStop = self.getMaxTime()
2377
+ # if routine has an estimated stop, it's not a hard stop but we shold draw the line anyway
2378
+ if self.routine.settings.params.get("durationEstim", False):
2379
+ hardStop = True
2380
+
2381
+ if hardStop:
2382
+ # if hard stop, draw orange final line
2383
+ dc.SetPen(
2384
+ wx.Pen(colors.app['rt_comp_force'], width=4)
2385
+ )
2386
+ dc.SetTextForeground(
2387
+ wx.Colour(colors.app['rt_comp_force'])
2388
+ )
2389
+ # vertical line:
2390
+ dc.DrawLine(self.timeXposEnd,
2391
+ self.rects['grid'].Top - 4,
2392
+ self.timeXposEnd,
2393
+ yPosBottom + 4)
2394
+ # label above:
2395
+ dc.DrawText('%.2g' % tMax,
2396
+ int(self.timeXposEnd - 4),
2397
+ self.rects['grid'].Top - 30)
2398
+
2399
+ def setFontSize(self, size, dc):
2400
+ font = self.GetFont()
2401
+ font.SetPointSize(size)
2402
+ dc.SetFont(font)
2403
+ self.SetFont(font)
2404
+
2405
+ def drawStatic(self, dc, component, yPosTop, yPosBottom):
2406
+ """draw a static (ISI) component box"""
2407
+
2408
+ # type conversion to `int`
2409
+ yPosTop = int(yPosTop)
2410
+ yPosBottom = int(yPosBottom)
2411
+
2412
+ # set an id for the region of this component (so it can
2413
+ # act as a button). see if we created this already.
2414
+ id = None
2415
+ for key in self.componentFromID:
2416
+ if self.componentFromID[key] == component:
2417
+ id = key
2418
+ if not id: # then create one and add to the dict
2419
+ id = wx.NewIdRef()
2420
+ self.componentFromID[id] = component
2421
+ dc.SetId(id)
2422
+ # deduce start and stop times if possible
2423
+ startTime, duration, nonSlipSafe = component.getStartAndDuration()
2424
+ # ensure static comps are clickable (even if $code start or duration)
2425
+ unknownTiming = False
2426
+ if startTime is None:
2427
+ startTime = 0
2428
+ unknownTiming = True
2429
+ if duration is None:
2430
+ duration = 0 # minimal extent ensured below
2431
+ unknownTiming = True
2432
+ # calculate rectangle for component
2433
+ xScale = self.getSecsPerPixel()
2434
+
2435
+ if component.params['disabled'].val:
2436
+ dc.SetBrush(wx.Brush(colors.app['rt_static_disabled']))
2437
+ dc.SetPen(wx.Pen(colors.app['rt_static_disabled']))
2438
+
2439
+ else:
2440
+ dc.SetBrush(wx.Brush(colors.app['rt_static']))
2441
+ dc.SetPen(wx.Pen(colors.app['rt_static']))
2442
+
2443
+ xSt = self.timeXposStart + startTime // xScale
2444
+ w = duration // xScale + 1 # +1 b/c border alpha=0 in dc.SetPen
2445
+ w = max(min(w, 10000), 2) # ensure 2..10000 pixels
2446
+ h = yPosBottom - yPosTop
2447
+ # name label, position:
2448
+ name = component.params['name'].val # "ISI"
2449
+ if unknownTiming:
2450
+ # flag it as not literally represented in time, e.g., $code
2451
+ # duration
2452
+ name += ' ???'
2453
+ nameW, nameH = self.GetFullTextExtent(name)[0:2]
2454
+ x = xSt + w // 2
2455
+ staticLabelTop = (0, 50, 60)[self.drawSize]
2456
+ y = staticLabelTop - nameH * 3
2457
+ fullRect = wx.Rect(int(x - 20), int(y), int(nameW), int(nameH))
2458
+ # draw the rectangle, draw text on top:
2459
+ dc.DrawRectangle(
2460
+ int(xSt), int(yPosTop - nameH * 4), int(w), int(h + nameH * 5))
2461
+ dc.DrawText(name, int(x - nameW // 2), y)
2462
+ # update bounds to include time bar
2463
+ fullRect.Union(wx.Rect(int(xSt), int(yPosTop), int(w), int(h)))
2464
+ dc.SetIdBounds(id, fullRect)
2465
+
2466
+ def drawComponent(self, dc, component, yPos):
2467
+ """Draw the timing of one component on the timeline"""
2468
+ # set an id for the region of this component (so it
2469
+ # can act as a button). see if we created this already
2470
+
2471
+ yPos = int(yPos) # explicit type conversion
2472
+
2473
+ id = None
2474
+ for key in self.componentFromID:
2475
+ if self.componentFromID[key] == component:
2476
+ id = key
2477
+ if not id: # then create one and add to the dict
2478
+ id = wx.NewIdRef()
2479
+ self.componentFromID[id] = component
2480
+ dc.SetId(id)
2481
+
2482
+ iconYOffset = (6, 6, 0)[self.drawSize]
2483
+ # get default icon and bar color
2484
+ thisIcon = icons.ComponentIcon(component, size=self.iconSize).bitmap
2485
+ thisColor = colors.app['rt_comp']
2486
+ thisStyle = wx.BRUSHSTYLE_SOLID
2487
+
2488
+ # check True/False on ForceEndRoutine
2489
+ if 'forceEndRoutine' in component.params:
2490
+ if component.params['forceEndRoutine'].val:
2491
+ thisColor = colors.app['rt_comp_force']
2492
+ # check True/False on ForceEndRoutineOnPress
2493
+ if 'forceEndRoutineOnPress' in component.params:
2494
+ if component.params['forceEndRoutineOnPress'].val in ['any click', 'correct click', 'valid click']:
2495
+ thisColor = colors.app['rt_comp_force']
2496
+ # check True aliases on EndRoutineOn
2497
+ if 'endRoutineOn' in component.params:
2498
+ if component.params['endRoutineOn'].val in ['look at', 'look away']:
2499
+ thisColor = colors.app['rt_comp_force']
2500
+ # grey bar if comp is disabled
2501
+ if component.params['disabled'].val:
2502
+ thisIcon = thisIcon.ConvertToDisabled()
2503
+ thisColor = colors.app['rt_comp_disabled']
2504
+
2505
+ dc.DrawBitmap(thisIcon, int(self.iconXpos) + 6, int(yPos + iconYOffset), True)
2506
+ fullRect = wx.Rect(
2507
+ int(self.iconXpos),
2508
+ yPos,
2509
+ thisIcon.GetWidth(),
2510
+ thisIcon.GetHeight())
2511
+
2512
+ self.setFontSize(self.fontBaseSize // self.dpi, dc)
2513
+
2514
+ name = component.params['name'].val
2515
+ # elipsize name if it's too long
2516
+ if self.GetFullTextExtent(name)[0] > self.rects['compLbls'].Width:
2517
+ name = name[:6] + "..." + name[-6:]
2518
+ # get size based on text
2519
+ w = self.rects['compLbls'].Width
2520
+ h = self.GetFullTextExtent(name)[1]
2521
+
2522
+ # draw text
2523
+ # + x position of icon (left side)
2524
+ # - half width of icon (including whitespace around it)
2525
+ # - FULL width of text
2526
+ # + slight adjustment for whitespace
2527
+ x = self.rects['compLbls'].Right - 6 - self.GetFullTextExtent(name)[0]
2528
+ _adjust = (5, 5, -2)[self.drawSize]
2529
+ y = yPos + thisIcon.GetHeight() // 2 - h // 2 + _adjust
2530
+ dc.DrawText(name, int(x), y)
2531
+ fullRect.Union(
2532
+ wx.Rect(int(x - 20), int(y), int(w), int(h)))
2533
+
2534
+ # deduce start and stop times if possible
2535
+ startTime, duration, nonSlipSafe = component.getStartAndDuration()
2536
+ # draw entries on timeline (if they have some time definition)
2537
+ if duration is not None:
2538
+ yOffset = (3.5, 3.5, 0.5)[self.drawSize]
2539
+ h = self.componentStep // (4, 3.25, 2.5)[self.drawSize]
2540
+ xScale = self.getSecsPerPixel()
2541
+ # then we can draw a sensible time bar!
2542
+ thisPen = wx.Pen(thisColor, style=wx.TRANSPARENT)
2543
+ thisBrush = wx.Brush(thisColor, style=thisStyle)
2544
+ dc.SetPen(thisPen)
2545
+ dc.SetBrush(thisBrush)
2546
+ # cap duration if routine has a max
2547
+ maxDur, useMax = self.routine.settings.getDuration()
2548
+ overspill = 0
2549
+ if useMax:
2550
+ if maxDur is None:
2551
+ maxDur = duration
2552
+ overspill = max(duration - maxDur, 0)
2553
+ duration = min(maxDur, duration)
2554
+ # If there's a fixed end time and no start time, start 20px before 0
2555
+ if ('stopType' in component.params) and ('startType' in component.params) and (
2556
+ component.params['stopType'].val in ('time (s)', 'duration (s)')
2557
+ and component.params['startType'].val in ('time (s)')
2558
+ and startTime is None
2559
+ ):
2560
+ startTime = -20 * self.getSecsPerPixel()
2561
+ duration += 20 * self.getSecsPerPixel()
2562
+ # thisBrush.SetStyle(wx.BRUSHSTYLE_BDIAGONAL_HATCH)
2563
+ # dc.SetBrush(thisBrush)
2564
+
2565
+ if startTime is not None:
2566
+ xSt = self.timeXposStart + startTime // xScale
2567
+ w = duration // xScale + 1
2568
+ if w > 10000:
2569
+ w = 10000 # limit width to 10000 pixels!
2570
+ if w < 2:
2571
+ w = 2 # make sure at least one pixel shows
2572
+ dc.DrawRectangle(int(xSt), int(y + yOffset), int(w), int(h))
2573
+ # update bounds to include time bar
2574
+ fullRect.Union(wx.Rect(int(xSt), int(y + yOffset), int(w), int(h)))
2575
+ # draw greyed out bar for any overspill (if routine has a max dur)
2576
+ if useMax and overspill > 0:
2577
+ # use disabled color
2578
+ dc.SetBrush(
2579
+ wx.Brush(colors.app['rt_comp_disabled'], style=thisStyle)
2580
+ )
2581
+ dc.SetPen(
2582
+ wx.Pen(colors.app['rt_comp_disabled'], style=wx.TRANSPARENT)
2583
+ )
2584
+ # draw rest of bar
2585
+ w = overspill // xScale + 1
2586
+ if w > 10000:
2587
+ w = 10000 # limit width to 10000 pixels!
2588
+ if w < 2:
2589
+ w = 2 # make sure at least one pixel shows
2590
+ dc.DrawRectangle(self.timeXposEnd, int(y + yOffset), int(w), int(h))
2591
+ dc.SetIdBounds(id, fullRect)
2592
+
2593
+ def drawSettingsBtn(self, dc, component):
2594
+ # Setup ID
2595
+ id = None
2596
+ for key in self.componentFromID:
2597
+ if self.componentFromID[key] == component:
2598
+ id = key
2599
+ if not id: # then create one and add to the dict
2600
+ id = wx.NewIdRef()
2601
+ self.componentFromID[id] = component
2602
+ dc.SetId(id)
2603
+ # Get settings icon
2604
+ sz = int(self.iconSize/3)
2605
+ thisIcon = icons.ComponentIcon(component, size=sz).bitmap
2606
+ # Some parameters
2607
+ lbl = _translate("Routine settings")
2608
+ padding = 12
2609
+ # Set font
2610
+ fontSize = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT).GetPointSize()
2611
+ self.setFontSize(fontSize, dc)
2612
+ # Calculate extent
2613
+ extent = wx.Rect(
2614
+ x=self.rects['topBar'].Left,
2615
+ y=self.rects['topBar'].Top,
2616
+ width=padding + sz + 6 + self.GetTextExtent(lbl)[0] + padding,
2617
+ height=padding + sz + padding
2618
+ )
2619
+ extent = extent.CenterIn(self.rects['topBar'], dir=wx.VERTICAL)
2620
+ # Get content rect
2621
+ rect = wx.Rect(extent.TopLeft, extent.BottomRight)
2622
+ rect.Deflate(padding)
2623
+ # Draw rect
2624
+ dc.SetPen(wx.Pen(colors.app['panel_bg']))
2625
+ dc.SetBrush(wx.Brush(colors.app['tab_bg']))
2626
+ dc.DrawRoundedRectangle(extent, 6)
2627
+ # Draw button
2628
+ dc.SetTextForeground(
2629
+ wx.Colour(colors.app['text'])
2630
+ )
2631
+ dc.DrawLabel(
2632
+ lbl,
2633
+ image=thisIcon,
2634
+ rect=rect,
2635
+ alignment=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL
2636
+ )
2637
+ # Bind to ID bounds
2638
+ dc.SetIdBounds(id, extent)
2639
+
2640
+ return extent
2641
+
2642
+ def copyCompon(self, event=None, component=None):
2643
+ """This is easy - just take a copy of the component into memory
2644
+ """
2645
+ self.app.copiedCompon = component.copy()
2646
+
2647
+ def pasteCompon(self, event=None, component=None, index=None):
2648
+ # Alias None for component stored in app
2649
+ if component is None and self.app.copiedCompon:
2650
+ component = self.app.copiedCompon
2651
+ # Fail if nothing copied
2652
+ if component is None:
2653
+ return -1
2654
+ exp = self.frame.exp
2655
+ origName = component.params['name'].val
2656
+ defaultName = exp.namespace.makeValid(origName)
2657
+ msg = _translate('New name for copy of "%(copied)s"? [%(default)s]')
2658
+ vals = {'copied': origName, 'default': defaultName}
2659
+ message = msg % vals
2660
+ dlg = wx.TextEntryDialog(self, message=message,
2661
+ caption=_translate('Paste Component'))
2662
+ if dlg.ShowModal() == wx.ID_OK:
2663
+ # Get new name
2664
+ newName = dlg.GetValue()
2665
+ if not newName:
2666
+ newName = defaultName
2667
+ newName = exp.namespace.makeValid(newName)
2668
+ # Create copy of component with new references
2669
+ newCompon = component.copy(
2670
+ exp=exp,
2671
+ parentName=self.routine.name,
2672
+ name=newName
2673
+ )
2674
+ # Add to routine
2675
+ if index is None:
2676
+ self.routine.addComponent(newCompon)
2677
+ else:
2678
+ self.routine.insertComponent(index, newCompon)
2679
+ self.frame.exp.namespace.user.append(newName)
2680
+ # could do redrawRoutines but would be slower?
2681
+ self.redrawRoutine()
2682
+ self.frame.addToUndoStack("PASTE Component `%s`" % newName)
2683
+ dlg.Destroy()
2684
+
2685
+ def editComponentProperties(self, event=None, component=None, openToPage=None):
2686
+ # we got here from a wx.button press (rather than our own drawn icons)
2687
+ if event:
2688
+ componentName = event.EventObject.GetName()
2689
+ component = self.routine.getComponentFromName(componentName)
2690
+ # does this component have a help page?
2691
+ if hasattr(component, 'url'):
2692
+ helpUrl = component.url
2693
+ else:
2694
+ helpUrl = None
2695
+ old_name = component.params['name'].val
2696
+ old_disabled = component.params['disabled'].val
2697
+ # check current timing settings of component (if it changes we
2698
+ # need to update views)
2699
+ initialTimings = component.getStartAndDuration()
2700
+ if 'forceEndRoutine' in component.params \
2701
+ or 'forceEndRoutineOnPress' in component.params:
2702
+ # If component can force end routine, check if it did before
2703
+ initialForce = [component.params[key].val
2704
+ for key in ['forceEndRoutine', 'forceEndRoutineOnPress']
2705
+ if key in component.params]
2706
+ else:
2707
+ initialForce = False
2708
+ # create the dialog
2709
+ if hasattr(component, 'type') and component.type.lower() == 'code':
2710
+ _Dlg = DlgCodeComponentProperties
2711
+ else:
2712
+ _Dlg = DlgComponentProperties
2713
+ dlg = _Dlg(frame=self.frame,
2714
+ element=component,
2715
+ experiment=self.frame.exp, editing=True,
2716
+ openToPage=openToPage)
2717
+ if dlg.OK:
2718
+ # Redraw if force end routine has changed
2719
+ if any(key in component.params for key in ['forceEndRoutine', 'forceEndRoutineOnPress', 'endRoutineOn']):
2720
+ newForce = [component.params[key].val
2721
+ for key in ['forceEndRoutine', 'forceEndRoutineOnPress', 'endRoutineOn']
2722
+ if key in component.params]
2723
+ if initialForce != newForce:
2724
+ self.redrawRoutine() # need to refresh timings section
2725
+ self.Refresh() # then redraw visible
2726
+ self.frame.flowPanel.canvas.draw()
2727
+ # Redraw if timings have changed (or always, if comp was RoutineSettings)
2728
+ if (
2729
+ component.getStartAndDuration() != initialTimings
2730
+ or component.type == "RoutineSettingsComponent"
2731
+ ):
2732
+ self.redrawRoutine() # need to refresh timings section
2733
+ self.Refresh() # then redraw visible
2734
+ self.frame.flowPanel.canvas.draw()
2735
+ # self.frame.flowPanel.Refresh()
2736
+ elif component.name != old_name:
2737
+ if component == self.routine.settings:
2738
+ self.frame.flowPanel.canvas.draw()
2739
+ self.frame._doRenameRoutine(oldName=old_name, newName=component.name)
2740
+ self.redrawRoutine() # need to refresh name
2741
+ elif component.params['disabled'].val != old_disabled:
2742
+ self.redrawRoutine() # need to refresh color
2743
+ self.frame.exp.namespace.remove(old_name)
2744
+ self.frame.exp.namespace.add(component.params['name'].val)
2745
+ self.frame.addToUndoStack("EDIT `%s`" %
2746
+ component.params['name'].val)
2747
+
2748
+ def getSecsPerPixel(self):
2749
+ pixels = float(self.timeXposEnd - self.timeXposStart)
2750
+ return self.getMaxTime()[0] / pixels
2751
+
2752
+
2753
+ class StandaloneRoutineCanvas(scrolledpanel.ScrolledPanel):
2754
+ def __init__(self, parent, routine=None):
2755
+ # Init super
2756
+ scrolledpanel.ScrolledPanel.__init__(
2757
+ self, parent,
2758
+ style=wx.BORDER_NONE)
2759
+ # Store basics
2760
+ self.frame = parent.frame
2761
+ self.app = self.frame.app
2762
+ self.dpi = self.app.dpi
2763
+ self.routine = routine
2764
+ self.helpUrl = self.routine.url
2765
+ self.params = routine.params
2766
+ # Setup sizer
2767
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
2768
+ self.SetSizer(self.sizer)
2769
+ # Setup categ notebook
2770
+ self.warnings = WarningManager(self)
2771
+ self.ctrls = ParamNotebook(self, experiment=self.frame.exp, element=routine)
2772
+ self.ctrls.Bind(EVT_PARAM_CHANGED, self.updateExperiment)
2773
+ self.paramCtrls = self.ctrls.paramCtrls
2774
+ self.sizer.Add(self.ctrls, border=12, proportion=1, flag=wx.ALIGN_CENTER | wx.TOP)
2775
+ # Make buttons
2776
+ self.btnsSizer = wx.BoxSizer(wx.HORIZONTAL)
2777
+ self.helpBtn = utils.HoverButton(self, id=wx.ID_HELP, label=_translate("Help"))
2778
+ self.helpBtn.Bind(wx.EVT_BUTTON, self.onHelp)
2779
+ self.btnsSizer.Add(self.helpBtn, border=6, flag=wx.ALL | wx.EXPAND)
2780
+ self.btnsSizer.AddStretchSpacer(1)
2781
+ # add warnings to sizer
2782
+ self.sizer.Add(self.warnings.output, border=3, flag=wx.EXPAND | wx.ALL)
2783
+ # add buttons to sizer
2784
+ self.sizer.Add(self.btnsSizer, border=3, proportion=0, flag=wx.EXPAND | wx.ALL)
2785
+ # Style
2786
+ self.SetupScrolling(scroll_y=True)
2787
+
2788
+ def _applyAppTheme(self):
2789
+ self.SetBackgroundColour(colors.app['tab_bg'])
2790
+ self.helpBtn._applyAppTheme()
2791
+ self.Refresh()
2792
+ self.Update()
2793
+
2794
+ def updateExperiment(self, evt=None):
2795
+ """Update this routine's saved parameters to what is currently entered"""
2796
+ # Get params in correct formats
2797
+ self.routine.params = self.ctrls.getParams()
2798
+ # Duplicate routine list and iterate through to find this one
2799
+ routines = self.frame.exp.routines.copy()
2800
+ for name, routine in routines.items():
2801
+ if routine == self.routine:
2802
+ # Update the routine dict keys to use the current name for this routine
2803
+ self.frame.exp.routines[self.routine.name] = self.frame.exp.routines.pop(name)
2804
+ # update experiment namespace
2805
+ self.frame.exp.namespace.remove(name)
2806
+ self.frame.exp.namespace.add(self.routine.name)
2807
+ # Redraw the flow panel
2808
+ self.frame.flowPanel.canvas.draw()
2809
+ # Rename this page
2810
+ page = self.frame.routinePanel.GetPageIndex(self)
2811
+ self.frame.routinePanel.SetPageText(page, self.routine.name)
2812
+ # Update save button
2813
+ self.frame.setIsModified(True)
2814
+
2815
+ def onHelp(self, event=None):
2816
+ """Uses self.app.followLink() to self.helpUrl
2817
+ """
2818
+ self.app.followLink(url=self.helpUrl)
2819
+
2820
+ def Validate(self, *args, **kwargs):
2821
+ return self.ctrls.Validate()
2822
+
2823
+
2824
+ class ComponentsPanel(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
2825
+ """Panel containing buttons for each component, sorted by category"""
2826
+
2827
+ class CategoryButton(wx.ToggleButton, handlers.ThemeMixin, HoverMixin):
2828
+ """Button to show/hide a category of components"""
2829
+ def __init__(self, parent, name, cat):
2830
+ if sys.platform == 'darwin':
2831
+ label = name # on macOS the wx.BU_LEFT flag has no effect
2832
+ else:
2833
+ label = " "+name
2834
+ # Initialise button
2835
+ wx.ToggleButton.__init__(self, parent,
2836
+ label=label, size=(-1, 24),
2837
+ style= wx.BORDER_NONE | wx.BU_LEFT)
2838
+ self.parent = parent
2839
+ # Link to category of buttons
2840
+ self.menu = self.parent.catSizers[cat]
2841
+ # # Set own sizer
2842
+ # self.sizer = wx.GridSizer(wx.HORIZONTAL)
2843
+ # self.SetSizer(self.sizer)
2844
+ # # Add icon
2845
+ # self.icon = wx.StaticText(parent=self, label="DOWN")
2846
+ # self.sizer.Add(self.icon, border=5, flag=wx.ALL | wx.ALIGN_RIGHT)
2847
+ # Default states to false
2848
+ self.state = False
2849
+ self.hover = False
2850
+ # Bind toggle function
2851
+ self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleMenu)
2852
+ # Bind hover functions
2853
+ self.SetupHover()
2854
+
2855
+ def ToggleMenu(self, event):
2856
+ # If triggered manually with a bool, treat that as a substitute for event selection
2857
+ if isinstance(event, bool):
2858
+ state = event
2859
+ else:
2860
+ state = event.GetSelection()
2861
+ # Set state
2862
+ self.SetValue(state)
2863
+ # Refresh view (which will show/hide according to this button's state)
2864
+ self.parent.refreshView()
2865
+ # Restyle
2866
+ self.OnHover()
2867
+
2868
+ def _applyAppTheme(self):
2869
+ """Apply app theme to this button"""
2870
+ self.OnHover()
2871
+
2872
+ class ComponentButton(wx.Button, handlers.ThemeMixin):
2873
+ """Button to open component parameters dialog"""
2874
+ def __init__(self, parent, name, comp, cat):
2875
+ self.parent = parent
2876
+ self.component = comp
2877
+ self.category = cat
2878
+ # construct label
2879
+ label = name
2880
+ # remove "Component" from the end
2881
+ for redundant in ['component', 'Component']:
2882
+ label = label.replace(redundant, "")
2883
+ # convert to title case
2884
+ label = st.CaseSwitcher.pascal2title(label)
2885
+ # wrap
2886
+ label = st.wrap(label, 10)
2887
+
2888
+ # Make button
2889
+ wx.Button.__init__(self, parent, wx.ID_ANY,
2890
+ label=label, name=name,
2891
+ size=(68, 68+12*label.count("\n")),
2892
+ style=wx.NO_BORDER)
2893
+ self.SetToolTip(wx.ToolTip(comp.tooltip or name))
2894
+ # Style
2895
+ self._applyAppTheme()
2896
+ # Bind to functions
2897
+ self.Bind(wx.EVT_BUTTON, self.onClick)
2898
+ self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)
2899
+
2900
+ @property
2901
+ def element(self):
2902
+ return self.component
2903
+
2904
+ def onClick(self, evt=None, timeout=None):
2905
+ """Called when a component button is clicked on.
2906
+ """
2907
+ routine = self.parent.frame.routinePanel.getCurrentRoutine()
2908
+ if routine is None:
2909
+ if timeout is not None: # just return, we're testing the UI
2910
+ return
2911
+ # Show a message telling the user there is no routine in the
2912
+ # experiment, making adding a component pointless until they do
2913
+ # so.
2914
+ dlg = wx.MessageDialog(
2915
+ self,
2916
+ _translate(
2917
+ "Cannot add component, experiment has no routines."),
2918
+ _translate("Error"),
2919
+ wx.OK | wx.ICON_ERROR | wx.CENTRE)
2920
+ dlg.ShowModal()
2921
+ dlg.Destroy()
2922
+ return
2923
+
2924
+ page = self.parent.frame.routinePanel.getCurrentPage()
2925
+ comp = self.component(
2926
+ parentName=routine.name,
2927
+ exp=self.parent.frame.exp)
2928
+
2929
+ # does this component have a help page?
2930
+ if hasattr(comp, 'url'):
2931
+ helpUrl = comp.url
2932
+ else:
2933
+ helpUrl = None
2934
+ # create component template
2935
+ if comp.type == 'Code':
2936
+ _Dlg = DlgCodeComponentProperties
2937
+ else:
2938
+ _Dlg = DlgComponentProperties
2939
+ dlg = _Dlg(frame=self.parent.frame,
2940
+ element=comp,
2941
+ experiment=self.parent.frame.exp,
2942
+ timeout=timeout)
2943
+
2944
+ if dlg.OK:
2945
+ # Add to the actual routine
2946
+ routine.addComponent(comp)
2947
+ namespace = self.parent.frame.exp.namespace
2948
+ desiredName = comp.params['name'].val
2949
+ name = comp.params['name'].val = namespace.makeValid(desiredName)
2950
+ namespace.add(name)
2951
+ # update the routine's view with the new component too
2952
+ page.redrawRoutine()
2953
+ self.parent.frame.addToUndoStack(
2954
+ "ADD `%s` to `%s`" % (name, routine.name))
2955
+ return True
2956
+
2957
+ def onRightClick(self, evt):
2958
+ """
2959
+ Defines rightclick behavior within builder view's
2960
+ components panel
2961
+ """
2962
+ # Get fave levels
2963
+ faveLevels = prefs.appDataCfg['builder']['favComponents']
2964
+ # Make menu
2965
+ menu = wx.Menu()
2966
+ if faveLevels[self.component.__name__] > ComponentsPanel.faveThreshold:
2967
+ # If is in favs
2968
+ msg = _translate("Remove from favorites")
2969
+ fun = self.removeFromFavorites
2970
+ else:
2971
+ # If is not in favs
2972
+ msg = _translate("Add to favorites")
2973
+ fun = self.addToFavorites
2974
+ btn = menu.Append(wx.ID_ANY, msg)
2975
+ menu.Bind(wx.EVT_MENU, fun, btn)
2976
+ # Show as popup
2977
+ self.PopupMenu(menu, evt.GetPosition())
2978
+ # Destroy to avoid mem leak
2979
+ menu.Destroy()
2980
+
2981
+ def addToFavorites(self, evt):
2982
+ self.parent.addToFavorites(self.component)
2983
+
2984
+ def removeFromFavorites(self, evt):
2985
+ self.parent.removeFromFavorites(self)
2986
+
2987
+ def _applyAppTheme(self):
2988
+ # Set colors
2989
+ self.SetForegroundColour(colors.app['text'])
2990
+ self.SetBackgroundColour(colors.app['panel_bg'])
2991
+ # Set bitmap
2992
+ icon = icons.ComponentIcon(self.component, size=48)
2993
+ if hasattr(self.component, "beta") and self.component.beta:
2994
+ icon = icon.beta
2995
+ else:
2996
+ icon = icon.bitmap
2997
+ self.SetBitmap(icon)
2998
+ self.SetBitmapCurrent(icon)
2999
+ self.SetBitmapPressed(icon)
3000
+ self.SetBitmapFocus(icon)
3001
+ self.SetBitmapPosition(wx.TOP)
3002
+ # Refresh
3003
+ self.Refresh()
3004
+
3005
+ class RoutineButton(wx.Button, handlers.ThemeMixin):
3006
+ """Button to open component parameters dialog"""
3007
+ def __init__(self, parent, name, rt, cat):
3008
+ self.parent = parent
3009
+ self.routine = rt
3010
+ self.category = cat
3011
+ # construct label
3012
+ label = name
3013
+ # remove "Routine" from the end
3014
+ for redundant in ['routine', 'Routine', "ButtonBox"]:
3015
+ label = label.replace(redundant, "")
3016
+ # convert to title case
3017
+ label = st.CaseSwitcher.pascal2title(label)
3018
+ # wrap
3019
+ label = st.wrap(label, 10)
3020
+ # Make button
3021
+ wx.Button.__init__(self, parent, wx.ID_ANY,
3022
+ label=label, name=name,
3023
+ size=(68, 68+12*label.count("\n")),
3024
+ style=wx.NO_BORDER)
3025
+ self.SetToolTip(wx.ToolTip(rt.tooltip or name))
3026
+ # Style
3027
+ self._applyAppTheme()
3028
+ # Bind to functions
3029
+ self.Bind(wx.EVT_BUTTON, self.onClick)
3030
+ self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)
3031
+
3032
+ @property
3033
+ def element(self):
3034
+ return self.routine
3035
+
3036
+ def onClick(self, evt=None, timeout=None):
3037
+ # Make a routine instance
3038
+ comp = self.routine(exp=self.parent.frame.exp)
3039
+ # Add to the actual routine
3040
+ exp = self.parent.frame.exp
3041
+ namespace = exp.namespace
3042
+ name = comp.params['name'].val = namespace.makeValid(
3043
+ comp.params['name'].val)
3044
+ namespace.add(name)
3045
+ exp.addStandaloneRoutine(name, comp)
3046
+ # update the routine's view with the new routine too
3047
+ self.parent.frame.addToUndoStack(
3048
+ "ADD `%s` to `%s`" % (name, exp.name))
3049
+ # Add a routine page
3050
+ notebook = self.parent.frame.routinePanel
3051
+ notebook.addRoutinePage(name, comp)
3052
+ notebook.setCurrentRoutine(comp)
3053
+
3054
+ def onRightClick(self, evt):
3055
+ """
3056
+ Defines rightclick behavior within builder view's
3057
+ routines panel
3058
+ """
3059
+ return
3060
+
3061
+ def addToFavorites(self, evt):
3062
+ self.parent.addToFavorites(self.routine)
3063
+
3064
+ def removeFromFavorites(self, evt):
3065
+ self.parent.removeFromFavorites(self)
3066
+
3067
+ def _applyAppTheme(self):
3068
+ # Set colors
3069
+ self.SetForegroundColour(colors.app['text'])
3070
+ self.SetBackgroundColour(colors.app['panel_bg'])
3071
+ # Set bitmap
3072
+ icon = icons.ComponentIcon(self.routine, size=48)
3073
+ if hasattr(self.routine, "beta") and self.routine.beta:
3074
+ icon = icon.beta
3075
+ else:
3076
+ icon = icon.bitmap
3077
+ self.SetBitmap(icon)
3078
+ self.SetBitmapCurrent(icon)
3079
+ self.SetBitmapPressed(icon)
3080
+ self.SetBitmapFocus(icon)
3081
+ self.SetBitmapPosition(wx.TOP)
3082
+ # Refresh
3083
+ self.Refresh()
3084
+
3085
+ class FilterDialog(wx.Dialog, handlers.ThemeMixin):
3086
+ def __init__(self, parent, size=(200, 300)):
3087
+ wx.Dialog.__init__(self, parent, size=size)
3088
+ self.parent = parent
3089
+ # Setup sizer
3090
+ self.border = wx.BoxSizer(wx.VERTICAL)
3091
+ self.SetSizer(self.border)
3092
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
3093
+ self.border.Add(self.sizer, border=6, proportion=1, flag=wx.ALL | wx.EXPAND)
3094
+ # Label
3095
+ self.label = wx.StaticText(self, label="Show components which \nwork with...")
3096
+ self.sizer.Add(self.label, border=6, flag=wx.ALL | wx.EXPAND)
3097
+ # Control
3098
+ self.viewCtrl = ToggleButtonArray(self,
3099
+ labels=("PsychoPy (local)", "PsychoJS (online)", "Both", "Any"),
3100
+ values=("PsychoPy", "PsychoJS", "Both", "Any"),
3101
+ multi=False, ori=wx.VERTICAL)
3102
+ self.viewCtrl.Bind(wx.EVT_CHOICE, self.onChange)
3103
+ self.sizer.Add(self.viewCtrl, border=6, flag=wx.ALL | wx.EXPAND)
3104
+ self.viewCtrl.SetValue(prefs.builder['componentFilter'])
3105
+ # OK
3106
+ self.OKbtn = wx.Button(self, id=wx.ID_OK, label=_translate("OK"))
3107
+ self.SetAffirmativeId(wx.ID_OK)
3108
+ self.border.Add(self.OKbtn, border=6, flag=wx.ALL | wx.ALIGN_RIGHT)
3109
+
3110
+ self.Layout()
3111
+ self._applyAppTheme()
3112
+
3113
+ def _applyAppTheme(self):
3114
+ self.SetBackgroundColour(colors.app['panel_bg'])
3115
+ self.label.SetForegroundColour(colors.app['text'])
3116
+
3117
+ def GetValue(self):
3118
+ return self.viewCtrl.GetValue()
3119
+
3120
+ def onChange(self, evt=None):
3121
+ self.parent.filter = prefs.builder['componentFilter'] = self.GetValue()
3122
+ prefs.saveUserPrefs()
3123
+ self.parent.refreshView()
3124
+
3125
+ faveThreshold = 20
3126
+
3127
+ def __init__(self, frame, id=-1):
3128
+ """A panel that displays available components.
3129
+ """
3130
+ self.frame = frame
3131
+ self.app = frame.app
3132
+ self.dpi = self.app.dpi
3133
+ self.prefs = self.app.prefs
3134
+ panelWidth = 3 * (68 + 12) + 12 + 12
3135
+ scrolledpanel.ScrolledPanel.__init__(self,
3136
+ frame,
3137
+ id,
3138
+ size=(panelWidth, 10 * self.dpi),
3139
+ style=wx.BORDER_NONE)
3140
+ # Get filter from prefs
3141
+ self.filter = prefs.builder['componentFilter']
3142
+ # Setup sizer
3143
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
3144
+ self.SetSizer(self.sizer)
3145
+ # Top bar
3146
+ self.topBarSizer = wx.BoxSizer(wx.HORIZONTAL)
3147
+ self.sizer.Add(self.topBarSizer, border=0, flag=wx.ALL | wx.EXPAND)
3148
+ # Add plugins button
3149
+ self.pluginBtn = wx.Button(self, label=_translate("Get more..."), style=wx.BU_EXACTFIT | wx.BORDER_NONE)
3150
+ self.pluginBtn.SetToolTip(_translate("Add new components and features via plugins."))
3151
+ self.topBarSizer.Add(self.pluginBtn, border=3, flag=wx.ALL)
3152
+ self.pluginBtn.Bind(wx.EVT_BUTTON, self.onPluginBtn)
3153
+ # Add filter button
3154
+ self.topBarSizer.AddStretchSpacer(1)
3155
+ self.filterBtn = wx.Button(self, style=wx.BU_EXACTFIT | wx.BORDER_NONE)
3156
+ self.filterBtn.SetToolTip(_translate("Filter components by whether they work with PsychoJS, PsychoPy or both."))
3157
+ self.topBarSizer.Add(self.filterBtn, border=3, flag=wx.ALL)
3158
+ self.filterBtn.Bind(wx.EVT_BUTTON, self.onFilterBtn)
3159
+
3160
+ # Attributes to store handles in
3161
+ self.catLabels = {}
3162
+ self.catSizers = {}
3163
+ self.compButtons = []
3164
+ self.rtButtons = []
3165
+ self.objectHandles = {}
3166
+ # Create buttons
3167
+ self.populate()
3168
+ # Apply filter
3169
+ self.refreshView()
3170
+ # Do sizing
3171
+ self.Fit()
3172
+ # double buffered better rendering except if retina
3173
+ self.SetDoubleBuffered(not self.frame.isRetina)
3174
+
3175
+ def getSortedElements(self):
3176
+ allElements = getAllElements()
3177
+ if "SettingsComponent" in allElements:
3178
+ del allElements['SettingsComponent']
3179
+
3180
+ # Create array to store elements in
3181
+ elements = OrderedDict()
3182
+ # Specify which categories are fixed to start/end
3183
+ firstCats = ['Favorites', 'Stimuli', 'Responses', 'Custom']
3184
+ lastCats = ['I/O', 'Other']
3185
+ # Add categories which are fixed to start
3186
+ for cat in firstCats:
3187
+ elements[cat] = {}
3188
+ # Add unfixed categories
3189
+ for cat in getAllCategories():
3190
+ if cat not in lastCats + firstCats:
3191
+ elements[cat] = {}
3192
+ # Add categories which are fixed to end
3193
+ for cat in lastCats:
3194
+ elements[cat] = {}
3195
+ # Get elements and sort by category
3196
+ for name, emt in allElements.items():
3197
+ for cat in emt.categories:
3198
+ if cat in elements:
3199
+ elements[cat][name] = emt
3200
+ # Assign favorites
3201
+ self.faveLevels = prefs.appDataCfg['builder']['favComponents']
3202
+ for name, emt in allElements.items():
3203
+ # Make sure element has a level
3204
+ if name not in self.faveLevels:
3205
+ self.faveLevels[name] = 0
3206
+ # If it exceeds the threshold, add to favorites
3207
+ if self.faveLevels[name] > self.faveThreshold:
3208
+ elements['Favorites'][name] = emt
3209
+ # Fill in gaps in favorites with defaults
3210
+ faveDefaults = [
3211
+ ('ImageComponent', allElements['ImageComponent']),
3212
+ ('KeyboardComponent', allElements['KeyboardComponent']),
3213
+ ('SoundComponent', allElements['SoundComponent']),
3214
+ ('TextComponent', allElements['TextComponent']),
3215
+ ('MouseComponent', allElements['MouseComponent']),
3216
+ ('SliderComponent', allElements['SliderComponent']),
3217
+ ]
3218
+ while len(elements['Favorites']) < 6:
3219
+ name, emt = faveDefaults.pop(0)
3220
+ if name not in elements['Favorites']:
3221
+ elements['Favorites'][name] = emt
3222
+ self.faveLevels[name] = self.faveThreshold + 1
3223
+
3224
+ return elements
3225
+
3226
+ def populate(self):
3227
+ """
3228
+ Find all component/standalone routine classes and create buttons for each, sorted by category.
3229
+
3230
+ This *can* be called multiple times - already existing buttons are simply detached from their sizer and
3231
+ reattached in the correct place given any changes since last called.
3232
+ """
3233
+ elements = self.getSortedElements()
3234
+
3235
+ # Detach any extant category labels and sizers from main sizer
3236
+ for cat in self.objectHandles:
3237
+ self.sizer.Detach(self.catLabels[cat])
3238
+ self.sizer.Detach(self.catSizers[cat])
3239
+ # Add each category
3240
+ for cat, emts in elements.items():
3241
+ if cat not in self.objectHandles:
3242
+ # Make category sizer
3243
+ self.catSizers[cat] = wx.WrapSizer(orient=wx.HORIZONTAL)
3244
+ # Make category button
3245
+ self.catLabels[cat] = self.CategoryButton(self, name=_translate(cat), cat=cat)
3246
+ # Store category reference
3247
+ self.objectHandles[cat] = {}
3248
+ # Add to sizer
3249
+ self.sizer.Add(self.catLabels[cat], border=3, flag=wx.BOTTOM | wx.EXPAND)
3250
+ self.sizer.Add(self.catSizers[cat], border=6, flag=wx.ALL | wx.ALIGN_CENTER)
3251
+
3252
+ # Detach any extant buttons from sizer
3253
+ for btn in self.objectHandles[cat].values():
3254
+ self.catSizers[cat].Detach(btn)
3255
+ # Add each element
3256
+ for name, emt in emts.items():
3257
+ if name not in self.objectHandles[cat]:
3258
+ # Make appropriate button
3259
+ if issubclass(emt, BaseStandaloneRoutine):
3260
+ emtBtn = self.RoutineButton(self, name=name, rt=emt, cat=cat)
3261
+ self.rtButtons.append(emtBtn)
3262
+ else:
3263
+ emtBtn = self.ComponentButton(self, name=name, comp=emt, cat=cat)
3264
+ self.compButtons.append(emtBtn)
3265
+ # If we're in standalone routine view, disable new component button
3266
+ rtPage = self.frame.routinePanel.getCurrentPage()
3267
+ if rtPage:
3268
+ emtBtn.Enable(
3269
+ not isinstance(rtPage.routine, BaseStandaloneRoutine)
3270
+ )
3271
+ # Store reference by category
3272
+ self.objectHandles[cat][name] = emtBtn
3273
+ # Add to category sizer
3274
+ self.catSizers[cat].Add(self.objectHandles[cat][name], border=3, flag=wx.ALL)
3275
+
3276
+ # Show favourites on startup
3277
+ self.catLabels['Favorites'].ToggleMenu(True)
3278
+
3279
+ def refreshView(self):
3280
+ # Get view value(s)
3281
+ if prefs.builder['componentFilter'] == "Both":
3282
+ view = ["PsychoPy", "PsychoJS"]
3283
+ elif prefs.builder['componentFilter'] == "Any":
3284
+ view = []
3285
+ else:
3286
+ view = [prefs.builder['componentFilter']]
3287
+
3288
+ # Iterate through categories and buttons
3289
+ for cat in self.objectHandles:
3290
+ anyShown = False
3291
+ for name, btn in self.objectHandles[cat].items():
3292
+ shown = True
3293
+ # Get element button refers to
3294
+ if isinstance(btn, self.ComponentButton):
3295
+ emt = btn.component
3296
+ elif isinstance(btn, self.RoutineButton):
3297
+ emt = btn.routine
3298
+ else:
3299
+ emt = None
3300
+ # Check whether button is hidden by filter
3301
+ for v in view:
3302
+ if v not in btn.element.targets:
3303
+ shown = False
3304
+ # Check whether button is hidden by prefs
3305
+ if name in prefs.builder['hiddenComponents'] + alwaysHidden:
3306
+ shown = False
3307
+ # check whether comp/rt indicates itsef as hidden
3308
+ if emt.hidden:
3309
+ shown = False
3310
+ # Check whether button refers to a future comp/rt
3311
+ if hasattr(emt, "version"):
3312
+ ver = parseVersionSafely(emt.version)
3313
+ if ver > psychopyVersion:
3314
+ shown = False
3315
+ # Show/hide button
3316
+ btn.Show(shown)
3317
+ # Count state towards category
3318
+ anyShown = anyShown or shown
3319
+ # Only show category button if there are some buttons
3320
+ self.catLabels[cat].Show(anyShown)
3321
+ # If comp button is set to hide, hide all regardless
3322
+ if not self.catLabels[cat].GetValue():
3323
+ self.catSizers[cat].ShowItems(False)
3324
+
3325
+ # Do sizing
3326
+ self.Layout()
3327
+ self.SetupScrolling(scrollToTop=False)
3328
+
3329
+ def _applyAppTheme(self, target=None):
3330
+ # Style component panel
3331
+ self.SetForegroundColour(colors.app['text'])
3332
+ self.SetBackgroundColour(colors.app['panel_bg'])
3333
+ # Style category labels
3334
+ for lbl in self.catLabels:
3335
+ self.catLabels[lbl].SetForegroundColour(colors.app['text'])
3336
+ # Style filter button
3337
+ self.filterBtn.SetBackgroundColour(colors.app['panel_bg'])
3338
+ icon = icons.ButtonIcon("filter", size=16).bitmap
3339
+ self.filterBtn.SetBitmap(icon)
3340
+ self.filterBtn.SetBitmapCurrent(icon)
3341
+ self.filterBtn.SetBitmapPressed(icon)
3342
+ self.filterBtn.SetBitmapFocus(icon)
3343
+ # Style plugin button
3344
+ self.pluginBtn.SetBackgroundColour(colors.app['panel_bg'])
3345
+ self.pluginBtn.SetForegroundColour(colors.app['text'])
3346
+ icon = icons.ButtonIcon("plus", size=16).bitmap
3347
+ self.pluginBtn.SetBitmap(icon)
3348
+ self.pluginBtn.SetBitmapCurrent(icon)
3349
+ self.pluginBtn.SetBitmapPressed(icon)
3350
+ self.pluginBtn.SetBitmapFocus(icon)
3351
+
3352
+ self.Refresh()
3353
+
3354
+ def addToFavorites(self, comp):
3355
+ name = comp.__name__
3356
+ # Mark component as a favorite
3357
+ self.faveLevels[name] = self.faveThreshold + 1
3358
+ # Repopulate
3359
+ self.populate()
3360
+ # Do sizing
3361
+ self.Layout()
3362
+
3363
+ def removeFromFavorites(self, button):
3364
+ comp = button.component
3365
+ name = comp.__name__
3366
+ # Unmark component as favorite
3367
+ self.faveLevels[name] = 0
3368
+ # Remove button from favorites menu
3369
+ button.Destroy()
3370
+ del self.objectHandles["Favorites"][name]
3371
+ # Do sizing
3372
+ self.Layout()
3373
+
3374
+ def enableComponents(self, enable=True):
3375
+ for button in self.compButtons:
3376
+ button.Enable(enable)
3377
+ self.Update()
3378
+
3379
+ def onFilterBtn(self, evt=None):
3380
+ dlg = self.FilterDialog(self)
3381
+ dlg.ShowModal()
3382
+
3383
+ def onPluginBtn(self, evt=None):
3384
+ dlg = psychopy.app.plugin_manager.dialog.EnvironmentManagerDlg(self)
3385
+ dlg.Show()
3386
+ # Do post-close checks
3387
+ dlg.onClose()
3388
+
3389
+
3390
+ class ReadmeFrame(wx.Frame, handlers.ThemeMixin):
3391
+ """Defines construction of the Readme Frame"""
3392
+
3393
+ def __init__(self, parent, filename=None):
3394
+ """
3395
+ A frame for presenting/loading/saving readme files
3396
+ """
3397
+ self.parent = parent
3398
+ try:
3399
+ title = "%s readme" % (parent.exp.name)
3400
+ except AttributeError:
3401
+ title = "readme"
3402
+ self._fileLastModTime = None
3403
+ pos = wx.Point(parent.Position[0] + 80, parent.Position[1] + 80)
3404
+ _style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT
3405
+ wx.Frame.__init__(self, parent, title=title,
3406
+ size=(600, 500), pos=pos, style=_style)
3407
+ # Setup sizer
3408
+ self.sizer = wx.BoxSizer()
3409
+ self.SetSizer(self.sizer)
3410
+
3411
+ self.Bind(wx.EVT_CLOSE, self.onClose)
3412
+ self.Hide()
3413
+ # create icon
3414
+ if sys.platform == 'darwin':
3415
+ pass # doesn't work and not necessary - handled by app bundle
3416
+ else:
3417
+ iconFile = os.path.join(parent.paths['resources'], 'coder.ico')
3418
+ if os.path.isfile(iconFile):
3419
+ self.SetIcon(wx.Icon(iconFile, wx.BITMAP_TYPE_ICO))
3420
+ self.ctrl = utils.MarkdownCtrl(self, file=filename, style=wx.BOTTOM)
3421
+ self.sizer.Add(self.ctrl, border=6, proportion=1, flag=wx.ALL | wx.EXPAND)
3422
+
3423
+ def show(self, value=True):
3424
+ self.Show(value)
3425
+ if value:
3426
+ self._applyAppTheme()
3427
+
3428
+ def _applyAppTheme(self):
3429
+ from psychopy.app.themes import fonts
3430
+ self.SetBackgroundColour(fonts.coderTheme.base.backColor)
3431
+ self.ctrl.SetBackgroundColour(fonts.coderTheme.base.backColor)
3432
+ self.Update()
3433
+ self.Refresh()
3434
+
3435
+ def onClose(self, evt=None):
3436
+ """
3437
+ Defines behavior on close of the Readme Frame
3438
+ """
3439
+ self.parent.readmeFrame = None
3440
+ self.Destroy()
3441
+
3442
+ def makeMenus(self):
3443
+ """Produces menus for the Readme Frame"""
3444
+
3445
+ # ---Menus---#000000#FFFFFF-------------------------------------------
3446
+ menuBar = wx.MenuBar()
3447
+ # ---_file---#000000#FFFFFF-------------------------------------------
3448
+ self.fileMenu = wx.Menu()
3449
+ menuBar.Append(self.fileMenu, _translate('&File'))
3450
+ menu = self.fileMenu
3451
+ keys = self.parent.app.keys
3452
+ menu.Append(wx.ID_EDIT, _translate("Edit"))
3453
+ self.Bind(wx.EVT_MENU, self.fileEdit, id=wx.ID_EDIT)
3454
+ menu.Append(wx.ID_CLOSE,
3455
+ _translate("&Close readme\t%s") % keys['close'])
3456
+ item = self.Bind(wx.EVT_MENU, self.toggleVisible, id=wx.ID_CLOSE)
3457
+ item = menu.Append(-1,
3458
+ _translate("&Toggle readme\t%s") % keys[
3459
+ 'toggleReadme'],
3460
+ _translate("Toggle Readme"))
3461
+ self.Bind(wx.EVT_MENU, self.toggleVisible, item)
3462
+ self.SetMenuBar(menuBar)
3463
+
3464
+ def setFile(self, filename):
3465
+ """Sets the readme file found with current builder experiment"""
3466
+ self.filename = self.ctrl.file = filename
3467
+ self.expName = self.parent.exp.getExpName()
3468
+ if not self.expName:
3469
+ self.expName = "untitled"
3470
+ # check we can read
3471
+ if filename is None: # check if we can write to the directory
3472
+ return False
3473
+ # Path-ise file
3474
+ filename = Path(filename)
3475
+ if not filename.is_file():
3476
+ filename.write_text("")
3477
+ self.filename = filename
3478
+ return False
3479
+ elif not os.access(filename, os.R_OK):
3480
+ msg = "Found readme file (%s) no read permissions"
3481
+ logging.warning(msg % filename)
3482
+ return False
3483
+ # attempt to open
3484
+ try:
3485
+ f = codecs.open(filename, 'r', 'utf-8-sig')
3486
+ except IOError as err:
3487
+ msg = ("Found readme file for %s and appear to have"
3488
+ " permissions, but can't open")
3489
+ logging.warning(msg % self.expName)
3490
+ logging.warning(err)
3491
+ return False
3492
+ # attempt to read
3493
+ try:
3494
+ readmeText = f.read().replace("\r\n", "\n")
3495
+ except Exception:
3496
+ msg = ("Opened readme file for %s it but failed to read it "
3497
+ "(not text/unicode?)")
3498
+ logging.error(msg % self.expName)
3499
+ return False
3500
+ f.close()
3501
+ self._fileLastModTime = os.path.getmtime(filename)
3502
+ self.ctrl.setValue(readmeText)
3503
+ self.SetTitle("readme (%s)" % self.expName)
3504
+
3505
+ def refresh(self, evt=None):
3506
+ if hasattr(self, 'filename'):
3507
+ self.setFile(self.filename)
3508
+
3509
+ def fileEdit(self, evt=None):
3510
+ self.parent.app.showCoder()
3511
+ coder = self.parent.app.coder
3512
+ if not self.filename:
3513
+ self.parent.updateReadme()
3514
+ coder.fileOpen(filename=self.filename)
3515
+ # Close README window
3516
+ self.Close()
3517
+
3518
+ def fileSave(self, evt=None):
3519
+ """Defines save behavior for readme frame"""
3520
+ mtime = os.path.getmtime(self.filename)
3521
+ if self._fileLastModTime and mtime > self._fileLastModTime:
3522
+ logging.warning(
3523
+ 'readme file has been changed by another program?')
3524
+ txt = self.rawText
3525
+ with codecs.open(self.filename, 'w', 'utf-8-sig') as f:
3526
+ f.write(txt)
3527
+
3528
+ def toggleVisible(self, evt=None):
3529
+ """Defines visibility toggle for readme frame"""
3530
+ if self.IsShown():
3531
+ self.Hide()
3532
+ else:
3533
+ self.Show()
3534
+
3535
+
3536
+ class FlowPanel(wx.Panel, handlers.ThemeMixin):
3537
+ def __init__(self, frame, id=-1):
3538
+ wx.Panel.__init__(self, parent=frame)
3539
+ # setup sizer
3540
+ self.sizer = wx.BoxSizer(wx.HORIZONTAL)
3541
+ self.SetSizer(self.sizer)
3542
+ # buttons panel
3543
+ self.btnPanel = ThemedPanel(self)
3544
+ self.btnPanel.sizer = wx.BoxSizer(wx.VERTICAL)
3545
+ self.btnPanel.SetSizer(self.btnPanel.sizer)
3546
+ self.sizer.Add(self.btnPanel, border=6, flag=wx.EXPAND | wx.ALL)
3547
+ # canvas
3548
+ self.canvas = FlowCanvas(parent=self, frame=frame)
3549
+ self.sizer.Add(self.canvas, border=0, proportion=1, flag=wx.EXPAND | wx.ALL)
3550
+ # add routine button
3551
+ self.btnInsertRoutine = self.canvas.btnInsertRoutine = HoverButton(
3552
+ self.btnPanel, -1, _translate('Insert Routine'), size=(120, 50),
3553
+ style=wx.BORDER_NONE
3554
+ )
3555
+ self.btnInsertRoutine.Bind(wx.EVT_BUTTON, self.canvas.onInsertRoutine)
3556
+ self.btnPanel.sizer.Add(self.btnInsertRoutine, border=6, flag=wx.EXPAND | wx.ALL)
3557
+ # add loop button
3558
+ self.btnInsertLoop = self.canvas.btnInsertLoop = HoverButton(
3559
+ self.btnPanel, -1, _translate('Insert Loop'), size=(120, 50),
3560
+ style=wx.BORDER_NONE
3561
+ )
3562
+ self.btnInsertLoop.Bind(wx.EVT_BUTTON, self.canvas.setLoopPoint1)
3563
+ self.btnPanel.sizer.Add(self.btnInsertLoop, border=6, flag=wx.EXPAND | wx.ALL)
3564
+ # align buttons to top
3565
+ self.btnPanel.sizer.AddStretchSpacer(1)
3566
+
3567
+ self.Layout()
3568
+
3569
+ def _applyAppTheme(self):
3570
+ self.SetBackgroundColour(colors.app['panel_bg'])
3571
+ self.btnPanel.SetBackgroundColour(colors.app['panel_bg'])
3572
+
3573
+ self.Refresh()
3574
+
3575
+
3576
+ class FlowCanvas(wx.ScrolledWindow, handlers.ThemeMixin):
3577
+
3578
+ def __init__(self, parent, frame, id=-1):
3579
+ """A panel that shows how the routines will fit together
3580
+ """
3581
+ self.frame = frame
3582
+ self.parent = parent
3583
+ self.app = frame.app
3584
+ self.dpi = self.app.dpi
3585
+ wx.ScrolledWindow.__init__(self, parent, id,
3586
+ style=wx.HSCROLL | wx.VSCROLL | wx.BORDER_NONE)
3587
+ self.needUpdate = True
3588
+ self.maxWidth = 50 * self.dpi
3589
+ self.maxHeight = 2 * self.dpi
3590
+ self.mousePos = None
3591
+ # if we're adding a loop or routine then add spots to timeline
3592
+ # self.drawNearestRoutinePoint = True
3593
+ # self.drawNearestLoopPoint = False
3594
+ # lists the x-vals of points to draw, eg loop locations:
3595
+ self.pointsToDraw = []
3596
+ # for flowSize, showLoopInfoInFlow:
3597
+ self.appData = self.app.prefs.appData
3598
+
3599
+ # self.SetAutoLayout(True)
3600
+ self.SetScrollRate(self.dpi // 16, self.dpi // 16)
3601
+
3602
+ # create a PseudoDC to record our drawing
3603
+ self.pdc = PseudoDC()
3604
+ if Version(wx.__version__) < Version('4.0.0a1'):
3605
+ self.pdc.DrawRoundedRectangle = self.pdc.DrawRoundedRectangleRect
3606
+ self.pen_cache = {}
3607
+ self.brush_cache = {}
3608
+ # vars for handling mouse clicks
3609
+ self.hitradius = 5
3610
+ self.dragid = -1
3611
+ self.entryPointPosList = []
3612
+ self.entryPointIDlist = []
3613
+ self.gapsExcluded = []
3614
+ # mode can also be 'loopPoint1','loopPoint2','routinePoint'
3615
+ self.mode = 'normal'
3616
+ self.insertingRoutine = ""
3617
+
3618
+ # for the context menu use the ID of the drawn icon to retrieve
3619
+ # the component (loop or routine)
3620
+ self.componentFromID = {}
3621
+ self.contextMenuLabels = {
3622
+ 'remove': _translate('remove'),
3623
+ 'rename': _translate('rename')}
3624
+ self.contextMenuItems = ['remove', 'rename']
3625
+ self.contextItemFromID = {}
3626
+ self.contextIDFromItem = {}
3627
+ for item in self.contextMenuItems:
3628
+ id = wx.NewIdRef()
3629
+ self.contextItemFromID[id] = item
3630
+ self.contextIDFromItem[item] = id
3631
+
3632
+ # self.btnInsertRoutine = wx.Button(self,-1,
3633
+ # 'Insert Routine', pos=(10,10))
3634
+ # self.btnInsertLoop = wx.Button(self,-1,'Insert Loop', pos=(10,30))
3635
+
3636
+ # use self.appData['flowSize'] to index a tuple to get a specific
3637
+ # value, eg: (4,6,8)[self.appData['flowSize']]
3638
+ self.flowMaxSize = 2 # upper limit on increaseSize
3639
+
3640
+ # bind events
3641
+ self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
3642
+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
3643
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
3644
+
3645
+ idClear = wx.NewIdRef()
3646
+ self.Bind(wx.EVT_MENU, self.clearMode, id=idClear)
3647
+ aTable = wx.AcceleratorTable([
3648
+ (wx.ACCEL_NORMAL, wx.WXK_ESCAPE, idClear)
3649
+ ])
3650
+ self.SetAcceleratorTable(aTable)
3651
+
3652
+ # double buffered better rendering except if retina
3653
+ self.SetDoubleBuffered(not self.frame.isRetina)
3654
+
3655
+ def _applyAppTheme(self, target=None):
3656
+ """Apply any changes which have been made to the theme since panel was last loaded"""
3657
+ # Set background
3658
+ self.SetBackgroundColour(colors.app['panel_bg'])
3659
+
3660
+ self.draw()
3661
+
3662
+ def clearMode(self, event=None):
3663
+ """If we were in middle of doing something (like inserting routine)
3664
+ then end it, allowing user to cancel
3665
+ """
3666
+ self.mode = 'normal'
3667
+ self.insertingRoutine = None
3668
+ for id in self.entryPointIDlist:
3669
+ self.pdc.RemoveId(id)
3670
+ self.entryPointPosList = []
3671
+ self.entryPointIDlist = []
3672
+ self.gapsExcluded = []
3673
+ self.draw()
3674
+ self.frame.SetStatusText("")
3675
+ self.btnInsertRoutine.SetLabel(_translate('Insert Routine'))
3676
+ self.btnInsertRoutine.Update()
3677
+ self.btnInsertLoop.SetLabel(_translate('Insert Loop'))
3678
+ self.btnInsertRoutine.Update()
3679
+
3680
+ def ConvertEventCoords(self, event):
3681
+ xView, yView = self.GetViewStart()
3682
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
3683
+ return (event.GetX() + (xView * xDelta),
3684
+ event.GetY() + (yView * yDelta))
3685
+
3686
+ def OffsetRect(self, r):
3687
+ """Offset the rectangle, r, to appear in the given position
3688
+ in the window
3689
+ """
3690
+ xView, yView = self.GetViewStart()
3691
+ xDelta, yDelta = self.GetScrollPixelsPerUnit()
3692
+ r.Offset((-(xView * xDelta), -(yView * yDelta)))
3693
+
3694
+ def onInsertRoutine(self, evt):
3695
+ """For when the insert Routine button is pressed - bring up
3696
+ dialog and present insertion point on flow line.
3697
+ see self.insertRoutine() for further info
3698
+ """
3699
+ if self.mode.startswith('loopPoint'):
3700
+ self.clearMode()
3701
+ elif self.mode == 'routine':
3702
+ # clicked again with label now being "Cancel..."
3703
+ self.clearMode()
3704
+ return
3705
+ self.frame.SetStatusText(_translate(
3706
+ "Select a Routine to insert (Esc to exit)"))
3707
+ menu = wx.Menu()
3708
+ self.routinesFromID = {}
3709
+ id = wx.NewIdRef()
3710
+ menu.Append(id, '(new)')
3711
+ self.routinesFromID[id] = '(new)'
3712
+ menu.Bind(wx.EVT_MENU, self.insertNewRoutine, id=id)
3713
+ flow = self.frame.exp.flow
3714
+ for name, routine in self.frame.exp.routines.items():
3715
+ id = wx.NewIdRef()
3716
+ item = menu.Append(id, name)
3717
+ # Enable / disable each routine's button according to limits
3718
+ if hasattr(routine, "limit"):
3719
+ limitProgress = 0
3720
+ for rt in flow:
3721
+ limitProgress += int(isinstance(rt, type(routine)))
3722
+ item.Enable(limitProgress < routine.limit or routine in flow)
3723
+ self.routinesFromID[id] = name
3724
+ menu.Bind(wx.EVT_MENU, self.onInsertRoutineSelect, id=id)
3725
+ self.PopupMenu(menu)
3726
+ menu.Bind(wx.EVT_MENU_CLOSE, self.clearMode)
3727
+ menu.Destroy() # destroy to avoid mem leak
3728
+
3729
+ def insertNewRoutine(self, event):
3730
+ """selecting (new) is a short-cut for:
3731
+ make new routine, insert it into the flow
3732
+ """
3733
+ newRoutine = self.frame.routinePanel.createNewRoutine()
3734
+ if newRoutine:
3735
+ self.routinesFromID[event.GetId()] = newRoutine
3736
+ self.onInsertRoutineSelect(event)
3737
+ else:
3738
+ self.clearMode()
3739
+
3740
+ def onInsertRoutineSelect(self, event):
3741
+ """User has selected a routine to be entered so bring up the
3742
+ entrypoint marker and await mouse button press.
3743
+ see self.insertRoutine() for further info
3744
+ """
3745
+ self.mode = 'routine'
3746
+ self.btnInsertRoutine.SetLabel(_translate('CANCEL Insert'))
3747
+ self.frame.SetStatusText(_translate(
3748
+ 'Click where you want to insert the Routine, or CANCEL insert.'))
3749
+ self.insertingRoutine = self.routinesFromID[event.GetId()]
3750
+ x = self.getNearestGapPoint(0)
3751
+ self.drawEntryPoints([x])
3752
+
3753
+ def insertRoutine(self, ii):
3754
+ """Insert a routine into the Flow knowing its name and location
3755
+
3756
+ onInsertRoutine() the button has been pressed so present menu
3757
+ onInsertRoutineSelect() user selected the name so present entry points
3758
+ OnMouse() user has selected a point on the timeline to insert entry
3759
+
3760
+ """
3761
+ rtn = self.frame.exp.routines[self.insertingRoutine]
3762
+ self.frame.exp.flow.addRoutine(rtn, ii)
3763
+ self.frame.addToUndoStack("ADD Routine `%s`" % rtn.name)
3764
+ # reset flow drawing (remove entry point)
3765
+ self.clearMode()
3766
+ # enable/disable add loop button
3767
+ self.btnInsertLoop.Enable(bool(len(self.frame.exp.flow)))
3768
+
3769
+ def setLoopPoint1(self, evt=None):
3770
+ """Someone pushed the insert loop button.
3771
+ Fetch the dialog
3772
+ """
3773
+ if self.mode == 'routine':
3774
+ self.clearMode()
3775
+ # clicked again, label is "Cancel..."
3776
+ elif self.mode.startswith('loopPoint'):
3777
+ self.clearMode()
3778
+ return
3779
+ self.btnInsertLoop.SetLabel(_translate('CANCEL insert'))
3780
+ self.mode = 'loopPoint1'
3781
+ self.frame.SetStatusText(_translate(
3782
+ 'Click where you want the loop to start/end, or CANCEL insert.'))
3783
+ x = self.getNearestGapPoint(0)
3784
+ self.drawEntryPoints([x])
3785
+
3786
+ def setLoopPoint2(self, evt=None):
3787
+ """We have the location of the first point, waiting to get the second
3788
+ """
3789
+ self.mode = 'loopPoint2'
3790
+ self.frame.SetStatusText(_translate(
3791
+ 'Click the other end for the loop'))
3792
+ thisPos = self.entryPointPosList[0]
3793
+ self.gapsExcluded = [thisPos]
3794
+ self.gapsExcluded.extend(self.getGapPointsCrossingStreams(thisPos))
3795
+ # is there more than one available point
3796
+ diff = wx.GetMousePosition()[0] - self.GetScreenPosition()[0]
3797
+ x = self.getNearestGapPoint(diff, exclude=self.gapsExcluded)
3798
+ self.drawEntryPoints([self.entryPointPosList[0], x])
3799
+ nAvailableGaps = len(self.gapMidPoints) - len(self.gapsExcluded)
3800
+ if nAvailableGaps == 1:
3801
+ self.insertLoop() # there's only one place - use it
3802
+
3803
+ def insertLoop(self, evt=None):
3804
+ # bring up listbox to choose the routine to add, and / or a new one
3805
+ loopDlg = DlgLoopProperties(frame=self.frame,
3806
+ helpUrl=self.app.urls['builder.loops'])
3807
+ startII = self.gapMidPoints.index(min(self.entryPointPosList))
3808
+ endII = self.gapMidPoints.index(max(self.entryPointPosList))
3809
+ if loopDlg.OK:
3810
+ handler = loopDlg.currentHandler
3811
+ self.frame.exp.flow.addLoop(handler,
3812
+ startPos=startII, endPos=endII)
3813
+ action = "ADD Loop `%s` to Flow" % handler.params['name'].val
3814
+ self.frame.addToUndoStack(action)
3815
+ self.clearMode()
3816
+ self.draw()
3817
+
3818
+ def increaseSize(self, event=None):
3819
+ if self.appData['flowSize'] == self.flowMaxSize:
3820
+ self.appData['showLoopInfoInFlow'] = True
3821
+ self.appData['flowSize'] = min(
3822
+ self.flowMaxSize, self.appData['flowSize'] + 1)
3823
+ self.clearMode() # redraws
3824
+
3825
+ def decreaseSize(self, event=None):
3826
+ if self.appData['flowSize'] == 0:
3827
+ self.appData['showLoopInfoInFlow'] = False
3828
+ self.appData['flowSize'] = max(0, self.appData['flowSize'] - 1)
3829
+ self.clearMode() # redraws
3830
+
3831
+ def editLoopProperties(self, event=None, loop=None):
3832
+ # add routine points to the timeline
3833
+ self.setDrawPoints('loops')
3834
+ self.draw()
3835
+ if 'conditions' in loop.params:
3836
+ condOrig = loop.params['conditions'].val
3837
+ condFileOrig = loop.params['conditionsFile'].val
3838
+ title = loop.params['name'].val + ' Properties'
3839
+ loopDlg = DlgLoopProperties(frame=self.frame,
3840
+ helpUrl=self.app.urls['builder.loops'],
3841
+ title=title, loop=loop)
3842
+ if loopDlg.OK:
3843
+ prevLoop = loop
3844
+ if loopDlg.params['loopType'].val == 'staircase':
3845
+ loop = loopDlg.stairHandler
3846
+ elif loopDlg.params['loopType'].val == 'interleaved staircases':
3847
+ loop = loopDlg.multiStairHandler
3848
+ else:
3849
+ # ['random','sequential', 'fullRandom', ]
3850
+ loop = loopDlg.trialHandler
3851
+ # if the loop is a whole new class then we can't just update the
3852
+ # params
3853
+ if loop.getType() != prevLoop.getType():
3854
+ # get indices for start and stop points of prev loop
3855
+ flow = self.frame.exp.flow
3856
+ # find the index of the initiator
3857
+ startII = flow.index(prevLoop.initiator)
3858
+ # minus one because initiator will have been deleted
3859
+ endII = flow.index(prevLoop.terminator) - 1
3860
+ # remove old loop completely
3861
+ flow.removeComponent(prevLoop)
3862
+ # finally insert the new loop
3863
+ flow.addLoop(loop, startII, endII)
3864
+ self.frame.addToUndoStack("EDIT Loop `%s`" %
3865
+ (loop.params['name'].val))
3866
+ elif 'conditions' in loop.params:
3867
+ loop.params['conditions'].val = condOrig
3868
+ loop.params['conditionsFile'].val = condFileOrig
3869
+ # remove the points from the timeline
3870
+ self.setDrawPoints(None)
3871
+ self.draw()
3872
+
3873
+ def OnMouse(self, event):
3874
+ x, y = self.ConvertEventCoords(event)
3875
+ handlerTypes = ('StairHandler', 'TrialHandler', 'MultiStairHandler')
3876
+ if self.mode == 'normal':
3877
+ if event.LeftDown():
3878
+ icons = self.pdc.FindObjectsByBBox(x, y)
3879
+ for thisIcon in icons:
3880
+ # might intersect several and only one has a callback
3881
+ if thisIcon in self.componentFromID:
3882
+ comp = self.componentFromID[thisIcon]
3883
+ if comp.getType() in handlerTypes:
3884
+ self.editLoopProperties(loop=comp)
3885
+ if comp.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
3886
+ self.frame.routinePanel.setCurrentRoutine(
3887
+ routine=comp)
3888
+ elif event.RightDown():
3889
+ icons = self.pdc.FindObjectsByBBox(x, y)
3890
+ # todo: clean-up remove `comp`, its unused
3891
+ comp = None
3892
+ for thisIcon in icons:
3893
+ # might intersect several and only one has a callback
3894
+ if thisIcon in self.componentFromID:
3895
+ # loop through comps looking for Routine, or a Loop if
3896
+ # no routine
3897
+ thisComp = self.componentFromID[thisIcon]
3898
+ if thisComp.getType() in handlerTypes:
3899
+ comp = thisComp # unused
3900
+ icon = thisIcon
3901
+ if thisComp.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
3902
+ comp = thisComp
3903
+ icon = thisIcon
3904
+ break # we've found a Routine so stop looking
3905
+ self.frame.routinePanel.setCurrentRoutine(comp)
3906
+ try:
3907
+ self._menuComponentID = icon
3908
+ xy = wx.Point(event.X + self.parent.GetPosition()[0],
3909
+ event.Y + self.parent.GetPosition()[1])
3910
+ self.showContextMenu(self._menuComponentID, xy=xy)
3911
+ except UnboundLocalError:
3912
+ # right click but not on an icon
3913
+ # might as well do something
3914
+ self.Refresh()
3915
+ elif event.Moving():
3916
+ icons = self.pdc.FindObjectsByBBox(x, y)
3917
+ if not icons:
3918
+ self.frame.SetStatusText("")
3919
+ for thisIcon in icons:
3920
+ # might intersect several and only one has a callback
3921
+ if thisIcon in self.componentFromID:
3922
+ comp = self.componentFromID[thisIcon]
3923
+ # indicate hover target in bottom bar
3924
+ if comp.getType() in handlerTypes:
3925
+ self.frame.SetStatusText(f"Loop ({comp.getType()}): {comp.name}")
3926
+ else:
3927
+ self.frame.SetStatusText(f"{comp.getType()}: {comp.name}")
3928
+ elif self.mode == 'routine':
3929
+ if event.LeftDown():
3930
+ pt = self.entryPointPosList[0]
3931
+ self.insertRoutine(ii=self.gapMidPoints.index(pt))
3932
+ else: # move spot if needed
3933
+ point = self.getNearestGapPoint(mouseX=x)
3934
+ self.drawEntryPoints([point])
3935
+ elif self.mode == 'loopPoint1':
3936
+ if event.LeftDown():
3937
+ self.setLoopPoint2()
3938
+ else: # move spot if needed
3939
+ point = self.getNearestGapPoint(mouseX=x)
3940
+ self.drawEntryPoints([point])
3941
+ elif self.mode == 'loopPoint2':
3942
+ if event.LeftDown():
3943
+ self.insertLoop()
3944
+ else: # move spot if needed
3945
+ point = self.getNearestGapPoint(mouseX=x,
3946
+ exclude=self.gapsExcluded)
3947
+ self.drawEntryPoints([self.entryPointPosList[0], point])
3948
+
3949
+ def OnScroll(self, evt):
3950
+ xy = self.GetViewStart()
3951
+ delta = int(evt.WheelRotation * self.dpi / 1600)
3952
+ if evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:
3953
+ # scroll vertically
3954
+ self.Scroll(xy[0], xy[1] - delta)
3955
+ if evt.GetWheelAxis() == wx.MOUSE_WHEEL_HORIZONTAL:
3956
+ # scroll horizontally
3957
+ self.Scroll(xy[0] + delta, xy[1])
3958
+
3959
+ def getNearestGapPoint(self, mouseX, exclude=()):
3960
+ """Get gap that is nearest to a particular mouse location
3961
+ """
3962
+ d = 1000000000
3963
+ nearest = None
3964
+ for point in self.gapMidPoints:
3965
+ if point in exclude:
3966
+ continue
3967
+ if (point - mouseX) ** 2 < d:
3968
+ d = (point - mouseX) ** 2
3969
+ nearest = point
3970
+ return nearest
3971
+
3972
+ def getGapPointsCrossingStreams(self, gapPoint):
3973
+ """For a given gap point, identify the gap points that are
3974
+ excluded by crossing a loop line
3975
+ """
3976
+ gapArray = numpy.array(self.gapMidPoints)
3977
+ nestLevels = numpy.array(self.gapNestLevels)
3978
+ thisLevel = nestLevels[gapArray == gapPoint]
3979
+ invalidGaps = (gapArray[nestLevels != thisLevel]).tolist()
3980
+ return invalidGaps
3981
+
3982
+ def showContextMenu(self, component, xy):
3983
+ menu = wx.Menu()
3984
+ # get ID
3985
+ # the ID is also the index to the element in the flow list
3986
+ compID = self._menuComponentID
3987
+ flow = self.frame.exp.flow
3988
+ component = flow[compID]
3989
+ compType = component.getType()
3990
+ if compType == 'Routine':
3991
+ for item in self.contextMenuItems:
3992
+ id = self.contextIDFromItem[item]
3993
+ menu.Append(id, self.contextMenuLabels[item])
3994
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
3995
+ self.frame.PopupMenu(menu, xy)
3996
+ # destroy to avoid mem leak:
3997
+ menu.Destroy()
3998
+ else:
3999
+ for item in self.contextMenuItems:
4000
+ if item == 'rename':
4001
+ continue
4002
+ id = self.contextIDFromItem[item]
4003
+ menu.Append(id, self.contextMenuLabels[item])
4004
+ menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
4005
+ self.frame.PopupMenu(menu, xy)
4006
+ # destroy to avoid mem leak:
4007
+ menu.Destroy()
4008
+
4009
+ def onContextSelect(self, event):
4010
+ """Perform a given action on the component chosen
4011
+ """
4012
+ # get ID
4013
+ op = self.contextItemFromID[event.GetId()]
4014
+ # the ID is also the index to the element in the flow list
4015
+ compID = self._menuComponentID
4016
+ flow = self.frame.exp.flow
4017
+ component = flow[compID]
4018
+ # if we have a Loop Initiator, remove the whole loop
4019
+ if component.getType() == 'LoopInitiator':
4020
+ component = component.loop
4021
+ if op == 'remove':
4022
+ self.removeComponent(component, compID)
4023
+ self.frame.addToUndoStack(
4024
+ "REMOVE `%s` from Flow" % component.params['name'])
4025
+ if op == 'rename':
4026
+ self.frame.renameRoutine(component)
4027
+
4028
+ def removeComponent(self, component, compID):
4029
+ """Remove either a Routine or a Loop from the Flow
4030
+ """
4031
+ flow = self.frame.exp.flow
4032
+ if component.getType() in ['Routine'] + list(getAllStandaloneRoutines()):
4033
+ # check whether this will cause a collapsed loop
4034
+ # prev and next elements on flow are a loop init/end
4035
+ prevIsLoop = nextIsLoop = False
4036
+ if compID > 0: # there is at least one preceding
4037
+ prevIsLoop = (flow[compID - 1]).getType() == 'LoopInitiator'
4038
+ if len(flow) > (compID + 1): # there is at least one more compon
4039
+ nextIsLoop = (flow[compID + 1]).getType() == 'LoopTerminator'
4040
+ if prevIsLoop and nextIsLoop:
4041
+ # because flow[compID+1] is a terminator
4042
+ loop = flow[compID + 1].loop
4043
+ msg = _translate('The "%s" Loop is about to be deleted as '
4044
+ 'well (by collapsing). OK to proceed?')
4045
+ title = _translate('Impending Loop collapse')
4046
+ warnDlg = dialogs.MessageDialog(
4047
+ parent=self.frame, message=msg % loop.params['name'],
4048
+ type='Warning', title=title)
4049
+ resp = warnDlg.ShowModal()
4050
+ if resp in [wx.ID_CANCEL, wx.ID_NO]:
4051
+ return # abort
4052
+ elif resp == wx.ID_YES:
4053
+ # make recursive calls to this same method until success
4054
+ # remove the loop first
4055
+ self.removeComponent(loop, compID)
4056
+ # because the loop has been removed ID is now one less
4057
+ self.removeComponent(component, compID - 1)
4058
+ return # have done the removal in final successful call
4059
+ # remove name from namespace only if it's a loop;
4060
+ # loops exist only in the flow
4061
+ elif 'conditionsFile' in component.params:
4062
+ conditionsFile = component.params['conditionsFile'].val
4063
+ if conditionsFile and conditionsFile not in ['None', '']:
4064
+ try:
4065
+ trialList, fieldNames = data.importConditions(
4066
+ conditionsFile, returnFieldNames=True)
4067
+ for fname in fieldNames:
4068
+ self.frame.exp.namespace.remove(fname)
4069
+ except Exception:
4070
+ msg = ("Conditions file %s couldn't be found so names not"
4071
+ " removed from namespace")
4072
+ logging.debug(msg % conditionsFile)
4073
+ self.frame.exp.namespace.remove(component.params['name'].val)
4074
+ # perform the actual removal
4075
+ flow.removeComponent(component, id=compID)
4076
+ self.draw()
4077
+ # enable/disable add loop button
4078
+ self.btnInsertLoop.Enable(bool(len(flow)))
4079
+
4080
+ def OnPaint(self, event):
4081
+ # Create a buffered paint DC. It will create the real
4082
+ # wx.PaintDC and then blit the bitmap to it when dc is
4083
+ # deleted.
4084
+ dc = wx.GCDC(wx.BufferedPaintDC(self))
4085
+ # use PrepareDC to set position correctly
4086
+ self.PrepareDC(dc)
4087
+ # we need to clear the dc BEFORE calling PrepareDC
4088
+ bg = wx.Brush(self.GetBackgroundColour())
4089
+ dc.SetBackground(bg)
4090
+ dc.Clear()
4091
+ # create a clipping rect from our position and size
4092
+ # and the Update Region
4093
+ xv, yv = self.GetViewStart()
4094
+ dx, dy = self.GetScrollPixelsPerUnit()
4095
+ x, y = (xv * dx, yv * dy)
4096
+ rgn = self.GetUpdateRegion()
4097
+ rgn.Offset(x, y)
4098
+ r = rgn.GetBox()
4099
+ # draw to the dc using the calculated clipping rect
4100
+ self.pdc.DrawToDCClipped(dc, r)
4101
+
4102
+ def draw(self, evt=None):
4103
+ """This is the main function for drawing the Flow panel.
4104
+ It should be called whenever something changes in the exp.
4105
+
4106
+ This then makes calls to other drawing functions,
4107
+ like drawEntryPoints...
4108
+ """
4109
+ if not hasattr(self.frame, 'exp'):
4110
+ # we haven't yet added an exp
4111
+ return
4112
+ # retrieve the current flow from the experiment
4113
+ expFlow = self.frame.exp.flow
4114
+ pdc = self.pdc
4115
+
4116
+ # use the ID of the drawn icon to retrieve component (loop or routine)
4117
+ self.componentFromID = {}
4118
+
4119
+ pdc.Clear() # clear the screen
4120
+ pdc.RemoveAll() # clear all objects (icon buttons)
4121
+
4122
+ font = self.GetFont()
4123
+
4124
+ # draw the main time line
4125
+ self.linePos = (1 * self.dpi, 0.5 * self.dpi) # x,y of start
4126
+ gap = self.dpi // (6, 4, 2)[self.appData['flowSize']]
4127
+ dLoopToBaseLine = (15, 25, 43)[self.appData['flowSize']]
4128
+ dBetweenLoops = (20, 24, 30)[self.appData['flowSize']]
4129
+
4130
+ # guess virtual size; nRoutines wide by nLoops high
4131
+ # make bigger than needed and shrink later
4132
+ nRoutines = len(expFlow)
4133
+ nLoops = 0
4134
+ for entry in expFlow:
4135
+ if entry.getType() == 'LoopInitiator':
4136
+ nLoops += 1
4137
+ sizeX = nRoutines * self.dpi * 2
4138
+ sizeY = nLoops * dBetweenLoops + dLoopToBaseLine * 3
4139
+ self.SetVirtualSize(size=(int(sizeX), int(sizeY)))
4140
+
4141
+ # this has type `float` for values, needs to be `int`
4142
+ linePosX, linePosY = [int(x) for x in self.linePos]
4143
+
4144
+ # step through components in flow, get spacing from text size, etc
4145
+ currX = self.linePos[0] # float
4146
+ lineId = wx.NewIdRef()
4147
+ pdc.SetPen(wx.Pen(colour=colors.app['fl_flowline_bg']))
4148
+ pdc.DrawLine(
4149
+ x1=linePosX - gap,
4150
+ y1=linePosY,
4151
+ x2=linePosX,
4152
+ y2=linePosY)
4153
+
4154
+ # NB the loop is itself the key, value is further info about it
4155
+ self.loops = {}
4156
+ nestLevel = 0
4157
+ maxNestLevel = 0
4158
+ self.gapMidPoints = [currX - gap // 2]
4159
+ self.gapNestLevels = [0]
4160
+ for ii, entry in enumerate(expFlow):
4161
+ if entry.getType() == 'LoopInitiator':
4162
+ # NB the loop is itself the dict key!?
4163
+ self.loops[entry.loop] = {
4164
+ 'init': currX, 'nest': nestLevel, 'id': ii}
4165
+ nestLevel += 1 # start of loop so increment level of nesting
4166
+ maxNestLevel = max(nestLevel, maxNestLevel)
4167
+ elif entry.getType() == 'LoopTerminator':
4168
+ # NB the loop is itself the dict key!
4169
+ self.loops[entry.loop]['term'] = currX
4170
+ nestLevel -= 1 # end of loop so decrement level of nesting
4171
+ elif entry.getType() == 'Routine' or entry.getType() in getAllStandaloneRoutines():
4172
+ # just get currX based on text size, don't draw anything yet:
4173
+ currX = self.drawFlowRoutine(pdc, entry, id=ii,
4174
+ pos=[currX, linePosY - 10],
4175
+ draw=False)
4176
+ self.gapMidPoints.append(currX + gap // 2)
4177
+ self.gapNestLevels.append(nestLevel)
4178
+ pdc.SetId(lineId)
4179
+ pdc.SetPen(wx.Pen(colour=colors.app['fl_flowline_bg']))
4180
+ pdc.DrawLine(
4181
+ x1=int(currX),
4182
+ y1=linePosY,
4183
+ x2=int(currX + gap),
4184
+ y2=linePosY)
4185
+ currX += gap
4186
+
4187
+ lineRect = wx.Rect(
4188
+ linePosX - 2,
4189
+ linePosY - 2,
4190
+ int(currX) - linePosX + 2,
4191
+ 4)
4192
+ pdc.SetIdBounds(lineId, lineRect)
4193
+
4194
+ # draw the loops first:
4195
+ maxHeight = 0
4196
+ for thisLoop in self.loops:
4197
+ thisInit = self.loops[thisLoop]['init']
4198
+ thisTerm = self.loops[thisLoop]['term']
4199
+ thisNest = maxNestLevel - self.loops[thisLoop]['nest'] - 1
4200
+ thisId = self.loops[thisLoop]['id']
4201
+ height = (linePosY + dLoopToBaseLine +
4202
+ thisNest * dBetweenLoops)
4203
+ self.drawLoop(pdc, thisLoop, id=thisId,
4204
+ startX=thisInit, endX=thisTerm,
4205
+ base=linePosY, height=height)
4206
+ self.drawLoopStart(pdc, pos=[thisInit, linePosY])
4207
+ self.drawLoopEnd(pdc, pos=[thisTerm, linePosY])
4208
+ if height > maxHeight:
4209
+ maxHeight = height
4210
+
4211
+ # draw routines second (over loop lines):
4212
+ currX = int(self.linePos[0])
4213
+ for ii, entry in enumerate(expFlow):
4214
+ if entry.getType() == 'Routine' or entry.getType() in getAllStandaloneRoutines():
4215
+ currX = self.drawFlowRoutine(
4216
+ pdc, entry, id=ii, pos=[currX, linePosY - 10])
4217
+ pdc.SetPen(wx.Pen(wx.Pen(colour=colors.app['fl_flowline_bg'])))
4218
+ pdc.DrawLine(
4219
+ x1=int(currX),
4220
+ y1=linePosY,
4221
+ x2=int(currX + gap),
4222
+ y2=linePosY)
4223
+ currX += gap
4224
+
4225
+ self.SetVirtualSize(size=(currX + 100, maxHeight + 50))
4226
+
4227
+ self.drawLineStart(pdc, (linePosX - gap, linePosY))
4228
+ self.drawLineEnd(pdc, (currX, linePosY))
4229
+
4230
+ # refresh the visible window after drawing (using OnPaint)
4231
+ self.Refresh()
4232
+
4233
+ def drawEntryPoints(self, posList):
4234
+ ptSize = (3, 4, 5)[self.appData['flowSize']]
4235
+ for n, pos in enumerate(posList):
4236
+ pos = int(pos)
4237
+ if n >= len(self.entryPointPosList):
4238
+ # draw for first time
4239
+ id = wx.NewIdRef()
4240
+ self.entryPointIDlist.append(id)
4241
+ self.pdc.SetId(id)
4242
+ self.pdc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4243
+ self.pdc.DrawCircle(pos, int(self.linePos[1]), ptSize)
4244
+ r = self.pdc.GetIdBounds(id)
4245
+ self.OffsetRect(r)
4246
+ self.RefreshRect(r, False)
4247
+ elif pos == self.entryPointPosList[n]:
4248
+ pass # nothing to see here, move along please :-)
4249
+ else:
4250
+ # move to new position
4251
+ dx = pos - self.entryPointPosList[n]
4252
+ dy = 0
4253
+ r = self.pdc.GetIdBounds(self.entryPointIDlist[n])
4254
+ self.pdc.TranslateId(self.entryPointIDlist[n], int(dx), int(dy))
4255
+ r2 = self.pdc.GetIdBounds(self.entryPointIDlist[n])
4256
+ # combine old and new locations to get redraw area
4257
+ rectToRedraw = r.Union(r2)
4258
+ rectToRedraw.Inflate(4, 4)
4259
+ self.OffsetRect(rectToRedraw)
4260
+ self.RefreshRect(rectToRedraw, False)
4261
+
4262
+ self.entryPointPosList = posList
4263
+ # refresh the visible window after drawing (using OnPaint)
4264
+ self.Refresh()
4265
+
4266
+ def setDrawPoints(self, ptType, startPoint=None):
4267
+ """Set the points of 'routines', 'loops', or None
4268
+ """
4269
+ if ptType == 'routines':
4270
+ self.pointsToDraw = self.gapMidPoints
4271
+ elif ptType == 'loops':
4272
+ self.pointsToDraw = self.gapMidPoints
4273
+ else:
4274
+ self.pointsToDraw = []
4275
+
4276
+ def drawLineStart(self, dc, pos):
4277
+ # draw bar at start of timeline; circle looked bad, offset vertically
4278
+ tmpId = wx.NewIdRef(count=1)
4279
+ dc.SetId(tmpId)
4280
+ ptSize = (9, 9, 12)[self.appData['flowSize']]
4281
+ thic = (1, 1, 2)[self.appData['flowSize']]
4282
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4283
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4284
+
4285
+ posX, posY = pos
4286
+ dc.DrawPolygon(
4287
+ [[0, -ptSize], [thic, -ptSize], [thic, ptSize], [0, ptSize]],
4288
+ int(posX), int(posY))
4289
+
4290
+ def drawLineEnd(self, dc, pos):
4291
+ # draws arrow at end of timeline
4292
+ tmpId = wx.NewIdRef(count=1)
4293
+ dc.SetId(tmpId)
4294
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4295
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4296
+
4297
+ posX, posY = pos
4298
+ dc.DrawPolygon([[0, -3], [5, 0], [0, 3]], int(posX), int(posY))
4299
+ # dc.SetIdBounds(tmpId,wx.Rect(pos[0],pos[1]+3,5,6))
4300
+
4301
+ def drawLoopEnd(self, dc, pos, downwards=True):
4302
+ # define the right side of a loop but draw nothing
4303
+ # idea: might want an ID for grabbing and relocating the loop endpoint
4304
+ tmpId = wx.NewIdRef()
4305
+ dc.SetId(tmpId)
4306
+ # dc.SetBrush(wx.Brush(wx.Colour(0,0,0, 250)))
4307
+ # dc.SetPen(wx.Pen(wx.Colour(0,0,0, 255)))
4308
+ size = (3, 4, 5)[self.appData['flowSize']]
4309
+ # if downwards:
4310
+ # dc.DrawPolygon([[size, 0], [0, size], [-size, 0]],
4311
+ # pos[0], pos[1] + 2 * size) # points down
4312
+ # else:
4313
+ # dc.DrawPolygon([[size, size], [0, 0], [-size, size]],
4314
+ # pos[0], pos[1]-3*size) # points up
4315
+
4316
+ posX, posY = pos
4317
+ doubleSize = int(2 * size)
4318
+ dc.SetIdBounds(tmpId, wx.Rect(
4319
+ int(posX) - size, int(posY) - size,
4320
+ doubleSize,
4321
+ doubleSize))
4322
+
4323
+ return
4324
+
4325
+ def drawLoopStart(self, dc, pos, downwards=True):
4326
+ # draws direction arrow on left side of a loop
4327
+ tmpId = wx.NewIdRef()
4328
+ dc.SetId(tmpId)
4329
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4330
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4331
+
4332
+ size = (3, 4, 5)[self.appData['flowSize']]
4333
+ offset = (3, 2, 0)[self.appData['flowSize']]
4334
+ posX, posY = [int(x) for x in pos]
4335
+ if downwards:
4336
+ dc.DrawPolygon(
4337
+ [[size, size], [0, 0], [-size, size]],
4338
+ posX, posY + 3 * size - offset) # points up
4339
+ else:
4340
+ dc.DrawPolygon(
4341
+ [[size, 0], [0, size], [-size, 0]],
4342
+ posX,
4343
+ posY - 4 * size) # points down
4344
+
4345
+ doubleSize = int(2 * size)
4346
+ dc.SetIdBounds(tmpId, wx.Rect(
4347
+ posX - size,
4348
+ posY - size,
4349
+ doubleSize,
4350
+ doubleSize))
4351
+
4352
+ def drawFlowRoutine(self, dc, routine, id, pos=(0, 0), draw=True):
4353
+ """Draw a box to show a routine on the timeline
4354
+ draw=False is for a dry-run, esp to compute and return size
4355
+ without drawing or setting a pdc ID
4356
+ """
4357
+ name = routine.name
4358
+ if self.appData['flowSize'] == 0 and len(name) > 5:
4359
+ name = ' ' + name[:4] + '..'
4360
+ else:
4361
+ name = ' ' + name + ' '
4362
+ if draw:
4363
+ dc.SetId(id)
4364
+ font = self.GetFont()
4365
+ if sys.platform == 'darwin':
4366
+ fontSizeDelta = (9, 6, 0)[self.appData['flowSize']]
4367
+ font.SetPointSize(1400 // self.dpi - fontSizeDelta)
4368
+ elif sys.platform.startswith('linux'):
4369
+ fontSizeDelta = (6, 4, 0)[self.appData['flowSize']]
4370
+ font.SetPointSize(1400 // self.dpi - fontSizeDelta)
4371
+ else:
4372
+ fontSizeDelta = (8, 4, 0)[self.appData['flowSize']]
4373
+ font.SetPointSize(1000 // self.dpi - fontSizeDelta)
4374
+ # if selected, bold text
4375
+ if routine == self.frame.routinePanel.getCurrentRoutine():
4376
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
4377
+ else:
4378
+ font.SetWeight(wx.FONTWEIGHT_NORMAL)
4379
+
4380
+ maxTime, nonSlip = routine.getMaxTime()
4381
+ if hasattr(routine, "disabled") and routine.disabled:
4382
+ rtFill = colors.app['rt_comp_disabled']
4383
+ rtEdge = colors.app['rt_comp_disabled']
4384
+ rtText = colors.app['fl_routine_fg']
4385
+ elif nonSlip:
4386
+ rtFill = colors.app['fl_routine_bg_nonslip']
4387
+ rtEdge = colors.app['fl_routine_bg_nonslip']
4388
+ rtText = colors.app['fl_routine_fg']
4389
+ else:
4390
+ rtFill = colors.app['fl_routine_bg_slip']
4391
+ rtEdge = colors.app['fl_routine_bg_slip']
4392
+ rtText = colors.app['fl_routine_fg']
4393
+
4394
+ # get size based on text
4395
+ self.SetFont(font)
4396
+ if draw:
4397
+ dc.SetFont(font)
4398
+ w, h = self.GetFullTextExtent(name)[0:2]
4399
+ pos = [int(x) for x in pos] # explicit type conversion for position
4400
+ pad = (5, 10, 20)[self.appData['flowSize']]
4401
+ # draw box
4402
+ rect = wx.Rect(pos[0], pos[1] + 2 - self.appData['flowSize'],
4403
+ w + pad, h + pad)
4404
+ endX = pos[0] + w + pad
4405
+ # the edge should match the text, unless selected
4406
+ if draw:
4407
+ dc.SetPen(wx.Pen(wx.Colour(rtEdge[0], rtEdge[1],
4408
+ rtEdge[2], wx.ALPHA_OPAQUE)))
4409
+ dc.SetBrush(wx.Brush(rtFill))
4410
+ dc.DrawRoundedRectangle(
4411
+ rect, (4, 6, 8)[self.appData['flowSize']])
4412
+ # draw text
4413
+ dc.SetTextForeground(rtText)
4414
+ dc.DrawLabel(name, rect, alignment=wx.ALIGN_CENTRE)
4415
+ if nonSlip and self.appData['flowSize'] != 0:
4416
+ font.SetPointSize(int(font.GetPointSize() * 0.6))
4417
+ dc.SetFont(font)
4418
+ _align = wx.ALIGN_CENTRE | wx.ALIGN_BOTTOM
4419
+ timeRect = wx.Rect(rect.Left, rect.Top, rect.Width, rect.Height-2)
4420
+ dc.DrawLabel("(%.2fs)" % maxTime, timeRect, alignment=_align)
4421
+
4422
+ self.componentFromID[id] = routine
4423
+ # set the area for this component
4424
+ dc.SetIdBounds(id, rect)
4425
+
4426
+ return endX
4427
+
4428
+ def drawLoop(self, dc, loop, id, startX, endX, base, height,
4429
+ downwards=True):
4430
+ if downwards:
4431
+ up = -1
4432
+ else:
4433
+ up = +1
4434
+
4435
+ # draw loop itself, as transparent rect with curved corners
4436
+ tmpId = wx.NewIdRef()
4437
+ dc.SetId(tmpId)
4438
+ # extra distance, in both h and w for curve
4439
+ curve = (6, 11, 15)[self.appData['flowSize']]
4440
+ # convert args types to `int`
4441
+ startX, endX, base, height = [
4442
+ int(x) for x in (startX, endX, base, height)]
4443
+
4444
+ yy = [base, height + curve * up, height +
4445
+ curve * up // 2, height] # for area
4446
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4447
+ vertOffset = 0 # 1 is interesting too
4448
+ area = wx.Rect(startX, base + vertOffset,
4449
+ endX - startX, max(yy) - min(yy))
4450
+ dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 0), style=wx.TRANSPARENT))
4451
+ # draws outline:
4452
+ dc.DrawRoundedRectangle(area, curve)
4453
+ dc.SetIdBounds(tmpId, area)
4454
+
4455
+ flowsize = self.appData['flowSize'] # 0, 1, or 2
4456
+
4457
+ # add a name label, loop info, except at smallest size
4458
+ name = loop.params['name'].val
4459
+ _show = self.appData['showLoopInfoInFlow']
4460
+ if _show and flowsize:
4461
+ _cond = 'conditions' in list(loop.params)
4462
+ if _cond and loop.params['conditions'].val:
4463
+ xnumTrials = 'x' + str(len(loop.params['conditions'].val))
4464
+ else:
4465
+ xnumTrials = ''
4466
+ name += ' (' + str(loop.params['nReps'].val) + xnumTrials
4467
+ abbrev = ['', # for flowsize == 0
4468
+ {'random': 'rand.',
4469
+ 'sequential': 'sequ.',
4470
+ 'fullRandom': 'f-ran.',
4471
+ 'staircase': 'stair.',
4472
+ 'interleaved staircases': "int-str."},
4473
+ {'random': 'random',
4474
+ 'sequential': 'sequential',
4475
+ 'fullRandom': 'fullRandom',
4476
+ 'staircase': 'staircase',
4477
+ 'interleaved staircases': "interl'vd stairs"}]
4478
+ name += ' ' + abbrev[flowsize][loop.params['loopType'].val] + ')'
4479
+ if flowsize == 0:
4480
+ if len(name) > 9:
4481
+ name = ' ' + name[:8] + '..'
4482
+ else:
4483
+ name = ' ' + name[:9]
4484
+ else:
4485
+ name = ' ' + name + ' '
4486
+
4487
+ dc.SetId(id)
4488
+ font = self.GetFont()
4489
+ font.SetWeight(wx.FONTWEIGHT_NORMAL)
4490
+ if sys.platform == 'darwin':
4491
+ basePtSize = (650, 750, 900)[flowsize]
4492
+ elif sys.platform.startswith('linux'):
4493
+ basePtSize = (750, 850, 1000)[flowsize]
4494
+ else:
4495
+ basePtSize = (700, 750, 800)[flowsize]
4496
+ font.SetPointSize(basePtSize // self.dpi)
4497
+ self.SetFont(font)
4498
+ dc.SetFont(font)
4499
+
4500
+ # get size based on text
4501
+ pad = (5, 8, 10)[self.appData['flowSize']]
4502
+ w, h = self.GetFullTextExtent(name)[0:2]
4503
+ x = startX + (endX - startX) // 2 - w // 2 - pad // 2
4504
+ y = (height - h // 2)
4505
+
4506
+ # draw box
4507
+ rect = wx.Rect(int(x), int(y), int(w + pad), int(h + pad))
4508
+ # the edge should match the text
4509
+ dc.SetPen(wx.Pen(colors.app['fl_flowline_bg']))
4510
+ # try to make the loop fill brighter than the background canvas:
4511
+ dc.SetBrush(wx.Brush(colors.app['fl_flowline_bg']))
4512
+
4513
+ dc.DrawRoundedRectangle(rect, (4, 6, 8)[flowsize])
4514
+ # draw text
4515
+ dc.SetTextForeground(colors.app['fl_flowline_fg'])
4516
+ dc.DrawText(name, x + pad // 2, y + pad // 2)
4517
+
4518
+ self.componentFromID[id] = loop
4519
+ # set the area for this component
4520
+ dc.SetIdBounds(id, rect)
4521
+
4522
+
4523
+ class BuilderRibbon(ribbon.FrameRibbon):
4524
+ def __init__(self, parent):
4525
+ # initialize
4526
+ ribbon.FrameRibbon.__init__(self, parent)
4527
+
4528
+ # --- File ---
4529
+ self.addSection(
4530
+ "file", label=_translate("File"), icon="file"
4531
+ )
4532
+ # file new
4533
+ self.addButton(
4534
+ section="file", name="new", label=_translate("New"), icon="filenew",
4535
+ tooltip=_translate("Create new experiment file"),
4536
+ callback=parent.app.newBuilderFrame
4537
+ )
4538
+ # file open
4539
+ self.addButton(
4540
+ section="file", name="open", label=_translate("Open"), icon="fileopen",
4541
+ tooltip=_translate("Open an existing experiment file"),
4542
+ callback=parent.fileOpen
4543
+ )
4544
+ # file save
4545
+ self.addButton(
4546
+ section="file", name="save", label=_translate("Save"), icon="filesave",
4547
+ tooltip=_translate("Save current experiment file"),
4548
+ callback=parent.fileSave
4549
+ )
4550
+ # file save as
4551
+ self.addButton(
4552
+ section="file", name="saveas", label=_translate("Save as..."), icon="filesaveas",
4553
+ tooltip=_translate("Save current experiment file as..."),
4554
+ callback=parent.fileSaveAs
4555
+ )
4556
+
4557
+ self.addSeparator()
4558
+
4559
+ # --- Edit ---
4560
+ self.addSection(
4561
+ "edit", label=_translate("Edit"), icon="edit"
4562
+ )
4563
+ # undo
4564
+ self.addButton(
4565
+ section="edit", name="undo", label=_translate("Undo"), icon="undo",
4566
+ tooltip=_translate("Undo last action"),
4567
+ callback=parent.undo
4568
+ )
4569
+ # redo
4570
+ self.addButton(
4571
+ section="edit", name="redo", label=_translate("Redo"), icon="redo",
4572
+ tooltip=_translate("Redo last action"),
4573
+ callback=parent.redo
4574
+ )
4575
+ # find
4576
+ self.addButton(
4577
+ section="edit", name="find", label=_translate("Find"), icon="find",
4578
+ tooltip=_translate("Search the whole experiment for a specific term"),
4579
+ callback=parent.onFindInExperiment
4580
+ )
4581
+
4582
+ self.addSeparator()
4583
+
4584
+ # --- Tools ---
4585
+ self.addSection(
4586
+ "experiment", label=_translate("Experiment"), icon="experiment"
4587
+ )
4588
+ # monitor center
4589
+ self.addButton(
4590
+ section="experiment", name='monitor', label=_translate('Monitor center'),
4591
+ icon="monitors",
4592
+ tooltip=_translate("Monitor settings and calibration"),
4593
+ callback=parent.app.openMonitorCenter
4594
+ )
4595
+ # device manager
4596
+ self.addButton(
4597
+ section="experiment", name='devices', label=_translate('Device manager'),
4598
+ icon="devices",
4599
+ tooltip=_translate("Map devices from this machine to names in your experiment"),
4600
+ callback=parent.openDeviceManager
4601
+ )
4602
+ # settings
4603
+ self.addButton(
4604
+ section="experiment", name='expsettings', label=_translate('Experiment settings'), icon="expsettings",
4605
+ tooltip=_translate("Edit experiment settings"),
4606
+ callback=parent.setExperimentSettings
4607
+ )
4608
+ # switch run/pilot
4609
+ self.addSwitchCtrl(
4610
+ section="experiment", name="pyswitch",
4611
+ labels=(_translate("Pilot"), _translate("Run")),
4612
+ startMode=0, callback=parent.onRunModeToggle,
4613
+ style=wx.HORIZONTAL
4614
+ )
4615
+ # send to runner
4616
+ self.addButton(
4617
+ section="experiment", name='sendRunner', label=_translate('Runner'), icon="runner",
4618
+ tooltip=_translate("Send experiment to Runner"),
4619
+ callback=parent.sendToRunner
4620
+ )
4621
+ # send to runner (pilot icon)
4622
+ self.addButton(
4623
+ section="experiment", name='pilotRunner', label=_translate('Runner'),
4624
+ icon="runnerPilot",
4625
+ tooltip=_translate("Send experiment to Runner"),
4626
+ callback=parent.sendToRunner
4627
+ )
4628
+
4629
+ self.addSeparator()
4630
+
4631
+ # --- Python ---
4632
+ self.addSection(
4633
+ "py", label=_translate("Desktop"), icon="desktop"
4634
+ )
4635
+ # compile python
4636
+ self.addButton(
4637
+ section="py", name="pycompile", label=_translate('Write Python'), icon='compile_py',
4638
+ tooltip=_translate("Write experiment as a Python script"),
4639
+ callback=parent.compileScript
4640
+ )
4641
+ # pilot Py
4642
+ self.addButton(
4643
+ section="py", name="pypilot", label=_translate("Pilot"), icon='pyPilot',
4644
+ tooltip=_translate("Run the current script in Python with piloting features on"),
4645
+ callback=parent.pilotFile
4646
+ )
4647
+ # run Py
4648
+ self.addButton(
4649
+ section="py", name="pyrun", label=_translate("Run"), icon='pyRun',
4650
+ tooltip=_translate("Run the current script in Python"),
4651
+ callback=parent.runFile
4652
+ )
4653
+
4654
+ self.addSeparator()
4655
+
4656
+ # --- JS ---
4657
+ self.addSection(
4658
+ "browser", label=_translate("Browser"), icon="browser"
4659
+ )
4660
+ # compile JS
4661
+ self.addButton(
4662
+ section="browser", name="jscompile", label=_translate('Write JS'), icon='compile_js',
4663
+ tooltip=_translate("Write experiment as a JavaScript (JS) script"),
4664
+ callback=parent.fileExport
4665
+ )
4666
+ # pilot JS
4667
+ self.addButton(
4668
+ section="browser", name="jspilot", label=_translate("Pilot in browser"),
4669
+ icon='jsPilot',
4670
+ tooltip=_translate("Pilot experiment locally in your browser"),
4671
+ callback=parent.onPavloviaDebug
4672
+ )
4673
+ # run JS
4674
+ self.addButton(
4675
+ section="browser", name="jsrun", label=_translate("Run on Pavlovia"), icon='jsRun',
4676
+ tooltip=_translate("Run experiment on Pavlovia"),
4677
+ callback=parent.onPavloviaRun
4678
+ )
4679
+ # sync project
4680
+ self.addButton(
4681
+ section="browser", name="pavsync", label=_translate("Sync"), icon='pavsync',
4682
+ tooltip=_translate("Sync project with Pavlovia"),
4683
+ callback=parent.onPavloviaSync
4684
+ )
4685
+
4686
+ self.addSeparator()
4687
+
4688
+ # --- JS ---
4689
+ self.addSection(
4690
+ "pavlovia", label=_translate("Pavlovia"), icon="pavlovia"
4691
+ )
4692
+ # pavlovia user
4693
+ self.addPavloviaUserCtrl(
4694
+ section="pavlovia", name="pavuser", frame=parent
4695
+ )
4696
+ # pavlovia project
4697
+ self.addPavloviaProjectCtrl(
4698
+ section="pavlovia", name="pavproject", frame=parent
4699
+ )
4700
+
4701
+ self.addSeparator()
4702
+
4703
+ # --- Plugin sections ---
4704
+ self.addPluginSections("psychopy.app.builder")
4705
+
4706
+ # --- Views ---
4707
+ self.addStretchSpacer()
4708
+ self.addSeparator()
4709
+
4710
+ self.addSection(
4711
+ "views", label=_translate("Views"), icon="windows"
4712
+ )
4713
+ # show Builder
4714
+ self.addButton(
4715
+ section="views", name="builder", label=_translate("Show Builder"), icon="showBuilder",
4716
+ tooltip=_translate("Switch to Builder view"),
4717
+ callback=parent.app.showBuilder
4718
+ ).Disable()
4719
+ # show Coder
4720
+ self.addButton(
4721
+ section="views", name="coder", label=_translate("Show Coder"), icon="showCoder",
4722
+ tooltip=_translate("Switch to Coder view"),
4723
+ callback=parent.app.showCoder
4724
+ )
4725
+ # show Runner
4726
+ self.addButton(
4727
+ section="views", name="runner", label=_translate("Show Runner"), icon="showRunner",
4728
+ tooltip=_translate("Switch to Runner view"),
4729
+ callback=parent.app.showRunner
4730
+ )
4731
+
4732
+ def extractText(stream):
4733
+ """Take a byte stream (or any file object of type b?) and return
4734
+
4735
+ :param stream: stream from wx.Process or any byte stream from a file
4736
+ :return: text converted to unicode ready for appending to wx text view
4737
+ """
4738
+ return stream.read().decode('utf-8')