pyobs-gui 2.4.0__tar.gz → 2.4.2__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.4.2/CLAUDE.md +26 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/PKG-INFO +3 -3
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/gui.py +6 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/mainwindow.py +84 -2
- pyobs_gui-2.4.2/pyobs_gui/movestack.py +18 -0
- pyobs_gui-2.4.2/pyobs_gui/nowheelfilter.py +56 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/filterwidget.ui +3 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/filterwidget_ui.py +2 -1
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/focuswidget.ui +3 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/focuswidget_ui.py +2 -1
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/mainwindow.ui +17 -3
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/mainwindow_ui.py +12 -5
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/telescopewidget.ui +9 -1
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/telescopewidget_ui.py +6 -4
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/telescopewidget.py +12 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyproject.toml +3 -3
- pyobs_gui-2.4.2/specs/2026-09-14-fitswidget-toolbar-overflow.md +183 -0
- pyobs_gui-2.4.2/specs/2026-09-14-stacked-widget-scroll-fallback.md +87 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/index.md +17 -3
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/uv.lock +85 -78
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.claude/skills/verify/SKILL.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.github/dependabot.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.github/workflows/dependabot-automerge.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.github/workflows/pypi.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.github/workflows/pyrefly.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.github/workflows/ruff.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.gitignore +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.pre-commit-config.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/.readthedocs.yml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/CHANGELOG.rst +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/LICENSE +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/README.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/bin/mypyuic5 +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/Makefile +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/make.bat +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/requirements.txt +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/source/conf.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/docs/source/index.rst +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/mypy.ini +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/__init__.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/__main__.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/accounts.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/acquisitionwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/autofocuswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/autoguidingwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/base.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/camerawidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/commandinputwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/compassmovewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/coolingwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/datadisplaywidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/eventswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/filterwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/fitsheaderswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/focuswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/login.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/loginwindow.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/logmodel.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/modewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/modifiedmixin.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/modulegui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/__init__.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/autofocuswidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/camerawidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/camerawidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/compassmoveplugin.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/compassmovewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/compassmovewidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/compile.sh +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/coolingwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/datadisplayplugin.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/designer.sh +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/eventswidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/eventswidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/modewidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/modewidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/search-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources.qrc +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/resources_rc.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/roboticwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/roboticwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/roofwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/roofwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/schedulewidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/schedulewidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/shellwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/shellwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/spectrographwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/temperatureswidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/videograbwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/videograbwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/videowidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/videowidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/weatherwidget.ui +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/roboticwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/roofwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/schedulewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/shellwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/spectrographwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/statuswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/structuredconfigwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/temperaturesplotwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/temperatureswidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/utils.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/videograbwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/videowidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/watchedlabel.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pyobs_gui/weatherwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/pysidedeploy.spec +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-08-20-gui-remote-call-error-handling.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-08-21-gui-widget-startup-responsiveness.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-08-28-gui-main-vs-sidebar-widgets.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-08-28-structuredconfig-widget.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-08-31-irobotic-widgets.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/specs/2026-09-01-gui-video-widget-split.md +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/spike_standalone/main.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/spike_standalone/pysidedeploy.spec +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/acquisition.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/autofocus.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/camera.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/full.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/guiding.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/mode.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/robotic.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/roof.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/spectrograph.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/structuredconfig.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/telescope.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/telescope_acl.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/telescope_acl_denied.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/test/video.yaml +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/__init__.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/conftest.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_accounts.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_camerawidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_loginwindow.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_mainwindow_startup.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_modewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_multiwidget_pages.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_roboticwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_schedulewidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_structuredconfigwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_videograbwidget.py +0 -0
- {pyobs_gui-2.4.0 → pyobs_gui-2.4.2}/tests/test_videowidget.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Entry points for working in this repo.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`pyobs-gui` is a PySide6/Qt GUI for operating a whole `pyobs` system: one page per connected
|
|
8
|
+
module (camera, telescope, roof, focuser, ...), plus Shell/Events/Status tool pages. Run standalone
|
|
9
|
+
with interactive login (`uv run pyobs-gui`) or via the standard `pyobs` CLI with a config file
|
|
10
|
+
whose top-level class is `pyobs_gui.GUI` (see `docs/source/index.rst`).
|
|
11
|
+
|
|
12
|
+
## Design history and planning
|
|
13
|
+
|
|
14
|
+
This repo keeps its own implementation plans directly under `specs/` (`YYYY-MM-DD-<slug>.md`,
|
|
15
|
+
listed in `specs/index.md`), following the same conventions as `pyobs-core`'s `specs/` tree. Older
|
|
16
|
+
design docs, plans, and ADRs that concern `pyobs-gui` live in `pyobs-core`'s `specs/` tree instead,
|
|
17
|
+
tagged with a `Repos:` line — see `specs/index.md` for the current cross-repo list and
|
|
18
|
+
`pyobs-core/CLAUDE.md`'s "Cross-repo docs" section for the convention.
|
|
19
|
+
|
|
20
|
+
## Tooling
|
|
21
|
+
|
|
22
|
+
- Lint: `ruff` (config in `pyproject.toml`; excludes `pyobs_gui/qt/`)
|
|
23
|
+
- Format: `black`
|
|
24
|
+
- Type checking: `pyrefly` (`project-includes = ["pyobs_gui", "tests"]`; a `mypy.ini` also exists
|
|
25
|
+
in the repo root but is not the active type checker — use `pyrefly`)
|
|
26
|
+
- Tests: `pytest` (`asyncio_mode = "strict"`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyobs-gui
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
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,10 +11,10 @@ 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.7.
|
|
14
|
+
Requires-Dist: pyobs-core<3,>=2.7.2
|
|
15
15
|
Requires-Dist: pyside6>=6.10.1
|
|
16
16
|
Requires-Dist: qasync<0.29,>=0.27.1
|
|
17
|
-
Requires-Dist: qfitswidget>=
|
|
17
|
+
Requires-Dist: qfitswidget>=1.1.3
|
|
18
18
|
Requires-Dist: qtawesome<2,>=1.4.0
|
|
19
19
|
Requires-Dist: secretstorage<4,>=3.5.0
|
|
20
20
|
Requires-Dist: sunpy[all]>=7.0.1
|
|
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from pyobs.interfaces import FitsHeaderEntry, IFitsHeaderBefore
|
|
12
12
|
from pyobs.modules import Module
|
|
13
13
|
|
|
14
|
+
from .nowheelfilter import NoWheelWhenUnfocused
|
|
14
15
|
from .utils import QAsyncMessageBox
|
|
15
16
|
|
|
16
17
|
log = logging.getLogger(__name__)
|
|
@@ -20,6 +21,9 @@ class GUI(Module, IFitsHeaderBefore):
|
|
|
20
21
|
__module__ = "pyobs_gui"
|
|
21
22
|
|
|
22
23
|
app: QtWidgets.QApplication | None = None
|
|
24
|
+
# kept alive here -- QApplication.installEventFilter() doesn't itself keep a Python reference,
|
|
25
|
+
# and an unparented QObject with nothing else referencing it is fair game for GC
|
|
26
|
+
_wheel_filter: NoWheelWhenUnfocused | None = None
|
|
23
27
|
|
|
24
28
|
def __init__(
|
|
25
29
|
self,
|
|
@@ -67,6 +71,8 @@ class GUI(Module, IFitsHeaderBefore):
|
|
|
67
71
|
# "quit when the last window closes" would tear down the whole event loop right there,
|
|
68
72
|
# so this app manages its own lifetime exclusively through Module.quit() instead.
|
|
69
73
|
GUI.app.setQuitOnLastWindowClosed(False)
|
|
74
|
+
GUI._wheel_filter = NoWheelWhenUnfocused()
|
|
75
|
+
GUI.app.installEventFilter(GUI._wheel_filter)
|
|
70
76
|
return cast("asyncio.AbstractEventLoop", qasync.QEventLoop(GUI.app))
|
|
71
77
|
|
|
72
78
|
async def open(self) -> None:
|
|
@@ -288,6 +288,9 @@ class ModulePage(BaseWidget):
|
|
|
288
288
|
# this page's widgets have actually opened -- see its sidebar-fill steps (c) and (e)
|
|
289
289
|
self.sidebar_preferred_choices: List[WidgetChoice] = sidebar_preferred or []
|
|
290
290
|
self.custom_sidebar: List[Dict[str, Any]] = custom_sidebar or []
|
|
291
|
+
# user's manual show/hide choice via sidebar_toggle -- independent of
|
|
292
|
+
# hide_if_empty_sidebar()'s content-driven hiding, see _update_sidebar_visibility()
|
|
293
|
+
self._sidebar_collapsed = False
|
|
291
294
|
|
|
292
295
|
layout = QtWidgets.QHBoxLayout(self)
|
|
293
296
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
@@ -311,12 +314,50 @@ class ModulePage(BaseWidget):
|
|
|
311
314
|
self.sidebar_scroll.setWidget(self.widgetSidebar)
|
|
312
315
|
self.sidebar_scroll.setWidgetResizable(True)
|
|
313
316
|
self.sidebar_scroll.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
|
|
317
|
+
# No horizontal scrollbar -- deliberately. Without a real minimum width, that would leave
|
|
318
|
+
# the sidebar's own content as the only thing standing between "fits" and "silently
|
|
319
|
+
# clipped" once something upstream (MainWindow.resizeEvent's forced splitterNav.setSizes())
|
|
320
|
+
# squeezes this pane below its natural size. _update_sidebar_min_width() is what actually
|
|
321
|
+
# keeps it from getting that small in the first place -- see there.
|
|
314
322
|
self.sidebar_scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
315
323
|
self.sidebar_scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
316
|
-
self.sidebar_scroll.setMaximumWidth(320)
|
|
317
324
|
self.sidebar_scroll.setVisible(False)
|
|
325
|
+
# _update_sidebar_min_width() (called below and on every add_to_sidebar()) sets both the
|
|
326
|
+
# min and max width from real content -- nothing hardcoded here to get stale
|
|
327
|
+
|
|
318
328
|
layout.addWidget(self.sidebar_scroll)
|
|
319
329
|
|
|
330
|
+
# manual show/hide toggle -- independent of the automatic content-driven hiding in
|
|
331
|
+
# hide_if_empty_sidebar(). Lets someone reclaim the sidebar's width on a narrow window
|
|
332
|
+
# without that width being a permanent, un-opt-outable floor.
|
|
333
|
+
#
|
|
334
|
+
# Deliberately NOT a layout item (a QHBoxLayout cell always spans the full row height, so
|
|
335
|
+
# a small button in its own cell reads as a tall, mostly-empty column -- confirmed against
|
|
336
|
+
# a real screenshot, not just theory). Instead it's a small floating overlay, positioned by
|
|
337
|
+
# _position_sidebar_toggle(), parented directly on `self` and raised above everything else.
|
|
338
|
+
self.sidebar_toggle = QtWidgets.QToolButton(self)
|
|
339
|
+
self.sidebar_toggle.setAutoRaise(True)
|
|
340
|
+
self.sidebar_toggle.clicked.connect(self._toggle_sidebar)
|
|
341
|
+
self.sidebar_toggle.setVisible(False)
|
|
342
|
+
self._update_sidebar_visibility()
|
|
343
|
+
|
|
344
|
+
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
|
|
345
|
+
super().resizeEvent(event)
|
|
346
|
+
self._position_sidebar_toggle()
|
|
347
|
+
|
|
348
|
+
def _position_sidebar_toggle(self) -> None:
|
|
349
|
+
"""Pins the toggle just outside the sidebar's left edge when it's shown (a compact handle
|
|
350
|
+
right at the boundary, not a dedicated column), or to the page's right edge when it's
|
|
351
|
+
collapsed (so there's still something to click to bring it back)."""
|
|
352
|
+
margin = 4
|
|
353
|
+
y = margin
|
|
354
|
+
if self.sidebar_scroll.isVisible():
|
|
355
|
+
x = self.sidebar_scroll.x() - self.sidebar_toggle.width() - margin
|
|
356
|
+
else:
|
|
357
|
+
x = self.width() - self.sidebar_toggle.width() - margin
|
|
358
|
+
self.sidebar_toggle.move(max(0, x), y)
|
|
359
|
+
self.sidebar_toggle.raise_()
|
|
360
|
+
|
|
320
361
|
def remove_widget(self, widget: BaseWidget) -> None:
|
|
321
362
|
"""Removes one tab (D5: a partially-failed open drops just that widget's tab, keeping
|
|
322
363
|
the rest of the page). No-op if `widget` is the page's sole (non-tabbed) content --
|
|
@@ -329,8 +370,49 @@ class ModulePage(BaseWidget):
|
|
|
329
370
|
if idx != -1:
|
|
330
371
|
self.tab_widget.removeTab(idx)
|
|
331
372
|
|
|
373
|
+
async def add_to_sidebar(self, widget: BaseWidget) -> None:
|
|
374
|
+
await super().add_to_sidebar(widget)
|
|
375
|
+
self._update_sidebar_min_width()
|
|
376
|
+
|
|
377
|
+
_SIDEBAR_DEFAULT_MAX_WIDTH = 320
|
|
378
|
+
|
|
379
|
+
def _update_sidebar_min_width(self) -> None:
|
|
380
|
+
"""Give sidebar_scroll a real minimum width tracking its content, so the horizontal
|
|
381
|
+
scrollbar being off (see __init__) doesn't mean content silently gets compressed below
|
|
382
|
+
what it needs instead.
|
|
383
|
+
|
|
384
|
+
Previous version capped the minimum at the same 320px the column's maximumWidth uses,
|
|
385
|
+
on the theory that content needing more than that was already going to clip and this
|
|
386
|
+
method wasn't responsible for that case -- wrong in practice: real content (a populated
|
|
387
|
+
FITS-headers table, Temperatures/Cooling widgets) routinely exceeds 320px, and capping
|
|
388
|
+
the *minimum* at the same value as the *maximum* left no room between them, so it clipped
|
|
389
|
+
regardless of how correct the minimum-width mechanism otherwise was. The maximum now
|
|
390
|
+
grows to match real content instead: 320px stays the default ceiling for typical small
|
|
391
|
+
content (letting it size elastically within that range, same as before), but never
|
|
392
|
+
shrinks below what's actually needed."""
|
|
393
|
+
natural = self.widgetSidebar.sizeHint().width()
|
|
394
|
+
self.sidebar_scroll.setMaximumWidth(max(natural, self._SIDEBAR_DEFAULT_MAX_WIDTH))
|
|
395
|
+
self.sidebar_scroll.setMinimumWidth(natural)
|
|
396
|
+
|
|
332
397
|
def hide_if_empty_sidebar(self) -> None:
|
|
333
|
-
self.
|
|
398
|
+
self._update_sidebar_visibility()
|
|
399
|
+
|
|
400
|
+
def _toggle_sidebar(self) -> None:
|
|
401
|
+
self._sidebar_collapsed = not self._sidebar_collapsed
|
|
402
|
+
self._update_sidebar_visibility()
|
|
403
|
+
|
|
404
|
+
def _update_sidebar_visibility(self) -> None:
|
|
405
|
+
has_content = len(self.sidebar_widgets) > 0
|
|
406
|
+
shown = has_content and not self._sidebar_collapsed
|
|
407
|
+
self.sidebar_toggle.setVisible(has_content)
|
|
408
|
+
self.sidebar_scroll.setVisible(shown)
|
|
409
|
+
# icon points the direction the *next click* moves the sidebar, not the current state:
|
|
410
|
+
# left = "click to bring it in and show it", right = "click to push it out and hide it"
|
|
411
|
+
self.sidebar_toggle.setIcon(qta.icon("fa5s.angle-double-right" if shown else "fa5s.angle-double-left"))
|
|
412
|
+
self.sidebar_toggle.setToolTip("Hide sidebar" if shown else "Show sidebar")
|
|
413
|
+
# sidebar_scroll's geometry only settles after Qt processes this visibility/layout change
|
|
414
|
+
# -- queue the reposition for right after that, rather than reading stale geometry now.
|
|
415
|
+
QtCore.QTimer.singleShot(0, self._position_sidebar_toggle)
|
|
334
416
|
|
|
335
417
|
async def discard(self) -> None:
|
|
336
418
|
for widget in list(self.widgets):
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from PySide6 import QtCore, QtWidgets # type: ignore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MoveStack(QtWidgets.QStackedWidget):
|
|
5
|
+
"""QStackedWidget sizes itself to the largest of all its pages by default, so
|
|
6
|
+
TelescopeWidget's coordinate-type stack (RA/Dec, Alt/Az, Orbit Elements, ...) reserves width
|
|
7
|
+
for its widest page even when a narrower one is shown -- and every new coordinate-type page
|
|
8
|
+
added in the future raises that floor further, permanently, for every module using this
|
|
9
|
+
widget. Size to the current page instead; call updateGeometry() after switching pages so the
|
|
10
|
+
new size actually takes effect."""
|
|
11
|
+
|
|
12
|
+
def sizeHint(self) -> QtCore.QSize:
|
|
13
|
+
current = self.currentWidget()
|
|
14
|
+
return current.sizeHint() if current is not None else super().sizeHint()
|
|
15
|
+
|
|
16
|
+
def minimumSizeHint(self) -> QtCore.QSize:
|
|
17
|
+
current = self.currentWidget()
|
|
18
|
+
return current.minimumSizeHint() if current is not None else super().minimumSizeHint()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from PySide6 import QtCore, QtWidgets # type: ignore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NoWheelWhenUnfocused(QtCore.QObject):
|
|
5
|
+
"""Application-wide event filter: a spin box/combo box/slider that doesn't have keyboard focus
|
|
6
|
+
ignores mouse wheel events instead of changing its value, and forwards the wheel event to the
|
|
7
|
+
nearest scrollable ancestor instead -- so scrolling a page that happens to pass the cursor over
|
|
8
|
+
one of these controls scrolls the page, not the control underneath the cursor. Confirmed a real
|
|
9
|
+
problem, not a hypothetical one: sending a wheel event to an unfocused QDoubleSpinBox changed
|
|
10
|
+
its value before this filter existed (see
|
|
11
|
+
../specs/2026-09-14-stacked-widget-scroll-fallback.md, risk #1) -- became a live concern once
|
|
12
|
+
stackedWidget started living inside a QScrollArea, where a normal "scroll the page" gesture can
|
|
13
|
+
easily pass over a spin/combo box.
|
|
14
|
+
|
|
15
|
+
Install once, on QApplication (see gui.py's GUI.new_event_loop()). Focusing the control
|
|
16
|
+
(click or Tab) restores the normal wheel-changes-value behavior, same as before this filter
|
|
17
|
+
existed -- this only changes what happens when the control merely has the mouse over it.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_WHEEL_ADJUSTABLE = (QtWidgets.QAbstractSpinBox, QtWidgets.QComboBox, QtWidgets.QAbstractSlider)
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
# re-entrancy guard: sendEvent() below re-enters this same app-wide filter (nested scroll
|
|
25
|
+
# areas are common -- e.g. CameraWidget's own left-panel scrollArea sits between a spinbox
|
|
26
|
+
# and the outer stackedWidgetScroll), and if the forwarded copy also lands on an unfocused
|
|
27
|
+
# adjustable control, that's infinite recursion without this. While forwarding, every event
|
|
28
|
+
# just passes through untouched instead of being re-evaluated.
|
|
29
|
+
self._forwarding = False
|
|
30
|
+
|
|
31
|
+
def eventFilter(self, watched: QtCore.QObject, event: QtCore.QEvent) -> bool:
|
|
32
|
+
if self._forwarding:
|
|
33
|
+
return False
|
|
34
|
+
if (
|
|
35
|
+
event.type() == QtCore.QEvent.Type.Wheel
|
|
36
|
+
and isinstance(watched, self._WHEEL_ADJUSTABLE)
|
|
37
|
+
and not watched.hasFocus()
|
|
38
|
+
):
|
|
39
|
+
scroll_area = self._nearest_scroll_area(watched)
|
|
40
|
+
if scroll_area is not None:
|
|
41
|
+
self._forwarding = True
|
|
42
|
+
try:
|
|
43
|
+
QtWidgets.QApplication.sendEvent(scroll_area.viewport(), event)
|
|
44
|
+
finally:
|
|
45
|
+
self._forwarding = False
|
|
46
|
+
return True # swallow the original either way -- never let it reach the control itself
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _nearest_scroll_area(widget: QtWidgets.QWidget) -> QtWidgets.QAbstractScrollArea | None:
|
|
51
|
+
parent = widget.parentWidget()
|
|
52
|
+
while parent is not None:
|
|
53
|
+
if isinstance(parent, QtWidgets.QAbstractScrollArea):
|
|
54
|
+
return parent
|
|
55
|
+
parent = parent.parentWidget()
|
|
56
|
+
return None
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
<string>Filter</string>
|
|
21
21
|
</property>
|
|
22
22
|
<layout class="QFormLayout" name="formLayout">
|
|
23
|
+
<property name="rowWrapPolicy">
|
|
24
|
+
<enum>QFormLayout::RowWrapPolicy::WrapLongRows</enum>
|
|
25
|
+
</property>
|
|
23
26
|
<item row="0" column="0">
|
|
24
27
|
<widget class="QLabel" name="label_9">
|
|
25
28
|
<property name="text">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
################################################################################
|
|
4
4
|
## Form generated from reading UI file 'filterwidget.ui'
|
|
5
5
|
##
|
|
6
|
-
## Created by: Qt User Interface Compiler version 6.
|
|
6
|
+
## Created by: Qt User Interface Compiler version 6.11.2
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -31,6 +31,7 @@ class Ui_FilterWidget(object):
|
|
|
31
31
|
self.groupBox_2.setObjectName(u"groupBox_2")
|
|
32
32
|
self.formLayout = QFormLayout(self.groupBox_2)
|
|
33
33
|
self.formLayout.setObjectName(u"formLayout")
|
|
34
|
+
self.formLayout.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)
|
|
34
35
|
self.label_9 = QLabel(self.groupBox_2)
|
|
35
36
|
self.label_9.setObjectName(u"label_9")
|
|
36
37
|
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
<string>Focus</string>
|
|
36
36
|
</property>
|
|
37
37
|
<layout class="QFormLayout" name="formLayout">
|
|
38
|
+
<property name="rowWrapPolicy">
|
|
39
|
+
<enum>QFormLayout::RowWrapPolicy::WrapLongRows</enum>
|
|
40
|
+
</property>
|
|
38
41
|
<item row="0" column="0">
|
|
39
42
|
<widget class="QLabel" name="label_12">
|
|
40
43
|
<property name="text">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
################################################################################
|
|
4
4
|
## Form generated from reading UI file 'focuswidget.ui'
|
|
5
5
|
##
|
|
6
|
-
## Created by: Qt User Interface Compiler version 6.
|
|
6
|
+
## Created by: Qt User Interface Compiler version 6.11.2
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -33,6 +33,7 @@ class Ui_FocusWidget(object):
|
|
|
33
33
|
self.groupBox_5.setObjectName(u"groupBox_5")
|
|
34
34
|
self.formLayout = QFormLayout(self.groupBox_5)
|
|
35
35
|
self.formLayout.setObjectName(u"formLayout")
|
|
36
|
+
self.formLayout.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)
|
|
36
37
|
self.label_12 = QLabel(self.groupBox_5)
|
|
37
38
|
self.label_12.setObjectName(u"label_12")
|
|
38
39
|
|
|
@@ -369,10 +369,24 @@
|
|
|
369
369
|
<property name="orientation">
|
|
370
370
|
<enum>Qt::Orientation::Horizontal</enum>
|
|
371
371
|
</property>
|
|
372
|
-
<widget class="
|
|
373
|
-
<property name="
|
|
374
|
-
<
|
|
372
|
+
<widget class="QScrollArea" name="stackedWidgetScroll">
|
|
373
|
+
<property name="widgetResizable">
|
|
374
|
+
<bool>true</bool>
|
|
375
|
+
</property>
|
|
376
|
+
<property name="frameShape">
|
|
377
|
+
<enum>QFrame::Shape::NoFrame</enum>
|
|
375
378
|
</property>
|
|
379
|
+
<property name="horizontalScrollBarPolicy">
|
|
380
|
+
<enum>Qt::ScrollBarPolicy::ScrollBarAsNeeded</enum>
|
|
381
|
+
</property>
|
|
382
|
+
<property name="verticalScrollBarPolicy">
|
|
383
|
+
<enum>Qt::ScrollBarPolicy::ScrollBarAsNeeded</enum>
|
|
384
|
+
</property>
|
|
385
|
+
<widget class="QStackedWidget" name="stackedWidget">
|
|
386
|
+
<property name="currentIndex">
|
|
387
|
+
<number>-1</number>
|
|
388
|
+
</property>
|
|
389
|
+
</widget>
|
|
376
390
|
</widget>
|
|
377
391
|
</widget>
|
|
378
392
|
<widget class="QSplitter" name="splitterClients">
|
|
@@ -17,9 +17,9 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
|
|
17
17
|
QPalette, QPixmap, QRadialGradient, QTransform)
|
|
18
18
|
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QFrame, QHeaderView,
|
|
19
19
|
QLabel, QListView, QListWidget, QListWidgetItem,
|
|
20
|
-
QMainWindow, QPushButton,
|
|
21
|
-
QSplitter, QStackedWidget, QTableView,
|
|
22
|
-
QVBoxLayout, QWidget)
|
|
20
|
+
QMainWindow, QPushButton, QScrollArea, QSizePolicy,
|
|
21
|
+
QSpacerItem, QSplitter, QStackedWidget, QTableView,
|
|
22
|
+
QToolButton, QVBoxLayout, QWidget)
|
|
23
23
|
from . import resources_rc
|
|
24
24
|
|
|
25
25
|
class Ui_MainWindow(object):
|
|
@@ -141,9 +141,16 @@ class Ui_MainWindow(object):
|
|
|
141
141
|
self.splitterToolBox = QSplitter(self.splitterLog)
|
|
142
142
|
self.splitterToolBox.setObjectName(u"splitterToolBox")
|
|
143
143
|
self.splitterToolBox.setOrientation(Qt.Orientation.Horizontal)
|
|
144
|
-
self.
|
|
144
|
+
self.stackedWidgetScroll = QScrollArea(self.splitterToolBox)
|
|
145
|
+
self.stackedWidgetScroll.setObjectName(u"stackedWidgetScroll")
|
|
146
|
+
self.stackedWidgetScroll.setWidgetResizable(True)
|
|
147
|
+
self.stackedWidgetScroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
148
|
+
self.stackedWidgetScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
149
|
+
self.stackedWidgetScroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
150
|
+
self.stackedWidget = QStackedWidget()
|
|
145
151
|
self.stackedWidget.setObjectName(u"stackedWidget")
|
|
146
|
-
self.
|
|
152
|
+
self.stackedWidgetScroll.setWidget(self.stackedWidget)
|
|
153
|
+
self.splitterToolBox.addWidget(self.stackedWidgetScroll)
|
|
147
154
|
self.splitterLog.addWidget(self.splitterToolBox)
|
|
148
155
|
self.splitterClients = QSplitter(self.splitterLog)
|
|
149
156
|
self.splitterClients.setObjectName(u"splitterClients")
|
|
@@ -626,7 +626,7 @@
|
|
|
626
626
|
</widget>
|
|
627
627
|
</item>
|
|
628
628
|
<item>
|
|
629
|
-
<widget class="
|
|
629
|
+
<widget class="MoveStack" name="stackedMove">
|
|
630
630
|
<property name="currentIndex">
|
|
631
631
|
<number>1</number>
|
|
632
632
|
</property>
|
|
@@ -634,6 +634,9 @@
|
|
|
634
634
|
<layout class="QVBoxLayout" name="verticalLayout_9">
|
|
635
635
|
<item>
|
|
636
636
|
<layout class="QFormLayout" name="formLayout">
|
|
637
|
+
<property name="rowWrapPolicy">
|
|
638
|
+
<enum>QFormLayout::RowWrapPolicy::WrapLongRows</enum>
|
|
639
|
+
</property>
|
|
637
640
|
<item row="1" column="0">
|
|
638
641
|
<widget class="QLabel" name="label_11">
|
|
639
642
|
<property name="text">
|
|
@@ -1716,6 +1719,11 @@
|
|
|
1716
1719
|
<extends>QWidget</extends>
|
|
1717
1720
|
<header>..compassmovewidget</header>
|
|
1718
1721
|
</customwidget>
|
|
1722
|
+
<customwidget>
|
|
1723
|
+
<class>MoveStack</class>
|
|
1724
|
+
<extends>QStackedWidget</extends>
|
|
1725
|
+
<header>..movestack</header>
|
|
1726
|
+
</customwidget>
|
|
1719
1727
|
</customwidgets>
|
|
1720
1728
|
<tabstops>
|
|
1721
1729
|
<tabstop>labelStatus</tabstop>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
################################################################################
|
|
4
4
|
## Form generated from reading UI file 'telescopewidget.ui'
|
|
5
5
|
##
|
|
6
|
-
## Created by: Qt User Interface Compiler version 6.11.
|
|
6
|
+
## Created by: Qt User Interface Compiler version 6.11.2
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -18,10 +18,11 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
|
|
18
18
|
from PySide6.QtWidgets import (QApplication, QComboBox, QDoubleSpinBox, QFormLayout,
|
|
19
19
|
QFrame, QGridLayout, QGroupBox, QHBoxLayout,
|
|
20
20
|
QLabel, QLineEdit, QPushButton, QSizePolicy,
|
|
21
|
-
QSpacerItem, QSpinBox,
|
|
22
|
-
|
|
21
|
+
QSpacerItem, QSpinBox, QToolButton, QVBoxLayout,
|
|
22
|
+
QWidget)
|
|
23
23
|
|
|
24
24
|
from ..compassmovewidget import CompassMoveWidget
|
|
25
|
+
from ..movestack import MoveStack
|
|
25
26
|
from . import resources_rc
|
|
26
27
|
|
|
27
28
|
class Ui_TelescopeWidget(object):
|
|
@@ -236,7 +237,7 @@ class Ui_TelescopeWidget(object):
|
|
|
236
237
|
|
|
237
238
|
self.verticalLayout_7.addWidget(self.comboMoveType)
|
|
238
239
|
|
|
239
|
-
self.stackedMove =
|
|
240
|
+
self.stackedMove = MoveStack(self.groupBox_5)
|
|
240
241
|
self.stackedMove.setObjectName(u"stackedMove")
|
|
241
242
|
self.pageMoveEquatorial = QWidget()
|
|
242
243
|
self.pageMoveEquatorial.setObjectName(u"pageMoveEquatorial")
|
|
@@ -244,6 +245,7 @@ class Ui_TelescopeWidget(object):
|
|
|
244
245
|
self.verticalLayout_9.setObjectName(u"verticalLayout_9")
|
|
245
246
|
self.formLayout = QFormLayout()
|
|
246
247
|
self.formLayout.setObjectName(u"formLayout")
|
|
248
|
+
self.formLayout.setRowWrapPolicy(QFormLayout.RowWrapPolicy.WrapLongRows)
|
|
247
249
|
self.label_11 = QLabel(self.pageMoveEquatorial)
|
|
248
250
|
self.label_11.setObjectName(u"label_11")
|
|
249
251
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import warnings
|
|
2
3
|
from enum import Enum
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
@@ -39,6 +40,12 @@ from .base import BaseWidget
|
|
|
39
40
|
|
|
40
41
|
log = logging.getLogger(__name__)
|
|
41
42
|
|
|
43
|
+
# astropy 7.2.2 / numpy 2.5.x: Angle.to_string()'s vectorized sexagesimal formatter raises a
|
|
44
|
+
# spurious "invalid value encountered in do_format" RuntimeWarning on essentially any non-zero
|
|
45
|
+
# angle, without affecting the (correct) formatted result. Silence it here rather than at every
|
|
46
|
+
# call site.
|
|
47
|
+
warnings.filterwarnings("ignore", message="invalid value encountered in do_format", category=RuntimeWarning)
|
|
48
|
+
|
|
42
49
|
|
|
43
50
|
class COORDS(Enum):
|
|
44
51
|
EQUITORIAL = "Equitorial"
|
|
@@ -726,4 +733,9 @@ class TelescopeWidget(BaseWidget, Ui_TelescopeWidget):
|
|
|
726
733
|
except ValueError:
|
|
727
734
|
return
|
|
728
735
|
self.stackedMove.setCurrentWidget(self._MOVE_WIDGETS[coord])
|
|
736
|
+
# MoveStack sizes itself to the current page, not the widest one (see movestack.py) --
|
|
737
|
+
# updateGeometry() re-triggers layout after the current page changed, so the window
|
|
738
|
+
# actually shrinks/grows to match instead of staying pinned at whatever page was
|
|
739
|
+
# previously widest.
|
|
740
|
+
self.stackedMove.updateGeometry()
|
|
729
741
|
self._DEST_CALC[coord]()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyobs-gui"
|
|
3
|
-
version = "2.4.
|
|
3
|
+
version = "2.4.2"
|
|
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"
|
|
@@ -11,8 +11,8 @@ dependencies = [
|
|
|
11
11
|
"qasync>=0.27.1,<0.29",
|
|
12
12
|
"sunpy[all]>=7.0.1",
|
|
13
13
|
"QtAwesome>=1.4.0,<2",
|
|
14
|
-
"qfitswidget>=
|
|
15
|
-
"pyobs-core>=2.7.
|
|
14
|
+
"qfitswidget>=1.1.3",
|
|
15
|
+
"pyobs-core>=2.7.2,<3",
|
|
16
16
|
"pyside6>=6.10.1",
|
|
17
17
|
"astroplan>=0.10.1",
|
|
18
18
|
"astropy>=7.0.1,<8",
|