pyobs-gui 2.2.0__tar.gz → 2.3.0__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.
Files changed (171) hide show
  1. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/PKG-INFO +1 -1
  2. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/__init__.py +1 -0
  3. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/camerawidget.py +16 -0
  4. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/mainwindow.py +16 -3
  5. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/modewidget.py +9 -2
  6. pyobs_gui-2.3.0/pyobs_gui/qt/videograbwidget.ui +257 -0
  7. pyobs_gui-2.3.0/pyobs_gui/qt/videograbwidget_ui.py +146 -0
  8. pyobs_gui-2.3.0/pyobs_gui/qt/videowidget.ui +102 -0
  9. pyobs_gui-2.3.0/pyobs_gui/qt/videowidget_ui.py +101 -0
  10. pyobs_gui-2.3.0/pyobs_gui/videograbwidget.py +143 -0
  11. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/videowidget.py +7 -95
  12. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyproject.toml +1 -1
  13. pyobs_gui-2.3.0/specs/2026-09-01-gui-video-widget-split.md +90 -0
  14. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/index.md +4 -3
  15. pyobs_gui-2.3.0/tests/test_modewidget.py +72 -0
  16. pyobs_gui-2.3.0/tests/test_videograbwidget.py +38 -0
  17. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_videowidget.py +1 -34
  18. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/uv.lock +1 -1
  19. pyobs_gui-2.2.0/pyobs_gui/qt/videowidget.ui +0 -1115
  20. pyobs_gui-2.2.0/pyobs_gui/qt/videowidget_ui.py +0 -354
  21. pyobs_gui-2.2.0/specs/2026-09-01-gui-video-widget-split.md +0 -107
  22. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.claude/skills/verify/SKILL.md +0 -0
  23. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.github/dependabot.yml +0 -0
  24. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.github/workflows/dependabot-automerge.yml +0 -0
  25. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.github/workflows/pypi.yml +0 -0
  26. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.github/workflows/pyrefly.yml +0 -0
  27. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.github/workflows/ruff.yml +0 -0
  28. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.gitignore +0 -0
  29. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.pre-commit-config.yaml +0 -0
  30. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/.readthedocs.yml +0 -0
  31. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/CHANGELOG.rst +0 -0
  32. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/LICENSE +0 -0
  33. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/README.md +0 -0
  34. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/bin/mypyuic5 +0 -0
  35. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/Makefile +0 -0
  36. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/make.bat +0 -0
  37. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/requirements.txt +0 -0
  38. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/source/_static/pyobs.gif +0 -0
  39. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/source/conf.py +0 -0
  40. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/docs/source/index.rst +0 -0
  41. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/mypy.ini +0 -0
  42. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/__main__.py +0 -0
  43. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
  44. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/accounts.py +0 -0
  45. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/acquisitionwidget.py +0 -0
  46. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/autofocuswidget.py +0 -0
  47. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/autoguidingwidget.py +0 -0
  48. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/base.py +0 -0
  49. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/commandinputwidget.py +0 -0
  50. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/compassmovewidget.py +0 -0
  51. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/coolingwidget.py +0 -0
  52. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/datadisplaywidget.py +0 -0
  53. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/eventswidget.py +0 -0
  54. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/filterwidget.py +0 -0
  55. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/fitsheaderswidget.py +0 -0
  56. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/focuswidget.py +0 -0
  57. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/gui.py +0 -0
  58. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/login.py +0 -0
  59. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/loginwindow.py +0 -0
  60. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/logmodel.py +0 -0
  61. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/modifiedmixin.py +0 -0
  62. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/modulegui.py +0 -0
  63. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/__init__.py +0 -0
  64. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
  65. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
  66. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/autofocuswidget.ui +0 -0
  67. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
  68. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
  69. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
  70. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/camerawidget.ui +0 -0
  71. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/camerawidget_ui.py +0 -0
  72. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/compassmoveplugin.py +0 -0
  73. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/compassmovewidget.py +0 -0
  74. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/compassmovewidget.ui +0 -0
  75. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
  76. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/compile.sh +0 -0
  77. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/coolingwidget.ui +0 -0
  78. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
  79. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/datadisplayplugin.py +0 -0
  80. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
  81. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
  82. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/designer.sh +0 -0
  83. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/eventswidget.ui +0 -0
  84. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/eventswidget_ui.py +0 -0
  85. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/filterwidget.ui +0 -0
  86. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/filterwidget_ui.py +0 -0
  87. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
  88. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
  89. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/focuswidget.ui +0 -0
  90. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/focuswidget_ui.py +0 -0
  91. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/mainwindow.ui +0 -0
  92. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/mainwindow_ui.py +0 -0
  93. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/modewidget.ui +0 -0
  94. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/modewidget_ui.py +0 -0
  95. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
  96. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
  97. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
  98. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
  99. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
  100. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
  101. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
  102. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
  103. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
  104. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
  105. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/search-solid.svg +0 -0
  106. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
  107. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources.qrc +0 -0
  108. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/resources_rc.py +0 -0
  109. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/roboticwidget.ui +0 -0
  110. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/roboticwidget_ui.py +0 -0
  111. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/roofwidget.ui +0 -0
  112. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/roofwidget_ui.py +0 -0
  113. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/schedulewidget.ui +0 -0
  114. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/schedulewidget_ui.py +0 -0
  115. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/shellwidget.ui +0 -0
  116. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/shellwidget_ui.py +0 -0
  117. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/spectrographwidget.ui +0 -0
  118. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
  119. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/telescopewidget.ui +0 -0
  120. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
  121. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
  122. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
  123. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/temperatureswidget.ui +0 -0
  124. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
  125. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/weatherwidget.ui +0 -0
  126. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
  127. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/roboticwidget.py +0 -0
  128. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/roofwidget.py +0 -0
  129. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/schedulewidget.py +0 -0
  130. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/shellwidget.py +0 -0
  131. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/spectrographwidget.py +0 -0
  132. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/statuswidget.py +0 -0
  133. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/structuredconfigwidget.py +0 -0
  134. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/telescopewidget.py +0 -0
  135. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/temperaturesplotwidget.py +0 -0
  136. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/temperatureswidget.py +0 -0
  137. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/utils.py +0 -0
  138. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/watchedlabel.py +0 -0
  139. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pyobs_gui/weatherwidget.py +0 -0
  140. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/pysidedeploy.spec +0 -0
  141. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/2026-08-20-gui-remote-call-error-handling.md +0 -0
  142. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/2026-08-21-gui-widget-startup-responsiveness.md +0 -0
  143. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/2026-08-28-gui-main-vs-sidebar-widgets.md +0 -0
  144. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/2026-08-28-structuredconfig-widget.md +0 -0
  145. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/specs/2026-08-31-irobotic-widgets.md +0 -0
  146. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/spike_standalone/main.py +0 -0
  147. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/spike_standalone/pysidedeploy.spec +0 -0
  148. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/acquisition.yaml +0 -0
  149. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/autofocus.yaml +0 -0
  150. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/camera.yaml +0 -0
  151. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/full.yaml +0 -0
  152. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/guiding.yaml +0 -0
  153. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/mode.yaml +0 -0
  154. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/robotic.yaml +0 -0
  155. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/roof.yaml +0 -0
  156. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/spectrograph.yaml +0 -0
  157. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/structuredconfig.yaml +0 -0
  158. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/telescope.yaml +0 -0
  159. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/telescope_acl.yaml +0 -0
  160. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/telescope_acl_denied.yaml +0 -0
  161. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/test/video.yaml +0 -0
  162. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/__init__.py +0 -0
  163. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/conftest.py +0 -0
  164. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_accounts.py +0 -0
  165. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_camerawidget.py +0 -0
  166. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_loginwindow.py +0 -0
  167. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_mainwindow_startup.py +0 -0
  168. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_multiwidget_pages.py +0 -0
  169. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_roboticwidget.py +0 -0
  170. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_schedulewidget.py +0 -0
  171. {pyobs_gui-2.2.0 → pyobs_gui-2.3.0}/tests/test_structuredconfigwidget.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-gui
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A remote GUI for pyobs
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -17,6 +17,7 @@ from .datadisplaywidget import DataDisplayWidget
17
17
  from .coolingwidget import CoolingWidget
