pcdswidgets 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.
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/PKG-INFO +1 -1
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/_version.py +3 -3
- pcdswidgets-2.3.0/pcdswidgets/common/tools/view_saver/registry.py +140 -0
- pcdswidgets-2.3.0/pcdswidgets/common/tools/view_saver/view_saver.py +389 -0
- pcdswidgets-2.3.0/pcdswidgets/common/tools/view_saver/view_saver_dialog.py +145 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/path_defs.py +1 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/camera_viewer_stretch.py +1 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/centroid_tracker_full.py +23 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/collapsible_section.py +21 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/colormap_intesity_control_full.py +23 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/epics_roi_full.py +15 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/marker_selection_full.py +24 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_tip_tilt_full.py +19 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GHCPLC_expert.ui +8 -2
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PIPPLC_detailed.ui +16 -10
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PIPPLC_expert.ui +11 -5
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PTMPLC_expert.ui +88 -73
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VFS_detailed.ui +4 -4
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VFS_expert.ui +46 -5
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VGC_detailed.ui +35 -4
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VGC_expert.ui +196 -5
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/imaging/test_colormap_intesity_control_full.py +11 -3
- pcdswidgets-2.3.0/pcdswidgets/vacuum/demo/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/PKG-INFO +1 -1
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/SOURCES.txt +4 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/entry_points.txt +1 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/scm_file_list.json +4 -0
- pcdswidgets-2.3.0/pcdswidgets.egg-info/scm_version.json +8 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pyproject.toml +1 -0
- pcdswidgets-2.2.0/pcdswidgets.egg-info/scm_version.json +0 -8
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.coveragerc +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.git_archival.txt +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.gitattributes +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.github/CODEOWNERS +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.github/dependabot.yml +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.github/workflows/pixi_test.yml +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.gitignore +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.pre-commit-config.yaml +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/.python-version +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/AUTHORS.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/CONTRIBUTING.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/LICENSE.md +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/MANIFEST.in +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/Makefile +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/README.md +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/conda-recipe/meta.yaml +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/Makefile +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/_static/content_location.png +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/_static/icons/.gitignore +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/addwidget.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/conf.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/dev.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/icons.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/index.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/mixins.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/utils.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/vacuum/gauges.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/vacuum/others.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/vacuum/pumps.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/vacuum/valves.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/docs/source/vacuum.rst +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/basic_table.json +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/basic_table.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/basic_table.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/basic_table_row.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/show_symbol.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/symbols.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/valve.db +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/examples/valve.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/build.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/designer_options.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/designer_widget.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/entrypoint_finder.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/get_icon_options.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/icon_options.j2 +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/icon_options.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/inits.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/path_defs.py.j2 +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/read_ui.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/screen_paths.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/ui_base_widget.j2 +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/ui_main_widget.j2 +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/builder/valid_glyph_map.json +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/dock/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/dock/indicator_grid.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/dock/tab_dock.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/dock/tab_dock_button.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/toolbar/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/toolbar/yaml_toolbar.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/tools/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/tools/feature_finder.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/common/tools/pv_channel.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated → pcdswidgets-2.3.0/pcdswidgets/common/tools/view_saver}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/eps_byteindicator.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/README.md +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/common → pcdswidgets-2.3.0/pcdswidgets/generated}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/common/tools → pcdswidgets-2.3.0/pcdswidgets/generated/common}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/imaging → pcdswidgets-2.3.0/pcdswidgets/generated/common/tools}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/common/tools/feature_finder_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/common/tools/feature_finder_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/imaging/common → pcdswidgets-2.3.0/pcdswidgets/generated/imaging}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/motion → pcdswidgets-2.3.0/pcdswidgets/generated/imaging/common}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/acquisition_control_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/acquisition_control_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/camera_viewer_stretch_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/camera_viewer_stretch_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/centroid_tracker_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/centroid_tracker_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/colormap_intesity_control_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/colormap_intesity_control_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/epics_roi_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/epics_roi_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/exposure_timing_control_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/exposure_timing_control_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/marker_selection_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/imaging/common/marker_selection_full_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/motion/common → pcdswidgets-2.3.0/pcdswidgets/generated/motion}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/motion/expert → pcdswidgets-2.3.0/pcdswidgets/generated/motion/common}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_beckhoff_slits_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_beckhoff_slits_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_full_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_row_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_row_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_vert_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_classic_vert_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_state_mover_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_state_mover_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tc_classic_row_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tc_classic_row_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tip_tilt_double_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tip_tilt_double_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tip_tilt_full_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/common/motor_tip_tilt_full_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/motion/smaract → pcdswidgets-2.3.0/pcdswidgets/generated/motion/expert}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/expert/motor_expert_screen_beckhoff_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/expert/motor_expert_screen_beckhoff_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/tests → pcdswidgets-2.3.0/pcdswidgets/generated/motion/smaract}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/smaract/smaract_open_loop_context_double_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/motion/smaract/smaract_open_loop_context_double_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/generated/tests/builder → pcdswidgets-2.3.0/pcdswidgets/generated/tests}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/icons → pcdswidgets-2.3.0/pcdswidgets/generated/tests/builder}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/tests/builder/builder_basic_test_widget_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/tests/builder/builder_basic_test_widget_form.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/tests/builder/builder_filepath_test_widget_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/generated/tests/builder/builder_filepath_test_widget_form.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/imaging → pcdswidgets-2.3.0/pcdswidgets/icons}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/feature_finder_icon.png +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/camera-cog.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/crosshair.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/disconnected.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/download.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/error.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/eye-off.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/eye.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/move.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/moving.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/ok.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/pen-tool.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/scissors.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/thickness.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/trash.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs/warning.svg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/glyphs.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/lcls.png +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/multi_state_led_icon.png +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/smaract_tip_tilt_context_qt_icon.png +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/icons/smaract_tip_tilt_qt_icon.png +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/imaging/common → pcdswidgets-2.3.0/pcdswidgets/imaging}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/motion → pcdswidgets-2.3.0/pcdswidgets/imaging/common}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/acquisition_control_full.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/cam_marker.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/cam_roi.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/centroid_marker_style_dialog.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/colorselect_button.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/exposure_timing_control_full.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/marker_style_dialog.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/imaging/common/spinbox.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/motion/common → pcdswidgets-2.3.0/pcdswidgets/motion}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/motion/expert → pcdswidgets-2.3.0/pcdswidgets/motion/common}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_beckhoff_slits.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_classic_full.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_classic_row.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_classic_vert.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_state_mover.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_state_mover_expanded.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_style.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_tc_classic_row.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_tip_tilt.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/motor_tip_tilt_double.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/state_mover_common.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/common/svg_multi_state_led.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/motion/smaract → pcdswidgets-2.3.0/pcdswidgets/motion/expert}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/expert/motor_expert_screen_beckhoff.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/symbols/demo → pcdswidgets-2.3.0/pcdswidgets/motion/smaract}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/smaract/smaract_open_loop_classic_row.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/motion/smaract/smaract_open_loop_context_double.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/README.md +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/motion/motor_state_mover_expert.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GCCPLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GCCPLC_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GFSPLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GFSPLC_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GHCPLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GaugePLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/gauges/GaugePLC_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/Kashiyama_G_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/Kashiyama_G_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PIPCombined_controller.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PIPCombined_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PIPCombined_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PROPLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/pumps/PTMPLC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VCN_VAT590_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VCN_VAT590_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VCN_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VCN_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VGC_2S_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VGC_2S_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRCDA_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRCDA_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRCNO_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRCNO_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VRC_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VVCNO_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VVCNO_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VVC_detailed.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/screens/vacuum/valves/VVC_expert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/show_screen.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/stylesheets/masterStyleSheet.qss +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/base.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests → pcdswidgets-2.3.0/pcdswidgets/symbols/demo}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/demo/__main__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/gauges.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/others.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/pumps.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/symbols/valves.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/table.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests/builder → pcdswidgets-2.3.0/pcdswidgets/tests}/__init__.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests/dock → pcdswidgets-2.3.0/pcdswidgets/tests/builder}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/builder_basic_test_widget.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/builder_filepath_test_widget.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/subdisplay.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/test_builder.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/test_designer_widget.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/builder/test_entrypoint_widgets.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/conftest.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests/imaging → pcdswidgets-2.3.0/pcdswidgets/tests/dock}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/dock/dock1.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/dock/dock2.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/dock/test_dock.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/dock/test_dock_button.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/dock/test_indicator_grid.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests/toolbar → pcdswidgets-2.3.0/pcdswidgets/tests/imaging}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/test_show_screen.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/test_symbols.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/tests/vacuum → pcdswidgets-2.3.0/pcdswidgets/tests/toolbar}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/toolbar/test_yaml_layout.py +0 -0
- {pcdswidgets-2.2.0/pcdswidgets/vacuum/demo → pcdswidgets-2.3.0/pcdswidgets/tests/vacuum}/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/vacuum/test_base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/vacuum/test_mixins.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/tests/vacuum/test_symbols.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/README.md +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/common/tools/feature_finder.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/acquisition_control_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/camera_viewer_stretch.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/centroid_tracker_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/colormap_intesity_control_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/epics_roi_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/exposure_timing_control_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/imaging/common/marker_selection_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_beckhoff_slits.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_classic_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_classic_row.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_classic_vert.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_state_mover.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_tc_classic_row.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_tip_tilt_double.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/common/motor_tip_tilt_full.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/expert/motor_expert_screen_beckhoff.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/smaract/smaract_open_loop_classic_row.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/motion/smaract/smaract_open_loop_context_double.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/tests/builder/builder_basic_test_widget.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/ui/tests/builder/builder_filepath_test_widget.ui +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/utils.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/__init__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/base.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/demo/__main__.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/gauges.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/mixins.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/others.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/pumps.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/vacuum/valves.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets/version.py +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/dependency_links.txt +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/requires.txt +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pcdswidgets.egg-info/top_level.txt +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pixi.lock +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pixi.toml +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pixi_scripts/env_setup.sh +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/pixi_scripts/install.sh +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/setup.cfg +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/show_icon_options.sh +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/try_in_designer.sh +0 -0
- {pcdswidgets-2.2.0 → pcdswidgets-2.3.0}/try_in_pydm.sh +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gccf31454c'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Fallback registry of savable widget properties for ViewSaver.
|
|
2
|
+
|
|
3
|
+
This registry is the exception path used for widget classes
|
|
4
|
+
we do not plan to override/subclasswith a ``get_view_saver_properties`` method.
|
|
5
|
+
- i.e. built-in Qt widgets and third-party widgets
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from operator import attrgetter
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
|
|
13
|
+
from qtpy.QtWidgets import QWidget
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _identity(value: Any) -> Any:
|
|
19
|
+
"""No-op converter, for values QSettings already round-trips faithfully."""
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _json_default(obj: Any) -> Any:
|
|
24
|
+
"""Fallback encoder for values ``json.dumps`` can't handle natively.
|
|
25
|
+
|
|
26
|
+
``ViewBox.getState()`` embeds numpy arrays; convert to lists.
|
|
27
|
+
"""
|
|
28
|
+
if hasattr(obj, "tolist"):
|
|
29
|
+
return obj.tolist()
|
|
30
|
+
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _json_dumps(value: Any) -> str:
|
|
34
|
+
"""``json.dumps`` that tolerates numpy arrays/scalars via ``_json_default``."""
|
|
35
|
+
return json.dumps(value, default=_json_default)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _make_getter(widget: QWidget, path: str, save_fn: Callable) -> Callable:
|
|
39
|
+
"""wrap the widget property getter to return the encoded value.
|
|
40
|
+
|
|
41
|
+
``save_fn`` turns value to what QSettings stores (usually string)
|
|
42
|
+
"""
|
|
43
|
+
method = attrgetter(path)(widget)
|
|
44
|
+
return lambda: save_fn(method())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _make_setter(widget: QWidget, path: str, load_fn: Callable) -> Callable:
|
|
48
|
+
"""wrap the widget property setter to first decode then apply a value.
|
|
49
|
+
|
|
50
|
+
``load_fn`` decodes the raw QSettings value -> expected type;
|
|
51
|
+
|
|
52
|
+
Special case: tuples are splatted into positional args for
|
|
53
|
+
multi-argument setters (e.g. ``set_levels(mn, mx)``)
|
|
54
|
+
"""
|
|
55
|
+
method = attrgetter(path)(widget)
|
|
56
|
+
|
|
57
|
+
def setter(raw: Any) -> None:
|
|
58
|
+
value = load_fn(raw)
|
|
59
|
+
if isinstance(value, tuple):
|
|
60
|
+
method(*value)
|
|
61
|
+
else:
|
|
62
|
+
method(value)
|
|
63
|
+
|
|
64
|
+
return setter
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# dict of properties that should have persistance given a ClassName
|
|
68
|
+
#
|
|
69
|
+
# Format is:
|
|
70
|
+
# Qt class name -> { propKey: (getter, setter, load_fn, save_fn) }
|
|
71
|
+
#
|
|
72
|
+
# ``getter``/``setter`` are a (possibly dotted) attribute path resolved on the
|
|
73
|
+
# widget, each of which must resolve to a bound method.
|
|
74
|
+
#
|
|
75
|
+
# ``load_fn`` decodes the raw value read from QSettings (IniFormat stores
|
|
76
|
+
# everything as a string) back to the type the setter expects.
|
|
77
|
+
#
|
|
78
|
+
# ``save_fn`` encodes the getter's return value into what is written to
|
|
79
|
+
# QSettings, and must yield a string: use ``str`` for scalars and
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Only built-in / third-party classes we do not subclass belong here; every
|
|
83
|
+
# widget we own defines ``get_view_saver_properties`` instead.
|
|
84
|
+
WIDGET_REGISTRY: dict[str, dict[str, tuple[str, str, Callable, Callable]]] = {
|
|
85
|
+
# QT BASE
|
|
86
|
+
"QTabWidget": {"currentIndex": ("currentIndex", "setCurrentIndex", int, str)},
|
|
87
|
+
"QComboBox": {"currentIndex": ("currentIndex", "setCurrentIndex", int, str)},
|
|
88
|
+
"QGroupBox": {"checked": ("isChecked", "setChecked", json.loads, json.dumps)},
|
|
89
|
+
"QCheckBox": {"checked": ("isChecked", "setChecked", json.loads, json.dumps)},
|
|
90
|
+
"QPushButton": {"checked": ("isChecked", "setChecked", json.loads, json.dumps)},
|
|
91
|
+
"QSplitter": {"state": ("saveState", "restoreState", _identity, _identity)},
|
|
92
|
+
# Imaging (PyDM widget, not subclassed by us)
|
|
93
|
+
"PyDMImageView": {
|
|
94
|
+
"view": ("view.vb.getState", "view.vb.setState", json.loads, _json_dumps),
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Registered classes that are also containers of other savable widgets.
|
|
99
|
+
CONTAINER_REGISTRY_CLASSES: set[str] = {
|
|
100
|
+
"QSplitter",
|
|
101
|
+
"QTabWidget",
|
|
102
|
+
"QGroupBox",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_registry_savable(widget: QWidget) -> bool:
|
|
107
|
+
"""True if *widget*'s class has fallback properties in ``WIDGET_REGISTRY``."""
|
|
108
|
+
return type(widget).__name__ in WIDGET_REGISTRY
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_registry_container(widget: QWidget) -> bool:
|
|
112
|
+
"""True if *widget*'s class is a registered container of other savables."""
|
|
113
|
+
return type(widget).__name__ in CONTAINER_REGISTRY_CLASSES
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def resolve_registry_props(
|
|
117
|
+
widget: QWidget,
|
|
118
|
+
) -> dict[str, tuple[Callable, Callable]] | None:
|
|
119
|
+
"""Resolve getter/setter callables for each registered property of *widget*.
|
|
120
|
+
|
|
121
|
+
Fallback used only for classes without ``get_view_saver_properties`` (see
|
|
122
|
+
module docstring). Returns ``{propKey: (getter, setter)}`` where the getter
|
|
123
|
+
yields a QSettings-storable value and the setter accepts the raw stored
|
|
124
|
+
value, or ``None`` if the class is not registered.
|
|
125
|
+
"""
|
|
126
|
+
class_name = type(widget).__name__
|
|
127
|
+
props = WIDGET_REGISTRY.get(class_name)
|
|
128
|
+
if props is None:
|
|
129
|
+
logger.error(f"No registered properties for {class_name}")
|
|
130
|
+
return None
|
|
131
|
+
resolved_props: dict[str, tuple[Callable, Callable]] = {}
|
|
132
|
+
for prop_name, (getter, setter, load_fn, save_fn) in props.items():
|
|
133
|
+
try:
|
|
134
|
+
resolved_props[prop_name] = (
|
|
135
|
+
_make_getter(widget, getter, save_fn),
|
|
136
|
+
_make_setter(widget, setter, load_fn),
|
|
137
|
+
)
|
|
138
|
+
except AttributeError:
|
|
139
|
+
logger.exception(f"ViewSaver: could not resolve {class_name}.{prop_name}, skipping")
|
|
140
|
+
return resolved_props
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""ViewSaver widget — persists and restores widget states via QSettings."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import string
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
from pydm.utilities import is_qt_designer
|
|
11
|
+
from pydm.utilities.iconfont import IconFont
|
|
12
|
+
from pydm.widgets.base import PyDMPrimitiveWidget
|
|
13
|
+
from pydm.widgets.designer_settings import update_property_for_widget
|
|
14
|
+
from qtpy.QtCore import Property, QEvent, QObject, QRect, QSettings, QSize, Qt, QTimer
|
|
15
|
+
from qtpy.QtGui import QColor, QFont, QIcon, QPainter, QPaintEvent, QPen
|
|
16
|
+
from qtpy.QtWidgets import QAction, QApplication, QFrame, QSizePolicy, QWidget
|
|
17
|
+
|
|
18
|
+
from .registry import (
|
|
19
|
+
is_registry_container,
|
|
20
|
+
is_registry_savable,
|
|
21
|
+
resolve_registry_props,
|
|
22
|
+
)
|
|
23
|
+
from .view_saver_dialog import ViewSaverDialog
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
POLL_INTERVAL_MS = 60_000
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EditWidgetListExtension:
|
|
31
|
+
"""Adds an 'Edit ViewSaver…' action to the Designer right-click menu.
|
|
32
|
+
|
|
33
|
+
Follows the same pattern as ``MacroEditExtension`` in
|
|
34
|
+
``pcdswidgets.builder.designer_widget``.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, widget: "ViewSaver"):
|
|
38
|
+
self.widget = widget
|
|
39
|
+
self._action = QAction("&Edit ViewSaver\u2026", self.widget)
|
|
40
|
+
self._action.triggered.connect(widget.open_dialog)
|
|
41
|
+
|
|
42
|
+
def actions(self) -> list[QAction]:
|
|
43
|
+
return [self._action]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ViewSaver(QFrame, PyDMPrimitiveWidget):
|
|
47
|
+
"""
|
|
48
|
+
An invisible container that auto-saves and restores the view-state of the
|
|
49
|
+
widgets placed inside it, using QSettings.
|
|
50
|
+
|
|
51
|
+
Drop this widget onto a screen and place the widgets you want persisted
|
|
52
|
+
inside it.
|
|
53
|
+
|
|
54
|
+
Any child that exposes a ``get_view_saver_properties`` or
|
|
55
|
+
is registered in ``registry.WIDGET_REGISTRY`` is tracked.
|
|
56
|
+
|
|
57
|
+
Optional ``excludedWidgets`` list can
|
|
58
|
+
opt children out.
|
|
59
|
+
|
|
60
|
+
In QtDesigner the container draws a dashed border and a small corner label
|
|
61
|
+
so it can be located and edited (double-click, or the right-click task
|
|
62
|
+
menu, opens the settings dialog). At runtime it is fully transparent with
|
|
63
|
+
no border or margins, so only its child widgets are visible.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Designer properties
|
|
67
|
+
-------------------
|
|
68
|
+
dirName : str
|
|
69
|
+
Directory where the settings .ini file is stored
|
|
70
|
+
(default ``~/.config/pcds_view_saves``).
|
|
71
|
+
fileName : str
|
|
72
|
+
Name of the .ini file (without extension). Supports PyDM
|
|
73
|
+
``${MACRO}`` expansion and this is recommended in situations the same UI
|
|
74
|
+
screen is used for different devices
|
|
75
|
+
Auto-generated rand on first load
|
|
76
|
+
excludedWidgets : list[str]
|
|
77
|
+
objectNames of savable child widgets to skip.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_qt_designer_ = {
|
|
81
|
+
"group": "ECS Common Tools",
|
|
82
|
+
"is_container": True,
|
|
83
|
+
"extensions": [EditWidgetListExtension],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_HINT_TEXT = "ViewSaver \u2022 Double-click to edit"
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def get_designer_icon(cls) -> QIcon:
|
|
90
|
+
return IconFont().icon("save")
|
|
91
|
+
|
|
92
|
+
def __init__(self, parent: QWidget | None = None, **kwargs: Any):
|
|
93
|
+
super().__init__(parent=parent, **kwargs)
|
|
94
|
+
|
|
95
|
+
self._dir_name: str = os.path.join("~", ".config", "pcds_view_saves")
|
|
96
|
+
self._file_name: str = ""
|
|
97
|
+
self._excluded: list[str] = []
|
|
98
|
+
# objectName -> {propKey: (getter, setter)} resolved at runtime load
|
|
99
|
+
self._tracked_widgets: dict[str, dict[str, tuple[Callable, Callable]]] = {}
|
|
100
|
+
self._loaded: bool = False
|
|
101
|
+
self._last_snapshot: dict[str, Any] = {}
|
|
102
|
+
|
|
103
|
+
# Invisible passthrough container: no frame, no margins, transparent.
|
|
104
|
+
self.setFrameShape(QFrame.NoFrame)
|
|
105
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
106
|
+
self.setAttribute(Qt.WA_TranslucentBackground, True)
|
|
107
|
+
self.setAutoFillBackground(False)
|
|
108
|
+
|
|
109
|
+
# Poll timer — started in _initial_load
|
|
110
|
+
self._poll_timer = QTimer(self)
|
|
111
|
+
self._poll_timer.setInterval(POLL_INTERVAL_MS)
|
|
112
|
+
self._poll_timer.timeout.connect(self._save_settings)
|
|
113
|
+
|
|
114
|
+
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
|
115
|
+
|
|
116
|
+
# Defer setup to the next event-loop cycle.
|
|
117
|
+
# ensures fileName loaded from .ui file and sibling widgets exist
|
|
118
|
+
QTimer.singleShot(0, self._deferred_init)
|
|
119
|
+
|
|
120
|
+
def _deferred_init(self) -> None:
|
|
121
|
+
"""Run mode-appropriate setup once the event loop resumes."""
|
|
122
|
+
if is_qt_designer():
|
|
123
|
+
# Stay visible and on top so it's easy to find
|
|
124
|
+
self.raise_()
|
|
125
|
+
self._ensure_default_file_name()
|
|
126
|
+
else:
|
|
127
|
+
self._initial_load()
|
|
128
|
+
|
|
129
|
+
def _ensure_default_file_name(self) -> None:
|
|
130
|
+
"""Assign a semi-unique default fileName if none was loaded from the .ui"""
|
|
131
|
+
if self._file_name:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
suffix = "".join(random.choices(string.ascii_letters + "123456789", k=8))
|
|
135
|
+
self._file_name = f"view_saver_{suffix}"
|
|
136
|
+
# informs designer the property has changed so it will be saved.
|
|
137
|
+
update_property_for_widget(self, "fileName", self._file_name)
|
|
138
|
+
|
|
139
|
+
def _build_settings(self) -> QSettings | None:
|
|
140
|
+
"""Build a QSettings object in IniFormat, or None macros are not expanded."""
|
|
141
|
+
name = self._file_name
|
|
142
|
+
if not name or "${" in name:
|
|
143
|
+
logger.error(f"View save path ''{self._file_name}'' not initialized.")
|
|
144
|
+
return None
|
|
145
|
+
dir_path = Path(os.path.expanduser(os.path.expandvars(self._dir_name)))
|
|
146
|
+
ini_path = str(dir_path / f"{name}.ini")
|
|
147
|
+
return QSettings(ini_path, QSettings.IniFormat)
|
|
148
|
+
|
|
149
|
+
def _initial_load(self) -> None:
|
|
150
|
+
"""non-designer init that happens after all widgets load"""
|
|
151
|
+
settings = self._build_settings()
|
|
152
|
+
if settings is None:
|
|
153
|
+
return
|
|
154
|
+
self._tracked_widgets = self._resolve_tracked_widgets()
|
|
155
|
+
logger.debug(f"ViewSaver: loading from {settings.fileName()} ({len(self._tracked_widgets)} tracked widgets)")
|
|
156
|
+
restored = 0
|
|
157
|
+
for widget_name, prop_dict in self._tracked_widgets.items():
|
|
158
|
+
# restore any saved settings
|
|
159
|
+
for prop_name, (_getter, setter) in prop_dict.items():
|
|
160
|
+
saved_val = settings.value(f"{widget_name}/{prop_name}")
|
|
161
|
+
if saved_val is None:
|
|
162
|
+
logger.debug(f"No saved value for {widget_name}/{prop_name}")
|
|
163
|
+
continue
|
|
164
|
+
try:
|
|
165
|
+
setter(saved_val)
|
|
166
|
+
restored += 1
|
|
167
|
+
logger.debug(f"Restored {widget_name}/{prop_name} = {saved_val!r}")
|
|
168
|
+
except Exception:
|
|
169
|
+
logger.exception(f"Failed to restore {widget_name}/{prop_name}")
|
|
170
|
+
logger.debug(f"Restored {restored} value(s)")
|
|
171
|
+
self._loaded = True
|
|
172
|
+
self._poll_timer.start()
|
|
173
|
+
|
|
174
|
+
app = QApplication.instance()
|
|
175
|
+
if app is not None:
|
|
176
|
+
# Backstop for quit paths that tear the display down without ever
|
|
177
|
+
# firing closeEvent (e.g. PyDMMainWindow's File > Quit calling
|
|
178
|
+
# app.quit() directly), so the last poll interval isn't lost.
|
|
179
|
+
app.aboutToQuit.connect(self._flush_on_quit)
|
|
180
|
+
|
|
181
|
+
# closeEvent is only delivered to the top-level window, never to this
|
|
182
|
+
# hidden child widget, so watch the container window instead to catch
|
|
183
|
+
# the user closing just this screen while the app keeps running.
|
|
184
|
+
window = self.window()
|
|
185
|
+
if window is not None:
|
|
186
|
+
window.installEventFilter(self)
|
|
187
|
+
|
|
188
|
+
def _flush_on_quit(self) -> None:
|
|
189
|
+
"""Write current widget state before the application exits."""
|
|
190
|
+
if not self._loaded:
|
|
191
|
+
return
|
|
192
|
+
logger.debug("aboutToQuit fired, saving view to file.")
|
|
193
|
+
self._save_settings()
|
|
194
|
+
|
|
195
|
+
def _save_settings(self) -> None:
|
|
196
|
+
settings = self._build_settings()
|
|
197
|
+
if settings is None:
|
|
198
|
+
return
|
|
199
|
+
saved = 0
|
|
200
|
+
for widget_name, prop_list in self._tracked_widgets.items():
|
|
201
|
+
if prop_list is None:
|
|
202
|
+
continue
|
|
203
|
+
for prop_name, (getter, _setter) in prop_list.items():
|
|
204
|
+
try:
|
|
205
|
+
val = getter()
|
|
206
|
+
settings.setValue(f"{widget_name}/{prop_name}", val)
|
|
207
|
+
saved += 1
|
|
208
|
+
logger.debug(f"Saved {widget_name}/{prop_name} = {val!r}")
|
|
209
|
+
except Exception:
|
|
210
|
+
logger.exception(f"Failed to save {widget_name}/{prop_name}")
|
|
211
|
+
logger.debug(f"Wrote {saved} value(s) to {settings.fileName()}")
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
# Widget Discovery
|
|
215
|
+
# ------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
def _walk_widget_tree(self) -> list[QWidget]:
|
|
218
|
+
"""Return every savable descendant.
|
|
219
|
+
|
|
220
|
+
A widget is savable if it defines ``get_view_saver_properties`` or
|
|
221
|
+
if its class is listed in ``WIDGET_REGISTRY``.
|
|
222
|
+
|
|
223
|
+
The child tree is walked manually so it stops as soon as a widget is
|
|
224
|
+
found savable. Non-savable containers and registered/duck-typed
|
|
225
|
+
containers are descended into recursively.
|
|
226
|
+
"""
|
|
227
|
+
found: list[QWidget] = []
|
|
228
|
+
|
|
229
|
+
def _walk(widget: QWidget) -> None:
|
|
230
|
+
for child in widget.children():
|
|
231
|
+
if not isinstance(child, QWidget):
|
|
232
|
+
continue
|
|
233
|
+
# check if widget is saveable
|
|
234
|
+
if hasattr(child, "get_view_saver_properties") or is_registry_savable(child):
|
|
235
|
+
found.append(child)
|
|
236
|
+
# A savable container still holds nested savables; keep
|
|
237
|
+
# descending otherwise stop.
|
|
238
|
+
if getattr(child, "VIEW_SAVER_IS_CONTAINER", False) or is_registry_container(child):
|
|
239
|
+
_walk(child)
|
|
240
|
+
else:
|
|
241
|
+
_walk(child)
|
|
242
|
+
|
|
243
|
+
_walk(self)
|
|
244
|
+
return found
|
|
245
|
+
|
|
246
|
+
def _walk_widget_names(self) -> list[str]:
|
|
247
|
+
"""Return the objectName of every savable descendant, skipping unnamed ones."""
|
|
248
|
+
return [name for w in self._walk_widget_tree() if (name := w.objectName())]
|
|
249
|
+
|
|
250
|
+
def _resolve_tracked_widgets(self) -> dict[str, dict[str, tuple[Callable, Callable]]]:
|
|
251
|
+
"""Resolve savable child widgets into ``{objectName: {prop: (get, set)}}``.
|
|
252
|
+
|
|
253
|
+
Walks the child tree and resolve the getter/setter for each saveable widget's
|
|
254
|
+
properties.
|
|
255
|
+
|
|
256
|
+
Skips any excluded objectNames, and warns for savable widgets that lack an
|
|
257
|
+
objectName since they cannot be keyed in the settings file.
|
|
258
|
+
"""
|
|
259
|
+
resolved: dict[str, dict[str, tuple[Callable, Callable]]] = {}
|
|
260
|
+
for widget in self._walk_widget_tree():
|
|
261
|
+
name = widget.objectName()
|
|
262
|
+
if not name:
|
|
263
|
+
logger.warning(
|
|
264
|
+
"ViewSaver: skipping savable %s with no objectName",
|
|
265
|
+
type(widget).__name__,
|
|
266
|
+
)
|
|
267
|
+
continue
|
|
268
|
+
if name in self._excluded:
|
|
269
|
+
continue
|
|
270
|
+
prop_list = self._resolve_widget_props(widget)
|
|
271
|
+
if prop_list:
|
|
272
|
+
resolved[name] = prop_list
|
|
273
|
+
return resolved
|
|
274
|
+
|
|
275
|
+
def _resolve_widget_props(
|
|
276
|
+
self,
|
|
277
|
+
widget: QWidget,
|
|
278
|
+
) -> dict[str, tuple[Callable, Callable]] | None:
|
|
279
|
+
"""Resolve getter/setter callables for each persisted property of *widget*.
|
|
280
|
+
|
|
281
|
+
widgets with ``get_view_saver_properties`` are resolved here; this is preferred.
|
|
282
|
+
built-in widgets are checked in the widget registry (see ``registry.resolve_registry_props``).
|
|
283
|
+
|
|
284
|
+
Returns a mapping ``{propKey: (getter, setter)}`` for the given widget
|
|
285
|
+
instance, or ``None`` if no savable properties could be resolved.
|
|
286
|
+
"""
|
|
287
|
+
if hasattr(widget, "get_view_saver_properties"):
|
|
288
|
+
try:
|
|
289
|
+
return widget.get_view_saver_properties()
|
|
290
|
+
except Exception:
|
|
291
|
+
logger.exception(f"ViewSaver: get_view_saver_properties failed for {type(widget).__name__}")
|
|
292
|
+
return None
|
|
293
|
+
return resolve_registry_props(widget)
|
|
294
|
+
|
|
295
|
+
# ------------------------------------------------------------------
|
|
296
|
+
# Events
|
|
297
|
+
# ------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
def eventFilter(self, obj: QObject, event: QEvent) -> bool: # noqa: N802
|
|
300
|
+
"""Save when the watched container window is closing."""
|
|
301
|
+
if event.type() == QEvent.Close and self._loaded:
|
|
302
|
+
logger.debug("Window close event fired, saving view to file.")
|
|
303
|
+
self._save_settings()
|
|
304
|
+
return super().eventFilter(obj, event)
|
|
305
|
+
|
|
306
|
+
# ------------------------------------------------------------------
|
|
307
|
+
# UI
|
|
308
|
+
# ------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
def sizeHint(self) -> QSize: # noqa: N802
|
|
311
|
+
"""Default size when first dropped in Designer."""
|
|
312
|
+
return QSize(200, 200)
|
|
313
|
+
|
|
314
|
+
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802
|
|
315
|
+
"""Draw a dashed outline and corner label, but only in Qt Designer.
|
|
316
|
+
|
|
317
|
+
At runtime nothing is painted so the container is fully transparent and
|
|
318
|
+
only its child widgets are visible.
|
|
319
|
+
"""
|
|
320
|
+
if not is_qt_designer():
|
|
321
|
+
return
|
|
322
|
+
painter = QPainter(self)
|
|
323
|
+
painter.setRenderHint(QPainter.Antialiasing)
|
|
324
|
+
|
|
325
|
+
# Dashed border to mark the container bounds.
|
|
326
|
+
pen = QPen(QColor(90, 130, 200))
|
|
327
|
+
pen.setStyle(Qt.DashLine)
|
|
328
|
+
painter.setPen(pen)
|
|
329
|
+
painter.setBrush(Qt.NoBrush)
|
|
330
|
+
painter.drawRoundedRect(self.rect().adjusted(1, 1, -2, -2), 4, 4)
|
|
331
|
+
|
|
332
|
+
# Small corner label chip (kept out of the center so it never sits on
|
|
333
|
+
# top of child widgets).
|
|
334
|
+
font = QFont()
|
|
335
|
+
font.setPointSize(7)
|
|
336
|
+
painter.setFont(font)
|
|
337
|
+
metrics = painter.fontMetrics()
|
|
338
|
+
text = self._HINT_TEXT
|
|
339
|
+
chip = QRect(2, 2, metrics.horizontalAdvance(text) + 8, metrics.height() + 2)
|
|
340
|
+
painter.setPen(Qt.NoPen)
|
|
341
|
+
painter.setBrush(QColor(90, 130, 200, 210))
|
|
342
|
+
painter.drawRoundedRect(chip, 3, 3)
|
|
343
|
+
painter.setPen(QColor(255, 255, 255))
|
|
344
|
+
painter.drawText(chip, Qt.AlignCenter, text)
|
|
345
|
+
painter.end()
|
|
346
|
+
|
|
347
|
+
def open_dialog(self) -> None:
|
|
348
|
+
dialog = ViewSaverDialog(
|
|
349
|
+
available_widgets=self._walk_widget_names(),
|
|
350
|
+
excluded_widgets=list(self._excluded),
|
|
351
|
+
dir_name=self._dir_name,
|
|
352
|
+
file_name=self._file_name,
|
|
353
|
+
parent=self,
|
|
354
|
+
)
|
|
355
|
+
if dialog.exec_():
|
|
356
|
+
# set properties based on result
|
|
357
|
+
dir_name, file_name, excluded = dialog.results()
|
|
358
|
+
# informs designer the properties have changed for saves to ui file.
|
|
359
|
+
update_property_for_widget(self, "dirName", dir_name)
|
|
360
|
+
update_property_for_widget(self, "fileName", file_name)
|
|
361
|
+
update_property_for_widget(self, "excludedWidgets", list(excluded))
|
|
362
|
+
|
|
363
|
+
# ------------------------------------------------------------------
|
|
364
|
+
# Properties
|
|
365
|
+
# ------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
def _get_dir_name(self) -> str:
|
|
368
|
+
return self._dir_name
|
|
369
|
+
|
|
370
|
+
def _set_dir_name(self, value: str) -> None:
|
|
371
|
+
self._dir_name = value
|
|
372
|
+
|
|
373
|
+
dirName = Property("QString", _get_dir_name, _set_dir_name)
|
|
374
|
+
|
|
375
|
+
def _get_file_name(self) -> str:
|
|
376
|
+
return self._file_name
|
|
377
|
+
|
|
378
|
+
def _set_file_name(self, value: str) -> None:
|
|
379
|
+
self._file_name = value
|
|
380
|
+
|
|
381
|
+
fileName = Property("QString", _get_file_name, _set_file_name)
|
|
382
|
+
|
|
383
|
+
def _get_excluded_widgets(self) -> list[str]:
|
|
384
|
+
return list(self._excluded)
|
|
385
|
+
|
|
386
|
+
def _set_excluded_widgets(self, value: list[str]) -> None:
|
|
387
|
+
self._excluded = list(value)
|
|
388
|
+
|
|
389
|
+
excludedWidgets = Property("QStringList", _get_excluded_widgets, _set_excluded_widgets)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Dialog for editing ViewSaver settings in Qt Designer."""
|
|
2
|
+
|
|
3
|
+
from qtpy.QtCore import Qt
|
|
4
|
+
from qtpy.QtWidgets import (
|
|
5
|
+
QDialog,
|
|
6
|
+
QFileDialog,
|
|
7
|
+
QGroupBox,
|
|
8
|
+
QHBoxLayout,
|
|
9
|
+
QLabel,
|
|
10
|
+
QLineEdit,
|
|
11
|
+
QListWidget,
|
|
12
|
+
QListWidgetItem,
|
|
13
|
+
QPushButton,
|
|
14
|
+
QVBoxLayout,
|
|
15
|
+
QWidget,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ViewSaverDialog(QDialog):
|
|
20
|
+
"""Editor dialog for all ViewSaver settings.
|
|
21
|
+
|
|
22
|
+
Provides fields for the save directory and file name, plus a checklist of
|
|
23
|
+
the savable widgets discovered inside the container. Every discovered
|
|
24
|
+
widget is saved by default; unchecking one adds it to the excluded list so
|
|
25
|
+
it is skipped.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
available_widgets: list[str],
|
|
31
|
+
excluded_widgets: list[str],
|
|
32
|
+
dir_name: str,
|
|
33
|
+
file_name: str,
|
|
34
|
+
parent: QWidget | None = None,
|
|
35
|
+
):
|
|
36
|
+
super().__init__(parent)
|
|
37
|
+
self.setWindowTitle("Edit ViewSaver Settings")
|
|
38
|
+
self.setMinimumWidth(450)
|
|
39
|
+
|
|
40
|
+
self._available_widgets: list[str] = list(available_widgets)
|
|
41
|
+
|
|
42
|
+
layout = QVBoxLayout(self)
|
|
43
|
+
|
|
44
|
+
# --- File settings group ---
|
|
45
|
+
file_group = QGroupBox("Save Location")
|
|
46
|
+
file_layout = QVBoxLayout(file_group)
|
|
47
|
+
|
|
48
|
+
# Directory
|
|
49
|
+
dir_row = QHBoxLayout()
|
|
50
|
+
dir_label = QLabel("Directory:")
|
|
51
|
+
dir_tooltip = "Folder where the .ini settings file will be stored."
|
|
52
|
+
dir_label.setToolTip(dir_tooltip)
|
|
53
|
+
self._dir_edit = QLineEdit(dir_name)
|
|
54
|
+
self._dir_edit.setToolTip(dir_tooltip)
|
|
55
|
+
dir_browse = QPushButton("Browse\u2026")
|
|
56
|
+
dir_browse.setToolTip("Open a file dialog to pick the save directory.")
|
|
57
|
+
dir_browse.clicked.connect(self._browse_dir)
|
|
58
|
+
dir_row.addWidget(dir_label)
|
|
59
|
+
dir_row.addWidget(self._dir_edit, stretch=1)
|
|
60
|
+
dir_row.addWidget(dir_browse)
|
|
61
|
+
file_layout.addLayout(dir_row)
|
|
62
|
+
|
|
63
|
+
# Filename
|
|
64
|
+
name_row = QHBoxLayout()
|
|
65
|
+
name_label = QLabel("File name:")
|
|
66
|
+
tool_tip = (
|
|
67
|
+
"Name of the .ini file (without extension).\n"
|
|
68
|
+
"Supports PyDM ${MACRO} expansion.\n"
|
|
69
|
+
"Auto-generated if left empty."
|
|
70
|
+
)
|
|
71
|
+
name_label.setToolTip(tool_tip)
|
|
72
|
+
self._name_edit = QLineEdit(file_name)
|
|
73
|
+
self._name_edit.setToolTip(tool_tip)
|
|
74
|
+
name_row.addWidget(name_label)
|
|
75
|
+
name_row.addWidget(self._name_edit, stretch=1)
|
|
76
|
+
file_layout.addLayout(name_row)
|
|
77
|
+
|
|
78
|
+
layout.addWidget(file_group)
|
|
79
|
+
|
|
80
|
+
# --- Saved widgets group ---
|
|
81
|
+
widget_list_group = QGroupBox("Saved Widgets")
|
|
82
|
+
bind_layout = QVBoxLayout(widget_list_group)
|
|
83
|
+
bind_layout.addWidget(QLabel("Widgets found inside this container. Uncheck any you do not want to persist."))
|
|
84
|
+
|
|
85
|
+
self._list = QListWidget()
|
|
86
|
+
excluded = set(excluded_widgets)
|
|
87
|
+
for name in self._available_widgets:
|
|
88
|
+
item = QListWidgetItem(name)
|
|
89
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
90
|
+
item.setCheckState(Qt.Unchecked if name in excluded else Qt.Checked)
|
|
91
|
+
self._list.addItem(item)
|
|
92
|
+
bind_layout.addWidget(self._list)
|
|
93
|
+
|
|
94
|
+
# Check-all / uncheck-all convenience buttons.
|
|
95
|
+
select_row = QHBoxLayout()
|
|
96
|
+
check_all_btn = QPushButton("Save A&ll")
|
|
97
|
+
check_all_btn.setToolTip("Persist every discovered widget.")
|
|
98
|
+
check_all_btn.clicked.connect(lambda: self._set_all(Qt.Checked))
|
|
99
|
+
uncheck_all_btn = QPushButton("Save &None")
|
|
100
|
+
uncheck_all_btn.setToolTip("Exclude every discovered widget.")
|
|
101
|
+
uncheck_all_btn.clicked.connect(lambda: self._set_all(Qt.Unchecked))
|
|
102
|
+
select_row.addStretch()
|
|
103
|
+
select_row.addWidget(check_all_btn)
|
|
104
|
+
select_row.addWidget(uncheck_all_btn)
|
|
105
|
+
bind_layout.addLayout(select_row)
|
|
106
|
+
|
|
107
|
+
layout.addWidget(widget_list_group)
|
|
108
|
+
|
|
109
|
+
# --- OK / Cancel ---
|
|
110
|
+
button_row = QHBoxLayout()
|
|
111
|
+
button_row.addStretch()
|
|
112
|
+
cancel_btn = QPushButton("&Cancel")
|
|
113
|
+
cancel_btn.clicked.connect(self.reject)
|
|
114
|
+
ok_btn = QPushButton("&OK")
|
|
115
|
+
ok_btn.setDefault(True)
|
|
116
|
+
ok_btn.clicked.connect(self.accept)
|
|
117
|
+
button_row.addWidget(cancel_btn)
|
|
118
|
+
button_row.addWidget(ok_btn)
|
|
119
|
+
layout.addLayout(button_row)
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def _browse_dir(self) -> None:
|
|
124
|
+
path = QFileDialog.getExistingDirectory(self, "Select Save Directory", self._dir_edit.text())
|
|
125
|
+
if path:
|
|
126
|
+
self._dir_edit.setText(path)
|
|
127
|
+
|
|
128
|
+
def _set_all(self, state: Qt.CheckState) -> None:
|
|
129
|
+
for i in range(self._list.count()):
|
|
130
|
+
self._list.item(i).setCheckState(state)
|
|
131
|
+
|
|
132
|
+
def _excluded_names(self) -> list[str]:
|
|
133
|
+
return [
|
|
134
|
+
self._list.item(i).text()
|
|
135
|
+
for i in range(self._list.count())
|
|
136
|
+
if self._list.item(i).checkState() == Qt.Unchecked
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
def results(self) -> tuple[str, str, list[str]]:
|
|
140
|
+
"""Return ``(dir_name, file_name, excluded_widget_names)``."""
|
|
141
|
+
return (
|
|
142
|
+
self._dir_edit.text(),
|
|
143
|
+
self._name_edit.text(),
|
|
144
|
+
self._excluded_names(),
|
|
145
|
+
)
|