psychopy 2024.2.1__py3-none-any.whl → 2024.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.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/.DS_Store +0 -0
- psychopy/GIT_SHA +1 -1
- psychopy/VERSION +1 -1
- psychopy/__init__.py +10 -1
- psychopy/__init__.py.orig +65 -0
- psychopy/app/{locale/ar_001/.DS_Store → .DS_Store} +0 -0
- psychopy/app/Resources/.DS_Store +0 -0
- psychopy/app/_psychopyApp.py +11 -3
- psychopy/app/appData.spec +1 -1
- psychopy/app/builder/builder.py +1 -1
- psychopy/app/builder/builder.py.orig +3932 -0
- psychopy/app/builder/dialogs/__init__.py.orig +1679 -0
- psychopy/app/builder/dialogs/paramCtrls.py +1 -1
- psychopy/app/builder/dialogs/paramCtrls.py.orig +713 -0
- psychopy/app/colorpicker/__init__.py.orig +411 -0
- psychopy/app/cortex.log +0 -0
- psychopy/app/jobs.py +8 -1
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +2452 -1731
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN.po +6127 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN_allFlagged.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN_allFlagged.po +7366 -0
- psychopy/app/plugin_manager/dialog.py +9 -7
- psychopy/app/ribbon.py +2 -1
- psychopy/app/runner/runner.py +7 -5
- psychopy/clock.py +8 -4
- psychopy/core.py.orig +169 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/index.html +23 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/randomisedBlocks-legacy-browsers.js +423 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/randomisedBlocks.js +427 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/chooseBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/facesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/housesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.py +330 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks_lastrun.py +330 -0
- psychopy/demos/builder/Feature Demos/eyetracking/eyetracking.xml +298 -0
- psychopy/demos/builder/Feature Demos/eyetracking/eyetracking.xsd +120 -0
- psychopy/demos/builder/Tools/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.csv +38 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.log +3418 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.csv +2 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.log +15 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual.psyexp +323 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual.py +562 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual_lastrun.py +562 -0
- psychopy/demos/builder/Tools/gammaCalibration/questStairs.xlsx +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/readme.md +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/low_contrast.png +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/make_2nd_order_tex.py +59 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/second_order_tex.png +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/experiment control/info_gamma.pickle +0 -0
- psychopy/demos/coder/iohub/.iohpid +1 -0
- psychopy/demos/coder/iohub/eyetracking/.iohpid +1 -0
- psychopy/demos/coder/iohub/wintab/.DS_Store +0 -0
- psychopy/demos/coder/stimuli/.DS_Store +0 -0
- psychopy/demos/coder/stimuli/radialGratingContracting.py +29 -0
- psychopy/experiment/_experiment.py.orig +1032 -0
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/_base.py +13 -4
- psychopy/experiment/components/_base.py.orig +823 -0
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +10 -1
- psychopy/experiment/components/microphone/__init__.py.orig +490 -0
- psychopy/experiment/components/polygon/__init__.py +21 -22
- psychopy/experiment/components/settings/__init__.py +13 -14
- psychopy/experiment/components/settings/__init__.py.orig +1337 -0
- psychopy/experiment/components/textbox/__init__.py.orig +310 -0
- psychopy/experiment/components/webcam/.DS_Store +0 -0
- psychopy/experiment/components/webcam/light/.DS_Store +0 -0
- psychopy/experiment/flow.py +10 -8
- psychopy/experiment/loops.py.orig +829 -0
- psychopy/experiment/params.py +8 -3
- psychopy/experiment/params.py.orig +408 -0
- psychopy/experiment/routine.py.orig +503 -0
- psychopy/experiment/routines/_base.py +15 -6
- psychopy/experiment/routines/counterbalance/__init__.py +1 -0
- psychopy/gui/qtgui.py +14 -7
- psychopy/gui/util.py +10 -14
- psychopy/gui/wxgui.py +10 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/brainproducts.py.orig +680 -0
- psychopy/hardware/iolab.py.orig +238 -0
- psychopy/hardware/manager.py +1 -1
- psychopy/hardware/photodiode.py +59 -27
- psychopy/hardware/serialport.py +51 -0
- psychopy/hardware/speaker.py +4 -4
- psychopy/iohub/datastore/__init__.py.orig +443 -0
- psychopy/iohub/datastore/util.py.orig +692 -0
- psychopy/iohub/devices/mouse/darwin.py.orig +427 -0
- psychopy/iohub/devices/mouse/linux2.py.orig +198 -0
- psychopy/preferences/.DS_Store +0 -0
- psychopy/projects/pavlovia.py +10 -3
- psychopy/projects/pavlovia.py.orig +1295 -0
- psychopy/sound/backend_ptb.py +22 -5
- psychopy/sound/transcribe.py +24 -4
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestCircle_fill_local.png +0 -0
- psychopy/tests/data/__test.png +0 -0
- psychopy/tests/data/aperture1_normHexbackground_local.png +0 -0
- psychopy/tests/data/aperture1_norm_local.png +0 -0
- psychopy/tests/data/aperture2_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_height_local.png +0 -0
- psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_norm_local.png +0 -0
- psychopy/tests/data/beatandrcos_stencil_local.png +0 -0
- psychopy/tests/data/blend_add_height_local.png +0 -0
- psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
- psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
- psychopy/tests/data/blend_add_normNoShade_local.png +0 -0
- psychopy/tests/data/blend_add_norm_local.png +0 -0
- psychopy/tests/data/blend_add_stencil_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_height_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normNoShade_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_stencil_local.png +0 -0
- psychopy/tests/data/circleHex_height_local.png +0 -0
- psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
- psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
- psychopy/tests/data/circleHex_normNoShade_local.png +0 -0
- psychopy/tests/data/circleHex_norm_local.png +0 -0
- psychopy/tests/data/circleHex_stencil_local.png +0 -0
- psychopy/tests/data/color_comparison_local.png +0 -0
- psychopy/tests/data/corrFullRandom_local.csv +16 -0
- psychopy/tests/data/corrFullRandom_local.tsv +6 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/dots_height_local.png +0 -0
- psychopy/tests/data/dots_normAddBlend_local.png +0 -0
- psychopy/tests/data/dots_normHexbackground_local.png +0 -0
- psychopy/tests/data/dots_normNoShade_local.png +0 -0
- psychopy/tests/data/dots_norm_local.png +0 -0
- psychopy/tests/data/dots_stencil_local.png +0 -0
- psychopy/tests/data/elarray1_height_local.png +0 -0
- psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
- psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
- psychopy/tests/data/elarray1_norm_local.png +0 -0
- psychopy/tests/data/elarray1_stencil_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_stencil_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_stencil_local.png +0 -0
- psychopy/tests/data/gabor1_height_local.png +0 -0
- psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
- psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
- psychopy/tests/data/gabor1_normNoShade_local.png +0 -0
- psychopy/tests/data/gabor1_norm_local.png +0 -0
- psychopy/tests/data/gabor1_stencil_local.png +0 -0
- psychopy/tests/data/greyscale_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_height_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normNoShade_local.png +0 -0
- psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
- psychopy/tests/data/imageAndGauss_stencil_local.png +0 -0
- psychopy/tests/data/movFrame1_stencil_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/numpyImage_height_local.png +0 -0
- psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
- psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
- psychopy/tests/data/numpyImage_normNoShade_local.png +0 -0
- psychopy/tests/data/numpyImage_norm_local.png +0 -0
- psychopy/tests/data/numpyImage_stencil_local.png +0 -0
- psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
- psychopy/tests/data/shape2_1_normHexbackground_local.png +0 -0
- psychopy/tests/data/shape2_1_normNoShade_local.png +0 -0
- psychopy/tests/data/shape2_1_norm_local.png +0 -0
- psychopy/tests/data/shape2_1_stencil_local.png +0 -0
- psychopy/tests/data/testLoopsBlocks.psyexp_local.py +328 -0
- psychopy/tests/data/text1_height_local.png +0 -0
- psychopy/tests/data/text1_normAddBlend_local.png +0 -0
- psychopy/tests/data/text1_normHexbackground_local.png +0 -0
- psychopy/tests/data/text1_norm_local.png +0 -0
- psychopy/tests/data/text1_stencil_local.png +0 -0
- psychopy/tests/data/text2_height.png +0 -0
- psychopy/tests/data/text2_normAddBlend.png +0 -0
- psychopy/tests/data/text2_normHexbackground.png +0 -0
- psychopy/tests/data/text2_stencil.png +0 -0
- psychopy/tests/data/wedge1_height_local.png +0 -0
- psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
- psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
- psychopy/tests/data/wedge1_normNoShade_local.png +0 -0
- psychopy/tests/data/wedge1_norm_local.png +0 -0
- psychopy/tests/data/wedge1_stencil_local.png +0 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.csv +9 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.log +177 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.xlsx +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.csv +9 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.log +168 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.xlsx +0 -0
- psychopy/tests/test_data/.DS_Store +0 -0
- psychopy/tests/test_hardware/test_CRS_BitsSharp.py.orig +68 -0
- psychopy/tests/test_tools/test_arraytools.py +112 -0
- psychopy/tests/test_visual/test_image.py.orig +219 -0
- psychopy/tools/arraytools.py +47 -0
- psychopy/tools/versionchooser.py +1 -1
- psychopy/visual/backends/pygletbackend.py +26 -8
- psychopy/visual/basevisual.py.orig +1723 -0
- psychopy/visual/form.py.orig +1181 -0
- psychopy/visual/text.py.orig +752 -0
- psychopy/visual/textbox2/textbox2.py.orig +1315 -0
- psychopy/visual/window.py +13 -5
- psychopy/visual/windowwarp.py.orig +463 -0
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/METADATA +9 -9
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/RECORD +244 -78
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/WHEEL +1 -1
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/entry_points.txt +2 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy-2024.2.1.dist-info/licenses/AUTHORS.md +0 -138
- /psychopy/{app/locale/ar_001/LC_MESSAGE → demos/builder}/.DS_Store +0 -0
- /psychopy/{app/locale/es_ES/LC_MESSAGE → demos/builder/Experiments}/.DS_Store +0 -0
- /psychopy/{visual → demos/builder/Tools/gammaCalibration/data}/.DS_Store +0 -0
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1679 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Part of the PsychoPy library
|
|
5
|
+
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2021 Open Science Tools Ltd.
|
|
6
|
+
# Distributed under the terms of the GNU General Public License (GPL).
|
|
7
|
+
|
|
8
|
+
"""Dialog classes for the Builder, including ParamCtrls
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import absolute_import, division, print_function
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from builtins import map
|
|
15
|
+
from builtins import str
|
|
16
|
+
from builtins import object
|
|
17
|
+
import os
|
|
18
|
+
import copy
|
|
19
|
+
from collections import OrderedDict
|
|
20
|
+
|
|
21
|
+
import numpy
|
|
22
|
+
import re
|
|
23
|
+
import wx
|
|
24
|
+
|
|
25
|
+
import psychopy.experiment.utils
|
|
26
|
+
from psychopy.experiment import Param
|
|
27
|
+
|
|
28
|
+
from ... import dialogs
|
|
29
|
+
from .. import experiment
|
|
30
|
+
from .. validators import NameValidator, CodeSnippetValidator, WarningManager
|
|
31
|
+
from .dlgsConditions import DlgConditions
|
|
32
|
+
from .dlgsCode import DlgCodeComponentProperties, CodeBox
|
|
33
|
+
from . import paramCtrls
|
|
34
|
+
from psychopy import data, logging
|
|
35
|
+
from psychopy.localization import _translate
|
|
36
|
+
from psychopy.tools import versionchooser as vc
|
|
37
|
+
from ...colorpicker import PsychoColorPicker
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
from ...themes import ThemeMixin
|
|
41
|
+
|
|
42
|
+
white = wx.Colour(255, 255, 255, 255)
|
|
43
|
+
codeSyntaxOkay = wx.Colour(220, 250, 220, 255) # light green
|
|
44
|
+
|
|
45
|
+
from ..localizedStrings import _localizedDialogs as _localized
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ParamCtrls(object):
|
|
49
|
+
|
|
50
|
+
def __init__(self, dlg, label, param, parent, fieldName,
|
|
51
|
+
browse=False, noCtrls=False, advanced=False, appPrefs=None):
|
|
52
|
+
"""Create a set of ctrls for a particular Component Parameter, to be
|
|
53
|
+
used in Component Properties dialogs. These need to be positioned
|
|
54
|
+
by the calling dlg.
|
|
55
|
+
|
|
56
|
+
e.g.::
|
|
57
|
+
|
|
58
|
+
param = experiment.Param(val='boo', valType='str')
|
|
59
|
+
ctrls = ParamCtrls(dlg=self, label=fieldName,param=param)
|
|
60
|
+
self.paramCtrls[fieldName] = ctrls # keep track in the dlg
|
|
61
|
+
sizer.Add(ctrls.nameCtrl, (currRow,0), (1,1),wx.ALIGN_RIGHT )
|
|
62
|
+
sizer.Add(ctrls.valueCtrl, (currRow,1) )
|
|
63
|
+
# these are optional (the parameter might be None)
|
|
64
|
+
if ctrls.typeCtrl:
|
|
65
|
+
sizer.Add(ctrls.typeCtrl, (currRow,2) )
|
|
66
|
+
if ctrls.updateCtrl:
|
|
67
|
+
sizer.Add(ctrls.updateCtrl, (currRow,3))
|
|
68
|
+
|
|
69
|
+
If browse is True then a browseCtrl will be added (you need to
|
|
70
|
+
bind events yourself). If noCtrls is True then no actual wx widgets
|
|
71
|
+
are made, but attribute names are created
|
|
72
|
+
|
|
73
|
+
`fieldName`'s value is always in en_US, and never for display,
|
|
74
|
+
whereas `label` is only for display and can be translated or
|
|
75
|
+
tweaked (e.g., add '$'). Component._localized.keys() are
|
|
76
|
+
`fieldName`s, and .values() are `label`s.
|
|
77
|
+
"""
|
|
78
|
+
super(ParamCtrls, self).__init__()
|
|
79
|
+
self.param = param
|
|
80
|
+
self.dlg = dlg
|
|
81
|
+
self.dpi = self.dlg.dpi
|
|
82
|
+
self.valueWidth = self.dpi * 3.5
|
|
83
|
+
# try to find the experiment
|
|
84
|
+
self.exp = None
|
|
85
|
+
tryForExp = self.dlg
|
|
86
|
+
|
|
87
|
+
while self.exp is None:
|
|
88
|
+
if hasattr(tryForExp, 'frame'):
|
|
89
|
+
self.exp = tryForExp.frame.exp
|
|
90
|
+
else:
|
|
91
|
+
try:
|
|
92
|
+
tryForExp = tryForExp.parent # try going up a level
|
|
93
|
+
except Exception:
|
|
94
|
+
tryForExp.parent
|
|
95
|
+
|
|
96
|
+
# param has the fields:
|
|
97
|
+
# val, valType, allowedVals=[],allowedTypes=[],
|
|
98
|
+
# hint="", updates=None, allowedUpdates=None
|
|
99
|
+
# we need the following:
|
|
100
|
+
self.nameCtrl = self.valueCtrl = self.typeCtrl = None
|
|
101
|
+
self.updateCtrl = self.browseCtrl = None
|
|
102
|
+
if noCtrls:
|
|
103
|
+
return # we don't need to do any more
|
|
104
|
+
|
|
105
|
+
if type(param.val) == numpy.ndarray:
|
|
106
|
+
initial = param.val.tolist() # convert numpy arrays to lists
|
|
107
|
+
label = _translate(label)
|
|
108
|
+
self.nameCtrl = wx.StaticText(parent, -1, label, size=wx.DefaultSize)
|
|
109
|
+
|
|
110
|
+
if fieldName == 'Use version':
|
|
111
|
+
# _localVersionsCache is the default (faster) when creating
|
|
112
|
+
# settings. If remote info has become available in the meantime,
|
|
113
|
+
# now populate with that as well
|
|
114
|
+
if vc._remoteVersionsCache:
|
|
115
|
+
options = vc._versionFilter(vc.versionOptions(local=False), wx.__version__)
|
|
116
|
+
versions = vc._versionFilter(vc.availableVersions(local=False), wx.__version__)
|
|
117
|
+
param.allowedVals = (options + [''] + versions)
|
|
118
|
+
|
|
119
|
+
if param.inputType == "single":
|
|
120
|
+
# Create single line string control
|
|
121
|
+
self.valueCtrl = paramCtrls.SingleLineCtrl(parent,
|
|
122
|
+
val=str(param.val), valType=param.valType,
|
|
123
|
+
fieldName=fieldName,size=wx.Size(self.valueWidth, 24))
|
|
124
|
+
elif param.inputType == 'multi':
|
|
125
|
+
# Create multiline string control
|
|
126
|
+
self.valueCtrl = paramCtrls.MultiLineCtrl(parent,
|
|
127
|
+
val=str(param.val), valType=param.valType,
|
|
128
|
+
fieldName=fieldName, size=wx.Size(self.valueWidth, 144))
|
|
129
|
+
# Set focus if field is text of a Textbox or Text component
|
|
130
|
+
if fieldName == 'text':
|
|
131
|
+
self.valueCtrl.SetFocus()
|
|
132
|
+
elif param.inputType == 'spin':
|
|
133
|
+
# Create single line string control
|
|
134
|
+
self.valueCtrl = paramCtrls.SingleLineCtrl(parent,
|
|
135
|
+
val=str(param.val), valType=param.valType,
|
|
136
|
+
fieldName=fieldName,size=wx.Size(self.valueWidth, 24))
|
|
137
|
+
# Will have to disable spinCtrl until we have a dropdown for inputType, sadly
|
|
138
|
+
# self.valueCtrl = paramCtrls.IntCtrl(parent,
|
|
139
|
+
# val=param.val, valType=param.valType,
|
|
140
|
+
# fieldName=fieldName,size=wx.Size(self.valueWidth, 24),
|
|
141
|
+
# limits=param.allowedVals)
|
|
142
|
+
elif param.inputType == 'choice':
|
|
143
|
+
self.valueCtrl = paramCtrls.ChoiceCtrl(parent,
|
|
144
|
+
val=str(param.val), valType=param.valType, choices=param.allowedVals,
|
|
145
|
+
fieldName=fieldName, size=wx.Size(self.valueWidth, 24))
|
|
146
|
+
elif param.inputType == 'multiChoice':
|
|
147
|
+
self.valueCtrl = paramCtrls.MultiChoiceCtrl(parent, valType=param.valType,
|
|
148
|
+
vals=param.val, choices=param.allowedVals, fieldName=fieldName,
|
|
149
|
+
size=wx.Size(self.valueWidth, -1))
|
|
150
|
+
elif param.inputType == 'bool':
|
|
151
|
+
self.valueCtrl = paramCtrls.BoolCtrl(parent,
|
|
152
|
+
name=fieldName,size=wx.Size(self.valueWidth, 24))
|
|
153
|
+
self.valueCtrl.SetValue(bool(param.val))
|
|
154
|
+
elif param.inputType == 'file' or browse:
|
|
155
|
+
self.valueCtrl = paramCtrls.FileCtrl(parent,
|
|
156
|
+
val=str(param.val), valType=param.valType,
|
|
157
|
+
fieldName=fieldName, size=wx.Size(self.valueWidth, 24))
|
|
158
|
+
self.valueCtrl.allowedVals = param.allowedVals
|
|
159
|
+
elif param.inputType == 'fileList':
|
|
160
|
+
self.valueCtrl = paramCtrls.FileListCtrl(parent,
|
|
161
|
+
choices=param.val, valType=param.valType,
|
|
162
|
+
size=wx.Size(self.valueWidth, 100), pathtype="rel")
|
|
163
|
+
elif param.inputType == 'table':
|
|
164
|
+
self.valueCtrl = paramCtrls.TableCtrl(parent, val=param.val, valType=param.valType,
|
|
165
|
+
fieldName=fieldName, size=wx.Size(self.valueWidth, 24))
|
|
166
|
+
elif param.inputType == 'color':
|
|
167
|
+
self.valueCtrl = paramCtrls.ColorCtrl(parent,
|
|
168
|
+
val=param.val, valType=param.valType,
|
|
169
|
+
fieldName=fieldName, size=wx.Size(self.valueWidth, 24))
|
|
170
|
+
elif param.inputType == 'dict':
|
|
171
|
+
self.valueCtrl = paramCtrls.DictCtrl(parent,
|
|
172
|
+
val=self.exp.settings.getInfo(), valType=param.valType,
|
|
173
|
+
fieldName=fieldName)
|
|
174
|
+
else:
|
|
175
|
+
self.valueCtrl = paramCtrls.SingleLineCtrl(parent,
|
|
176
|
+
val=str(param.val), valType=param.valType,
|
|
177
|
+
fieldName=fieldName,size=wx.Size(self.valueWidth, 24))
|
|
178
|
+
logging.warn(f"Parameter {fieldName} has unrecognised inputType \"{param.inputType}\"")
|
|
179
|
+
|
|
180
|
+
# if fieldName == 'Experiment info':
|
|
181
|
+
# # for expInfo convert from a string to the list-of-dicts
|
|
182
|
+
# val = self.expInfoToListWidget(param.val)
|
|
183
|
+
# self.valueCtrl = dialogs.ListWidget(
|
|
184
|
+
# parent, val, order=['Field', 'Default'])
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
self.valueCtrl.SetToolTip(wx.ToolTip(_translate(param.hint)))
|
|
188
|
+
except AttributeError as e:
|
|
189
|
+
self.valueCtrl.SetToolTipString(_translate(param.hint))
|
|
190
|
+
|
|
191
|
+
if len(param.allowedVals) == 1 or param.readOnly:
|
|
192
|
+
self.valueCtrl.Disable() # visible but can't be changed
|
|
193
|
+
|
|
194
|
+
# add a Validator to the valueCtrl
|
|
195
|
+
if fieldName == "name":
|
|
196
|
+
self.valueCtrl.SetValidator(NameValidator())
|
|
197
|
+
elif param.inputType in ("single", "multi"):
|
|
198
|
+
# only want anything that is valType code, or can be with $
|
|
199
|
+
self.valueCtrl.SetValidator(CodeSnippetValidator(fieldName))
|
|
200
|
+
|
|
201
|
+
# create the type control
|
|
202
|
+
if len(param.allowedTypes):
|
|
203
|
+
# are there any components with non-empty allowedTypes?
|
|
204
|
+
self.typeCtrl = wx.Choice(parent, choices=param.allowedTypes)
|
|
205
|
+
self.typeCtrl._choices = copy.copy(param.allowedTypes)
|
|
206
|
+
index = param.allowedTypes.index(param.valType)
|
|
207
|
+
self.typeCtrl.SetSelection(index)
|
|
208
|
+
if len(param.allowedTypes) == 1:
|
|
209
|
+
self.typeCtrl.Disable() # visible but can't be changed
|
|
210
|
+
|
|
211
|
+
# create update control
|
|
212
|
+
if param.allowedUpdates is not None and len(param.allowedUpdates):
|
|
213
|
+
# updates = display-only version of allowed updates
|
|
214
|
+
updateLabels = [_localized[upd] for upd in param.allowedUpdates]
|
|
215
|
+
# allowedUpdates = extend version of allowed updates that includes
|
|
216
|
+
# "set during:static period"
|
|
217
|
+
allowedUpdates = copy.copy(param.allowedUpdates)
|
|
218
|
+
for routineName, routine in list(self.exp.routines.items()):
|
|
219
|
+
for static in routine.getStatics():
|
|
220
|
+
# Note: replacing following line with
|
|
221
|
+
# "localizedMsg = _translate(msg)",
|
|
222
|
+
# poedit would not able to find this message.
|
|
223
|
+
msg = "set during: "
|
|
224
|
+
localizedMsg = _translate("set during: ")
|
|
225
|
+
fullName = "{}.{}".format(
|
|
226
|
+
routineName, static.params['name'])
|
|
227
|
+
allowedUpdates.append(msg + fullName)
|
|
228
|
+
updateLabels.append(localizedMsg + fullName)
|
|
229
|
+
self.updateCtrl = wx.Choice(parent, choices=updateLabels)
|
|
230
|
+
# stash non-localized choices to allow retrieval by index:
|
|
231
|
+
self.updateCtrl._choices = copy.copy(allowedUpdates)
|
|
232
|
+
# If parameter isn't in list, default to the first choice
|
|
233
|
+
if param.updates not in allowedUpdates:
|
|
234
|
+
param.updates = allowedUpdates[0]
|
|
235
|
+
# get index of the currently set update value, set display:
|
|
236
|
+
index = allowedUpdates.index(param.updates)
|
|
237
|
+
# set by integer index, not string value
|
|
238
|
+
self.updateCtrl.SetSelection(index)
|
|
239
|
+
|
|
240
|
+
if param.allowedUpdates != None and len(param.allowedUpdates) == 1:
|
|
241
|
+
self.updateCtrl.Disable() # visible but can't be changed
|
|
242
|
+
|
|
243
|
+
def _getCtrlValue(self, ctrl):
|
|
244
|
+
"""Retrieve the current value form the control (whatever type of ctrl
|
|
245
|
+
it is, e.g. checkbox.GetValue, choice.GetSelection)
|
|
246
|
+
Different types of control have different methods for retrieving
|
|
247
|
+
value. This function checks them all and returns the value or None.
|
|
248
|
+
|
|
249
|
+
.. note::
|
|
250
|
+
Don't use GetStringSelection() here to avoid that translated value
|
|
251
|
+
is returned. Instead, use GetSelection() to get index of selection
|
|
252
|
+
and get untranslated value from _choices attribute.
|
|
253
|
+
"""
|
|
254
|
+
if ctrl is None:
|
|
255
|
+
return None
|
|
256
|
+
elif hasattr(ctrl, 'GetText'):
|
|
257
|
+
return ctrl.GetText()
|
|
258
|
+
elif hasattr(ctrl, 'GetValue'): # e.g. TextCtrl
|
|
259
|
+
val = ctrl.GetValue()
|
|
260
|
+
if isinstance(self.valueCtrl, dialogs.ListWidget):
|
|
261
|
+
val = self.expInfoFromListWidget(val)
|
|
262
|
+
return val
|
|
263
|
+
elif hasattr(ctrl, 'GetCheckedStrings'):
|
|
264
|
+
return ctrl.GetCheckedStrings()
|
|
265
|
+
elif hasattr(ctrl, 'GetSelection'): # for wx.Choice
|
|
266
|
+
# _choices is defined during __init__ for all wx.Choice() ctrls
|
|
267
|
+
# NOTE: add untranslated value to _choices if
|
|
268
|
+
# _choices[ctrl.GetSelection()] fails.
|
|
269
|
+
return ctrl._choices[ctrl.GetSelection()]
|
|
270
|
+
elif hasattr(ctrl, 'GetLabel'): # for wx.StaticText
|
|
271
|
+
return ctrl.GetLabel()
|
|
272
|
+
else:
|
|
273
|
+
print("failed to retrieve the value for %s" % ctrl)
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
def _setCtrlValue(self, ctrl, newVal):
|
|
277
|
+
"""Set the current value of the control (whatever type of ctrl it
|
|
278
|
+
is, e.g. checkbox.SetValue, choice.SetSelection)
|
|
279
|
+
Different types of control have different methods for retrieving
|
|
280
|
+
value. This function checks them all and returns the value or None.
|
|
281
|
+
|
|
282
|
+
.. note::
|
|
283
|
+
Don't use SetStringSelection() here to avoid using translated
|
|
284
|
+
value. Instead, get index of the value using _choices attribute
|
|
285
|
+
and use SetSelection() to set the value.
|
|
286
|
+
"""
|
|
287
|
+
if ctrl is None:
|
|
288
|
+
return None
|
|
289
|
+
elif hasattr(ctrl, 'SetValue'): # e.g. TextCtrl
|
|
290
|
+
ctrl.SetValue(newVal)
|
|
291
|
+
elif hasattr(ctrl, 'SetSelection'): # for wx.Choice
|
|
292
|
+
# _choices = list of non-localized strings, set during __init__
|
|
293
|
+
# NOTE: add untranslated value to _choices if
|
|
294
|
+
# _choices.index(newVal) fails.
|
|
295
|
+
index = ctrl._choices.index(newVal)
|
|
296
|
+
# set the display to the localized version of the string:
|
|
297
|
+
ctrl.SetSelection(index)
|
|
298
|
+
elif hasattr(ctrl, 'SetLabel'): # for wx.StaticText
|
|
299
|
+
ctrl.SetLabel(newVal)
|
|
300
|
+
else:
|
|
301
|
+
print("failed to retrieve the value for %s" % (ctrl))
|
|
302
|
+
|
|
303
|
+
def getValue(self):
|
|
304
|
+
"""Get the current value of the value ctrl
|
|
305
|
+
"""
|
|
306
|
+
return self._getCtrlValue(self.valueCtrl)
|
|
307
|
+
|
|
308
|
+
def setValue(self, newVal):
|
|
309
|
+
"""Get the current value of the value ctrl
|
|
310
|
+
"""
|
|
311
|
+
return self._setCtrlValue(self.valueCtrl, newVal)
|
|
312
|
+
|
|
313
|
+
def getType(self):
|
|
314
|
+
"""Get the current value of the type ctrl
|
|
315
|
+
"""
|
|
316
|
+
if self.typeCtrl:
|
|
317
|
+
return self._getCtrlValue(self.typeCtrl)
|
|
318
|
+
|
|
319
|
+
def getUpdates(self):
|
|
320
|
+
"""Get the current value of the updates ctrl
|
|
321
|
+
"""
|
|
322
|
+
if self.updateCtrl:
|
|
323
|
+
return self._getCtrlValue(self.updateCtrl)
|
|
324
|
+
|
|
325
|
+
def setVisible(self, newVal=True):
|
|
326
|
+
if hasattr(self.valueCtrl, "ShowAll"):
|
|
327
|
+
self.valueCtrl.ShowAll(newVal)
|
|
328
|
+
else:
|
|
329
|
+
self.valueCtrl.Show(newVal)
|
|
330
|
+
self.nameCtrl.Show(newVal)
|
|
331
|
+
if self.updateCtrl:
|
|
332
|
+
self.updateCtrl.Show(newVal)
|
|
333
|
+
if self.typeCtrl:
|
|
334
|
+
self.typeCtrl.Show(newVal)
|
|
335
|
+
|
|
336
|
+
def expInfoToListWidget(self, expInfoStr):
|
|
337
|
+
"""Takes a string describing a dictionary and turns it into a format
|
|
338
|
+
that the ListWidget can receive.
|
|
339
|
+
|
|
340
|
+
returns: list of dicts of {Field:'', Default:''}
|
|
341
|
+
"""
|
|
342
|
+
expInfo = self.exp.settings.getInfo()
|
|
343
|
+
|
|
344
|
+
listOfDicts = []
|
|
345
|
+
for field, default in list(expInfo.items()):
|
|
346
|
+
listOfDicts.append({'Field': field, 'Default': default})
|
|
347
|
+
return listOfDicts
|
|
348
|
+
|
|
349
|
+
def expInfoFromListWidget(self, listOfDicts):
|
|
350
|
+
"""Creates a string representation of a dict from a list of
|
|
351
|
+
field / default values.
|
|
352
|
+
"""
|
|
353
|
+
expInfo = {}
|
|
354
|
+
for field in listOfDicts:
|
|
355
|
+
expInfo[field['Field']] = field['Default']
|
|
356
|
+
expInfoStr = repr(expInfo)
|
|
357
|
+
return expInfoStr
|
|
358
|
+
|
|
359
|
+
def setChangesCallback(self, callbackFunction):
|
|
360
|
+
"""Set a callback to detect any changes in this value (whether it's
|
|
361
|
+
a checkbox event or a text event etc
|
|
362
|
+
|
|
363
|
+
:param callbackFunction: the function to be called when the valueCtrl
|
|
364
|
+
changes value
|
|
365
|
+
:return:
|
|
366
|
+
"""
|
|
367
|
+
if isinstance(self.valueCtrl, wx.TextCtrl):
|
|
368
|
+
self.valueCtrl.Bind(wx.EVT_KEY_UP, callbackFunction)
|
|
369
|
+
elif isinstance(self.valueCtrl, CodeBox):
|
|
370
|
+
self.valueCtrl.Bind(wx.stc.EVT_STC_CHANGE, callbackFunction)
|
|
371
|
+
elif isinstance(self.valueCtrl, wx.ComboBox):
|
|
372
|
+
self.valueCtrl.Bind(wx.EVT_COMBOBOX, callbackFunction)
|
|
373
|
+
elif isinstance(self.valueCtrl, wx.Choice):
|
|
374
|
+
self.valueCtrl.Bind(wx.EVT_CHOICE, callbackFunction)
|
|
375
|
+
elif isinstance(self.valueCtrl, wx.CheckListBox):
|
|
376
|
+
self.valueCtrl.Bind(wx.EVT_CHECKLISTBOX, callbackFunction)
|
|
377
|
+
elif isinstance(self.valueCtrl, wx.CheckBox):
|
|
378
|
+
self.valueCtrl.Bind(wx.EVT_CHECKBOX, callbackFunction)
|
|
379
|
+
elif isinstance(self.valueCtrl, (paramCtrls.DictCtrl, paramCtrls.FileListCtrl)):
|
|
380
|
+
pass
|
|
381
|
+
else:
|
|
382
|
+
print("setChangesCallback doesn't know how to handle ctrl {}"
|
|
383
|
+
.format(type(self.valueCtrl)))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class StartStopCtrls(wx.GridBagSizer):
|
|
387
|
+
def __init__(self, parent, params):
|
|
388
|
+
wx.GridBagSizer.__init__(self, 0, 0)
|
|
389
|
+
# Make ctrls
|
|
390
|
+
self.ctrls = {}
|
|
391
|
+
for name, param in params.items():
|
|
392
|
+
if name in ['startVal', 'stopVal']:
|
|
393
|
+
self.ctrls[name] = wx.TextCtrl(parent,
|
|
394
|
+
value=str(param.val), size=wx.Size(-1, 24))
|
|
395
|
+
self.label = wx.StaticText(parent, label=param.label)
|
|
396
|
+
self.Add(self.ctrls[name], (0, 1), border=6, flag=wx.EXPAND | wx.TOP)
|
|
397
|
+
if name in ['startType', 'stopType']:
|
|
398
|
+
localizedChoices = list(map(_translate, param.allowedVals))
|
|
399
|
+
self.ctrls[name] = wx.Choice(parent,
|
|
400
|
+
<<<<<<< HEAD
|
|
401
|
+
choices=param.allowedVals or [param.val],
|
|
402
|
+
=======
|
|
403
|
+
choices=localizedChoices,
|
|
404
|
+
>>>>>>> 8531e762b38a68bca6708110a4caa707d9d960ca
|
|
405
|
+
size=wx.Size(96, 24))
|
|
406
|
+
self.ctrls[name]._choices = copy.copy(param.allowedVals)
|
|
407
|
+
self.ctrls[name].SetSelection(param.allowedVals.index(str(param.val)))
|
|
408
|
+
self.Add(self.ctrls[name], (0, 0), border=6, flag=wx.EXPAND | wx.TOP)
|
|
409
|
+
if name in ['startEstim', 'durationEstim']:
|
|
410
|
+
self.ctrls[name] = wx.TextCtrl(parent,
|
|
411
|
+
value=str(param.val), size=wx.Size(-1, 24))
|
|
412
|
+
self.estimLabel = wx.StaticText(parent,
|
|
413
|
+
label=param.label, size=wx.Size(-1, 24))
|
|
414
|
+
self.estimLabel.SetForegroundColour("grey")
|
|
415
|
+
self.Add(self.estimLabel, (1, 0), border=6, flag=wx.EXPAND | wx.ALL)
|
|
416
|
+
self.Add(self.ctrls[name], (1, 1), border=6, flag=wx.EXPAND | wx.TOP | wx.BOTTOM)
|
|
417
|
+
self.AddGrowableCol(1)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class ParamNotebook(wx.Notebook, ThemeMixin):
|
|
421
|
+
class CategoryPage(wx.Panel, ThemeMixin):
|
|
422
|
+
def __init__(self, parent, dlg, params):
|
|
423
|
+
wx.Panel.__init__(self, parent, size=(600, -1))
|
|
424
|
+
self.parent = parent
|
|
425
|
+
self.dlg = dlg
|
|
426
|
+
self.app = self.dlg.app
|
|
427
|
+
# Setup sizer
|
|
428
|
+
self.sizer = wx.GridBagSizer(0, 0)
|
|
429
|
+
self.SetSizer(self.sizer)
|
|
430
|
+
# Add empty row for spacing
|
|
431
|
+
self.sizer.Add(wx.StaticText(self, label=""), (0, 0))
|
|
432
|
+
# Add controls
|
|
433
|
+
self.ctrls = {}
|
|
434
|
+
self.row = 1
|
|
435
|
+
# Sort params
|
|
436
|
+
sortedParams = OrderedDict(params)
|
|
437
|
+
for name in reversed(self.parent.element.order):
|
|
438
|
+
if name in sortedParams:
|
|
439
|
+
sortedParams.move_to_end(name, last=False)
|
|
440
|
+
# Make name ctrl
|
|
441
|
+
if "name" in sortedParams:
|
|
442
|
+
param = sortedParams.pop("name")
|
|
443
|
+
self.addParam("name", param)
|
|
444
|
+
# Make start controls
|
|
445
|
+
startParams = OrderedDict()
|
|
446
|
+
for name in ['startVal', 'startType', 'startEstim']:
|
|
447
|
+
if name in sortedParams:
|
|
448
|
+
startParams[name] = sortedParams.pop(name)
|
|
449
|
+
if startParams:
|
|
450
|
+
self.addStartStopCtrl(startParams)
|
|
451
|
+
# Make stop controls
|
|
452
|
+
stopParams = OrderedDict()
|
|
453
|
+
for name in ['stopVal', 'stopType', 'durationEstim']:
|
|
454
|
+
if name in sortedParams:
|
|
455
|
+
stopParams[name] = sortedParams.pop(name)
|
|
456
|
+
if stopParams:
|
|
457
|
+
self.addStartStopCtrl(stopParams)
|
|
458
|
+
# Make controls
|
|
459
|
+
for name, param in sortedParams.items():
|
|
460
|
+
self.addParam(name, param)
|
|
461
|
+
# Add growable
|
|
462
|
+
self.sizer.AddGrowableCol(1, 1)
|
|
463
|
+
# Check depends
|
|
464
|
+
self.checkDepends()
|
|
465
|
+
|
|
466
|
+
def addParam(self, name, param):
|
|
467
|
+
# Make ctrl
|
|
468
|
+
self.ctrls[name] = ParamCtrls(self.dlg, param.label, param, self, name)
|
|
469
|
+
# Add value ctrl
|
|
470
|
+
_flag = wx.EXPAND | wx.ALL
|
|
471
|
+
if hasattr(self.ctrls[name].valueCtrl, '_szr'):
|
|
472
|
+
self.sizer.Add(self.ctrls[name].valueCtrl._szr, (self.row, 1), border=6, flag=_flag)
|
|
473
|
+
else:
|
|
474
|
+
self.sizer.Add(self.ctrls[name].valueCtrl, (self.row, 1), border=6, flag=_flag)
|
|
475
|
+
# Add other ctrl stuff
|
|
476
|
+
_flag = wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL
|
|
477
|
+
self.sizer.Add(self.ctrls[name].nameCtrl, (self.row, 0), (1, 1), border=5, flag=_flag)
|
|
478
|
+
if self.ctrls[name].typeCtrl:
|
|
479
|
+
self.sizer.Add(self.ctrls[name].typeCtrl, (self.row, 2), border=5, flag=_flag)
|
|
480
|
+
if self.ctrls[name].updateCtrl:
|
|
481
|
+
self.sizer.Add(self.ctrls[name].updateCtrl, (self.row, 3), border=5, flag=_flag)
|
|
482
|
+
# Link to depends callback
|
|
483
|
+
self.ctrls[name].setChangesCallback(self.doValidate)
|
|
484
|
+
if name == 'name':
|
|
485
|
+
self.ctrls[name].valueCtrl.SetFocus()
|
|
486
|
+
# Iterate row
|
|
487
|
+
self.row += 1
|
|
488
|
+
|
|
489
|
+
def addStartStopCtrl(self, params):
|
|
490
|
+
# Make controls
|
|
491
|
+
panel = StartStopCtrls(self, params)
|
|
492
|
+
# Add to dict of ctrls
|
|
493
|
+
self.ctrls.update(panel.ctrls)
|
|
494
|
+
# Add label
|
|
495
|
+
_flag = wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL
|
|
496
|
+
self.sizer.Add(panel.label, (self.row, 0), (1, 1), border=5, flag=_flag)
|
|
497
|
+
# Add ctrls
|
|
498
|
+
_flag = wx.EXPAND | wx.ALL
|
|
499
|
+
self.sizer.Add(panel, (self.row, 1), border=6, flag=_flag)
|
|
500
|
+
# Iterate row
|
|
501
|
+
self.row += 1
|
|
502
|
+
|
|
503
|
+
def checkDepends(self, event=None):
|
|
504
|
+
"""Checks the relationships between params that depend on each other
|
|
505
|
+
|
|
506
|
+
Dependencies are a list of dicts like this (as in BaseComponent):
|
|
507
|
+
{"dependsOn": "shape",
|
|
508
|
+
"condition": "=='n vertices",
|
|
509
|
+
"param": "n vertices",
|
|
510
|
+
"true": "Enable", # what to do with param if condition is True
|
|
511
|
+
"false": "Disable", # permitted: hide, show, enable, disable
|
|
512
|
+
}"""
|
|
513
|
+
for thisDep in self.parent.element.depends:
|
|
514
|
+
if not (
|
|
515
|
+
thisDep['param'] in self.ctrls
|
|
516
|
+
and thisDep['dependsOn'] in self.ctrls):
|
|
517
|
+
# If params are on another page, skip
|
|
518
|
+
continue
|
|
519
|
+
dependentCtrls = self.ctrls[thisDep['param']]
|
|
520
|
+
dependencyCtrls = self.ctrls[thisDep['dependsOn']]
|
|
521
|
+
condString = "dependencyCtrls.getValue() {}".format(thisDep['condition'])
|
|
522
|
+
if eval(condString):
|
|
523
|
+
action = thisDep['true']
|
|
524
|
+
else:
|
|
525
|
+
action = thisDep['false']
|
|
526
|
+
if action == "hide":
|
|
527
|
+
dependentCtrls.setVisible(False)
|
|
528
|
+
elif action == "show":
|
|
529
|
+
dependentCtrls.setVisible(True)
|
|
530
|
+
else:
|
|
531
|
+
# if action is "enable" then do ctrl.Enable() etc
|
|
532
|
+
for ctrlName in ['valueCtrl', 'nameCtrl', 'updatesCtrl']:
|
|
533
|
+
# disable/enable all parts of the control
|
|
534
|
+
if hasattr(dependentCtrls, ctrlName):
|
|
535
|
+
evalStr = ("dependentCtrls.{}.{}()"
|
|
536
|
+
.format(ctrlName, action.title()))
|
|
537
|
+
eval(evalStr)
|
|
538
|
+
# Update sizer
|
|
539
|
+
self.sizer.SetEmptyCellSize((0, 0))
|
|
540
|
+
self.sizer.Layout()
|
|
541
|
+
self.Fit()
|
|
542
|
+
self.dlg.Fit()
|
|
543
|
+
self.Refresh()
|
|
544
|
+
|
|
545
|
+
def doValidate(self, event=None):
|
|
546
|
+
self.Validate()
|
|
547
|
+
self.checkDepends(event=event)
|
|
548
|
+
if hasattr(self.dlg, "updateExperiment"):
|
|
549
|
+
self.dlg.updateExperiment()
|
|
550
|
+
|
|
551
|
+
def _applyAppTheme(self, target=None):
|
|
552
|
+
self.SetBackgroundColour("white")
|
|
553
|
+
|
|
554
|
+
def __init__(self, parent, element, experiment):
|
|
555
|
+
wx.Notebook.__init__(self, parent)
|
|
556
|
+
self.parent = parent
|
|
557
|
+
self.exp = experiment
|
|
558
|
+
self.element = element
|
|
559
|
+
self.params = element.params
|
|
560
|
+
# Setup sizer
|
|
561
|
+
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
|
562
|
+
self.SetSizer(self.sizer)
|
|
563
|
+
# Get arrays of params
|
|
564
|
+
paramsByCateg = OrderedDict()
|
|
565
|
+
for name, param in self.params.items():
|
|
566
|
+
# Add categ if not present
|
|
567
|
+
if param.categ not in paramsByCateg:
|
|
568
|
+
paramsByCateg[param.categ] = OrderedDict()
|
|
569
|
+
# Append param to categ
|
|
570
|
+
paramsByCateg[param.categ][name] = param
|
|
571
|
+
# Move high priority categs to the front
|
|
572
|
+
for categ in reversed(['Basic', 'Layout', 'Appearance', 'Formatting', 'Texture']):
|
|
573
|
+
if categ in paramsByCateg:
|
|
574
|
+
paramsByCateg.move_to_end(categ, last=False)
|
|
575
|
+
# Move low priority categs to the end
|
|
576
|
+
for categ in ['Data', 'Custom', 'Hardware', 'Testing']:
|
|
577
|
+
if categ in paramsByCateg:
|
|
578
|
+
paramsByCateg.move_to_end(categ, last=True)
|
|
579
|
+
# Setup pages
|
|
580
|
+
self.paramCtrls = {}
|
|
581
|
+
for categ, params in paramsByCateg.items():
|
|
582
|
+
page = self.CategoryPage(self, self.parent, params)
|
|
583
|
+
self.paramCtrls.update(page.ctrls)
|
|
584
|
+
# Add page to notebook
|
|
585
|
+
self.AddPage(page, _translate(categ))
|
|
586
|
+
|
|
587
|
+
def checkDepends(self, event=None):
|
|
588
|
+
"""
|
|
589
|
+
When check depends is called on the whole notebook, check each page
|
|
590
|
+
"""
|
|
591
|
+
for i in range(self.GetPageCount()):
|
|
592
|
+
self.GetPage(i).checkDepends(event)
|
|
593
|
+
|
|
594
|
+
def getParams(self):
|
|
595
|
+
"""retrieves data from any fields in self.paramCtrls
|
|
596
|
+
(populated during the __init__ function)
|
|
597
|
+
|
|
598
|
+
The new data from the dlg get inserted back into the original params
|
|
599
|
+
used in __init__ and are also returned from this method.
|
|
600
|
+
|
|
601
|
+
.. note::
|
|
602
|
+
Don't use GetStringSelection() here to avoid that translated value
|
|
603
|
+
is returned. Instead, use GetSelection() to get index of selection
|
|
604
|
+
and get untranslated value from _choices attribute.
|
|
605
|
+
"""
|
|
606
|
+
# get data from input fields
|
|
607
|
+
for fieldName in self.params:
|
|
608
|
+
param = self.params[fieldName]
|
|
609
|
+
# Get control
|
|
610
|
+
ctrl = self.paramCtrls[fieldName]
|
|
611
|
+
# Get value
|
|
612
|
+
if hasattr(ctrl, "getValue"):
|
|
613
|
+
param.val = ctrl.getValue()
|
|
614
|
+
elif isinstance(ctrl, wx.Choice):
|
|
615
|
+
if hasattr(ctrl, "_choices"):
|
|
616
|
+
param.val = ctrl._choices[ctrl.GetSelection()]
|
|
617
|
+
else:
|
|
618
|
+
# use GetStringSelection()
|
|
619
|
+
# only if this control doesn't has _choices
|
|
620
|
+
param.val = ctrl.GetStringSelection()
|
|
621
|
+
elif hasattr(ctrl, "GetValue"):
|
|
622
|
+
param.val = ctrl.GetValue()
|
|
623
|
+
# Get type
|
|
624
|
+
if hasattr(ctrl, "typeCtrl"):
|
|
625
|
+
if ctrl.typeCtrl:
|
|
626
|
+
param.valType = ctrl.typeCtrl._choices[ctrl.typeCtrl.GetSelection()]
|
|
627
|
+
# Get update type
|
|
628
|
+
if hasattr(ctrl, "updateCtrl"):
|
|
629
|
+
if ctrl.updateCtrl:
|
|
630
|
+
updates = ctrl.updateCtrl._choices[ctrl.updateCtrl.GetSelection()]
|
|
631
|
+
# may also need to update a static
|
|
632
|
+
if param.updates != updates:
|
|
633
|
+
self._updateStaticUpdates(fieldName,
|
|
634
|
+
param.updates, updates)
|
|
635
|
+
param.updates = updates
|
|
636
|
+
return self.params
|
|
637
|
+
|
|
638
|
+
def _updateStaticUpdates(self, fieldName, updates, newUpdates):
|
|
639
|
+
"""If the old/new updates ctrl is using a Static component then we
|
|
640
|
+
need to remove/add the component name to the appropriate static
|
|
641
|
+
"""
|
|
642
|
+
exp = self.exp
|
|
643
|
+
compName = self.params['name'].val
|
|
644
|
+
if hasattr(updates, 'startswith') and "during:" in updates:
|
|
645
|
+
# remove the part that says 'during'
|
|
646
|
+
updates = updates.split(': ')[1]
|
|
647
|
+
origRoutine, origStatic = updates.split('.')
|
|
648
|
+
_comp = exp.routines[origRoutine].getComponentFromName(origStatic)
|
|
649
|
+
if _comp is not None:
|
|
650
|
+
_comp.remComponentUpdate(origRoutine, compName, fieldName)
|
|
651
|
+
if hasattr(newUpdates, 'startswith') and "during:" in newUpdates:
|
|
652
|
+
# remove the part that says 'during'
|
|
653
|
+
newUpdates = newUpdates.split(': ')[1]
|
|
654
|
+
newRoutine, newStatic = newUpdates.split('.')
|
|
655
|
+
_comp = exp.routines[newRoutine].getComponentFromName(newStatic)
|
|
656
|
+
_comp.addComponentUpdate(newRoutine, compName, fieldName)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class _BaseParamsDlg(wx.Dialog):
|
|
660
|
+
_style = wx.DEFAULT_DIALOG_STYLE | wx.DIALOG_NO_PARENT | wx.TAB_TRAVERSAL
|
|
661
|
+
|
|
662
|
+
def __init__(self, frame, element, experiment,
|
|
663
|
+
suppressTitles=True,
|
|
664
|
+
showAdvanced=False,
|
|
665
|
+
size=wx.DefaultSize,
|
|
666
|
+
style=_style, editing=False,
|
|
667
|
+
timeout=None):
|
|
668
|
+
|
|
669
|
+
# translate title
|
|
670
|
+
if "name" in element.params:
|
|
671
|
+
title = element.params['name'].val + _translate(' Properties')
|
|
672
|
+
elif "expName" in element.params:
|
|
673
|
+
title = element.params['expName'].val + _translate(' Properties')
|
|
674
|
+
else:
|
|
675
|
+
title = "Properties"
|
|
676
|
+
# get help url
|
|
677
|
+
if hasattr(element, 'url'):
|
|
678
|
+
helpUrl = element.url
|
|
679
|
+
else:
|
|
680
|
+
helpUrl = None
|
|
681
|
+
|
|
682
|
+
# use translated title for display
|
|
683
|
+
wx.Dialog.__init__(self, parent=None, id=-1, title=title,
|
|
684
|
+
size=size, style=style)
|
|
685
|
+
self.frame = frame
|
|
686
|
+
self.app = frame.app
|
|
687
|
+
self.dpi = self.app.dpi
|
|
688
|
+
self.helpUrl = helpUrl
|
|
689
|
+
self.params = params = element.params # dict
|
|
690
|
+
self.title = title
|
|
691
|
+
self.timeout = timeout
|
|
692
|
+
if (not editing and
|
|
693
|
+
title != 'Experiment Settings' and
|
|
694
|
+
'name' in self.params):
|
|
695
|
+
# then we're adding a new component, so provide known-valid name:
|
|
696
|
+
makeValid = self.frame.exp.namespace.makeValid
|
|
697
|
+
self.params['name'].val = makeValid(params['name'].val)
|
|
698
|
+
self.paramCtrls = {}
|
|
699
|
+
CodeSnippetValidator.clsWarnings = {}
|
|
700
|
+
self.suppressTitles = suppressTitles
|
|
701
|
+
self.showAdvanced = showAdvanced
|
|
702
|
+
self.order = element.order
|
|
703
|
+
self.depends = element.depends
|
|
704
|
+
self.data = []
|
|
705
|
+
# max( len(str(self.params[x])) for x in keys )
|
|
706
|
+
self.maxFieldLength = 10
|
|
707
|
+
self.timeParams = ['startType', 'startVal', 'stopType', 'stopVal']
|
|
708
|
+
self.codeFieldNameFromID = {}
|
|
709
|
+
self.codeIDFromFieldName = {}
|
|
710
|
+
# a list of all panels in the ctrl to be traversed by validator
|
|
711
|
+
self.panels = []
|
|
712
|
+
# need font size for STCs:
|
|
713
|
+
if wx.Platform == '__WXMSW__':
|
|
714
|
+
self.faceSize = 10
|
|
715
|
+
elif wx.Platform == '__WXMAC__':
|
|
716
|
+
self.faceSize = 14
|
|
717
|
+
else:
|
|
718
|
+
self.faceSize = 12
|
|
719
|
+
|
|
720
|
+
# create main sizer
|
|
721
|
+
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
|
|
722
|
+
|
|
723
|
+
self.ctrls = ParamNotebook(self, element, experiment)
|
|
724
|
+
self.paramCtrls = self.ctrls.paramCtrls
|
|
725
|
+
|
|
726
|
+
self.mainSizer.Add(self.ctrls, # ctrls is the notebook of params
|
|
727
|
+
proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
|
|
728
|
+
|
|
729
|
+
self.SetSizerAndFit(self.mainSizer)
|
|
730
|
+
|
|
731
|
+
def getParams(self):
|
|
732
|
+
return self.ctrls.getParams()
|
|
733
|
+
|
|
734
|
+
def openMonitorCenter(self, event):
|
|
735
|
+
self.app.openMonitorCenter(event)
|
|
736
|
+
self.paramCtrls['Monitor'].valueCtrl.SetFocus()
|
|
737
|
+
# need to delay until the user closes the monitor center
|
|
738
|
+
# self.paramCtrls['Monitor'].valueCtrl.Clear()
|
|
739
|
+
# if wx.TheClipboard.Open():
|
|
740
|
+
# dataObject = wx.TextDataObject()
|
|
741
|
+
# if wx.TheClipboard.GetData(dataObject):
|
|
742
|
+
# self.paramCtrls['Monitor'].valueCtrl.
|
|
743
|
+
# WriteText(dataObject.GetText())
|
|
744
|
+
# wx.TheClipboard.Close()
|
|
745
|
+
|
|
746
|
+
def launchColorPicker(self, event):
|
|
747
|
+
# bring up a colorPicker
|
|
748
|
+
dlg = PsychoColorPicker(self.frame)
|
|
749
|
+
dlg.ShowModal()
|
|
750
|
+
dlg.Destroy()
|
|
751
|
+
|
|
752
|
+
def onNewTextSize(self, event):
|
|
753
|
+
self.Fit() # for ExpandoTextCtrl this is needed
|
|
754
|
+
|
|
755
|
+
def show(self, testing=False):
|
|
756
|
+
"""Adds an OK and cancel button, shows dialogue.
|
|
757
|
+
|
|
758
|
+
This method returns wx.ID_OK (as from ShowModal), but also
|
|
759
|
+
sets self.OK to be True or False
|
|
760
|
+
"""
|
|
761
|
+
# add buttons for OK and Cancel
|
|
762
|
+
buttons = wx.BoxSizer(wx.HORIZONTAL)
|
|
763
|
+
# help button if we know the url
|
|
764
|
+
if self.helpUrl != None:
|
|
765
|
+
helpBtn = wx.Button(self, wx.ID_HELP, _translate(" Help "))
|
|
766
|
+
_tip = _translate("Go to online help about this component")
|
|
767
|
+
helpBtn.SetToolTip(wx.ToolTip(_tip))
|
|
768
|
+
helpBtn.Bind(wx.EVT_BUTTON, self.onHelp)
|
|
769
|
+
buttons.Add(helpBtn, 0,
|
|
770
|
+
flag=wx.LEFT | wx.ALL | wx.ALIGN_CENTER_VERTICAL,
|
|
771
|
+
border=3)
|
|
772
|
+
self.OKbtn = wx.Button(self, wx.ID_OK, _translate(" OK "))
|
|
773
|
+
# intercept OK button if a loop dialog, in case file name was edited:
|
|
774
|
+
if type(self) == DlgLoopProperties:
|
|
775
|
+
self.OKbtn.Bind(wx.EVT_BUTTON, self.onOK)
|
|
776
|
+
self.OKbtn.SetDefault()
|
|
777
|
+
CANCEL = wx.Button(self, wx.ID_CANCEL, _translate(" Cancel "))
|
|
778
|
+
|
|
779
|
+
# Add validator stuff
|
|
780
|
+
self.warnings = WarningManager(self)
|
|
781
|
+
self.mainSizer.Add(self.warnings.output, border=3, flag=wx.EXPAND | wx.ALL)
|
|
782
|
+
self.Validate() # disables OKbtn if bad name, syntax error, etc
|
|
783
|
+
|
|
784
|
+
buttons.AddStretchSpacer()
|
|
785
|
+
|
|
786
|
+
# Add Okay and Cancel buttons
|
|
787
|
+
if sys.platform == "win32":
|
|
788
|
+
btns = [self.OKbtn, CANCEL]
|
|
789
|
+
else:
|
|
790
|
+
btns = [CANCEL, self.OKbtn]
|
|
791
|
+
buttons.Add(btns[0], 0,
|
|
792
|
+
wx.ALL | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,
|
|
793
|
+
border=3)
|
|
794
|
+
buttons.Add(btns[1], 0,
|
|
795
|
+
wx.ALL | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,
|
|
796
|
+
border=3)
|
|
797
|
+
|
|
798
|
+
# buttons.Realize()
|
|
799
|
+
# add to sizer
|
|
800
|
+
self.mainSizer.Add(buttons, flag=wx.ALL | wx.EXPAND, border=3)
|
|
801
|
+
self.SetSizerAndFit(self.mainSizer)
|
|
802
|
+
self.mainSizer.Layout()
|
|
803
|
+
# move the position to be v near the top of screen and
|
|
804
|
+
# to the right of the left-most edge of builder
|
|
805
|
+
builderPos = self.frame.GetPosition()
|
|
806
|
+
self.SetPosition((builderPos[0] + 200, 20))
|
|
807
|
+
|
|
808
|
+
# self.paramCtrls['name'].valueCtrl.SetFocus()
|
|
809
|
+
# do show and process return
|
|
810
|
+
if self.timeout is not None:
|
|
811
|
+
timeout = wx.CallLater(self.timeout, self.autoTerminate)
|
|
812
|
+
timeout.Start()
|
|
813
|
+
if testing:
|
|
814
|
+
self.Show()
|
|
815
|
+
else:
|
|
816
|
+
retVal = self.ShowModal()
|
|
817
|
+
self.OK = bool(retVal == wx.ID_OK)
|
|
818
|
+
return wx.ID_OK
|
|
819
|
+
|
|
820
|
+
def autoTerminate(self, event=None, retval=1):
|
|
821
|
+
"""Terminates the dialog early, for use with a timeout
|
|
822
|
+
|
|
823
|
+
:param event: an optional wx.EVT
|
|
824
|
+
:param retval: an optional int to pass to EndModal()
|
|
825
|
+
:return:
|
|
826
|
+
"""
|
|
827
|
+
self.EndModal(retval)
|
|
828
|
+
|
|
829
|
+
def Validate(self, *args, **kwargs):
|
|
830
|
+
"""Validate form data and disable OK button if validation fails.
|
|
831
|
+
"""
|
|
832
|
+
return self.ctrls.Validate()
|
|
833
|
+
|
|
834
|
+
def onOK(self, event=None):
|
|
835
|
+
"""Handler for OK button which should validate dialog contents.
|
|
836
|
+
"""
|
|
837
|
+
valid = self.Validate()
|
|
838
|
+
if not valid:
|
|
839
|
+
return
|
|
840
|
+
event.Skip()
|
|
841
|
+
|
|
842
|
+
def onTextEventCode(self, event=None):
|
|
843
|
+
"""process text events for code components: change color to grey
|
|
844
|
+
"""
|
|
845
|
+
codeBox = event.GetEventObject()
|
|
846
|
+
textBeforeThisKey = codeBox.GetText()
|
|
847
|
+
keyCode = event.GetKeyCode()
|
|
848
|
+
pos = event.GetPosition()
|
|
849
|
+
# ord(10)='\n', ord(13)='\l'
|
|
850
|
+
if keyCode < 256 and keyCode not in [10, 13]:
|
|
851
|
+
# new line is trigger to check syntax
|
|
852
|
+
codeBox.setStatus('changed')
|
|
853
|
+
elif (keyCode in (10, 13) and
|
|
854
|
+
len(textBeforeThisKey) and
|
|
855
|
+
textBeforeThisKey[-1] != ':'):
|
|
856
|
+
# ... but skip the check if end of line is colon ord(58)=':'
|
|
857
|
+
self._setNameColor(self._testCompile(codeBox))
|
|
858
|
+
event.Skip()
|
|
859
|
+
|
|
860
|
+
def _testCompile(self, ctrl, mode='exec'):
|
|
861
|
+
"""checks whether code.val is legal python syntax,
|
|
862
|
+
returns error status
|
|
863
|
+
|
|
864
|
+
mode = 'exec' (statement or expr) or 'eval' (expr only)
|
|
865
|
+
"""
|
|
866
|
+
if hasattr(ctrl, 'GetText'):
|
|
867
|
+
val = ctrl.GetText()
|
|
868
|
+
elif hasattr(ctrl, 'GetValue'):
|
|
869
|
+
# e.g. TextCtrl
|
|
870
|
+
val = ctrl.GetValue()
|
|
871
|
+
else:
|
|
872
|
+
msg = 'Unknown type of ctrl in _testCompile: %s'
|
|
873
|
+
raise ValueError(msg % (type(ctrl)))
|
|
874
|
+
try:
|
|
875
|
+
compile(val, '', mode)
|
|
876
|
+
syntaxOk = True
|
|
877
|
+
ctrl.setStatus('OK')
|
|
878
|
+
except SyntaxError:
|
|
879
|
+
ctrl.setStatus('error')
|
|
880
|
+
syntaxOk = False
|
|
881
|
+
return syntaxOk
|
|
882
|
+
|
|
883
|
+
def checkCodeSyntax(self, event=None):
|
|
884
|
+
"""Checks syntax for whole code component by code box,
|
|
885
|
+
sets box bg-color.
|
|
886
|
+
"""
|
|
887
|
+
if hasattr(event, 'GetEventObject'):
|
|
888
|
+
codeBox = event.GetEventObject()
|
|
889
|
+
elif hasattr(event, 'GetText'):
|
|
890
|
+
# we were given the control itself, not an event
|
|
891
|
+
codeBox = event
|
|
892
|
+
else:
|
|
893
|
+
msg = ('checkCodeSyntax received unexpected event object (%s). '
|
|
894
|
+
'Should be a wx.Event or a CodeBox')
|
|
895
|
+
raise ValueError(msg % type(event))
|
|
896
|
+
text = codeBox.GetText()
|
|
897
|
+
if not text.strip():
|
|
898
|
+
# if basically empty
|
|
899
|
+
codeBox.SetBackgroundColour(white)
|
|
900
|
+
return
|
|
901
|
+
# test syntax:
|
|
902
|
+
goodSyntax = self._testCompile(codeBox)
|
|
903
|
+
# not quite every dialog has a name (e.g. settings)
|
|
904
|
+
# but if so then set its color
|
|
905
|
+
if 'name' in self.paramCtrls:
|
|
906
|
+
self._setNameColor(goodSyntax)
|
|
907
|
+
|
|
908
|
+
def _setNameColor(self, goodSyntax):
|
|
909
|
+
if goodSyntax:
|
|
910
|
+
_nValCtrl = self.paramCtrls['name'].valueCtrl
|
|
911
|
+
_nValCtrl.SetBackgroundColour(codeSyntaxOkay)
|
|
912
|
+
self.nameOKlabel.SetLabel('')
|
|
913
|
+
else:
|
|
914
|
+
self.paramCtrls['name'].valueCtrl.SetBackgroundColour(white)
|
|
915
|
+
self.nameOKlabel.SetLabel('syntax error')
|
|
916
|
+
|
|
917
|
+
def checkCodeWanted(self, event=None):
|
|
918
|
+
"""check whether a $ is present (if so, set the display font)
|
|
919
|
+
"""
|
|
920
|
+
if hasattr(event, 'GetEventObject'):
|
|
921
|
+
strBox = event.GetEventObject()
|
|
922
|
+
elif hasattr(event, 'GetValue'):
|
|
923
|
+
# we were given the control itself, not an event
|
|
924
|
+
strBox = event
|
|
925
|
+
else:
|
|
926
|
+
raise ValueError('checkCodeWanted received unexpected event'
|
|
927
|
+
' object (%s).')
|
|
928
|
+
try:
|
|
929
|
+
val = strBox.GetValue()
|
|
930
|
+
stc = False
|
|
931
|
+
except Exception:
|
|
932
|
+
if not hasattr(strBox, 'GetText'):
|
|
933
|
+
# eg, wx.Choice control
|
|
934
|
+
if hasattr(event, 'Skip'):
|
|
935
|
+
event.Skip()
|
|
936
|
+
return
|
|
937
|
+
val = strBox.GetText()
|
|
938
|
+
# might be StyledTextCtrl
|
|
939
|
+
stc = True
|
|
940
|
+
|
|
941
|
+
if hasattr(event, 'Skip'):
|
|
942
|
+
event.Skip()
|
|
943
|
+
|
|
944
|
+
def _checkName(self, event=None, name=None):
|
|
945
|
+
"""checks namespace, return error-msg (str), enable (bool)
|
|
946
|
+
"""
|
|
947
|
+
if event:
|
|
948
|
+
newName = event.GetString()
|
|
949
|
+
elif name:
|
|
950
|
+
newName = name
|
|
951
|
+
elif hasattr(self, 'paramCtrls'):
|
|
952
|
+
newName = self.paramCtrls['name'].getValue()
|
|
953
|
+
elif hasattr(self, 'globalCtrls'):
|
|
954
|
+
newName = self.globalCtrls['name'].getValue()
|
|
955
|
+
if newName == '':
|
|
956
|
+
return _translate("Missing name"), False
|
|
957
|
+
else:
|
|
958
|
+
namespace = self.frame.exp.namespace
|
|
959
|
+
used = namespace.exists(newName)
|
|
960
|
+
sameOldName = bool(newName == self.params['name'].val)
|
|
961
|
+
if used and not sameOldName:
|
|
962
|
+
msg = _translate(
|
|
963
|
+
"That name is in use (it's a %s). Try another name.")
|
|
964
|
+
return msg % _translate(used), False
|
|
965
|
+
elif not namespace.isValid(newName): # valid as a var name
|
|
966
|
+
msg = _translate("Name must be alpha-numeric or _, no spaces")
|
|
967
|
+
return msg, False
|
|
968
|
+
# warn but allow, chances are good that its actually ok
|
|
969
|
+
elif namespace.isPossiblyDerivable(newName):
|
|
970
|
+
msg = namespace.isPossiblyDerivable(newName)
|
|
971
|
+
return msg, True
|
|
972
|
+
else:
|
|
973
|
+
return "", True
|
|
974
|
+
|
|
975
|
+
def onHelp(self, event=None):
|
|
976
|
+
"""Uses self.app.followLink() to self.helpUrl
|
|
977
|
+
"""
|
|
978
|
+
self.app.followLink(url=self.helpUrl)
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
class DlgLoopProperties(_BaseParamsDlg):
|
|
982
|
+
_style = wx.DEFAULT_DIALOG_STYLE | wx.DIALOG_NO_PARENT | wx.RESIZE_BORDER
|
|
983
|
+
|
|
984
|
+
def __init__(self, frame, title="Loop Properties", loop=None,
|
|
985
|
+
helpUrl=None, pos=wx.DefaultPosition, size=wx.DefaultSize,
|
|
986
|
+
style=_style, depends=[], timeout=None):
|
|
987
|
+
# translate title
|
|
988
|
+
localizedTitle = title.replace(' Properties',
|
|
989
|
+
_translate(' Properties'))
|
|
990
|
+
|
|
991
|
+
wx.Dialog.__init__(self, None, wx.ID_ANY, localizedTitle,
|
|
992
|
+
pos, size, style)
|
|
993
|
+
self.helpUrl = helpUrl
|
|
994
|
+
self.frame = frame
|
|
995
|
+
self.exp = frame.exp
|
|
996
|
+
self.app = frame.app
|
|
997
|
+
self.dpi = self.app.dpi
|
|
998
|
+
self.params = {}
|
|
999
|
+
self.timeout = timeout
|
|
1000
|
+
self.panel = wx.Panel(self, -1)
|
|
1001
|
+
self.globalCtrls = {}
|
|
1002
|
+
self.constantsCtrls = {}
|
|
1003
|
+
self.staircaseCtrls = {}
|
|
1004
|
+
self.multiStairCtrls = {}
|
|
1005
|
+
self.currentCtrls = {}
|
|
1006
|
+
self.data = []
|
|
1007
|
+
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
|
|
1008
|
+
self.conditions = None
|
|
1009
|
+
self.conditionsFile = None
|
|
1010
|
+
# create a valid new name; save old name in case we need to revert
|
|
1011
|
+
namespace = frame.exp.namespace
|
|
1012
|
+
defaultName = namespace.makeValid('trials')
|
|
1013
|
+
oldLoopName = defaultName
|
|
1014
|
+
if loop:
|
|
1015
|
+
oldLoopName = loop.params['name'].val
|
|
1016
|
+
|
|
1017
|
+
# create default instances of the diff loop types
|
|
1018
|
+
# for 'random','sequential', 'fullRandom'
|
|
1019
|
+
self.trialHandler = experiment.loops.TrialHandler(
|
|
1020
|
+
exp=self.exp, name=oldLoopName, loopType='random',
|
|
1021
|
+
nReps=5, conditions=[])
|
|
1022
|
+
# for staircases:
|
|
1023
|
+
self.stairHandler = experiment.loops.StairHandler(
|
|
1024
|
+
exp=self.exp, name=oldLoopName, nReps=50, nReversals='',
|
|
1025
|
+
stepSizes='[0.8,0.8,0.4,0.4,0.2]', stepType='log', startVal=0.5)
|
|
1026
|
+
self.multiStairHandler = experiment.loops.MultiStairHandler(
|
|
1027
|
+
exp=self.exp, name=oldLoopName, nReps=50, stairType='simple',
|
|
1028
|
+
switchStairs='random', conditions=[], conditionsFile='')
|
|
1029
|
+
|
|
1030
|
+
# replace defaults with the loop we were given
|
|
1031
|
+
if loop is None:
|
|
1032
|
+
self.currentType = 'random'
|
|
1033
|
+
self.currentHandler = self.trialHandler
|
|
1034
|
+
elif loop.type == 'TrialHandler':
|
|
1035
|
+
self.conditions = loop.params['conditions'].val
|
|
1036
|
+
self.conditionsFile = loop.params['conditionsFile'].val
|
|
1037
|
+
self.trialHandler = self.currentHandler = loop
|
|
1038
|
+
# could be 'random', 'sequential', 'fullRandom'
|
|
1039
|
+
self.currentType = loop.params['loopType'].val
|
|
1040
|
+
elif loop.type == 'StairHandler':
|
|
1041
|
+
self.stairHandler = self.currentHandler = loop
|
|
1042
|
+
self.currentType = 'staircase'
|
|
1043
|
+
elif loop.type == 'MultiStairHandler':
|
|
1044
|
+
self.conditions = loop.params['conditions'].val
|
|
1045
|
+
self.conditionsFile = loop.params['conditionsFile'].val
|
|
1046
|
+
self.multiStairHandler = self.currentHandler = loop
|
|
1047
|
+
self.currentType = 'interleaved staircases'
|
|
1048
|
+
elif loop.type == 'QuestHandler':
|
|
1049
|
+
pass # what to do for quest?
|
|
1050
|
+
self.params['name'] = self.currentHandler.params['name']
|
|
1051
|
+
self.globalPanel = self.makeGlobalCtrls()
|
|
1052
|
+
self.stairPanel = self.makeStaircaseCtrls()
|
|
1053
|
+
# the controls for Method of Constants
|
|
1054
|
+
self.constantsPanel = self.makeConstantsCtrls()
|
|
1055
|
+
self.multiStairPanel = self.makeMultiStairCtrls()
|
|
1056
|
+
self.mainSizer.Add(self.globalPanel, border=12,
|
|
1057
|
+
flag=wx.ALL | wx.EXPAND)
|
|
1058
|
+
self.mainSizer.Add(wx.StaticLine(self), border=6,
|
|
1059
|
+
flag=wx.ALL | wx.EXPAND)
|
|
1060
|
+
self.mainSizer.Add(self.stairPanel, border=12,
|
|
1061
|
+
flag=wx.ALL | wx.EXPAND)
|
|
1062
|
+
self.mainSizer.Add(self.constantsPanel, border=12,
|
|
1063
|
+
flag=wx.ALL | wx.EXPAND)
|
|
1064
|
+
self.mainSizer.Add(self.multiStairPanel, border=12,
|
|
1065
|
+
flag=wx.ALL | wx.EXPAND)
|
|
1066
|
+
self.setCtrls(self.currentType)
|
|
1067
|
+
# create a list of panels in the dialog, for the validator to step
|
|
1068
|
+
# through
|
|
1069
|
+
self.panels = [self.globalPanel, self.stairPanel,
|
|
1070
|
+
self.constantsPanel, self.multiStairPanel]
|
|
1071
|
+
|
|
1072
|
+
self.params = {}
|
|
1073
|
+
self.params.update(self.trialHandler.params)
|
|
1074
|
+
self.params.update(self.stairHandler.params)
|
|
1075
|
+
self.params.update(self.multiStairHandler.params)
|
|
1076
|
+
self.paramCtrls = {}
|
|
1077
|
+
self.paramCtrls.update(self.globalCtrls)
|
|
1078
|
+
self.paramCtrls.update(self.constantsCtrls)
|
|
1079
|
+
self.paramCtrls.update(self.staircaseCtrls)
|
|
1080
|
+
self.paramCtrls.update(self.multiStairCtrls)
|
|
1081
|
+
|
|
1082
|
+
# show dialog and get most of the data
|
|
1083
|
+
self.show()
|
|
1084
|
+
if self.OK:
|
|
1085
|
+
self.params = self.getParams()
|
|
1086
|
+
# convert endPoints from str to list
|
|
1087
|
+
_endP = self.params['endPoints'].val
|
|
1088
|
+
self.params['endPoints'].val = eval("%s" % _endP)
|
|
1089
|
+
# then sort the list so the endpoints are in correct order
|
|
1090
|
+
self.params['endPoints'].val.sort()
|
|
1091
|
+
if loop:
|
|
1092
|
+
# editing an existing loop
|
|
1093
|
+
namespace.remove(oldLoopName)
|
|
1094
|
+
namespace.add(self.params['name'].val)
|
|
1095
|
+
# don't always have a conditionsFile
|
|
1096
|
+
if hasattr(self, 'condNamesInFile'):
|
|
1097
|
+
namespace.add(self.condNamesInFile)
|
|
1098
|
+
if hasattr(self, 'duplCondNames'):
|
|
1099
|
+
namespace.remove(self.duplCondNames)
|
|
1100
|
+
else:
|
|
1101
|
+
if loop is not None:
|
|
1102
|
+
# if we had a loop during init then revert to its old name
|
|
1103
|
+
loop.params['name'].val = oldLoopName
|
|
1104
|
+
|
|
1105
|
+
# make sure we set this back regardless of whether OK
|
|
1106
|
+
# otherwise it will be left as a summary string, not a conditions
|
|
1107
|
+
if 'conditionsFile' in self.currentHandler.params:
|
|
1108
|
+
self.currentHandler.params['conditions'].val = self.conditions
|
|
1109
|
+
|
|
1110
|
+
def Validate(self, *args, **kwargs):
|
|
1111
|
+
for ctrl in self.globalCtrls.values():
|
|
1112
|
+
checker = ctrl.valueCtrl.GetValidator()
|
|
1113
|
+
if checker:
|
|
1114
|
+
checker.Validate(self)
|
|
1115
|
+
return self.warnings.OK
|
|
1116
|
+
|
|
1117
|
+
def makeGlobalCtrls(self):
|
|
1118
|
+
panel = wx.Panel(parent=self)
|
|
1119
|
+
panelSizer = wx.GridBagSizer(0, 0)
|
|
1120
|
+
panel.SetSizer(panelSizer)
|
|
1121
|
+
row = 0
|
|
1122
|
+
for fieldName in ('name', 'loopType', 'isTrials'):
|
|
1123
|
+
try:
|
|
1124
|
+
label = self.currentHandler.params[fieldName].label
|
|
1125
|
+
except Exception:
|
|
1126
|
+
label = fieldName
|
|
1127
|
+
self.globalCtrls[fieldName] = ctrls = ParamCtrls(
|
|
1128
|
+
dlg=self, parent=panel, label=label, fieldName=fieldName,
|
|
1129
|
+
param=self.currentHandler.params[fieldName])
|
|
1130
|
+
panelSizer.Add(ctrls.nameCtrl, [row, 0], border=3,
|
|
1131
|
+
flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
|
|
1132
|
+
if hasattr(ctrls.valueCtrl, '_szr'):
|
|
1133
|
+
panelSizer.Add(ctrls.valueCtrl._szr, [row, 1], border=3,
|
|
1134
|
+
flag=wx.EXPAND | wx.ALL)
|
|
1135
|
+
else:
|
|
1136
|
+
panelSizer.Add(ctrls.valueCtrl, [row, 1], border=3,
|
|
1137
|
+
flag=wx.EXPAND | wx.ALL)
|
|
1138
|
+
row += 1
|
|
1139
|
+
panelSizer.AddGrowableCol(1, 1)
|
|
1140
|
+
self.globalCtrls['name'].valueCtrl.Bind(wx.EVT_TEXT, self.Validate)
|
|
1141
|
+
self.Bind(wx.EVT_CHOICE, self.onTypeChanged,
|
|
1142
|
+
self.globalCtrls['loopType'].valueCtrl)
|
|
1143
|
+
return panel
|
|
1144
|
+
|
|
1145
|
+
def makeConstantsCtrls(self):
|
|
1146
|
+
# a list of controls for the random/sequential versions
|
|
1147
|
+
# that can be hidden or shown
|
|
1148
|
+
handler = self.trialHandler
|
|
1149
|
+
# loop through the params
|
|
1150
|
+
keys = list(handler.params.keys())
|
|
1151
|
+
panel = wx.Panel(parent=self)
|
|
1152
|
+
panel.app=self.app
|
|
1153
|
+
panelSizer = wx.GridBagSizer(0, 0)
|
|
1154
|
+
panel.SetSizer(panelSizer)
|
|
1155
|
+
row = 0
|
|
1156
|
+
# add conditions stuff to the *end*
|
|
1157
|
+
if 'conditionsFile' in keys:
|
|
1158
|
+
keys.remove('conditionsFile')
|
|
1159
|
+
keys.append('conditionsFile')
|
|
1160
|
+
if 'conditions' in keys:
|
|
1161
|
+
keys.remove('conditions')
|
|
1162
|
+
keys.append('conditions')
|
|
1163
|
+
_flag = wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
|
|
1164
|
+
# then step through them
|
|
1165
|
+
for fieldName in keys:
|
|
1166
|
+
# try and get alternative "label" for the parameter
|
|
1167
|
+
try:
|
|
1168
|
+
label = self.currentHandler.params[fieldName].label
|
|
1169
|
+
if not label:
|
|
1170
|
+
# it might exist but be empty
|
|
1171
|
+
label = fieldName
|
|
1172
|
+
except Exception:
|
|
1173
|
+
label = fieldName
|
|
1174
|
+
# handle special cases
|
|
1175
|
+
if fieldName == 'endPoints':
|
|
1176
|
+
continue # this was deprecated in v1.62.00
|
|
1177
|
+
if fieldName in self.globalCtrls:
|
|
1178
|
+
# these have already been made and inserted into sizer
|
|
1179
|
+
ctrls = self.globalCtrls[fieldName]
|
|
1180
|
+
elif fieldName == 'conditions':
|
|
1181
|
+
if 'conditions' in handler.params:
|
|
1182
|
+
_cond = handler.params['conditions'].val
|
|
1183
|
+
text, OK = self.getTrialsSummary(_cond)
|
|
1184
|
+
else:
|
|
1185
|
+
text = _translate("No parameters set")
|
|
1186
|
+
OK = True # No condition file is not an error
|
|
1187
|
+
# we'll create our own widgets
|
|
1188
|
+
ctrls = ParamCtrls(dlg=self, parent=panel, label=label,
|
|
1189
|
+
fieldName=fieldName,
|
|
1190
|
+
param=text, noCtrls=True)
|
|
1191
|
+
ctrls.valueCtrl = wx.StaticText(
|
|
1192
|
+
panel, label=text, style=wx.ALIGN_RIGHT)
|
|
1193
|
+
if OK:
|
|
1194
|
+
ctrls.valueCtrl.SetForegroundColour("Black")
|
|
1195
|
+
else:
|
|
1196
|
+
ctrls.valueCtrl.SetForegroundColour("Red")
|
|
1197
|
+
if hasattr(ctrls.valueCtrl, "_szr"):
|
|
1198
|
+
panelSizer.Add(ctrls.valueCtrl._szr, (row, 1),
|
|
1199
|
+
flag=wx.ALIGN_RIGHT)
|
|
1200
|
+
else:
|
|
1201
|
+
panelSizer.Add(ctrls.valueCtrl, (row, 1),
|
|
1202
|
+
flag=wx.ALIGN_RIGHT)
|
|
1203
|
+
row += 1
|
|
1204
|
+
else: # normal text entry field
|
|
1205
|
+
ctrls = ParamCtrls(dlg=self, parent=panel, label=label,
|
|
1206
|
+
fieldName=fieldName,
|
|
1207
|
+
param=handler.params[fieldName])
|
|
1208
|
+
panelSizer.Add(ctrls.nameCtrl, [row, 0], border=3, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
|
|
1209
|
+
if hasattr(ctrls.valueCtrl, "_szr"):
|
|
1210
|
+
panelSizer.Add(ctrls.valueCtrl._szr, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1211
|
+
else:
|
|
1212
|
+
panelSizer.Add(ctrls.valueCtrl, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1213
|
+
row += 1
|
|
1214
|
+
# Link conditions file browse button to its own special method
|
|
1215
|
+
if fieldName == 'conditionsFile':
|
|
1216
|
+
ctrls.valueCtrl.findBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
|
|
1217
|
+
# store info about the field
|
|
1218
|
+
self.constantsCtrls[fieldName] = ctrls
|
|
1219
|
+
panelSizer.AddGrowableCol(1, 1)
|
|
1220
|
+
return panel
|
|
1221
|
+
|
|
1222
|
+
def makeMultiStairCtrls(self):
|
|
1223
|
+
# a list of controls for the random/sequential versions
|
|
1224
|
+
panel = wx.Panel(parent=self)
|
|
1225
|
+
panel.app = self.app
|
|
1226
|
+
panelSizer = wx.GridBagSizer(0, 0)
|
|
1227
|
+
panel.SetSizer(panelSizer)
|
|
1228
|
+
row = 0
|
|
1229
|
+
# that can be hidden or shown
|
|
1230
|
+
handler = self.multiStairHandler
|
|
1231
|
+
# loop through the params
|
|
1232
|
+
keys = list(handler.params.keys())
|
|
1233
|
+
# add conditions stuff to the *end*
|
|
1234
|
+
# add conditions stuff to the *end*
|
|
1235
|
+
if 'conditionsFile' in keys:
|
|
1236
|
+
keys.remove('conditionsFile')
|
|
1237
|
+
keys.append('conditionsFile')
|
|
1238
|
+
if 'conditions' in keys:
|
|
1239
|
+
keys.remove('conditions')
|
|
1240
|
+
keys.append('conditions')
|
|
1241
|
+
# then step through them
|
|
1242
|
+
for fieldName in keys:
|
|
1243
|
+
# try and get alternative "label" for the parameter
|
|
1244
|
+
try:
|
|
1245
|
+
label = handler.params[fieldName].label
|
|
1246
|
+
if not label: # it might exist but be empty
|
|
1247
|
+
label = fieldName
|
|
1248
|
+
except Exception:
|
|
1249
|
+
label = fieldName
|
|
1250
|
+
# handle special cases
|
|
1251
|
+
if fieldName == 'endPoints':
|
|
1252
|
+
continue # this was deprecated in v1.62.00
|
|
1253
|
+
if fieldName in self.globalCtrls:
|
|
1254
|
+
# these have already been made and inserted into sizer
|
|
1255
|
+
ctrls = self.globalCtrls[fieldName]
|
|
1256
|
+
elif fieldName == 'conditions':
|
|
1257
|
+
if 'conditions' in handler.params:
|
|
1258
|
+
text, OK = self.getTrialsSummary(
|
|
1259
|
+
handler.params['conditions'].val)
|
|
1260
|
+
else:
|
|
1261
|
+
text = _translate(
|
|
1262
|
+
"No parameters set (select a file above)")
|
|
1263
|
+
OK = False
|
|
1264
|
+
# we'll create our own widgets
|
|
1265
|
+
ctrls = ParamCtrls(dlg=self, parent=panel, label=label,
|
|
1266
|
+
fieldName=fieldName,
|
|
1267
|
+
param=text, noCtrls=True)
|
|
1268
|
+
ctrls.valueCtrl = wx.StaticText(panel, label=text,
|
|
1269
|
+
style=wx.ALIGN_CENTER)
|
|
1270
|
+
if OK:
|
|
1271
|
+
ctrls.valueCtrl.SetForegroundColour("Black")
|
|
1272
|
+
else:
|
|
1273
|
+
ctrls.valueCtrl.SetForegroundColour("Red")
|
|
1274
|
+
if hasattr(ctrls.valueCtrl, "_szr"):
|
|
1275
|
+
panelSizer.Add(ctrls.valueCtrl._szr, (row, 0),
|
|
1276
|
+
span=(1, 3), flag=wx.ALIGN_CENTER)
|
|
1277
|
+
else:
|
|
1278
|
+
panelSizer.Add(ctrls.valueCtrl, (row, 0),
|
|
1279
|
+
span=(1, 3), flag=wx.ALIGN_CENTER)
|
|
1280
|
+
row += 1
|
|
1281
|
+
else:
|
|
1282
|
+
# normal text entry field
|
|
1283
|
+
ctrls = ParamCtrls(dlg=self, parent=panel, label=label,
|
|
1284
|
+
fieldName=fieldName,
|
|
1285
|
+
param=handler.params[fieldName])
|
|
1286
|
+
panelSizer.Add(ctrls.nameCtrl, [row, 0], border=3, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
|
|
1287
|
+
if hasattr(ctrls.valueCtrl, "_szr"):
|
|
1288
|
+
panelSizer.Add(ctrls.valueCtrl._szr, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1289
|
+
else:
|
|
1290
|
+
panelSizer.Add(ctrls.valueCtrl, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1291
|
+
row += 1
|
|
1292
|
+
# Bind file button with its own special method
|
|
1293
|
+
if fieldName == 'conditionsFile':
|
|
1294
|
+
ctrls.valueCtrl.findBtn.Bind(wx.EVT_BUTTON, self.onBrowseTrialsFile)
|
|
1295
|
+
# store info about the field
|
|
1296
|
+
self.multiStairCtrls[fieldName] = ctrls
|
|
1297
|
+
panelSizer.AddGrowableCol(1, 1)
|
|
1298
|
+
return panel
|
|
1299
|
+
|
|
1300
|
+
def makeStaircaseCtrls(self):
|
|
1301
|
+
"""Setup the controls for a StairHandler
|
|
1302
|
+
"""
|
|
1303
|
+
panel = wx.Panel(parent=self)
|
|
1304
|
+
panelSizer = wx.GridBagSizer(0, 0)
|
|
1305
|
+
panel.SetSizer(panelSizer)
|
|
1306
|
+
row = 0
|
|
1307
|
+
handler = self.stairHandler
|
|
1308
|
+
# loop through the params
|
|
1309
|
+
for fieldName in handler.params:
|
|
1310
|
+
# try and get alternative "label" for the parameter
|
|
1311
|
+
try:
|
|
1312
|
+
label = handler.params[fieldName].label
|
|
1313
|
+
if not label:
|
|
1314
|
+
# it might exist but be empty
|
|
1315
|
+
label = fieldName
|
|
1316
|
+
except Exception:
|
|
1317
|
+
label = fieldName
|
|
1318
|
+
# handle special cases
|
|
1319
|
+
if fieldName == 'endPoints':
|
|
1320
|
+
continue # this was deprecated in v1.62.00
|
|
1321
|
+
if fieldName in self.globalCtrls:
|
|
1322
|
+
# these have already been made and inserted into sizer
|
|
1323
|
+
ctrls = self.globalCtrls[fieldName]
|
|
1324
|
+
else: # normal text entry field
|
|
1325
|
+
ctrls = ParamCtrls(dlg=self, parent=panel, label=label,
|
|
1326
|
+
fieldName=fieldName,
|
|
1327
|
+
param=handler.params[fieldName])
|
|
1328
|
+
panelSizer.Add(ctrls.nameCtrl, [row, 0], border=3, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
|
|
1329
|
+
if hasattr(ctrls.valueCtrl, "_szr"):
|
|
1330
|
+
panelSizer.Add(ctrls.valueCtrl._szr, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1331
|
+
else:
|
|
1332
|
+
panelSizer.Add(ctrls.valueCtrl, [row, 1], border=3, flag=wx.EXPAND | wx.ALL)
|
|
1333
|
+
row += 1
|
|
1334
|
+
# store info about the field
|
|
1335
|
+
self.staircaseCtrls[fieldName] = ctrls
|
|
1336
|
+
panelSizer.AddGrowableCol(1, 1)
|
|
1337
|
+
return panel
|
|
1338
|
+
|
|
1339
|
+
def getTrialsSummary(self, conditions):
|
|
1340
|
+
if type(conditions) == list and len(conditions) > 0:
|
|
1341
|
+
# get attr names (conditions[0].keys() inserts u'name' and u' is
|
|
1342
|
+
# annoying for novice)
|
|
1343
|
+
paramStr = "["
|
|
1344
|
+
for param in conditions[0]:
|
|
1345
|
+
paramStr += (str(param) + ', ')
|
|
1346
|
+
paramStr = paramStr[:-2] + "]" # remove final comma and add ]
|
|
1347
|
+
# generate summary info
|
|
1348
|
+
msg = _translate('%(nCondition)i conditions, with %(nParam)i '
|
|
1349
|
+
'parameters\n%(paramStr)s')
|
|
1350
|
+
vals = {'nCondition': len(conditions),
|
|
1351
|
+
'nParam': len(conditions[0]),
|
|
1352
|
+
'paramStr': paramStr}
|
|
1353
|
+
return msg % vals, True
|
|
1354
|
+
else:
|
|
1355
|
+
if (self.conditionsFile and
|
|
1356
|
+
not os.path.isfile(self.conditionsFile)):
|
|
1357
|
+
return _translate("No parameters set (conditionsFile not found)"), False
|
|
1358
|
+
# No condition file is not an error
|
|
1359
|
+
return _translate("No parameters set"), True
|
|
1360
|
+
|
|
1361
|
+
def setCtrls(self, ctrlType):
|
|
1362
|
+
# choose the ctrls to show/hide
|
|
1363
|
+
if ctrlType == 'staircase':
|
|
1364
|
+
self.currentHandler = self.stairHandler
|
|
1365
|
+
self.stairPanel.Show()
|
|
1366
|
+
self.constantsPanel.Hide()
|
|
1367
|
+
self.multiStairPanel.Hide()
|
|
1368
|
+
self.currentCtrls = self.staircaseCtrls
|
|
1369
|
+
elif ctrlType == 'interleaved staircases':
|
|
1370
|
+
self.currentHandler = self.multiStairHandler
|
|
1371
|
+
self.stairPanel.Hide()
|
|
1372
|
+
self.constantsPanel.Hide()
|
|
1373
|
+
self.multiStairPanel.Show()
|
|
1374
|
+
self.currentCtrls = self.multiStairCtrls
|
|
1375
|
+
else:
|
|
1376
|
+
self.currentHandler = self.trialHandler
|
|
1377
|
+
self.stairPanel.Hide()
|
|
1378
|
+
self.constantsPanel.Show()
|
|
1379
|
+
self.multiStairPanel.Hide()
|
|
1380
|
+
self.currentCtrls = self.constantsCtrls
|
|
1381
|
+
self.currentType = ctrlType
|
|
1382
|
+
# redo layout
|
|
1383
|
+
self.mainSizer.Layout()
|
|
1384
|
+
self.Fit()
|
|
1385
|
+
self.Refresh()
|
|
1386
|
+
|
|
1387
|
+
@property
|
|
1388
|
+
def type(self):
|
|
1389
|
+
"""What type of loop is represented by this dlg"""
|
|
1390
|
+
return self.currentHandler.type
|
|
1391
|
+
|
|
1392
|
+
@type.setter
|
|
1393
|
+
def type(self, value):
|
|
1394
|
+
self.setCtrls(value)
|
|
1395
|
+
|
|
1396
|
+
def onTypeChanged(self, evt=None):
|
|
1397
|
+
newType = evt.GetString()
|
|
1398
|
+
if newType == self.currentType:
|
|
1399
|
+
return
|
|
1400
|
+
self.setCtrls(newType)
|
|
1401
|
+
|
|
1402
|
+
def onBrowseTrialsFile(self, event):
|
|
1403
|
+
self.conditionsFileOrig = self.conditionsFile
|
|
1404
|
+
self.conditionsOrig = self.conditions
|
|
1405
|
+
expFolder, expName = os.path.split(self.frame.filename)
|
|
1406
|
+
dlg = wx.FileDialog(self, message=_translate("Open file ..."),
|
|
1407
|
+
style=wx.FD_OPEN, defaultDir=expFolder)
|
|
1408
|
+
if dlg.ShowModal() == wx.ID_OK:
|
|
1409
|
+
newFullPath = dlg.GetPath()
|
|
1410
|
+
if self.conditionsFile:
|
|
1411
|
+
_path = os.path.join(expFolder, self.conditionsFile)
|
|
1412
|
+
oldFullPath = os.path.abspath(_path)
|
|
1413
|
+
isSameFilePathAndName = bool(newFullPath == oldFullPath)
|
|
1414
|
+
else:
|
|
1415
|
+
isSameFilePathAndName = False
|
|
1416
|
+
|
|
1417
|
+
newPath = str(Path(newFullPath).relative_to(expFolder))
|
|
1418
|
+
self.conditionsFile = newPath
|
|
1419
|
+
needUpdate = False
|
|
1420
|
+
try:
|
|
1421
|
+
_c, _n = data.importConditions(dlg.GetPath(),
|
|
1422
|
+
returnFieldNames=True)
|
|
1423
|
+
self.conditions, self.condNamesInFile = _c, _n
|
|
1424
|
+
needUpdate = True
|
|
1425
|
+
except (ImportError, ValueError) as msg:
|
|
1426
|
+
msg = str(msg)
|
|
1427
|
+
if msg.startswith('Could not open'):
|
|
1428
|
+
msg = _translate('Could not read conditions from:\n')
|
|
1429
|
+
_file = newFullPath.split(os.path.sep)[-1]
|
|
1430
|
+
self.currentCtrls['conditions'].setValue(msg + _file)
|
|
1431
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1432
|
+
logging.error(
|
|
1433
|
+
'Could not open as a conditions file: %s' % newFullPath)
|
|
1434
|
+
else:
|
|
1435
|
+
mo = re.search(r'".+\.[0-9]+"$', msg)
|
|
1436
|
+
if 'cannot contain punctuation or spaces' in msg and mo:
|
|
1437
|
+
# column name is something like "stim.1", which may
|
|
1438
|
+
# be in conditionsFile or generated by pandas when
|
|
1439
|
+
# duplicated column names are found.
|
|
1440
|
+
m2 = 'Parameters (column headers) cannot contain dots' \
|
|
1441
|
+
' or be duplicated.'
|
|
1442
|
+
else:
|
|
1443
|
+
# other exceptions
|
|
1444
|
+
sep2 = os.linesep * 2
|
|
1445
|
+
m2 = msg.replace(
|
|
1446
|
+
'Conditions file ','').replace(': ', sep2)
|
|
1447
|
+
# Display error message dialog
|
|
1448
|
+
_title = _translate(
|
|
1449
|
+
'Configuration error in conditions file')
|
|
1450
|
+
dlgErr = dialogs.MessageDialog(
|
|
1451
|
+
parent=self.frame, message=m2,
|
|
1452
|
+
type='Info', title=_title).ShowModal()
|
|
1453
|
+
msg = _translate('Bad condition name(s) in file:\n')
|
|
1454
|
+
val = msg + newFullPath.split(os.path.sep)[-1]
|
|
1455
|
+
self.currentCtrls['conditions'].setValue(val)
|
|
1456
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1457
|
+
msg = 'Rejected bad condition name(s) in file: %s'
|
|
1458
|
+
logging.error(msg % newFullPath)
|
|
1459
|
+
self.conditionsFile = self.conditionsFileOrig
|
|
1460
|
+
self.conditions = self.conditionsOrig
|
|
1461
|
+
return # no update or display changes
|
|
1462
|
+
|
|
1463
|
+
# check for Builder variables
|
|
1464
|
+
builderVariables = []
|
|
1465
|
+
for condName in self.condNamesInFile:
|
|
1466
|
+
if condName in self.exp.namespace.builder:
|
|
1467
|
+
builderVariables.append(condName)
|
|
1468
|
+
if builderVariables:
|
|
1469
|
+
msg = _translate('Builder variable(s) ({}) in file:{}').format(
|
|
1470
|
+
','.join(builderVariables), newFullPath.split(os.path.sep)[-1])
|
|
1471
|
+
self.currentCtrls['conditions'].setValue(msg)
|
|
1472
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1473
|
+
msg = 'Rejected Builder variable(s) ({}) in file:{}'.format(
|
|
1474
|
+
','.join(builderVariables), newFullPath.split(os.path.sep)[-1])
|
|
1475
|
+
logging.error(msg)
|
|
1476
|
+
self.conditionsFile = self.conditionsFileOrig
|
|
1477
|
+
self.conditions = self.conditionsOrig
|
|
1478
|
+
return # no update or display changes
|
|
1479
|
+
|
|
1480
|
+
duplCondNames = []
|
|
1481
|
+
if len(self.condNamesInFile):
|
|
1482
|
+
for condName in self.condNamesInFile:
|
|
1483
|
+
if self.exp.namespace.exists(condName):
|
|
1484
|
+
duplCondNames.append(condName)
|
|
1485
|
+
# abbrev long strings to better fit in the dialog:
|
|
1486
|
+
duplCondNamesStr = ' '.join(duplCondNames)[:42]
|
|
1487
|
+
if len(duplCondNamesStr) == 42:
|
|
1488
|
+
duplCondNamesStr = duplCondNamesStr[:39] + '...'
|
|
1489
|
+
if len(duplCondNames):
|
|
1490
|
+
if isSameFilePathAndName:
|
|
1491
|
+
msg = ('Assuming reloading file: same filename and '
|
|
1492
|
+
'duplicate condition names in file: %s')
|
|
1493
|
+
logging.info(msg % self.conditionsFile)
|
|
1494
|
+
else:
|
|
1495
|
+
self.currentCtrls['conditionsFile'].setValue(newPath)
|
|
1496
|
+
val = ('Warning: Condition names conflict with existing'
|
|
1497
|
+
':\n[' + duplCondNamesStr + ']\nProceed'
|
|
1498
|
+
' anyway? (= safe if these are in old file)')
|
|
1499
|
+
self.currentCtrls['conditions'].setValue(val)
|
|
1500
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1501
|
+
msg = ('Duplicate condition names, different '
|
|
1502
|
+
'conditions file: %s')
|
|
1503
|
+
logging.warning(msg % duplCondNamesStr)
|
|
1504
|
+
# stash condition names but don't add to namespace yet, user can
|
|
1505
|
+
# still cancel
|
|
1506
|
+
# add after self.show() in __init__:
|
|
1507
|
+
self.duplCondNames = duplCondNames
|
|
1508
|
+
|
|
1509
|
+
if (needUpdate
|
|
1510
|
+
or ('conditionsFile' in list(self.currentCtrls.keys())
|
|
1511
|
+
and not duplCondNames)):
|
|
1512
|
+
self.currentCtrls['conditionsFile'].setValue(newPath)
|
|
1513
|
+
msg, OK = self.getTrialsSummary(self.conditions)
|
|
1514
|
+
self.currentCtrls['conditions'].setValue(msg)
|
|
1515
|
+
if OK:
|
|
1516
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Black")
|
|
1517
|
+
else:
|
|
1518
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1519
|
+
self.Layout()
|
|
1520
|
+
self.Fit()
|
|
1521
|
+
|
|
1522
|
+
def getParams(self):
|
|
1523
|
+
"""Retrieves data and re-inserts it into the handler and returns
|
|
1524
|
+
those handler params
|
|
1525
|
+
"""
|
|
1526
|
+
# get data from input fields
|
|
1527
|
+
for fieldName in self.currentHandler.params:
|
|
1528
|
+
if fieldName == 'endPoints':
|
|
1529
|
+
continue # this was deprecated in v1.62.00
|
|
1530
|
+
param = self.currentHandler.params[fieldName]
|
|
1531
|
+
if fieldName in ['conditionsFile']:
|
|
1532
|
+
param.val = self.conditionsFile
|
|
1533
|
+
# not the value from ctrl - that was abbreviated
|
|
1534
|
+
# see onOK() for partial handling = check for '...'
|
|
1535
|
+
else: # most other fields
|
|
1536
|
+
# the various dlg ctrls for this param
|
|
1537
|
+
ctrls = self.currentCtrls[fieldName]
|
|
1538
|
+
param.val = ctrls.getValue()
|
|
1539
|
+
# from _baseParamsDlg (handles diff control types)
|
|
1540
|
+
if ctrls.typeCtrl:
|
|
1541
|
+
param.valType = ctrls.getType()
|
|
1542
|
+
if ctrls.updateCtrl:
|
|
1543
|
+
param.updates = ctrls.getUpdates()
|
|
1544
|
+
return self.currentHandler.params
|
|
1545
|
+
|
|
1546
|
+
def refreshConditions(self):
|
|
1547
|
+
"""user might have manually edited the conditionsFile name,
|
|
1548
|
+
which in turn affects self.conditions and namespace. It's harder
|
|
1549
|
+
to handle changes to long names that have been abbrev()'d, so
|
|
1550
|
+
skip them (names containing '...').
|
|
1551
|
+
"""
|
|
1552
|
+
val = self.currentCtrls['conditionsFile'].valueCtrl.GetValue()
|
|
1553
|
+
if val.find('...') == -1 and self.conditionsFile != val:
|
|
1554
|
+
self.conditionsFile = val
|
|
1555
|
+
if self.conditions:
|
|
1556
|
+
self.exp.namespace.remove(list(self.conditions[0].keys()))
|
|
1557
|
+
if os.path.isfile(self.conditionsFile):
|
|
1558
|
+
try:
|
|
1559
|
+
self.conditions = data.importConditions(
|
|
1560
|
+
self.conditionsFile)
|
|
1561
|
+
msg, OK = self.getTrialsSummary(self.conditions)
|
|
1562
|
+
self.currentCtrls['conditions'].setValue(msg)
|
|
1563
|
+
if OK:
|
|
1564
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Black")
|
|
1565
|
+
else:
|
|
1566
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1567
|
+
except (ImportError, ValueError) as e:
|
|
1568
|
+
msg1 = _translate(
|
|
1569
|
+
'Badly formed condition name(s) in file:\n')
|
|
1570
|
+
msg2 = _translate('.\nNeed to be legal as var name; '
|
|
1571
|
+
'edit file, try again.')
|
|
1572
|
+
val = msg1 + str(e).replace(':', '\n') + msg2
|
|
1573
|
+
self.currentCtrls['conditions'].setValue(val)
|
|
1574
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1575
|
+
self.conditions = ''
|
|
1576
|
+
msg3 = 'Reject bad condition name in conditions file: %s'
|
|
1577
|
+
logging.error(msg3 % str(e).split(':')[0])
|
|
1578
|
+
else:
|
|
1579
|
+
self.conditions = None
|
|
1580
|
+
self.currentCtrls['conditions'].setValue(_translate(
|
|
1581
|
+
"No parameters set (conditionsFile not found)"))
|
|
1582
|
+
self.currentCtrls['conditions'].valueCtrl.SetForegroundColour("Red")
|
|
1583
|
+
else:
|
|
1584
|
+
msg = ('DlgLoop: could not determine if a condition'
|
|
1585
|
+
' filename was edited')
|
|
1586
|
+
logging.debug(msg)
|
|
1587
|
+
# self.currentCtrls['conditions'] could be misleading here
|
|
1588
|
+
|
|
1589
|
+
def onOK(self, event=None):
|
|
1590
|
+
# intercept OK in case user deletes or edits the filename manually
|
|
1591
|
+
if 'conditionsFile' in self.currentCtrls:
|
|
1592
|
+
self.refreshConditions()
|
|
1593
|
+
event.Skip() # do the OK button press
|
|
1594
|
+
|
|
1595
|
+
|
|
1596
|
+
class DlgComponentProperties(_BaseParamsDlg):
|
|
1597
|
+
|
|
1598
|
+
def __init__(self, frame, element, experiment,
|
|
1599
|
+
suppressTitles=True, size=wx.DefaultSize,
|
|
1600
|
+
style=wx.DEFAULT_DIALOG_STYLE | wx.DIALOG_NO_PARENT,
|
|
1601
|
+
editing=False,
|
|
1602
|
+
timeout=None, testing=False, type=None):
|
|
1603
|
+
style = style | wx.RESIZE_BORDER
|
|
1604
|
+
self.type = type or element.type
|
|
1605
|
+
_BaseParamsDlg.__init__(self, frame=frame, element=element, experiment=experiment,
|
|
1606
|
+
size=size,
|
|
1607
|
+
style=style, editing=editing,
|
|
1608
|
+
timeout=timeout)
|
|
1609
|
+
self.frame = frame
|
|
1610
|
+
self.app = frame.app
|
|
1611
|
+
self.dpi = self.app.dpi
|
|
1612
|
+
|
|
1613
|
+
# for all components
|
|
1614
|
+
self.show(testing)
|
|
1615
|
+
if not testing:
|
|
1616
|
+
if self.OK:
|
|
1617
|
+
self.params = self.getParams() # get new vals from dlg
|
|
1618
|
+
self.Destroy()
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
class DlgExperimentProperties(_BaseParamsDlg):
|
|
1622
|
+
|
|
1623
|
+
def __init__(self, frame, element, experiment,
|
|
1624
|
+
suppressTitles=False, size=wx.DefaultSize,
|
|
1625
|
+
style=wx.DEFAULT_DIALOG_STYLE | wx.DIALOG_NO_PARENT,
|
|
1626
|
+
timeout=None):
|
|
1627
|
+
style = style | wx.RESIZE_BORDER
|
|
1628
|
+
_BaseParamsDlg.__init__(self, frame=frame, element=element, experiment=experiment,
|
|
1629
|
+
size=size,
|
|
1630
|
+
style=style,
|
|
1631
|
+
timeout=timeout)
|
|
1632
|
+
self.frame = frame
|
|
1633
|
+
self.app = frame.app
|
|
1634
|
+
self.dpi = self.app.dpi
|
|
1635
|
+
|
|
1636
|
+
# for input devices:
|
|
1637
|
+
# do this just to set the initial values to be
|
|
1638
|
+
self.paramCtrls['Full-screen window'].setChangesCallback(self.onFullScrChange)
|
|
1639
|
+
self.onFullScrChange(event=None)
|
|
1640
|
+
self.Bind(wx.EVT_CHECKBOX, self.onFullScrChange,
|
|
1641
|
+
self.paramCtrls['Full-screen window'].valueCtrl)
|
|
1642
|
+
|
|
1643
|
+
# for all components
|
|
1644
|
+
self.show()
|
|
1645
|
+
if self.OK:
|
|
1646
|
+
self.params = self.getParams() # get new vals from dlg
|
|
1647
|
+
self.Destroy()
|
|
1648
|
+
|
|
1649
|
+
def onFullScrChange(self, event=None):
|
|
1650
|
+
"""full-screen has been checked / unchecked.
|
|
1651
|
+
Show or hide the window size field accordingly
|
|
1652
|
+
"""
|
|
1653
|
+
if self.paramCtrls['Full-screen window'].valueCtrl.GetValue():
|
|
1654
|
+
# get screen size for requested display
|
|
1655
|
+
numDisplays = wx.Display.GetCount()
|
|
1656
|
+
try:
|
|
1657
|
+
screenValue = int(
|
|
1658
|
+
self.paramCtrls['Screen'].valueCtrl.GetValue())
|
|
1659
|
+
except ValueError:
|
|
1660
|
+
# param control currently contains no integer value
|
|
1661
|
+
screenValue = 1
|
|
1662
|
+
if screenValue < 1 or screenValue > numDisplays:
|
|
1663
|
+
logging.error("User requested non-existent screen")
|
|
1664
|
+
screenN = 0
|
|
1665
|
+
else:
|
|
1666
|
+
screenN = screenValue - 1
|
|
1667
|
+
size = list(wx.Display(screenN).GetGeometry()[2:])
|
|
1668
|
+
# set vals and disable changes
|
|
1669
|
+
field = 'Window size (pixels)'
|
|
1670
|
+
self.paramCtrls[field].valueCtrl.SetValue(str(size))
|
|
1671
|
+
self.paramCtrls[field].param.val = size
|
|
1672
|
+
self.paramCtrls[field].valueCtrl.Disable()
|
|
1673
|
+
self.paramCtrls[field].nameCtrl.Disable()
|
|
1674
|
+
else:
|
|
1675
|
+
self.paramCtrls['Window size (pixels)'].valueCtrl.Enable()
|
|
1676
|
+
self.paramCtrls['Window size (pixels)'].nameCtrl.Enable()
|
|
1677
|
+
self.mainSizer.Layout()
|
|
1678
|
+
self.Fit()
|
|
1679
|
+
self.Refresh()
|