18
18
  from .shellwidget import ShellWidget
19
19
  from .videowidget import VideoWidget
20
+ from .videograbwidget import VideoGrabWidget
20
21
  from .focuswidget import FocusWidget
21
22
  from .telescopewidget import TelescopeWidget
22
23
  from .weatherwidget import WeatherWidget
@@ -57,6 +57,15 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
57
57
  BaseWidget.__init__(self, **kwargs)
58
58
  self.setupUi(self) # type: ignore
59
59
 
60
+ # scrollArea's horizontal scrollbar is off (this panel should never need to scroll
61
+ # sideways), but with widgetResizable=True that leaves nothing to widen the scroll area
62
+ # itself to fit its content -- it was clipping the right edge of every group box
63
+ # (values/buttons ran past the visible border) because the .ui's Designer-time width hint
64
+ # (239px) is narrower than the controls' actual required width. Size it from the real
65
+ # content instead of a hardcoded guess; recomputed again at the end of _init() once
66
+ # capability-dependent combo boxes (binning, image format) have their real items.
67
+ self._size_scroll_area_to_content()
68
+
60
69
  # variables
61
70
  self.new_image = False
62
71
  self.image_filename = None
@@ -130,9 +139,16 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
130
139
  self._init_once("data_sequence", self._init_data_sequence),
