psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__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/CHANGELOG.txt +206 -0
- psychopy/GIT_SHA +1 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +77 -15
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/__init__.py +76 -2
- psychopy/app/_psychopyApp.py +126 -101
- psychopy/app/builder/builder.py +14 -10
- psychopy/app/builder/dialogs/__init__.py +8 -8
- psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
- psychopy/app/builder/dialogs/paramCtrls.py +24 -57
- psychopy/app/builder/validators.py +2 -2
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +4 -4
- psychopy/app/connections/sendusage.py +2 -2
- psychopy/app/connections/updates.py +9 -9
- psychopy/app/dialogs.py +34 -2
- psychopy/app/idle.py +31 -0
- psychopy/app/jobs.py +21 -3
- psychopy/app/linuxconfig/__init__.py +9 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
- psychopy/app/pavlovia_ui/_base.py +33 -3
- psychopy/app/pavlovia_ui/search.py +0 -1
- psychopy/app/plugin_manager/dialog.py +104 -51
- psychopy/app/plugin_manager/packages.py +5 -0
- psychopy/app/plugin_manager/plugins.py +145 -67
- psychopy/app/preferencesDlg.py +8 -8
- psychopy/app/psychopyApp.py +11 -5
- psychopy/app/ribbon.py +124 -14
- psychopy/app/runner/runner.py +6 -1
- psychopy/app/stdout/stdOutRich.py +27 -11
- psychopy/app/themes/icons.py +52 -2
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +2 -2
- psychopy/colors.py +2 -1
- psychopy/compatibility.py +53 -1
- psychopy/contrib/.DS_Store +0 -0
- psychopy/contrib/configobj/__init__.py +10 -8
- psychopy/data/__init__.py +3 -2
- psychopy/data/base.py +5 -5
- psychopy/data/experiment.py +130 -4
- psychopy/data/routine.py +56 -0
- psychopy/data/staircase.py +2 -2
- psychopy/data/trial.py +559 -97
- psychopy/data/utils.py +56 -21
- psychopy/demos/.DS_Store +0 -0
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Hardware/.DS_Store +0 -0
- psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
- psychopy/demos/coder/iohub/.DS_Store +0 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/event.py +30 -29
- psychopy/experiment/.DS_Store +0 -0
- psychopy/experiment/_experiment.py +6 -6
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/__init__.py +6 -3
- psychopy/experiment/components/_base.py +286 -131
- psychopy/experiment/components/aperture/.DS_Store +0 -0
- psychopy/experiment/components/brush/.DS_Store +0 -0
- psychopy/experiment/components/button/.DS_Store +0 -0
- psychopy/experiment/components/button/__init__.py +5 -1
- psychopy/experiment/components/buttonBox/.DS_Store +0 -0
- psychopy/experiment/components/camera/.DS_Store +0 -0
- psychopy/experiment/components/code/.DS_Store +0 -0
- psychopy/experiment/components/dots/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/form/__init__.py +6 -2
- psychopy/experiment/components/grating/.DS_Store +0 -0
- psychopy/experiment/components/grating/__init__.py +14 -3
- psychopy/experiment/components/image/.DS_Store +0 -0
- psychopy/experiment/components/image/__init__.py +14 -3
- psychopy/experiment/components/joyButtons/.DS_Store +0 -0
- psychopy/experiment/components/joystick/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/__init__.py +22 -10
- psychopy/experiment/components/microphone/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +59 -39
- psychopy/experiment/components/mouse/.DS_Store +0 -0
- psychopy/experiment/components/mouse/__init__.py +44 -29
- psychopy/experiment/components/movie/.DS_Store +0 -0
- psychopy/experiment/components/movie/__init__.py +1 -1
- psychopy/experiment/components/panorama/.DS_Store +0 -0
- psychopy/experiment/components/parallelOut/.DS_Store +0 -0
- psychopy/experiment/components/patch/.DS_Store +0 -0
- psychopy/experiment/components/polygon/.DS_Store +0 -0
- psychopy/experiment/components/polygon/__init__.py +26 -6
- psychopy/experiment/components/progress/.DS_Store +0 -0
- psychopy/experiment/components/ratingScale/.DS_Store +0 -0
- psychopy/experiment/components/resourceManager/.DS_Store +0 -0
- psychopy/experiment/components/roi/.DS_Store +0 -0
- psychopy/experiment/components/roi/__init__.py +5 -0
- psychopy/experiment/components/routineSettings/.DS_Store +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +57 -10
- psychopy/experiment/components/serialOut/.DS_Store +0 -0
- psychopy/experiment/components/settings/.DS_Store +0 -0
- psychopy/experiment/components/settings/__init__.py +117 -42
- psychopy/experiment/components/slider/.DS_Store +0 -0
- psychopy/experiment/components/sound/.DS_Store +0 -0
- psychopy/experiment/components/sound/__init__.py +54 -19
- psychopy/experiment/components/static/.DS_Store +0 -0
- psychopy/experiment/components/static/__init__.py +1 -1
- psychopy/experiment/components/text/.DS_Store +0 -0
- psychopy/experiment/components/text/__init__.py +28 -3
- psychopy/experiment/components/textbox/.DS_Store +0 -0
- psychopy/experiment/components/textbox/__init__.py +12 -2
- psychopy/experiment/components/unknown/.DS_Store +0 -0
- psychopy/experiment/components/unknown/__init__.py +1 -2
- psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
- psychopy/experiment/components/variable/.DS_Store +0 -0
- psychopy/experiment/flow.py +11 -4
- psychopy/experiment/loops.py +85 -37
- psychopy/experiment/params.py +74 -32
- psychopy/experiment/py2js_transpiler.py +8 -1
- psychopy/experiment/routines/.DS_Store +0 -0
- psychopy/experiment/routines/_base.py +102 -22
- psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +5 -1
- psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
- psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
- psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
- psychopy/experiment/routines/unknown/.DS_Store +0 -0
- psychopy/gui/wxgui.py +4 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/__init__.py +1 -1
- psychopy/hardware/base.py +12 -0
- psychopy/hardware/camera/__init__.py +1 -15
- psychopy/hardware/cedrus.py +10 -11
- psychopy/hardware/crs/colorcal.py +13 -22
- psychopy/hardware/crs/optical.py +10 -20
- psychopy/hardware/emulator.py +17 -14
- psychopy/hardware/eyetracker.py +42 -118
- psychopy/hardware/gammasci.py +4 -15
- psychopy/hardware/keyboard.py +102 -10
- psychopy/hardware/listener.py +3 -0
- psychopy/hardware/microphone.py +148 -18
- psychopy/hardware/minolta.py +8 -15
- psychopy/hardware/photodiode.py +191 -16
- psychopy/hardware/photometer/__init__.py +11 -19
- psychopy/hardware/pr.py +8 -15
- psychopy/hardware/speaker.py +39 -4
- psychopy/info.py +0 -71
- psychopy/iohub/.DS_Store +0 -0
- psychopy/iohub/__init__.py +1 -1
- psychopy/iohub/client/__init__.py +30 -20
- psychopy/iohub/client/keyboard.py +24 -24
- psychopy/iohub/datastore/__init__.py +2 -2
- psychopy/iohub/datastore/util.py +2 -2
- psychopy/iohub/default_config.yaml +1 -1
- psychopy/iohub/devices/.DS_Store +0 -0
- psychopy/iohub/devices/__init__.py +112 -25
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
- psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
- psychopy/iohub/devices/eyetracker/__init__.py +46 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
- psychopy/iohub/server.py +2 -2
- psychopy/iohub/start_iohub_process.py +3 -0
- psychopy/iohub/util/__init__.py +62 -70
- psychopy/layout.py +5 -5
- psychopy/logging.py +8 -1
- psychopy/microphone.py +10 -37
- psychopy/platform_specific/__init__.py +0 -2
- psychopy/platform_specific/darwin.py +1 -3
- psychopy/platform_specific/linux.py +31 -33
- psychopy/platform_specific/win32.py +38 -13
- psychopy/plugins/__init__.py +148 -116
- psychopy/plugins/util.py +39 -0
- psychopy/preferences/Darwin.spec +4 -2
- psychopy/preferences/FreeBSD.spec +4 -2
- psychopy/preferences/Linux.spec +4 -2
- psychopy/preferences/Windows.spec +4 -2
- psychopy/preferences/baseNoArch.spec +4 -2
- psychopy/preferences/preferences.py +47 -24
- psychopy/projects/pavlovia.py +47 -4
- psychopy/scripts/psyexpCompile.py +0 -4
- psychopy/session.py +153 -21
- psychopy/sound/__init__.py +31 -21
- psychopy/sound/_base.py +20 -3
- psychopy/sound/audioclip.py +320 -33
- psychopy/sound/backend_ptb.py +47 -58
- psychopy/sound/backend_pygame.py +1 -1
- psychopy/sound/backend_pysound.py +6 -15
- psychopy/sound/transcribe.py +53 -0
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_session/.DS_Store +0 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/conftest.py +2 -2
- psychopy/tests/test_app/test_speed.py +4 -1
- psychopy/tests/test_data/test_TrialHandler2.py +146 -1
- psychopy/tests/test_experiment/.DS_Store +0 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
- psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
- psychopy/tests/test_experiment/test_py2js.py +1 -1
- psychopy/tests/test_hardware/test_keyboard.py +31 -0
- psychopy/tests/test_hardware/test_ports.py +1 -11
- psychopy/tests/test_liaison/test_Liaison.py +47 -0
- psychopy/tests/test_misc/test_core.py +5 -0
- psychopy/tests/test_session/test_Session.py +5 -1
- psychopy/tests/test_tools/test_versionchooser.py +39 -8
- psychopy/tests/test_visual/test_all_stimuli.py +0 -97
- psychopy/tests/test_visual/test_image.py +6 -5
- psychopy/tests/test_visual/test_textbox.py +36 -0
- psychopy/tests/utils.py +4 -0
- psychopy/tools/filetools.py +1 -1
- psychopy/tools/pkgtools.py +160 -137
- psychopy/tools/versionchooser.py +10 -10
- psychopy/tools/wizard.py +3 -3
- psychopy/visual/.DS_Store +0 -0
- psychopy/visual/backends/pygletbackend.py +24 -13
- psychopy/visual/basevisual.py +5 -11
- psychopy/visual/button.py +2 -14
- psychopy/visual/helpers.py +5 -5
- psychopy/visual/line.py +1 -2
- psychopy/visual/movie2.py +7 -816
- psychopy/visual/movie3.py +7 -589
- psychopy/visual/movies/__init__.py +8 -11
- psychopy/visual/movies/frame.py +5 -2
- psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
- psychopy/visual/noise.py +8 -7
- psychopy/visual/patch.py +7 -16
- psychopy/visual/radial.py +9 -7
- psychopy/visual/ratingscale.py +8 -1415
- psychopy/visual/secondorder.py +10 -9
- psychopy/visual/shape.py +7 -2
- psychopy/visual/text.py +1 -1
- psychopy/visual/textbox2/textbox2.py +28 -5
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
- psychopy/app/Resources/click.png +0 -0
- psychopy/app/Resources/next.png +0 -0
- psychopy/experiment/components/patch/__init__.py +0 -121
- psychopy/experiment/components/patch/classic/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
- psychopy/experiment/components/patch/light/patch.png +0 -0
- psychopy/experiment/components/patch/light/patch@2x.png +0 -0
- psychopy/experiment/components/ratingScale/__init__.py +0 -337
- psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
- psychopy/platform_specific/posix.py +0 -16
- psychopy/tests/test_sound/test_microphone.py +0 -217
- psychopy/tests/test_visual/test_ratingScale.py +0 -299
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
- /psychopy/{app/Resources → assets}/USB-C.png +0 -0
- /psychopy/{app/Resources → assets}/USB.png +0 -0
- /psychopy/{app/Resources → assets}/creditCard.png +0 -0
- /psychopy/{app/Resources → assets}/default.mp3 +0 -0
- /psychopy/{app/Resources → assets}/default.mp4 +0 -0
- /psychopy/{app/Resources → assets}/default.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
import shutil
|
|
2
3
|
|
|
3
4
|
import wx
|
|
4
5
|
from wx.lib import scrolledpanel
|
|
@@ -220,7 +221,7 @@ class PluginInfo:
|
|
|
220
221
|
return
|
|
221
222
|
|
|
222
223
|
wx.CallAfter(
|
|
223
|
-
self.parent.GetTopLevelParent().
|
|
224
|
+
self.parent.GetTopLevelParent().uninstallPlugin, self)
|
|
224
225
|
|
|
225
226
|
@property
|
|
226
227
|
def installed(self):
|
|
@@ -277,6 +278,12 @@ class PluginManagerPanel(wx.Panel, handlers.ThemeMixin):
|
|
|
277
278
|
self.Layout()
|
|
278
279
|
self.splitter.SetSashPosition(1, True)
|
|
279
280
|
self.theme = theme.app
|
|
281
|
+
|
|
282
|
+
def updateInfo(self):
|
|
283
|
+
# refresh all list items
|
|
284
|
+
self.pluginList.updateInfo()
|
|
285
|
+
# set current plugin again to refresh view
|
|
286
|
+
self.pluginViewer.info = self.pluginViewer.info
|
|
280
287
|
|
|
281
288
|
def _applyAppTheme(self):
|
|
282
289
|
# Set colors
|
|
@@ -318,9 +325,21 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
318
325
|
# self.activeBtn.Bind(wx.EVT_BUTTON, self.onToggleActivate)
|
|
319
326
|
# self.btnSizer.Add(self.activeBtn, border=3, flag=wx.ALL | wx.ALIGN_RIGHT)
|
|
320
327
|
# Add install button
|
|
321
|
-
self.installBtn = wx.Button(self)
|
|
328
|
+
self.installBtn = wx.Button(self, label=_translate("Install"))
|
|
329
|
+
self.installBtn.SetBitmap(
|
|
330
|
+
icons.ButtonIcon("download", 16).bitmap
|
|
331
|
+
)
|
|
332
|
+
self.installBtn.SetBitmapMargins(6, 3)
|
|
322
333
|
self.installBtn.Bind(wx.EVT_BUTTON, self.onInstall)
|
|
323
334
|
self.btnSizer.Add(self.installBtn, border=3, flag=wx.ALL | wx.ALIGN_RIGHT)
|
|
335
|
+
# # add uninstall button
|
|
336
|
+
# self.uninstallBtn = wx.Button(self, label=_translate("Uninstall"))
|
|
337
|
+
# self.uninstallBtn.SetBitmap(
|
|
338
|
+
# icons.ButtonIcon("delete", 16).bitmap
|
|
339
|
+
# )
|
|
340
|
+
# self.uninstallBtn.SetBitmapMargins(6, 3)
|
|
341
|
+
# self.uninstallBtn.Bind(wx.EVT_BUTTON, self.onUninstall)
|
|
342
|
+
# self.btnSizer.Add(self.uninstallBtn, border=3, flag=wx.ALL | wx.EXPAND)
|
|
324
343
|
|
|
325
344
|
# Map to onclick function
|
|
326
345
|
self.Bind(wx.EVT_LEFT_DOWN, self.onSelect)
|
|
@@ -329,10 +348,8 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
329
348
|
# Bind navigation
|
|
330
349
|
self.Bind(wx.EVT_NAVIGATION_KEY, self.onNavigation)
|
|
331
350
|
|
|
332
|
-
# Handle version mismatch
|
|
333
|
-
self.installBtn.Enable(__version__ in self.info.version)
|
|
334
|
-
|
|
335
351
|
self._applyAppTheme()
|
|
352
|
+
self.markInstalled(self.info.installed)
|
|
336
353
|
|
|
337
354
|
@property
|
|
338
355
|
def viewer(self):
|
|
@@ -341,6 +358,10 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
341
358
|
"""
|
|
342
359
|
return self.parent.viewer
|
|
343
360
|
|
|
361
|
+
def updateInfo(self):
|
|
362
|
+
# update install state
|
|
363
|
+
self.markInstalled(self.info.installed)
|
|
364
|
+
|
|
344
365
|
def _applyAppTheme(self):
|
|
345
366
|
# Set label fonts
|
|
346
367
|
from psychopy.app.themes import fonts
|
|
@@ -429,14 +450,40 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
429
450
|
active=active
|
|
430
451
|
)
|
|
431
452
|
|
|
432
|
-
def
|
|
433
|
-
|
|
453
|
+
def _doInstall(self):
|
|
454
|
+
"""Routine to run the installation of a package after the `onInstall`
|
|
455
|
+
event is processed.
|
|
456
|
+
"""
|
|
457
|
+
# mark as pending
|
|
434
458
|
self.markInstalled(None)
|
|
435
|
-
#
|
|
459
|
+
# install
|
|
436
460
|
self.info.install()
|
|
437
|
-
#
|
|
461
|
+
# mark according to install success
|
|
462
|
+
self.markInstalled(self.info.installed)
|
|
463
|
+
|
|
464
|
+
def _doUninstall(self):
|
|
465
|
+
# mark as pending
|
|
466
|
+
self.markInstalled(None)
|
|
467
|
+
# uninstall
|
|
468
|
+
self.info.uninstall()
|
|
469
|
+
# mark according to uninstall success
|
|
438
470
|
self.markInstalled(self.info.installed)
|
|
439
471
|
|
|
472
|
+
def onInstall(self, evt=None):
|
|
473
|
+
"""Event called when the install button is clicked.
|
|
474
|
+
"""
|
|
475
|
+
wx.CallAfter(self._doInstall) # call after processing button events
|
|
476
|
+
if evt is not None and hasattr(evt, 'Skip'):
|
|
477
|
+
evt.Skip()
|
|
478
|
+
|
|
479
|
+
def onUninstall(self, evt=None):
|
|
480
|
+
"""
|
|
481
|
+
Event called when the uninstall button is clicked.
|
|
482
|
+
"""
|
|
483
|
+
wx.CallAfter(self._doUninstall) # call after processing button events
|
|
484
|
+
if evt is not None and hasattr(evt, 'Skip'):
|
|
485
|
+
evt.Skip()
|
|
486
|
+
|
|
440
487
|
def onToggleActivate(self, evt=None):
|
|
441
488
|
if self.info.active:
|
|
442
489
|
self.onDeactivate(evt=evt)
|
|
@@ -491,6 +538,14 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
491
538
|
)
|
|
492
539
|
self.sizer.Add(self.errorCtrl, proportion=1, border=3, flag=wx.ALL | wx.EXPAND)
|
|
493
540
|
self.errorCtrl.Hide()
|
|
541
|
+
# add button to uninstall all
|
|
542
|
+
self.uninstallAllBtn = wx.Button(self, label=_translate("Uninstall all plugins"))
|
|
543
|
+
self.uninstallAllBtn.SetBitmap(
|
|
544
|
+
icons.ButtonIcon("delete", 16).bitmap
|
|
545
|
+
)
|
|
546
|
+
self.uninstallAllBtn.SetBitmapMargins(6, 3)
|
|
547
|
+
self.uninstallAllBtn.Bind(wx.EVT_BUTTON, self.onUninstallAll)
|
|
548
|
+
self.sizer.Add(self.uninstallAllBtn, border=12, flag=wx.ALL | wx.CENTER)
|
|
494
549
|
|
|
495
550
|
# Bind deselect
|
|
496
551
|
self.Bind(wx.EVT_LEFT_DOWN, self.onDeselect)
|
|
@@ -519,6 +574,10 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
519
574
|
# layout
|
|
520
575
|
self.Layout()
|
|
521
576
|
self.SetupScrolling()
|
|
577
|
+
|
|
578
|
+
def updateInfo(self):
|
|
579
|
+
for item in self.items:
|
|
580
|
+
item.updateInfo()
|
|
522
581
|
|
|
523
582
|
def search(self, evt=None):
|
|
524
583
|
searchTerm = self.searchCtrl.GetValue().strip()
|
|
@@ -571,6 +630,31 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
|
|
|
571
630
|
def onClick(self, evt=None):
|
|
572
631
|
self.SetFocusIgnoringChildren()
|
|
573
632
|
self.viewer.info = None
|
|
633
|
+
|
|
634
|
+
def onUninstallAll(self, evt=None):
|
|
635
|
+
"""
|
|
636
|
+
Called when the "Uninstall all" button is clicked
|
|
637
|
+
"""
|
|
638
|
+
# warn user that they'll delete the packages folder
|
|
639
|
+
dlg = wx.MessageDialog(
|
|
640
|
+
self,
|
|
641
|
+
message=_translate("This will uninstall all plugins an additional packages you have installed, are you sure you want to continue?"),
|
|
642
|
+
style=wx.ICON_WARNING | wx.YES | wx.NO
|
|
643
|
+
)
|
|
644
|
+
if dlg.ShowModal() != wx.ID_YES:
|
|
645
|
+
# cancel if they didn't explicitly say yes
|
|
646
|
+
return
|
|
647
|
+
# delete the packages folder
|
|
648
|
+
shutil.rmtree(prefs.paths['packages'])
|
|
649
|
+
# print success
|
|
650
|
+
dlg = wx.MessageDialog(
|
|
651
|
+
self,
|
|
652
|
+
message=_translate("All plugins and additional packages have been uninstalled. You will need to restart PsychoPy for this to take effect."),
|
|
653
|
+
style=wx.ICON_INFORMATION | wx.OK
|
|
654
|
+
)
|
|
655
|
+
dlg.ShowModal()
|
|
656
|
+
# close dialog
|
|
657
|
+
self.GetTopLevelParent().Close()
|
|
574
658
|
|
|
575
659
|
def setSelection(self, item):
|
|
576
660
|
"""
|
|
@@ -672,14 +756,22 @@ class PluginDetailsPanel(wx.Panel, handlers.ThemeMixin):
|
|
|
672
756
|
# Buttons
|
|
673
757
|
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
674
758
|
self.titleSizer.Add(self.buttonSizer, flag=wx.EXPAND)
|
|
675
|
-
#
|
|
676
|
-
self.installBtn = wx.Button(self)
|
|
759
|
+
# install btn
|
|
760
|
+
self.installBtn = wx.Button(self, label=_translate("Install"))
|
|
761
|
+
self.installBtn.SetBitmap(
|
|
762
|
+
icons.ButtonIcon("download", 16).bitmap
|
|
763
|
+
)
|
|
764
|
+
self.installBtn.SetBitmapMargins(6, 3)
|
|
677
765
|
self.installBtn.Bind(wx.EVT_BUTTON, self.onInstall)
|
|
678
766
|
self.buttonSizer.Add(self.installBtn, border=3, flag=wx.ALL | wx.EXPAND)
|
|
679
|
-
#
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
767
|
+
# uninstall btn
|
|
768
|
+
self.uninstallBtn = wx.Button(self, label=_translate("Uninstall"))
|
|
769
|
+
self.uninstallBtn.SetBitmap(
|
|
770
|
+
icons.ButtonIcon("delete", 16).bitmap
|
|
771
|
+
)
|
|
772
|
+
self.uninstallBtn.SetBitmapMargins(6, 3)
|
|
773
|
+
self.uninstallBtn.Bind(wx.EVT_BUTTON, self.onUninstall)
|
|
774
|
+
self.buttonSizer.Add(self.uninstallBtn, border=3, flag=wx.ALL | wx.EXPAND)
|
|
683
775
|
# Homepage btn
|
|
684
776
|
self.homepageBtn = wx.Button(self, label=_translate("Homepage"))
|
|
685
777
|
self.homepageBtn.Bind(wx.EVT_BUTTON, self.onHomepage)
|
|
@@ -779,11 +871,19 @@ class PluginDetailsPanel(wx.Panel, handlers.ThemeMixin):
|
|
|
779
871
|
"""Routine to run the installation of a package after the `onInstall`
|
|
780
872
|
event is processed.
|
|
781
873
|
"""
|
|
782
|
-
#
|
|
874
|
+
# mark as pending
|
|
783
875
|
self.markInstalled(None)
|
|
784
|
-
#
|
|
876
|
+
# install
|
|
785
877
|
self.info.install()
|
|
786
|
-
#
|
|
878
|
+
# mark according to install success
|
|
879
|
+
self.markInstalled(self.info.installed)
|
|
880
|
+
|
|
881
|
+
def _doUninstall(self):
|
|
882
|
+
# mark as pending
|
|
883
|
+
self.markInstalled(None)
|
|
884
|
+
# uninstall
|
|
885
|
+
self.info.uninstall()
|
|
886
|
+
# mark according to uninstall success
|
|
787
887
|
self.markInstalled(self.info.installed)
|
|
788
888
|
|
|
789
889
|
def onInstall(self, evt=None):
|
|
@@ -792,7 +892,15 @@ class PluginDetailsPanel(wx.Panel, handlers.ThemeMixin):
|
|
|
792
892
|
wx.CallAfter(self._doInstall) # call after processing button events
|
|
793
893
|
if evt is not None and hasattr(evt, 'Skip'):
|
|
794
894
|
evt.Skip()
|
|
795
|
-
|
|
895
|
+
|
|
896
|
+
def onUninstall(self, evt=None):
|
|
897
|
+
"""
|
|
898
|
+
Event called when the uninstall button is clicked.
|
|
899
|
+
"""
|
|
900
|
+
wx.CallAfter(self._doUninstall) # call after processing button events
|
|
901
|
+
if evt is not None and hasattr(evt, 'Skip'):
|
|
902
|
+
evt.Skip()
|
|
903
|
+
|
|
796
904
|
def _doActivate(self, state=True):
|
|
797
905
|
"""Activate a plugin or package after the `onActivate` event.
|
|
798
906
|
|
|
@@ -1031,69 +1139,39 @@ def markInstalled(pluginItem, pluginPanel, installed=True):
|
|
|
1031
1139
|
installed : bool or None
|
|
1032
1140
|
True if installed, False if not installed, None if pending/unclear
|
|
1033
1141
|
"""
|
|
1034
|
-
def _setAllBitmaps(btn, bmp):
|
|
1035
|
-
"""
|
|
1036
|
-
Set all bitmaps (enabled, disabled, focus, unfocus, etc.) for a button
|
|
1037
|
-
"""
|
|
1038
|
-
btn.SetBitmap(bmp)
|
|
1039
|
-
btn.SetBitmapDisabled(bmp)
|
|
1040
|
-
btn.SetBitmapPressed(bmp)
|
|
1041
|
-
btn.SetBitmapCurrent(bmp)
|
|
1042
|
-
btn.SetBitmapMargins(6, 3)
|
|
1043
1142
|
|
|
1044
|
-
#
|
|
1143
|
+
# update plugin item
|
|
1045
1144
|
if pluginItem:
|
|
1046
1145
|
if installed is None:
|
|
1047
|
-
#
|
|
1048
|
-
pluginItem.installBtn.
|
|
1049
|
-
pluginItem.installBtn.SetLabel("...")
|
|
1050
|
-
_setAllBitmaps(pluginItem.installBtn, icons.ButtonIcon("view-refresh", 16).bitmap)
|
|
1051
|
-
# Hide active button while pending
|
|
1052
|
-
# pluginItem.activeBtn.Hide()
|
|
1146
|
+
# if pending, hide both buttons
|
|
1147
|
+
pluginItem.installBtn.Hide()
|
|
1053
1148
|
elif installed:
|
|
1054
|
-
#
|
|
1149
|
+
# if installed, hide install button
|
|
1055
1150
|
pluginItem.installBtn.Hide()
|
|
1056
|
-
# Show active button when installed
|
|
1057
|
-
# pluginItem.activeBtn.Show()
|
|
1058
1151
|
else:
|
|
1059
|
-
#
|
|
1152
|
+
# if not installed, show "Install" and download icon
|
|
1060
1153
|
pluginItem.installBtn.Show()
|
|
1061
|
-
|
|
1062
|
-
_setAllBitmaps(pluginItem.installBtn, icons.ButtonIcon("download", 16).bitmap)
|
|
1063
|
-
# Hide active button when not installed
|
|
1064
|
-
# pluginItem.activeBtn.Hide()
|
|
1065
|
-
# Refresh buttons
|
|
1154
|
+
# refresh buttons
|
|
1066
1155
|
pluginItem.Update()
|
|
1067
1156
|
pluginItem.Layout()
|
|
1068
1157
|
|
|
1069
|
-
#
|
|
1158
|
+
# update panel (if applicable)
|
|
1070
1159
|
if pluginPanel and pluginItem and pluginPanel.info == pluginItem.info:
|
|
1071
1160
|
if installed is None:
|
|
1072
|
-
#
|
|
1073
|
-
pluginPanel.installBtn.
|
|
1074
|
-
pluginPanel.
|
|
1075
|
-
pluginPanel.installBtn.SetLabel("...")
|
|
1076
|
-
_setAllBitmaps(pluginPanel.installBtn, icons.ButtonIcon("view-refresh", 16).bitmap)
|
|
1077
|
-
# Hide active button while pending
|
|
1078
|
-
# pluginPanel.activeBtn.Hide()
|
|
1161
|
+
# if pending, show elipsis and refresh icon
|
|
1162
|
+
pluginPanel.installBtn.Hide()
|
|
1163
|
+
pluginPanel.uninstallBtn.Hide()
|
|
1079
1164
|
elif installed:
|
|
1080
|
-
#
|
|
1081
|
-
pluginPanel.installBtn.
|
|
1082
|
-
pluginPanel.
|
|
1083
|
-
pluginPanel.installBtn.SetLabelText(_translate("Installed"))
|
|
1084
|
-
_setAllBitmaps(pluginPanel.installBtn, icons.ButtonIcon("greytick", 16).bitmap)
|
|
1085
|
-
# Show active button when installed
|
|
1086
|
-
# pluginPanel.activeBtn.Show()
|
|
1165
|
+
# if installed, show as installed with tick
|
|
1166
|
+
pluginPanel.installBtn.Hide()
|
|
1167
|
+
pluginPanel.uninstallBtn.Show()
|
|
1087
1168
|
else:
|
|
1088
|
-
#
|
|
1169
|
+
# if not installed, show "Install" and download icon
|
|
1089
1170
|
pluginPanel.installBtn.Show()
|
|
1090
|
-
pluginPanel.
|
|
1091
|
-
|
|
1092
|
-
_setAllBitmaps(pluginPanel.installBtn, icons.ButtonIcon("download", 16).bitmap)
|
|
1093
|
-
# Hide active button when not installed
|
|
1094
|
-
# pluginPanel.activeBtn.Hide()
|
|
1095
|
-
# Refresh buttons
|
|
1171
|
+
pluginPanel.uninstallBtn.Hide()
|
|
1172
|
+
# refresh buttons
|
|
1096
1173
|
pluginPanel.Update()
|
|
1174
|
+
pluginPanel.Layout()
|
|
1097
1175
|
|
|
1098
1176
|
|
|
1099
1177
|
def markActive(pluginItem, pluginPanel, active=True):
|
|
@@ -1260,7 +1338,7 @@ def getAllPluginDetails():
|
|
|
1260
1338
|
return None
|
|
1261
1339
|
# attempt to parse JSON
|
|
1262
1340
|
try:
|
|
1263
|
-
with srcFile.open("r", encoding="utf-8") as f:
|
|
1341
|
+
with srcFile.open("r", encoding="utf-8", errors="ignore") as f:
|
|
1264
1342
|
return json.load(f)
|
|
1265
1343
|
except json.decoder.JSONDecodeError:
|
|
1266
1344
|
# if JSON parse fails, return nothing
|
psychopy/app/preferencesDlg.py
CHANGED
|
@@ -15,7 +15,7 @@ from psychopy.app.themes import icons
|
|
|
15
15
|
from . import dialogs
|
|
16
16
|
from psychopy import localization, prefs
|
|
17
17
|
from psychopy.localization import _translate
|
|
18
|
-
from
|
|
18
|
+
from packaging.version import Version
|
|
19
19
|
from psychopy import sound
|
|
20
20
|
from psychopy.app.utils import getSystemFonts
|
|
21
21
|
import collections
|
|
@@ -190,7 +190,7 @@ class PrefPropGrid(wx.Panel):
|
|
|
190
190
|
self.helpText[name] = helpText
|
|
191
191
|
|
|
192
192
|
def addDirItem(self, section, label=wx.propgrid.PG_LABEL,
|
|
193
|
-
|
|
193
|
+
name=wx.propgrid.PG_LABEL, value='', helpText=""):
|
|
194
194
|
if section not in self.sections.keys():
|
|
195
195
|
self.sections[section] = dict()
|
|
196
196
|
|
|
@@ -430,12 +430,12 @@ class PreferencesDlg(wx.Dialog):
|
|
|
430
430
|
# get sound devices for "audioDevice" property
|
|
431
431
|
try:
|
|
432
432
|
devnames = sorted(sound.getDevices('output'))
|
|
433
|
-
except (ValueError, OSError, ImportError):
|
|
433
|
+
except (ValueError, OSError, ImportError, AttributeError):
|
|
434
434
|
devnames = []
|
|
435
435
|
|
|
436
436
|
audioConf = self.prefsCfg['hardware']['audioDevice']
|
|
437
437
|
self.audioDevDefault = audioConf \
|
|
438
|
-
if type(audioConf)
|
|
438
|
+
if type(audioConf) is list else list(audioConf)
|
|
439
439
|
self.audioDevNames = [
|
|
440
440
|
dev.replace('\r\n', '') for dev in devnames
|
|
441
441
|
if dev != self.audioDevDefault]
|
|
@@ -479,7 +479,7 @@ class PreferencesDlg(wx.Dialog):
|
|
|
479
479
|
hint = hints[-1].lstrip().lstrip('#').lstrip()
|
|
480
480
|
helpText = _translate(hint)
|
|
481
481
|
|
|
482
|
-
if type(thisPref)
|
|
482
|
+
if type(thisPref) is bool:
|
|
483
483
|
# only True or False - use a checkbox
|
|
484
484
|
self.proPrefs.addBoolItem(
|
|
485
485
|
sectionName, pLabel, prefName, thisPref,
|
|
@@ -695,7 +695,7 @@ class PreferencesDlg(wx.Dialog):
|
|
|
695
695
|
try:
|
|
696
696
|
# if thisPref is not a null string, do eval() to get a
|
|
697
697
|
# list.
|
|
698
|
-
if thisPref == '' or type(thisPref)
|
|
698
|
+
if thisPref == '' or type(thisPref) is list:
|
|
699
699
|
newVal = thisPref
|
|
700
700
|
else:
|
|
701
701
|
newVal = eval(thisPref)
|
|
@@ -713,7 +713,7 @@ class PreferencesDlg(wx.Dialog):
|
|
|
713
713
|
title=title)
|
|
714
714
|
warnDlg.ShowModal()
|
|
715
715
|
return
|
|
716
|
-
if type(newVal)
|
|
716
|
+
if type(newVal) is not list:
|
|
717
717
|
self.prefsCfg[sectionName][prefName] = [newVal]
|
|
718
718
|
else:
|
|
719
719
|
self.prefsCfg[sectionName][prefName] = newVal
|
|
@@ -781,7 +781,7 @@ class PreferencesDlg(wx.Dialog):
|
|
|
781
781
|
|
|
782
782
|
if __name__ == '__main__':
|
|
783
783
|
from psychopy import preferences
|
|
784
|
-
if
|
|
784
|
+
if Version(wx.__version__) < Version('2.9'):
|
|
785
785
|
app = wx.PySimpleApp()
|
|
786
786
|
else:
|
|
787
787
|
app = wx.App(False)
|
psychopy/app/psychopyApp.py
CHANGED
|
@@ -73,17 +73,23 @@ Options:
|
|
|
73
73
|
('| packaged by conda-forge |' in sys.version or
|
|
74
74
|
'|Anaconda' in sys.version)):
|
|
75
75
|
|
|
76
|
-
# On macOS with Anaconda, GUI applications need to be run using
|
|
76
|
+
# On macOS with Anaconda, GUI applications used to need to be run using
|
|
77
77
|
# `pythonw`. Since we have no way to determine whether this is currently
|
|
78
78
|
# the case, we run this script again -- ensuring we're definitely using
|
|
79
79
|
# pythonw.
|
|
80
80
|
import os
|
|
81
81
|
env = os.environ
|
|
82
82
|
PYTHONW = env.get('PYTHONW', 'False')
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
pyw_exe = sys.executable + 'w'
|
|
84
|
+
|
|
85
|
+
# Updated 2024.1.6: as of Python 3, `pythonw` and `python` can be used
|
|
86
|
+
# interchangeably for wxPython applications on macOS with GUI support.
|
|
87
|
+
# The defaults and conda-forge channels no longer install python with a
|
|
88
|
+
# framework build (to do so: `conda install python=3.8 python.app`).
|
|
89
|
+
# Therefore `pythonw` often doesn't exist, and we can just use `python`.
|
|
90
|
+
if PYTHONW != 'True' and os.path.isfile(pyw_exe):
|
|
85
91
|
from psychopy import core
|
|
86
|
-
cmd = [
|
|
92
|
+
cmd = [pyw_exe, __file__]
|
|
87
93
|
if '--no-splash' in sys.argv:
|
|
88
94
|
cmd.append('--no-splash')
|
|
89
95
|
|
|
@@ -100,4 +106,4 @@ Options:
|
|
|
100
106
|
|
|
101
107
|
|
|
102
108
|
if __name__ == '__main__':
|
|
103
|
-
main()
|
|
109
|
+
main()
|
psychopy/app/ribbon.py
CHANGED
|
@@ -55,6 +55,52 @@ class FrameRibbon(wx.Panel, handlers.ThemeMixin):
|
|
|
55
55
|
|
|
56
56
|
return sct
|
|
57
57
|
|
|
58
|
+
def addPluginSections(self, group):
|
|
59
|
+
"""
|
|
60
|
+
Add any sections to the ribbon which are defined by plugins, targeting the given entry point group.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
group : str
|
|
65
|
+
Entry point group to look for plugin sections in.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
list[FrameRubbinPluginSection]
|
|
70
|
+
List of section objects which were added
|
|
71
|
+
"""
|
|
72
|
+
from importlib import metadata
|
|
73
|
+
# start off with no entry points or sections
|
|
74
|
+
entryPoints = []
|
|
75
|
+
sections = []
|
|
76
|
+
# iterate through all entry point groups
|
|
77
|
+
for thisGroup, eps in metadata.entry_points().items():
|
|
78
|
+
# get entry points for matching group
|
|
79
|
+
if thisGroup == group:
|
|
80
|
+
# add to list of all entry points
|
|
81
|
+
entryPoints += eps
|
|
82
|
+
# iterate through found entry points
|
|
83
|
+
for ep in entryPoints:
|
|
84
|
+
try:
|
|
85
|
+
# load (import) module
|
|
86
|
+
cls = ep.load()
|
|
87
|
+
except:
|
|
88
|
+
# if failed for any reason, skip it
|
|
89
|
+
continue
|
|
90
|
+
# if the target is not a subclass of FrameRibbonPluginSection, discard it
|
|
91
|
+
if not isinstance(cls, type) or not issubclass(cls, FrameRibbonPluginSection):
|
|
92
|
+
continue
|
|
93
|
+
# if it's a section, add it
|
|
94
|
+
sct = cls(parent=self)
|
|
95
|
+
self.sections[sct.name] = sct
|
|
96
|
+
sections.append(sct)
|
|
97
|
+
# add to sizer
|
|
98
|
+
self.sizer.Add(sct, border=0, flag=wx.EXPAND | wx.ALL)
|
|
99
|
+
# add separator
|
|
100
|
+
self.addSeparator()
|
|
101
|
+
|
|
102
|
+
return sections
|
|
103
|
+
|
|
58
104
|
def addButton(self, section, name, label="", icon=None, tooltip="", callback=None,
|
|
59
105
|
style=wx.BU_NOTEXT):
|
|
60
106
|
"""
|
|
@@ -244,13 +290,14 @@ class FrameRibbonSection(wx.Panel, handlers.ThemeMixin):
|
|
|
244
290
|
self.label, flag=wx.EXPAND
|
|
245
291
|
)
|
|
246
292
|
|
|
247
|
-
|
|
248
293
|
# add space
|
|
249
294
|
self.border.AddSpacer(6)
|
|
250
295
|
|
|
251
296
|
# dict in which to store buttons
|
|
252
297
|
self.buttons = {}
|
|
253
298
|
|
|
299
|
+
self._applyAppTheme()
|
|
300
|
+
|
|
254
301
|
def addButton(self, name, label="", icon=None, tooltip="", callback=None, style=wx.BU_NOTEXT):
|
|
255
302
|
"""
|
|
256
303
|
Add a button to this section.
|
|
@@ -362,9 +409,34 @@ class FrameRibbonSection(wx.Panel, handlers.ThemeMixin):
|
|
|
362
409
|
return btn
|
|
363
410
|
|
|
364
411
|
def _applyAppTheme(self):
|
|
412
|
+
# set color
|
|
365
413
|
self.SetBackgroundColour(colors.app['frame_bg'])
|
|
366
|
-
|
|
414
|
+
self.SetForegroundColour(colors.app['text'])
|
|
415
|
+
# set bitmaps again
|
|
416
|
+
self._icon.reload()
|
|
367
417
|
self.icon.SetBitmap(self._icon.bitmap)
|
|
418
|
+
# refresh
|
|
419
|
+
self.Refresh()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class FrameRibbonPluginSection(FrameRibbonSection):
|
|
423
|
+
"""
|
|
424
|
+
Subclass of FrameRibbonSection specifically for adding sections to the ribbon via plugins. To
|
|
425
|
+
add a section, create a subclass of FrameRibbonPluginSection in your plugin and add any buttons
|
|
426
|
+
you want it to have in the `__init__` function. Then give it an entry point in either
|
|
427
|
+
"psychopy.app.builder", "psychopy.app.coder" or "psychopy.app.runner" to tell PsychoPy which
|
|
428
|
+
frame to add it to.
|
|
429
|
+
"""
|
|
430
|
+
def __init__(self, parent, name, label=None):
|
|
431
|
+
# if not given a label, use name
|
|
432
|
+
if label is None:
|
|
433
|
+
label = name
|
|
434
|
+
# store name
|
|
435
|
+
self.name = name
|
|
436
|
+
# initialise subclass
|
|
437
|
+
FrameRibbonSection.__init__(
|
|
438
|
+
self, parent, label=label, icon="plugin"
|
|
439
|
+
)
|
|
368
440
|
|
|
369
441
|
|
|
370
442
|
class FrameRibbonButton(wx.Button, handlers.ThemeMixin):
|
|
@@ -402,11 +474,8 @@ class FrameRibbonButton(wx.Button, handlers.ThemeMixin):
|
|
|
402
474
|
tooltip = f"{label}: {tooltip}"
|
|
403
475
|
self.SetToolTip(tooltip)
|
|
404
476
|
# set icon
|
|
477
|
+
self._icon = icons.ButtonIcon(icon, size=32)
|
|
405
478
|
bmpStyle = style & (wx.TOP | wx.BOTTOM | wx.LEFT | wx.RIGHT)
|
|
406
|
-
self.SetBitmap(
|
|
407
|
-
icons.ButtonIcon(icon, size=32).bitmap,
|
|
408
|
-
dir=bmpStyle or wx.TOP
|
|
409
|
-
)
|
|
410
479
|
# if given, bind callback
|
|
411
480
|
if callback is not None:
|
|
412
481
|
self.Bind(wx.EVT_BUTTON, callback)
|
|
@@ -414,8 +483,20 @@ class FrameRibbonButton(wx.Button, handlers.ThemeMixin):
|
|
|
414
483
|
self.Bind(wx.EVT_ENTER_WINDOW, self.onHover)
|
|
415
484
|
self.Bind(wx.EVT_LEAVE_WINDOW, self.onHover)
|
|
416
485
|
|
|
486
|
+
self._applyAppTheme()
|
|
487
|
+
|
|
417
488
|
def _applyAppTheme(self):
|
|
489
|
+
# set color
|
|
418
490
|
self.SetBackgroundColour(colors.app['frame_bg'])
|
|
491
|
+
self.SetForegroundColour(colors.app['text'])
|
|
492
|
+
# set bitmaps again
|
|
493
|
+
self._icon.reload()
|
|
494
|
+
self.SetBitmap(self._icon.bitmap)
|
|
495
|
+
self.SetBitmapCurrent(self._icon.bitmap)
|
|
496
|
+
self.SetBitmapPressed(self._icon.bitmap)
|
|
497
|
+
self.SetBitmapFocus(self._icon.bitmap)
|
|
498
|
+
# refresh
|
|
499
|
+
self.Refresh()
|
|
419
500
|
|
|
420
501
|
def onHover(self, evt):
|
|
421
502
|
if evt.EventType == wx.EVT_ENTER_WINDOW.typeId:
|
|
@@ -437,9 +518,7 @@ class FrameRibbonDropdownButton(wx.Panel, handlers.ThemeMixin):
|
|
|
437
518
|
self.button = wx.Button(self, label=label, style=wx.BORDER_NONE)
|
|
438
519
|
self.sizer.Add(self.button, proportion=1, border=0, flag=wx.EXPAND | wx.ALL)
|
|
439
520
|
# set icon
|
|
440
|
-
self.
|
|
441
|
-
icons.ButtonIcon(icon, size=32).bitmap
|
|
442
|
-
)
|
|
521
|
+
self._icon = icons.ButtonIcon(icon, size=32)
|
|
443
522
|
# bind button callback
|
|
444
523
|
if callback is not None:
|
|
445
524
|
self.button.Bind(wx.EVT_BUTTON, callback)
|
|
@@ -457,6 +536,8 @@ class FrameRibbonDropdownButton(wx.Panel, handlers.ThemeMixin):
|
|
|
457
536
|
self.drop.Bind(wx.EVT_ENTER_WINDOW, self.onHover)
|
|
458
537
|
self.drop.Bind(wx.EVT_LEAVE_WINDOW, self.onHover)
|
|
459
538
|
|
|
539
|
+
self._applyAppTheme()
|
|
540
|
+
|
|
460
541
|
def onMenu(self, evt):
|
|
461
542
|
menu = self.menu
|
|
462
543
|
# skip if there's no menu
|
|
@@ -469,9 +550,18 @@ class FrameRibbonDropdownButton(wx.Panel, handlers.ThemeMixin):
|
|
|
469
550
|
self.PopupMenu(menu)
|
|
470
551
|
|
|
471
552
|
def _applyAppTheme(self):
|
|
472
|
-
|
|
473
|
-
self.button.
|
|
474
|
-
|
|
553
|
+
# set color
|
|
554
|
+
for obj in (self, self.button, self.drop):
|
|
555
|
+
obj.SetBackgroundColour(colors.app['frame_bg'])
|
|
556
|
+
obj.SetForegroundColour(colors.app['text'])
|
|
557
|
+
# set bitmaps again
|
|
558
|
+
self._icon.reload()
|
|
559
|
+
self.button.SetBitmap(self._icon.bitmap)
|
|
560
|
+
self.button.SetBitmapCurrent(self._icon.bitmap)
|
|
561
|
+
self.button.SetBitmapPressed(self._icon.bitmap)
|
|
562
|
+
self.button.SetBitmapFocus(self._icon.bitmap)
|
|
563
|
+
# refresh
|
|
564
|
+
self.Refresh()
|
|
475
565
|
|
|
476
566
|
def onHover(self, evt):
|
|
477
567
|
if evt.EventType == wx.EVT_ENTER_WINDOW.typeId:
|
|
@@ -676,6 +766,16 @@ class PavloviaUserCtrl(FrameRibbonDropdownButton):
|
|
|
676
766
|
# bind deletion behaviour
|
|
677
767
|
self.Bind(wx.EVT_WINDOW_DESTROY, self.onDelete)
|
|
678
768
|
|
|
769
|
+
self._applyAppTheme()
|
|
770
|
+
|
|
771
|
+
def _applyAppTheme(self):
|
|
772
|
+
# set color
|
|
773
|
+
for obj in (self, self.button, self.drop):
|
|
774
|
+
obj.SetBackgroundColour(colors.app['frame_bg'])
|
|
775
|
+
obj.SetForegroundColour(colors.app['text'])
|
|
776
|
+
# refresh
|
|
777
|
+
self.Refresh()
|
|
778
|
+
|
|
679
779
|
def onDelete(self, evt=None):
|
|
680
780
|
i = self.frame.app.pavloviaButtons['user'].index(self)
|
|
681
781
|
self.frame.app.pavloviaButtons['user'].pop(i)
|
|
@@ -803,6 +903,16 @@ class PavloviaProjectCtrl(FrameRibbonDropdownButton):
|
|
|
803
903
|
# bind deletion behaviour
|
|
804
904
|
self.Bind(wx.EVT_WINDOW_DESTROY, self.onDelete)
|
|
805
905
|
|
|
906
|
+
self._applyAppTheme()
|
|
907
|
+
|
|
908
|
+
def _applyAppTheme(self):
|
|
909
|
+
# set color
|
|
910
|
+
for obj in (self, self.button, self.drop):
|
|
911
|
+
obj.SetBackgroundColour(colors.app['frame_bg'])
|
|
912
|
+
obj.SetForegroundColour(colors.app['text'])
|
|
913
|
+
# refresh
|
|
914
|
+
self.Refresh()
|
|
915
|
+
|
|
806
916
|
def onDelete(self, evt=None):
|
|
807
917
|
i = self.frame.app.pavloviaButtons['project'].index(self)
|
|
808
918
|
self.frame.app.pavloviaButtons['project'].pop(i)
|
|
@@ -845,7 +955,7 @@ class PavloviaProjectCtrl(FrameRibbonDropdownButton):
|
|
|
845
955
|
# get project
|
|
846
956
|
project = self.GetTopLevelParent().project
|
|
847
957
|
|
|
848
|
-
if project is None:
|
|
958
|
+
if project is None or project['path_with_namespace'] is None:
|
|
849
959
|
self.button.SetLabel(_translate("No project"))
|
|
850
960
|
else:
|
|
851
961
|
self.button.SetLabel(project['path_with_namespace'])
|
|
@@ -895,7 +1005,7 @@ class PavloviaProjectCtrl(FrameRibbonDropdownButton):
|
|
|
895
1005
|
else:
|
|
896
1006
|
name = path = ""
|
|
897
1007
|
# open dlg to create new project
|
|
898
|
-
createDlg = sync.CreateDlg(self,
|
|
1008
|
+
createDlg = sync.CreateDlg(self.frame,
|
|
899
1009
|
user=pavlovia.getCurrentSession().user,
|
|
900
1010
|
name=name,
|
|
901
1011
|
path=path)
|