psychopy 2024.1.0__py3-none-any.whl → 2024.1.2__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.

Files changed (57) hide show
  1. psychopy/__init__.py +2 -2
  2. psychopy/alerts/_alerts.py +6 -2
  3. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -0
  4. psychopy/app/builder/builder.py +3 -0
  5. psychopy/app/builder/dialogs/paramCtrls.py +4 -2
  6. psychopy/app/builder/localizedStrings.py +16 -4
  7. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  8. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3548 -5422
  9. psychopy/app/plugin_manager/plugins.py +80 -57
  10. psychopy/app/preferencesDlg.py +2 -1
  11. psychopy/app/utils.py +2 -2
  12. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +552 -550
  13. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +572 -570
  14. psychopy/demos/coder/timing/timeByFrames.py +7 -1
  15. psychopy/experiment/components/_base.py +122 -0
  16. psychopy/experiment/components/aperture/__init__.py +6 -2
  17. psychopy/experiment/components/brush/__init__.py +3 -1
  18. psychopy/experiment/components/button/__init__.py +6 -2
  19. psychopy/experiment/components/buttonBox/__init__.py +2 -1
  20. psychopy/experiment/components/camera/__init__.py +13 -0
  21. psychopy/experiment/components/code/__init__.py +34 -1
  22. psychopy/experiment/components/form/formItems.xltx +0 -0
  23. psychopy/experiment/components/polygon/__init__.py +14 -3
  24. psychopy/experiment/components/settings/__init__.py +4 -1
  25. psychopy/experiment/components/sound/__init__.py +1 -1
  26. psychopy/experiment/components/text/__init__.py +1 -1
  27. psychopy/experiment/params.py +17 -0
  28. psychopy/experiment/routines/_base.py +122 -0
  29. psychopy/experiment/routines/counterbalance/__init__.py +1 -1
  30. psychopy/hardware/button.py +4 -3
  31. psychopy/hardware/camera/__init__.py +6 -0
  32. psychopy/hardware/keyboard.py +7 -2
  33. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +1 -1
  34. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -1
  35. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +1 -1
  36. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +1 -1
  37. psychopy/localization/generateTranslationTemplate.py +45 -19
  38. psychopy/localization/messages.pot +5049 -3418
  39. psychopy/preferences/Darwin.spec +2 -0
  40. psychopy/preferences/FreeBSD.spec +2 -0
  41. psychopy/preferences/Linux.spec +2 -0
  42. psychopy/preferences/Windows.spec +2 -0
  43. psychopy/preferences/baseNoArch.spec +2 -0
  44. psychopy/preferences/generateHints.py +2 -1
  45. psychopy/preferences/hints.py +118 -97
  46. psychopy/tools/fontmanager.py +1 -1
  47. psychopy/tools/versionchooser.py +1 -1
  48. psychopy/visual/noise.py +9 -0
  49. psychopy/visual/radial.py +8 -0
  50. psychopy/visual/secondorder.py +9 -0
  51. psychopy/visual/textbox2/textbox2.py +19 -21
  52. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/METADATA +2 -2
  53. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/RECORD +57 -57
  54. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/WHEEL +1 -1
  55. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/entry_points.txt +0 -0
  56. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/licenses/AUTHORS.md +0 -0
  57. {psychopy-2024.1.0.dist-info → psychopy-2024.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  import wx
2
4
  from wx.lib import scrolledpanel
3
5
  import webbrowser
@@ -479,6 +481,16 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
479
481
  self.sizer.Add(self.badItemLbl, border=9, flag=wx.ALL | wx.EXPAND)
480
482
  self.badItemSizer = wx.BoxSizer(wx.VERTICAL)
481
483
  self.sizer.Add(self.badItemSizer, border=3, flag=wx.ALL | wx.EXPAND)
484
+ # ctrl to display when plugins can't be retrieved
485
+ self.errorCtrl = utils.MarkdownCtrl(
486
+ self, value=_translate(
487
+ "Could not retrieve plugins. Try restarting the PsychoPy app and make sure you "
488
+ "are connected to the internet."
489
+ ),
490
+ style=wx.TE_READONLY
491
+ )
492
+ self.sizer.Add(self.errorCtrl, proportion=1, border=3, flag=wx.ALL | wx.EXPAND)
493
+ self.errorCtrl.Hide()
482
494
 
483
495
  # Bind deselect
484
496
  self.Bind(wx.EVT_LEFT_DOWN, self.onDeselect)
@@ -492,17 +504,19 @@ class PluginBrowserList(scrolledpanel.ScrolledPanel, handlers.ThemeMixin):
492
504
  self.initState[item.info.pipname] = {"installed": item.info.installed, "active": item.info.active}
493
505
 
494
506
  def populate(self):
495
- # Get all plugin details
507
+ # get all plugin details
496
508
  items = getAllPluginDetails()
497
- # Start off assuming no headings
509
+ # start off assuming no headings
498
510
  self.badItemLbl.Hide()
499
- # Put installed packages at top of list
511
+ # put installed packages at top of list
500
512
  items.sort(key=lambda obj: obj.installed, reverse=True)
501
513
  for item in items:
502
514
  item.setParent(self)
503
515
  self.appendItem(item)
504
-
505
- # Layout
516
+ # if we got no items, display error message
517
+ if not len(items):
518
+ self.errorCtrl.Show()
519
+ # layout
506
520
  self.Layout()
507
521
  self.SetupScrolling()
508
522
 
@@ -697,7 +711,7 @@ class PluginDetailsPanel(wx.Panel, handlers.ThemeMixin):
697
711
  # Add placeholder for when there's no plugin selected
698
712
  self.placeholder = utils.MarkdownCtrl(
699
713
  self, value=_translate("Select a plugin to view details."),
700
- style=wx.TE_MULTILINE | wx.BORDER_NONE | wx.TE_NO_VSCROLL
714
+ style=wx.TE_READONLY
701
715
  )
702
716
  self.border.Add(
703
717
  self.placeholder,
@@ -840,7 +854,6 @@ class PluginDetailsPanel(wx.Panel, handlers.ThemeMixin):
840
854
  self.sizer.ShowItems(value is not None)
841
855
  # Show/hide placeholder according to None
842
856
  self.placeholder.Show(value is None)
843
- self.placeholder.editBtn.Hide()
844
857
  # Handle None
845
858
  if value is None:
846
859
  value = PluginInfo(
@@ -1145,6 +1158,8 @@ def markActive(pluginItem, pluginPanel, active=True):
1145
1158
 
1146
1159
  # store plugin objects for later use
1147
1160
  _pluginObjects = None
1161
+ # persistent variable to keep track of whether we need to update plugins
1162
+ redownloadPlugins = True
1148
1163
 
1149
1164
 
1150
1165
  def getAllPluginDetails():
@@ -1175,7 +1190,7 @@ def getAllPluginDetails():
1175
1190
  pass
1176
1191
 
1177
1192
  # where the database is expected to be
1178
- pluginDatabaseFile = os.path.join(appPluginCacheDir, 'plugins.json')
1193
+ pluginDatabaseFile = Path(appPluginCacheDir) / "plugins.json"
1179
1194
 
1180
1195
  def downloadPluginDatabase(srcURL="https://psychopy.org/plugins.json"):
1181
1196
  """Downloads the plugin database from the server and returns the text
@@ -1188,23 +1203,35 @@ def getAllPluginDetails():
1188
1203
 
1189
1204
  Returns
1190
1205
  -------
1191
- str or None
1192
- The plugin database as a string, or None if the download failed.
1206
+ list or None
1207
+ The plugin database as a list, or None if the download failed.
1193
1208
 
1194
1209
  """
1210
+ global redownloadPlugins
1211
+ # if plugins already up to date, skip
1212
+ if not redownloadPlugins:
1213
+ return None
1214
+ # download database from website
1195
1215
  try:
1196
1216
  resp = requests.get(srcURL)
1197
- if resp.status_code == 404:
1198
- return None
1199
- value = resp.text
1200
- # confirm json is valid
1201
- try:
1202
- json.loads(value)
1203
- return value
1204
- except json.decoder.JSONDecodeError:
1205
- return None
1206
1217
  except requests.exceptions.ConnectionError:
1218
+ # if connection to website fails, return nothing
1219
+ return None
1220
+ # if download failed, return nothing
1221
+ if resp.status_code == 404:
1207
1222
  return None
1223
+ # otherwise get as a string
1224
+ value = resp.text
1225
+ # attempt to parse JSON
1226
+ try:
1227
+ database = json.loads(value)
1228
+ except json.decoder.JSONDecodeError:
1229
+ # if JSON parse fails, return nothing
1230
+ return None
1231
+ # if we made it this far, mark plugins as not needing update
1232
+ redownloadPlugins = False
1233
+
1234
+ return database
1208
1235
 
1209
1236
  def readLocalPluginDatabase(srcFile):
1210
1237
  """Read the local plugin database file (if it exists) and return the
@@ -1212,20 +1239,25 @@ def getAllPluginDetails():
1212
1239
 
1213
1240
  Parameters
1214
1241
  ----------
1215
- srcFile : str
1242
+ srcFile : pathlib.Path
1216
1243
  The expected path to the plugin database file.
1217
1244
 
1218
1245
  Returns
1219
1246
  -------
1220
- str or None
1221
- The plugin database as a string, or None if the file doesn't exist.
1247
+ list or None
1248
+ The plugin database as a list, or None if the file doesn't exist.
1222
1249
 
1223
1250
  """
1224
- if os.path.exists(srcFile):
1225
- with open(srcFile, 'r') as f:
1226
- return f.read()
1227
-
1228
- return None
1251
+ # if source file doesn't exist, return nothing
1252
+ if not srcFile.is_file():
1253
+ return None
1254
+ # attempt to parse JSON
1255
+ try:
1256
+ with srcFile.open("r", encoding="utf-8") as f:
1257
+ return json.load(f)
1258
+ except json.decoder.JSONDecodeError:
1259
+ # if JSON parse fails, return nothing
1260
+ return None
1229
1261
 
1230
1262
  def deletePluginDlgCache():
1231
1263
  """Delete the local plugin database file and cached files related to
@@ -1238,47 +1270,38 @@ def getAllPluginDetails():
1238
1270
 
1239
1271
  # get a copy of the plugin database from the server, check if it's newer
1240
1272
  # than the local copy, and if so, replace the local copy
1241
- refreshPlugins = False # database has changed
1242
- serverPluginDatabase = downloadPluginDatabase() # text
1243
- localPluginDatabase = readLocalPluginDatabase(pluginDatabaseFile) # text
1273
+
1274
+ # get remote database
1275
+ serverPluginDatabase = downloadPluginDatabase()
1276
+ # get local database
1277
+ localPluginDatabase = readLocalPluginDatabase(pluginDatabaseFile)
1278
+
1244
1279
  if serverPluginDatabase is not None:
1245
- if localPluginDatabase is None:
1280
+ # if we have a database from the remote, use it
1281
+ pluginDatabase = serverPluginDatabase
1282
+ # if the file contents has changed, delete cached icons and etc.
1283
+ if str(pluginDatabase) != str(localPluginDatabase):
1246
1284
  deletePluginDlgCache()
1247
- # write the new plugin database file
1248
- with open(pluginDatabaseFile, 'w') as f: # save the file
1249
- f.write(serverPluginDatabase)
1250
- localPluginDatabase = json.loads(serverPluginDatabase)
1251
- else:
1252
- # exists, but does it need updating?
1253
- localPluginDatabase = json.loads(localPluginDatabase)
1254
- serverPluginDatabase = json.loads(serverPluginDatabase)
1255
- if localPluginDatabase != serverPluginDatabase:
1256
- # clear the old cache
1257
- deletePluginDlgCache()
1258
- # write the new plugin database file
1259
- with open(pluginDatabaseFile, 'w') as f: # save the file
1260
- json.dump(serverPluginDatabase, f, indent=True)
1261
- localPluginDatabase = serverPluginDatabase
1262
- refreshPlugins = True
1285
+ # write new contents to file
1286
+ with pluginDatabaseFile.open("w", encoding='utf-8') as f:
1287
+ json.dump(pluginDatabase, f, indent=True)
1288
+
1289
+ elif localPluginDatabase is not None:
1290
+ # otherwise use cached
1291
+ pluginDatabase = localPluginDatabase
1263
1292
  else:
1264
- # no server connection, use local copy
1265
- if localPluginDatabase is None:
1266
- # no local copy, so no plugins
1267
- return []
1268
- else:
1269
- # use local copy
1270
- localPluginDatabase = json.loads(localPluginDatabase)
1271
- refreshPlugins = True
1293
+ # if we have neither, treat as blank list
1294
+ pluginDatabase = []
1272
1295
 
1273
1296
  # check if we need to update plugin objects, if not return the cached data
1274
1297
  global _pluginObjects
1275
- requiresRefresh = refreshPlugins or _pluginObjects is None
1298
+ requiresRefresh = _pluginObjects is None
1276
1299
  if not requiresRefresh:
1277
1300
  return _pluginObjects
1278
1301
 
1279
1302
  # Create PluginInfo objects from info list
1280
1303
  objs = []
1281
- for info in localPluginDatabase:
1304
+ for info in pluginDatabase:
1282
1305
  objs.append(PluginInfo(**info))
1283
1306
 
1284
1307
  # Add info objects for local plugins which aren't found online
@@ -521,7 +521,8 @@ class PreferencesDlg(wx.Dialog):
521
521
  # set default locale ''
522
522
  default = locales.index('')
523
523
  # '' must be appended after other labels are translated
524
- labels = [_translate('system locale') + i for i in self.app.localization.available]
524
+ labels = self.app.localization.available.copy()
525
+ labels.insert(0, _translate('system locale'))
525
526
  self.proPrefs.addEnumItem(
526
527
  sectionName,
527
528
  pLabel,
psychopy/app/utils.py CHANGED
@@ -448,7 +448,7 @@ class MarkdownCtrl(wx.Panel, handlers.ThemeMixin):
448
448
  Path to markdown file to edit via this control.
449
449
  style : wx.Style
450
450
  Style tags for this control. Accepts the following:
451
- - wx.READONLY: Hides all button controls and shows only the rendered HTML
451
+ - wx.TE_READONLY: Hides all button controls and shows only the rendered HTML
452
452
  - wx.RIGHT: Arranges buttons vertically along the right hand side
453
453
  - wx.BOTTOM: Arranges buttons horizontally along the bottom
454
454
  - wx.BU_NOTEXT: Don't show any label on the buttons
@@ -511,7 +511,7 @@ class MarkdownCtrl(wx.Panel, handlers.ThemeMixin):
511
511
  if value is None and self.file is not None:
512
512
  self.load()
513
513
  elif value is not None:
514
- self.rawTextCtrl.SetValue(value)
514
+ self.setValue(value)
515
515
 
516
516
  # Set initial view
517
517
  self.showHTML()