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.
- psychopy/CHANGELOG.txt +3981 -0
- psychopy/GIT_SHA +1 -0
- psychopy/LICENSE.txt +11 -0
- psychopy/LICENSES.txt +55 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +148 -0
- psychopy/alerts/__init__.py +65 -0
- psychopy/alerts/_alerts.py +224 -0
- psychopy/alerts/_errorHandler.py +50 -0
- psychopy/alerts/alertsCatalogue/1000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/1100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/1500.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2110.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2115.yaml +21 -0
- psychopy/alerts/alertsCatalogue/2120.yaml +22 -0
- psychopy/alerts/alertsCatalogue/2150.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2155.yaml +21 -0
- psychopy/alerts/alertsCatalogue/2300.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2500.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3110.yaml +25 -0
- psychopy/alerts/alertsCatalogue/3115.yaml +24 -0
- psychopy/alerts/alertsCatalogue/3200.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
- psychopy/alerts/alertsCatalogue/3400.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3600.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4051.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4052.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4105.yaml +18 -0
- psychopy/alerts/alertsCatalogue/4115.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4120.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4125.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4160.yaml +25 -0
- psychopy/alerts/alertsCatalogue/4200.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4205.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4210.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4305.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4310.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4315.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4320.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4325.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4330.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4335.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4340.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4405.yaml +24 -0
- psychopy/alerts/alertsCatalogue/4505.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4510.yaml +25 -0
- psychopy/alerts/alertsCatalogue/4520.yaml +26 -0
- psychopy/alerts/alertsCatalogue/4530.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4540.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4545.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4550.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4605.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4610.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4615.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4705.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4710.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/5000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/5055.yaml +25 -0
- psychopy/alerts/alertsCatalogue/6000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/6105.yaml +18 -0
- psychopy/alerts/alertsCatalogue/7105.yaml +19 -0
- psychopy/alerts/alertsCatalogue/8105.yaml +21 -0
- psychopy/alerts/alertsCatalogue/8110.yaml +21 -0
- psychopy/alerts/alertsCatalogue/9998.yaml +19 -0
- psychopy/alerts/alertsCatalogue/9999.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +123 -0
- psychopy/alerts/alertsCatalogue/alertTemplate.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +173 -0
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +34 -0
- psychopy/alerts/alerttools.py +322 -0
- psychopy/app/Resources/README.md +12 -0
- psychopy/app/Resources/__init__.py +0 -0
- psychopy/app/Resources/betasplash.png +0 -0
- psychopy/app/Resources/betasplash@2x.png +0 -0
- psychopy/app/Resources/builder.ico +0 -0
- psychopy/app/Resources/classic/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/classic/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/classic/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/classic/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/classic/README.txt +9 -0
- psychopy/app/Resources/classic/__init__.py +0 -0
- psychopy/app/Resources/classic/_layouts.ai +1138 -1
- psychopy/app/Resources/classic/add.png +0 -0
- psychopy/app/Resources/classic/add@2x.png +0 -0
- psychopy/app/Resources/classic/addExp32.png +0 -0
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/alerts.png +0 -0
- psychopy/app/Resources/classic/beta.png +0 -0
- psychopy/app/Resources/classic/browser.png +0 -0
- psychopy/app/Resources/classic/browser@2x.png +0 -0
- psychopy/app/Resources/classic/bug16.png +0 -0
- psychopy/app/Resources/classic/case.png +0 -0
- psychopy/app/Resources/classic/case@2x.png +0 -0
- psychopy/app/Resources/classic/circle_mask.png +0 -0
- psychopy/app/Resources/classic/circle_mask@2x.png +0 -0
- psychopy/app/Resources/classic/clear.png +0 -0
- psychopy/app/Resources/classic/clear@2x.png +0 -0
- psychopy/app/Resources/classic/coderclass16.png +0 -0
- psychopy/app/Resources/classic/coderfunc16.png +0 -0
- psychopy/app/Resources/classic/coderimport16.png +0 -0
- psychopy/app/Resources/classic/coderjs16.png +0 -0
- psychopy/app/Resources/classic/coderpython16.png +0 -0
- psychopy/app/Resources/classic/codervar16.png +0 -0
- psychopy/app/Resources/classic/cogwindow32.png +0 -0
- psychopy/app/Resources/classic/color32.png +0 -0
- psychopy/app/Resources/classic/compile32.png +0 -0
- psychopy/app/Resources/classic/compile_js.png +0 -0
- psychopy/app/Resources/classic/compile_js@2x.png +0 -0
- psychopy/app/Resources/classic/compile_py.png +0 -0
- psychopy/app/Resources/classic/compile_py@2x.png +0 -0
- psychopy/app/Resources/classic/copy16.png +0 -0
- psychopy/app/Resources/classic/currentFile.png +0 -0
- psychopy/app/Resources/classic/currentFile@2x.png +0 -0
- psychopy/app/Resources/classic/delete16.png +0 -0
- psychopy/app/Resources/classic/desktop.png +0 -0
- psychopy/app/Resources/classic/desktop@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/dirup16.png +0 -0
- psychopy/app/Resources/classic/docclose16.png +0 -0
- psychopy/app/Resources/classic/download.png +0 -0
- psychopy/app/Resources/classic/edit.png +0 -0
- psychopy/app/Resources/classic/edit@2x.png +0 -0
- psychopy/app/Resources/classic/editbtn.png +0 -0
- psychopy/app/Resources/classic/editbtn16.png +0 -0
- psychopy/app/Resources/classic/editbtn16@2x.png +0 -0
- psychopy/app/Resources/classic/editbtn@2x.png +0 -0
- psychopy/app/Resources/classic/email.png +0 -0
- psychopy/app/Resources/classic/email@2x.png +0 -0
- psychopy/app/Resources/classic/experiment.png +0 -0
- psychopy/app/Resources/classic/experiment@2x.png +0 -0
- psychopy/app/Resources/classic/expsettings.png +0 -0
- psychopy/app/Resources/classic/expsettings@2x.png +0 -0
- psychopy/app/Resources/classic/file.png +0 -0
- psychopy/app/Resources/classic/file@2x.png +0 -0
- psychopy/app/Resources/classic/fileaudio.png +0 -0
- psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
- psychopy/app/Resources/classic/filecss.png +0 -0
- psychopy/app/Resources/classic/filecss@2x.png +0 -0
- psychopy/app/Resources/classic/filecsv.png +0 -0
- psychopy/app/Resources/classic/filecsv@2x.png +0 -0
- psychopy/app/Resources/classic/filedesign.png +0 -0
- psychopy/app/Resources/classic/filedesign@2x.png +0 -0
- psychopy/app/Resources/classic/filefont.png +0 -0
- psychopy/app/Resources/classic/filefont@2x.png +0 -0
- psychopy/app/Resources/classic/filegit.png +0 -0
- psychopy/app/Resources/classic/filegit@2x.png +0 -0
- psychopy/app/Resources/classic/filehtml.png +0 -0
- psychopy/app/Resources/classic/filehtml@2x.png +0 -0
- psychopy/app/Resources/classic/fileimage.png +0 -0
- psychopy/app/Resources/classic/fileimage@2x.png +0 -0
- psychopy/app/Resources/classic/fileinfo.png +0 -0
- psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
- psychopy/app/Resources/classic/filejs.png +0 -0
- psychopy/app/Resources/classic/filejs@2x.png +0 -0
- psychopy/app/Resources/classic/filejson.png +0 -0
- psychopy/app/Resources/classic/filejson@2x.png +0 -0
- psychopy/app/Resources/classic/filenew.png +0 -0
- psychopy/app/Resources/classic/filenew32.png +0 -0
- psychopy/app/Resources/classic/filenew@2x.png +0 -0
- psychopy/app/Resources/classic/fileopen.png +0 -0
- psychopy/app/Resources/classic/fileopen32.png +0 -0
- psychopy/app/Resources/classic/fileopen@2x.png +0 -0
- psychopy/app/Resources/classic/filepkg.png +0 -0
- psychopy/app/Resources/classic/filepkg@2x.png +0 -0
- psychopy/app/Resources/classic/filepsyexp.png +0 -0
- psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/classic/filepy.png +0 -0
- psychopy/app/Resources/classic/filepy@2x.png +0 -0
- psychopy/app/Resources/classic/filesave.png +0 -0
- psychopy/app/Resources/classic/filesave32.png +0 -0
- psychopy/app/Resources/classic/filesave@2x.png +0 -0
- psychopy/app/Resources/classic/filesaveas.png +0 -0
- psychopy/app/Resources/classic/filesaveas32.png +0 -0
- psychopy/app/Resources/classic/filesaveas@2x.png +0 -0
- psychopy/app/Resources/classic/filetxt.png +0 -0
- psychopy/app/Resources/classic/filetxt@2x.png +0 -0
- psychopy/app/Resources/classic/fileunknown.png +0 -0
- psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
- psychopy/app/Resources/classic/filevideo.png +0 -0
- psychopy/app/Resources/classic/filevideo@2x.png +0 -0
- psychopy/app/Resources/classic/filter.png +0 -0
- psychopy/app/Resources/classic/filter@2x.png +0 -0
- psychopy/app/Resources/classic/find.png +0 -0
- psychopy/app/Resources/classic/find@2x.png +0 -0
- psychopy/app/Resources/classic/folder-open16.png +0 -0
- psychopy/app/Resources/classic/folder16.png +0 -0
- psychopy/app/Resources/classic/foldernew16.png +0 -0
- psychopy/app/Resources/classic/fork.png +0 -0
- psychopy/app/Resources/classic/fork@2x.png +0 -0
- psychopy/app/Resources/classic/github.png +0 -0
- psychopy/app/Resources/classic/github@2x.png +0 -0
- psychopy/app/Resources/classic/globe.png +0 -0
- psychopy/app/Resources/classic/globe@2x.png +0 -0
- psychopy/app/Resources/classic/globe_bug.png +0 -0
- psychopy/app/Resources/classic/globe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/globe_greensync.png +0 -0
- psychopy/app/Resources/classic/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/globe_info.png +0 -0
- psychopy/app/Resources/classic/globe_info@2x.png +0 -0
- psychopy/app/Resources/classic/globe_magnifier.png +0 -0
- psychopy/app/Resources/classic/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/globe_run.png +0 -0
- psychopy/app/Resources/classic/globe_run@2x.png +0 -0
- psychopy/app/Resources/classic/globe_user.png +0 -0
- psychopy/app/Resources/classic/globe_user@2x.png +0 -0
- psychopy/app/Resources/classic/goto16.png +0 -0
- psychopy/app/Resources/classic/greendot.png +0 -0
- psychopy/app/Resources/classic/greendot@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe.png +0 -0
- psychopy/app/Resources/classic/greenglobe@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_bug.png +0 -0
- psychopy/app/Resources/classic/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/classic/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_info.png +0 -0
- psychopy/app/Resources/classic/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/classic/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_run.png +0 -0
- psychopy/app/Resources/classic/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_user.png +0 -0
- psychopy/app/Resources/classic/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/classic/greydot.png +0 -0
- psychopy/app/Resources/classic/greydot@2x.png +0 -0
- psychopy/app/Resources/classic/greytick.png +0 -0
- psychopy/app/Resources/classic/greytick@2x.png +0 -0
- psychopy/app/Resources/classic/invalid_img.png +0 -0
- psychopy/app/Resources/classic/jsPilot.png +0 -0
- psychopy/app/Resources/classic/jsPilot@2x.png +0 -0
- psychopy/app/Resources/classic/jsRun.png +0 -0
- psychopy/app/Resources/classic/jsRun@2x.png +0 -0
- psychopy/app/Resources/classic/libroot.png +0 -0
- psychopy/app/Resources/classic/libroot@2x.png +0 -0
- psychopy/app/Resources/classic/loop.png +0 -0
- psychopy/app/Resources/classic/loop@2x.png +0 -0
- psychopy/app/Resources/classic/monitor16.png +0 -0
- psychopy/app/Resources/classic/monitors.png +0 -0
- psychopy/app/Resources/classic/monitors16.png +0 -0
- psychopy/app/Resources/classic/monitors32.png +0 -0
- psychopy/app/Resources/classic/monitors@2x.png +0 -0
- psychopy/app/Resources/classic/orangedot.png +0 -0
- psychopy/app/Resources/classic/orangedot@2x.png +0 -0
- psychopy/app/Resources/classic/pavlovia.png +0 -0
- psychopy/app/Resources/classic/pavlovia16.png +0 -0
- psychopy/app/Resources/classic/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/classic/pavlovia@2x.png +0 -0
- psychopy/app/Resources/classic/pavsync.png +0 -0
- psychopy/app/Resources/classic/pavsync@2x.png +0 -0
- psychopy/app/Resources/classic/person_off.png +0 -0
- psychopy/app/Resources/classic/person_off@2x.png +0 -0
- psychopy/app/Resources/classic/person_on.png +0 -0
- psychopy/app/Resources/classic/person_on@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/classic/plugins32.png +0 -0
- psychopy/app/Resources/classic/plus.png +0 -0
- psychopy/app/Resources/classic/plus@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-app.png +0 -0
- psychopy/app/Resources/classic/preferences-app48.png +0 -0
- psychopy/app/Resources/classic/preferences-app@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-conn.png +0 -0
- psychopy/app/Resources/classic/preferences-conn48.png +0 -0
- psychopy/app/Resources/classic/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-debug.png +0 -0
- psychopy/app/Resources/classic/preferences-debug@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-general.png +0 -0
- psychopy/app/Resources/classic/preferences-general48.png +0 -0
- psychopy/app/Resources/classic/preferences-general@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware48.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-pilot.png +0 -0
- psychopy/app/Resources/classic/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/classic/preferences32.png +0 -0
- psychopy/app/Resources/classic/pyPilot.png +0 -0
- psychopy/app/Resources/classic/pyPilot@2x.png +0 -0
- psychopy/app/Resources/classic/pyRun.png +0 -0
- psychopy/app/Resources/classic/pyRun@2x.png +0 -0
- psychopy/app/Resources/classic/reddot.png +0 -0
- psychopy/app/Resources/classic/reddot@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe.png +0 -0
- psychopy/app/Resources/classic/redglobe@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_bug.png +0 -0
- psychopy/app/Resources/classic/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_greensync.png +0 -0
- psychopy/app/Resources/classic/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_info.png +0 -0
- psychopy/app/Resources/classic/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/classic/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_run.png +0 -0
- psychopy/app/Resources/classic/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_user.png +0 -0
- psychopy/app/Resources/classic/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/classic/redo.png +0 -0
- psychopy/app/Resources/classic/redo32.png +0 -0
- psychopy/app/Resources/classic/redo@2x.png +0 -0
- psychopy/app/Resources/classic/regex.png +0 -0
- psychopy/app/Resources/classic/regex@2x.png +0 -0
- psychopy/app/Resources/classic/removeExp32.png +0 -0
- psychopy/app/Resources/classic/rename16.png +0 -0
- psychopy/app/Resources/classic/restart.png +0 -0
- psychopy/app/Resources/classic/restart@2x.png +0 -0
- psychopy/app/Resources/classic/run.png +0 -0
- psychopy/app/Resources/classic/run@2x.png +0 -0
- psychopy/app/Resources/classic/runner.png +0 -0
- psychopy/app/Resources/classic/runner@2x.png +0 -0
- psychopy/app/Resources/classic/runnerPilot.png +0 -0
- psychopy/app/Resources/classic/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/classic/savebtn.png +0 -0
- psychopy/app/Resources/classic/savebtn16.png +0 -0
- psychopy/app/Resources/classic/savebtn16@2x.png +0 -0
- psychopy/app/Resources/classic/savebtn@2x.png +0 -0
- psychopy/app/Resources/classic/search.png +0 -0
- psychopy/app/Resources/classic/search@2x.png +0 -0
- psychopy/app/Resources/classic/showBuilder.png +0 -0
- psychopy/app/Resources/classic/showBuilder@2x.png +0 -0
- psychopy/app/Resources/classic/showCoder.png +0 -0
- psychopy/app/Resources/classic/showCoder@2x.png +0 -0
- psychopy/app/Resources/classic/showRunner.png +0 -0
- psychopy/app/Resources/classic/showRunner@2x.png +0 -0
- psychopy/app/Resources/classic/starred.png +0 -0
- psychopy/app/Resources/classic/starred@2x.png +0 -0
- psychopy/app/Resources/classic/start.png +0 -0
- psychopy/app/Resources/classic/start@2x.png +0 -0
- psychopy/app/Resources/classic/stdout.png +0 -0
- psychopy/app/Resources/classic/stop.png +0 -0
- psychopy/app/Resources/classic/stop32.png +0 -0
- psychopy/app/Resources/classic/stop@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlBot.png +0 -0
- psychopy/app/Resources/classic/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/classic/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlRight.png +0 -0
- psychopy/app/Resources/classic/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlTop.png +0 -0
- psychopy/app/Resources/classic/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/classic/tick.png +0 -0
- psychopy/app/Resources/classic/tick@2x.png +0 -0
- psychopy/app/Resources/classic/undo.png +0 -0
- psychopy/app/Resources/classic/undo32.png +0 -0
- psychopy/app/Resources/classic/undo@2x.png +0 -0
- psychopy/app/Resources/classic/unstarred.png +0 -0
- psychopy/app/Resources/classic/unstarred@2x.png +0 -0
- psychopy/app/Resources/classic/user_none.png +0 -0
- psychopy/app/Resources/classic/view-refresh16.png +0 -0
- psychopy/app/Resources/classic/viewbtn.png +0 -0
- psychopy/app/Resources/classic/viewbtn16.png +0 -0
- psychopy/app/Resources/classic/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/classic/viewbtn@2x.png +0 -0
- psychopy/app/Resources/classic/windows16.png +0 -0
- psychopy/app/Resources/classic/windows16@2x.png +0 -0
- psychopy/app/Resources/coder.ico +0 -0
- psychopy/app/Resources/dark/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/dark/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/dark/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/dark/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/dark/README.txt +3 -0
- psychopy/app/Resources/dark/__init__.py +0 -0
- psychopy/app/Resources/dark/add.png +0 -0
- psychopy/app/Resources/dark/add@2x.png +0 -0
- psychopy/app/Resources/dark/addExp32.png +0 -0
- psychopy/app/Resources/dark/addExp32@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/alerts.png +0 -0
- psychopy/app/Resources/dark/alerts@2x.png +0 -0
- psychopy/app/Resources/dark/beta.png +0 -0
- psychopy/app/Resources/dark/beta@2x.png +0 -0
- psychopy/app/Resources/dark/browser.png +0 -0
- psychopy/app/Resources/dark/browser@2x.png +0 -0
- psychopy/app/Resources/dark/bug16.png +0 -0
- psychopy/app/Resources/dark/bug16@2x.png +0 -0
- psychopy/app/Resources/dark/case.png +0 -0
- psychopy/app/Resources/dark/case@2x.png +0 -0
- psychopy/app/Resources/dark/circle_mask.png +0 -0
- psychopy/app/Resources/dark/circle_mask@2x.png +0 -0
- psychopy/app/Resources/dark/clear.png +0 -0
- psychopy/app/Resources/dark/clear@2x.png +0 -0
- psychopy/app/Resources/dark/coderclass16.png +0 -0
- psychopy/app/Resources/dark/coderclass16@2x.png +0 -0
- psychopy/app/Resources/dark/coderfunc16.png +0 -0
- psychopy/app/Resources/dark/coderfunc16@2x.png +0 -0
- psychopy/app/Resources/dark/coderimport16.png +0 -0
- psychopy/app/Resources/dark/coderimport16@2x.png +0 -0
- psychopy/app/Resources/dark/coderjs.png +0 -0
- psychopy/app/Resources/dark/coderjs@2x.png +0 -0
- psychopy/app/Resources/dark/coderpython.png +0 -0
- psychopy/app/Resources/dark/coderpython@2x.png +0 -0
- psychopy/app/Resources/dark/codervar16.png +0 -0
- psychopy/app/Resources/dark/codervar16@2x.png +0 -0
- psychopy/app/Resources/dark/cogwindow32.png +0 -0
- psychopy/app/Resources/dark/cogwindow32@2x.png +0 -0
- psychopy/app/Resources/dark/color32.png +0 -0
- psychopy/app/Resources/dark/color32@2x.png +0 -0
- psychopy/app/Resources/dark/compile_js.png +0 -0
- psychopy/app/Resources/dark/compile_js@2x.png +0 -0
- psychopy/app/Resources/dark/compile_py.png +0 -0
- psychopy/app/Resources/dark/compile_py@2x.png +0 -0
- psychopy/app/Resources/dark/copy16.png +0 -0
- psychopy/app/Resources/dark/copy16@2x.png +0 -0
- psychopy/app/Resources/dark/currentFile16.png +0 -0
- psychopy/app/Resources/dark/currentFile16@2x.png +0 -0
- psychopy/app/Resources/dark/delete16.png +0 -0
- psychopy/app/Resources/dark/delete16@2x.png +0 -0
- psychopy/app/Resources/dark/desktop.png +0 -0
- psychopy/app/Resources/dark/desktop@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/dirup16.png +0 -0
- psychopy/app/Resources/dark/dirup16@2x.png +0 -0
- psychopy/app/Resources/dark/docclose16.png +0 -0
- psychopy/app/Resources/dark/docclose16@2x.png +0 -0
- psychopy/app/Resources/dark/download.png +0 -0
- psychopy/app/Resources/dark/download@2x.png +0 -0
- psychopy/app/Resources/dark/edit.png +0 -0
- psychopy/app/Resources/dark/edit@2x.png +0 -0
- psychopy/app/Resources/dark/editbtn16.png +0 -0
- psychopy/app/Resources/dark/editbtn16@2x.png +0 -0
- psychopy/app/Resources/dark/email.png +0 -0
- psychopy/app/Resources/dark/email@2x.png +0 -0
- psychopy/app/Resources/dark/experiment.png +0 -0
- psychopy/app/Resources/dark/experiment@2x.png +0 -0
- psychopy/app/Resources/dark/expsettings.png +0 -0
- psychopy/app/Resources/dark/expsettings@2x.png +0 -0
- psychopy/app/Resources/dark/file.png +0 -0
- psychopy/app/Resources/dark/file@16w.png +0 -0
- psychopy/app/Resources/dark/file@16w@2x.png +0 -0
- psychopy/app/Resources/dark/file@2x.png +0 -0
- psychopy/app/Resources/dark/fileaudio.png +0 -0
- psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
- psychopy/app/Resources/dark/filecss.png +0 -0
- psychopy/app/Resources/dark/filecss@2x.png +0 -0
- psychopy/app/Resources/dark/filecsv.png +0 -0
- psychopy/app/Resources/dark/filecsv@2x.png +0 -0
- psychopy/app/Resources/dark/filedesign.png +0 -0
- psychopy/app/Resources/dark/filedesign@2x.png +0 -0
- psychopy/app/Resources/dark/filefont.png +0 -0
- psychopy/app/Resources/dark/filefont@2x.png +0 -0
- psychopy/app/Resources/dark/filegit.png +0 -0
- psychopy/app/Resources/dark/filegit@2x.png +0 -0
- psychopy/app/Resources/dark/filehtml.png +0 -0
- psychopy/app/Resources/dark/filehtml@2x.png +0 -0
- psychopy/app/Resources/dark/fileimage.png +0 -0
- psychopy/app/Resources/dark/fileimage@2x.png +0 -0
- psychopy/app/Resources/dark/fileinfo.png +0 -0
- psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
- psychopy/app/Resources/dark/filejs.png +0 -0
- psychopy/app/Resources/dark/filejs@2x.png +0 -0
- psychopy/app/Resources/dark/filejson.png +0 -0
- psychopy/app/Resources/dark/filejson@2x.png +0 -0
- psychopy/app/Resources/dark/filenew.png +0 -0
- psychopy/app/Resources/dark/filenew32.png +0 -0
- psychopy/app/Resources/dark/filenew32@2x.png +0 -0
- psychopy/app/Resources/dark/filenew@2x.png +0 -0
- psychopy/app/Resources/dark/fileopen.png +0 -0
- psychopy/app/Resources/dark/fileopen32.png +0 -0
- psychopy/app/Resources/dark/fileopen32@2x.png +0 -0
- psychopy/app/Resources/dark/fileopen@2x.png +0 -0
- psychopy/app/Resources/dark/filepkg.png +0 -0
- psychopy/app/Resources/dark/filepkg@2x.png +0 -0
- psychopy/app/Resources/dark/filepsyexp.png +0 -0
- psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/dark/filepy.png +0 -0
- psychopy/app/Resources/dark/filepy@2x.png +0 -0
- psychopy/app/Resources/dark/filesave.png +0 -0
- psychopy/app/Resources/dark/filesave32.png +0 -0
- psychopy/app/Resources/dark/filesave32@2x.png +0 -0
- psychopy/app/Resources/dark/filesave@2x.png +0 -0
- psychopy/app/Resources/dark/filesaveas.png +0 -0
- psychopy/app/Resources/dark/filesaveas32.png +0 -0
- psychopy/app/Resources/dark/filesaveas32@2x.png +0 -0
- psychopy/app/Resources/dark/filesaveas@2x.png +0 -0
- psychopy/app/Resources/dark/filetxt.png +0 -0
- psychopy/app/Resources/dark/filetxt@2x.png +0 -0
- psychopy/app/Resources/dark/fileunknown.png +0 -0
- psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
- psychopy/app/Resources/dark/filevideo.png +0 -0
- psychopy/app/Resources/dark/filevideo@2x.png +0 -0
- psychopy/app/Resources/dark/filter.png +0 -0
- psychopy/app/Resources/dark/filter@2x.png +0 -0
- psychopy/app/Resources/dark/find.png +0 -0
- psychopy/app/Resources/dark/find@2x.png +0 -0
- psychopy/app/Resources/dark/folder-open16.png +0 -0
- psychopy/app/Resources/dark/folder-open16@2x.png +0 -0
- psychopy/app/Resources/dark/folder16.png +0 -0
- psychopy/app/Resources/dark/folder16@2x.png +0 -0
- psychopy/app/Resources/dark/foldernew16.png +0 -0
- psychopy/app/Resources/dark/foldernew16@2x.png +0 -0
- psychopy/app/Resources/dark/fork.png +0 -0
- psychopy/app/Resources/dark/fork@2x.png +0 -0
- psychopy/app/Resources/dark/github.png +0 -0
- psychopy/app/Resources/dark/github@2x.png +0 -0
- psychopy/app/Resources/dark/globe.png +0 -0
- psychopy/app/Resources/dark/globe@2x.png +0 -0
- psychopy/app/Resources/dark/globe_bug.png +0 -0
- psychopy/app/Resources/dark/globe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/globe_greensync.png +0 -0
- psychopy/app/Resources/dark/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/globe_info.png +0 -0
- psychopy/app/Resources/dark/globe_info@2x.png +0 -0
- psychopy/app/Resources/dark/globe_magnifier.png +0 -0
- psychopy/app/Resources/dark/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/globe_run.png +0 -0
- psychopy/app/Resources/dark/globe_run@2x.png +0 -0
- psychopy/app/Resources/dark/globe_user.png +0 -0
- psychopy/app/Resources/dark/globe_user@2x.png +0 -0
- psychopy/app/Resources/dark/goto.png +0 -0
- psychopy/app/Resources/dark/goto@2x.png +0 -0
- psychopy/app/Resources/dark/greendot.png +0 -0
- psychopy/app/Resources/dark/greendot@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe.png +0 -0
- psychopy/app/Resources/dark/greenglobe@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_bug.png +0 -0
- psychopy/app/Resources/dark/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/dark/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_info.png +0 -0
- psychopy/app/Resources/dark/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/dark/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_run.png +0 -0
- psychopy/app/Resources/dark/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_user.png +0 -0
- psychopy/app/Resources/dark/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/dark/greydot.png +0 -0
- psychopy/app/Resources/dark/greydot@2x.png +0 -0
- psychopy/app/Resources/dark/greytick.png +0 -0
- psychopy/app/Resources/dark/greytick@2x.png +0 -0
- psychopy/app/Resources/dark/invalid_img.png +0 -0
- psychopy/app/Resources/dark/jsPilot.png +0 -0
- psychopy/app/Resources/dark/jsPilot@2x.png +0 -0
- psychopy/app/Resources/dark/jsRun.png +0 -0
- psychopy/app/Resources/dark/jsRun@2x.png +0 -0
- psychopy/app/Resources/dark/libroot16.png +0 -0
- psychopy/app/Resources/dark/libroot16@2x.png +0 -0
- psychopy/app/Resources/dark/loop.png +0 -0
- psychopy/app/Resources/dark/loop@2x.png +0 -0
- psychopy/app/Resources/dark/monitor16.png +0 -0
- psychopy/app/Resources/dark/monitor16@2x.png +0 -0
- psychopy/app/Resources/dark/monitors.png +0 -0
- psychopy/app/Resources/dark/monitors32.png +0 -0
- psychopy/app/Resources/dark/monitors32@2x.png +0 -0
- psychopy/app/Resources/dark/monitors@2x.png +0 -0
- psychopy/app/Resources/dark/orangedot.png +0 -0
- psychopy/app/Resources/dark/orangedot@2x.png +0 -0
- psychopy/app/Resources/dark/pavlovia.png +0 -0
- psychopy/app/Resources/dark/pavlovia16.png +0 -0
- psychopy/app/Resources/dark/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/dark/pavlovia@2x.png +0 -0
- psychopy/app/Resources/dark/pavsync.png +0 -0
- psychopy/app/Resources/dark/pavsync@2x.png +0 -0
- psychopy/app/Resources/dark/person_off.png +0 -0
- psychopy/app/Resources/dark/person_off@2x.png +0 -0
- psychopy/app/Resources/dark/person_on.png +0 -0
- psychopy/app/Resources/dark/person_on@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugins32.png +0 -0
- psychopy/app/Resources/dark/plugins32@2x.png +0 -0
- psychopy/app/Resources/dark/plus.png +0 -0
- psychopy/app/Resources/dark/plus@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-app.png +0 -0
- psychopy/app/Resources/dark/preferences-app48.png +0 -0
- psychopy/app/Resources/dark/preferences-app48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-app@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-conn.png +0 -0
- psychopy/app/Resources/dark/preferences-conn48.png +0 -0
- psychopy/app/Resources/dark/preferences-conn48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-debug.png +0 -0
- psychopy/app/Resources/dark/preferences-debug@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-general.png +0 -0
- psychopy/app/Resources/dark/preferences-general48.png +0 -0
- psychopy/app/Resources/dark/preferences-general48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-general@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware48.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-pilot.png +0 -0
- psychopy/app/Resources/dark/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/dark/preferences32.png +0 -0
- psychopy/app/Resources/dark/preferences32@2x.png +0 -0
- psychopy/app/Resources/dark/pyPilot.png +0 -0
- psychopy/app/Resources/dark/pyPilot@2x.png +0 -0
- psychopy/app/Resources/dark/pyRun.png +0 -0
- psychopy/app/Resources/dark/pyRun@2x.png +0 -0
- psychopy/app/Resources/dark/reddot.png +0 -0
- psychopy/app/Resources/dark/reddot@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe.png +0 -0
- psychopy/app/Resources/dark/redglobe@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_bug.png +0 -0
- psychopy/app/Resources/dark/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_greensync.png +0 -0
- psychopy/app/Resources/dark/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_info.png +0 -0
- psychopy/app/Resources/dark/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/dark/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_run.png +0 -0
- psychopy/app/Resources/dark/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_user.png +0 -0
- psychopy/app/Resources/dark/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/dark/redo.png +0 -0
- psychopy/app/Resources/dark/redo32.png +0 -0
- psychopy/app/Resources/dark/redo32@2x.png +0 -0
- psychopy/app/Resources/dark/redo@2x.png +0 -0
- psychopy/app/Resources/dark/regex.png +0 -0
- psychopy/app/Resources/dark/regex@2x.png +0 -0
- psychopy/app/Resources/dark/removeExp32.png +0 -0
- psychopy/app/Resources/dark/removeExp32@2x.png +0 -0
- psychopy/app/Resources/dark/rename16.png +0 -0
- psychopy/app/Resources/dark/rename16@2x.png +0 -0
- psychopy/app/Resources/dark/restart.png +0 -0
- psychopy/app/Resources/dark/restart@2x.png +0 -0
- psychopy/app/Resources/dark/runner.png +0 -0
- psychopy/app/Resources/dark/runner@2x.png +0 -0
- psychopy/app/Resources/dark/runnerPilot.png +0 -0
- psychopy/app/Resources/dark/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/dark/savebtn16.png +0 -0
- psychopy/app/Resources/dark/savebtn16@2x.png +0 -0
- psychopy/app/Resources/dark/search.png +0 -0
- psychopy/app/Resources/dark/search@2x.png +0 -0
- psychopy/app/Resources/dark/showBuilder.png +0 -0
- psychopy/app/Resources/dark/showBuilder@2x.png +0 -0
- psychopy/app/Resources/dark/showCoder.png +0 -0
- psychopy/app/Resources/dark/showCoder@2x.png +0 -0
- psychopy/app/Resources/dark/showRunner.png +0 -0
- psychopy/app/Resources/dark/showRunner@2x.png +0 -0
- psychopy/app/Resources/dark/starred.png +0 -0
- psychopy/app/Resources/dark/starred@2x.png +0 -0
- psychopy/app/Resources/dark/start.png +0 -0
- psychopy/app/Resources/dark/start@2x.png +0 -0
- psychopy/app/Resources/dark/stdout.png +0 -0
- psychopy/app/Resources/dark/stdout@2x.png +0 -0
- psychopy/app/Resources/dark/stop.png +0 -0
- psychopy/app/Resources/dark/stop32.png +0 -0
- psychopy/app/Resources/dark/stop32@2x.png +0 -0
- psychopy/app/Resources/dark/stop@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlBot.png +0 -0
- psychopy/app/Resources/dark/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/dark/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlRight.png +0 -0
- psychopy/app/Resources/dark/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlTop.png +0 -0
- psychopy/app/Resources/dark/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/dark/tick.png +0 -0
- psychopy/app/Resources/dark/tick@2x.png +0 -0
- psychopy/app/Resources/dark/undo.png +0 -0
- psychopy/app/Resources/dark/undo32.png +0 -0
- psychopy/app/Resources/dark/undo32@2x.png +0 -0
- psychopy/app/Resources/dark/undo@2x.png +0 -0
- psychopy/app/Resources/dark/unstarred.png +0 -0
- psychopy/app/Resources/dark/unstarred@2x.png +0 -0
- psychopy/app/Resources/dark/user_none.png +0 -0
- psychopy/app/Resources/dark/view-refresh16.png +0 -0
- psychopy/app/Resources/dark/view-refresh16@2x.png +0 -0
- psychopy/app/Resources/dark/viewbtn16.png +0 -0
- psychopy/app/Resources/dark/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/dark/windows16.png +0 -0
- psychopy/app/Resources/dark/windows16@2x.png +0 -0
- psychopy/app/Resources/light/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/light/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/light/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/light/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/light/README.txt +3 -0
- psychopy/app/Resources/light/__init__.py +0 -0
- psychopy/app/Resources/light/add.png +0 -0
- psychopy/app/Resources/light/add@2x.png +0 -0
- psychopy/app/Resources/light/addExp32.png +0 -0
- psychopy/app/Resources/light/addExp32@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/alerts.png +0 -0
- psychopy/app/Resources/light/alerts@2x.png +0 -0
- psychopy/app/Resources/light/beta.png +0 -0
- psychopy/app/Resources/light/beta@2x.png +0 -0
- psychopy/app/Resources/light/browser.png +0 -0
- psychopy/app/Resources/light/browser@2x.png +0 -0
- psychopy/app/Resources/light/bug16.png +0 -0
- psychopy/app/Resources/light/bug16@2x.png +0 -0
- psychopy/app/Resources/light/case.png +0 -0
- psychopy/app/Resources/light/case@2x.png +0 -0
- psychopy/app/Resources/light/circle_mask.png +0 -0
- psychopy/app/Resources/light/circle_mask@2x.png +0 -0
- psychopy/app/Resources/light/clear.png +0 -0
- psychopy/app/Resources/light/clear@2x.png +0 -0
- psychopy/app/Resources/light/coderclass16.png +0 -0
- psychopy/app/Resources/light/coderclass16@2x.png +0 -0
- psychopy/app/Resources/light/coderfunc16.png +0 -0
- psychopy/app/Resources/light/coderfunc16@2x.png +0 -0
- psychopy/app/Resources/light/coderimport16.png +0 -0
- psychopy/app/Resources/light/coderimport16@2x.png +0 -0
- psychopy/app/Resources/light/coderjs.png +0 -0
- psychopy/app/Resources/light/coderjs@2x.png +0 -0
- psychopy/app/Resources/light/coderpython.png +0 -0
- psychopy/app/Resources/light/coderpython@2x.png +0 -0
- psychopy/app/Resources/light/codervar16.png +0 -0
- psychopy/app/Resources/light/codervar16@2x.png +0 -0
- psychopy/app/Resources/light/cogwindow32.png +0 -0
- psychopy/app/Resources/light/cogwindow32@2x.png +0 -0
- psychopy/app/Resources/light/color16.png +0 -0
- psychopy/app/Resources/light/color16@2x.png +0 -0
- psychopy/app/Resources/light/color32.png +0 -0
- psychopy/app/Resources/light/color32@2x.png +0 -0
- psychopy/app/Resources/light/compile_js.png +0 -0
- psychopy/app/Resources/light/compile_js@2x.png +0 -0
- psychopy/app/Resources/light/compile_py.png +0 -0
- psychopy/app/Resources/light/compile_py@2x.png +0 -0
- psychopy/app/Resources/light/copy16.png +0 -0
- psychopy/app/Resources/light/copy16@2x.png +0 -0
- psychopy/app/Resources/light/currentFile16.png +0 -0
- psychopy/app/Resources/light/currentFile16@2x.png +0 -0
- psychopy/app/Resources/light/delete16.png +0 -0
- psychopy/app/Resources/light/delete16@2x.png +0 -0
- psychopy/app/Resources/light/delete8.png +0 -0
- psychopy/app/Resources/light/desktop.png +0 -0
- psychopy/app/Resources/light/desktop@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/dirup16.png +0 -0
- psychopy/app/Resources/light/dirup16@2x.png +0 -0
- psychopy/app/Resources/light/docclose16.png +0 -0
- psychopy/app/Resources/light/docclose16@2x.png +0 -0
- psychopy/app/Resources/light/download.png +0 -0
- psychopy/app/Resources/light/download@2x.png +0 -0
- psychopy/app/Resources/light/edit.png +0 -0
- psychopy/app/Resources/light/edit@2x.png +0 -0
- psychopy/app/Resources/light/editbtn16.png +0 -0
- psychopy/app/Resources/light/editbtn16@2x.png +0 -0
- psychopy/app/Resources/light/email.png +0 -0
- psychopy/app/Resources/light/email@2x.png +0 -0
- psychopy/app/Resources/light/experiment.png +0 -0
- psychopy/app/Resources/light/experiment@2x.png +0 -0
- psychopy/app/Resources/light/expsettings.png +0 -0
- psychopy/app/Resources/light/expsettings@2x.png +0 -0
- psychopy/app/Resources/light/file.png +0 -0
- psychopy/app/Resources/light/file@2x.png +0 -0
- psychopy/app/Resources/light/fileaudio.png +0 -0
- psychopy/app/Resources/light/fileaudio@2x.png +0 -0
- psychopy/app/Resources/light/filecss.png +0 -0
- psychopy/app/Resources/light/filecss@2x.png +0 -0
- psychopy/app/Resources/light/filecsv.png +0 -0
- psychopy/app/Resources/light/filecsv@2x.png +0 -0
- psychopy/app/Resources/light/filedesign.png +0 -0
- psychopy/app/Resources/light/filedesign@2x.png +0 -0
- psychopy/app/Resources/light/filefont.png +0 -0
- psychopy/app/Resources/light/filefont@2x.png +0 -0
- psychopy/app/Resources/light/filegit.png +0 -0
- psychopy/app/Resources/light/filegit@2x.png +0 -0
- psychopy/app/Resources/light/filehtml.png +0 -0
- psychopy/app/Resources/light/filehtml@2x.png +0 -0
- psychopy/app/Resources/light/fileimage.png +0 -0
- psychopy/app/Resources/light/fileimage@2x.png +0 -0
- psychopy/app/Resources/light/fileinfo.png +0 -0
- psychopy/app/Resources/light/fileinfo@2x.png +0 -0
- psychopy/app/Resources/light/filejs.png +0 -0
- psychopy/app/Resources/light/filejs@2x.png +0 -0
- psychopy/app/Resources/light/filejson.png +0 -0
- psychopy/app/Resources/light/filejson@2x.png +0 -0
- psychopy/app/Resources/light/filenew.png +0 -0
- psychopy/app/Resources/light/filenew32.png +0 -0
- psychopy/app/Resources/light/filenew32@2x.png +0 -0
- psychopy/app/Resources/light/filenew@2x.png +0 -0
- psychopy/app/Resources/light/fileopen.png +0 -0
- psychopy/app/Resources/light/fileopen32.png +0 -0
- psychopy/app/Resources/light/fileopen32@2x.png +0 -0
- psychopy/app/Resources/light/fileopen@2x.png +0 -0
- psychopy/app/Resources/light/filepkg.png +0 -0
- psychopy/app/Resources/light/filepkg@2x.png +0 -0
- psychopy/app/Resources/light/filepsyexp.png +0 -0
- psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/light/filepy.png +0 -0
- psychopy/app/Resources/light/filepy@2x.png +0 -0
- psychopy/app/Resources/light/filesave.png +0 -0
- psychopy/app/Resources/light/filesave32.png +0 -0
- psychopy/app/Resources/light/filesave32@2x.png +0 -0
- psychopy/app/Resources/light/filesave@2x.png +0 -0
- psychopy/app/Resources/light/filesaveas.png +0 -0
- psychopy/app/Resources/light/filesaveas32.png +0 -0
- psychopy/app/Resources/light/filesaveas32@2x.png +0 -0
- psychopy/app/Resources/light/filesaveas@2x.png +0 -0
- psychopy/app/Resources/light/filetxt.png +0 -0
- psychopy/app/Resources/light/filetxt@2x.png +0 -0
- psychopy/app/Resources/light/fileunknown.png +0 -0
- psychopy/app/Resources/light/fileunknown@2x.png +0 -0
- psychopy/app/Resources/light/filevideo.png +0 -0
- psychopy/app/Resources/light/filevideo@2x.png +0 -0
- psychopy/app/Resources/light/filter.png +0 -0
- psychopy/app/Resources/light/filter@2x.png +0 -0
- psychopy/app/Resources/light/find.png +0 -0
- psychopy/app/Resources/light/find@2x.png +0 -0
- psychopy/app/Resources/light/folder-open16.png +0 -0
- psychopy/app/Resources/light/folder-open16@2x.png +0 -0
- psychopy/app/Resources/light/folder16.png +0 -0
- psychopy/app/Resources/light/folder16@2x.png +0 -0
- psychopy/app/Resources/light/foldernew16.png +0 -0
- psychopy/app/Resources/light/foldernew16@2x.png +0 -0
- psychopy/app/Resources/light/fork.png +0 -0
- psychopy/app/Resources/light/fork@2x.png +0 -0
- psychopy/app/Resources/light/github.png +0 -0
- psychopy/app/Resources/light/github@2x.png +0 -0
- psychopy/app/Resources/light/globe.png +0 -0
- psychopy/app/Resources/light/globe@2x.png +0 -0
- psychopy/app/Resources/light/globe_bug.png +0 -0
- psychopy/app/Resources/light/globe_bug@2x.png +0 -0
- psychopy/app/Resources/light/globe_greensync.png +0 -0
- psychopy/app/Resources/light/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/globe_info.png +0 -0
- psychopy/app/Resources/light/globe_info@2x.png +0 -0
- psychopy/app/Resources/light/globe_magnifier.png +0 -0
- psychopy/app/Resources/light/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/globe_run.png +0 -0
- psychopy/app/Resources/light/globe_run@2x.png +0 -0
- psychopy/app/Resources/light/globe_user.png +0 -0
- psychopy/app/Resources/light/globe_user@2x.png +0 -0
- psychopy/app/Resources/light/goto16.png +0 -0
- psychopy/app/Resources/light/goto16@2x.png +0 -0
- psychopy/app/Resources/light/greendot.png +0 -0
- psychopy/app/Resources/light/greendot@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe.png +0 -0
- psychopy/app/Resources/light/greenglobe@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_bug.png +0 -0
- psychopy/app/Resources/light/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/light/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_info.png +0 -0
- psychopy/app/Resources/light/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/light/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_run.png +0 -0
- psychopy/app/Resources/light/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_user.png +0 -0
- psychopy/app/Resources/light/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/light/greydot.png +0 -0
- psychopy/app/Resources/light/greydot@2x.png +0 -0
- psychopy/app/Resources/light/greytick.png +0 -0
- psychopy/app/Resources/light/greytick@2x.png +0 -0
- psychopy/app/Resources/light/invalid_img.png +0 -0
- psychopy/app/Resources/light/jsPilot.png +0 -0
- psychopy/app/Resources/light/jsPilot@2x.png +0 -0
- psychopy/app/Resources/light/jsRun.png +0 -0
- psychopy/app/Resources/light/jsRun@2x.png +0 -0
- psychopy/app/Resources/light/libroot16.png +0 -0
- psychopy/app/Resources/light/libroot16@2x.png +0 -0
- psychopy/app/Resources/light/loop.png +0 -0
- psychopy/app/Resources/light/loop@2x.png +0 -0
- psychopy/app/Resources/light/monitor16.png +0 -0
- psychopy/app/Resources/light/monitor16@2x.png +0 -0
- psychopy/app/Resources/light/monitors.png +0 -0
- psychopy/app/Resources/light/monitors32.png +0 -0
- psychopy/app/Resources/light/monitors32@2x.png +0 -0
- psychopy/app/Resources/light/monitors@2x.png +0 -0
- psychopy/app/Resources/light/orangedot.png +0 -0
- psychopy/app/Resources/light/orangedot@2x.png +0 -0
- psychopy/app/Resources/light/pavlovia.png +0 -0
- psychopy/app/Resources/light/pavlovia16.png +0 -0
- psychopy/app/Resources/light/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/light/pavlovia@2x.png +0 -0
- psychopy/app/Resources/light/pavsync.png +0 -0
- psychopy/app/Resources/light/pavsync@2x.png +0 -0
- psychopy/app/Resources/light/person_off.png +0 -0
- psychopy/app/Resources/light/person_off@2x.png +0 -0
- psychopy/app/Resources/light/person_on.png +0 -0
- psychopy/app/Resources/light/person_on@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugins32.png +0 -0
- psychopy/app/Resources/light/plugins32@2x.png +0 -0
- psychopy/app/Resources/light/plus.png +0 -0
- psychopy/app/Resources/light/plus@2x.png +0 -0
- psychopy/app/Resources/light/preferences-app.png +0 -0
- psychopy/app/Resources/light/preferences-app48.png +0 -0
- psychopy/app/Resources/light/preferences-app48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-app@2x.png +0 -0
- psychopy/app/Resources/light/preferences-conn.png +0 -0
- psychopy/app/Resources/light/preferences-conn48.png +0 -0
- psychopy/app/Resources/light/preferences-conn48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/light/preferences-general.png +0 -0
- psychopy/app/Resources/light/preferences-general48.png +0 -0
- psychopy/app/Resources/light/preferences-general48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-general@2x.png +0 -0
- psychopy/app/Resources/light/preferences-hardware.png +0 -0
- psychopy/app/Resources/light/preferences-hardware48.png +0 -0
- psychopy/app/Resources/light/preferences-hardware48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/light/preferences-pilot.png +0 -0
- psychopy/app/Resources/light/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/light/preferences32.png +0 -0
- psychopy/app/Resources/light/preferences32@2x.png +0 -0
- psychopy/app/Resources/light/pyPilot.png +0 -0
- psychopy/app/Resources/light/pyPilot@2x.png +0 -0
- psychopy/app/Resources/light/pyRun.png +0 -0
- psychopy/app/Resources/light/pyRun@2x.png +0 -0
- psychopy/app/Resources/light/reddot.png +0 -0
- psychopy/app/Resources/light/reddot@2x.png +0 -0
- psychopy/app/Resources/light/redglobe.png +0 -0
- psychopy/app/Resources/light/redglobe@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_bug.png +0 -0
- psychopy/app/Resources/light/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_greensync.png +0 -0
- psychopy/app/Resources/light/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_info.png +0 -0
- psychopy/app/Resources/light/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/light/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_run.png +0 -0
- psychopy/app/Resources/light/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_user.png +0 -0
- psychopy/app/Resources/light/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/light/redo.png +0 -0
- psychopy/app/Resources/light/redo32.png +0 -0
- psychopy/app/Resources/light/redo32@2x.png +0 -0
- psychopy/app/Resources/light/redo@2x.png +0 -0
- psychopy/app/Resources/light/regex.png +0 -0
- psychopy/app/Resources/light/regex@2x.png +0 -0
- psychopy/app/Resources/light/removeExp32.png +0 -0
- psychopy/app/Resources/light/removeExp32@2x.png +0 -0
- psychopy/app/Resources/light/rename16.png +0 -0
- psychopy/app/Resources/light/rename16@2x.png +0 -0
- psychopy/app/Resources/light/restart.png +0 -0
- psychopy/app/Resources/light/restart@2x.png +0 -0
- psychopy/app/Resources/light/runner.png +0 -0
- psychopy/app/Resources/light/runner@2x.png +0 -0
- psychopy/app/Resources/light/runnerPilot.png +0 -0
- psychopy/app/Resources/light/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/light/savebtn16.png +0 -0
- psychopy/app/Resources/light/savebtn16@2x.png +0 -0
- psychopy/app/Resources/light/search.png +0 -0
- psychopy/app/Resources/light/search@2x.png +0 -0
- psychopy/app/Resources/light/showBuilder.png +0 -0
- psychopy/app/Resources/light/showBuilder@2x.png +0 -0
- psychopy/app/Resources/light/showCoder.png +0 -0
- psychopy/app/Resources/light/showCoder@2x.png +0 -0
- psychopy/app/Resources/light/showRunner.png +0 -0
- psychopy/app/Resources/light/showRunner@2x.png +0 -0
- psychopy/app/Resources/light/starred.png +0 -0
- psychopy/app/Resources/light/starred@2x.png +0 -0
- psychopy/app/Resources/light/start.png +0 -0
- psychopy/app/Resources/light/start@2x.png +0 -0
- psychopy/app/Resources/light/stdout.png +0 -0
- psychopy/app/Resources/light/stdout@2x.png +0 -0
- psychopy/app/Resources/light/stop.png +0 -0
- psychopy/app/Resources/light/stop32.png +0 -0
- psychopy/app/Resources/light/stop32@2x.png +0 -0
- psychopy/app/Resources/light/stop@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlBot.png +0 -0
- psychopy/app/Resources/light/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/light/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlRight.png +0 -0
- psychopy/app/Resources/light/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlTop.png +0 -0
- psychopy/app/Resources/light/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/light/tick.png +0 -0
- psychopy/app/Resources/light/tick@2x.png +0 -0
- psychopy/app/Resources/light/undo.png +0 -0
- psychopy/app/Resources/light/undo32.png +0 -0
- psychopy/app/Resources/light/undo32@2x.png +0 -0
- psychopy/app/Resources/light/undo@2x.png +0 -0
- psychopy/app/Resources/light/unstarred.png +0 -0
- psychopy/app/Resources/light/unstarred@2x.png +0 -0
- psychopy/app/Resources/light/user_none.png +0 -0
- psychopy/app/Resources/light/view-refresh16.png +0 -0
- psychopy/app/Resources/light/view-refresh16@2x.png +0 -0
- psychopy/app/Resources/light/viewbtn16.png +0 -0
- psychopy/app/Resources/light/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/light/windows16.png +0 -0
- psychopy/app/Resources/light/windows16@2x.png +0 -0
- psychopy/app/Resources/moveComponentIcons.py +50 -0
- psychopy/app/Resources/psychopy.desktop +13 -0
- psychopy/app/Resources/psychopy.icns +0 -0
- psychopy/app/Resources/psychopy.ico +0 -0
- psychopy/app/Resources/psychopy.png +0 -0
- psychopy/app/Resources/psychopy.xml +9 -0
- psychopy/app/Resources/psychopy@2x.png +0 -0
- psychopy/app/Resources/routine_templates/Basic.psyexp +344 -0
- psychopy/app/Resources/routine_templates/Misc.psyexp +228 -0
- psychopy/app/Resources/routine_templates/Online.psyexp +200 -0
- psychopy/app/Resources/routine_templates/Trials.psyexp +735 -0
- psychopy/app/Resources/routine_templates/readme.md +14 -0
- psychopy/app/Resources/runner.ico +0 -0
- psychopy/app/Resources/splash.png +0 -0
- psychopy/app/Resources/splash@2x.png +0 -0
- psychopy/app/Resources/tips.txt +45 -0
- psychopy/app/Resources/tips_ar_001.txt +45 -0
- psychopy/app/Resources/tips_fr_FR.txt +45 -0
- psychopy/app/Resources/tips_ja_JP.txt +42 -0
- psychopy/app/Resources/tips_zh_CN.txt +45 -0
- psychopy/app/Resources/window.ico +0 -0
- psychopy/app/__init__.py +339 -0
- psychopy/app/__main__.py +3 -0
- psychopy/app/_psychopyApp.py +1298 -0
- psychopy/app/appData.spec +58 -0
- psychopy/app/builder/__init__.py +5 -0
- psychopy/app/builder/builder.py +4738 -0
- psychopy/app/builder/dialogs/__init__.py +1910 -0
- psychopy/app/builder/dialogs/dlgsCode.py +631 -0
- psychopy/app/builder/dialogs/dlgsConditions.py +669 -0
- psychopy/app/builder/dialogs/findDlg.py +275 -0
- psychopy/app/builder/dialogs/paramCtrls.py +1802 -0
- psychopy/app/builder/validators.py +589 -0
- psychopy/app/coder/__init__.py +8 -0
- psychopy/app/coder/codeEditorBase.py +460 -0
- psychopy/app/coder/coder.py +3100 -0
- psychopy/app/coder/fileBrowser.py +714 -0
- psychopy/app/coder/folding.py +128 -0
- psychopy/app/coder/psychoParser.py +139 -0
- psychopy/app/coder/repl.py +496 -0
- psychopy/app/coder/sourceTree.py +302 -0
- psychopy/app/colorpicker/__init__.py +594 -0
- psychopy/app/colorpicker/ui.py +336 -0
- psychopy/app/connections/__init__.py +10 -0
- psychopy/app/connections/news.py +104 -0
- psychopy/app/connections/sendusage.py +61 -0
- psychopy/app/connections/updates.py +642 -0
- psychopy/app/console.py +164 -0
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/dialogs.py +664 -0
- psychopy/app/errorDlg.py +238 -0
- psychopy/app/frametracker.py +8 -0
- psychopy/app/idle.py +181 -0
- psychopy/app/jobs.py +676 -0
- psychopy/app/linuxconfig/__init__.py +153 -0
- psychopy/app/linuxconfig/ui.py +88 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +11268 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -0
- psychopy/app/localizedStrings.py +386 -0
- psychopy/app/pavlovia_ui/__init__.py +22 -0
- psychopy/app/pavlovia_ui/_base.py +268 -0
- psychopy/app/pavlovia_ui/functions.py +176 -0
- psychopy/app/pavlovia_ui/menu.py +140 -0
- psychopy/app/pavlovia_ui/project.py +943 -0
- psychopy/app/pavlovia_ui/search.py +444 -0
- psychopy/app/pavlovia_ui/sync.py +137 -0
- psychopy/app/pavlovia_ui/user.py +264 -0
- psychopy/app/plugin_manager/__init__.py +5 -0
- psychopy/app/plugin_manager/dialog.py +402 -0
- psychopy/app/plugin_manager/output.py +132 -0
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +643 -0
- psychopy/app/plugin_manager/plugins.py +1363 -0
- psychopy/app/plugin_manager/utils.py +115 -0
- psychopy/app/preferencesDlg.py +771 -0
- psychopy/app/psychopyApp.py +207 -0
- psychopy/app/ribbon.py +1019 -0
- psychopy/app/runner/__init__.py +1 -0
- psychopy/app/runner/runner.py +1299 -0
- psychopy/app/runner/scriptProcess.py +363 -0
- psychopy/app/stdout/__init__.py +1 -0
- psychopy/app/stdout/stdOutRich.py +410 -0
- psychopy/app/sysInfoDlg.py +242 -0
- psychopy/app/themes/__init__.py +78 -0
- psychopy/app/themes/colors.py +201 -0
- psychopy/app/themes/css/contrast_black.css +112 -0
- psychopy/app/themes/css/contrast_white.css +115 -0
- psychopy/app/themes/css/dark.css +112 -0
- psychopy/app/themes/css/light.css +115 -0
- psychopy/app/themes/fonts.py +629 -0
- psychopy/app/themes/handlers.py +321 -0
- psychopy/app/themes/icons.py +267 -0
- psychopy/app/themes/spec/Classic.json +154 -0
- psychopy/app/themes/spec/ClassicDark.json +154 -0
- psychopy/app/themes/spec/GitHub.json +154 -0
- psychopy/app/themes/spec/HiVisDark.json +153 -0
- psychopy/app/themes/spec/HiVisLight.json +154 -0
- psychopy/app/themes/spec/MinimalDark.json +154 -0
- psychopy/app/themes/spec/MinimalLight.json +153 -0
- psychopy/app/themes/spec/PsychopyDark.json +231 -0
- psychopy/app/themes/spec/PsychopyLight.json +236 -0
- psychopy/app/themes/ui.py +74 -0
- psychopy/app/ui/__init__.py +184 -0
- psychopy/app/urls.py +31 -0
- psychopy/app/utils.py +1741 -0
- psychopy/app/viewer/__init__.py +0 -0
- psychopy/assets/Psychopy Window Favicon@16w.png +0 -0
- psychopy/assets/Psychopy Window Favicon@32w.png +0 -0
- psychopy/assets/USB-C.png +0 -0
- psychopy/assets/USB.png +0 -0
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/creditCard.png +0 -0
- psychopy/assets/default.mp3 +0 -0
- psychopy/assets/default.mp4 +0 -0
- psychopy/assets/default.png +0 -0
- psychopy/assets/fonts/Arvo-Bold.ttf +0 -0
- psychopy/assets/fonts/Arvo-BoldItalic.ttf +0 -0
- psychopy/assets/fonts/Arvo-Italic.ttf +0 -0
- psychopy/assets/fonts/Arvo-Regular.ttf +0 -0
- psychopy/assets/fonts/DejaVuSerif.ttf +0 -0
- psychopy/assets/fonts/IndieFlower-Regular.ttf +0 -0
- psychopy/assets/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- psychopy/assets/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
- psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/templates/instruct1.png +0 -0
- psychopy/assets/templates/instruct2.png +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/voicekeyThresholdStim.wav +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +619 -0
- psychopy/colors.py +1058 -0
- psychopy/compatibility.py +147 -0
- psychopy/constants.py +87 -0
- psychopy/contrib/__init__.py +0 -0
- psychopy/contrib/configobj/LICENSE +39 -0
- psychopy/contrib/configobj/__init__.py +2455 -0
- psychopy/contrib/configobj/_version.py +2 -0
- psychopy/contrib/configobj/validate.py +1458 -0
- psychopy/contrib/lazy_import.py +402 -0
- psychopy/contrib/mseq.py +273 -0
- psychopy/contrib/mseqSearch.py +183 -0
- psychopy/contrib/psi.py +100 -0
- psychopy/contrib/quest.py +484 -0
- psychopy/contrib/tesselate.py +188 -0
- psychopy/core.py +167 -0
- psychopy/data/__init__.py +45 -0
- psychopy/data/base.py +566 -0
- psychopy/data/counterbalance.py +210 -0
- psychopy/data/experiment.py +1049 -0
- psychopy/data/fit.py +248 -0
- psychopy/data/routine.py +94 -0
- psychopy/data/shelf.py +237 -0
- psychopy/data/staircase.py +2270 -0
- psychopy/data/trial.py +2505 -0
- psychopy/data/utils.py +858 -0
- psychopy/demos/__init__.py +0 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/README.md +5 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +354 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Design Templates/psychophysicsStaircase/README.md +20 -0
- psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +286 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/README.md +14 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +340 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/stairDefinitions.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/README.md +18 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/chooseBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/facesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/housesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +219 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house03.jpg +0 -0
- psychopy/demos/builder/Experiments/BART/README.md +30 -0
- psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
- psychopy/demos/builder/Experiments/BART/assets/background.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/bang.mp3 +0 -0
- psychopy/demos/builder/Experiments/BART/assets/bang.wav +0 -0
- psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/bart.psyexp +844 -0
- psychopy/demos/builder/Experiments/BART/spreadsheets/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +255 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/README.md +23 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/TIPI.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/bigFiveItems.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/demographics.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/mini_IPIP.xlsx +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +462 -0
- psychopy/demos/builder/Experiments/GoNoGo/go.png +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/nogo.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/README.md +5 -0
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +494 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/goNoGo/readme.md +14 -0
- psychopy/demos/builder/Experiments/mentalRotation/F.png +0 -0
- psychopy/demos/builder/Experiments/mentalRotation/FR.png +0 -0
- psychopy/demos/builder/Experiments/mentalRotation/MentalRot.csv +33 -0
- psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +646 -0
- psychopy/demos/builder/Experiments/mentalRotation/README.md +23 -0
- psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +509 -0
- psychopy/demos/builder/Experiments/navon/README.md +26 -0
- psychopy/demos/builder/Experiments/navon/bigHsmallH.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigHsmallS.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigSsmallH.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigSsmallS.png +0 -0
- psychopy/demos/builder/Experiments/navon/mask.png +0 -0
- psychopy/demos/builder/Experiments/navon/stimuli.pptx +0 -0
- psychopy/demos/builder/Experiments/navon/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/README.md +14 -0
- psychopy/demos/builder/Experiments/sternberg/mainTrials.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/pracTrials.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +649 -0
- psychopy/demos/builder/Experiments/stroop/README.md +15 -0
- psychopy/demos/builder/Experiments/stroop/stroop.psyexp +321 -0
- psychopy/demos/builder/Experiments/stroop/trialTypes.csv +7 -0
- psychopy/demos/builder/Experiments/stroopExtended/README.md +14 -0
- psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +418 -0
- psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +418 -0
- psychopy/demos/builder/Experiments/stroopExtended/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopExtended/trialTypesReverse.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/README.md +27 -0
- psychopy/demos/builder/Experiments/stroopVoice/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +382 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +371 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +308 -0
- psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +391 -0
- psychopy/demos/builder/Feature Demos/gratings/readme.md +8 -0
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/noise/face.jpg +0 -0
- psychopy/demos/builder/Feature Demos/noise/noise.psyexp +477 -0
- psychopy/demos/builder/Feature Demos/noise/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/panorama/panImg.jpg +0 -0
- psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +177 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +434 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +451 -0
- psychopy/demos/builder/Feature Demos/progress/readme.md +1 -0
- psychopy/demos/builder/Feature Demos/sliders/README.md +8 -0
- psychopy/demos/builder/Feature Demos/sliders/fruitConditions.xlsx +0 -0
- psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +984 -0
- psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +199 -0
- psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +639 -0
- psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +415 -0
- psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +659 -0
- psychopy/demos/builder/Hardware/EGI_netstation/README.md +17 -0
- psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +339 -0
- psychopy/demos/builder/Hardware/EGI_netstation/trialTypesEEG.csv +7 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/readme.md +19 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/trials_params.xlsx +0 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +830 -0
- psychopy/demos/builder/Hardware/camera/camera.psyexp +330 -0
- psychopy/demos/builder/Hardware/camera/readme.md +6 -0
- psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +459 -0
- psychopy/demos/builder/Hardware/eyetracking/readme.md +7 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/Bullseye_grey.mov +0 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +468 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/readme.md +7 -0
- psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +211 -0
- psychopy/demos/builder/Hardware/fMRI/readme.md +7 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +484 -0
- psychopy/demos/builder/Hardware/microphone/phrases.xlsx +0 -0
- psychopy/demos/builder/Hardware/microphone/readme.md +9 -0
- psychopy/demos/builder/Hardware/pump/README.md +19 -0
- psychopy/demos/builder/Hardware/pump/pump.psyexp +646 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +350 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/readme.md +5 -0
- psychopy/demos/builder/Helper Tools/clockFace/README.md +3 -0
- psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +211 -0
- psychopy/demos/builder/Helper Tools/colors/README.md +5 -0
- psychopy/demos/builder/Helper Tools/colors/colors.psyexp +321 -0
- psychopy/demos/builder/Helper Tools/drawPolygon/README.md +5 -0
- psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +704 -0
- psychopy/demos/builder/Helper Tools/keyNameFinder/README.md +14 -0
- psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +225 -0
- psychopy/demos/builder/Helper Tools/spatialUnits/README.md +6 -0
- psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +206 -0
- psychopy/demos/builder/README.txt +43 -0
- psychopy/demos/builder/__init__.py +0 -0
- psychopy/demos/coder/__init__.py +0 -0
- psychopy/demos/coder/basic/hello_world.py +37 -0
- psychopy/demos/coder/csvFromPsydat.py +29 -0
- psychopy/demos/coder/experiment control/JND_staircase_analysis.py +85 -0
- psychopy/demos/coder/experiment control/JND_staircase_exp.py +110 -0
- psychopy/demos/coder/experiment control/TrialHandler.py +64 -0
- psychopy/demos/coder/experiment control/TrialHandler2.py +42 -0
- psychopy/demos/coder/experiment control/__init__.py +0 -0
- psychopy/demos/coder/experiment control/autoDraw_autoLog.py +50 -0
- psychopy/demos/coder/experiment control/experimentHandler.py +57 -0
- psychopy/demos/coder/experiment control/fMRI_launchScan.py +74 -0
- psychopy/demos/coder/experiment control/gammaMotionAnalysis.py +68 -0
- psychopy/demos/coder/experiment control/gammaMotionNull.py +169 -0
- psychopy/demos/coder/experiment control/logFiles.py +55 -0
- psychopy/demos/coder/experiment control/piloting.py +65 -0
- psychopy/demos/coder/experiment control/runtimeInfo.py +79 -0
- psychopy/demos/coder/hardware/CRS_BitsBox.py +75 -0
- psychopy/demos/coder/hardware/CRS_BitsPlusPlus.py +73 -0
- psychopy/demos/coder/hardware/RiftHeadTrackingExample.py +95 -0
- psychopy/demos/coder/hardware/RiftMinimal.py +44 -0
- psychopy/demos/coder/hardware/VSHD_Distortion.py +49 -0
- psychopy/demos/coder/hardware/__init__.py +0 -0
- psychopy/demos/coder/hardware/camera.py +90 -0
- psychopy/demos/coder/hardware/cedrusRB730.py +38 -0
- psychopy/demos/coder/hardware/crsBitsAdvancedDemo.py +780 -0
- psychopy/demos/coder/hardware/egi_netstation.py +45 -0
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/demos/coder/hardware/ioLab_bbox.py +62 -0
- psychopy/demos/coder/hardware/labjack_u3.py +61 -0
- psychopy/demos/coder/hardware/monitorDemo.py +28 -0
- psychopy/demos/coder/hardware/parallelPortOutput.py +49 -0
- psychopy/demos/coder/hardware/qmixPump.py +79 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +127 -0
- psychopy/demos/coder/input/GUI.py +52 -0
- psychopy/demos/coder/input/__init__.py +0 -0
- psychopy/demos/coder/input/customMouse.py +51 -0
- psychopy/demos/coder/input/joystick_universal.py +84 -0
- psychopy/demos/coder/input/keyNameFinder.py +39 -0
- psychopy/demos/coder/input/mic.png +0 -0
- psychopy/demos/coder/input/mouse.py +66 -0
- psychopy/demos/coder/iohub/delaytest.py +231 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/canal.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/fall.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/lake.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/party.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/swimming.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByConditionVariables.py +67 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByMessages.py +34 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/run.py +323 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/trial_conditions.xlsx +0 -0
- psychopy/demos/coder/iohub/eyetracking/simple.py +147 -0
- psychopy/demos/coder/iohub/eyetracking/validation.py +251 -0
- psychopy/demos/coder/iohub/iodatastore/saveEventReport.py +56 -0
- psychopy/demos/coder/iohub/keyboard.py +189 -0
- psychopy/demos/coder/iohub/keyboardreactiontime.py +88 -0
- psychopy/demos/coder/iohub/launchHub.py +160 -0
- psychopy/demos/coder/iohub/mouse.py +112 -0
- psychopy/demos/coder/iohub/mouse_multi_window.py +124 -0
- psychopy/demos/coder/iohub/serial/_parseserial.py +53 -0
- psychopy/demos/coder/iohub/serial/customparser.py +84 -0
- psychopy/demos/coder/iohub/serial/pstbox.py +166 -0
- psychopy/demos/coder/iohub/wintab/_wintabgraphics.py +281 -0
- psychopy/demos/coder/iohub/wintab/pen_demo.py +260 -0
- psychopy/demos/coder/misc/encrypt_data.py +48 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/demos/coder/misc/makeMovie.py +28 -0
- psychopy/demos/coder/misc/rigidBodyTransform.py +76 -0
- psychopy/demos/coder/stimuli/Campaign.ttf +0 -0
- psychopy/demos/coder/stimuli/MovieStim.py +65 -0
- psychopy/demos/coder/stimuli/__init__.py +0 -0
- psychopy/demos/coder/stimuli/aperture.py +37 -0
- psychopy/demos/coder/stimuli/beach.jpg +0 -0
- psychopy/demos/coder/stimuli/bufferImageStim.py +78 -0
- psychopy/demos/coder/stimuli/clockface.py +43 -0
- psychopy/demos/coder/stimuli/colorPalette.py +134 -0
- psychopy/demos/coder/stimuli/compare_text_timing.py +193 -0
- psychopy/demos/coder/stimuli/counterphase.py +43 -0
- psychopy/demos/coder/stimuli/customTextures.py +57 -0
- psychopy/demos/coder/stimuli/dot_gabors.py +35 -0
- psychopy/demos/coder/stimuli/dots.py +35 -0
- psychopy/demos/coder/stimuli/elementArrays.py +92 -0
- psychopy/demos/coder/stimuli/embeddedOpenGL.py +35 -0
- psychopy/demos/coder/stimuli/face.jpg +0 -0
- psychopy/demos/coder/stimuli/face_jpg.py +48 -0
- psychopy/demos/coder/stimuli/gabor.py +30 -0
- psychopy/demos/coder/stimuli/imagesAndPatches.py +51 -0
- psychopy/demos/coder/stimuli/jwpIntro.mp4 +0 -0
- psychopy/demos/coder/stimuli/kanizsa.py +41 -0
- psychopy/demos/coder/stimuli/maskReveal.py +63 -0
- psychopy/demos/coder/stimuli/plaid.py +43 -0
- psychopy/demos/coder/stimuli/ratingScale.py +183 -0
- psychopy/demos/coder/stimuli/rotatingFlashingWedge.py +37 -0
- psychopy/demos/coder/stimuli/screensAndWindows.py +58 -0
- psychopy/demos/coder/stimuli/secondOrderGratings.py +66 -0
- psychopy/demos/coder/stimuli/shapeContains.py +50 -0
- psychopy/demos/coder/stimuli/shapes.py +63 -0
- psychopy/demos/coder/stimuli/soundStimuli.py +49 -0
- psychopy/demos/coder/stimuli/starField.py +48 -0
- psychopy/demos/coder/stimuli/stim3d.py +107 -0
- psychopy/demos/coder/stimuli/textBoxStim/textbox_glyph_placement.py +93 -0
- psychopy/demos/coder/stimuli/textBoxStim/textbox_simple.py +71 -0
- psychopy/demos/coder/stimuli/textStimuli.py +106 -0
- psychopy/demos/coder/stimuli/textbox_editable.py +64 -0
- psychopy/demos/coder/stimuli/variousVisualStims.py +47 -0
- psychopy/demos/coder/stimuli/visual_noise.py +31 -0
- psychopy/demos/coder/sysInfo.py +56 -0
- psychopy/demos/coder/timing/__init__.py +0 -0
- psychopy/demos/coder/timing/callOnFlip.py +39 -0
- psychopy/demos/coder/timing/clocksAndTimers.py +46 -0
- psychopy/demos/coder/timing/millikeyKeyboardTimingTest.py +323 -0
- psychopy/demos/coder/timing/timeByFrames.py +80 -0
- psychopy/demos/coder/timing/timeByFramesEx.py +107 -0
- psychopy/demos/coder/understanding psychopy/colors.py +70 -0
- psychopy/demos/coder/understanding psychopy/fontLayout.py +69 -0
- psychopy/demos/demo_migration.py +214 -0
- psychopy/demos/modernizeDemos.py +16 -0
- psychopy/demos/test_demo_migration.py +155 -0
- psychopy/devices/__init__.py +8 -0
- psychopy/event.py +1339 -0
- psychopy/exceptions.py +59 -0
- psychopy/experiment/__init__.py +51 -0
- psychopy/experiment/_experiment.py +1514 -0
- psychopy/experiment/blankTemplate.xltx +0 -0
- psychopy/experiment/components/__init__.py +402 -0
- psychopy/experiment/components/_base.py +1689 -0
- psychopy/experiment/components/aperture/__init__.py +153 -0
- psychopy/experiment/components/aperture/classic/aperture.png +0 -0
- psychopy/experiment/components/aperture/classic/aperture@2x.png +0 -0
- psychopy/experiment/components/aperture/dark/aperture.png +0 -0
- psychopy/experiment/components/aperture/dark/aperture@2x.png +0 -0
- psychopy/experiment/components/aperture/light/aperture.png +0 -0
- psychopy/experiment/components/aperture/light/aperture@2x.png +0 -0
- psychopy/experiment/components/brush/__init__.py +165 -0
- psychopy/experiment/components/brush/classic/brush.png +0 -0
- psychopy/experiment/components/brush/classic/brush@2x.png +0 -0
- psychopy/experiment/components/brush/dark/brush.png +0 -0
- psychopy/experiment/components/brush/dark/brush@2x.png +0 -0
- psychopy/experiment/components/brush/light/brush.png +0 -0
- psychopy/experiment/components/brush/light/brush@2x.png +0 -0
- psychopy/experiment/components/button/__init__.py +493 -0
- psychopy/experiment/components/button/classic/button.png +0 -0
- psychopy/experiment/components/button/classic/button@2x.png +0 -0
- psychopy/experiment/components/button/dark/button.png +0 -0
- psychopy/experiment/components/button/dark/button@2x.png +0 -0
- psychopy/experiment/components/button/light/button.png +0 -0
- psychopy/experiment/components/button/light/button@2x.png +0 -0
- psychopy/experiment/components/buttonBox/__init__.py +322 -0
- psychopy/experiment/components/buttonBox/classic/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/classic/buttonBox@2x.png +0 -0
- psychopy/experiment/components/buttonBox/dark/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/dark/buttonBox@2x.png +0 -0
- psychopy/experiment/components/buttonBox/light/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/light/buttonBox@2x.png +0 -0
- psychopy/experiment/components/camera/__init__.py +381 -0
- psychopy/experiment/components/camera/classic/webcam.png +0 -0
- psychopy/experiment/components/camera/classic/webcam@2x.png +0 -0
- psychopy/experiment/components/camera/dark/webcam.png +0 -0
- psychopy/experiment/components/camera/dark/webcam@2x.png +0 -0
- psychopy/experiment/components/camera/light/webcam.png +0 -0
- psychopy/experiment/components/camera/light/webcam@2x.png +0 -0
- psychopy/experiment/components/code/__init__.py +299 -0
- psychopy/experiment/components/code/classic/code.png +0 -0
- psychopy/experiment/components/code/classic/code@2x.png +0 -0
- psychopy/experiment/components/code/dark/code.png +0 -0
- psychopy/experiment/components/code/dark/code@2x.png +0 -0
- psychopy/experiment/components/code/light/code.png +0 -0
- psychopy/experiment/components/code/light/code@2x.png +0 -0
- psychopy/experiment/components/dots/__init__.py +199 -0
- psychopy/experiment/components/dots/classic/dots.png +0 -0
- psychopy/experiment/components/dots/classic/dots@2x.png +0 -0
- psychopy/experiment/components/dots/dark/dots.png +0 -0
- psychopy/experiment/components/dots/dark/dots@2x.png +0 -0
- psychopy/experiment/components/dots/dots.xcf +0 -0
- psychopy/experiment/components/dots/light/dots.png +0 -0
- psychopy/experiment/components/dots/light/dots@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +194 -0
- psychopy/experiment/components/eyetracker_record/classic/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/classic/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/dark/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/dark/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/light/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/light/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/form/__init__.py +227 -0
- psychopy/experiment/components/form/classic/form.png +0 -0
- psychopy/experiment/components/form/classic/form@2x.png +0 -0
- psychopy/experiment/components/form/dark/form.png +0 -0
- psychopy/experiment/components/form/dark/form@2x.png +0 -0
- psychopy/experiment/components/form/formItems.xltx +0 -0
- psychopy/experiment/components/form/light/form.png +0 -0
- psychopy/experiment/components/form/light/form@2x.png +0 -0
- psychopy/experiment/components/grating/__init__.py +203 -0
- psychopy/experiment/components/grating/classic/grating.png +0 -0
- psychopy/experiment/components/grating/classic/grating@2x.png +0 -0
- psychopy/experiment/components/grating/dark/grating.png +0 -0
- psychopy/experiment/components/grating/dark/grating@2x.png +0 -0
- psychopy/experiment/components/grating/light/grating.png +0 -0
- psychopy/experiment/components/grating/light/grating@2x.png +0 -0
- psychopy/experiment/components/image/__init__.py +195 -0
- psychopy/experiment/components/image/classic/image.png +0 -0
- psychopy/experiment/components/image/classic/image@2x.png +0 -0
- psychopy/experiment/components/image/dark/image.png +0 -0
- psychopy/experiment/components/image/dark/image@2x.png +0 -0
- psychopy/experiment/components/image/light/image.png +0 -0
- psychopy/experiment/components/image/light/image@2x.png +0 -0
- psychopy/experiment/components/joyButtons/__init__.py +453 -0
- psychopy/experiment/components/joyButtons/classic/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/classic/joybuttons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/dark/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/dark/joyButtons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/light/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/light/joyButtons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/virtualJoyButtons.py +34 -0
- psychopy/experiment/components/joystick/__init__.py +572 -0
- psychopy/experiment/components/joystick/classic/joystick.png +0 -0
- psychopy/experiment/components/joystick/classic/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/dark/joystick.png +0 -0
- psychopy/experiment/components/joystick/dark/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/light/joystick.png +0 -0
- psychopy/experiment/components/joystick/light/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/virtualJoystick.py +40 -0
- psychopy/experiment/components/keyboard/__init__.py +568 -0
- psychopy/experiment/components/keyboard/classic/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/classic/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard/dark/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/dark/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard/light/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/light/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard.xcf +0 -0
- psychopy/experiment/components/microphone/__init__.py +600 -0
- psychopy/experiment/components/microphone/classic/microphone.png +0 -0
- psychopy/experiment/components/microphone/classic/microphone@2x.png +0 -0
- psychopy/experiment/components/microphone/dark/microphone.png +0 -0
- psychopy/experiment/components/microphone/dark/microphone@2x.png +0 -0
- psychopy/experiment/components/microphone/light/microphone.png +0 -0
- psychopy/experiment/components/microphone/light/microphone@2x.png +0 -0
- psychopy/experiment/components/mouse/__init__.py +773 -0
- psychopy/experiment/components/mouse/classic/mouse.png +0 -0
- psychopy/experiment/components/mouse/classic/mouse@2x.png +0 -0
- psychopy/experiment/components/mouse/dark/mouse.png +0 -0
- psychopy/experiment/components/mouse/dark/mouse@2x.png +0 -0
- psychopy/experiment/components/mouse/light/mouse.png +0 -0
- psychopy/experiment/components/mouse/light/mouse@2x.png +0 -0
- psychopy/experiment/components/movie/__init__.py +348 -0
- psychopy/experiment/components/movie/classic/movie.png +0 -0
- psychopy/experiment/components/movie/classic/movie@2x.png +0 -0
- psychopy/experiment/components/movie/dark/movie.png +0 -0
- psychopy/experiment/components/movie/dark/movie@2x.png +0 -0
- psychopy/experiment/components/movie/light/movie.png +0 -0
- psychopy/experiment/components/movie/light/movie@2x.png +0 -0
- psychopy/experiment/components/panorama/__init__.py +456 -0
- psychopy/experiment/components/panorama/classic/panorama.png +0 -0
- psychopy/experiment/components/panorama/classic/panorama@2x.png +0 -0
- psychopy/experiment/components/panorama/dark/panorama.png +0 -0
- psychopy/experiment/components/panorama/dark/panorama@2x.png +0 -0
- psychopy/experiment/components/panorama/light/panorama.png +0 -0
- psychopy/experiment/components/panorama/light/panorama@2x.png +0 -0
- psychopy/experiment/components/parallelOut/__init__.py +178 -0
- psychopy/experiment/components/parallelOut/classic/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/classic/parallel@2x.png +0 -0
- psychopy/experiment/components/parallelOut/dark/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/dark/parallel@2x.png +0 -0
- psychopy/experiment/components/parallelOut/light/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/light/parallel@2x.png +0 -0
- psychopy/experiment/components/polygon/__init__.py +332 -0
- psychopy/experiment/components/polygon/classic/polygon.png +0 -0
- psychopy/experiment/components/polygon/classic/polygon@2x.png +0 -0
- psychopy/experiment/components/polygon/dark/polygon.png +0 -0
- psychopy/experiment/components/polygon/dark/polygon@2x.png +0 -0
- psychopy/experiment/components/polygon/light/polygon.png +0 -0
- psychopy/experiment/components/polygon/light/polygon@2x.png +0 -0
- psychopy/experiment/components/progress/__init__.py +130 -0
- psychopy/experiment/components/progress/classic/progress.png +0 -0
- psychopy/experiment/components/progress/classic/progress@2x.png +0 -0
- psychopy/experiment/components/progress/dark/progress.png +0 -0
- psychopy/experiment/components/progress/dark/progress@2x.png +0 -0
- psychopy/experiment/components/progress/light/progress.png +0 -0
- psychopy/experiment/components/progress/light/progress@2x.png +0 -0
- psychopy/experiment/components/resourceManager/__init__.py +176 -0
- psychopy/experiment/components/resourceManager/classic/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/classic/resource_manager@2x.png +0 -0
- psychopy/experiment/components/resourceManager/dark/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/dark/resource_manager@2x.png +0 -0
- psychopy/experiment/components/resourceManager/light/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/light/resource_manager@2x.png +0 -0
- psychopy/experiment/components/roi/__init__.py +316 -0
- psychopy/experiment/components/roi/classic/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/classic/eyetracker_roi@2x.png +0 -0
- psychopy/experiment/components/roi/dark/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/dark/eyetracker_roi@2X.png +0 -0
- psychopy/experiment/components/roi/light/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/light/eyetracker_roi@2X.png +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +387 -0
- psychopy/experiment/components/routineSettings/classic/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/classic/routineSettings@2x.png +0 -0
- psychopy/experiment/components/routineSettings/dark/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/dark/routineSettings@2x.png +0 -0
- psychopy/experiment/components/routineSettings/light/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/light/routineSettings@2x.png +0 -0
- psychopy/experiment/components/serialOut/__init__.py +304 -0
- psychopy/experiment/components/serialOut/classic/serial.png +0 -0
- psychopy/experiment/components/serialOut/classic/serial@2x.png +0 -0
- psychopy/experiment/components/serialOut/dark/serial.png +0 -0
- psychopy/experiment/components/serialOut/dark/serial@2x.png +0 -0
- psychopy/experiment/components/serialOut/light/serial.png +0 -0
- psychopy/experiment/components/serialOut/light/serial@2x.png +0 -0
- psychopy/experiment/components/settings/JS_htmlHeader.tmpl +23 -0
- psychopy/experiment/components/settings/JS_setupExp.tmpl +28 -0
- psychopy/experiment/components/settings/__init__.py +2254 -0
- psychopy/experiment/components/settings/classic/settings.png +0 -0
- psychopy/experiment/components/settings/classic/settings@2x.png +0 -0
- psychopy/experiment/components/settings/dark/settings.png +0 -0
- psychopy/experiment/components/settings/dark/settings@2x.png +0 -0
- psychopy/experiment/components/settings/eyetracking.py +108 -0
- psychopy/experiment/components/settings/light/settings.png +0 -0
- psychopy/experiment/components/settings/light/settings@2x.png +0 -0
- psychopy/experiment/components/slider/__init__.py +410 -0
- psychopy/experiment/components/slider/classic/slider.png +0 -0
- psychopy/experiment/components/slider/classic/slider@2x.png +0 -0
- psychopy/experiment/components/slider/dark/slider.png +0 -0
- psychopy/experiment/components/slider/dark/slider@2x.png +0 -0
- psychopy/experiment/components/slider/light/slider.png +0 -0
- psychopy/experiment/components/slider/light/slider@2x.png +0 -0
- psychopy/experiment/components/sound/__init__.py +441 -0
- psychopy/experiment/components/sound/classic/sound.png +0 -0
- psychopy/experiment/components/sound/classic/sound@2x.png +0 -0
- psychopy/experiment/components/sound/dark/sound.png +0 -0
- psychopy/experiment/components/sound/dark/sound@2x.png +0 -0
- psychopy/experiment/components/sound/light/sound.png +0 -0
- psychopy/experiment/components/sound/light/sound@2x.png +0 -0
- psychopy/experiment/components/soundsensor/__init__.py +324 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
- psychopy/experiment/components/static/__init__.py +293 -0
- psychopy/experiment/components/static/classic/static.png +0 -0
- psychopy/experiment/components/static/classic/static@2x.png +0 -0
- psychopy/experiment/components/static/dark/static.png +0 -0
- psychopy/experiment/components/static/dark/static@2x.png +0 -0
- psychopy/experiment/components/static/light/static.png +0 -0
- psychopy/experiment/components/static/light/static@2x.png +0 -0
- psychopy/experiment/components/text/__init__.py +190 -0
- psychopy/experiment/components/text/classic/text.png +0 -0
- psychopy/experiment/components/text/classic/text@2x.png +0 -0
- psychopy/experiment/components/text/dark/text.png +0 -0
- psychopy/experiment/components/text/dark/text@2x.png +0 -0
- psychopy/experiment/components/text/light/text.png +0 -0
- psychopy/experiment/components/text/light/text@2x.png +0 -0
- psychopy/experiment/components/textbox/__init__.py +336 -0
- psychopy/experiment/components/textbox/classic/textbox.png +0 -0
- psychopy/experiment/components/textbox/classic/textbox@2x.png +0 -0
- psychopy/experiment/components/textbox/dark/textbox.png +0 -0
- psychopy/experiment/components/textbox/dark/textbox@2x.png +0 -0
- psychopy/experiment/components/textbox/light/textbox.png +0 -0
- psychopy/experiment/components/textbox/light/textbox@2x.png +0 -0
- psychopy/experiment/components/unknown/__init__.py +87 -0
- psychopy/experiment/components/unknown/classic/unknown.png +0 -0
- psychopy/experiment/components/unknown/classic/unknown@2x.png +0 -0
- psychopy/experiment/components/unknown/dark/unknown.png +0 -0
- psychopy/experiment/components/unknown/dark/unknown@2x.png +0 -0
- psychopy/experiment/components/unknown/light/unknown.png +0 -0
- psychopy/experiment/components/unknown/light/unknown@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +86 -0
- psychopy/experiment/components/unknownPlugin/classic/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/classic/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/dark/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/dark/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/light/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/light/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/utils.py +0 -0
- psychopy/experiment/components/variable/__init__.py +184 -0
- psychopy/experiment/components/variable/classic/variable.png +0 -0
- psychopy/experiment/components/variable/classic/variable@2x.png +0 -0
- psychopy/experiment/components/variable/dark/variable.png +0 -0
- psychopy/experiment/components/variable/dark/variable@2x.png +0 -0
- psychopy/experiment/components/variable/light/variable.png +0 -0
- psychopy/experiment/components/variable/light/variable@2x.png +0 -0
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/experiment.xsd +223 -0
- psychopy/experiment/exports.py +390 -0
- psychopy/experiment/flow.py +547 -0
- psychopy/experiment/localization.py +11 -0
- psychopy/experiment/loopTemplate.xltx +0 -0
- psychopy/experiment/loops.py +1051 -0
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +541 -0
- psychopy/experiment/plugins.py +42 -0
- psychopy/experiment/py2js.py +183 -0
- psychopy/experiment/py2js_transpiler.py +613 -0
- psychopy/experiment/questPlusTemplate.xltx +0 -0
- psychopy/experiment/questTemplate.xltx +0 -0
- psychopy/experiment/routines/__init__.py +108 -0
- psychopy/experiment/routines/_base.py +1172 -0
- psychopy/experiment/routines/audioValidator/__init__.py +229 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +320 -0
- psychopy/experiment/routines/counterbalance/classic/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/classic/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/counterbalanceItems.xltx +0 -0
- psychopy/experiment/routines/counterbalance/dark/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/dark/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/light/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/light/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/__init__.py +334 -0
- psychopy/experiment/routines/eyetracker_calibrate/classic/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/__init__.py +354 -0
- psychopy/experiment/routines/eyetracker_validate/classic/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/__init__.py +236 -0
- psychopy/experiment/routines/pavlovia_survey/classic/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/classic/survey@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/dark/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/dark/survey@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/light/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/light/survey@2x.png +0 -0
- psychopy/experiment/routines/unknown/__init__.py +30 -0
- psychopy/experiment/routines/unknown/classic/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/classic/unknown.png +0 -0
- psychopy/experiment/routines/unknown/dark/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/dark/eyetracker_record@2x.png +0 -0
- psychopy/experiment/routines/unknown/dark/unknown.png +0 -0
- psychopy/experiment/routines/unknown/dark/unknown@2x.png +0 -0
- psychopy/experiment/routines/unknown/light/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/light/eyetracker_record@2x.png +0 -0
- psychopy/experiment/routines/unknown/light/unknown.png +0 -0
- psychopy/experiment/routines/unknown/light/unknown@2x.png +0 -0
- psychopy/experiment/routines/utils.py +0 -0
- psychopy/experiment/routines/visualValidator/__init__.py +395 -0
- psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
- psychopy/experiment/staircaseTemplate.xltx +0 -0
- psychopy/experiment/utils.py +44 -0
- psychopy/filters.py +11 -0
- psychopy/gamma.py +9 -0
- psychopy/gui/__init__.py +47 -0
- psychopy/gui/qtgui.py +882 -0
- psychopy/gui/util.py +74 -0
- psychopy/gui/wxgui.py +420 -0
- psychopy/hardware/__init__.py +178 -0
- psychopy/hardware/base.py +466 -0
- psychopy/hardware/bbtk/__init__.py +24 -0
- psychopy/hardware/brainproducts.py +25 -0
- psychopy/hardware/button.py +238 -0
- psychopy/hardware/buttonbox/__init__.py +122 -0
- psychopy/hardware/camera/__init__.py +3657 -0
- psychopy/hardware/cedrus.py +24 -0
- psychopy/hardware/crs/__init__.py +46 -0
- psychopy/hardware/crs/bits.py +37 -0
- psychopy/hardware/crs/colorcal.py +18 -0
- psychopy/hardware/crs/optical.py +33 -0
- psychopy/hardware/crs/shaders.py +34 -0
- psychopy/hardware/emotiv.py +33 -0
- psychopy/hardware/emulator.py +46 -0
- psychopy/hardware/exceptions.py +86 -0
- psychopy/hardware/eyetracker.py +132 -0
- psychopy/hardware/forp.py +37 -0
- psychopy/hardware/gammasci.py +26 -0
- psychopy/hardware/iolab.py +24 -0
- psychopy/hardware/joystick/__init__.py +1196 -0
- psychopy/hardware/joystick/_base.py +251 -0
- psychopy/hardware/joystick/backend_glfw.py +306 -0
- psychopy/hardware/joystick/backend_pyglet.py +309 -0
- psychopy/hardware/joystick/mappings.py +287 -0
- psychopy/hardware/keyboard.py +975 -0
- psychopy/hardware/knownDevices.json +52 -0
- psychopy/hardware/labhackers.py +27 -0
- psychopy/hardware/labjacks.py +26 -0
- psychopy/hardware/lightsensor.py +789 -0
- psychopy/hardware/listener.py +330 -0
- psychopy/hardware/manager.py +992 -0
- psychopy/hardware/microphone.py +1630 -0
- psychopy/hardware/minolta.py +33 -0
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/mouse/__init__.py +885 -0
- psychopy/hardware/photometer/__init__.py +207 -0
- psychopy/hardware/pr.py +33 -0
- psychopy/hardware/qmix.py +32 -0
- psychopy/hardware/serialdevice.py +387 -0
- psychopy/hardware/soundsensor.py +476 -0
- psychopy/hardware/spatial/__init__.py +231 -0
- psychopy/hardware/speaker.py +351 -0
- psychopy/hardware/triggerbox/__init__.py +56 -0
- psychopy/hardware/triggerbox/base.py +170 -0
- psychopy/hardware/triggerbox/parallel.py +362 -0
- psychopy/info.py +719 -0
- psychopy/iohub/__init__.py +50 -0
- psychopy/iohub/client/__init__.py +1481 -0
- psychopy/iohub/client/connect.py +260 -0
- psychopy/iohub/client/eyetracker/__init__.py +4 -0
- psychopy/iohub/client/eyetracker/validation/__init__.py +8 -0
- psychopy/iohub/client/eyetracker/validation/posgrid.py +274 -0
- psychopy/iohub/client/eyetracker/validation/procedure.py +1292 -0
- psychopy/iohub/client/eyetracker/validation/trigger.py +240 -0
- psychopy/iohub/client/keyboard.py +513 -0
- psychopy/iohub/client/wintab.py +378 -0
- psychopy/iohub/constants.py +1176 -0
- psychopy/iohub/datastore/__init__.py +439 -0
- psychopy/iohub/datastore/default_datastore.yaml +8 -0
- psychopy/iohub/datastore/util.py +868 -0
- psychopy/iohub/default_config.yaml +17 -0
- psychopy/iohub/devices/__init__.py +1047 -0
- psychopy/iohub/devices/computer.py +607 -0
- psychopy/iohub/devices/default_device.yaml +15 -0
- psychopy/iohub/devices/deviceConfigValidation.py +519 -0
- psychopy/iohub/devices/display/__init__.py +755 -0
- psychopy/iohub/devices/display/default_display.yaml +118 -0
- psychopy/iohub/devices/display/supported_config_settings.yaml +69 -0
- psychopy/iohub/devices/eventfilters.py +3572 -0
- psychopy/iohub/devices/experiment/__init__.py +316 -0
- psychopy/iohub/devices/experiment/default_experiment.yaml +74 -0
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +26 -0
- psychopy/iohub/devices/eyetracker/__init__.py +474 -0
- psychopy/iohub/devices/eyetracker/calibration/__init__.py +1 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +391 -0
- psychopy/iohub/devices/eyetracker/default_eyetracker.yaml +18 -0
- psychopy/iohub/devices/eyetracker/eye_events.py +1751 -0
- psychopy/iohub/devices/eyetracker/filters/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/filters/parser.py +913 -0
- psychopy/iohub/devices/eyetracker/hw/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +31 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/__init__.py +8 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/calibration.py +10 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/default_eyetracker.yaml +102 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +443 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/supported_config_settings.yaml +110 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +27 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/eyetracker.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/bisector.py +20 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/constants.py +19 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/data_parse.py +22 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/eyetracker.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/pupil_remote.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/__init__.py +28 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/calibration.py +22 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/eyetracker.py +38 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/calibration.py +19 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/eyetracker.py +17 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/tobiiwrapper.py +19 -0
- psychopy/iohub/devices/eyetracker/supported_config_settings.yaml +44 -0
- psychopy/iohub/devices/keyboard/__init__.py +309 -0
- psychopy/iohub/devices/keyboard/darwin.py +431 -0
- psychopy/iohub/devices/keyboard/darwinkey.py +94 -0
- psychopy/iohub/devices/keyboard/default_keyboard.yaml +94 -0
- psychopy/iohub/devices/keyboard/linux2.py +102 -0
- psychopy/iohub/devices/keyboard/supported_config_settings.yaml +39 -0
- psychopy/iohub/devices/keyboard/win32.py +356 -0
- psychopy/iohub/devices/mouse/__init__.py +709 -0
- psychopy/iohub/devices/mouse/darwin.py +414 -0
- psychopy/iohub/devices/mouse/default_mouse.yaml +92 -0
- psychopy/iohub/devices/mouse/linux2.py +172 -0
- psychopy/iohub/devices/mouse/supported_config_settings.yaml +39 -0
- psychopy/iohub/devices/mouse/win32.py +284 -0
- psychopy/iohub/devices/pyXHook.py +619 -0
- psychopy/iohub/devices/serial/__init__.py +779 -0
- psychopy/iohub/devices/serial/default_pstbox.yaml +108 -0
- psychopy/iohub/devices/serial/default_serial.yaml +108 -0
- psychopy/iohub/devices/serial/supported_config_settings.yaml +85 -0
- psychopy/iohub/devices/serial/supported_config_settings_pstbox.yaml +85 -0
- psychopy/iohub/devices/supported_config_settings.yaml +36 -0
- psychopy/iohub/devices/wintab/__init__.py +580 -0
- psychopy/iohub/devices/wintab/default_wintab.yaml +89 -0
- psychopy/iohub/devices/wintab/supported_config_settings.yaml +42 -0
- psychopy/iohub/devices/wintab/win32.py +722 -0
- psychopy/iohub/devices/xlib.py +6795 -0
- psychopy/iohub/errors.py +61 -0
- psychopy/iohub/lazy_import.py +582 -0
- psychopy/iohub/net.py +349 -0
- psychopy/iohub/server.py +1075 -0
- psychopy/iohub/start_iohub_process.py +121 -0
- psychopy/iohub/util/__init__.py +832 -0
- psychopy/iohub/util/visualangle.py +126 -0
- psychopy/layout.py +916 -0
- psychopy/liaison.py +471 -0
- psychopy/locale_setup.py +41 -0
- psychopy/localization/__init__.py +22 -0
- psychopy/localization/_localization.py +155 -0
- psychopy/localization/generateTranslationTemplate.py +257 -0
- psychopy/localization/mappings.txt +170 -0
- psychopy/localization/messages.pot +9950 -0
- psychopy/localization/readme.txt +68 -0
- psychopy/logging.py +415 -0
- psychopy/microphone.py +28 -0
- psychopy/misc.py +40 -0
- psychopy/monitors/MonitorCenter.py +1354 -0
- psychopy/monitors/__init__.py +30 -0
- psychopy/monitors/calibData.py +78 -0
- psychopy/monitors/calibTools.py +1301 -0
- psychopy/monitors/getLumSeries.py +50 -0
- psychopy/monitors/psychopy-icon.svg +395 -0
- psychopy/monitors/psychopy.ico +0 -0
- psychopy/parallel/__init__.py +206 -0
- psychopy/parallel/_dlportio.py +150 -0
- psychopy/parallel/_inpout.py +120 -0
- psychopy/parallel/_linux.py +105 -0
- psychopy/piloting.py +61 -0
- psychopy/platform_specific/__init__.py +46 -0
- psychopy/platform_specific/darwin.py +326 -0
- psychopy/platform_specific/linux.py +77 -0
- psychopy/platform_specific/win32.py +124 -0
- psychopy/plugins/__init__.py +1397 -0
- psychopy/plugins/util.py +104 -0
- psychopy/preferences/Darwin.spec +294 -0
- psychopy/preferences/FreeBSD.spec +294 -0
- psychopy/preferences/Linux.spec +294 -0
- psychopy/preferences/Windows.spec +294 -0
- psychopy/preferences/__init__.py +20 -0
- psychopy/preferences/baseNoArch.spec +290 -0
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +93 -0
- psychopy/preferences/generateSpec.py +80 -0
- psychopy/preferences/hints.py +367 -0
- psychopy/preferences/preferences.py +448 -0
- psychopy/projects/__init__.py +11 -0
- psychopy/projects/gitignore.py +45 -0
- psychopy/projects/pavlovia.py +1674 -0
- psychopy/projects/sshkeys.py +107 -0
- psychopy/psychojs.zip +0 -0
- psychopy/pylintrc +69 -0
- psychopy/scripts/__init__.py +0 -0
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +229 -0
- psychopy/session.py +1651 -0
- psychopy/sound/__init__.py +50 -0
- psychopy/sound/_base.py +399 -0
- psychopy/sound/audioclip.py +1325 -0
- psychopy/sound/audiodevice.py +857 -0
- psychopy/sound/backend_ptb.py +406 -0
- psychopy/sound/backend_pygame.py +323 -0
- psychopy/sound/backend_pyo.py +64 -0
- psychopy/sound/backend_pysound.py +324 -0
- psychopy/sound/backend_sounddevice.py +44 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/exceptions.py +115 -0
- psychopy/sound/microphone.py +357 -0
- psychopy/sound/sound.py +58 -0
- psychopy/sound/transcribe.py +1321 -0
- psychopy/tests/README.md +344 -0
- psychopy/tests/__init__.py +47 -0
- psychopy/tests/data/Electronic_Chime-KevanGC-495939803.wav +0 -0
- psychopy/tests/data/TestCircle_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s1.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s1.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s1.png +0 -0
- psychopy/tests/data/TestImage_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestImage_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestImage_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestImage_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestImage_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestImage_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestImage_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestImage_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestImage_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestImage_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestImage_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestImage_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_minmax_0.png +0 -0
- psychopy/tests/data/TestProgress_testValue_minmax_1.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_center center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_center center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_top center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_top center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestROI_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestROI_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestROI_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestROI_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestROI_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestROI_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestROI_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestROI_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestROI_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestROI_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestROI_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestROI_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestSession_exp1.png +0 -0
- psychopy/tests/data/TestSession_exp2.png +0 -0
- psychopy/tests/data/TestShape_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestShape_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestShape_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestShape_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestShape_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestShape_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestShape_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestShape_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestShape_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestShape_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestShape_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestShape_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_0p6.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_0p8.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p2.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p4.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p6.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p8.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_2p0.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_None.png +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_-3ovr8_0ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_-3ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_3ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_3ovr8_0ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_top_right.png +0 -0
- psychopy/tests/data/TextComponent_disabled.psyexp +63 -0
- psychopy/tests/data/TextComponent_not_disabled.psyexp +63 -0
- psychopy/tests/data/aperture1_deg.png +0 -0
- psychopy/tests/data/aperture1_degFlat.png +0 -0
- psychopy/tests/data/aperture1_degFlatPos.png +0 -0
- psychopy/tests/data/aperture1_norm.png +0 -0
- psychopy/tests/data/aperture1_normHexbackground.png +0 -0
- psychopy/tests/data/aperture1_normNoShade.png +0 -0
- psychopy/tests/data/aperture1_pix.png +0 -0
- psychopy/tests/data/aperture1_stencil.png +0 -0
- psychopy/tests/data/aperture2_deg.png +0 -0
- psychopy/tests/data/aperture2_degFlat.png +0 -0
- psychopy/tests/data/aperture2_degFlatPos.png +0 -0
- psychopy/tests/data/aperture2_norm.png +0 -0
- psychopy/tests/data/aperture2_normHexbackground.png +0 -0
- psychopy/tests/data/aperture2_normNoShade.png +0 -0
- psychopy/tests/data/aperture2_pix.png +0 -0
- psychopy/tests/data/aperture2_stencil.png +0 -0
- psychopy/tests/data/beatandrcos_cm.png +0 -0
- psychopy/tests/data/beatandrcos_deg.png +0 -0
- psychopy/tests/data/beatandrcos_degFlat.png +0 -0
- psychopy/tests/data/beatandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/beatandrcos_height.png +0 -0
- psychopy/tests/data/beatandrcos_norm.png +0 -0
- psychopy/tests/data/beatandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/beatandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/beatandrcos_pix.png +0 -0
- psychopy/tests/data/beatandrcos_stencil.png +0 -0
- psychopy/tests/data/blend_add_cm.png +0 -0
- psychopy/tests/data/blend_add_deg.png +0 -0
- psychopy/tests/data/blend_add_degFlat.png +0 -0
- psychopy/tests/data/blend_add_degFlatPos.png +0 -0
- psychopy/tests/data/blend_add_height.png +0 -0
- psychopy/tests/data/blend_add_norm.png +0 -0
- psychopy/tests/data/blend_add_normAddBlend.png +0 -0
- psychopy/tests/data/blend_add_normHexbackground.png +0 -0
- psychopy/tests/data/blend_add_normNoShade.png +0 -0
- psychopy/tests/data/blend_add_pix.png +0 -0
- psychopy/tests/data/blend_add_stencil.png +0 -0
- psychopy/tests/data/broken2020_2_5_resources/broken_resources.psyexp +84 -0
- psychopy/tests/data/broken2020_2_5_resources/psychopy72.png +0 -0
- psychopy/tests/data/bufferimg_gabor_cm.png +0 -0
- psychopy/tests/data/bufferimg_gabor_deg.png +0 -0
- psychopy/tests/data/bufferimg_gabor_degFlat.png +0 -0
- psychopy/tests/data/bufferimg_gabor_degFlatPos.png +0 -0
- psychopy/tests/data/bufferimg_gabor_height.png +0 -0
- psychopy/tests/data/bufferimg_gabor_norm.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normAddBlend.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normHexbackground.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normNoShade.png +0 -0
- psychopy/tests/data/bufferimg_gabor_pix.png +0 -0
- psychopy/tests/data/bufferimg_gabor_stencil.png +0 -0
- psychopy/tests/data/circleHex_cm.png +0 -0
- psychopy/tests/data/circleHex_deg.png +0 -0
- psychopy/tests/data/circleHex_degFlat.png +0 -0
- psychopy/tests/data/circleHex_degFlatPos.png +0 -0
- psychopy/tests/data/circleHex_height.png +0 -0
- psychopy/tests/data/circleHex_norm.png +0 -0
- psychopy/tests/data/circleHex_normAddBlend.png +0 -0
- psychopy/tests/data/circleHex_normHexbackground.png +0 -0
- psychopy/tests/data/circleHex_normNoShade.png +0 -0
- psychopy/tests/data/circleHex_pix.png +0 -0
- psychopy/tests/data/circleHex_stencil.png +0 -0
- psychopy/tests/data/corrFullRandom.csv +16 -0
- psychopy/tests/data/corrFullRandom.tsv +6 -0
- psychopy/tests/data/corrFullRandomTH2.csv +16 -0
- psychopy/tests/data/corrMultiKeyExpWide.csv +3 -0
- psychopy/tests/data/corrMultiKeyTrials.csv +8 -0
- psychopy/tests/data/corrMultiKeyTrials.xlsx +0 -0
- psychopy/tests/data/corrRandom.csv +16 -0
- psychopy/tests/data/corrRandom.tsv +6 -0
- psychopy/tests/data/corrRandomTH2.csv +16 -0
- psychopy/tests/data/corrXlsx.xlsx +0 -0
- psychopy/tests/data/correctScript/js/correctCodeComponent.js +181 -0
- psychopy/tests/data/correctScript/js/correctImageComponent.js +204 -0
- psychopy/tests/data/correctScript/js/correctKeyboardComponent.js +225 -0
- psychopy/tests/data/correctScript/js/correctMouseComponent.js +221 -0
- psychopy/tests/data/correctScript/js/correctMovieComponent.js +210 -0
- psychopy/tests/data/correctScript/js/correctPolygonComponent.js +205 -0
- psychopy/tests/data/correctScript/js/correctSliderComponent.js +209 -0
- psychopy/tests/data/correctScript/js/correctSoundComponent.js +205 -0
- psychopy/tests/data/correctScript/js/correctTextComponent.js +207 -0
- psychopy/tests/data/correctScript/python/correctApertureComponent.py +157 -0
- psychopy/tests/data/correctScript/python/correctCodeComponent.py +135 -0
- psychopy/tests/data/correctScript/python/correctDotsComponent.py +161 -0
- psychopy/tests/data/correctScript/python/correctEnvGratingComponent.py +167 -0
- psychopy/tests/data/correctScript/python/correctFormComponent.py +150 -0
- psychopy/tests/data/correctScript/python/correctGratingComponent.py +158 -0
- psychopy/tests/data/correctScript/python/correctImageComponent.py +160 -0
- psychopy/tests/data/correctScript/python/correctJoyButtonsComponent.py +204 -0
- psychopy/tests/data/correctScript/python/correctJoystickComponent.py +214 -0
- psychopy/tests/data/correctScript/python/correctKeyboardComponent.py +169 -0
- psychopy/tests/data/correctScript/python/correctMicrophoneComponent.py +163 -0
- psychopy/tests/data/correctScript/python/correctMouseComponent.py +174 -0
- psychopy/tests/data/correctScript/python/correctMovieComponent.py +160 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +169 -0
- psychopy/tests/data/correctScript/python/correctParallelOutComponent.py +156 -0
- psychopy/tests/data/correctScript/python/correctPatchComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctPolygonComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctQmixPumpComponent.py +174 -0
- psychopy/tests/data/correctScript/python/correctRatingScaleComponent.py +152 -0
- psychopy/tests/data/correctScript/python/correctSliderComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctSoundComponent.py +157 -0
- psychopy/tests/data/correctScript/python/correctStaticComponent.py +176 -0
- psychopy/tests/data/correctScript/python/correctTextComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctVariableComponent.py +139 -0
- psychopy/tests/data/correctScript/python/correctcedrusButtonBoxComponent.py +199 -0
- psychopy/tests/data/correctScript/python/correctioLabsButtonBoxComponent.py +187 -0
- psychopy/tests/data/dataTest.xlsx +0 -0
- psychopy/tests/data/dots_cm.png +0 -0
- psychopy/tests/data/dots_deg.png +0 -0
- psychopy/tests/data/dots_degFlat.png +0 -0
- psychopy/tests/data/dots_degFlatPos.png +0 -0
- psychopy/tests/data/dots_height.png +0 -0
- psychopy/tests/data/dots_norm.png +0 -0
- psychopy/tests/data/dots_normAddBlend.png +0 -0
- psychopy/tests/data/dots_normHexbackground.png +0 -0
- psychopy/tests/data/dots_normNoShade.png +0 -0
- psychopy/tests/data/dots_pix.png +0 -0
- psychopy/tests/data/dots_stencil.png +0 -0
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/data/elarray1_cm.png +0 -0
- psychopy/tests/data/elarray1_deg.png +0 -0
- psychopy/tests/data/elarray1_degFlat.png +0 -0
- psychopy/tests/data/elarray1_degFlatPos.png +0 -0
- psychopy/tests/data/elarray1_height.png +0 -0
- psychopy/tests/data/elarray1_norm.png +0 -0
- psychopy/tests/data/elarray1_normAddBlend.png +0 -0
- psychopy/tests/data/elarray1_normHexbackground.png +0 -0
- psychopy/tests/data/elarray1_pix.png +0 -0
- psychopy/tests/data/elarray1_stencil.png +0 -0
- psychopy/tests/data/envelopeandrcos_cm.png +0 -0
- psychopy/tests/data/envelopeandrcos_deg.png +0 -0
- psychopy/tests/data/envelopeandrcos_degFlat.png +0 -0
- psychopy/tests/data/envelopeandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/envelopeandrcos_height.png +0 -0
- psychopy/tests/data/envelopeandrcos_norm.png +0 -0
- psychopy/tests/data/envelopeandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/envelopeandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/envelopeandrcos_pix.png +0 -0
- psychopy/tests/data/envelopeandrcos_stencil.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_cm.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_deg.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_degFlat.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_height.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_norm.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_pix.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_stencil.png +0 -0
- psychopy/tests/data/filltext.png +0 -0
- psychopy/tests/data/form_font_demographics.png +0 -0
- psychopy/tests/data/form_font_demographics.xlsx +0 -0
- psychopy/tests/data/form_font_languages.png +0 -0
- psychopy/tests/data/form_font_languages.xlsx +0 -0
- psychopy/tests/data/form_font_nonstandard.png +0 -0
- psychopy/tests/data/futureParams.psyexp +150 -0
- psychopy/tests/data/gabor1_cm.png +0 -0
- psychopy/tests/data/gabor1_deg.png +0 -0
- psychopy/tests/data/gabor1_degFlat.png +0 -0
- psychopy/tests/data/gabor1_degFlatPos.png +0 -0
- psychopy/tests/data/gabor1_height.png +0 -0
- psychopy/tests/data/gabor1_norm.png +0 -0
- psychopy/tests/data/gabor1_normAddBlend.png +0 -0
- psychopy/tests/data/gabor1_normHexbackground.png +0 -0
- psychopy/tests/data/gabor1_normNoShade.png +0 -0
- psychopy/tests/data/gabor1_pix.png +0 -0
- psychopy/tests/data/gabor1_stencil.png +0 -0
- psychopy/tests/data/gabor2_cm.png +0 -0
- psychopy/tests/data/gabor2_deg.png +0 -0
- psychopy/tests/data/gabor2_degFlat.png +0 -0
- psychopy/tests/data/gabor2_degFlatPos.png +0 -0
- psychopy/tests/data/gabor2_height.png +0 -0
- psychopy/tests/data/gabor2_norm.png +0 -0
- psychopy/tests/data/gabor2_normAddBlend.png +0 -0
- psychopy/tests/data/gabor2_normHexbackground.png +0 -0
- psychopy/tests/data/gabor2_normNoShade.png +0 -0
- psychopy/tests/data/gabor2_pix.png +0 -0
- psychopy/tests/data/gabor2_stencil.png +0 -0
- psychopy/tests/data/ghost_stroop.psyexp +131 -0
- psychopy/tests/data/ghost_trialTypes.xlsx +0 -0
- psychopy/tests/data/green_48000.flac.dist +0 -0
- psychopy/tests/data/greyscale.jpg +0 -0
- psychopy/tests/data/greyscale2.png +0 -0
- psychopy/tests/data/greyscale2_cm.png +0 -0
- psychopy/tests/data/greyscale2_deg.png +0 -0
- psychopy/tests/data/greyscale2_degFlat.png +0 -0
- psychopy/tests/data/greyscale2_degFlatPos.png +0 -0
- psychopy/tests/data/greyscale2_height.png +0 -0
- psychopy/tests/data/greyscale2_norm.png +0 -0
- psychopy/tests/data/greyscale2_normAddBlend.png +0 -0
- psychopy/tests/data/greyscale2_normHexbackground.png +0 -0
- psychopy/tests/data/greyscale2_normNoShade.png +0 -0
- psychopy/tests/data/greyscale2_pix.png +0 -0
- psychopy/tests/data/greyscale2_stencil.png +0 -0
- psychopy/tests/data/greyscaleLowContr_cm.png +0 -0
- psychopy/tests/data/greyscaleLowContr_deg.png +0 -0
- psychopy/tests/data/greyscaleLowContr_degFlat.png +0 -0
- psychopy/tests/data/greyscaleLowContr_degFlatPos.png +0 -0
- psychopy/tests/data/greyscaleLowContr_height.png +0 -0
- psychopy/tests/data/greyscaleLowContr_norm.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normAddBlend.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normHexbackground.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normNoShade.png +0 -0
- psychopy/tests/data/greyscaleLowContr_pix.png +0 -0
- psychopy/tests/data/greyscaleLowContr_stencil.png +0 -0
- psychopy/tests/data/greyscale_cm.png +0 -0
- psychopy/tests/data/greyscale_deg.png +0 -0
- psychopy/tests/data/greyscale_degFlat.png +0 -0
- psychopy/tests/data/greyscale_degFlatPos.png +0 -0
- psychopy/tests/data/greyscale_height.png +0 -0
- psychopy/tests/data/greyscale_norm.png +0 -0
- psychopy/tests/data/greyscale_normAddBlend.png +0 -0
- psychopy/tests/data/greyscale_normHexbackground.png +0 -0
- psychopy/tests/data/greyscale_normNoShade.png +0 -0
- psychopy/tests/data/greyscale_pix.png +0 -0
- psychopy/tests/data/greyscale_stencil.png +0 -0
- psychopy/tests/data/imageAndGauss_cm.png +0 -0
- psychopy/tests/data/imageAndGauss_deg.png +0 -0
- psychopy/tests/data/imageAndGauss_degFlat.png +0 -0
- psychopy/tests/data/imageAndGauss_degFlatPos.png +0 -0
- psychopy/tests/data/imageAndGauss_height.png +0 -0
- psychopy/tests/data/imageAndGauss_norm.png +0 -0
- psychopy/tests/data/imageAndGauss_normAddBlend.png +0 -0
- psychopy/tests/data/imageAndGauss_normHexbackground.png +0 -0
- psychopy/tests/data/imageAndGauss_normNoShade.png +0 -0
- psychopy/tests/data/imageAndGauss_pix.png +0 -0
- psychopy/tests/data/imageAndGauss_stencil.png +0 -0
- psychopy/tests/data/movFrame1_cm.png +0 -0
- psychopy/tests/data/movFrame1_deg.png +0 -0
- psychopy/tests/data/movFrame1_degFlat.png +0 -0
- psychopy/tests/data/movFrame1_degFlatPos.png +0 -0
- psychopy/tests/data/movFrame1_height.png +0 -0
- psychopy/tests/data/movFrame1_norm.png +0 -0
- psychopy/tests/data/movFrame1_normAddBlend.png +0 -0
- psychopy/tests/data/movFrame1_normHexbackground.png +0 -0
- psychopy/tests/data/movFrame1_normNoShade.png +0 -0
- psychopy/tests/data/movFrame1_pix.png +0 -0
- psychopy/tests/data/movFrame1_stencil.png +0 -0
- psychopy/tests/data/multiKeypressExperiment.psydat +357 -0
- psychopy/tests/data/multiKeypressTrialhandler.psydat +252 -0
- psychopy/tests/data/multiStairConds.xlsx +0 -0
- psychopy/tests/data/multiStairQuestPlus.xlsx +0 -0
- psychopy/tests/data/newstyle.psydat +6155 -0
- psychopy/tests/data/noiseAndRcos_cm.png +0 -0
- psychopy/tests/data/noiseAndRcos_deg.png +0 -0
- psychopy/tests/data/noiseAndRcos_degFlat.png +0 -0
- psychopy/tests/data/noiseAndRcos_degFlatPos.png +0 -0
- psychopy/tests/data/noiseAndRcos_height.png +0 -0
- psychopy/tests/data/noiseAndRcos_norm.png +0 -0
- psychopy/tests/data/noiseAndRcos_normAddBlend.png +0 -0
- psychopy/tests/data/noiseAndRcos_normHexbackground.png +0 -0
- psychopy/tests/data/noiseAndRcos_normNoShade.png +0 -0
- psychopy/tests/data/noiseAndRcos_pix.png +0 -0
- psychopy/tests/data/noiseAndRcos_stencil.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_cm.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_deg.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_degFlat.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_degFlatPos.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_height.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_norm.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normAddBlend.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normHexbackground.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normNoShade.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_pix.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_stencil.png +0 -0
- psychopy/tests/data/numpyImage_cm.png +0 -0
- psychopy/tests/data/numpyImage_deg.png +0 -0
- psychopy/tests/data/numpyImage_degFlat.png +0 -0
- psychopy/tests/data/numpyImage_degFlatPos.png +0 -0
- psychopy/tests/data/numpyImage_height.png +0 -0
- psychopy/tests/data/numpyImage_norm.png +0 -0
- psychopy/tests/data/numpyImage_normAddBlend.png +0 -0
- psychopy/tests/data/numpyImage_normHexbackground.png +0 -0
- psychopy/tests/data/numpyImage_normNoShade.png +0 -0
- psychopy/tests/data/numpyImage_pix.png +0 -0
- psychopy/tests/data/numpyImage_stencil.png +0 -0
- psychopy/tests/data/numpyLowContr_cm.png +0 -0
- psychopy/tests/data/numpyLowContr_deg.png +0 -0
- psychopy/tests/data/numpyLowContr_degFlat.png +0 -0
- psychopy/tests/data/numpyLowContr_degFlatPos.png +0 -0
- psychopy/tests/data/numpyLowContr_height.png +0 -0
- psychopy/tests/data/numpyLowContr_norm.png +0 -0
- psychopy/tests/data/numpyLowContr_normAddBlend.png +0 -0
- psychopy/tests/data/numpyLowContr_normHexbackground.png +0 -0
- psychopy/tests/data/numpyLowContr_normNoShade.png +0 -0
- psychopy/tests/data/numpyLowContr_pix.png +0 -0
- psychopy/tests/data/numpyLowContr_stencil.png +0 -0
- psychopy/tests/data/oldstyle.psydat +1534 -0
- psychopy/tests/data/oldstyle_stair.psydat +263 -0
- psychopy/tests/data/ratingscale1_cm.png +0 -0
- psychopy/tests/data/ratingscale1_deg.png +0 -0
- psychopy/tests/data/ratingscale1_degFlat.png +0 -0
- psychopy/tests/data/ratingscale1_degFlatPos.png +0 -0
- psychopy/tests/data/ratingscale1_height.png +0 -0
- psychopy/tests/data/ratingscale1_norm.png +0 -0
- psychopy/tests/data/ratingscale1_normAddBlend.png +0 -0
- psychopy/tests/data/ratingscale1_normHexbackground.png +0 -0
- psychopy/tests/data/ratingscale1_normNoShade.png +0 -0
- psychopy/tests/data/ratingscale1_pix.png +0 -0
- psychopy/tests/data/ratingscale1_stencil.png +0 -0
- psychopy/tests/data/red_16000.flac.dist +0 -0
- psychopy/tests/data/retroListParam.psyexp +82 -0
- psychopy/tests/data/right_to_left_unidcode.xlsx +0 -0
- psychopy/tests/data/sample.meshwarp.data +11522 -0
- psychopy/tests/data/shape2_1_cm.png +0 -0
- psychopy/tests/data/shape2_1_deg.png +0 -0
- psychopy/tests/data/shape2_1_degFlat.png +0 -0
- psychopy/tests/data/shape2_1_degFlatPos.png +0 -0
- psychopy/tests/data/shape2_1_height.png +0 -0
- psychopy/tests/data/shape2_1_norm.png +0 -0
- psychopy/tests/data/shape2_1_normAddBlend.png +0 -0
- psychopy/tests/data/shape2_1_normHexbackground.png +0 -0
- psychopy/tests/data/shape2_1_normNoShade.png +0 -0
- psychopy/tests/data/shape2_1_pix.png +0 -0
- psychopy/tests/data/shape2_1_stencil.png +0 -0
- psychopy/tests/data/shape2_2_cm.png +0 -0
- psychopy/tests/data/shape2_2_deg.png +0 -0
- psychopy/tests/data/shape2_2_degFlat.png +0 -0
- psychopy/tests/data/shape2_2_degFlatPos.png +0 -0
- psychopy/tests/data/shape2_2_height.png +0 -0
- psychopy/tests/data/shape2_2_norm.png +0 -0
- psychopy/tests/data/shape2_2_normAddBlend.png +0 -0
- psychopy/tests/data/shape2_2_normHexbackground.png +0 -0
- psychopy/tests/data/shape2_2_normNoShade.png +0 -0
- psychopy/tests/data/shape2_2_pix.png +0 -0
- psychopy/tests/data/shape2_2_stencil.png +0 -0
- psychopy/tests/data/simpleimage1_cm.png +0 -0
- psychopy/tests/data/simpleimage1_deg.png +0 -0
- psychopy/tests/data/simpleimage1_degFlat.png +0 -0
- psychopy/tests/data/simpleimage1_degFlatPos.png +0 -0
- psychopy/tests/data/simpleimage1_height.png +0 -0
- psychopy/tests/data/simpleimage1_norm.png +0 -0
- psychopy/tests/data/simpleimage1_normAddBlend.png +0 -0
- psychopy/tests/data/simpleimage1_normHexbackground.png +0 -0
- psychopy/tests/data/simpleimage1_normNoShade.png +0 -0
- psychopy/tests/data/simpleimage1_pix.png +0 -0
- psychopy/tests/data/simpleimage1_stencil.png +0 -0
- psychopy/tests/data/test001EntryImporting.psyexp +66 -0
- psychopy/tests/data/testMovie.mp4 +0 -0
- psychopy/tests/data/test_basic_run.py +1 -0
- psychopy/tests/data/test_circle_radius_.1height.png +0 -0
- psychopy/tests/data/test_circle_radius_.2height.png +0 -0
- psychopy/tests/data/test_circle_radius_10pix.png +0 -0
- psychopy/tests/data/test_circle_radius_20pix.png +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_form_combinations_choice_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_get_resources/blue.png +0 -0
- psychopy/tests/data/test_get_resources/groupA.csv +3 -0
- psychopy/tests/data/test_get_resources/groupB.csv +3 -0
- psychopy/tests/data/test_get_resources/groups.csv +3 -0
- psychopy/tests/data/test_get_resources/handledbyrm_constrloop.psyexp +143 -0
- psychopy/tests/data/test_get_resources/handledbyrm_noloop.psyexp +114 -0
- psychopy/tests/data/test_get_resources/handledbyrm_recurloop.psyexp +138 -0
- psychopy/tests/data/test_get_resources/handledbyrm_strloop.psyexp +126 -0
- psychopy/tests/data/test_get_resources/handledbystatic_constrloop.psyexp +141 -0
- psychopy/tests/data/test_get_resources/handledbystatic_noloop.psyexp +112 -0
- psychopy/tests/data/test_get_resources/handledbystatic_recurloop.psyexp +136 -0
- psychopy/tests/data/test_get_resources/handledbystatic_strloop.psyexp +124 -0
- psychopy/tests/data/test_get_resources/unhandled_constrloop.psyexp +128 -0
- psychopy/tests/data/test_get_resources/unhandled_noloop.psyexp +99 -0
- psychopy/tests/data/test_get_resources/unhandled_recurloop.psyexp +123 -0
- psychopy/tests/data/test_get_resources/unhandled_strloop.psyexp +111 -0
- psychopy/tests/data/test_get_resources/white.png +0 -0
- psychopy/tests/data/test_get_resources/yellow.png +0 -0
- psychopy/tests/data/test_image_aspect_default_None.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xFull_yNone.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xNone_yFull.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xNone_yNone.png +0 -0
- psychopy/tests/data/test_image_flip_anchor_horiz.png +0 -0
- psychopy/tests/data/test_image_flip_anchor_vert.png +0 -0
- psychopy/tests/data/test_loaded_namespace/test_counterbalance.psyexp +142 -0
- psychopy/tests/data/test_loaded_namespace/test_custom_missing.psyexp +129 -0
- psychopy/tests/data/test_loaded_namespace/test_missing_counterbalance.psyexp +116 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_exp.psyexp +181 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_missing.psyexp +140 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_name_calibration.psyexp +164 -0
- psychopy/tests/data/test_loops/testLoopsBlocks.psyexp +161 -0
- psychopy/tests/data/test_loops/testStaircase.psyexp +105 -0
- psychopy/tests/data/test_loops/test_current_loop_attr.psyexp +185 -0
- psychopy/tests/data/test_panorama/panoramaTestImage.png +0 -0
- psychopy/tests/data/test_panorama/panoramaTestImage.svg +1 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_-1.0.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_1.0.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_0.3.png +0 -0
- psychopy/tests/data/test_resources.psyexp +491 -0
- psychopy/tests/data/test_session/outside_root/externalExp.psyexp +116 -0
- psychopy/tests/data/test_session/root/annotation/annotation.psyexp +161 -0
- psychopy/tests/data/test_session/root/error/error.psyexp +93 -0
- psychopy/tests/data/test_session/root/exp1/exp1.psyexp +133 -0
- psychopy/tests/data/test_session/root/exp2/exp2.psyexp +133 -0
- psychopy/tests/data/test_session/root/frameRate/frameRate.psyexp +114 -0
- psychopy/tests/data/test_session/root/invUseVersion/invUseVersion.psyexp +133 -0
- psychopy/tests/data/test_session/root/testClockFormat/testClockFormat.psyexp +122 -0
- psychopy/tests/data/test_session/root/testCtrls/testCtrls.psyexp +115 -0
- psychopy/tests/data/test_session/root/testEditExpInfo/testEditExpInfo.psyexp +135 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testNamedButtonBox/testNamedButtonBox.psyexp +145 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/data/test_slider_horiz_accute_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_accute_vert.png +0 -0
- psychopy/tests/data/test_slider_horiz_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_obtuse_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_obtuse_vert.png +0 -0
- psychopy/tests/data/test_slider_horiz_vert.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_blanks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_clustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_morelabels.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_morelabelsclustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_moreticks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_moreticksclustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_nolabels.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_noticks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_simple.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_False_flip_False.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_False_flip_True.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_True_flip_False.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_True_flip_True.png +0 -0
- psychopy/tests/data/test_sounds/default_16000.wav +0 -0
- psychopy/tests/data/test_sounds/default_192000.wav +0 -0
- psychopy/tests/data/test_sounds/default_22050.wav +0 -0
- psychopy/tests/data/test_sounds/default_32000.wav +0 -0
- psychopy/tests/data/test_sounds/default_44100.wav +0 -0
- psychopy/tests/data/test_sounds/default_48000.wav +0 -0
- psychopy/tests/data/test_sounds/default_8000.wav +0 -0
- psychopy/tests/data/test_sounds/default_96000.wav +0 -0
- psychopy/tests/data/test_static_component_script.psyexp +76 -0
- psychopy/tests/data/test_win_bg_large_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_large_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_small_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_small_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_fill_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_fill_default.png +0 -0
- psychopy/tests/data/test_win_bgcolor_blue.png +0 -0
- psychopy/tests/data/test_win_bgcolor_green.png +0 -0
- psychopy/tests/data/test_win_bgcolor_red.png +0 -0
- psychopy/tests/data/testimage.jpg +0 -0
- psychopy/tests/data/testimagegray.jpg +0 -0
- psychopy/tests/data/testpixels.png +0 -0
- psychopy/tests/data/testwedges.png +0 -0
- psychopy/tests/data/text1_cm.png +0 -0
- psychopy/tests/data/text1_deg.png +0 -0
- psychopy/tests/data/text1_degFlat.png +0 -0
- psychopy/tests/data/text1_degFlatPos.png +0 -0
- psychopy/tests/data/text1_height.png +0 -0
- psychopy/tests/data/text1_norm.png +0 -0
- psychopy/tests/data/text1_normAddBlend.png +0 -0
- psychopy/tests/data/text1_normHexbackground.png +0 -0
- psychopy/tests/data/text1_normNoShade.png +0 -0
- psychopy/tests/data/text1_pix.png +0 -0
- psychopy/tests/data/text1_stencil.png +0 -0
- psychopy/tests/data/text2_cm.png +0 -0
- psychopy/tests/data/text2_deg.png +0 -0
- psychopy/tests/data/text2_degFlat.png +0 -0
- psychopy/tests/data/text2_degFlatPos.png +0 -0
- psychopy/tests/data/text2_norm.png +0 -0
- psychopy/tests/data/text2_normNoShade.png +0 -0
- psychopy/tests/data/text2_pix.png +0 -0
- psychopy/tests/data/textbox_charcolors_default_roygbiv.png +0 -0
- psychopy/tests/data/textbox_charcolors_default_white_hello_black_there.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_center.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_left.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_right.png +0 -0
- psychopy/tests/data/textbox_default_align_center.png +0 -0
- psychopy/tests/data/textbox_default_align_center_center.png +0 -0
- psychopy/tests/data/textbox_default_align_center_left.png +0 -0
- psychopy/tests/data/textbox_default_align_center_right.png +0 -0
- psychopy/tests/data/textbox_default_align_centre.png +0 -0
- psychopy/tests/data/textbox_default_align_centre_centre.png +0 -0
- psychopy/tests/data/textbox_default_align_more_than_two_words.png +0 -0
- psychopy/tests/data/textbox_default_align_someword.png +0 -0
- psychopy/tests/data/textbox_default_align_top_center.png +0 -0
- psychopy/tests/data/textbox_default_align_top_left.png +0 -0
- psychopy/tests/data/textbox_default_align_top_right.png +0 -0
- psychopy/tests/data/textbox_default_colors_WOB.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar1.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar2.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar3.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke1.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke2.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke3.png +0 -0
- psychopy/tests/data/textbox_default_cutoff_top.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_1.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_2.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_3.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_4.png +0 -0
- psychopy/tests/data/textbox_default_tyke_1.png +0 -0
- psychopy/tests/data/textbox_default_tyke_2.png +0 -0
- psychopy/tests/data/textbox_typing_blank.png +0 -0
- psychopy/tests/data/textbox_typing_longKoeran.png +0 -0
- psychopy/tests/data/textbox_typing_longWord.png +0 -0
- psychopy/tests/data/textbox_typing_navDel.png +0 -0
- psychopy/tests/data/textbox_typing_navLR.png +0 -0
- psychopy/tests/data/textbox_typing_newline.png +0 -0
- psychopy/tests/data/textbox_typing_pangram.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_right.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_right.png +0 -0
- psychopy/tests/data/textbox_uax14_align_centre.png +0 -0
- psychopy/tests/data/textbox_uax14_align_centre_centre.png +0 -0
- psychopy/tests/data/textbox_uax14_align_more_than_two_words.png +0 -0
- psychopy/tests/data/textbox_uax14_align_someword.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_right.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_WOB.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar1.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar2.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar3.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke1.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke2.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke3.png +0 -0
- psychopy/tests/data/textbox_uax14_cutoff_top.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_1.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_2.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_3.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_4.png +0 -0
- psychopy/tests/data/textbox_uax14_tyke_1.png +0 -0
- psychopy/tests/data/textbox_uax14_tyke_2.png +0 -0
- psychopy/tests/data/trialTypes.csv +1 -0
- psychopy/tests/data/trialTypes.docx +0 -0
- psychopy/tests/data/trialTypes.pkl +0 -0
- psychopy/tests/data/trialTypes.tsv +7 -0
- psychopy/tests/data/trialTypes.xls +0 -0
- psychopy/tests/data/trialTypes.xlsx +0 -0
- psychopy/tests/data/trialTypes_eu.csv +7 -0
- psychopy/tests/data/trialsBlankCols.xlsx +0 -0
- psychopy/tests/data/wedge1_cm.png +0 -0
- psychopy/tests/data/wedge1_deg.png +0 -0
- psychopy/tests/data/wedge1_degFlat.png +0 -0
- psychopy/tests/data/wedge1_degFlatPos.png +0 -0
- psychopy/tests/data/wedge1_height.png +0 -0
- psychopy/tests/data/wedge1_norm.png +0 -0
- psychopy/tests/data/wedge1_normAddBlend.png +0 -0
- psychopy/tests/data/wedge1_normHexbackground.png +0 -0
- psychopy/tests/data/wedge1_normNoShade.png +0 -0
- psychopy/tests/data/wedge1_pix.png +0 -0
- psychopy/tests/data/wedge1_stencil.png +0 -0
- psychopy/tests/data/wedge2_cm.png +0 -0
- psychopy/tests/data/wedge2_deg.png +0 -0
- psychopy/tests/data/wedge2_degFlat.png +0 -0
- psychopy/tests/data/wedge2_degFlatPos.png +0 -0
- psychopy/tests/data/wedge2_height.png +0 -0
- psychopy/tests/data/wedge2_norm.png +0 -0
- psychopy/tests/data/wedge2_normAddBlend.png +0 -0
- psychopy/tests/data/wedge2_normHexbackground.png +0 -0
- psychopy/tests/data/wedge2_normNoShade.png +0 -0
- psychopy/tests/data/wedge2_pix.png +0 -0
- psychopy/tests/data/wedge2_stencil.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_-1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_-2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale2_2_pos0_0.png +0 -0
- psychopy/tests/doc/run_btn.png +0 -0
- psychopy/tests/doc/test-suite-bugs.jpg +0 -0
- psychopy/tests/doc/user-bugs.jpg +0 -0
- psychopy/tests/dummy_xorg.conf +21 -0
- psychopy/tests/run.py +50 -0
- psychopy/tests/test_Installation.py +38 -0
- psychopy/tests/test_alerts/test_alerts.py +37 -0
- psychopy/tests/test_alerts/test_alerttools.py +132 -0
- psychopy/tests/test_app/__init__.py +0 -0
- psychopy/tests/test_app/conftest.py +40 -0
- psychopy/tests/test_app/test_builder/__init__.py +0 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +166 -0
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +133 -0
- psychopy/tests/test_app/test_builder/test_ComponentDialogs.py +98 -0
- psychopy/tests/test_app/test_command_line.py +65 -0
- psychopy/tests/test_app/test_runner/__init__.py +0 -0
- psychopy/tests/test_app/test_runner/test_RunnerFrame.py +166 -0
- psychopy/tests/test_app/test_speed.py +147 -0
- psychopy/tests/test_app/test_themes/test_icons.py +79 -0
- psychopy/tests/test_codecov.py +29 -0
- psychopy/tests/test_colors/test_Color.py +41 -0
- psychopy/tests/test_data/dataPrev.xlsx +0 -0
- psychopy/tests/test_data/test_ExperimentHandler.py +171 -0
- psychopy/tests/test_data/test_MultiStairHandler.py +174 -0
- psychopy/tests/test_data/test_StairHandlers.py +1241 -0
- psychopy/tests/test_data/test_TrialHandler.py +335 -0
- psychopy/tests/test_data/test_TrialHandler2.py +584 -0
- psychopy/tests/test_data/test_TrialHandlerExt.py +359 -0
- psychopy/tests/test_data/test_fitFunctions.py +101 -0
- psychopy/tests/test_data/test_utils.py +173 -0
- psychopy/tests/test_data/test_xlsx.py +92 -0
- psychopy/tests/test_demos/test_builder_demos.py +52 -0
- psychopy/tests/test_experiment/__init__.py +0 -0
- psychopy/tests/test_experiment/known_py_diffs.txt +47 -0
- psychopy/tests/test_experiment/needs_wx/__init__.py +0 -0
- psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +14780 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +130 -0
- psychopy/tests/test_experiment/needs_wx/test_Experiment.py +406 -0
- psychopy/tests/test_experiment/needs_wx/test_components.py +170 -0
- psychopy/tests/test_experiment/test_component_compile_js.py +59 -0
- psychopy/tests/test_experiment/test_component_compile_python.py +165 -0
- psychopy/tests/test_experiment/test_components/__init__.py +1 -0
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +218 -0
- psychopy/tests/test_experiment/test_components/test_CodeComponent.py +70 -0
- psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
- psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_MouseComponent.py +136 -0
- psychopy/tests/test_experiment/test_components/test_PolygonComponent.py +65 -0
- psychopy/tests/test_experiment/test_components/test_ResourceManagerComponent.py +50 -0
- psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
- psychopy/tests/test_experiment/test_components/test_SettingsComponent.py +80 -0
- psychopy/tests/test_experiment/test_components/test_StaticComponent.py +51 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_all_components.py +104 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +477 -0
- psychopy/tests/test_experiment/test_experiment.py +199 -0
- psychopy/tests/test_experiment/test_loops.py +103 -0
- psychopy/tests/test_experiment/test_params.py +363 -0
- psychopy/tests/test_experiment/test_py2js.py +230 -0
- psychopy/tests/test_experiment/test_routines/__init__.py +1 -0
- psychopy/tests/test_experiment/test_routines/test_EyetrackerCalibrationRoutine.py +9 -0
- psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +9 -0
- psychopy/tests/test_experiment/test_routines/test_all_routines.py +23 -0
- psychopy/tests/test_experiment/test_routines/test_base_routine.py +154 -0
- psychopy/tests/test_experiment/test_routines/test_standalone_routines.py +36 -0
- psychopy/tests/test_gui/test_DlgFromDictQt.py +82 -0
- psychopy/tests/test_gui/test_DlgFromDictWx.py +82 -0
- psychopy/tests/test_hardware/__init__.py +0 -0
- psychopy/tests/test_hardware/test_device_manager.py +36 -0
- psychopy/tests/test_hardware/test_emulator.py +106 -0
- psychopy/tests/test_hardware/test_gammasci.py +35 -0
- psychopy/tests/test_hardware/test_keyboard.py +241 -0
- psychopy/tests/test_hardware/test_keyboard_events.py +362 -0
- psychopy/tests/test_hardware/test_photodiode.py +66 -0
- psychopy/tests/test_hardware/test_ports.py +129 -0
- psychopy/tests/test_iohub/__init__.py +1 -0
- psychopy/tests/test_iohub/test_computer.py +124 -0
- psychopy/tests/test_iohub/test_event_get_and_clear.py +94 -0
- psychopy/tests/test_iohub/test_keyboard.py +91 -0
- psychopy/tests/test_iohub/test_launch.py +19 -0
- psychopy/tests/test_iohub/testutil.py +54 -0
- psychopy/tests/test_liaison/test_Liaison.py +275 -0
- psychopy/tests/test_misc/__init__.py +0 -0
- psychopy/tests/test_misc/memory_usage.py +164 -0
- psychopy/tests/test_misc/test_GammaFun.py +15 -0
- psychopy/tests/test_misc/test_clock.py +63 -0
- psychopy/tests/test_misc/test_color.py +205 -0
- psychopy/tests/test_misc/test_core.py +451 -0
- psychopy/tests/test_misc/test_event.py +308 -0
- psychopy/tests/test_misc/test_info.py +18 -0
- psychopy/tests/test_misc/test_layout.py +49 -0
- psychopy/tests/test_misc/test_locale.py +38 -0
- psychopy/tests/test_misc/test_web.py +22 -0
- psychopy/tests/test_monitors/test_monitors.py +91 -0
- psychopy/tests/test_plugins/__init__.py +0 -0
- psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
- psychopy/tests/test_preferences/__init__.py +0 -0
- psychopy/tests/test_preferences/test_prefs.py +28 -0
- psychopy/tests/test_session/test_Session.py +257 -0
- psychopy/tests/test_sound/__init__.py +0 -0
- psychopy/tests/test_sound/test_audioclip.py +331 -0
- psychopy/tests/test_sound/test_hamming.py +45 -0
- psychopy/tests/test_sound/test_sound.py +138 -0
- psychopy/tests/test_sound/test_sound_pygame.py +67 -0
- psychopy/tests/test_tools/test_animationtools.py +51 -0
- psychopy/tests/test_tools/test_arraytools.py +242 -0
- psychopy/tests/test_tools/test_attributetools.py +257 -0
- psychopy/tests/test_tools/test_colorspacetools.py +150 -0
- psychopy/tests/test_tools/test_environmenttools.py +52 -0
- psychopy/tests/test_tools/test_fileerrortools.py +97 -0
- psychopy/tests/test_tools/test_filetools.py +104 -0
- psychopy/tests/test_tools/test_imagetools.py +57 -0
- psychopy/tests/test_tools/test_mathtools.py +688 -0
- psychopy/tests/test_tools/test_stringtools.py +143 -0
- psychopy/tests/test_tools/test_versionchooser.py +209 -0
- psychopy/tests/test_tools/test_viewtools.py +168 -0
- psychopy/tests/test_validators/__init__.py +0 -0
- psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
- psychopy/tests/test_visual/__init__.py +0 -0
- psychopy/tests/test_visual/measure_parity.py +243 -0
- psychopy/tests/test_visual/test_all_stimuli.py +655 -0
- psychopy/tests/test_visual/test_basevisual.py +564 -0
- psychopy/tests/test_visual/test_brush.py +68 -0
- psychopy/tests/test_visual/test_button.py +15 -0
- psychopy/tests/test_visual/test_circle.py +74 -0
- psychopy/tests/test_visual/test_contains_overlaps.py +242 -0
- psychopy/tests/test_visual/test_custommouse.py +47 -0
- psychopy/tests/test_visual/test_dots.py +155 -0
- psychopy/tests/test_visual/test_form.py +414 -0
- psychopy/tests/test_visual/test_framepacking.py +41 -0
- psychopy/tests/test_visual/test_gamma.py +152 -0
- psychopy/tests/test_visual/test_glfw_backend.py +14 -0
- psychopy/tests/test_visual/test_image.py +219 -0
- psychopy/tests/test_visual/test_panorama.py +41 -0
- psychopy/tests/test_visual/test_progress.py +96 -0
- psychopy/tests/test_visual/test_projections.py +215 -0
- psychopy/tests/test_visual/test_projections_interactive.py +163 -0
- psychopy/tests/test_visual/test_roi.py +105 -0
- psychopy/tests/test_visual/test_shape.py +23 -0
- psychopy/tests/test_visual/test_slider.py +328 -0
- psychopy/tests/test_visual/test_target.py +35 -0
- psychopy/tests/test_visual/test_textbox.py +508 -0
- psychopy/tests/test_visual/test_winFlipTiming.py +95 -0
- psychopy/tests/test_visual/test_winScalePos.py +78 -0
- psychopy/tests/test_visual/test_window.py +124 -0
- psychopy/tests/utils.py +372 -0
- psychopy/tools/LineBreak.txt +3597 -0
- psychopy/tools/__init__.py +9 -0
- psychopy/tools/animationtools.py +49 -0
- psychopy/tools/apptools.py +32 -0
- psychopy/tools/arraytools.py +561 -0
- psychopy/tools/attributetools.py +263 -0
- psychopy/tools/audiotools.py +361 -0
- psychopy/tools/colorspacetools.py +717 -0
- psychopy/tools/coordinatetools.py +104 -0
- psychopy/tools/environmenttools.py +62 -0
- psychopy/tools/fileerrortools.py +66 -0
- psychopy/tools/filetools.py +406 -0
- psychopy/tools/fontmanager.py +1065 -0
- psychopy/tools/gltools.py +7367 -0
- psychopy/tools/imagetools.py +65 -0
- psychopy/tools/linebreak.py +322 -0
- psychopy/tools/mathtools.py +4910 -0
- psychopy/tools/monitorunittools.py +276 -0
- psychopy/tools/movietools.py +1111 -0
- psychopy/tools/pkgtools.py +654 -0
- psychopy/tools/plottools.py +25 -0
- psychopy/tools/rifttools.py +76 -0
- psychopy/tools/stereotools.py +9 -0
- psychopy/tools/stimulustools.py +198 -0
- psychopy/tools/stringtools.py +466 -0
- psychopy/tools/systemtools.py +1331 -0
- psychopy/tools/typetools.py +52 -0
- psychopy/tools/unittools.py +16 -0
- psychopy/tools/versionchooser.py +604 -0
- psychopy/tools/viewtools.py +1068 -0
- psychopy/tools/wizard.py +804 -0
- psychopy/validation/__init__.py +6 -0
- psychopy/validation/audio.py +78 -0
- psychopy/validation/visual.py +119 -0
- psychopy/visual/__init__.py +121 -0
- psychopy/visual/aperture.py +333 -0
- psychopy/visual/backends/__init__.py +77 -0
- psychopy/visual/backends/_base.py +481 -0
- psychopy/visual/backends/gamma.py +349 -0
- psychopy/visual/backends/glfwbackend.py +24 -0
- psychopy/visual/backends/pygamebackend.py +343 -0
- psychopy/visual/backends/pygletbackend.py +970 -0
- psychopy/visual/basevisual.py +2027 -0
- psychopy/visual/brush.py +204 -0
- psychopy/visual/bufferimage.py +311 -0
- psychopy/visual/button.py +194 -0
- psychopy/visual/circle.py +165 -0
- psychopy/visual/custommouse.py +263 -0
- psychopy/visual/dot.py +734 -0
- psychopy/visual/dropdown.py +165 -0
- psychopy/visual/elementarray.py +802 -0
- psychopy/visual/filters.py +419 -0
- psychopy/visual/form.py +1195 -0
- psychopy/visual/globalVars.py +24 -0
- psychopy/visual/grating.py +581 -0
- psychopy/visual/helpers.py +336 -0
- psychopy/visual/image.py +438 -0
- psychopy/visual/line.py +227 -0
- psychopy/visual/movie.py +12 -0
- psychopy/visual/movie2.py +21 -0
- psychopy/visual/movie3.py +18 -0
- psychopy/visual/movies/__init__.py +2334 -0
- psychopy/visual/movies/frame.py +258 -0
- psychopy/visual/movies/metadata.py +242 -0
- psychopy/visual/movies/players/_base.py +364 -0
- psychopy/visual/nnlvs.py +830 -0
- psychopy/visual/noise.py +32 -0
- psychopy/visual/panorama.py +313 -0
- psychopy/visual/patch.py +21 -0
- psychopy/visual/pie.py +237 -0
- psychopy/visual/polygon.py +226 -0
- psychopy/visual/progress.py +313 -0
- psychopy/visual/radial.py +29 -0
- psychopy/visual/ratingscale.py +18 -0
- psychopy/visual/rect.py +195 -0
- psychopy/visual/rift.py +2660 -0
- psychopy/visual/roi.py +141 -0
- psychopy/visual/secondorder.py +27 -0
- psychopy/visual/shaders.py +787 -0
- psychopy/visual/shape.py +897 -0
- psychopy/visual/simpleimage.py +303 -0
- psychopy/visual/slider.py +1222 -0
- psychopy/visual/stim3d.py +2109 -0
- psychopy/visual/target.py +278 -0
- psychopy/visual/text.py +769 -0
- psychopy/visual/textbox/__init__.py +1280 -0
- psychopy/visual/textbox/fontmanager.py +574 -0
- psychopy/visual/textbox/parsedtext.py +317 -0
- psychopy/visual/textbox/textgrid.py +278 -0
- psychopy/visual/textbox/textureatlas.py +248 -0
- psychopy/visual/textbox2/__init__.py +4 -0
- psychopy/visual/textbox2/textbox2.py +1970 -0
- psychopy/visual/vlcmoviestim.py +1294 -0
- psychopy/visual/window.py +3910 -0
- psychopy/visual/windowframepack.py +86 -0
- psychopy/visual/windowwarp.py +457 -0
- psychopy/voicekey/__init__.py +56 -0
- psychopy/voicekey/labjack_vks.py +9 -0
- psychopy/voicekey/parallel_vks.py +9 -0
- psychopy/voicekey/vk_tools.py +131 -0
- psychopy/web.py +289 -0
- psychopy-2025.2.4.dist-info/METADATA +160 -0
- psychopy-2025.2.4.dist-info/RECORD +3127 -0
- psychopy-2025.2.4.dist-info/WHEEL +4 -0
- psychopy-2025.2.4.dist-info/entry_points.txt +5 -0
- 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')
|