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,713 @@
|
|
|
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-2022 Open Science Tools Ltd.
|
|
6
|
+
# Distributed under the terms of the GNU General Public License (GPL).
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import wx
|
|
13
|
+
|
|
14
|
+
from psychopy.app.colorpicker import PsychoColorPicker
|
|
15
|
+
from psychopy.app.dialogs import ListWidget
|
|
16
|
+
from psychopy.colors import Color
|
|
17
|
+
from psychopy.localization import _translate
|
|
18
|
+
from psychopy import data, prefs, experiment
|
|
19
|
+
import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from ..localizedStrings import _localizedDialogs as _localized
|
|
23
|
+
from ...themes import icons
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _ValidatorMixin:
|
|
27
|
+
def validate(self, evt=None):
|
|
28
|
+
"""Redirect validate calls to global validate method, assigning
|
|
29
|
+
appropriate `valType`.
|
|
30
|
+
"""
|
|
31
|
+
validate(self, self.valType)
|
|
32
|
+
|
|
33
|
+
if evt is not None:
|
|
34
|
+
evt.Skip()
|
|
35
|
+
|
|
36
|
+
def showValid(self, valid):
|
|
37
|
+
"""Style input box according to valid"""
|
|
38
|
+
if not hasattr(self, "SetForegroundColour"):
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if valid:
|
|
42
|
+
self.SetForegroundColour(wx.Colour(0, 0, 0))
|
|
43
|
+
else:
|
|
44
|
+
self.SetForegroundColour(wx.Colour(1, 0, 0))
|
|
45
|
+
|
|
46
|
+
def updateCodeFont(self, valType):
|
|
47
|
+
"""Style input box according to code wanted"""
|
|
48
|
+
if not hasattr(self, "SetFont"):
|
|
49
|
+
# Skip if font not applicable to object type
|
|
50
|
+
return
|
|
51
|
+
if self.GetName() == "name":
|
|
52
|
+
# Name is never code
|
|
53
|
+
valType = "str"
|
|
54
|
+
|
|
55
|
+
fontNormal = self.GetTopLevelParent().app._mainFont
|
|
56
|
+
if valType == "code" or hasattr(self, "dollarLbl"):
|
|
57
|
+
# Set font
|
|
58
|
+
fontCode = self.GetTopLevelParent().app._codeFont
|
|
59
|
+
fontCodeBold = fontCode.Bold()
|
|
60
|
+
if fontCodeBold.IsOk():
|
|
61
|
+
self.SetFont(fontCodeBold)
|
|
62
|
+
else:
|
|
63
|
+
# use normal font if the bold version is invalid on the system
|
|
64
|
+
self.SetFont(fontCode)
|
|
65
|
+
else:
|
|
66
|
+
self.SetFont(fontNormal)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _FileMixin:
|
|
70
|
+
@property
|
|
71
|
+
def rootDir(self):
|
|
72
|
+
if not hasattr(self, "_rootDir"):
|
|
73
|
+
# Store location of root directory if not defined
|
|
74
|
+
self._rootDir = Path(self.GetTopLevelParent().frame.exp.filename)
|
|
75
|
+
if self._rootDir.is_file():
|
|
76
|
+
# Move up a dir if root is a file
|
|
77
|
+
self._rootDir = self._rootDir.parent
|
|
78
|
+
# Return stored rootDir
|
|
79
|
+
return self._rootDir
|
|
80
|
+
@rootDir.setter
|
|
81
|
+
def rootDir(self, value):
|
|
82
|
+
self._rootDir = value
|
|
83
|
+
|
|
84
|
+
def getFile(self, msg="Specify file ...", wildcard="All Files (*.*)|*.*"):
|
|
85
|
+
dlg = wx.FileDialog(self, message=_translate(msg), defaultDir=str(self.rootDir),
|
|
86
|
+
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
|
|
87
|
+
wildcard=_translate(wildcard))
|
|
88
|
+
if dlg.ShowModal() != wx.ID_OK:
|
|
89
|
+
return
|
|
90
|
+
file = dlg.GetPath()
|
|
91
|
+
try:
|
|
92
|
+
filename = Path(file).relative_to(self.rootDir)
|
|
93
|
+
except ValueError:
|
|
94
|
+
filename = Path(file).absolute()
|
|
95
|
+
return str(filename).replace("\\", "/")
|
|
96
|
+
|
|
97
|
+
def getFiles(self, msg="Specify file or files...", wildcard="All Files (*.*)|*.*"):
|
|
98
|
+
dlg = wx.FileDialog(self, message=_translate(msg), defaultDir=str(self.rootDir),
|
|
99
|
+
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE,
|
|
100
|
+
wildcard=_translate(wildcard))
|
|
101
|
+
if dlg.ShowModal() != wx.ID_OK:
|
|
102
|
+
return
|
|
103
|
+
inList = dlg.GetPaths()
|
|
104
|
+
outList = []
|
|
105
|
+
for file in inList:
|
|
106
|
+
try:
|
|
107
|
+
filename = Path(file).relative_to(self.rootDir)
|
|
108
|
+
except ValueError:
|
|
109
|
+
filename = Path(file).absolute()
|
|
110
|
+
outList.append(str(filename).replace("\\", "/"))
|
|
111
|
+
return outList
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class _HideMixin:
|
|
115
|
+
def ShowAll(self, visible):
|
|
116
|
+
# Get sizer, if present
|
|
117
|
+
if hasattr(self, "_szr"):
|
|
118
|
+
sizer = self._szr
|
|
119
|
+
elif isinstance(self, DictCtrl):
|
|
120
|
+
sizer = self
|
|
121
|
+
else:
|
|
122
|
+
sizer = self.GetSizer()
|
|
123
|
+
# If there is a sizer, recursively hide children
|
|
124
|
+
if sizer is not None:
|
|
125
|
+
self.tunnelShow(sizer, visible)
|
|
126
|
+
else:
|
|
127
|
+
self.Show(visible)
|
|
128
|
+
|
|
129
|
+
def HideAll(self):
|
|
130
|
+
self.Show(False)
|
|
131
|
+
|
|
132
|
+
def tunnelShow(self, sizer, visible):
|
|
133
|
+
if sizer is not None:
|
|
134
|
+
# Show/hide everything in the sizer
|
|
135
|
+
for child in sizer.Children:
|
|
136
|
+
if child.Window is not None:
|
|
137
|
+
child.Window.Show(visible)
|
|
138
|
+
if child.Sizer is not None:
|
|
139
|
+
# If child is a sizer, recur
|
|
140
|
+
self.tunnelShow(child.Sizer, visible)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class SingleLineCtrl(wx.TextCtrl, _ValidatorMixin, _HideMixin):
|
|
144
|
+
def __init__(self, parent, valType,
|
|
145
|
+
val="", fieldName="",
|
|
146
|
+
size=wx.Size(-1, 24), style=wx.DEFAULT):
|
|
147
|
+
# Create self
|
|
148
|
+
wx.TextCtrl.__init__(self)
|
|
149
|
+
self.Create(parent, -1, val, name=fieldName, size=size, style=style)
|
|
150
|
+
self.valType = valType
|
|
151
|
+
|
|
152
|
+
# On MacOS, we need to disable smart quotes
|
|
153
|
+
if sys.platform == 'darwin':
|
|
154
|
+
self.OSXDisableAllSmartSubstitutions()
|
|
155
|
+
|
|
156
|
+
# Add sizer
|
|
157
|
+
self._szr = wx.BoxSizer(wx.HORIZONTAL)
|
|
158
|
+
if not valType == "str" and not fieldName == "name":
|
|
159
|
+
# Add $ for anything to be interpreted verbatim
|
|
160
|
+
self.dollarLbl = wx.StaticText(parent, -1, "$", size=wx.Size(-1, -1), style=wx.ALIGN_RIGHT)
|
|
161
|
+
self.dollarLbl.SetToolTip(_translate("This parameter will be treated as code - we have already put in the $, so you don't have to."))
|
|
162
|
+
self._szr.Add(self.dollarLbl, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT)
|
|
163
|
+
# Add self to sizer
|
|
164
|
+
self._szr.Add(self, proportion=1, border=5, flag=wx.EXPAND)
|
|
165
|
+
# Bind to validation
|
|
166
|
+
self.Bind(wx.EVT_CHAR, self.validate)
|
|
167
|
+
self.validate()
|
|
168
|
+
|
|
169
|
+
def Show(self, value=True):
|
|
170
|
+
wx.TextCtrl.Show(self, value)
|
|
171
|
+
if hasattr(self, "dollarLbl"):
|
|
172
|
+
self.dollarLbl.Show(value)
|
|
173
|
+
if hasattr(self, "deleteBtn"):
|
|
174
|
+
self.deleteBtn.Show(value)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MultiLineCtrl(SingleLineCtrl, _ValidatorMixin, _HideMixin):
|
|
178
|
+
def __init__(self, parent, valType,
|
|
179
|
+
val="", fieldName="",
|
|
180
|
+
size=wx.Size(-1, 144)):
|
|
181
|
+
SingleLineCtrl.__init__(self, parent, valType,
|
|
182
|
+
val=val, fieldName=fieldName,
|
|
183
|
+
size=size, style=wx.TE_MULTILINE)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class InvalidCtrl(SingleLineCtrl, _ValidatorMixin, _HideMixin):
|
|
187
|
+
def __init__(self, parent, valType,
|
|
188
|
+
val="", fieldName="",
|
|
189
|
+
size=wx.Size(-1, 24), style=wx.DEFAULT):
|
|
190
|
+
SingleLineCtrl.__init__(self, parent, valType,
|
|
191
|
+
val=val, fieldName=fieldName,
|
|
192
|
+
size=size, style=style)
|
|
193
|
+
self.Disable()
|
|
194
|
+
# Add delete button
|
|
195
|
+
self.deleteBtn = wx.Button(parent, label="×", size=(24, 24))
|
|
196
|
+
self.deleteBtn.SetForegroundColour("red")
|
|
197
|
+
self.deleteBtn.Bind(wx.EVT_BUTTON, self.deleteParam)
|
|
198
|
+
self.deleteBtn.SetToolTip(_translate(
|
|
199
|
+
"This parameter has come from an older version of PsychoPy. "
|
|
200
|
+
"In the latest version of PsychoPy, it is not used. Click this "
|
|
201
|
+
"button to delete it. WARNING: This may affect how this experiment "
|
|
202
|
+
"works in older versions!"))
|
|
203
|
+
self._szr.Add(self.deleteBtn, border=6, flag=wx.LEFT | wx.RIGHT)
|
|
204
|
+
# Add deleted label
|
|
205
|
+
self.deleteLbl = wx.StaticText(parent, label=_translate("DELETED"))
|
|
206
|
+
self.deleteLbl.SetForegroundColour("red")
|
|
207
|
+
self.deleteLbl.Hide()
|
|
208
|
+
self._szr.Add(self.deleteLbl, border=6, proportion=1, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
|
|
209
|
+
# Add undo delete button
|
|
210
|
+
self.undoBtn = wx.Button(parent, label="⟲", size=(24, 24))
|
|
211
|
+
self.undoBtn.SetToolTip(_translate(
|
|
212
|
+
"This parameter will not be deleted until you click Okay. "
|
|
213
|
+
"Click this button to revert the deletion and keep the parameter."))
|
|
214
|
+
self.undoBtn.Hide()
|
|
215
|
+
self.undoBtn.Bind(wx.EVT_BUTTON, self.undoDelete)
|
|
216
|
+
self._szr.Add(self.undoBtn, border=6, flag=wx.LEFT | wx.RIGHT)
|
|
217
|
+
|
|
218
|
+
# Set deletion flag
|
|
219
|
+
self.forDeletion = False
|
|
220
|
+
|
|
221
|
+
def deleteParam(self, evt=None):
|
|
222
|
+
"""
|
|
223
|
+
When the remove button is pressed, mark this param as for deletion
|
|
224
|
+
"""
|
|
225
|
+
# Mark for deletion
|
|
226
|
+
self.forDeletion = True
|
|
227
|
+
# Hide value ctrl and delete button
|
|
228
|
+
self.Hide()
|
|
229
|
+
self.deleteBtn.Hide()
|
|
230
|
+
# Show delete label and
|
|
231
|
+
self.undoBtn.Show()
|
|
232
|
+
self.deleteLbl.Show()
|
|
233
|
+
|
|
234
|
+
self._szr.Layout()
|
|
235
|
+
|
|
236
|
+
def undoDelete(self, evt=None):
|
|
237
|
+
# Mark not for deletion
|
|
238
|
+
self.forDeletion = False
|
|
239
|
+
# Show value ctrl and delete button
|
|
240
|
+
self.Show()
|
|
241
|
+
self.deleteBtn.Show()
|
|
242
|
+
# Hide delete label and
|
|
243
|
+
self.undoBtn.Hide()
|
|
244
|
+
self.deleteLbl.Hide()
|
|
245
|
+
|
|
246
|
+
self._szr.Layout()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class IntCtrl(wx.SpinCtrl, _ValidatorMixin, _HideMixin):
|
|
250
|
+
def __init__(self, parent, valType,
|
|
251
|
+
val="", fieldName="",
|
|
252
|
+
size=wx.Size(-1, 24), limits=None):
|
|
253
|
+
wx.SpinCtrl.__init__(self)
|
|
254
|
+
limits = limits or (-100,100)
|
|
255
|
+
self.Create(parent, -1, str(val), name=fieldName, size=size, min=min(limits), max=max(limits))
|
|
256
|
+
self.valType = valType
|
|
257
|
+
self.Bind(wx.EVT_SPINCTRL, self.spin)
|
|
258
|
+
|
|
259
|
+
def spin(self, evt):
|
|
260
|
+
"""Redirect validate calls to global validate method, assigning appropriate valType"""
|
|
261
|
+
if evt.EventType == wx.EVT_SPIN_UP.evtType[0]:
|
|
262
|
+
self.SetValue(str(int(self.GetValue())+1))
|
|
263
|
+
elif evt.EventType == wx.EVT_SPIN_DOWN.evtType[0]:
|
|
264
|
+
self.SetValue(str(int(self.GetValue()) - 1))
|
|
265
|
+
validate(self, "int")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
BoolCtrl = wx.CheckBox
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class ChoiceCtrl(wx.Choice, _ValidatorMixin, _HideMixin):
|
|
272
|
+
def __init__(self, parent, valType,
|
|
273
|
+
val="", choices=[], labels=[], fieldName="",
|
|
274
|
+
size=wx.Size(-1, 24)):
|
|
275
|
+
<<<<<<< HEAD
|
|
276
|
+
# If not given any labels, alias values
|
|
277
|
+
if not labels:
|
|
278
|
+
labels = choices
|
|
279
|
+
# Map labels to values
|
|
280
|
+
self._choices = {}
|
|
281
|
+
for i, value in enumerate(choices):
|
|
282
|
+
if i < len(labels):
|
|
283
|
+
self._choices[labels[i]] = value
|
|
284
|
+
else:
|
|
285
|
+
self._choices[value] = value
|
|
286
|
+
=======
|
|
287
|
+
# translate and add labels to the dropdown
|
|
288
|
+
choiceLabels = []
|
|
289
|
+
for i, value in enumerate(choices):
|
|
290
|
+
if i < len(labels):
|
|
291
|
+
label = labels[i]
|
|
292
|
+
else:
|
|
293
|
+
label = value
|
|
294
|
+
try:
|
|
295
|
+
choiceLabels.append(_localized[label])
|
|
296
|
+
except:
|
|
297
|
+
choiceLabels.append(label)
|
|
298
|
+
|
|
299
|
+
>>>>>>> e32726e790e8485ee63b1419cb6e567f086836b9
|
|
300
|
+
# Create choice ctrl from labels
|
|
301
|
+
wx.Choice.__init__(self)
|
|
302
|
+
self.Create(parent, -1, size=size, choices=list(self._choices), name=fieldName)
|
|
303
|
+
self.valType = valType
|
|
304
|
+
self.SetStringSelection(val)
|
|
305
|
+
|
|
306
|
+
def SetStringSelection(self, string):
|
|
307
|
+
if string not in self._choices:
|
|
308
|
+
<<<<<<< HEAD
|
|
309
|
+
self._choices[string] = string
|
|
310
|
+
self.SetItems(list(self._choices))
|
|
311
|
+
wx.Choice.SetStringSelection(self, string)
|
|
312
|
+
|
|
313
|
+
def GetValue(self):
|
|
314
|
+
lbl = self.GetStringSelection()
|
|
315
|
+
return self._choices[lbl]
|
|
316
|
+
=======
|
|
317
|
+
self._choices.append(string)
|
|
318
|
+
self.SetItems(self._choices)
|
|
319
|
+
# Don't use wx.Choice.SetStringSelection here
|
|
320
|
+
# because label string is localized.
|
|
321
|
+
wx.Choice.SetSelection(self, self._choices.index(string))
|
|
322
|
+
|
|
323
|
+
def GetValue(self):
|
|
324
|
+
# Don't use wx.Choice.GetStringSelection here
|
|
325
|
+
# because label string is localized.
|
|
326
|
+
return self._choices[self.GetSelection()]
|
|
327
|
+
|
|
328
|
+
>>>>>>> e32726e790e8485ee63b1419cb6e567f086836b9
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class MultiChoiceCtrl(wx.CheckListBox, _ValidatorMixin, _HideMixin):
|
|
332
|
+
def __init__(self, parent, valType,
|
|
333
|
+
vals="", choices=[], fieldName="",
|
|
334
|
+
size=wx.Size(-1, -1)):
|
|
335
|
+
wx.CheckListBox.__init__(self)
|
|
336
|
+
self.Create(parent, id=wx.ID_ANY, size=size, choices=choices, name=fieldName, style=wx.LB_MULTIPLE)
|
|
337
|
+
self.valType = valType
|
|
338
|
+
self._choices = choices
|
|
339
|
+
# Make initial selection
|
|
340
|
+
if isinstance(vals, str):
|
|
341
|
+
# Convert to list if needed
|
|
342
|
+
vals = data.utils.listFromString(vals, excludeEmpties=True)
|
|
343
|
+
self.SetCheckedStrings(vals)
|
|
344
|
+
self.validate()
|
|
345
|
+
|
|
346
|
+
def SetCheckedStrings(self, strings):
|
|
347
|
+
if not isinstance(strings, (list, tuple)):
|
|
348
|
+
strings = [strings]
|
|
349
|
+
for s in strings:
|
|
350
|
+
if s not in self._choices:
|
|
351
|
+
self._choices.append(s)
|
|
352
|
+
self.SetItems(self._choices)
|
|
353
|
+
wx.CheckListBox.SetCheckedStrings(self, strings)
|
|
354
|
+
|
|
355
|
+
def GetValue(self, evt=None):
|
|
356
|
+
return self.GetCheckedStrings()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class FileCtrl(wx.TextCtrl, _ValidatorMixin, _HideMixin, _FileMixin):
|
|
360
|
+
def __init__(self, parent, valType,
|
|
361
|
+
val="", fieldName="",
|
|
362
|
+
size=wx.Size(-1, 24)):
|
|
363
|
+
# Create self
|
|
364
|
+
wx.TextCtrl.__init__(self)
|
|
365
|
+
self.Create(parent, -1, val, name=fieldName, size=size)
|
|
366
|
+
self.valType = valType
|
|
367
|
+
# Add sizer
|
|
368
|
+
self._szr = wx.BoxSizer(wx.HORIZONTAL)
|
|
369
|
+
self._szr.Add(self, border=5, proportion=1, flag=wx.EXPAND | wx.RIGHT)
|
|
370
|
+
# Add button to browse for file
|
|
371
|
+
fldr = icons.ButtonIcon(stem="folder", size=16).bitmap
|
|
372
|
+
self.findBtn = wx.BitmapButton(parent, -1, size=wx.Size(24, 24), bitmap=fldr)
|
|
373
|
+
self.findBtn.SetToolTip(_translate("Specify file ..."))
|
|
374
|
+
self.findBtn.Bind(wx.EVT_BUTTON, self.findFile)
|
|
375
|
+
self._szr.Add(self.findBtn)
|
|
376
|
+
# Configure validation
|
|
377
|
+
self.Bind(wx.EVT_TEXT, self.validate)
|
|
378
|
+
self.validate()
|
|
379
|
+
|
|
380
|
+
def findFile(self, evt):
|
|
381
|
+
file = self.getFile()
|
|
382
|
+
if file:
|
|
383
|
+
self.SetValue(file)
|
|
384
|
+
self.validate(evt)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class FileListCtrl(wx.ListBox, _ValidatorMixin, _HideMixin, _FileMixin):
|
|
388
|
+
def __init__(self, parent, valType,
|
|
389
|
+
choices=[], size=None, pathtype="rel"):
|
|
390
|
+
wx.ListBox.__init__(self)
|
|
391
|
+
self.valType = valType
|
|
392
|
+
parent.Bind(wx.EVT_DROP_FILES, self.addItem)
|
|
393
|
+
self.app = parent.app
|
|
394
|
+
if type(choices) == str:
|
|
395
|
+
choices = data.utils.listFromString(choices)
|
|
396
|
+
self.Create(id=wx.ID_ANY, parent=parent, choices=choices, size=size, style=wx.LB_EXTENDED | wx.LB_HSCROLL)
|
|
397
|
+
self.addCustomBtn = wx.Button(parent, -1, size=(24,24), style=wx.BU_EXACTFIT, label="...")
|
|
398
|
+
self.addCustomBtn.Bind(wx.EVT_BUTTON, self.addCustomItem)
|
|
399
|
+
self.addBtn = wx.Button(parent, -1, size=(24,24), style=wx.BU_EXACTFIT, label="+")
|
|
400
|
+
self.addBtn.Bind(wx.EVT_BUTTON, self.addItem)
|
|
401
|
+
self.subBtn = wx.Button(parent, -1, size=(24,24), style=wx.BU_EXACTFIT, label="-")
|
|
402
|
+
self.subBtn.Bind(wx.EVT_BUTTON, self.removeItem)
|
|
403
|
+
self._szr = wx.BoxSizer(wx.HORIZONTAL)
|
|
404
|
+
self.btns = wx.BoxSizer(wx.VERTICAL)
|
|
405
|
+
self.btns.AddMany((self.addCustomBtn, self.addBtn, self.subBtn))
|
|
406
|
+
self._szr.Add(self, proportion=1, flag=wx.EXPAND)
|
|
407
|
+
self._szr.Add(self.btns)
|
|
408
|
+
|
|
409
|
+
def addItem(self, event):
|
|
410
|
+
# Get files
|
|
411
|
+
if event.GetEventObject() == self.addBtn:
|
|
412
|
+
fileList = self.getFiles()
|
|
413
|
+
else:
|
|
414
|
+
fileList = event.GetFiles()
|
|
415
|
+
for i, filename in enumerate(fileList):
|
|
416
|
+
try:
|
|
417
|
+
fileList[i] = Path(filename).relative_to(self.rootDir)
|
|
418
|
+
except ValueError:
|
|
419
|
+
fileList[i] = Path(filename).absolute()
|
|
420
|
+
# Add files to list
|
|
421
|
+
if fileList:
|
|
422
|
+
self.InsertItems(fileList, 0)
|
|
423
|
+
|
|
424
|
+
def removeItem(self, event):
|
|
425
|
+
i = self.GetSelections()
|
|
426
|
+
if isinstance(i, int):
|
|
427
|
+
i = [i]
|
|
428
|
+
items = [item for index, item in enumerate(self.Items)
|
|
429
|
+
if index not in i]
|
|
430
|
+
self.SetItems(items)
|
|
431
|
+
|
|
432
|
+
def addCustomItem(self, event):
|
|
433
|
+
# Create string dialog
|
|
434
|
+
dlg = wx.TextEntryDialog(parent=self, message=_translate("Add custom item"))
|
|
435
|
+
# Show dialog
|
|
436
|
+
if dlg.ShowModal() != wx.ID_OK:
|
|
437
|
+
return
|
|
438
|
+
# Get string
|
|
439
|
+
stringEntry = dlg.GetValue()
|
|
440
|
+
# Add to list
|
|
441
|
+
if stringEntry:
|
|
442
|
+
self.InsertItems([stringEntry], 0)
|
|
443
|
+
|
|
444
|
+
def GetValue(self):
|
|
445
|
+
return self.Items
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class TableCtrl(wx.TextCtrl, _ValidatorMixin, _HideMixin, _FileMixin):
|
|
449
|
+
def __init__(self, parent, valType,
|
|
450
|
+
val="", fieldName="",
|
|
451
|
+
size=wx.Size(-1, 24)):
|
|
452
|
+
# Create self
|
|
453
|
+
wx.TextCtrl.__init__(self)
|
|
454
|
+
self.Create(parent, -1, val, name=fieldName, size=size)
|
|
455
|
+
self.valType = valType
|
|
456
|
+
# Add sizer
|
|
457
|
+
self._szr = wx.BoxSizer(wx.HORIZONTAL)
|
|
458
|
+
self._szr.Add(self, proportion=1, border=5, flag=wx.EXPAND | wx.RIGHT)
|
|
459
|
+
# Add button to browse for file
|
|
460
|
+
fldr = icons.ButtonIcon(stem="folder", size=16).bitmap
|
|
461
|
+
self.findBtn = wx.BitmapButton(parent, -1, size=wx.Size(24,24), bitmap=fldr)
|
|
462
|
+
self.findBtn.SetToolTip(_translate("Specify file ..."))
|
|
463
|
+
self.findBtn.Bind(wx.EVT_BUTTON, self.findFile)
|
|
464
|
+
self._szr.Add(self.findBtn)
|
|
465
|
+
# Add button to open in Excel
|
|
466
|
+
xl = icons.ButtonIcon(stem="filecsv", size=16).bitmap
|
|
467
|
+
self.xlBtn = wx.BitmapButton(parent, -1, size=wx.Size(24,24), bitmap=xl)
|
|
468
|
+
self.xlBtn.SetToolTip(_translate("Open/create in your default table editor"))
|
|
469
|
+
self.xlBtn.Bind(wx.EVT_BUTTON, self.openExcel)
|
|
470
|
+
self._szr.Add(self.xlBtn)
|
|
471
|
+
# Link to Excel templates for certain contexts
|
|
472
|
+
cmpRoot = Path(experiment.components.__file__).parent
|
|
473
|
+
expRoot = Path(cmpRoot).parent
|
|
474
|
+
self.templates = {
|
|
475
|
+
'Form': Path(cmpRoot) / "form" / "formItems.xltx",
|
|
476
|
+
'TrialHandler': Path(expRoot) / "loopTemplate.xltx",
|
|
477
|
+
'StairHandler': Path(expRoot) / "loopTemplate.xltx",
|
|
478
|
+
'MultiStairHandler': Path(expRoot) / "loopTemplate.xltx",
|
|
479
|
+
'QuestHandler': Path(expRoot) / "loopTemplate.xltx",
|
|
480
|
+
'None': Path(expRoot) / 'blankTemplate.xltx',
|
|
481
|
+
}
|
|
482
|
+
# Specify valid extensions
|
|
483
|
+
self.validExt = [".csv",".tsv",".txt",
|
|
484
|
+
".xl",".xlsx",".xlsm",".xlsb",".xlam",".xltx",".xltm",".xls",".xlt",
|
|
485
|
+
".htm",".html",".mht",".mhtml",
|
|
486
|
+
".xml",".xla",".xlm",
|
|
487
|
+
".odc",".ods",
|
|
488
|
+
".udl",".dsn",".mdb",".mde",".accdb",".accde",".dbc",".dbf",
|
|
489
|
+
".iqy",".dqy",".rqy",".oqy",
|
|
490
|
+
".cub",".atom",".atomsvc",
|
|
491
|
+
".prn",".slk",".dif"]
|
|
492
|
+
# Configure validation
|
|
493
|
+
self.Bind(wx.EVT_TEXT, self.validate)
|
|
494
|
+
self.validate()
|
|
495
|
+
|
|
496
|
+
def validate(self, evt=None):
|
|
497
|
+
"""Redirect validate calls to global validate method, assigning appropriate valType"""
|
|
498
|
+
validate(self, "file")
|
|
499
|
+
# Enable Excel button if valid
|
|
500
|
+
self.xlBtn.Enable(self.valid)
|
|
501
|
+
# Is component type available?
|
|
502
|
+
if self.GetValue() in [None, ""] + self.validExt and hasattr(self.GetTopLevelParent(), 'type'):
|
|
503
|
+
# Does this component have a default template?
|
|
504
|
+
if self.GetTopLevelParent().type in self.templates:
|
|
505
|
+
self.xlBtn.Enable(True)
|
|
506
|
+
|
|
507
|
+
def openExcel(self, event):
|
|
508
|
+
"""Either open the specified excel sheet, or make a new one from a template"""
|
|
509
|
+
file = self.rootDir / self.GetValue()
|
|
510
|
+
if not (file.is_file() and file.suffix in self.validExt): # If not a valid file
|
|
511
|
+
dlg = wx.MessageDialog(self, _translate(
|
|
512
|
+
"Once you have created and saved your table,"
|
|
513
|
+
"please remember to add it to {name}").format(name=_translate(self.Name)),
|
|
514
|
+
caption=_translate("Reminder"))
|
|
515
|
+
dlg.ShowModal()
|
|
516
|
+
if hasattr(self.GetTopLevelParent(), 'type'):
|
|
517
|
+
if self.GetTopLevelParent().type in self.templates:
|
|
518
|
+
file = self.templates[self.GetTopLevelParent().type] # Open type specific template
|
|
519
|
+
else:
|
|
520
|
+
file = self.templates['None'] # Open blank template
|
|
521
|
+
# Open whatever file is used
|
|
522
|
+
try:
|
|
523
|
+
os.startfile(file)
|
|
524
|
+
except AttributeError:
|
|
525
|
+
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
526
|
+
subprocess.call([opener, file])
|
|
527
|
+
|
|
528
|
+
def findFile(self, event):
|
|
529
|
+
_wld = f"All Table Files({'*'+';*'.join(self.validExt)})|{'*'+';*'.join(self.validExt)}|All Files (*.*)|*.*"
|
|
530
|
+
file = self.getFile(msg="Specify table file ...", wildcard=_wld)
|
|
531
|
+
if file:
|
|
532
|
+
self.SetValue(file)
|
|
533
|
+
self.validate(event)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class ColorCtrl(wx.TextCtrl, _ValidatorMixin, _HideMixin):
|
|
537
|
+
def __init__(self, parent, valType,
|
|
538
|
+
val="", fieldName="",
|
|
539
|
+
size=wx.Size(-1, 24)):
|
|
540
|
+
# Create self
|
|
541
|
+
wx.TextCtrl.__init__(self)
|
|
542
|
+
self.Create(parent, -1, val, name=fieldName, size=size)
|
|
543
|
+
self.valType = valType
|
|
544
|
+
# Add sizer
|
|
545
|
+
self._szr = wx.BoxSizer(wx.HORIZONTAL)
|
|
546
|
+
if valType == "code":
|
|
547
|
+
# Add $ for anything to be interpreted verbatim
|
|
548
|
+
self.dollarLbl = wx.StaticText(parent, -1, "$", size=wx.Size(-1, -1), style=wx.ALIGN_RIGHT)
|
|
549
|
+
self.dollarLbl.SetToolTip(_translate("This parameter will be treated as code - we have already put in the $, so you don't have to."))
|
|
550
|
+
self._szr.Add(self.dollarLbl, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT)
|
|
551
|
+
# Add ctrl to sizer
|
|
552
|
+
self._szr.Add(self, proportion=1, border=5, flag=wx.EXPAND | wx.RIGHT)
|
|
553
|
+
# Add button to activate color picker
|
|
554
|
+
fldr = icons.ButtonIcon(stem="color", size=16).bitmap
|
|
555
|
+
self.pickerBtn = wx.BitmapButton(parent, -1, size=wx.Size(24,24), bitmap=fldr)
|
|
556
|
+
self.pickerBtn.SetToolTip(_translate("Specify color ..."))
|
|
557
|
+
self.pickerBtn.Bind(wx.EVT_BUTTON, self.colorPicker)
|
|
558
|
+
self._szr.Add(self.pickerBtn)
|
|
559
|
+
# Bind to validation
|
|
560
|
+
self.Bind(wx.EVT_CHAR, self.validate)
|
|
561
|
+
self.validate()
|
|
562
|
+
|
|
563
|
+
def colorPicker(self, evt):
|
|
564
|
+
dlg = PsychoColorPicker(self, context=self, allowCopy=False) # open a color picker
|
|
565
|
+
dlg.ShowModal()
|
|
566
|
+
dlg.Destroy()
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def validate(obj, valType):
|
|
570
|
+
val = str(obj.GetValue())
|
|
571
|
+
valid = True
|
|
572
|
+
if val.startswith("$"):
|
|
573
|
+
# If indicated as code, treat as code
|
|
574
|
+
valType = "code"
|
|
575
|
+
# Validate string
|
|
576
|
+
if valType == "str":
|
|
577
|
+
if re.findall(r"(?<!\\)\"", val):
|
|
578
|
+
# If there are unescaped "
|
|
579
|
+
valid = False
|
|
580
|
+
if re.findall(r"(?<!\\)\'", val):
|
|
581
|
+
# If there are unescaped '
|
|
582
|
+
valid = False
|
|
583
|
+
# Validate code
|
|
584
|
+
if valType == "code":
|
|
585
|
+
# Replace unescaped curly quotes
|
|
586
|
+
if re.findall(r"(?<!\\)[\u201c\u201d]", val):
|
|
587
|
+
pt = obj.GetInsertionPoint()
|
|
588
|
+
obj.SetValue(re.sub(r"(?<!\\)[\u201c\u201d]", "\"", val))
|
|
589
|
+
obj.SetInsertionPoint(pt)
|
|
590
|
+
# For now, ignore
|
|
591
|
+
pass
|
|
592
|
+
# Validate num
|
|
593
|
+
if valType in ["num", "int"]:
|
|
594
|
+
try:
|
|
595
|
+
# Try to convert value to a float
|
|
596
|
+
float(val)
|
|
597
|
+
except ValueError:
|
|
598
|
+
# If conversion fails, value is invalid
|
|
599
|
+
valid = False
|
|
600
|
+
# Validate bool
|
|
601
|
+
if valType == "bool":
|
|
602
|
+
if val not in ["True", "False"]:
|
|
603
|
+
# If value is not True or False, it is invalid
|
|
604
|
+
valid = False
|
|
605
|
+
# Validate list
|
|
606
|
+
if valType == "list":
|
|
607
|
+
empty = not bool(val) # Is value empty?
|
|
608
|
+
fullList = re.fullmatch(r"[\(\[].*[\]\)]", val) # Is value full list with parentheses?
|
|
609
|
+
partList = "," in val and not re.match(r"[\(\[].*[\]\)]", val) # Is value list without parentheses?
|
|
610
|
+
singleVal = not " " in val or re.match(r"[\"\'].*[\"\']", val) # Is value a single value?
|
|
611
|
+
if not any([empty, fullList, partList, singleVal]):
|
|
612
|
+
# If value is not any of valid types, it is invalid
|
|
613
|
+
valid = False
|
|
614
|
+
# Validate color
|
|
615
|
+
if valType == "color":
|
|
616
|
+
# Strip function calls
|
|
617
|
+
if re.fullmatch(r"\$?(Advanced)?Color\(.*\)", val):
|
|
618
|
+
val = re.sub(r"\$?(Advanced)?Color\(", "", val[:-1])
|
|
619
|
+
try:
|
|
620
|
+
# Try to create a Color object from value
|
|
621
|
+
obj.color = Color(val, False)
|
|
622
|
+
if not obj.color:
|
|
623
|
+
# If invalid object is created, input is invalid
|
|
624
|
+
valid = False
|
|
625
|
+
except:
|
|
626
|
+
# If object creation fails, input is invalid
|
|
627
|
+
valid = False
|
|
628
|
+
if valType == "file":
|
|
629
|
+
val = Path(str(val))
|
|
630
|
+
if not val.is_absolute():
|
|
631
|
+
frame = obj.GetTopLevelParent().frame
|
|
632
|
+
# If not an absolute path, append to current directory
|
|
633
|
+
val = Path(frame.filename).parent / val
|
|
634
|
+
if not val.is_file():
|
|
635
|
+
# Is value a valid filepath?
|
|
636
|
+
valid = False
|
|
637
|
+
if hasattr(obj, "validExt"):
|
|
638
|
+
# If control has specified list of ext, does value end in correct ext?
|
|
639
|
+
if val.suffix not in obj.validExt:
|
|
640
|
+
valid = False
|
|
641
|
+
|
|
642
|
+
# If additional allowed values are defined, override validation
|
|
643
|
+
if hasattr(obj, "allowedVals"):
|
|
644
|
+
if val in obj.allowedVals:
|
|
645
|
+
valid = True
|
|
646
|
+
|
|
647
|
+
# Apply valid status to object
|
|
648
|
+
obj.valid = valid
|
|
649
|
+
if hasattr(obj, "showValid"):
|
|
650
|
+
obj.showValid(valid)
|
|
651
|
+
|
|
652
|
+
# Update code font
|
|
653
|
+
obj.updateCodeFont(valType)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class DictCtrl(ListWidget, _ValidatorMixin, _HideMixin):
|
|
657
|
+
def __init__(self, parent,
|
|
658
|
+
val={}, valType='dict',
|
|
659
|
+
fieldName=""):
|
|
660
|
+
if not isinstance(val, (dict, list)):
|
|
661
|
+
raise ValueError("DictCtrl must be supplied with either a dict or a list of 1-long dicts, value supplied was {}".format(val))
|
|
662
|
+
# If supplied with a dict, convert it to a list of dicts
|
|
663
|
+
if isinstance(val, dict):
|
|
664
|
+
newVal = []
|
|
665
|
+
for key, v in val.items():
|
|
666
|
+
newVal.append({'Field': key, 'Default': v.val})
|
|
667
|
+
val = newVal
|
|
668
|
+
# If any items within the list are not dicts or are dicts longer than 1, throw error
|
|
669
|
+
if not all(isinstance(v, dict) and len(v) == 2 for v in val):
|
|
670
|
+
raise ValueError("DictCtrl must be supplied with either a dict or a list of 1-long dicts, value supplied was {}".format(val))
|
|
671
|
+
# Create ListWidget
|
|
672
|
+
ListWidget.__init__(self, parent, val, order=['Field', 'Default'])
|
|
673
|
+
|
|
674
|
+
def SetForegroundColour(self, color):
|
|
675
|
+
for child in self.Children:
|
|
676
|
+
if hasattr(child, "SetForegroundColour"):
|
|
677
|
+
child.SetForegroundColour(color)
|
|
678
|
+
|
|
679
|
+
def Enable(self, enable=True):
|
|
680
|
+
"""
|
|
681
|
+
Enable or disable all items in the dict ctrl
|
|
682
|
+
"""
|
|
683
|
+
# Iterate through all children
|
|
684
|
+
for cell in self.Children:
|
|
685
|
+
# Get the actual child rather than the sizer item
|
|
686
|
+
child = cell.Window
|
|
687
|
+
# If it can be enabled/disabled, enable/disable it
|
|
688
|
+
if hasattr(child, "Enable"):
|
|
689
|
+
child.Enable(enable)
|
|
690
|
+
|
|
691
|
+
def Disable(self):
|
|
692
|
+
"""
|
|
693
|
+
Disable all items in the dict ctrl
|
|
694
|
+
"""
|
|
695
|
+
self.Enable(False)
|
|
696
|
+
|
|
697
|
+
def Show(self, show=True):
|
|
698
|
+
"""
|
|
699
|
+
Show or hide all items in the dict ctrl
|
|
700
|
+
"""
|
|
701
|
+
# Iterate through all children
|
|
702
|
+
for cell in self.Children:
|
|
703
|
+
# Get the actual child rather than the sizer item
|
|
704
|
+
child = cell.Window
|
|
705
|
+
# If it can be shown/hidden, show/hide it
|
|
706
|
+
if hasattr(child, "Show"):
|
|
707
|
+
child.Show(show)
|
|
708
|
+
|
|
709
|
+
def Hide(self):
|
|
710
|
+
"""
|
|
711
|
+
Hide all items in the dict ctrl
|
|
712
|
+
"""
|
|
713
|
+
self.Show(False)
|