Repository: psychopy/psychopy

Issue:
ENH: Builder demos
"modernising" buider demos - rebuilding so that components contain all the correct params after the param rework, as well as replacing Text stim with TextBox stim


Discussion:

Resolving patch:
diff --git a/psychopy/app/builder/builder.py b/psychopy/app/builder/builder.py
--- a/psychopy/app/builder/builder.py
+++ b/psychopy/app/builder/builder.py
@@ -1086,7 +1086,7 @@ def demosUnpack(self, event=None):
                     unpackFolder)
         self.prefs['unpackedDemosDir'] = unpackFolder
         self.app.prefs.saveUserPrefs()
-        updateDemosMenu()
+        updateDemosMenu(self, self.demosMenu, self.prefs['unpackedDemosDir'], ext=".psyexp")
 
     def demoLoad(self, event=None):
         """Defines Demo Loading Event."""
diff --git a/psychopy/demos/builder/readyToPlay/adaptive_decision_making_demo/chooser.py b/psychopy/demos/builder/Experiments/adaptiveDecisionMaking/chooser.py
similarity index 100%
rename from psychopy/demos/builder/readyToPlay/adaptive_decision_making_demo/chooser.py
rename to psychopy/demos/builder/Experiments/adaptiveDecisionMaking/chooser.py
diff --git a/psychopy/demos/builder/Feature Demos/iohub/stroop_keyboard/stroop.py b/psychopy/demos/builder/Feature Demos/iohub/stroop_keyboard/stroop.py
new file mode 100644
--- /dev/null
+++ b/psychopy/demos/builder/Feature Demos/iohub/stroop_keyboard/stroop.py	
@@ -0,0 +1,419 @@
+﻿#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+This experiment was created using PsychoPy3 Experiment Builder (v2020.2.10),
+    on January 25, 2021, at 15:12
+If you publish work using this script the most relevant publication is:
+
+    Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, Kastman E, Lindeløv JK. (2019) 
+        PsychoPy2: Experiments in behavior made easy Behav Res 51: 195. 
+        https://doi.org/10.3758/s13428-018-01193-y
+
+"""
+
+from __future__ import absolute_import, division
+
+from psychopy import locale_setup
+from psychopy import prefs
+from psychopy import sound, gui, visual, core, data, event, logging, clock, colors
+from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
+                                STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)
+
+import numpy as np  # whole numpy lib is available, prepend 'np.'
+from numpy import (sin, cos, tan, log, log10, pi, average,
+                   sqrt, std, deg2rad, rad2deg, linspace, asarray)
+from numpy.random import random, randint, normal, shuffle, choice as randchoice
+import os  # handy system and path functions
+import sys  # to get file system encoding
+
+from psychopy.hardware import keyboard
+
+
+
+# Ensure that relative paths start from the same directory as this script
+_thisDir = os.path.dirname(os.path.abspath(__file__))
+os.chdir(_thisDir)
+
+# Store info about the experiment session
+psychopyVersion = '2020.2.10'
+expName = 'stroop'  # from the Builder filename that created this script
+expInfo = {'Participant': ''}
+dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
+if dlg.OK == False:
+    core.quit()  # user pressed cancel
+expInfo['date'] = data.getDateStr()  # add a simple timestamp
+expInfo['expName'] = expName
+expInfo['psychopyVersion'] = psychopyVersion
+
+# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
+filename = _thisDir + os.sep + 'data' + os.sep + '%s_%s' % (expInfo['Participant'], expInfo['date'])
+
+# An ExperimentHandler isn't essential but helps with data saving
+thisExp = data.ExperimentHandler(name=expName, version='',
+    extraInfo=expInfo, runtimeInfo=None,
+    originPath='G:\\My Drive\\Throwaway\\PsychoPy3 Demos\\featureDemos\\iohub\\stroop_keyboard\\stroop.py',
+    savePickle=True, saveWideText=True,
+    dataFileName=filename)
+# save a log file for detail verbose info
+logFile = logging.LogFile(filename+'.log', level=logging.WARNING)
+logging.console.setLevel(logging.WARNING)  # this outputs to the screen, not a file
+
+endExpNow = False  # flag for 'escape' or other condition => quit the exp
+frameTolerance = 0.001  # how close to onset before 'same' frame
+
+# Start Code - component code to be run after the window creation
+
+# Setup the Window
+win = visual.Window(
+    size=[1680, 1050], fullscr=False, screen=0, 
+    winType='pyglet', allowGUI=True, allowStencil=False,
+    monitor='testMonitor', color='black', colorSpace='rgb',
+    blendMode='avg', useFBO=True, 
+    units='pix')
+# store frame rate of monitor if we can measure it
+expInfo['frameRate'] = win.getActualFrameRate()
+if expInfo['frameRate'] != None:
+    frameDur = 1.0 / round(expInfo['frameRate'])
+else:
+    frameDur = 1.0 / 60.0  # could not measure, so guess
+
+# create a default keyboard (e.g. to check for escape)
+defaultKeyboard = keyboard.Keyboard()
+
+# Initialize components for Routine "instruct"
+instructClock = core.Clock()
+instrText = visual.TextStim(win=win, name='instrText',
+    text="OK. Ready?\\n\\nRemember: \\n1) Stay fixated on the central white dot. \\n2) Ignore the word itself; press:\\n - Left for red LETTERS\\n - Down for green LETTERS\\n - Right for blue LETTERS\\n - (Esc will quit)\\n3) To toggle gaze position visibility, press 'g'. \\n\\nPress any key to continue",
+    font='Arial',
+    units='pix', pos=[0, 0], height=50, wrapWidth=800, ori=0, 
+    color=[1, 1, 1], colorSpace='rgb', opacity=1, 
+    languageStyle='LTR',
+    depth=0.0);
+ready = keyboard.Keyboard()
+
+# Initialize components for Routine "trial"
+trialClock = core.Clock()
+stimulus = visual.TextStim(win=win, name='stimulus',
+    text='default text',
+    font='Arial',
+    pos=[0, 0], height=100, wrapWidth=None, ori=0, 
+    color='white', colorSpace='rgb', opacity=1, 
+    languageStyle='LTR',
+    depth=0.0);
+try:
+    from psychopy.iohub.client import ioHubConnection,yload,yLoader
+    from psychopy.iohub.constants import EventConstants,KeyboardConstants
+    from psychopy.data import getDateStr
+    # Load the iohub device config, file converting it to a python dict.
+    io_config=yload(open('iohub_config.yaml','r'), Loader=yLoader)
+
+    # Add / Update the session code to be unique. Here we use the psychopy getDateStr() function for session code generation
+    session_info=io_config.get('data_store').get('session_info')
+    session_info.update(code="S_%s"%(getDateStr()))
+
+    # Create an ioHubConnection instance, which starts the ioHubProcess, and informs it of the requested devices and their configurations.
+    io=ioHubConnection(io_config)
+    iokeyboard=io.devices.keyboard
+    iomouse=io.devices.mouse
+except Exception as e:
+   print("!! Error starting ioHub: ",e," Exiting...")
+   core.quit()
+
+# Initialize components for Routine "thanks"
+thanksClock = core.Clock()
+thanksText = visual.TextStim(win=win, name='thanksText',
+    text='This is the end of the experiment.\\n\\nThanks!',
+    font='arial',
+    units='pix', pos=[0, 0], height=50, wrapWidth=800, ori=0, 
+    color=[1, 1, 1], colorSpace='rgb', opacity=1, 
+    languageStyle='LTR',
+    depth=0.0);
+
+# Create some handy timers
+globalClock = core.Clock()  # to track the time since experiment started
+routineTimer = core.CountdownTimer()  # to track time remaining of each (non-slip) routine 
+
+# ------Prepare to start Routine "instruct"-------
+continueRoutine = True
+# update component parameters for each repeat
+ready.keys = []
+ready.rt = []
+_ready_allKeys = []
+# keep track of which components have finished
+instructComponents = [instrText, ready]
+for thisComponent in instructComponents:
+    thisComponent.tStart = None
+    thisComponent.tStop = None
+    thisComponent.tStartRefresh = None
+    thisComponent.tStopRefresh = None
+    if hasattr(thisComponent, 'status'):
+        thisComponent.status = NOT_STARTED
+# reset timers
+t = 0
+_timeToFirstFrame = win.getFutureFlipTime(clock="now")
+instructClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
+frameN = -1
+
+# -------Run Routine "instruct"-------
+while continueRoutine:
+    # get current time
+    t = instructClock.getTime()
+    tThisFlip = win.getFutureFlipTime(clock=instructClock)
+    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
+    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
+    # update/draw components on each frame
+    
+    # *instrText* updates
+    if instrText.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
+        # keep track of start time/frame for later
+        instrText.frameNStart = frameN  # exact frame index
+        instrText.tStart = t  # local t and not account for scr refresh
+        instrText.tStartRefresh = tThisFlipGlobal  # on global time
+        win.timeOnFlip(instrText, 'tStartRefresh')  # time at next scr refresh
+        instrText.setAutoDraw(True)
+    
+    # *ready* updates
+    waitOnFlip = False
+    if ready.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
+        # keep track of start time/frame for later
+        ready.frameNStart = frameN  # exact frame index
+        ready.tStart = t  # local t and not account for scr refresh
+        ready.tStartRefresh = tThisFlipGlobal  # on global time
+        win.timeOnFlip(ready, 'tStartRefresh')  # time at next scr refresh
+        ready.status = STARTED
+        # keyboard checking is just starting
+        waitOnFlip = True
+        win.callOnFlip(ready.clock.reset)  # t=0 on next screen flip
+        win.callOnFlip(ready.clearEvents, eventType='keyboard')  # clear events on next screen flip
+    if ready.status == STARTED and not waitOnFlip:
+        theseKeys = ready.getKeys(keyList=None, waitRelease=False)
+        _ready_allKeys.extend(theseKeys)
+        if len(_ready_allKeys):
+            ready.keys = _ready_allKeys[-1].name  # just the last key pressed
+            ready.rt = _ready_allKeys[-1].rt
+            # a response ends the routine
+            continueRoutine = False
+    
+    # check for quit (typically the Esc key)
+    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
+        core.quit()
+    
+    # check if all components have finished
+    if not continueRoutine:  # a component has requested a forced-end of Routine
+        break
+    continueRoutine = False  # will revert to True if at least one component still running
+    for thisComponent in instructComponents:
+        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
+            continueRoutine = True
+            break  # at least one component has not yet finished
+    
+    # refresh the screen
+    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
+        win.flip()
+
+# -------Ending Routine "instruct"-------
+for thisComponent in instructComponents:
+    if hasattr(thisComponent, "setAutoDraw"):
+        thisComponent.setAutoDraw(False)
+thisExp.addData('instrText.started', instrText.tStartRefresh)
+thisExp.addData('instrText.stopped', instrText.tStopRefresh)
+# the Routine "instruct" was not non-slip safe, so reset the non-slip timer
+routineTimer.reset()
+
+# set up handler to look after randomisation of conditions etc
+trials = data.TrialHandler(nReps=5.0, method='random', 
+    extraInfo=expInfo, originPath=-1,
+    trialList=data.importConditions('trialTypes.xlsx'),
+    seed=None, name='trials')
+thisExp.addLoop(trials)  # add the loop to the experiment
+thisTrial = trials.trialList[0]  # so we can initialise stimuli with some values
+# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
+if thisTrial != None:
+    for paramName in thisTrial:
+        exec('{} = thisTrial[paramName]'.format(paramName))
+
+for thisTrial in trials:
+    currentLoop = trials
+    # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
+    if thisTrial != None:
+        for paramName in thisTrial:
+            exec('{} = thisTrial[paramName]'.format(paramName))
+    
+    # ------Prepare to start Routine "trial"-------
+    continueRoutine = True
+    # update component parameters for each repeat
+    stimulus.setColor(stimColor, colorSpace='rgb')
+    stimulus.setText(stimText)
+    response_event=None
+    trial_start=0
+    io.clearEvents()
+    # keep track of which components have finished
+    trialComponents = [stimulus]
+    for thisComponent in trialComponents:
+        thisComponent.tStart = None
+        thisComponent.tStop = None
+        thisComponent.tStartRefresh = None
+        thisComponent.tStopRefresh = None
+        if hasattr(thisComponent, 'status'):
+            thisComponent.status = NOT_STARTED
+    # reset timers
+    t = 0
+    _timeToFirstFrame = win.getFutureFlipTime(clock="now")
+    trialClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
+    frameN = -1
+    
+    # -------Run Routine "trial"-------
+    while continueRoutine:
+        # get current time
+        t = trialClock.getTime()
+        tThisFlip = win.getFutureFlipTime(clock=trialClock)
+        tThisFlipGlobal = win.getFutureFlipTime(clock=None)
+        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
+        # update/draw components on each frame
+        
+        # *stimulus* updates
+        if stimulus.status == NOT_STARTED and tThisFlip >= 0.5-frameTolerance:
+            # keep track of start time/frame for later
+            stimulus.frameNStart = frameN  # exact frame index
+            stimulus.tStart = t  # local t and not account for scr refresh
+            stimulus.tStartRefresh = tThisFlipGlobal  # on global time
+            win.timeOnFlip(stimulus, 'tStartRefresh')  # time at next scr refresh
+            stimulus.setAutoDraw(True)
+        if frameN == 0:
+            io.clearEvents('all')
+            trial_start=core.getTime()
+        else:
+            iokeys=iokeyboard.getEvents(EventConstants.KEYBOARD_PRESS)
+            for iok in iokeys:
+                if iok.key in [u'down',u'left',u'right']:
+                    response_event=iok
+                    continueRoutine = False 
+                    break
+        
+        # check for quit (typically the Esc key)
+        if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
+            core.quit()
+        
+        # check if all components have finished
+        if not continueRoutine:  # a component has requested a forced-end of Routine
+            break
+        continueRoutine = False  # will revert to True if at least one component still running
+        for thisComponent in trialComponents:
+            if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
+                continueRoutine = True
+                break  # at least one component has not yet finished
+        
+        # refresh the screen
+        if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
+            win.flip()
+    
+    # -------Ending Routine "trial"-------
+    for thisComponent in trialComponents:
+        if hasattr(thisComponent, "setAutoDraw"):
+            thisComponent.setAutoDraw(False)
+    trials.addData('stimulus.started', stimulus.tStartRefresh)
+    trials.addData('stimulus.stopped', stimulus.tStopRefresh)
+    trials.addData("trial_start_time", trial_start)
+    if response_event:
+        trials.addData("resp.time", response_event.time)
+        trials.addData("resp.rt", response_event.time-trial_start)
+        trials.addData("resp.duration", response_event.duration)
+        trials.addData('resp.keys',response_event.key)
+        trials.addData('resp.corr', response_event.key.lower()==corrAns)
+    else:
+        trials.addData("resp.time",-1.0)
+        trials.addData("resp.rt", -1.0)
+        trials.addData("resp.duration", -1.0)
+        trials.addData('resp.keys','None')
+        trials.addData('resp.corr', False)
+    
+    
+    # the Routine "trial" was not non-slip safe, so reset the non-slip timer
+    routineTimer.reset()
+    thisExp.nextEntry()
+    
+# completed 5.0 repeats of 'trials'
+
+
+# ------Prepare to start Routine "thanks"-------
+continueRoutine = True
+routineTimer.add(2.000000)
+# update component parameters for each repeat
+# keep track of which components have finished
+thanksComponents = [thanksText]
+for thisComponent in thanksComponents:
+    thisComponent.tStart = None
+    thisComponent.tStop = None
+    thisComponent.tStartRefresh = None
+    thisComponent.tStopRefresh = None
+    if hasattr(thisComponent, 'status'):
+        thisComponent.status = NOT_STARTED
+# reset timers
+t = 0
+_timeToFirstFrame = win.getFutureFlipTime(clock="now")
+thanksClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
+frameN = -1
+
+# -------Run Routine "thanks"-------
+while continueRoutine and routineTimer.getTime() > 0:
+    # get current time
+    t = thanksClock.getTime()
+    tThisFlip = win.getFutureFlipTime(clock=thanksClock)
+    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
+    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
+    # update/draw components on each frame
+    
+    # *thanksText* updates
+    if thanksText.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
+        # keep track of start time/frame for later
+        thanksText.frameNStart = frameN  # exact frame index
+        thanksText.tStart = t  # local t and not account for scr refresh
+        thanksText.tStartRefresh = tThisFlipGlobal  # on global time
+        win.timeOnFlip(thanksText, 'tStartRefresh')  # time at next scr refresh
+        thanksText.setAutoDraw(True)
+    if thanksText.status == STARTED:
+        # is it time to stop? (based on global clock, using actual start)
+        if tThisFlipGlobal > thanksText.tStartRefresh + 2.0-frameTolerance:
+            # keep track of stop time/frame for later
+            thanksText.tStop = t  # not accounting for scr refresh
+            thanksText.frameNStop = frameN  # exact frame index
+            win.timeOnFlip(thanksText, 'tStopRefresh')  # time at next scr refresh
+            thanksText.setAutoDraw(False)
+    
+    # check for quit (typically the Esc key)
+    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
+        core.quit()
+    
+    # check if all components have finished
+    if not continueRoutine:  # a component has requested a forced-end of Routine
+        break
+    continueRoutine = False  # will revert to True if at least one component still running
+    for thisComponent in thanksComponents:
+        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
+            continueRoutine = True
+            break  # at least one component has not yet finished
+    
+    # refresh the screen
+    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
+        win.flip()
+
+# -------Ending Routine "thanks"-------
+for thisComponent in thanksComponents:
+    if hasattr(thisComponent, "setAutoDraw"):
+        thisComponent.setAutoDraw(False)
+thisExp.addData('thanksText.started', thanksText.tStartRefresh)
+thisExp.addData('thanksText.stopped', thanksText.tStopRefresh)
+io.quit()
+
+# Flip one final time so any remaining win.callOnFlip() 
+# and win.timeOnFlip() tasks get executed before quitting
+win.flip()
+
+# these shouldn't be strictly necessary (should auto-save)
+thisExp.saveAsWideText(filename+'.csv', delim='auto')
+thisExp.saveAsPickle(filename)
+logging.flush()
+# make sure everything is closed down
+thisExp.abort()  # or data files will save again on exit
+win.close()
+core.quit()


Test patch:
diff --git a/psychopy/tests/test_app/test_builder/test_compileJS.py b/psychopy/tests/test_app/test_builder/test_compileJS.py
--- a/psychopy/tests/test_app/test_builder/test_compileJS.py
+++ b/psychopy/tests/test_app/test_builder/test_compileJS.py
@@ -65,7 +65,7 @@ def compileScript(self, infile=None, version=None, outfile=None):
     def test_stroop(self):
         #load experiment
         exp = experiment.Experiment()
-        exp.loadFromXML(demosDir/'builder/readyToPlay/stroop/stroop.psyexp')
+        exp.loadFromXML(demosDir/'builder'/'Experiments'/'stroop'/'stroop.psyexp')
         # try once packaging up the js libs
         exp.settings.params['JS libs'].val = 'remote'
         outFolder = self.temp_dir/'stroopJS_remote/html'
@@ -75,7 +75,7 @@ def test_stroop(self):
     def test_blocked(self):
         # load experiment
         exp = experiment.Experiment()
-        exp.loadFromXML(demosDir/'builder/understandingPsychopy/images_blocks/blockedTrials.psyexp')
+        exp.loadFromXML(demosDir/'builder'/'Design Templates'/'randomisedBlocks'/'randomisedBlocks.psyexp')
         # try once packaging up the js libs
         exp.settings.params['JS libs'].val = 'packaged'
         outFolder = self.temp_dir/'blocked_packaged/html'
@@ -86,7 +86,7 @@ def test_blocked(self):
     def test_JS_script_output(self):
         # Load experiment
         exp = experiment.Experiment()
-        exp.loadFromXML(demosDir/'builder'/'readyToPlay'/'stroop'/'stroop.psyexp')
+        exp.loadFromXML(demosDir/'builder'/'Experiments'/'stroop'/'stroop.psyexp')
         outFolder = self.temp_dir/'stroopJS_output/html'
         outFile = outFolder/'stroop.js'
         os.makedirs(outFolder)