#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Part of the PsychoPy library
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
# Distributed under the terms of the GNU General Public License (GPL).
"""Module for the PsychoPy GUI application.
"""
__all__ = [
'startApp',
'quitApp',
'restartApp',
'setRestartRequired',
'isRestartRequired',
'getAppInstance',
'getAppFrame',
'isAppStarted']
import sys
import os
from .console import StdStreamDispatcher
from .frametracker import openFrames
# Handle to the PsychoPy GUI application instance. We need to have this mainly
# to allow the plugin system to access GUI to allow for changes after startup.
_psychopyAppInstance = None
# Flag to indicate if the app requires a restart. This is set by the app when
# it needs to restart after an update or plugin installation. We can check this
# flag to determine if the app is in a state that it is recommended to restart.
REQUIRES_RESTART = False
[docs]def startApp(showSplash=True, testMode=False, safeMode=False, startView=None):
"""Start the PsychoPy GUI.
This function is idempotent, where additional calls after the app starts
will have no effect unless `quitApp()` was previously called. After this
function returns, you can get the handle to the created `PsychoPyApp`
instance by calling :func:`getAppInstance` (returns `None` otherwise).
Errors raised during initialization due to unhandled exceptions with respect
to the GUI application are usually fatal. You can examine
'last_app_load.log' inside the 'psychopy3' user directory (specified by
preference 'userPrefsDir') to see the traceback. After startup, unhandled
exceptions will appear in a special dialog box that shows the error
traceback and provides some means to recover their work. Regular logging
messages will appear in the log file or GUI. We use a separate error dialog
here is delineate errors occurring in the user's experiment scripts and
those of the application itself.
Parameters
----------
showSplash : bool
Show the splash screen on start.
testMode : bool
Must be `True` if creating an instance for unit testing.
safeMode : bool
Start PsychoPy in safe-mode. If `True`, the GUI application will launch
with without loading plugins.
startView : str, None
Name of the view to start the app with. Valid values are 'coder',
'builder' or 'runner'. If `None`, the app will start with the default
view or the view specifed with the `PSYCHOPYSTARTVIEW` environment
variable.
"""
global _psychopyAppInstance
if isAppStarted(): # do nothing it the app is already loaded
return # NOP
# Make sure logging is started before loading the bulk of the main
# application UI to catch as many errors as possible. After the app is
# loaded, messages are handled by the `StdStreamDispatcher` instance.
prefLogFilePath = None
if not testMode:
from psychopy.preferences import prefs
from psychopy.logging import console, DEBUG
# construct path to log file from preferences
userPrefsDir = prefs.paths['userPrefsDir']
prefLogFilePath = os.path.join(userPrefsDir, 'last_app_load.log')
lastRunLog = open(prefLogFilePath, 'w') # open the file for writing
console.setLevel(DEBUG)
# NOTE - messages and errors cropping up before this point will go to
# console, afterwards to 'last_app_load.log'.
sys.stderr = sys.stdout = lastRunLog # redirect output to file
# Create the application instance which starts loading it.
# If `testMode==True`, all messages and errors (i.e. exceptions) will log to
# console.
from psychopy.app._psychopyApp import PsychoPyApp
_psychopyAppInstance = PsychoPyApp(
0, testMode=testMode, showSplash=showSplash, safeMode=safeMode, startView=startView)
# After the app is loaded, we hand off logging to the stream dispatcher
# using the provided log file path. The dispatcher will write out any log
# messages to the extant log file and any GUI windows to show them to the
# user.
# ensure no instance was created before this one
if StdStreamDispatcher.getInstance() is not None:
raise RuntimeError(
'`StdStreamDispatcher` instance initialized outside of `startApp`, '
'this is not permitted.')
stdDisp = StdStreamDispatcher(_psychopyAppInstance, prefLogFilePath)
stdDisp.redirect()
if not testMode:
# Setup redirection of errors to the error reporting dialog box. We
# don't want this in the test environment since the box will cause the
# app to stall on error.
from psychopy.app.errorDlg import exceptionCallback
# After this point, errors will appear in a dialog box. Messages will
# continue to be written to the dialog.
sys.excepthook = exceptionCallback
# Allow the UI to refresh itself. Don't do this during testing where the
# UI is exercised programmatically.
_psychopyAppInstance.MainLoop()
[docs]def quitApp():
"""Quit the running PsychoPy application instance.
Will have no effect if `startApp()` has not been called previously.
"""
if not isAppStarted():
return
global _psychopyAppInstance
if hasattr(_psychopyAppInstance, 'quit'): # type check
_psychopyAppInstance.quit()
# PsychoPyApp._called_from_test = False # reset
_psychopyAppInstance = None
else:
raise AttributeError('Object `_psychopyApp` has no attribute `quit`.')
def restartApp():
"""Restart the PsychoPy application instance.
This will write a file named '.restart' to the user preferences directory
and quit the application. The presence of this file will indicate to the
launcher parent process that the app should restart.
The app restarts with the same arguments as the original launch. This is
useful for updating the application or plugins without requiring the user
to manually restart the app.
The user will be prompted to save any unsaved work before the app restarts.
"""
if not isAppStarted():
return
# write a restart file to the user preferences directory
from psychopy.preferences import prefs
restartFilePath = os.path.join(prefs.paths['userPrefsDir'], '.restart')
with open(restartFilePath, 'w') as restartFile:
restartFile.write('') # empty file
quitApp()
def setRestartRequired(state=True):
"""Set the flag to indicate that the app requires a restart.
This function is used by the app to indicate that a restart is required
after an update or plugin installation. The flag is checked by the launcher
parent process to determine if the app should restart.
Parameters
----------
state : bool
Set the restart flag. If `True`, the app will restart after quitting.
"""
global REQUIRES_RESTART
REQUIRES_RESTART = bool(state)
def isRestartRequired():
"""Check if the app requires a restart.
Parts of the application may set this flag to indicate that a restart is
required after an update or plugin installation.
Returns
-------
bool
`True` if the app requires a restart else `False`.
"""
return REQUIRES_RESTART
[docs]def getAppInstance():
"""Get a reference to the `PsychoPyApp` object.
This function will return `None` if PsychoPy has been imported as a library
or the app has not been fully realized.
Returns
-------
PsychoPyApp or None
Handle to the application instance. Returns `None` if the app has not
been started yet or the PsychoPy is being used without a GUI.
Examples
--------
Get the coder frame (if any)::
import psychopy.app as app
coder = app.getAppInstance().coder
"""
return _psychopyAppInstance # use a function here to protect the reference
def setAppInstance(obj):
"""
Define a reference to the current PsychoPyApp object.
Parameters
----------
obj : psychopy.app._psychopyApp.PsychoPyApp
Current instance of the PsychoPy app
"""
global _psychopyAppInstance
_psychopyAppInstance = obj
[docs]def isAppStarted():
"""Check if the GUI portion of PsychoPy is running.
Returns
-------
bool
`True` if the GUI is started else `False`.
"""
return _psychopyAppInstance is not None
[docs]def getAppFrame(frameName):
"""Get the reference to one of PsychoPy's application frames. Returns `None`
if the specified frame has not been fully realized yet or PsychoPy is not in
GUI mode.
Parameters
----------
frameName : str
Identifier for the frame to get a reference to. Valid names are
'coder', 'builder' or 'runner'.
Returns
-------
object or None
Reference to the frame instance (i.e. `CoderFrame`, `BuilderFrame` or
`RunnerFrame`). `None` is returned if the frame has not been created or
the app is not running. May return a list if more than one window is
opened.
"""
if not isAppStarted(): # PsychoPy is not in GUI mode
return None
if frameName not in ('builder', 'coder', 'runner'):
raise ValueError('Invalid identifier specified as `frameName`.')
# open the requested frame if no yet loaded
frameRef = getattr(_psychopyAppInstance, frameName, None)
if frameRef is None:
if frameName == 'builder' and hasattr(_psychopyAppInstance, 'showBuilder'):
_psychopyAppInstance.showBuilder()
elif frameName == 'coder' and hasattr(_psychopyAppInstance, 'showCoder'):
_psychopyAppInstance.showCoder()
elif frameName == 'runner' and hasattr(_psychopyAppInstance, 'showRunner'):
_psychopyAppInstance.showRunner()
else:
raise AttributeError('Cannot load frame. Method not available.')
frameRef = getattr(_psychopyAppInstance, frameName, None)
return frameRef
if __name__ == "__main__":
pass