131
140
  )
132
141
 
142
+ # capability-dependent combo boxes (binning, image format) may have widened the panel
143
+ # further, now that they're populated
144
+ self._size_scroll_area_to_content()
145
+
133
146
  # update GUI
134
147
  self.signal_update_gui.emit()
135
148
 
149
+ def _size_scroll_area_to_content(self) -> None:
150
+ self.scrollArea.setMinimumWidth(self.scrollAreaWidgetContents.sizeHint().width())
151
+
136
152
  async def _init_window(self) -> None:
137
153
  # window
138
154
  window_caps = await self.comm.get_capabilities(self.module, IWindow)
@@ -53,6 +53,7 @@ from .telescopewidget import TelescopeWidget
53
53
  from .focuswidget import FocusWidget
54
54
  from .temperatureswidget import TemperaturesWidget
55
55
  from .weatherwidget import WeatherWidget
56
+ from .videograbwidget import VideoGrabWidget
56
57
  from .videowidget import VideoWidget
57
58
  from .qt.mainwindow_ui import Ui_MainWindow
58
59
  from .logmodel import LogModel, LogModelProxy
@@ -83,8 +84,11 @@ class MainWidgetEntry:
83
84
  # cross-interface demotion: only promoted into `main` when nothing else matched (D1)
84
85
  sidebar_preferred: bool = False
85
86
  # same-interface pairing: always rendered together whenever this entry survives into `main`
86
- # (D6); no MAIN_WIDGETS entry sets this yet -- first consumer is the VideoWidget split,
87
- # tracked separately in specs/2026-09-01-gui-video-widget-split.md
87
+ # (D6); no MAIN_WIDGETS entry sets this yet. specs/2026-09-01-gui-video-widget-split.md's
88
+ # original plan was going to be the first consumer, but VideoWidget ended up split into two
89
+ # independent same-interface entries (see the two IVideo rows below) instead -- each tab is
90
+ # fully self-contained, with no state to share across a widget boundary, so this mechanism
91
+ # wasn't needed after all.
88
92
  paired_sidebar_widget: Optional[Type[BaseWidget]] = None
89
93
 
90
94
 
