pyobs-gui 2.3.0__tar.gz → 2.3.1__tar.gz
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.
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/PKG-INFO +2 -2
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/structuredconfigwidget.py +63 -6
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyproject.toml +2 -2
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-08-28-gui-main-vs-sidebar-widgets.md +1 -2
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-09-01-gui-video-widget-split.md +1 -1
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/index.md +16 -12
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_structuredconfigwidget.py +77 -1
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/uv.lock +5 -5
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.claude/skills/verify/SKILL.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.github/dependabot.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.github/workflows/dependabot-automerge.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.github/workflows/pypi.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.github/workflows/pyrefly.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.github/workflows/ruff.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.gitignore +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.pre-commit-config.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/.readthedocs.yml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/CHANGELOG.rst +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/LICENSE +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/README.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/bin/mypyuic5 +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/Makefile +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/make.bat +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/requirements.txt +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/source/conf.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/docs/source/index.rst +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/mypy.ini +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/__init__.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/__main__.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/accounts.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/acquisitionwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/autofocuswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/autoguidingwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/base.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/camerawidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/commandinputwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/compassmovewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/coolingwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/datadisplaywidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/eventswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/filterwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/fitsheaderswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/focuswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/gui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/login.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/loginwindow.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/logmodel.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/mainwindow.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/modewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/modifiedmixin.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/modulegui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/__init__.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/autofocuswidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/camerawidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/camerawidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/compassmoveplugin.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/compassmovewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/compassmovewidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/compile.sh +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/coolingwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/datadisplayplugin.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/designer.sh +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/eventswidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/eventswidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/filterwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/filterwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/focuswidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/focuswidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/mainwindow.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/mainwindow_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/modewidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/modewidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/search-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources.qrc +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources_rc.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/roboticwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/roboticwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/roofwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/roofwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/schedulewidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/schedulewidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/shellwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/shellwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/spectrographwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/telescopewidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/temperatureswidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/videograbwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/videograbwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/videowidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/videowidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/weatherwidget.ui +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/roboticwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/roofwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/schedulewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/shellwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/spectrographwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/statuswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/telescopewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/temperaturesplotwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/temperatureswidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/utils.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/videograbwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/videowidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/watchedlabel.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/weatherwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pysidedeploy.spec +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-08-20-gui-remote-call-error-handling.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-08-21-gui-widget-startup-responsiveness.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-08-28-structuredconfig-widget.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/specs/2026-08-31-irobotic-widgets.md +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/spike_standalone/main.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/spike_standalone/pysidedeploy.spec +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/acquisition.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/autofocus.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/camera.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/full.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/guiding.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/mode.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/robotic.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/roof.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/spectrograph.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/structuredconfig.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/telescope.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/telescope_acl.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/telescope_acl_denied.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/test/video.yaml +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/__init__.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/conftest.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_accounts.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_camerawidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_loginwindow.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_mainwindow_startup.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_modewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_multiwidget_pages.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_roboticwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_schedulewidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_videograbwidget.py +0 -0
- {pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/tests/test_videowidget.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyobs-gui
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: A remote GUI for pyobs
|
|
5
5
|
Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,7 +11,7 @@ Requires-Dist: astropy<8,>=7.0.1
|
|
|
11
11
|
Requires-Dist: colour<0.2,>=0.1.5
|
|
12
12
|
Requires-Dist: keyring<26,>=25.7.0
|
|
13
13
|
Requires-Dist: matplotlib<4,>=3.10.1
|
|
14
|
-
Requires-Dist: pyobs-core<3,>=2.
|
|
14
|
+
Requires-Dist: pyobs-core<3,>=2.7.1
|
|
15
15
|
Requires-Dist: pyside6>=6.10.1
|
|
16
16
|
Requires-Dist: qasync<0.29,>=0.27.1
|
|
17
17
|
Requires-Dist: qfitswidget>=0.13.1
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Any, Callable, Dict, Optional, cast
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, cast
|
|
4
4
|
|
|
5
5
|
from PySide6 import QtCore, QtWidgets # type: ignore
|
|
6
6
|
|
|
7
7
|
from pyobs.interfaces import ConfigAppliedState, ConfigFieldSchema, ConfigSchema, IStructuredConfig
|
|
8
8
|
from pyobs.interfaces.IStructuredConfig import ConfigValue
|
|
9
|
+
from pyobs.utils.enums import AccessLevel
|
|
9
10
|
from .base import BaseWidget
|
|
10
11
|
|
|
11
12
|
log = logging.getLogger(__name__)
|
|
@@ -27,6 +28,16 @@ class _FieldEditor:
|
|
|
27
28
|
get_value: Callable[[], ConfigValue]
|
|
28
29
|
set_value: Callable[[ConfigValue], None]
|
|
29
30
|
children: Optional[Dict[str, "_FieldEditor"]] = None
|
|
31
|
+
level: AccessLevel = AccessLevel.BASIC
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _collect_by_level(editor: "_FieldEditor", level: AccessLevel) -> List[QtWidgets.QWidget]:
|
|
35
|
+
"""Recursively gather every editor widget (including nested-object children) tagged at
|
|
36
|
+
exactly the given level, for the basic/expert toggle to enable/disable as a group."""
|
|
37
|
+
widgets = [editor.widget] if editor.level == level else []
|
|
38
|
+
for child in (editor.children or {}).values():
|
|
39
|
+
widgets.extend(_collect_by_level(child, level))
|
|
40
|
+
return widgets
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
def _nested_get(children: Dict[str, _FieldEditor]) -> Dict[str, ConfigValue]:
|
|
@@ -49,7 +60,9 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
49
60
|
if schema.default is not None:
|
|
50
61
|
line_edit.setText(str(schema.default))
|
|
51
62
|
line_edit.textChanged.connect(lambda _: on_change())
|
|
52
|
-
|
|
63
|
+
if schema.level == AccessLevel.EXPERT:
|
|
64
|
+
line_edit.setEnabled(False)
|
|
65
|
+
return _FieldEditor(line_edit, line_edit.text, lambda value: line_edit.setText(str(value)), level=schema.level)
|
|
53
66
|
|
|
54
67
|
if schema.type == "int":
|
|
55
68
|
spin_box = QtWidgets.QSpinBox()
|
|
@@ -57,7 +70,11 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
57
70
|
if schema.default is not None:
|
|
58
71
|
spin_box.setValue(int(schema.default))
|
|
59
72
|
spin_box.valueChanged.connect(lambda _: on_change())
|
|
60
|
-
|
|
73
|
+
if schema.level == AccessLevel.EXPERT:
|
|
74
|
+
spin_box.setEnabled(False)
|
|
75
|
+
return _FieldEditor(
|
|
76
|
+
spin_box, spin_box.value, lambda value: spin_box.setValue(int(cast("int", value))), level=schema.level
|
|
77
|
+
)
|
|
61
78
|
|
|
62
79
|
if schema.type == "float":
|
|
63
80
|
double_spin_box = QtWidgets.QDoubleSpinBox()
|
|
@@ -68,8 +85,13 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
68
85
|
if schema.default is not None:
|
|
69
86
|
double_spin_box.setValue(float(schema.default))
|
|
70
87
|
double_spin_box.valueChanged.connect(lambda _: on_change())
|
|
88
|
+
if schema.level == AccessLevel.EXPERT:
|
|
89
|
+
double_spin_box.setEnabled(False)
|
|
71
90
|
return _FieldEditor(
|
|
72
|
-
double_spin_box,
|
|
91
|
+
double_spin_box,
|
|
92
|
+
double_spin_box.value,
|
|
93
|
+
lambda value: double_spin_box.setValue(float(cast("float", value))),
|
|
94
|
+
level=schema.level,
|
|
73
95
|
)
|
|
74
96
|
|
|
75
97
|
if schema.type == "bool":
|
|
@@ -77,7 +99,11 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
77
99
|
if schema.default is not None:
|
|
78
100
|
check_box.setChecked(bool(schema.default))
|
|
79
101
|
check_box.toggled.connect(lambda _: on_change())
|
|
80
|
-
|
|
102
|
+
if schema.level == AccessLevel.EXPERT:
|
|
103
|
+
check_box.setEnabled(False)
|
|
104
|
+
return _FieldEditor(
|
|
105
|
+
check_box, check_box.isChecked, lambda value: check_box.setChecked(bool(value)), level=schema.level
|
|
106
|
+
)
|
|
81
107
|
|
|
82
108
|
if schema.type == "enum":
|
|
83
109
|
combo_box = QtWidgets.QComboBox()
|
|
@@ -86,7 +112,14 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
86
112
|
if schema.default is not None and str(schema.default) in options:
|
|
87
113
|
combo_box.setCurrentText(str(schema.default))
|
|
88
114
|
combo_box.currentTextChanged.connect(lambda _: on_change())
|
|
89
|
-
|
|
115
|
+
if schema.level == AccessLevel.EXPERT:
|
|
116
|
+
combo_box.setEnabled(False)
|
|
117
|
+
return _FieldEditor(
|
|
118
|
+
combo_box,
|
|
119
|
+
combo_box.currentText,
|
|
120
|
+
lambda value: combo_box.setCurrentText(str(value)),
|
|
121
|
+
level=schema.level,
|
|
122
|
+
)
|
|
90
123
|
|
|
91
124
|
if schema.type == "object" and schema.nested is not None:
|
|
92
125
|
group_box = QtWidgets.QGroupBox(name)
|
|
@@ -94,14 +127,19 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
94
127
|
group_box.setLayout(form_layout)
|
|
95
128
|
children: Dict[str, _FieldEditor] = {}
|
|
96
129
|
for child_name, child_schema in schema.nested.items():
|
|
130
|
+
if child_schema.level == AccessLevel.HIDDEN:
|
|
131
|
+
continue
|
|
97
132
|
child_editor = _build_editor(child_name, child_schema, on_change)
|
|
98
133
|
form_layout.addRow(child_name, child_editor.widget)
|
|
99
134
|
children[child_name] = child_editor
|
|
135
|
+
if schema.level == AccessLevel.EXPERT:
|
|
136
|
+
group_box.setEnabled(False)
|
|
100
137
|
return _FieldEditor(
|
|
101
138
|
group_box,
|
|
102
139
|
lambda: _nested_get(children),
|
|
103
140
|
lambda value: _nested_set(children, value),
|
|
104
141
|
children=children,
|
|
142
|
+
level=schema.level,
|
|
105
143
|
)
|
|
106
144
|
|
|
107
145
|
# object without a nested schema (pydantic freeform dict), or any other schema type this
|
|
@@ -120,6 +158,8 @@ def _build_editor(name: str, schema: ConfigFieldSchema, on_change: Callable[[],
|
|
|
120
158
|
placeholder.setReadOnly(True)
|
|
121
159
|
placeholder.setEnabled(False)
|
|
122
160
|
placeholder.setToolTip(f"Field type {schema.type!r} has no schema-driven editor for this widget.")
|
|
161
|
+
# always BASIC regardless of schema.level: this widget is permanently disabled (nothing to
|
|
162
|
+
# edit), so it must never be swept into the expert-toggle's re-enable group
|
|
123
163
|
return _FieldEditor(placeholder, _get_raw, _set_raw)
|
|
124
164
|
|
|
125
165
|
|
|
@@ -142,10 +182,19 @@ class StructuredConfigWidget(BaseWidget):
|
|
|
142
182
|
# itself register as a user edit (which would otherwise re-enable Apply/Reset for a
|
|
143
183
|
# change that came from the module, not from the operator)
|
|
144
184
|
self._applying_state = False
|
|
185
|
+
# every EXPERT-level editor widget across the whole (possibly nested) form, collected
|
|
186
|
+
# once in _build_form; toggled together by checkBoxAdvanced. No password gate, unlike
|
|
187
|
+
# pyftscontrol's Expert Mode button -- operators here are already authenticated via the
|
|
188
|
+
# comm layer, so it's just a basic/expert visibility toggle, not an access control.
|
|
189
|
+
self._expert_widgets: List[QtWidgets.QWidget] = []
|
|
145
190
|
|
|
146
191
|
outer_layout = QtWidgets.QVBoxLayout()
|
|
147
192
|
self.setLayout(outer_layout)
|
|
148
193
|
|
|
194
|
+
self.checkBoxAdvanced = QtWidgets.QCheckBox("Show advanced fields")
|
|
195
|
+
self.checkBoxAdvanced.toggled.connect(self._on_advanced_toggled)
|
|
196
|
+
outer_layout.addWidget(self.checkBoxAdvanced)
|
|
197
|
+
|
|
149
198
|
scroll_area = QtWidgets.QScrollArea()
|
|
150
199
|
scroll_area.setWidgetResizable(True)
|
|
151
200
|
self._form_container = QtWidgets.QWidget()
|
|
@@ -186,6 +235,8 @@ class StructuredConfigWidget(BaseWidget):
|
|
|
186
235
|
def _build_form(self, schema: ConfigSchema) -> None:
|
|
187
236
|
children: Dict[str, _FieldEditor] = {}
|
|
188
237
|
for name, field_schema in schema.fields.items():
|
|
238
|
+
if field_schema.level == AccessLevel.HIDDEN:
|
|
239
|
+
continue
|
|
189
240
|
editor = _build_editor(name, field_schema, self._on_editor_changed)
|
|
190
241
|
self._form_layout.addRow(name, editor.widget)
|
|
191
242
|
children[name] = editor
|
|
@@ -195,11 +246,17 @@ class StructuredConfigWidget(BaseWidget):
|
|
|
195
246
|
lambda value: _nested_set(children, value),
|
|
196
247
|
children=children,
|
|
197
248
|
)
|
|
249
|
+
self._expert_widgets = _collect_by_level(self._root, AccessLevel.EXPERT)
|
|
250
|
+
self._on_advanced_toggled(self.checkBoxAdvanced.isChecked())
|
|
198
251
|
# a state may already have arrived (and been cached) before the form existed to receive
|
|
199
252
|
# it -- apply it now instead of waiting for the next update
|
|
200
253
|
if self._last_applied is not None:
|
|
201
254
|
self.signal_update_gui.emit()
|
|
202
255
|
|
|
256
|
+
def _on_advanced_toggled(self, checked: bool) -> None:
|
|
257
|
+
for widget in self._expert_widgets:
|
|
258
|
+
widget.setEnabled(checked)
|
|
259
|
+
|
|
203
260
|
def _on_state(self, state: ConfigAppliedState) -> None:
|
|
204
261
|
self._last_applied = dict(state.config)
|
|
205
262
|
self.signal_update_gui.emit()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyobs-gui"
|
|
3
|
-
version = "2.3.
|
|
3
|
+
version = "2.3.1"
|
|
4
4
|
description = "A remote GUI for pyobs"
|
|
5
5
|
authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
"sunpy[all]>=7.0.1",
|
|
13
13
|
"QtAwesome>=1.4.0,<2",
|
|
14
14
|
"qfitswidget>=0.13.1",
|
|
15
|
-
"pyobs-core>=2.
|
|
15
|
+
"pyobs-core>=2.7.1,<3",
|
|
16
16
|
"pyside6>=6.10.1",
|
|
17
17
|
"astroplan>=0.10.1",
|
|
18
18
|
"astropy>=7.0.1,<8",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Plan: pyobs-gui — main widgets vs. sidebar widgets, automatic tab pages for multi-widget modules
|
|
2
2
|
|
|
3
|
-
Status: implemented,
|
|
4
|
-
reaches a release on `main`, per repo convention.
|
|
3
|
+
Status: implemented, closed (PR #157, `b7a14a6`; released `v2.3.0`, `main`). Issue #150 closed.
|
|
5
4
|
Audited: 2026-08-28
|
|
6
5
|
Revised: 2026-09-01 (D1, D2 — see "Revision 2026-09-01" below; adds D6)
|
|
7
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `VideoWidget` split into two independent main widgets
|
|
2
2
|
|
|
3
|
-
Status: implemented, closed.
|
|
3
|
+
Status: implemented, closed (`cf42ab4`; released `v2.3.0`).
|
|
4
4
|
|
|
5
5
|
Dependency landed 2026-09-01: `2026-08-28-gui-main-vs-sidebar-widgets.md` merged to develop as
|
|
6
6
|
PR #157 (`b7a14a6`).
|
|
@@ -13,18 +13,22 @@ ADRs that concern `pyobs-gui` live in `pyobs-core`'s `specs/` tree instead (`spe
|
|
|
13
13
|
- `2026-08-28-structuredconfig-widget.md` — **proposed (issue #154)**. Generic
|
|
14
14
|
`StructuredConfigWidget`: schema-driven, auto-built editable form for `IStructuredConfig`
|
|
15
15
|
modules (from `ConfigSchema` capabilities + `ConfigAppliedState` + `set_config`).
|
|
16
|
-
- `2026-08-28-gui-main-vs-sidebar-widgets.md` — **implemented,
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Post-merge follow-ups: sidebar-fill dedup + D5 sidebar-failure isolation + 3
|
|
22
|
-
findings (`fccf0e0`), FITS-headers sidebar box alignment (`1e89069`),
|
|
23
|
-
sidebar (`f05bcba`).
|
|
24
|
-
- `2026-09-01-gui-video-widget-split.md` — **implemented, closed
|
|
25
|
-
independent main widgets (`VideoWidget` "Live View" + new
|
|
26
|
-
matched on `IVideo`, rendered as separate tabs — turned out
|
|
27
|
-
mechanism (D6) the draft was written for; the two halves
|
|
16
|
+
- `2026-08-28-gui-main-vs-sidebar-widgets.md` — **implemented, closed (PR #157, `b7a14a6`;
|
|
17
|
+
released `v2.3.0`)**. Issue #150 closed. Explicit main-widget/sidebar-widget distinction,
|
|
18
|
+
automatic tab pages for multi-widget modules (shared sidebar, merge-per-interface custom
|
|
19
|
+
config, standalone parity), `sidebar_preferred` promotion rule, universal sidebar container,
|
|
20
|
+
`paired_sidebar_widget` mechanism (D6, still no consumer — the video-widget split below ended up
|
|
21
|
+
not needing it). Post-merge follow-ups: sidebar-fill dedup + D5 sidebar-failure isolation + 3
|
|
22
|
+
other review findings (`fccf0e0`), FITS-headers sidebar box alignment (`1e89069`),
|
|
23
|
+
vertical-only scrollable sidebar (`f05bcba`).
|
|
24
|
+
- `2026-09-01-gui-video-widget-split.md` — **implemented, closed, released `v2.3.0`**. Split
|
|
25
|
+
`VideoWidget` into two independent main widgets (`VideoWidget` "Live View" + new
|
|
26
|
+
`VideoGrabWidget` "FITS Image"), both matched on `IVideo`, rendered as separate tabs — turned out
|
|
27
|
+
not to need the `paired_sidebar_widget` mechanism (D6) the draft was written for; the two halves
|
|
28
|
+
never shared state to begin with. Same release also carries two unplanned same-day fixes with no
|
|
29
|
+
dedicated doc: `CameraWidget`'s control panel clipping its right edge (`scrollArea` sized off a
|
|
30
|
+
stale `.ui` width hint instead of real content, `c22d7c7`) and `ModeWidget`'s edit button staying
|
|
31
|
+
permanently disabled for an `IMode` module with no `IMotion` (`1439437`).
|
|
28
32
|
- `2026-08-21-gui-widget-startup-responsiveness.md` — **implemented, closed 2026-08-23 (PR #141,
|
|
29
33
|
`123161b`)**. Make module widgets appear and respond immediately at startup: non-blocking
|
|
30
34
|
`_add_client` with a "Loading…" placeholder page (clicks are never dropped), parallel
|
|
@@ -5,7 +5,7 @@ from PySide6 import QtWidgets # type: ignore
|
|
|
5
5
|
|
|
6
6
|
from pyobs.interfaces import ConfigAppliedState, ConfigFieldSchema, ConfigSchema
|
|
7
7
|
from pyobs.utils import exceptions as exc
|
|
8
|
-
from pyobs.utils.enums import Unit
|
|
8
|
+
from pyobs.utils.enums import AccessLevel, Unit
|
|
9
9
|
from pyobs_gui.structuredconfigwidget import StructuredConfigWidget, _build_editor, _nested_get
|
|
10
10
|
|
|
11
11
|
# -- shared fixtures -----------------------------------------------------------------------
|
|
@@ -152,6 +152,82 @@ def test_unsupported_field_type_is_also_a_disabled_placeholder() -> None:
|
|
|
152
152
|
assert editor.get_value() == [1, 2]
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
# -- _build_editor / _build_form: basic/expert level ----------------------------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_basic_field_starts_enabled() -> None:
|
|
159
|
+
editor = _build_editor("count", ConfigFieldSchema(type="int", default=3, level=AccessLevel.BASIC), lambda: None)
|
|
160
|
+
assert editor.widget.isEnabled() is True
|
|
161
|
+
assert editor.level == AccessLevel.BASIC
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_expert_field_starts_disabled() -> None:
|
|
165
|
+
editor = _build_editor("count", ConfigFieldSchema(type="int", default=3, level=AccessLevel.EXPERT), lambda: None)
|
|
166
|
+
assert editor.widget.isEnabled() is False
|
|
167
|
+
assert editor.level == AccessLevel.EXPERT
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_unsupported_type_placeholder_ignores_expert_level_for_toggle_grouping() -> None:
|
|
171
|
+
"""A permanently non-editable placeholder must never be swept into the expert-toggle's
|
|
172
|
+
re-enable group, even if the underlying field is tagged EXPERT -- see the comment in
|
|
173
|
+
_build_editor's fallback branch."""
|
|
174
|
+
editor = _build_editor(
|
|
175
|
+
"items", ConfigFieldSchema(type="list", default=[1, 2], level=AccessLevel.EXPERT), lambda: None
|
|
176
|
+
)
|
|
177
|
+
assert editor.level == AccessLevel.BASIC
|
|
178
|
+
assert editor.widget.isEnabled() is False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_nested_object_skips_hidden_children_and_disables_whole_group_when_expert() -> None:
|
|
182
|
+
schema = ConfigFieldSchema(
|
|
183
|
+
type="object",
|
|
184
|
+
level=AccessLevel.EXPERT,
|
|
185
|
+
nested={
|
|
186
|
+
"az": ConfigFieldSchema(type="float", default=1.0),
|
|
187
|
+
"secret": ConfigFieldSchema(type="float", default=2.0, level=AccessLevel.HIDDEN),
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
editor = _build_editor("pointing", schema, lambda: None)
|
|
191
|
+
assert editor.children is not None and set(editor.children) == {"az"}
|
|
192
|
+
assert editor.widget.isEnabled() is False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _sample_schema_with_levels() -> ConfigSchema:
|
|
196
|
+
return ConfigSchema(
|
|
197
|
+
fields={
|
|
198
|
+
"name": ConfigFieldSchema(type="str", default="foo", level=AccessLevel.BASIC),
|
|
199
|
+
"gain": ConfigFieldSchema(type="int", default=1, level=AccessLevel.EXPERT),
|
|
200
|
+
"secret": ConfigFieldSchema(type="int", default=0, level=AccessLevel.HIDDEN),
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_build_form_skips_hidden_fields_entirely() -> None:
|
|
206
|
+
widget = StructuredConfigWidget()
|
|
207
|
+
widget._build_form(_sample_schema_with_levels())
|
|
208
|
+
|
|
209
|
+
assert widget._form_layout.rowCount() == 2 # name + gain, not secret
|
|
210
|
+
assert widget._root is not None and widget._root.children is not None
|
|
211
|
+
assert set(widget._root.children) == {"name", "gain"}
|
|
212
|
+
widget.close()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_advanced_checkbox_toggles_expert_fields_enabled() -> None:
|
|
216
|
+
widget = StructuredConfigWidget()
|
|
217
|
+
widget._build_form(_sample_schema_with_levels())
|
|
218
|
+
assert widget._root is not None and widget._root.children is not None
|
|
219
|
+
expert_widget = widget._root.children["gain"].widget
|
|
220
|
+
|
|
221
|
+
assert expert_widget.isEnabled() is False # starts locked
|
|
222
|
+
|
|
223
|
+
widget.checkBoxAdvanced.setChecked(True)
|
|
224
|
+
assert expert_widget.isEnabled() is True
|
|
225
|
+
|
|
226
|
+
widget.checkBoxAdvanced.setChecked(False)
|
|
227
|
+
assert expert_widget.isEnabled() is False
|
|
228
|
+
widget.close()
|
|
229
|
+
|
|
230
|
+
|
|
155
231
|
# -- StructuredConfigWidget: state population, dirty tracking, apply/reset ------------------
|
|
156
232
|
|
|
157
233
|
|
|
@@ -3256,7 +3256,7 @@ wheels = [
|
|
|
3256
3256
|
|
|
3257
3257
|
[[package]]
|
|
3258
3258
|
name = "pyobs-core"
|
|
3259
|
-
version = "2.
|
|
3259
|
+
version = "2.7.1"
|
|
3260
3260
|
source = { registry = "https://pypi.org/simple" }
|
|
3261
3261
|
dependencies = [
|
|
3262
3262
|
{ name = "aiohttp" },
|
|
@@ -3288,14 +3288,14 @@ dependencies = [
|
|
|
3288
3288
|
{ name = "tenacity" },
|
|
3289
3289
|
{ name = "typing-extensions" },
|
|
3290
3290
|
]
|
|
3291
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
3291
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/5e/8d4de011685819cd4b04ee8a5aa35822814e0d6c607f4dfe6917b37e71e2/pyobs_core-2.7.1.tar.gz", hash = "sha256:beb50633cc21b6ea5a99713fe114973eecf7999a17f69578a996f4335fcb1ed7", size = 409990, upload-time = "2026-09-04T09:44:17.492Z" }
|
|
3292
3292
|
wheels = [
|
|
3293
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
3293
|
+
{ url = "https://files.pythonhosted.org/packages/fd/14/037a4e52b30816474fb4360a0741952499820e24af01c09f609311369379/pyobs_core-2.7.1-py3-none-any.whl", hash = "sha256:060de003b319e4f7a14dd80500d13d1d202df1e49364af74dac3e7715ff843cb", size = 639800, upload-time = "2026-09-04T09:44:16.291Z" },
|
|
3294
3294
|
]
|
|
3295
3295
|
|
|
3296
3296
|
[[package]]
|
|
3297
3297
|
name = "pyobs-gui"
|
|
3298
|
-
version = "2.3.
|
|
3298
|
+
version = "2.3.1"
|
|
3299
3299
|
source = { editable = "." }
|
|
3300
3300
|
dependencies = [
|
|
3301
3301
|
{ name = "astroplan" },
|
|
@@ -3339,7 +3339,7 @@ requires-dist = [
|
|
|
3339
3339
|
{ name = "colour", specifier = ">=0.1.5,<0.2" },
|
|
3340
3340
|
{ name = "keyring", specifier = ">=25.7.0,<26" },
|
|
3341
3341
|
{ name = "matplotlib", specifier = ">=3.10.1,<4" },
|
|
3342
|
-
{ name = "pyobs-core", specifier = ">=2.
|
|
3342
|
+
{ name = "pyobs-core", specifier = ">=2.7.1,<3" },
|
|
3343
3343
|
{ name = "pyside6", specifier = ">=6.10.1" },
|
|
3344
3344
|
{ name = "qasync", specifier = ">=0.27.1,<0.29" },
|
|
3345
3345
|
{ name = "qfitswidget", specifier = ">=0.13.1" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyobs_gui-2.3.0 → pyobs_gui-2.3.1}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|