@@ -105,7 +109,16 @@ MAIN_WIDGETS: List[MainWidgetEntry] = [
105
109
  MainWidgetEntry(IAcquisition, AcquisitionWidget, "Acquisition", "mdi.target"),
106
110
  MainWidgetEntry(IAutoGuiding, AutoGuidingWidget, "Auto guiding", "mdi.crosshairs-gps"),
107
111
  MainWidgetEntry(IWeather, WeatherWidget, "Weather", "fa5s.cloud-sun"),
108
- MainWidgetEntry(IVideo, VideoWidget, "Video", "fa5s.video", sidebar=((None, FitsHeadersWidget),)),
112
+ # two entries sharing one interface: collect_main_widgets() matches every entry whose
113
+ # interface the module implements (D2), so both render as separate tabs for any IVideo
114
+ # module -- live view and the FITS-grab controls, fully independent widgets/state
115
+ # (previously one VideoWidget combining both; see
116
+ # specs/2026-09-01-gui-video-widget-split.md). Note: a custom `widgets:` config entry with
117
+ # `interface: IVideo` can only ever replace the first of these two matches (mainwindow.py's
118
+ # replace-in-place lookup keys on interface name, not registry position) -- a known, unused
119
+ # edge case, not a blocker for this pair.
120
+ MainWidgetEntry(IVideo, VideoWidget, "Live View", "fa5s.video"),
121
+ MainWidgetEntry(IVideo, VideoGrabWidget, "FITS Image", "fa5s.image", sidebar=((None, FitsHeadersWidget),)),
109
122
  MainWidgetEntry(ISpectrograph, SpectrographWidget, "Spectrograph", "ei.graph"),
110
123
  MainWidgetEntry(IFilters, FilterWidget, "Filter wheel", "mdi.air-filter", sidebar_preferred=True),
111
124
  MainWidgetEntry(ITemperatures, TemperaturesWidget, "Temperatures", "mdi.thermometer", sidebar_preferred=True),
@@ -24,6 +24,11 @@ class ModeWidget(BaseWidget, Ui_ModeWidget):
24
24
  self._mode_options: dict[str, list[str]] = {}
25
25
  self._modes: dict[str, str] = {}
26
26
  self._motion_status = MotionStatus.UNKNOWN
27
+ # whether self.module implements IMotion at all -- an IMode module doesn't have to be a
28
+ # motion device (e.g. a plain camera's fiber-hole/video-path selector), and gating the
29
+ # edit button on motion status for one that isn't left it permanently disabled: the
30
+ # IMotion state subscription below never fires, so _motion_status never leaves UNKNOWN
31
+ self._has_motion = False
27
32
  self._mode_widgets: dict[str, tuple[QtWidgets.QLineEdit, QtWidgets.QToolButton]] = {}
28
33
 
29
34
  # connect signals
@@ -42,7 +47,9 @@ class ModeWidget(BaseWidget, Ui_ModeWidget):
42
47
  await self.register_event(ModeChangedEvent, self._on_mode_changed)
43
48
 
44
49
  async def _init(self) -> None:
45
- await self.comm.subscribe_state(self.module, IMotion, self._on_motion_state)
50
+ self._has_motion = await self.comm.has_proxy(self.module, IMotion)
51
+ if self._has_motion:
52
+ await self.comm.subscribe_state(self.module, IMotion, self._on_motion_state)
46
53
 
47
54
  # permitted methods (ACLs)
48
55
  await self._fetch_permitted_methods()
@@ -80,7 +87,7 @@ class ModeWidget(BaseWidget, Ui_ModeWidget):
80
87
  def update_gui(self) -> None:
81
88
  self.setEnabled(True)
82
89
  self.textStatus.setText(self._motion_status.name)
83
- initialized = self._motion_status in [
90
+ initialized = not self._has_motion or self._motion_status in [
84
91
  MotionStatus.SLEWING,
85
92
  MotionStatus.TRACKING,
86
93
  MotionStatus.IDLE,
@@ -0,0 +1,257 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>VideoGrabWidget</class>
4
+ <widget class="QWidget" name="VideoGrabWidget">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>618</width>
10
+ <height>530</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Form</string>
15
+ </property>
16
+ <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,0">
17
+ <item>
18
+ <widget class="QFrame" name="frame_2">
19
+ <property name="frameShape">
20
+ <enum>QFrame::Shape::StyledPanel</enum>
21
+ </property>
22
+ <property name="frameShadow">
23
+ <enum>QFrame::Shadow::Raised</enum>
24
+ </property>
25
+ <layout class="QVBoxLayout" name="verticalLayout">
26
+ <item>
27
+ <widget class="QGroupBox" name="groupExposure_2">
28
+ <property name="title">
29
+ <string>Exposure</string>
30
+ </property>
31
+ <layout class="QGridLayout" name="gridLayout_5">
32
+ <property name="spacing">
33
+ <number>0</number>
34
+ </property>
35
+ <item row="0" column="0">
36
+ <widget class="QLabel" name="labelImageType">
37
+ <property name="text">
38
+ <string>Type:</string>
39
+ </property>
40
+ </widget>
41
+ </item>
42
+ <item row="1" column="0">
43
+ <widget class="QLabel" name="label_9">
44
+ <property name="text">
45
+ <string>Count:</string>
46
+ </property>
47
+ </widget>
48
+ </item>
49
+ <item row="2" column="1">
50
+ <widget class="QCheckBox" name="checkBroadcast">
51
+ <property name="text">
52
+ <string>Broadcast</string>
53
+ </property>
54
+ <property name="checked">
55
+ <bool>true</bool>
56
+ </property>
57
+ </widget>
58
+ </item>
59
+ <item row="3" column="0" colspan="2">
60
+ <widget class="QPushButton" name="buttonGrabImage">
61
+ <property name="palette">
62
+ <palette>
63
+ <active>
64
+ <colorrole role="Button">
65
+ <brush brushstyle="SolidPattern">
66
+ <color alpha="255">
67
+ <red>0</red>
68
+ <green>85</green>
69
+ <blue>0</blue>
70
+ </color>
71
+ </brush>
72
+ </colorrole>
73
+ <colorrole role="ButtonText">
74
+ <brush brushstyle="SolidPattern">
75
+ <color alpha="255">
76
+ <red>255</red>
77
+ <green>255</green>
78
+ <blue>255</blue>
79
+ </color>
80
+ </brush>
81
+ </colorrole>
82
+ </active>
83
+ <inactive>
84
+ <colorrole role="Button">
85
+ <brush brushstyle="SolidPattern">
86
+ <color alpha="255">
87
+ <red>0</red>
88
+ <green>85</green>
89
+ <blue>0</blue>
90
+ </color>
91
+ </brush>
92
+ </colorrole>
93
+ <colorrole role="ButtonText">
94
+ <brush brushstyle="SolidPattern">
95
+ <color alpha="255">
96
+ <red>255</red>
97
+ <green>255</green>
98
+ <blue>255</blue>
99
+ </color>
100
+ </brush>
101
+ </colorrole>
102
+ </inactive>
103
+ <disabled>
104
+ <colorrole role="Button">
105
+ <brush brushstyle="SolidPattern">
106
+ <color alpha="255">
107
+ <red>0</red>
108
+ <green>85</green>
109
+ <blue>0</blue>
110
+ </color>
111
+ </brush>
112
+ </colorrole>
113
+ <colorrole role="ButtonText">
114
+ <brush brushstyle="SolidPattern">
115
+ <color alpha="255">
116
+ <red>0</red>
117
+ <green>42</green>
118
+ <blue>0</blue>
119
+ </color>
120
+ </brush>
121
+ </colorrole>
122
+ </disabled>
123
+ </palette>
124
+ </property>
125
+ <property name="text">
126
+ <string>Grab image</string>
127
+ </property>
128
+ </widget>
129
+ </item>
130
+ <item row="1" column="1">
131
+ <widget class="QSpinBox" name="spinCount">
132
+ <property name="minimum">
133
+ <number>1</number>
134
+ </property>
135
+ <property name="maximum">
136
+ <number>9999</number>
137
+ </property>
138
+ </widget>
139
+ </item>
140
+ <item row="0" column="1">
141
+ <widget class="QComboBox" name="comboImageType"/>
142
+ </item>
143
+ <item row="4" column="0" colspan="2">
144
+ <widget class="QPushButton" name="buttonAbort">
145
+ <property name="palette">
146
+ <palette>
147
+ <active>
148
+ <colorrole role="Button">
149
+ <brush brushstyle="SolidPattern">
150
+ <color alpha="255">
151
+ <red>170</red>
152
+ <green>0</green>
153
+ <blue>0</blue>
154
+ </color>
155
+ </brush>
156
+ </colorrole>
157
+ <colorrole role="ButtonText">
158
+ <brush brushstyle="SolidPattern">
159
+ <color alpha="255">
160
+ <red>0</red>
161
+ <green>0</green>
162
+ <blue>0</blue>
163
+ </color>
164
+ </brush>
165
+ </colorrole>
166
+ </active>
167
+ <inactive>
168
+ <colorrole role="Button">
169
+ <brush brushstyle="SolidPattern">
170
+ <color alpha="255">
171
+ <red>170</red>
172
+ <green>0</green>
173
+ <blue>0</blue>
174
+ </color>
175
+ </brush>
176
+ </colorrole>
177
+ <colorrole role="ButtonText">
178
+ <brush brushstyle="SolidPattern">
179
+ <color alpha="255">
180
+ <red>0</red>
181
+ <green>0</green>
182
+ <blue>0</blue>
183
+ </color>
184
+ </brush>
185
+ </colorrole>
186
+ </inactive>
187
+ <disabled>
188
+ <colorrole role="Button">
189
+ <brush brushstyle="SolidPattern">
190
+ <color alpha="255">
191
+ <red>170</red>
192
+ <green>0</green>
193
+ <blue>0</blue>
194
+ </color>
195
+ </brush>
196
+ </colorrole>
197
+ <colorrole role="ButtonText">
198
+ <brush brushstyle="SolidPattern">
199
+ <color alpha="255">
200
+ <red>85</red>
201
+ <green>0</green>
202
+ <blue>0</blue>
203
+ </color>
204
+ </brush>
205
+ </colorrole>
206
+ </disabled>
207
+ </palette>
208
+ </property>
209
+ <property name="text">
210
+ <string>Abort sequence</string>
211
+ </property>
212
+ </widget>
213
+ </item>
214
+ </layout>
215
+ </widget>
216
+ </item>
217
+ <item>
218
+ <spacer name="verticalSpacer_2">
219
+ <property name="orientation">
220
+ <enum>Qt::Orientation::Vertical</enum>
221
+ </property>
222
+ <property name="sizeHint" stdset="0">
223
+ <size>
224
+ <width>20</width>
225
+ <height>40</height>
226
+ </size>
227
+ </property>
228
+ </spacer>
229
+ </item>
230
+ <item>
231
+ <widget class="QLabel" name="labelExposuresLeft">
232
+ <property name="text">
233
+ <string>IDLE</string>
234
+ </property>
235
+ <property name="alignment">
236
+ <set>Qt::AlignmentFlag::AlignCenter</set>
237
+ </property>
238
+ </widget>
239
+ </item>
240
+ </layout>
241
+ </widget>
242
+ </item>
243
+ <item>
244
+ <widget class="DataDisplayWidget" name="datadisplay" native="true"/>
245
+ </item>
246
+ </layout>
247
+ </widget>
248
+ <customwidgets>
249
+ <customwidget>
250
+ <class>DataDisplayWidget</class>
251
+ <extends>QWidget</extends>
252
+ <header>..datadisplaywidget</header>
253
+ </customwidget>
254
+ </customwidgets>
255
+ <resources/>
256
+ <connections/>
257
+ </ui>
@@ -0,0 +1,146 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ################################################################################
4
+ ## Form generated from reading UI file 'videograbwidget.ui'
5
+ ##
6
+ ## Created by: Qt User Interface Compiler version 6.11.2
7
+ ##
8
+ ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
+ ################################################################################
10
+
11
+ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
12
+ QMetaObject, QObject, QPoint, QRect,
13
+ QSize, QTime, QUrl, Qt)
14
+ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15
+ QFont, QFontDatabase, QGradient, QIcon,
16
+ QImage, QKeySequence, QLinearGradient, QPainter,
17
+ QPalette, QPixmap, QRadialGradient, QTransform)
18
+ from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFrame,
19
+ QGridLayout, QGroupBox, QHBoxLayout, QLabel,
20
+ QPushButton, QSizePolicy, QSpacerItem, QSpinBox,
21
+ QVBoxLayout, QWidget)
22
+
23
+ from ..datadisplaywidget import DataDisplayWidget
24
+
25
+ class Ui_VideoGrabWidget(object):
26
+ def setupUi(self, VideoGrabWidget):
27
+ if not VideoGrabWidget.objectName():
28
+ VideoGrabWidget.setObjectName(u"VideoGrabWidget")
29
+ VideoGrabWidget.resize(618, 530)
30
+ self.horizontalLayout_3 = QHBoxLayout(VideoGrabWidget)
31
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
32
+ self.frame_2 = QFrame(VideoGrabWidget)
33
+ self.frame_2.setObjectName(u"frame_2")
34
+ self.frame_2.setFrameShape(QFrame.Shape.StyledPanel)
35
+ self.frame_2.setFrameShadow(QFrame.Shadow.Raised)
36
+ self.verticalLayout = QVBoxLayout(self.frame_2)
37
+ self.verticalLayout.setObjectName(u"verticalLayout")
38
+ self.groupExposure_2 = QGroupBox(self.frame_2)
39
+ self.groupExposure_2.setObjectName(u"groupExposure_2")
40
+ self.gridLayout_5 = QGridLayout(self.groupExposure_2)
41
+ self.gridLayout_5.setSpacing(0)
42
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
43
+ self.labelImageType = QLabel(self.groupExposure_2)
44
+ self.labelImageType.setObjectName(u"labelImageType")
45
+
46
+ self.gridLayout_5.addWidget(self.labelImageType, 0, 0, 1, 1)
47
+
48
+ self.label_9 = QLabel(self.groupExposure_2)
49
+ self.label_9.setObjectName(u"label_9")
50
+
51
+ self.gridLayout_5.addWidget(self.label_9, 1, 0, 1, 1)
52
+
53
+ self.checkBroadcast = QCheckBox(self.groupExposure_2)
54
+ self.checkBroadcast.setObjectName(u"checkBroadcast")
55
+ self.checkBroadcast.setChecked(True)
56
+
57
+ self.gridLayout_5.addWidget(self.checkBroadcast, 2, 1, 1, 1)
58
+
59
+ self.buttonGrabImage = QPushButton(self.groupExposure_2)
60
+ self.buttonGrabImage.setObjectName(u"buttonGrabImage")
61
+ palette = QPalette()
62
+ brush = QBrush(QColor(0, 85, 0, 255))
63
+ brush.setStyle(Qt.BrushStyle.SolidPattern)
64
+ palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.Button, brush)
65
+ brush1 = QBrush(QColor(255, 255, 255, 255))
66
+ brush1.setStyle(Qt.BrushStyle.SolidPattern)
67
+ palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.ButtonText, brush1)
68
+ palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Button, brush)
69
+ palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.ButtonText, brush1)
70
+ palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, brush)
71
+ brush2 = QBrush(QColor(0, 42, 0, 255))
72
+ brush2.setStyle(Qt.BrushStyle.SolidPattern)
73
+ palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, brush2)
74
+ self.buttonGrabImage.setPalette(palette)
75
+
76
+ self.gridLayout_5.addWidget(self.buttonGrabImage, 3, 0, 1, 2)
77
+
78
+ self.spinCount = QSpinBox(self.groupExposure_2)
79
+ self.spinCount.setObjectName(u"spinCount")
80
+ self.spinCount.setMinimum(1)
81
+ self.spinCount.setMaximum(9999)
82
+
83
+ self.gridLayout_5.addWidget(self.spinCount, 1, 1, 1, 1)
84
+
85
+ self.comboImageType = QComboBox(self.groupExposure_2)
86
+ self.comboImageType.setObjectName(u"comboImageType")
87
+
88
+ self.gridLayout_5.addWidget(self.comboImageType, 0, 1, 1, 1)
89
+
90
+ self.buttonAbort = QPushButton(self.groupExposure_2)
91
+ self.buttonAbort.setObjectName(u"buttonAbort")
92
+ palette1 = QPalette()
93
+ brush3 = QBrush(QColor(170, 0, 0, 255))
94
+ brush3.setStyle(Qt.BrushStyle.SolidPattern)
95
+ palette1.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.Button, brush3)
96
+ brush4 = QBrush(QColor(0, 0, 0, 255))
97
+ brush4.setStyle(Qt.BrushStyle.SolidPattern)
98
+ palette1.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.ButtonText, brush4)
99
+ palette1.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Button, brush3)
100
+ palette1.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.ButtonText, brush4)
101
+ palette1.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, brush3)
102
+ brush5 = QBrush(QColor(85, 0, 0, 255))
103
+ brush5.setStyle(Qt.BrushStyle.SolidPattern)
104
+ palette1.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, brush5)
105
+ self.buttonAbort.setPalette(palette1)
106
+
107
+ self.gridLayout_5.addWidget(self.buttonAbort, 4, 0, 1, 2)
108
+
109
+
110
+ self.verticalLayout.addWidget(self.groupExposure_2)
111
+
112
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
113
+
114
+ self.verticalLayout.addItem(self.verticalSpacer_2)
115
+
116
+ self.labelExposuresLeft = QLabel(self.frame_2)
117
+ self.labelExposuresLeft.setObjectName(u"labelExposuresLeft")
118
+ self.labelExposuresLeft.setAlignment(Qt.AlignmentFlag.AlignCenter)
119
+
120
+ self.verticalLayout.addWidget(self.labelExposuresLeft)
121
+
122
+
123
+ self.horizontalLayout_3.addWidget(self.frame_2)
124
+
125
+ self.datadisplay = DataDisplayWidget(VideoGrabWidget)
126
+ self.datadisplay.setObjectName(u"datadisplay")
127
+
128
+ self.horizontalLayout_3.addWidget(self.datadisplay)
129
+
130
+
131
+ self.retranslateUi(VideoGrabWidget)
132
+
133
+ QMetaObject.connectSlotsByName(VideoGrabWidget)
134
+ # setupUi
135
+
136
+ def retranslateUi(self, VideoGrabWidget):
137
+ VideoGrabWidget.setWindowTitle(QCoreApplication.translate("VideoGrabWidget", u"Form", None))
138
+ self.groupExposure_2.setTitle(QCoreApplication.translate("VideoGrabWidget", u"Exposure", None))
139
+ self.labelImageType.setText(QCoreApplication.translate("VideoGrabWidget", u"Type:", None))
140
+ self.label_9.setText(QCoreApplication.translate("VideoGrabWidget", u"Count:", None))
141
+ self.checkBroadcast.setText(QCoreApplication.translate("VideoGrabWidget", u"Broadcast", None))
142
+ self.buttonGrabImage.setText(QCoreApplication.translate("VideoGrabWidget", u"Grab image", None))
143
+ self.buttonAbort.setText(QCoreApplication.translate("VideoGrabWidget", u"Abort sequence", None))
144
+ self.labelExposuresLeft.setText(QCoreApplication.translate("VideoGrabWidget", u"IDLE", None))
145
+ # retranslateUi
146
+
@@ -0,0 +1,102 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>VideoWidget</class>
4
+ <widget class="QWidget" name="VideoWidget">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>618</width>
10
+ <height>530</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Form</string>
15
+ </property>
16
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
17
+ <item>
18
+ <widget class="QFrame" name="frame">
19
+ <property name="frameShape">
20
+ <enum>QFrame::Shape::StyledPanel</enum>
21
+ </property>
22
+ <property name="frameShadow">
23
+ <enum>QFrame::Shadow::Raised</enum>
24
+ </property>
25
+ <layout class="QVBoxLayout" name="verticalLayout_2">
26
+ <item>
27
+ <widget class="QGroupBox" name="groupExposure">
28
+ <property name="title">
29
+ <string/>
30
+ </property>
31
+ <layout class="QFormLayout" name="formLayout">
32
+ <item row="0" column="1">
33
+ <widget class="QDoubleSpinBox" name="spinExpTime">
34
+ <property name="suffix">
35
+ <string> s</string>
36
+ </property>
37
+ <property name="decimals">
38
+ <number>6</number>
39
+ </property>
40
+ <property name="maximum">
41
+ <double>999.000000000000000</double>
42
+ </property>
43
+ <property name="value">
44
+ <double>1.000000000000000</double>
45
+ </property>
46
+ </widget>
47
+ </item>
48
+ <item row="0" column="0">
49
+ <widget class="QLabel" name="labelExpTime">
50
+ <property name="text">
51
+ <string>ExpTime:</string>
52
+ </property>
53
+ </widget>
54
+ </item>
55
+ </layout>
56
+ </widget>
57
+ </item>
58
+ <item>
59
+ <widget class="QGroupBox" name="groupGain">
60
+ <property name="title">
61
+ <string/>
62
+ </property>
63
+ <layout class="QGridLayout" name="gridLayout">
64
+ <item row="0" column="0">
65
+ <widget class="QLabel" name="label_11">
66
+ <property name="text">
67
+ <string>Gain:</string>
68
+ </property>
69
+ </widget>
70
+ </item>
71
+ <item row="0" column="1">
72
+ <widget class="QDoubleSpinBox" name="spinGain"/>
73
+ </item>
74
+ </layout>
75
+ </widget>
76
+ </item>
77
+ <item>
78
+ <spacer name="verticalSpacer">
79
+ <property name="orientation">
80
+ <enum>Qt::Orientation::Vertical</enum>
81
+ </property>
82
+ <property name="sizeHint" stdset="0">
83
+ <size>
84
+ <width>20</width>
85
+ <height>340</height>
86
+ </size>
87
+ </property>
88
+ </spacer>
89
+ </item>
90
+ </layout>
91
+ </widget>
92
+ </item>
93
+ <item>
94
+ <widget class="QWidget" name="frameLiveView" native="true">
95
+ <layout class="QVBoxLayout" name="verticalLayout_4"/>
96
+ </widget>
97
+ </item>
98
+ </layout>
99
+ </widget>
100
+ <resources/>
101
+ <connections/>
102
+ </ui>