├── .coveragerc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml └── workflows │ ├── gh-pages.yml │ ├── pre-commit.yml │ ├── publish-to-pypi.yml │ └── run-tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.rst ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── SUPPORT.md ├── conda-recipe ├── build.sh └── meta.yaml ├── dev-requirements.txt ├── docs-requirements.txt ├── docs ├── Makefile └── source │ ├── _static │ ├── data_plugins │ │ ├── calc_example.gif │ │ └── waveform_curve_local_plugin.png │ ├── help_files.gif │ ├── tutorials │ │ ├── action │ │ │ ├── all_motors1.png │ │ │ ├── application.png │ │ │ ├── components.png │ │ │ ├── designer.png │ │ │ ├── expert │ │ │ │ ├── expert.png │ │ │ │ ├── expert_all_widgets_ok.png │ │ │ │ ├── expert_layout.gif │ │ │ │ └── widgets.png │ │ │ ├── inline │ │ │ │ ├── inline.png │ │ │ │ ├── inline_3_2.gif │ │ │ │ ├── inline_3_3.gif │ │ │ │ ├── inline_3_4.gif │ │ │ │ ├── inline_3_5.gif │ │ │ │ ├── inline_3_6.gif │ │ │ │ ├── inline_3_8.gif │ │ │ │ ├── inline_3_9.gif │ │ │ │ ├── inline_all_widgets.png │ │ │ │ ├── inline_all_widgets_ok.png │ │ │ │ ├── inline_desc.gif │ │ │ │ ├── inline_layout.gif │ │ │ │ └── widgets.png │ │ │ ├── little_code │ │ │ │ └── main.gif │ │ │ ├── main │ │ │ │ ├── main.png │ │ │ │ ├── main_all_widgets_ok.png │ │ │ │ └── widgets.png │ │ │ ├── new_widget.gif │ │ │ ├── new_widget.png │ │ │ ├── pydm_properties.png │ │ │ └── python │ │ │ │ ├── all_motors.gif │ │ │ │ └── all_motors.png │ │ ├── code │ │ │ ├── all_motors.py │ │ │ ├── expert_motor.ui │ │ │ ├── inline_motor.ui │ │ │ ├── main.py │ │ │ ├── main.ui │ │ │ └── motor_db.txt │ │ ├── designer.png │ │ ├── intro │ │ │ ├── architecture.png │ │ │ └── main_window.png │ │ ├── new_vm.png │ │ └── pydm_properties.png │ └── widgets │ │ ├── analog_indicator │ │ ├── analog_indicator.png │ │ ├── no_minor.png │ │ ├── no_upper_minor.png │ │ ├── smallest_size_set.png │ │ └── smallest_size_visual.png │ │ ├── archiver_time_plot │ │ ├── archiver_plot.gif │ │ └── archiver_time_plot_designer.png │ │ ├── curve_editor │ │ └── curve_editor.gif │ │ ├── tab_widget.png │ │ └── widget_rules │ │ ├── open_editor.gif │ │ └── rules_editor.png │ ├── add_data_plugins.rst │ ├── application.rst │ ├── channel.rst │ ├── conf.py │ ├── configuration.rst │ ├── data_plugins │ ├── calc_plugin.rst │ ├── external_plugins.rst │ ├── local_plugin.rst │ └── p4p_plugin.rst │ ├── development │ ├── designer_widgets.rst │ ├── development.rst │ ├── external_tools.rst │ └── resources.rst │ ├── help_files.rst │ ├── index.rst │ ├── installation.rst │ ├── stylesheets.rst │ ├── tutorials │ ├── action │ │ ├── designer.rst │ │ ├── designer_expert.rst │ │ ├── designer_inline.rst │ │ ├── designer_main.rst │ │ ├── intro_designer.rst │ │ ├── intro_python.rst │ │ ├── little_code.rst │ │ ├── python.rst │ │ └── tutorial.rst │ ├── contrib │ │ ├── help.rst │ │ └── requests.rst │ ├── index.rst │ ├── intro.rst │ └── intro │ │ ├── channels.rst │ │ ├── data_arch.rst │ │ ├── datasource.rst │ │ ├── features.rst │ │ ├── launcher.rst │ │ ├── macros.rst │ │ └── widgets.rst │ ├── utilities │ └── index.rst │ └── widgets │ ├── PyDMWidget.rst │ ├── analog_indicator.rst │ ├── archiver_timeplot.rst │ ├── byte.rst │ ├── checkbox.rst │ ├── curve_editor.rst │ ├── datetime_edit.rst │ ├── datetime_label.rst │ ├── drawing.rst │ ├── embedded_display.rst │ ├── enum_button.rst │ ├── enum_combo_box.rst │ ├── event_plot.rst │ ├── frame.rst │ ├── image.rst │ ├── index.rst │ ├── label.rst │ ├── line_edit.rst │ ├── log.rst │ ├── multistate.rst │ ├── nt_table.rst │ ├── pushbutton.rst │ ├── related_display_button.rst │ ├── scale.rst │ ├── scatterplot.rst │ ├── shell_command.rst │ ├── slider.rst │ ├── spinbox.rst │ ├── symbol.rst │ ├── tab_widget.rst │ ├── template_repeater.rst │ ├── timeplot.rst │ ├── utilities.rst │ ├── waveformplot.rst │ ├── waveformtable.rst │ └── widget_rules │ ├── customizing.rst │ └── index.rst ├── examples ├── README.md ├── __init__.py ├── accessory_window │ ├── accessory_window.py │ └── settings_window.ui ├── actions │ ├── actions.ui │ ├── actions2.ui │ └── label_text_from_rule.ui ├── archiver_time_plot │ ├── archiver_time_plot_example.py │ └── formula_curve_example.py ├── buttons │ └── buttons.ui ├── byte_indicator │ ├── byte_indicator.ui │ └── multi_state_indicator.ui ├── ca-filtering │ ├── README.md │ ├── ca_filtering.ui │ └── test.db ├── calc │ ├── README.md │ ├── demo.db │ └── sin_cos_tan.ui ├── camviewer │ ├── camviewer.py │ ├── camviewer.ui │ └── marker.py ├── checkbox │ └── checkbox.ui ├── code_only │ └── code_only.py ├── datetime │ └── datetime.ui ├── display_format │ └── display_format.ui ├── drawing │ ├── SLAC_logo.jpeg │ ├── draw_alarm.ui │ ├── drawing_demo.ui │ ├── gif_test.ui │ └── test.gif ├── embedded_displays │ ├── embedded_display.ui │ └── waveplot.ui ├── enum_buttons │ ├── buttons.ui │ ├── enum.db │ └── enum.ui ├── enum_combo_box │ └── enum_combo_box.ui ├── eventplot │ └── eventplot.ui ├── exception │ └── demo.py ├── external_tool │ ├── dummy_tool.py │ └── lookup_path │ │ ├── ignore.py │ │ ├── new_tool.py │ │ └── root_tool.py ├── frame │ └── frame.ui ├── home.ui ├── icons │ └── icons.ui ├── image_processing │ ├── drag-and-drop.ui │ ├── image_view.py │ ├── image_view.ui │ └── marker.py ├── image_view │ └── image.ui ├── infinity │ └── infinity.ui ├── label │ └── label.ui ├── line_edit │ └── edit.ui ├── local_plugin │ ├── test.ui │ └── test_value_types.ui ├── log_display │ └── log_display.ui ├── macros │ ├── basics │ │ ├── embedded_display_with_macro.ui │ │ ├── macro_pv.ui │ │ ├── readme.txt │ │ └── related_display_with_macros.ui │ ├── macros_and_python │ │ ├── macro_addition.py │ │ ├── macro_addition.ui │ │ ├── macros_to_python_displays.ui │ │ └── readme.txt │ └── nested_embedded_windows │ │ ├── macro_addition.py │ │ ├── macro_addition.ui │ │ ├── nested_embedded_windows.ui │ │ ├── nested_embedded_windows_level_2.ui │ │ └── readme.txt ├── oscilloscope │ └── scope.ui ├── positioner │ ├── cams.py │ ├── positioner-widget.ui │ ├── positioner_ioc.py │ └── positioner_module.py ├── push_button │ ├── latch_mode.ui │ ├── push_button.ui │ └── push_button_alarm.ui ├── pva │ ├── image.ui │ ├── labels.ui │ ├── nt_enum.ui │ ├── nt_table.ui │ ├── plots.ui │ └── writable_widgets.ui ├── related_displays │ ├── display1.ui │ ├── display2.ui │ ├── dynamic_text.ui │ └── multiple_files.ui ├── rpc │ ├── rpc_labels.ui │ └── rpc_testing_client.py ├── scale_indicator │ └── scale.ui ├── scatterplot │ └── scatterplot.ui ├── shell_command │ ├── example_cmd.sh │ ├── shell_command.ui │ └── shell_command_full_shell.ui ├── slider │ └── slider.ui ├── spinbox │ └── spinbox.ui ├── symbol │ ├── go.svg │ ├── stop.svg │ └── symbol.ui ├── tab_widget │ └── tab_widget.ui ├── template_repeater │ ├── flow_template_repeater.ui │ ├── mag_block_template.ui │ ├── magnet-list-item.ui │ ├── magnet_ioc.py │ ├── readme.md │ ├── template_repeater.ui │ ├── xcor_list.json │ └── xcor_list_small.json ├── template_repeater_json │ ├── main.ui │ └── template.ui ├── terminator │ └── terminator.ui ├── testing_ioc │ ├── access_rules.as │ ├── pva_testing_ioc.py │ ├── pydm-testing-ioc │ ├── pydm-tutorial-ioc │ └── rpc_testing_ioc.py ├── timeplot │ ├── multi_axis_timeplot.ui │ └── timeplot.ui ├── tutorial │ ├── all_motors.py │ ├── expert_motor.ui │ ├── inline_motor.ui │ ├── main.py │ ├── main.ui │ └── motor_db.txt ├── waveformplot │ ├── multi_axis_waveform_plot.ui │ ├── waveform_plot.ui │ └── waveform_with_transform.ui └── waveformtable │ └── wavetable.ui ├── github_deploy_key.enc ├── pydm ├── PyQt │ ├── Qt.py │ ├── QtCore.py │ ├── QtDesigner.py │ ├── QtGui.py │ ├── QtSvg.py │ ├── QtWidgets.py │ ├── __init__.py │ └── uic.py ├── __init__.py ├── about_pydm │ ├── __init__.py │ ├── about.py │ ├── about.ui │ ├── about_ui.py │ └── contributors.txt ├── application.py ├── config.py ├── connection_inspector │ ├── __init__.py │ ├── connection_inspector.py │ └── connection_table_model.py ├── data_plugins │ ├── __init__.py │ ├── archiver_plugin.py │ ├── calc_plugin.py │ ├── epics_plugin.py │ ├── epics_plugins │ │ ├── __init__.py │ │ ├── caproto_plugin_component.py │ │ ├── p4p_plugin_component.py │ │ ├── psp_plugin_component.py │ │ ├── pva_codec.py │ │ └── pyepics_plugin_component.py │ ├── fake_plugin.py │ ├── local_plugin.py │ ├── plugin.py │ └── pva_plugin.py ├── default_stylesheet.qss ├── display.py ├── display_module.py ├── exception.py ├── help_files │ ├── __init__.py │ └── help_window.py ├── main_window.py ├── pydm.ui ├── pydm_designer_plugin.py ├── pydm_ui.py ├── qtdesigner.py ├── show_macros │ ├── __init__.py │ └── show_macros.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── data_plugins │ │ ├── test_archiver_plugin.py │ │ ├── test_calc_plugin.py │ │ ├── test_p4p_plugin_component.py │ │ ├── test_plugin.py │ │ ├── test_psp_plugin_component.py │ │ └── test_pyepics_plugin_component.py │ ├── test_about_screen.py │ ├── test_data │ │ ├── global_stylesheet.css │ │ ├── macro_test.ui │ │ ├── no_display_test_file.py │ │ ├── subfolder │ │ │ └── test_relative_filename_child.ui │ │ ├── template.ui │ │ ├── test.txt │ │ ├── test.ui │ │ ├── test_emb_style.ui │ │ ├── test_relative_filename_parent.ui │ │ ├── test_stylesheet.css │ │ └── valid_display_test_file.py │ ├── test_data_plugins_import.py │ ├── test_display.py │ ├── test_main_window.py │ ├── test_plugins_import.py │ ├── test_show_macros.py │ ├── test_tools.py │ ├── utilities │ │ ├── __init__.py │ │ ├── test_colors.py │ │ ├── test_iconfont.py │ │ ├── test_macro.py │ │ ├── test_remove_protocol.py │ │ ├── test_stylesheet.py │ │ ├── test_units.py │ │ └── test_utilities.py │ └── widgets │ │ ├── __init__.py │ │ ├── test_archiver_timeplot.py │ │ ├── test_base.py │ │ ├── test_baseplot.py │ │ ├── test_byte.py │ │ ├── test_channel.py │ │ ├── test_checkbox.py │ │ ├── test_colormaps.py │ │ ├── test_curve_editor.py │ │ ├── test_datetime_edit.py │ │ ├── test_datetime_label.py │ │ ├── test_display_format.py │ │ ├── test_drawing.py │ │ ├── test_embedded_display.py │ │ ├── test_enum_button.py │ │ ├── test_enum_combo_box.py │ │ ├── test_eventplot.py │ │ ├── test_frame.py │ │ ├── test_label.py │ │ ├── test_lineedit.py │ │ ├── test_logdisplay.py │ │ ├── test_multiaxis_plot.py │ │ ├── test_multistate.py │ │ ├── test_pushbutton.py │ │ ├── test_related_display_button.py │ │ ├── test_rules.py │ │ ├── test_rules_editor.py │ │ ├── test_scatterplot.py │ │ ├── test_shell_command.py │ │ ├── test_slider.py │ │ ├── test_spinbox.py │ │ ├── test_symbol_editor.py │ │ ├── test_template_repeater.py │ │ ├── test_timeplot.py │ │ └── test_waveform_plot.py ├── tools │ ├── __init__.py │ └── tools.py ├── utilities │ ├── __init__.py │ ├── color2hex.pkl │ ├── colors.py │ ├── connection.py │ ├── display_loading.py │ ├── fontawesome-charmap.json │ ├── fontawesome.otf │ ├── hex2color.pkl │ ├── iconfont.py │ ├── macro.py │ ├── remove_protocol.py │ ├── shortcuts.py │ ├── stylesheet.py │ └── units.py └── widgets │ ├── __init__.py │ ├── analog_indicator.py │ ├── archiver_time_plot.py │ ├── archiver_time_plot_editor.py │ ├── axis_table_model.py │ ├── base.py │ ├── baseplot.py │ ├── baseplot_curve_editor.py │ ├── baseplot_table_model.py │ ├── byte.py │ ├── channel.py │ ├── checkbox.py │ ├── colormaps.py │ ├── datetime.py │ ├── designer_settings.py │ ├── display_format.py │ ├── drawing.py │ ├── embedded_display.py │ ├── enum_button.py │ ├── enum_combo_box.py │ ├── eventplot.py │ ├── eventplot_curve_editor.py │ ├── frame.py │ ├── icons │ └── terminator.png │ ├── image.py │ ├── label.py │ ├── line_edit.py │ ├── logdisplay.py │ ├── multi_axis_plot.py │ ├── multi_axis_viewbox.py │ ├── multi_axis_viewbox_menu.py │ ├── nt_table.py │ ├── pushbutton.py │ ├── qtplugin_base.py │ ├── qtplugin_extensions.py │ ├── qtplugins.py │ ├── related_display_button.py │ ├── rules.py │ ├── rules_editor.py │ ├── scale.py │ ├── scatterplot.py │ ├── scatterplot_curve_editor.py │ ├── shell_command.py │ ├── slider.py │ ├── spinbox.py │ ├── symbol.py │ ├── symbol_editor.py │ ├── tab_bar.py │ ├── tab_bar_qtplugin.py │ ├── template_repeater.py │ ├── terminator.py │ ├── timeplot.py │ ├── timeplot_curve_editor.py │ ├── waveformplot.py │ ├── waveformplot_curve_editor.py │ └── waveformtable.py ├── pydm_banner.png ├── pydm_banner_full.png ├── pydm_launcher ├── __init__.py ├── icons │ ├── pydm_128.png │ ├── pydm_16.png │ ├── pydm_24.png │ ├── pydm_256.png │ ├── pydm_32.png │ └── pydm_64.png └── main.py ├── pyproject.toml ├── requirements.txt ├── run_tests.py └── windows-dev-requirements.txt /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | pydm 4 | [report] 5 | omit = 6 | #tests 7 | */tests/* 8 | *test* 9 | #versioning 10 | .*version.* 11 | *_version.py 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | pydm/_version.py export-subst 2 | 3 | # Pickle files will always have LF endings on checkout 4 | *.pkl binary 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature for PyDM 3 | labels: [enhancement] 4 | body: 5 | 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Before reporting, please make a quick search through the existing [open issues](https://github.com/slaclab/pydm/issues?q=is%3Aissue+is%3Aopen), 10 | and also [closed issues](https://github.com/slaclab/pydm/issues?q=is%3Aissue+is%3Aclosed), to check if it's been previously addressed. 11 | 12 | - type: textarea 13 | attributes: 14 | label: "What's the problem this feature will solve?" 15 | description: "A clear and concise description of what the problem is." 16 | placeholder: "A clear and concise description of what the problem is." 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: "Describe the solution you'd like." 23 | description: "A clear and concise description of what you want to happen." 24 | placeholder: "A clear and concise description of what you want to happen." 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: "Additional context." 31 | description: "Add any other context, links, etc. about the feature here." 32 | placeholder: "Add any other context, links, etc. about the feature here." 33 | validations: 34 | required: false -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will publish a new version of the documentation to the gh-pages branch 2 | 3 | name: Publish Documentation 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | deploy: 11 | if: ${{ github.repository == 'slaclab/pydm' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: conda-incubator/setup-miniconda@v2 16 | with: 17 | python-version: 3.9 18 | mamba-version: "*" 19 | channels: conda-forge 20 | activate-environment: pydm-docs 21 | 22 | - name: Install python packages 23 | shell: bash -el {0} 24 | run: | 25 | mamba install pydm $(cat docs-requirements.txt) 26 | - name: Build Docs 27 | shell: bash -l {0} 28 | run: | 29 | pushd docs 30 | make html 31 | popd 32 | 33 | - name: Deploy to gh-pages 34 | uses: peaceiris/actions-gh-pages@v3 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_dir: docs/build/html 38 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v3 14 | - uses: pre-commit/action@v3.0.0 15 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run when a new release is published and upload it to PyPI using trusted publishing. 2 | # Done in two separate build and publish jobs per suggested best practice for limiting scope of token usage. 3 | 4 | name: Publish - PyPI 5 | 6 | on: 7 | release: 8 | types: 9 | - published 10 | workflow_dispatch: 11 | jobs: 12 | build: 13 | if: ${{ github.repository == 'slaclab/pydm' }} 14 | name: Build new release 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Repository 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up Python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.x' 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install build 30 | - name: Build Package 31 | run: python -m build 32 | 33 | - name: Upload package 34 | uses: actions/upload-artifact@v4.6.0 35 | with: 36 | name: package 37 | path: dist/* 38 | retention-days: 1 39 | 40 | publish: 41 | if: ${{ github.repository == 'slaclab/pydm' }} 42 | name: Publish release to PyPI 43 | runs-on: ubuntu-latest 44 | permissions: 45 | id-token: write # Used for trusted publishing 46 | steps: 47 | - name: Download package 48 | uses: actions/download-artifact@v4.1.8 49 | with: 50 | name: package 51 | path: dist/ 52 | 53 | - name: Publish package distributions to PyPI 54 | uses: pypa/gh-action-pypi-publish@release/v1 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # PyTest Cache 2 | .pytest_cache 3 | 4 | # OSX DS_Store 5 | .DS_Store 6 | 7 | # PyCharm 8 | .idea 9 | 10 | # PyDev 11 | .settings 12 | *.project 13 | *.pydevproject 14 | 15 | # Byte-compiled / optimized / DLL files 16 | __pycache__/ 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | pydm/_version.py 35 | sdist/ 36 | var/ 37 | wheels/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # celery beat schedule file 91 | celerybeat-schedule 92 | 93 | # SageMath parsed files 94 | *.sage.py 95 | 96 | # Environments 97 | .env 98 | .venv 99 | env/ 100 | venv/ 101 | ENV/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | 116 | # vim 117 | *.swp 118 | 119 | # Microsoft Visual Studio Code editor 120 | .vscode/ 121 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # no precommit checks should run on structured data files, since they can have odd formatting/spelling 2 | exclude: '\.(json|ui)$' 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v5.0.0 7 | hooks: 8 | - id: end-of-file-fixer 9 | files: '\.(py|txt)$' # Only run on .py and .txt files 10 | - id: trailing-whitespace 11 | files: '\.(py|txt)$' # Only run on .py and .txt files 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.11.2 14 | hooks: 15 | - id: ruff 16 | args: [--line-length, '120', --fix, --exit-non-zero-on-fix, --select, TID] # TID = fix and enforce absolute imports 17 | - id: ruff-format 18 | args: [--line-length, '120'] 19 | - repo: https://github.com/codespell-project/codespell 20 | rev: v2.4.1 21 | hooks: 22 | - id: codespell 23 | # don't correct 'slac' to 'slack' 24 | args: ["--check-hidden", "-L", "slac"] 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include pydm/_version.py 3 | include pydm/utilities/*.pkl 4 | include pydm/utilities/fontawesome* 5 | include pydm/about_pydm/contributors.txt 6 | include pydm/default_stylesheet.qss 7 | include pydm/widgets/icons/* 8 | include requirements.txt 9 | include dev-requirements.txt 10 | include README.md 11 | include LICENSE.md 12 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | ### Getting Started 2 | 3 | PyDM offers a great step-by-step [tutorial](https://slaclab.github.io/pydm-tutorial) 4 | with detailed information to help you get started with creating displays and even 5 | more complex applications. 6 | 7 | ### Bug reports & Feature request 8 | 9 | If you spot a problem with PyDM, please let us know by following the template in 10 | here: [Report a bug](https://github.com/slaclab/pydm/issues/new?template=bug-report.md). 11 | 12 | Ideas or suggestions for enhancements are more than welcome. Please use the following 13 | template in here: [Request feature](https://github.com/slaclab/pydm/issues/new?template=feature-request.md). 14 | 15 | ### Contact us 16 | 17 | If you have questions of comments in general about PyDM we want to know. 18 | 19 | You can choose between the channels open for communication the one that best fit you: 20 | 21 | - Chat channel using [Gitter]() (Public) 22 | - Stanford & SLAC [Slack channel]() (Limited for users with a Stanford Account) 23 | - PyDM [Mailing List](https://listserv.slac.stanford.edu/cgi-bin/wa?SUBED1=PYDM-USERS) (Limited for SLAC users for now) 24 | 25 | or you can [file a bug](https://github.com/slaclab/pydm/issues/new?template=bug-report.md) and let us know where our documentation could be improved. -------------------------------------------------------------------------------- /conda-recipe/build.sh: -------------------------------------------------------------------------------- 1 | # Install the package 2 | $PYTHON -m pip install . --no-deps -vv 3 | 4 | # Create auxiliary dirs 5 | mkdir -p $PREFIX/etc/conda/activate.d 6 | mkdir -p $PREFIX/etc/conda/deactivate.d 7 | mkdir -p $PREFIX/etc/pydm 8 | 9 | # Create auxiliary vars 10 | DESIGNER_PLUGIN_PATH=$PREFIX/etc/pydm 11 | DESIGNER_PLUGIN=$DESIGNER_PLUGIN_PATH/pydm_designer_plugin.py 12 | ACTIVATE=$PREFIX/etc/conda/activate.d/pydm 13 | DEACTIVATE=$PREFIX/etc/conda/deactivate.d/pydm 14 | 15 | echo "from pydm.widgets.qtplugins import *" >> $DESIGNER_PLUGIN 16 | 17 | echo "export PYQTDESIGNERPATH=\$CONDA_PREFIX/etc/pydm:\$PYQTDESIGNERPATH" >> $ACTIVATE.sh 18 | echo "unset PYQTDESIGNERPATH" >> $DEACTIVATE.sh 19 | 20 | echo '@echo OFF' >> $ACTIVATE.bat 21 | echo 'IF "%PYQTDESIGNERPATH%" == "" (' >> $ACTIVATE.bat 22 | echo 'set PYQTDESIGNERPATH=%CONDA_PREFIX%\etc\pydm' >> $ACTIVATE.bat 23 | echo ')ELSE (' >> $ACTIVATE.bat 24 | echo 'set PYQTDESIGNERPATH=%CONDA_PREFIX%\etc\pydm;%PYQTDESIGNERPATH%' >> $ACTIVATE.bat 25 | echo ')' >> $ACTIVATE.bat 26 | 27 | unset DESIGNER_PLUGIN_PATH 28 | unset DESIGNER_PLUGIN 29 | unset ACTIVATE 30 | unset DEACTIVATE 31 | -------------------------------------------------------------------------------- /conda-recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set package_name = "pydm" %} 2 | {% set import_name = "pydm" %} 3 | {% set version = load_file_regex(load_file=os.path.join(import_name, "_version.py"), regex_pattern=".*version = '(\S+)'").group(1) %} 4 | 5 | package: 6 | name : {{ package_name }} 7 | version : {{ version }} 8 | 9 | source: 10 | path: .. 11 | 12 | build: 13 | noarch: python 14 | entry_points: 15 | - pydm = pydm_launcher.main:main 16 | 17 | requirements: 18 | host: 19 | - python >=3.9 20 | - pip 21 | - setuptools 22 | - setuptools_scm 23 | - pyqt =5 24 | - qtpy >=2.2.0 25 | run: 26 | - python >=3.9 27 | - six 28 | - numpy 29 | - scipy 30 | - pyepics 31 | - pyqt =5 32 | - pyqtgraph 33 | - qtpy >=2.2.0 34 | - entrypoints 35 | 36 | test: 37 | imports: 38 | - pydm 39 | 40 | about: 41 | home: https://github.com/slaclab/pydm 42 | license: SLAC Open 43 | license_family: OTHER 44 | license_file: LICENSE.md 45 | summary: 'Python Display Manager' 46 | description: | 47 | PyDM is a PyQt-based framework for building user interfaces for control systems. 48 | The goal is to provide a no-code, drag-and-drop system to make simple screens, 49 | as well as a straightforward Python framework to build complex applications. 50 | doc_url: https://slaclab.github.io/pydm/ 51 | dev_url: https://github.com/slaclab/pydm 52 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | codecov 2 | pytest>=3.6 3 | pytest-qt 4 | pytest-cov 5 | pytest-timeout 6 | p4p 7 | pre-commit 8 | -------------------------------------------------------------------------------- /docs-requirements.txt: -------------------------------------------------------------------------------- 1 | doctr 2 | sphinx==5.3.0 3 | sphinx_rtd_theme 4 | sphinxcontrib-napoleon 5 | -------------------------------------------------------------------------------- /docs/source/_static/data_plugins/calc_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/data_plugins/calc_example.gif -------------------------------------------------------------------------------- /docs/source/_static/data_plugins/waveform_curve_local_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/data_plugins/waveform_curve_local_plugin.png -------------------------------------------------------------------------------- /docs/source/_static/help_files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/help_files.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/all_motors1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/all_motors1.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/application.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/components.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/designer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/designer.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/expert/expert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/expert/expert.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/expert/expert_all_widgets_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/expert/expert_all_widgets_ok.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/expert/expert_layout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/expert/expert_layout.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/expert/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/expert/widgets.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_2.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_3.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_4.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_5.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_6.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_8.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_3_9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_3_9.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_all_widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_all_widgets.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_all_widgets_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_all_widgets_ok.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_desc.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/inline_layout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/inline_layout.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/inline/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/inline/widgets.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/little_code/main.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/little_code/main.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/main/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/main/main.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/main/main_all_widgets_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/main/main_all_widgets_ok.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/main/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/main/widgets.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/new_widget.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/new_widget.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/new_widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/new_widget.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/pydm_properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/pydm_properties.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/python/all_motors.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/python/all_motors.gif -------------------------------------------------------------------------------- /docs/source/_static/tutorials/action/python/all_motors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/action/python/all_motors.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/code/main.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from pydm import Display 3 | from scipy.ndimage.measurements import maximum_position 4 | 5 | 6 | class BeamPositioning(Display): 7 | def __init__(self, parent=None, args=None): 8 | super().__init__(parent=parent, args=args) 9 | # Attach our custom process_image method 10 | self.ui.imageView.process_image = self.process_image 11 | # Hook up to the newImageSignal so we can update 12 | # our widgets after the new image is done 13 | self.ui.imageView.newImageSignal.connect(self.show_blob) 14 | # Store blob coordinate 15 | self.blob = (0, 0) 16 | 17 | def ui_filename(self): 18 | # Point to our UI file 19 | return "main.ui" 20 | 21 | def ui_filepath(self): 22 | # Return the full path to the UI file 23 | return path.join(path.dirname(path.realpath(__file__)), self.ui_filename()) 24 | 25 | def show_blob(self, *args, **kwargs): 26 | # If we have a blob, present the coordinates at label 27 | if self.blob != (0, 0): 28 | blob_txt = "Blob Found:" 29 | blob_txt += " ({}, {})".format(self.blob[1], self.blob[0]) 30 | else: 31 | # If no blob was found, present the "Not Found" message 32 | blob_txt = "Blob Not Found" 33 | # Update the label text 34 | self.ui.lbl_blobs.setText(blob_txt) 35 | 36 | def process_image(self, new_image): 37 | # Consider the maximum as the Blob since we have only 38 | # one blob. 39 | self.blob = maximum_position(new_image) 40 | # Send the original image data to the image widget 41 | return new_image 42 | -------------------------------------------------------------------------------- /docs/source/_static/tutorials/code/motor_db.txt: -------------------------------------------------------------------------------- 1 | IOC:m1 2 | IOC:m2 3 | IOC:m3 4 | IOC:m4 5 | IOC:m5 6 | IOC:m6 7 | IOC:m7 8 | IOC:m8 9 | -------------------------------------------------------------------------------- /docs/source/_static/tutorials/designer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/designer.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/intro/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/intro/architecture.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/intro/main_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/intro/main_window.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/new_vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/new_vm.png -------------------------------------------------------------------------------- /docs/source/_static/tutorials/pydm_properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/tutorials/pydm_properties.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/analog_indicator/analog_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/analog_indicator/analog_indicator.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/analog_indicator/no_minor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/analog_indicator/no_minor.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/analog_indicator/no_upper_minor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/analog_indicator/no_upper_minor.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/analog_indicator/smallest_size_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/analog_indicator/smallest_size_set.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/analog_indicator/smallest_size_visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/analog_indicator/smallest_size_visual.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/archiver_time_plot/archiver_plot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/archiver_time_plot/archiver_plot.gif -------------------------------------------------------------------------------- /docs/source/_static/widgets/archiver_time_plot/archiver_time_plot_designer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/archiver_time_plot/archiver_time_plot_designer.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/curve_editor/curve_editor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/curve_editor/curve_editor.gif -------------------------------------------------------------------------------- /docs/source/_static/widgets/tab_widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/tab_widget.png -------------------------------------------------------------------------------- /docs/source/_static/widgets/widget_rules/open_editor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/widget_rules/open_editor.gif -------------------------------------------------------------------------------- /docs/source/_static/widgets/widget_rules/rules_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/docs/source/_static/widgets/widget_rules/rules_editor.png -------------------------------------------------------------------------------- /docs/source/add_data_plugins.rst: -------------------------------------------------------------------------------- 1 | Including Data Plugins 2 | ###################### 3 | PyDM is built with a flexible architecture such that information from multiple 4 | sources can be displayed without changing widget code. This allows widgets to 5 | be agnostic of the data source that updates them and focus on the logic of 6 | displaying information. PyDM will always import the built-in plugins found in 7 | the ``data_plugins`` folder, but some advanced users may want to include 8 | custom data plugins as well. A mapping of ``protocol`` to ``PyDMPlugin`` is 9 | kept within ``pydm.data_plugins.plugin_modules`` and mirrored in 10 | :attr:`.PyDMApplication.plugins`. This is where the ``PyDMApplication`` will 11 | determine which plugin to use when you provided it with a channel. 12 | 13 | If you would like add a library of plugins from a specific folder for every 14 | session, PyDM will check all paths provided by the environment variable 15 | ``$PYDM_DATA_PLUGINS_PATH``. This folder will be searched for files that fit 16 | the name ``{}_plugin.py`` and attempt to load the contained plugin. Each of 17 | these files should contain a single ``PyDMPlugin``. For those searching for a 18 | more programmatic approach the API is documented below. Take note that all 19 | plugins should be registered before the ``PyDMApplication`` is launched, 20 | otherwise they will not be registered in time for connections to be made. 21 | 22 | .. autofunction:: pydm.data_plugins.add_plugin 23 | 24 | .. autofunction:: pydm.data_plugins.load_plugins_from_path 25 | -------------------------------------------------------------------------------- /docs/source/application.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Application 3 | ======================== 4 | 5 | .. autoclass:: pydm.PyDMApplication 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/channel.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Channel 3 | ======================== 4 | 5 | .. autoclass:: channel.PyDMChannel 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/data_plugins/external_plugins.rst: -------------------------------------------------------------------------------- 1 | External Data Plugins 2 | ===================== 3 | 4 | To add an external data plugin to PyDM, you will need to subclass the following 5 | base class and customize its methods: 6 | 7 | .. autoclass:: pydm.data_plugins.PyDMPlugin 8 | :members: 9 | :inherited-members: 10 | :show-inheritance: 11 | 12 | 13 | You will also need to create a connection class and refer to it with the 14 | ``connection_class`` class variable. It should be subclassed from the 15 | following: 16 | 17 | .. autoclass:: pydm.data_plugins.plugin.PyDMConnection 18 | :members: 19 | :inherited-members: 20 | :show-inheritance: 21 | 22 | 23 | Configuration 24 | ------------- 25 | 26 | There are two options for telling PyDM where your external data plugin can 27 | be found. 28 | 29 | The first is the ``PYDM_DATA_PLUGINS_PATH`` environment variable, which is an 30 | delimited list of paths to search for files that match the pattern 31 | ``*_plugin.py``. They can be in the provided path or any subdirectory of that 32 | path. On Linux, the delimiter is ":" whereas on Windows it is ";". 33 | 34 | Alternatively, for Python packages that contain external data plugins, an 35 | entrypoint may be used to locate it. 36 | 37 | Here is an example ``setup.py`` that could be used to locate a PyDM data plugin 38 | in your own Python package: 39 | 40 | .. code:: python 41 | 42 | from setuptools import setup, find_packages 43 | 44 | setup( 45 | name="my_package", 46 | # ... other settings will go here 47 | entry_points={ 48 | "gui_scripts": ["my_package_gui=my_package.main:main"], 49 | "pydm.data_plugin": [ 50 | "my_package=my_package.data:PluginClassName", 51 | ], 52 | }, 53 | install_requires=[], 54 | ) 55 | 56 | 57 | This would assume that you have the following: 58 | 59 | 1. A package named "my_package" with ``my_package/__init__.py`` and 60 | ``my_package/data.py``. 61 | 2. In ``my_package/data.py``, a ``PluginClassName`` that inherits from 62 | :class:`~pydm.data_plugins.PyDMPlugin`. 63 | 64 | After running ``pip install`` on the package, it should be readily available 65 | in PyDM. 66 | -------------------------------------------------------------------------------- /docs/source/development/resources.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Recommended Resources 3 | ===================== 4 | 5 | Packages We Recommend 6 | ===================== 7 | * `Pytest `_ -- Python Testing Framework 8 | * `Flake8 `_ -- Style Enforcement 9 | * `Sphinx `_ -- Automated HTML Documentation 10 | 11 | External Tutorials and Guides 12 | ============================= 13 | * `Git Tutorial `_ -- Thorough Git beginner guide 14 | * `Numpy Development Guide `_ -- A development guide from the folks at NumPy 15 | * `PEP8 `_ -- Python Coding Conventions 16 | * `Git Flight Rules `_ -- Specific instructions for performing advanced tasks in Git 17 | * `NumPy Documentation Guide `_ -- Guide to making NumPy style documentation 18 | * `Conda `_ -- Getting started guide for using Conda 19 | 20 | -------------------------------------------------------------------------------- /docs/source/help_files.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Adding Help Files 3 | ================= 4 | 5 | If you are creating a display and would like to add some documentation on how it works, PyDM provides the ability 6 | to do this with a minimum of extra effort. By placing a .txt or .html file in the same directory as your display, 7 | PyDM will load this file and automatically add it to both the View menu of the top menu bar, as well as the right 8 | click menu of widgets on the display. 9 | 10 | In order for PyDM to associate the help file with your display, it must have the same name as your display file. For 11 | example, let's say that we have a file called drawing_demo.ui. By adding a file called drawing_demo.txt to the same 12 | location, PyDM will load that file along with the display. 13 | 14 | .. figure:: /_static/help_files.gif 15 | :scale: 100 % 16 | :align: center 17 | :alt: Help files 18 | 19 | Where to find the automatically loaded help file 20 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | PyDM - Python Display Manager 2 | ============================= 3 | 4 | PyDM is a PyQt-based framework for building user interfaces for control systems. 5 | 6 | The goal is to provide a no-code, drag-and-drop system to make simple screens, 7 | as well as a straightforward python framework to build complex applications. 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :caption: Getting Started 12 | 13 | installation.rst 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | :caption: Configuration 18 | 19 | configuration.rst 20 | 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | :caption: Tutorials 25 | 26 | tutorials/index.rst 27 | 28 | .. toctree:: 29 | :maxdepth: 2 30 | :caption: User & API Documentation 31 | 32 | stylesheets.rst 33 | help_files.rst 34 | widgets/index.rst 35 | widgets/widget_rules/index.rst 36 | add_data_plugins.rst 37 | application.rst 38 | channel.rst 39 | utilities/index.rst 40 | 41 | .. toctree:: 42 | :maxdepth: 1 43 | :caption: Data Plugins 44 | 45 | data_plugins/local_plugin.rst 46 | data_plugins/calc_plugin.rst 47 | data_plugins/p4p_plugin.rst 48 | data_plugins/external_plugins.rst 49 | 50 | .. toctree:: 51 | :maxdepth: 1 52 | :caption: Developers Corner 53 | 54 | development/development.rst 55 | development/external_tools.rst 56 | widgets/widget_rules/customizing.rst 57 | development/designer_widgets.rst 58 | development/resources.rst 59 | 60 | .. toctree:: 61 | :maxdepth: 1 62 | :caption: Links 63 | :hidden: 64 | 65 | PyDM GitHub 66 | SLAC-wide GitHub 67 | -------------------------------------------------------------------------------- /docs/source/tutorials/action/designer.rst: -------------------------------------------------------------------------------- 1 | Building Your First Display with Qt Designer 2 | ============================================ 3 | 4 | This section will guide you through the work necessary to create PyDM screens 5 | using Qt Designer. 6 | 7 | To make it more interesting, we will develop three pieces of the 8 | :ref:`components ` described in our 9 | :ref:`tutorial application `. 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :caption: Components 14 | 15 | designer_inline.rst 16 | designer_expert.rst 17 | designer_main.rst 18 | -------------------------------------------------------------------------------- /docs/source/tutorials/action/tutorial.rst: -------------------------------------------------------------------------------- 1 | .. _Application: 2 | 3 | About the Application 4 | ===================== 5 | 6 | 7 | To demonstrate the concepts and capabilities of PyDM, let's develop a real 8 | application composed of PyDM widgets for beam positioning and alignment. 9 | 10 | 11 | PyDM allows users to create screens in three ways: 12 | 13 | #. Using only the Qt Designer application (.ui file) 14 | #. Using Qt Designer and Python Code (.ui and .py files) 15 | #. Using only Python code (.py file) 16 | 17 | In most of the cases users will choose between numbers 1 and 2 and in rare cases 18 | go with number 3. 19 | 20 | This tutorial will also cover the three scenarios above while building the proposed 21 | application. 22 | 23 | The application is a simulated x-ray beam positioning/alignment application 24 | in which the data from a camera will be presented along with two mirror motor 25 | axes to position the beam in X and Y. 26 | 27 | .. figure:: /_static/tutorials/action/application.png 28 | :scale: 100 % 29 | :align: center 30 | :alt: Tutorial Application 31 | 32 | Proposed Application Main Screen 33 | 34 | .. _App Components: 35 | 36 | Macro Components 37 | ---------------- 38 | 39 | .. figure:: /_static/tutorials/action/components.png 40 | :scale: 100 % 41 | :align: center 42 | :alt: Tutorial Application 43 | 44 | - The ``main.ui`` file (Highlighted in Red) uses an embedded display 45 | (Highlighted in Green) two times, which points to ``inline_motor.ui`` for **Motor X** 46 | and **Motor Y**. 47 | 48 | - Inside of this embedded display there is a related display button (Highlighted 49 | in Orange) which launches the ``expert_motor.ui`` for configuration of motor 50 | parameters. 51 | 52 | - Finally, the **View All Motors** related display button (Highlighted in Blue) 53 | launches the ``all_motors.py`` screen in which we can list all motor axes 54 | available. 55 | -------------------------------------------------------------------------------- /docs/source/tutorials/contrib/help.rst: -------------------------------------------------------------------------------- 1 | Getting Help With PyDM 2 | ====================== 3 | 4 | If you have questions of comments in general about PyDM we want to know. 5 | 6 | You can choose between the channels open for communication the one that best fit you: 7 | 8 | - `Chat channel using Gitter `_ (Public) 9 | - `Stanford & SLAC Slack channel `_ (Limited for users with a Stanford Account) 10 | - `PyDM Mailing List `_ (Limited for SLAC users for now) 11 | 12 | or you can `file a bug `_ and let us know where our documentation could be improved. 13 | -------------------------------------------------------------------------------- /docs/source/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======================================== 3 | 4 | PyDM (Python Display Manager) is a new framework for building control system 5 | graphical user interfaces using Python and Qt. 6 | 7 | It provides a system for the drag-and-drop creation of user interfaces using 8 | Qt Designer, and also allows for the creation of displays driven by Python code. 9 | 10 | PyDM is intended to span the range from simple displays without any dynamic 11 | behavior to complex high level applications, with the same set of widgets. 12 | 13 | Developers can extend the framework with custom widgets for site-specific 14 | tasks, and data plugins for multiple control systems. 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | :caption: Before You Start 19 | 20 | intro.rst 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | :caption: Introduction 25 | 26 | intro/launcher.rst 27 | intro/channels.rst 28 | intro/data_arch.rst 29 | intro/macros.rst 30 | intro/widgets.rst 31 | intro/datasource.rst 32 | intro/features.rst 33 | 34 | .. toctree:: 35 | :maxdepth: 2 36 | :caption: Hands-on 37 | 38 | action/tutorial.rst 39 | action/intro_designer.rst 40 | action/designer.rst 41 | action/intro_python.rst 42 | action/little_code.rst 43 | action/python.rst 44 | 45 | .. toctree:: 46 | :maxdepth: 1 47 | :caption: Contributing 48 | 49 | contrib/help.rst 50 | contrib/requests.rst 51 | 52 | .. toctree:: 53 | :maxdepth: 1 54 | :caption: Links 55 | :hidden: 56 | -------------------------------------------------------------------------------- /docs/source/tutorials/intro.rst: -------------------------------------------------------------------------------- 1 | .. _Setup: 2 | 3 | Tutorial Setup 4 | ========================== 5 | 6 | PCASpy Server 7 | --------------- 8 | 9 | A `PCASpy `_ server provides PVs for the tutorial files to read/write. 10 | 11 | The server mimics some PVs of a motor and camera, and is located as follows: 12 | .. code-block:: bash 13 | 14 | examples/testing_ioc/pydm-tutorial-ioc 15 | 16 | Installing PCASpy from the documentation above and following the :ref:`pydm installation instructions` provides all needed prerequisites for this tutorial. 17 | 18 | Using the PCASpy Server 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | .. note:: 22 | You will need to export the following variable in each terminal that will run either the PCASpy server or pydm: 23 | .. code-block:: bash 24 | 25 | export EPICS_CA_MAX_ARRAY_BYTES=300000 26 | 27 | Run the server as follows: 28 | .. code-block:: bash 29 | 30 | ./examples/testing_ioc/pydm-tutorial-ioc 31 | 32 | In another terminal window, enable the sever's running state: 33 | .. code-block:: bash 34 | 35 | caput IOC:Run 1 36 | 37 | The server will now be running and the tutorial files can access the necessary PV's. 38 | 39 | In another (third) terminal window, the completed tutorial files can be ran as follows: 40 | .. code-block:: bash 41 | 42 | pydm .ui|.py -------------------------------------------------------------------------------- /docs/source/tutorials/intro/channels.rst: -------------------------------------------------------------------------------- 1 | .. _Channel: 2 | 3 | Channels 4 | ======== 5 | 6 | **Channels** are the bridge between PyDM Widgets and the different Data Plugins 7 | provided. 8 | 9 | Usually channels are specified in the following format: 10 | 11 | .. code-block:: python 12 | 13 | :// 14 | 15 | Where ``protocol`` is a unique identifier for a given Data Plugin used with PyDM 16 | and ``channel address`` will vary depending on the data plugin. Every plugin 17 | should document the expected address format for users. 18 | 19 | Here are some examples: 20 | 21 | .. code-block:: python 22 | 23 | ca://MTEST:Float 24 | 25 | Where ``ca`` means the Channel Access plugin and ``MTEST:Float`` is the PV name in this case. 26 | 27 | Another example is the Archiver Appliance plugin in which channels are specified as: 28 | 29 | .. code-block:: python 30 | 31 | archiver://pv=test:pv:123&donotchunk 32 | 33 | In which everything in the ``channel address`` section is the same as what is sent 34 | to the ``retrieval`` part of the Archiver as specified at the **Retrieving data** 35 | **using other tools** section of the `Archiver Appliance User Guide `_ -------------------------------------------------------------------------------- /docs/source/tutorials/intro/data_arch.rst: -------------------------------------------------------------------------------- 1 | .. _DataArchitecture: 2 | 3 | Data Architecture 4 | ================= 5 | 6 | * PyDM widgets are data-source agnostic, and communicate with the PyDM Application through Qt signals and slots. 7 | 8 | * The PyDM application routes data between the widgets and data source plugins. 9 | 10 | * A data source plugin speaks to a particular source of data (EPICS, HTTP, modbus, databases, etc). 11 | 12 | * This system makes it possible to mix-and-match different data sources within the same display, using the same widget set. 13 | 14 | .. figure:: /_static/tutorials/intro/architecture.png 15 | :scale: 25 % 16 | :align: center 17 | :alt: PyDM Data Plugin Architecture -------------------------------------------------------------------------------- /docs/source/tutorials/intro/features.rst: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | 5 | Adding Menu Actions 6 | ------------------- 7 | 8 | You can add actions to the default menu bar in 2 ways: 9 | 10 | * Add any custom action to the "Actions" drop down 11 | * Add a "save", "save as", and/or "load" function to the "File" drop down 12 | 13 | To add to the menu bar, overload the ``menu_items()`` and ``file_menu_items()`` 14 | functions in your ``Display`` subclass. These functions should return dictionaries, 15 | where the keys are the action names, and the values one of the following: 16 | 17 | * A callable 18 | * A two element tuple, where the first item is a callable and the second is a keyboard shortcut 19 | * A dictionary corresponding to a sub menu, with the same key-value format so far described. This is only available for the "Actions" menu, not for the "File" menu 20 | 21 | .. note:: 22 | The only accepted keys for the "File" menu are: "save", "save_as", and "load" 23 | 24 | 25 | An example: 26 | 27 | .. code:: python 28 | 29 | from pydm import Display 30 | class MyDisplay(Display): 31 | 32 | def __init__(self, parent=None, args=None, macros=None): 33 | super().__init__(parent=parent, args=args, macros=macros) 34 | 35 | def file_menu_items(self): 36 | return {"save": self.save_function, "load": (self.load_function, "Ctrl+L")} 37 | 38 | def menu_items(self): 39 | return {"Action1": self.action1_function, "submenu": {"Action2": self.action2_function, "Action3": self.action3_function}} 40 | 41 | def save_function(self): 42 | # do something to save your data 43 | 44 | def load_function(self): 45 | # do something to load your data 46 | 47 | def action1_function(self): 48 | # do action 1 49 | 50 | def action2_function(self): 51 | # do action 2 52 | 53 | def action3_function(self): 54 | # do action 3 -------------------------------------------------------------------------------- /docs/source/tutorials/intro/macros.rst: -------------------------------------------------------------------------------- 1 | .. _Macros: 2 | 3 | Macro Substitution 4 | ================== 5 | 6 | PyDM has support for macro substitution, which is a way to make a .ui template 7 | for a display, and fill in variables in the template when the display is opened. 8 | 9 | The macro system is also a good way to supply data to python-based displays when 10 | launching them from the command line, related display button, or as an embedded 11 | display. 12 | 13 | Inserting Macro Variables 14 | ------------------------- 15 | 16 | Anywhere in a .ui file, you can insert a macro of the following form: ``${variable}``. 17 | Note that Qt Designer will only let you use macros in string properties, but you 18 | can insert macros anywhere in a .ui file using a text editor. 19 | 20 | Replacing Macro Variables at Launch Time 21 | ---------------------------------------- 22 | 23 | When launching a .ui file which contains macro variables, specify values for each 24 | variable using the '-m' flag on the command line: 25 | 26 | .. code-block:: bash 27 | 28 | python pydm.py -m 'variable1=value, variable2=another_value' my_file.ui 29 | 30 | Macros in Python-based Displays 31 | ------------------------------- 32 | If you open a python file and specify macros (via the command line, related display 33 | button, or embedded display widget), the macros will be passed as a dictionary to 34 | the Display class initializer, where they can be accessed and used to generate the 35 | display. 36 | 37 | In addition, if the Display class specifies a .ui file to generate its user 38 | interface, macro substitution will occur inside the .ui file. 39 | 40 | Macro Behavior at Run Time 41 | -------------------------- 42 | PyDM will remember the macros used to launch a display, and reuse them when 43 | navigating with the forward, back, and home buttons. When a new display is opened, 44 | any macros defined on the current window are also passed to the new display. 45 | This lets you cascade macros to child displays. -------------------------------------------------------------------------------- /docs/source/utilities/index.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Utilities 3 | ============================ 4 | PyDM comes with a set of utility methods ranging from simple replacement 5 | of protocol in a string, handling macro substitution in files, etc. 6 | 7 | ------- 8 | General 9 | ------- 10 | .. automodule:: pydm.utilities 11 | :members: 12 | 13 | -------- 14 | Icons 15 | -------- 16 | .. autoclass:: pydm.utilities.iconfont.IconFont 17 | :members: 18 | 19 | -------- 20 | Macro 21 | -------- 22 | .. automodule:: pydm.utilities.macro 23 | :members: 24 | 25 | -------- 26 | Colors 27 | -------- 28 | .. automodule:: pydm.utilities.colors 29 | :members: 30 | 31 | -------- 32 | Units 33 | -------- 34 | .. automodule:: pydm.utilities.units 35 | :members: 36 | -------------------------------------------------------------------------------- /docs/source/widgets/byte.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMByteIndicator 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.byte.PyDMByteIndicator 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/checkbox.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMCheckbox 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.checkbox.PyDMCheckbox 6 | :members: 7 | :show-inheritance: 8 | 9 | .. note:: 10 | See `QCheckBox Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/datetime_edit.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMDateTimeEdit 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.datetime.PyDMDateTimeEdit 6 | :members: 7 | :show-inheritance: 8 | 9 | .. note:: 10 | See `QDateTimeEdit Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/datetime_label.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMDateTimeLabel 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.datetime.PyDMDateTimeLabel 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QLabel Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/drawing.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMDrawing Widgets 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingLine 6 | :members: 7 | :show-inheritance: 8 | 9 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingImage 10 | :members: 11 | :show-inheritance: 12 | 13 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingRectangle 14 | :members: 15 | :show-inheritance: 16 | 17 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingTriangle 18 | :members: 19 | :show-inheritance: 20 | 21 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingEllipse 22 | :members: 23 | :show-inheritance: 24 | 25 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingCircle 26 | :members: 27 | :show-inheritance: 28 | 29 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingArc 30 | :members: 31 | :show-inheritance: 32 | 33 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingPie 34 | :members: 35 | :show-inheritance: 36 | 37 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingChord 38 | :members: 39 | :show-inheritance: 40 | 41 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingPolygon 42 | :members: 43 | :show-inheritance: 44 | 45 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingPolyline 46 | :members: 47 | :show-inheritance: 48 | 49 | .. autoclass:: pydm.widgets.drawing.PyDMDrawingIrregularPolygon 50 | :members: 51 | :show-inheritance: 52 | 53 | -------------------------------------------------------------------------------- /docs/source/widgets/embedded_display.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMEmbeddedDisplay 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.embedded_display.PyDMEmbeddedDisplay 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QFrame Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/enum_button.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMEnumButton 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.enum_button.PyDMEnumButton 6 | :members: 7 | :show-inheritance: 8 | 9 | .. note:: 10 | See `QWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/enum_combo_box.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMEnumComboBox 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.enum_combo_box.PyDMEnumComboBox 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QComboBox Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/event_plot.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMEventPlot 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.eventplot.PyDMEventPlot 6 | :members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/widgets/frame.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMFrame 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.frame.PyDMFrame 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QFrame Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/image.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMImageView 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.image.PyDMImageView 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `ImageView Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/index.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Widgets 3 | ============================ 4 | PyDM comes with a set of widgets useful for operating a control system. 5 | 6 | 7 | Display Widgets 8 | --------------- 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | byte.rst 13 | multistate.rst 14 | datetime_label.rst 15 | image.rst 16 | label.rst 17 | log.rst 18 | related_display_button.rst 19 | scale.rst 20 | symbol.rst 21 | analog_indicator.rst 22 | nt_table.rst 23 | 24 | Input Widgets 25 | ------------- 26 | .. toctree:: 27 | :maxdepth: 1 28 | 29 | checkbox.rst 30 | datetime_edit.rst 31 | enum_button.rst 32 | enum_combo_box.rst 33 | line_edit.rst 34 | pushbutton.rst 35 | shell_command.rst 36 | slider.rst 37 | spinbox.rst 38 | waveformtable.rst 39 | 40 | Plot Widgets 41 | ------------ 42 | .. toctree:: 43 | :maxdepth: 1 44 | 45 | curve_editor.rst 46 | scatterplot.rst 47 | timeplot.rst 48 | event_plot.rst 49 | archiver_timeplot.rst 50 | waveformplot.rst 51 | 52 | Container Widgets 53 | ----------------- 54 | .. toctree:: 55 | :maxdepth: 1 56 | 57 | embedded_display.rst 58 | frame.rst 59 | tab_widget.rst 60 | template_repeater.rst 61 | 62 | Drawing Widgets 63 | --------------- 64 | .. toctree:: 65 | :maxdepth: 1 66 | 67 | drawing.rst 68 | 69 | Base Widgets 70 | ------------ 71 | .. toctree:: 72 | :maxdepth: 1 73 | 74 | PyDMWidget.rst 75 | 76 | Utilities 77 | --------- 78 | .. toctree:: 79 | :maxdepth: 1 80 | 81 | utilities.rst 82 | -------------------------------------------------------------------------------- /docs/source/widgets/label.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMLabel 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.label.PyDMLabel 6 | :members: 7 | :show-inheritance: 8 | :exclude-members: DisplayFormat 9 | 10 | 11 | .. note:: 12 | See `QLabel Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/line_edit.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMLineEdit 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.line_edit.PyDMLineEdit 6 | :members: 7 | :show-inheritance: 8 | :exclude-members: DisplayFormat 9 | 10 | 11 | .. note:: 12 | See `QLineEdit Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/log.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMLogDisplay 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.logdisplay.PyDMLogDisplay 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/multistate.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMMultiStateIndicator 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.byte.PyDMMultiStateIndicator 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/nt_table.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMNTTable 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.nt_table.PythonTableModel 6 | :members: 7 | :show-inheritance: 8 | 9 | .. autoclass:: pydm.widgets.nt_table.PyDMNTTable 10 | :members: 11 | :show-inheritance: -------------------------------------------------------------------------------- /docs/source/widgets/pushbutton.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMPushButton 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.pushbutton.PyDMPushButton 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QPushButton Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/related_display_button.rst: -------------------------------------------------------------------------------- 1 | ######################## 2 | PyDMRelatedDisplayButton 3 | ######################## 4 | 5 | .. autoclass:: pydm.widgets.related_display_button.PyDMRelatedDisplayButton 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QPushButton Documentation `_ for all inherited properties and methods. 12 | -------------------------------------------------------------------------------- /docs/source/widgets/scale.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMScaleIndicator 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.scale.PyDMScaleIndicator 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QFrame Documentation `_ for all inherited properties and methods. 12 | -------------------------------------------------------------------------------- /docs/source/widgets/scatterplot.rst: -------------------------------------------------------------------------------- 1 | ############### 2 | PyDMScatterPlot 3 | ############### 4 | 5 | .. autoclass:: pydm.widgets.scatterplot.PyDMScatterPlot 6 | :members: 7 | :show-inheritance: 8 | 9 | ##################### 10 | ScatterPlotCurveItem 11 | ##################### 12 | 13 | .. autoclass:: pydm.widgets.scatterplot.ScatterPlotCurveItem 14 | :members: 15 | :show-inheritance: 16 | -------------------------------------------------------------------------------- /docs/source/widgets/shell_command.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMShellCommand 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.shell_command.PyDMShellCommand 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QPushButton Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/slider.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMSlider 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.slider.PyDMSlider 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QFrame Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/spinbox.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMSpinbox 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.spinbox.PyDMSpinbox 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QDoubleSpinBox Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/symbol.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMSymbol 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.symbol.PyDMSymbol 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QWidget Documentation `_ for all inherited properties and methods. 12 | -------------------------------------------------------------------------------- /docs/source/widgets/tab_widget.rst: -------------------------------------------------------------------------------- 1 | ############# 2 | PyDMTabWidget 3 | ############# 4 | 5 | The PyDM Tab Widget is a container widget that lets you switch between different pages of content using a tab bar. Each tab has an optional alarm channel. When a tab specifies an alarm channel, an alarm indicator will appear next to the label for that tab. When the channel's alarm severity changes, the indicator will update accordingly. This is most useful for 'summary' alarms, where you have one alarm that represents the alarm state of a whole group of devices. 6 | 7 | .. figure:: /_static/widgets/tab_widget.png 8 | :scale: 100 % 9 | :alt: A screenshot of the PyDM Tab Widget. 10 | 11 | A screenshot of the PyDM Tab Widget. The alarm indicators for each tab are displayed to the left of the tab's label. 12 | 13 | Using the PyDM Tab Widget in Designer 14 | ===================================== 15 | 16 | In designer, drag a tab widget from the widget list onto your display, and resize it appropriately. You can use the property editor to give the current tab a label (the 'currentTabText' property), a name (the 'currentTabName' property - this is what you will use to refer to this tab in code), and an alarm channel ('currentTabAlarmChannel'). To add a new tab to the tab widget, right click on the widget and select 'Insert Page'. You can choose to insert before or after the current tab. To remove a tab, right click on the tab widget, and select 'Page X of Y' -> Delete. 17 | 18 | 19 | Widget Properties 20 | ================= 21 | 22 | ====================== ==== =========== 23 | Property Type Description 24 | ====================== ==== =========== 25 | currentTabAlarmChannel str A channel to use for the alarm indicator on the current tab. 26 | currentTabText str The label to use for the current tab. 27 | currentTabName str A name for the tab. This is how you will refer to the tab in python code. 28 | ====================== ==== =========== 29 | 30 | 31 | API Documentation 32 | ================= 33 | 34 | .. autoclass:: pydm.widgets.tab_bar.PyDMTabWidget 35 | :members: 36 | :show-inheritance: 37 | 38 | 39 | .. note:: 40 | See `QTabWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/template_repeater.rst: -------------------------------------------------------------------------------- 1 | #################### 2 | PyDMTemplateRepeater 3 | #################### 4 | 5 | .. autoclass:: pydm.widgets.template_repeater.PyDMTemplateRepeater 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QFrame Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/timeplot.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMTimePlot 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.timeplot.PyDMTimePlot 6 | :members: 7 | :show-inheritance: 8 | 9 | ####################### 10 | TimePlotCurveItem 11 | ####################### 12 | 13 | .. autoclass:: pydm.widgets.timeplot.TimePlotCurveItem 14 | :members: 15 | :show-inheritance: 16 | -------------------------------------------------------------------------------- /docs/source/widgets/utilities.rst: -------------------------------------------------------------------------------- 1 | ######################## 2 | Display Format Utilities 3 | ######################## 4 | 5 | .. currentmodule:: pydm.widgets.display_format 6 | 7 | .. autoclass:: DisplayFormat 8 | :members: 9 | 10 | .. autofunction:: parse_value_for_display 11 | -------------------------------------------------------------------------------- /docs/source/widgets/waveformplot.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMWaveformPlot 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.waveformplot.PyDMWaveformPlot 6 | :members: 7 | :show-inheritance: 8 | 9 | ####################### 10 | WaveformCurveItem 11 | ####################### 12 | 13 | .. autoclass:: pydm.widgets.waveformplot.WaveformCurveItem 14 | :members: 15 | :show-inheritance: 16 | -------------------------------------------------------------------------------- /docs/source/widgets/waveformtable.rst: -------------------------------------------------------------------------------- 1 | ####################### 2 | PyDMWaveformTable 3 | ####################### 4 | 5 | .. autoclass:: pydm.widgets.waveformtable.PyDMWaveformTable 6 | :members: 7 | :show-inheritance: 8 | 9 | 10 | .. note:: 11 | See `QTableWidget Documentation `_ for all inherited properties and methods. -------------------------------------------------------------------------------- /docs/source/widgets/widget_rules/customizing.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | Customizing Properties for Rules 3 | ================================ 4 | 5 | The rules mechanism is very flexible and allow developers to customize 6 | which properties from the widgets are exposed. 7 | 8 | By default, ``PyDMPrimitiveWidget`` (base class for all the PyDM Widgets) defines 9 | two constants: 10 | 11 | .. code-block:: python 12 | 13 | DEFAULT_RULE_PROPERTY = "Visible" 14 | 15 | RULE_PROPERTIES = { 16 | 'Enable': ['setEnabled', bool], 17 | 'Visible': ['setVisible', bool], 18 | } 19 | 20 | - **DEFAULT_RULE_PROPERTY** 21 | Defines the default property to be selected from the list when creating a new 22 | rule using the ``Rules Editor``. 23 | 24 | - **RULE_PROPERTIES** 25 | This constant holds a dictionary in which the key element is the "user-friendly" 26 | name of the property and the value is a list in which the first element is the 27 | property name as a String and the second is the expected data type. 28 | 29 | You can customize those constants at your widget and that will reflect on what 30 | users are able to tweak and use when creating new rules. -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # pydm-examples: Python Display Manager Examples 2 | [PyDM](https://github.com/slaclab/pydm) is a PyQt-based framework for building user interfaces for control systems. The goal is to provide a no-code, drag-and-drop system to make simple screens, as well as a straightforward python framework to build complex applications. 3 | 4 | # Prerequisites for the examples 5 | * Python 3.6+ 6 | * pydm 7 | * Qt 5.7 or higher 8 | * PyQt5 >= 5.7 9 | * pcaspy (Optional) 10 | pcaspy is needed for the `pydm-testing-ioc` used in most of the examples. 11 | 12 | # Running the Examples 13 | There are various examples of some of the features of the display manager. 14 | To launch a particular display run 'pydm '. 15 | 16 | There is a 'home' display in the examples directory with buttons to launch all the examples: 17 | run 'pydm home.ui' 18 | 19 | Documentation for the examples is yet to come. 20 | Documentation for PyDM is available at http://slaclab.github.io/pydm/. Documentation is somewhat sparse right now, unfortunately. 21 | 22 | # Starting the testing IOC 23 | The testing IOC provides EPICS PVs for use on most of the examples here provided. 24 | As of now the PVs are generated using [pcaspy](https://pcaspy.readthedocs.io/en/latest/), for instructions on how to install this package please refer 25 | to the package [documentation](https://pcaspy.readthedocs.io/en/latest/installation.html) 26 | 27 | After having the dependency installed run the command: 28 | 29 | ```sh 30 | ./testing_ioc/pydm-testing-ioc 31 | ``` 32 | 33 | You may need to check the EPICS address and server port to ensure your local system can find the simulated PVs. Use the following procedure from the command line to identify the EPICS_CA_SERVER_PORT and add to your EPICS_CA_ADDR_LIST: 34 | 35 | ```sh 36 | env | grep EPICS_CA 37 | export EPICS_CA_ADDR_LIST="${EPICS_CA_ADDR_LIST} (your local host):(EPICS_CA_SERVER_PORT)" 38 | ``` 39 | 40 | Then run the testing_ioc as above. 41 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/examples/__init__.py -------------------------------------------------------------------------------- /examples/accessory_window/settings_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 315 11 | 12 | 13 | 14 | Settings Window 15 | 16 | 17 | 18 | 19 | 20 | What would you like to see? 21 | 22 | 23 | 24 | 25 | 26 | 27 | Boring old numbers 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | Track listing for Portishead's 'Roseland NYC Live' album 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Vertical 45 | 46 | 47 | 48 | 20 49 | 40 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/actions/label_text_from_rule.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | [{"channels": [{"trigger": true, "channel": "ca://MTEST:Run"}], "property": "Text", "expression": "{0: \"Its off :(\", 1: \"Its on!\"}[ch[0]]", "name": "Text from value"}] 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | PyDMLabel 32 | QLabel 33 |
pydm.widgets.label
34 |
35 |
36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /examples/archiver_time_plot/archiver_time_plot_example.py: -------------------------------------------------------------------------------- 1 | from pydm import Display 2 | 3 | from qtpy import QtCore 4 | from qtpy.QtWidgets import QHBoxLayout, QApplication, QCheckBox 5 | from pydm.widgets import PyDMArchiverTimePlot 6 | 7 | 8 | class archiver_time_plot_example(Display): 9 | def __init__(self, parent=None, args=None, macros=None): 10 | super().__init__(parent=parent, args=args, macros=macros) 11 | self.app = QApplication.instance() 12 | self.setup_ui() 13 | 14 | def minimumSizeHint(self): 15 | return QtCore.QSize(100, 100) 16 | 17 | def ui_filepath(self): 18 | return None 19 | 20 | def setup_ui(self): 21 | self.main_layout = QHBoxLayout() 22 | self.setLayout(self.main_layout) 23 | self.plot_live = PyDMArchiverTimePlot(background=[255, 255, 255, 255]) 24 | self.plot_archived = PyDMArchiverTimePlot(background=[255, 255, 255, 255]) 25 | self.plot_live.enableCrosshair(True) 26 | self.plot_archived.enableCrosshair(True) 27 | self.chkbx_live = QCheckBox() 28 | self.chkbx_live.setChecked(True) 29 | self.chkbx_archived = QCheckBox() 30 | self.chkbx_archived.setChecked(True) 31 | self.main_layout.addWidget(self.chkbx_live) 32 | self.main_layout.addWidget(self.plot_live) 33 | self.main_layout.addWidget(self.plot_archived) 34 | self.main_layout.addWidget(self.chkbx_archived) 35 | 36 | curve_live = self.plot_live.addYChannel( 37 | y_channel="ca://XCOR:LI29:302:IACT", 38 | name="name", 39 | color="red", 40 | yAxisName="Axis", 41 | useArchiveData=True, 42 | liveData=True, 43 | ) 44 | 45 | curve_archived = self.plot_archived.addYChannel( 46 | y_channel="ca://XCOR:LI28:302:IACT", 47 | name="name", 48 | color="blue", 49 | yAxisName="Axis", 50 | useArchiveData=True, 51 | liveData=False, 52 | ) 53 | 54 | self.chkbx_live.stateChanged.connect(lambda x: self.set_live(curve_live, x)) 55 | self.chkbx_archived.stateChanged.connect(lambda x: self.set_live(curve_archived, x)) 56 | 57 | @staticmethod 58 | def set_live(curve, live): 59 | curve.liveData = live 60 | -------------------------------------------------------------------------------- /examples/ca-filtering/README.md: -------------------------------------------------------------------------------- 1 | # Channel Access Filtering Example 2 | This example exercises the [Epics Channel Filters](http://www.aps.anl.gov/epics/base/R3-15/5-docs/filters.html) functionality with PyDM. 3 | 4 | # Prerequisites 5 | * pydm 6 | * Epics Base R3.15.5 or greater 7 | 8 | # Running the Examples 9 | In one shell do: 10 | ```sh 11 | testIoc -d test.db & 12 | pydm ca_filtering.ui 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /examples/ca-filtering/test.db: -------------------------------------------------------------------------------- 1 | record(calc, "FILTER:Incr"){ 2 | field(INPA, "FILTER:Incr.VAL") 3 | field(CALC, "A+1") 4 | field(SCAN, ".1 second") 5 | field(VAL, 0) 6 | } 7 | 8 | record(calc, "FILTER:Calc"){ 9 | field(INPA, "FILTER:Incr") 10 | field(CALC, "SIN(A % 360*0.01745329252)") 11 | field(PINI, "YES") 12 | field(SCAN, ".1 second") 13 | } 14 | -------------------------------------------------------------------------------- /examples/calc/README.md: -------------------------------------------------------------------------------- 1 | # Calculation Plugin Example 2 | 3 | # Running the Examples 4 | This example uses an EPICS IOC via the `demo.db`. 5 | The Process Variables available are: 6 | 7 | - `DEMO:ANGLE`, the angle in degrees which increments by one every 0.1s 8 | - `DEMO:SIN`, the sine of the angle 9 | - `DEMO:COS`, the cosine of the angle 10 | - `DEMO:TAN`, the tangent of the angle 11 | 12 | To run the IOC do: 13 | ```shell script 14 | softIoc -d demo.db 15 | ``` 16 | 17 | To run the screen do: 18 | 19 | ```shell script 20 | pydm sin_cos_tan.ui 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/calc/demo.db: -------------------------------------------------------------------------------- 1 | record(calc, "DEMO:ANGLE") 2 | { 3 | field(PINI, "YES") 4 | field(VAL, 0) 5 | field(CALC, "(A >= 360) ? 0:A+1") 6 | field(INPA, "DEMO:ANGLE.VAL") 7 | field(SCAN, ".1 second") 8 | } 9 | 10 | record(calc, "DEMO:SIN") 11 | { 12 | field(CALC, "SIN(A*0.0174533)") 13 | field(INPA, "DEMO:ANGLE CP") 14 | } 15 | 16 | record(calc, "DEMO:COS") 17 | { 18 | field(CALC, "COS(A*0.0174533)") 19 | field(INPA, "DEMO:ANGLE CP") 20 | } 21 | 22 | record(calc, "DEMO:TAN") 23 | { 24 | field(CALC, "TAN(A*0.0174533)") 25 | field(INPA, "DEMO:ANGLE CP") 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/camviewer/marker.py: -------------------------------------------------------------------------------- 1 | from qtpy import QtGui 2 | from pyqtgraph import Point, ROI 3 | 4 | 5 | class ImageMarker(ROI): 6 | """A crosshair ROI. Returns the full image line-out for X and Y at the position of the crosshair.""" 7 | 8 | def __init__(self, pos=None, size=None, **kargs): 9 | if size is None: 10 | # size = [100e-6,100e-6] 11 | size = [20, 20] 12 | if pos is None: 13 | pos = [0, 0] 14 | self._shape = None 15 | ROI.__init__(self, pos, size, **kargs) 16 | 17 | self.sigRegionChanged.connect(self.invalidate) 18 | self.aspectLocked = True 19 | 20 | def invalidate(self): 21 | self._shape = None 22 | self.prepareGeometryChange() 23 | 24 | def boundingRect(self): 25 | return self.shape().boundingRect() 26 | 27 | def getArrayRegion(self, data, img, axes=(0, 1)): 28 | # img_point = self.mapToItem(img, self.pos()) 29 | coords = self.getPixelCoords() 30 | ystrip = data[coords[0], :] 31 | xstrip = data[:, coords[1]] 32 | return ([xstrip, ystrip], coords) 33 | 34 | def getPixelCoords(self): 35 | img_point = self.pos() 36 | return (int(img_point.x()), int(img_point.y())) 37 | 38 | def shape(self): 39 | if self._shape is None: 40 | radius = self.getState()["size"][1] 41 | p = QtGui.QPainterPath() 42 | p.moveTo(Point(0, -radius)) 43 | p.lineTo(Point(0, radius)) 44 | p.moveTo(Point(-radius, 0)) 45 | p.lineTo(Point(radius, 0)) 46 | p = self.mapToDevice(p) 47 | stroker = QtGui.QPainterPathStroker() 48 | stroker.setWidth(10) 49 | outline = stroker.createStroke(p) 50 | self._shape = self.mapFromDevice(outline) 51 | return self._shape 52 | 53 | def paint(self, p, *args): 54 | radius = self.getState()["size"][1] 55 | p.setRenderHint(QtGui.QPainter.Antialiasing) 56 | p.setPen(self.currentPen) 57 | p.drawLine(Point(0, -radius), Point(0, radius)) 58 | p.drawLine(Point(-radius, 0), Point(radius, 0)) 59 | -------------------------------------------------------------------------------- /examples/checkbox/checkbox.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 110 20 | 100 21 | 121 22 | 18 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | MTEST:Run 33 | 34 | 35 | ca://MTEST:Run 36 | 37 | 38 | 39 | 40 | 41 | PyDMCheckbox 42 | QCheckBox 43 |
pydm.widgets.checkbox
44 |
45 |
46 | 47 | 48 |
49 | -------------------------------------------------------------------------------- /examples/code_only/code_only.py: -------------------------------------------------------------------------------- 1 | from pydm import Display 2 | from qtpy.QtWidgets import QLabel, QVBoxLayout, QHBoxLayout 3 | 4 | 5 | class MyDisplay(Display): 6 | def __init__(self, parent=None, args=[]): 7 | super().__init__(parent=parent, args=args) 8 | self.setup_ui() 9 | 10 | def setup_ui(self): 11 | main = QHBoxLayout() 12 | sub = QVBoxLayout() 13 | for i in range(10): 14 | sub.addWidget(QLabel(str(i))) 15 | main.addLayout(sub) 16 | self.setLayout(main) 17 | 18 | def ui_filename(self): 19 | return None 20 | 21 | def ui_filepath(self): 22 | return None 23 | -------------------------------------------------------------------------------- /examples/drawing/SLAC_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/examples/drawing/SLAC_logo.jpeg -------------------------------------------------------------------------------- /examples/drawing/gif_test.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 100 20 | 50 21 | 241 22 | 181 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Renders an image given by the ``filename`` property. 31 | This class inherits from PyDMDrawing. 32 | 33 | Parameters 34 | ---------- 35 | parent : QWidget 36 | The parent widget for the Label 37 | init_channel : str, optional 38 | The channel to be used by the widget. 39 | 40 | Attributes 41 | ---------- 42 | null_color : Qt.Color 43 | QColor to fill the image if the filename is not found. 44 | 45 | 46 | 47 | 0.000000000000000 48 | 49 | 50 | ./test.gif 51 | 52 | 53 | 54 | 55 | 56 | PyDMDrawingImage 57 | QWidget 58 |
pydm.widgets.drawing
59 |
60 |
61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /examples/drawing/test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/examples/drawing/test.gif -------------------------------------------------------------------------------- /examples/embedded_displays/embedded_display.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 622 10 | 467 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | This display contains an embedded display widget, which loads another display from a file and nests it inside this one. 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Widget to display other pydm guis inside of the current window. 41 | Requires a list of filenames, either absolute paths or relative to the the 42 | gui this widget is in. 43 | 44 | 45 | 46 | QFrame::Box 47 | 48 | 49 | QFrame::Raised 50 | 51 | 52 | 2 53 | 54 | 55 | waveplot.ui 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | PyDMEmbeddedDisplay 64 | QFrame 65 |
pydm.widgets.embedded_display
66 |
67 |
68 | 69 | 70 |
71 | -------------------------------------------------------------------------------- /examples/embedded_displays/waveplot.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 583 10 | 298 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Amazing! 23 | 24 | 25 | 26 | 27 | 28 | 29 | Wow! 30 | 31 | 32 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Waveform 48 | 49 | 50 | 51 | {"y_channel": "ca://MTEST:Waveform", "x_channel": "ca://MTEST:TimeBase", "name": "", "color": "white"} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | PyDMWaveformPlot 61 | QGraphicsView 62 |
pydm.widgets.waveformplot
63 |
64 |
65 | 66 | 67 |
68 | -------------------------------------------------------------------------------- /examples/enum_buttons/enum.db: -------------------------------------------------------------------------------- 1 | record(mbbo, "TEST:ENUM") 2 | { 3 | field(PINI, "YES") 4 | field(ZRST, "YES") 5 | field(ZRVL, "0") 6 | field(ONST, "NO") 7 | field(ONVL, "1") 8 | field(TWST, "MAYBE") 9 | field(TWVL, "2") 10 | field(VAL, "0") 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/enum_combo_box/enum_combo_box.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 462 10 | 181 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ca://MTEST:Run 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ca://MTEST:Run 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | PyDMEnumComboBox 48 | QWidget 49 |
pydm.widgets.enum_combo_box
50 |
51 | 52 | PyDMLabel 53 | QLabel 54 |
pydm.widgets.label
55 |
56 |
57 | 58 | 59 |
60 | -------------------------------------------------------------------------------- /examples/exception/demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import functools 3 | from qtpy import QtWidgets 4 | from pydm import exception 5 | 6 | 7 | class Screen(QtWidgets.QFrame): 8 | def __init__(self, *args, **kwargs): 9 | super().__init__(*args, **kwargs) 10 | self.setup_ui() 11 | 12 | def setup_ui(self): 13 | self.setLayout(QtWidgets.QVBoxLayout()) 14 | self.btn_install = QtWidgets.QPushButton(self) 15 | self.btn_install.setText("Install Handler") 16 | self.btn_install.clicked.connect(functools.partial(self.setup_handler, True)) 17 | 18 | self.btn_uninstall = QtWidgets.QPushButton(self) 19 | self.btn_uninstall.setText("Uninstall Handler") 20 | self.btn_uninstall.clicked.connect(functools.partial(self.setup_handler, False)) 21 | 22 | self.btn_raise = QtWidgets.QPushButton(self) 23 | self.btn_raise.setText("Raise Exception") 24 | self.btn_raise.clicked.connect(self.raise_exception) 25 | 26 | self.layout().addWidget(self.btn_install) 27 | self.layout().addWidget(self.btn_uninstall) 28 | self.layout().addWidget(self.btn_raise) 29 | 30 | def setup_handler(self, install=True): 31 | if install: 32 | exception.install(use_default_handler=True) 33 | else: 34 | exception.uninstall() 35 | 36 | def raise_exception(self): 37 | raise Exception("This is an exception being raised...") 38 | 39 | 40 | def main(): 41 | app = QtWidgets.QApplication(sys.argv) 42 | screen = Screen() 43 | screen.show() 44 | sys.exit(app.exec_()) 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /examples/external_tool/dummy_tool.py: -------------------------------------------------------------------------------- 1 | from pydm.tools import ExternalTool 2 | from pydm.utilities.iconfont import IconFont 3 | 4 | 5 | class DummyTool(ExternalTool): 6 | def __init__(self): 7 | icon = IconFont().icon("cogs") 8 | name = "Dummy Tool" 9 | group = "Example" 10 | use_with_widgets = False 11 | super().__init__(icon=icon, name=name, group=group, use_with_widgets=use_with_widgets) 12 | 13 | def call(self, channels, sender): 14 | print("Called Dummy Tool from: {} with:".format(sender)) 15 | print("Channels: ", channels) 16 | print("My info: ", self.get_info()) 17 | 18 | def to_json(self): 19 | return "" 20 | 21 | def from_json(self, content): 22 | print("Received from_json: ", content) 23 | 24 | def get_info(self): 25 | ret = ExternalTool.get_info(self) 26 | ret.update({"file": __file__}) 27 | return ret 28 | 29 | 30 | class DummyTool3(ExternalTool): 31 | def __init__(self): 32 | icon = None 33 | name = "Dummy Tool 3" 34 | group = "" 35 | use_with_widgets = False 36 | super().__init__(icon=icon, name=name, group=group, use_with_widgets=use_with_widgets) 37 | 38 | def call(self, channels, sender): 39 | print("Called Dummy Tool 3 from: {} with:".format(sender)) 40 | print("Channels: ", channels) 41 | print("My info: ", self.get_info()) 42 | 43 | def to_json(self): 44 | return "" 45 | 46 | def from_json(self, content): 47 | print("Received from_json: ", content) 48 | 49 | def get_info(self): 50 | ret = ExternalTool.get_info(self) 51 | ret.update({"file": __file__}) 52 | return ret 53 | -------------------------------------------------------------------------------- /examples/external_tool/lookup_path/ignore.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/examples/external_tool/lookup_path/ignore.py -------------------------------------------------------------------------------- /examples/external_tool/lookup_path/new_tool.py: -------------------------------------------------------------------------------- 1 | from pydm.tools import ExternalTool 2 | from pydm.utilities.iconfont import IconFont 3 | 4 | 5 | class DummyTool2(ExternalTool): 6 | def __init__(self): 7 | icon = IconFont().icon("code") 8 | name = "Dummy Tool 2" 9 | group = "Example" 10 | use_with_widgets = True 11 | use_without_widget = False 12 | super().__init__( 13 | icon=icon, 14 | name=name, 15 | group=group, 16 | use_with_widgets=use_with_widgets, 17 | use_without_widget=use_without_widget, 18 | ) 19 | 20 | def call(self, channels, sender): 21 | print("Called Dummy Tool 2 from: {} with:".format(sender)) 22 | print("Channels: ", channels) 23 | print("My info: ", self.get_info()) 24 | 25 | def to_json(self): 26 | return "" 27 | 28 | def from_json(self, content): 29 | print("Received from_json: ", content) 30 | 31 | def get_info(self): 32 | ret = ExternalTool.get_info(self) 33 | ret.update({"file": __file__}) 34 | return ret 35 | -------------------------------------------------------------------------------- /examples/external_tool/lookup_path/root_tool.py: -------------------------------------------------------------------------------- 1 | from pydm.tools import ExternalTool 2 | from pydm.utilities.iconfont import IconFont 3 | 4 | 5 | class RootTool(ExternalTool): 6 | def __init__(self): 7 | icon = IconFont().icon("code") 8 | name = "Root Tool" 9 | group = "" 10 | use_with_widgets = True 11 | super().__init__(icon=icon, name=name, group=group, use_with_widgets=use_with_widgets) 12 | 13 | def call(self, channels, sender): 14 | print("Called Root Tool from: {} with:".format(sender)) 15 | print("Channels: ", channels) 16 | print("My info: ", self.get_info()) 17 | 18 | def to_json(self): 19 | return "" 20 | 21 | def from_json(self, content): 22 | print("Received from_json: ", content) 23 | 24 | def get_info(self): 25 | ret = ExternalTool.get_info(self) 26 | ret.update({"file": __file__}) 27 | return ret 28 | -------------------------------------------------------------------------------- /examples/image_processing/drag-and-drop.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 747 10 | 511 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ca://MTEST:TwoSpotImage 27 | 28 | 29 | ca://MTEST:ImageWidth 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PyDMImageView 38 | QWidget 39 |
pydm.widgets.image
40 |
41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /examples/image_processing/image_view.py: -------------------------------------------------------------------------------- 1 | from pydm import Display 2 | import threading 3 | from skimage.feature import blob_doh 4 | from marker import ImageMarker 5 | from pyqtgraph import mkPen 6 | 7 | 8 | class ImageViewer(Display): 9 | def __init__(self, parent=None, args=None): 10 | super().__init__(parent=parent, args=args) 11 | self.markers_lock = threading.Lock() 12 | self.ui.imageView.process_image = self.process_image 13 | self.ui.imageView.newImageSignal.connect(self.draw_markers) 14 | self.markers = list() 15 | self.blobs = list() 16 | 17 | def ui_filename(self): 18 | return "image_view.ui" 19 | 20 | def draw_markers(self, *args, **kwargs): 21 | with self.markers_lock: 22 | view = self.ui.imageView.getView().getViewBox() 23 | 24 | for m in self.markers: 25 | if m in view.addedItems: 26 | view.removeItem(m) 27 | 28 | for blob in self.blobs: 29 | x, y, size = blob 30 | m = ImageMarker((y, x), size=size, pen=mkPen((100, 100, 255), width=3)) 31 | self.markers.append(m) 32 | view.addItem(m) 33 | 34 | self.ui.numBlobsLabel.setText(str(len(self.blobs))) 35 | 36 | def process_image(self, new_image): 37 | # Find blobs in the image with scikit-image 38 | self.blobs = blob_doh(new_image, max_sigma=512, min_sigma=64, threshold=0.02) 39 | 40 | # Send the original image data to the image widget 41 | return new_image 42 | 43 | 44 | intelclass = ImageViewer 45 | -------------------------------------------------------------------------------- /examples/image_processing/image_view.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 594 10 | 412 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PyDMImageView::Clike 27 | 28 | 29 | ca://MTEST:TwoSpotImage 30 | 31 | 32 | ca://MTEST:ImageWidth 33 | 34 | 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Number of Blobs: 45 | 46 | 47 | 48 | 49 | 50 | 51 | TextLabel 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | PyDMImageView 62 | QWidget 63 |
pydm.widgets.image
64 |
65 |
66 | 67 | 68 |
69 | -------------------------------------------------------------------------------- /examples/image_processing/marker.py: -------------------------------------------------------------------------------- 1 | from qtpy import QtGui 2 | from pyqtgraph import Point, ROI 3 | 4 | 5 | class ImageMarker(ROI): 6 | """A crosshair ROI. Returns the full image line-out for X and Y at the position of the crosshair.""" 7 | 8 | def __init__(self, pos=None, size=None, **kargs): 9 | if size is None: 10 | # size = [100e-6,100e-6] 11 | size = [20, 20] 12 | if pos is None: 13 | pos = [0, 0] 14 | self._shape = None 15 | ROI.__init__(self, pos, size, **kargs) 16 | 17 | self.sigRegionChanged.connect(self.invalidate) 18 | self.aspectLocked = True 19 | 20 | def invalidate(self): 21 | self._shape = None 22 | self.prepareGeometryChange() 23 | 24 | def boundingRect(self): 25 | return self.shape().boundingRect() 26 | 27 | def getArrayRegion(self, data, img, axes=(0, 1)): 28 | # img_point = self.mapToItem(img, self.pos()) 29 | coords = self.getPixelCoords() 30 | ystrip = data[coords[0], :] 31 | xstrip = data[:, coords[1]] 32 | return ([xstrip, ystrip], coords) 33 | 34 | def getPixelCoords(self): 35 | img_point = self.pos() 36 | return (int(img_point.x()), int(img_point.y())) 37 | 38 | def shape(self): 39 | if self._shape is None: 40 | radius = self.getState()["size"][1] 41 | p = QtGui.QPainterPath() 42 | p.moveTo(Point(0, -radius)) 43 | p.lineTo(Point(0, radius)) 44 | p.moveTo(Point(-radius, 0)) 45 | p.lineTo(Point(radius, 0)) 46 | p = self.mapToDevice(p) 47 | stroker = QtGui.QPainterPathStroker() 48 | stroker.setWidth(10) 49 | outline = stroker.createStroke(p) 50 | self._shape = self.mapFromDevice(outline) 51 | return self._shape 52 | 53 | def paint(self, p, *args): 54 | radius = self.getState()["size"][1] 55 | p.setRenderHint(QtGui.QPainter.Antialiasing) 56 | p.setPen(self.currentPen) 57 | p.drawLine(Point(0, -radius), Point(0, radius)) 58 | p.drawLine(Point(-radius, 0), Point(radius, 0)) 59 | -------------------------------------------------------------------------------- /examples/image_view/image.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 683 10 | 529 11 | 12 | 13 | 14 | Image View (VCC) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Displays an image from a PV. 24 | 25 | 26 | PyDMImageView::Inferno 27 | 28 | 29 | ca://MTEST:Image 30 | 31 | 32 | ca://MTEST:ImageWidth 33 | 34 | 35 | 30 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | PyDMImageView 44 | QWidget 45 |
pydm.widgets.image
46 |
47 |
48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /examples/infinity/infinity.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 813 10 | 587 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ca://MTEST:Infinity 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ca://MTEST:Infinity 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {"y_channel": "ca://MTEST:Infinity", "x_channel": null, "name": "", "color": "white", "lineStyle": 1, "lineWidth": 1, "symbol": null, "symbolSize": 10, "redraw_mode": 2} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | PyDMWaveformTable 55 | QTableWidget 56 |
pydm.widgets.waveformtable
57 |
58 | 59 | PyDMLineEdit 60 | QLineEdit 61 |
pydm.widgets.line_edit
62 |
63 | 64 | PyDMWaveformPlot 65 | QGraphicsView 66 |
pydm.widgets.waveformplot
67 |
68 |
69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /examples/log_display/log_display.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 417 10 | 337 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Standard display for Log Output 25 | 26 | This widget handles instantiating a ``GuiHandler`` and displaying log 27 | messages to a ``QPlainTextEdit``. The level of the log can be changed from 28 | inside the widget itself, allowing users to select from any of the 29 | ``.levels`` specified by the widget. 30 | 31 | Parameters 32 | ---------- 33 | logname : str 34 | Name of log to display in widget 35 | 36 | level : logging.Level 37 | Initial level of log display 38 | 39 | parent : QObject, optional 40 | 41 | 42 | 43 | PyDMLogDisplay::INFO 44 | 45 | 46 | root 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | PyDMLogDisplay 55 | QWidget 56 |
pydm.widgets.logdisplay
57 |
58 |
59 | 60 | 61 |
62 | -------------------------------------------------------------------------------- /examples/macros/basics/embedded_display_with_macro.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 536 10 | 354 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {"prefix": "MTEST"} 27 | 28 | 29 | macro_pv.ui 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PyDMEmbeddedDisplay 38 | QFrame 39 |
pydm.widgets.embedded_display
40 |
41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /examples/macros/basics/macro_pv.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Qt::AlignCenter 27 | 28 | 29 | ca://${prefix}:String 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PyDMLabel 38 | QLabel 39 |
pydm.widgets.label
40 |
41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /examples/macros/basics/readme.txt: -------------------------------------------------------------------------------- 1 | This example illustrates the basics of using macro variables with .ui files. 2 | Load either the 'related_display_with_macros.ui' file or the 'embedded_display_with_macro.ui' 3 | file to see examples of using the related display button or embedded display widget to pass 4 | macro variables to a .ui file used as a template (called macro_pv.ui, in these examples). 5 | -------------------------------------------------------------------------------- /examples/macros/basics/related_display_with_macros.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Launch Display With Macro 27 | 28 | 29 | macro_pv.ui 30 | 31 | 32 | {"prefix": "MTEST"} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | PyDMRelatedDisplayButton 41 | QFrame 42 |
pydm.widgets.related_display_button
43 |
44 |
45 | 46 | 47 |
48 | -------------------------------------------------------------------------------- /examples/macros/macros_and_python/macro_addition.py: -------------------------------------------------------------------------------- 1 | from pydm import Display 2 | 3 | 4 | class MacroAddition(Display): 5 | def __init__(self, parent=None, args=None, macros=None): 6 | super().__init__(parent=parent, macros=macros) 7 | self.ui.resultLabel.setText("{}".format(float(macros["a"]) + float(macros["b"]))) 8 | 9 | def ui_filename(self): 10 | return "macro_addition.ui" 11 | -------------------------------------------------------------------------------- /examples/macros/macros_and_python/macro_addition.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | ${a} + ${b} = 21 | 22 | 23 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 26 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/macros/macros_and_python/readme.txt: -------------------------------------------------------------------------------- 1 | This example illustrates how to write a python-based display that accepts 2 | macro variables and uses them to change the panel with code. In this case, 3 | two macro variables are added together in the display's initializer. 4 | 5 | To run the python-based display and supply the variables it needs, 6 | launch it from the command line with the -m argument and supply two 7 | variables, "a" and "b", like this: 8 | python pydm.py -m '{"a": 3, "b": 5}' examples/macros/macros_and_python/macro_addition.py 9 | 10 | Another .ui file, macros_to_python_displays.ui illustrates loading the 11 | python-based display with a related display button, and using the button 12 | to supply macros to the display. This example also demonstrates how PyDM 13 | cascades macros from one display to the next when a related display button 14 | is clicked. 15 | 16 | To run the .ui file, supply one macro variable, "a": 17 | python pydm.py -m '{"a": 3}' examples/macros/macros_and_python/macros_to_python_displays.ui 18 | -------------------------------------------------------------------------------- /examples/macros/nested_embedded_windows/macro_addition.py: -------------------------------------------------------------------------------- 1 | from pydm import Display 2 | 3 | 4 | class MacroAddition(Display): 5 | def __init__(self, parent=None, args=None, macros=None): 6 | super().__init__(parent=parent, macros=macros) 7 | self.ui.resultLabel.setText("{}".format(float(macros["a"]) + float(macros["b"]))) 8 | 9 | def ui_filename(self): 10 | return "macro_addition.ui" 11 | -------------------------------------------------------------------------------- /examples/macros/nested_embedded_windows/macro_addition.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | ${a} + ${b} = 21 | 22 | 23 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 26 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/macros/nested_embedded_windows/nested_embedded_windows.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | This embedded window defines "a" as 3. 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {"a": 3} 40 | 41 | 42 | nested_embedded_windows_level_2.ui 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | PyDMEmbeddedDisplay 51 | QFrame 52 |
pydm.widgets.embedded_display
53 |
54 |
55 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /examples/macros/nested_embedded_windows/nested_embedded_windows_level_2.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | This embedded window defines "b" as 5. 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {"b": 5} 40 | 41 | 42 | macro_addition.py 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | PyDMEmbeddedDisplay 51 | QFrame 52 |
pydm.widgets.embedded_display
53 |
54 |
55 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /examples/macros/nested_embedded_windows/readme.txt: -------------------------------------------------------------------------------- 1 | This example demonstrates how macro variables are cascaded through 2 | nested embedded displays. The first embedded display defines a 3 | variable "a", which is sent to a second embedded display, which 4 | defines another variable "b". The second display embeds a python 5 | based display which shows the sum of the macro variables "a" and "b". 6 | 7 | To run this example, open 'nested_embedded_windows.ui'. 8 | -------------------------------------------------------------------------------- /examples/positioner/cams.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # Cam Geometry 4 | R = 31 5 | L = 1.5875 6 | S1 = 34.925 7 | S2 = 290.5 8 | c = 282.0416 9 | b = c + S1 - np.sqrt(2) * R 10 | a = 139.6238 11 | dx = 74.55 12 | dy = 245.47 13 | 14 | # Silence invalid arcsin arguments, we deal with that ourselves. 15 | np.seterr(invalid="ignore") 16 | 17 | 18 | def real2cams(coords): 19 | (x, y, theta) = coords 20 | theta = theta * 1.0e-3 # Convert to rad from mrad 21 | 22 | x1 = x + (a * np.cos(theta)) + (b * np.sin(theta)) - a 23 | y1 = y - (b * np.cos(theta)) + (a * np.sin(theta)) + c 24 | bp = (np.cos(theta) + np.sin(theta)) / np.sqrt(2.0) 25 | bm = (np.cos(theta) - np.sin(theta)) / np.sqrt(2.0) 26 | 27 | p1 = theta - np.arcsin((1 / L) * (((x1 + S2) * np.sin(theta)) - (y1 * np.cos(theta)) + (c - b))) 28 | p2 = theta - np.arcsin((1 / L) * (((x1 + S1) * bm) + (y1 * bp) - R)) 29 | p3 = theta - np.arcsin((1 / L) * (((x1 - S1) * bp) - (y1 * bm) + R)) 30 | 31 | valid = False 32 | if np.all(np.isreal([p1, p2, p3])) and not np.any(np.isnan([p1, p2, p3])): 33 | valid = True 34 | 35 | return (p1 * 180.0 / np.pi, p2 * 180.0 / np.pi, p3 * 180.0 / np.pi, valid) 36 | -------------------------------------------------------------------------------- /examples/positioner/positioner_ioc.py: -------------------------------------------------------------------------------- 1 | from pcaspy import SimpleServer, Driver, Severity 2 | import numpy 3 | 4 | prefix = "MOTOR:" 5 | pvdb = { 6 | "1:VAL": { 7 | "value": 0.0, 8 | "prec": 2, 9 | "hilim": 180, 10 | "hihi": 140, 11 | "high": 100, 12 | "low": -100, 13 | "lolo": -140, 14 | "lolim": -180, 15 | "unit": "deg", 16 | }, 17 | "1:STR": {"type": "string", "value": "Hello"}, 18 | "1:INT": {"type": "int", "value": 5}, 19 | "2:VAL": { 20 | "value": 0.0, 21 | "prec": 2, 22 | "hilim": 180, 23 | "hihi": 140, 24 | "high": 100, 25 | "low": -100, 26 | "lolo": -140, 27 | "lolim": -180, 28 | "unit": "deg", 29 | }, 30 | "3:VAL": { 31 | "value": 0.0, 32 | "prec": 2, 33 | "hilim": 180, 34 | "hihi": 140, 35 | "high": 100, 36 | "low": -100, 37 | "lolo": -140, 38 | "lolim": -180, 39 | "unit": "deg", 40 | }, 41 | "STATUS": { 42 | "type": "enum", 43 | "enums": ["Off", "On"], 44 | "states": [Severity.MINOR_ALARM, Severity.NO_ALARM], 45 | }, 46 | "WAVE": {"count": 16, "prec": 2, "value": numpy.arange(16, dtype=float)}, 47 | } 48 | 49 | 50 | class myDriver(Driver): 51 | def __init__(self): 52 | super().__init__() 53 | 54 | 55 | if __name__ == "__main__": 56 | server = SimpleServer() 57 | server.createPV(prefix, pvdb) 58 | driver = myDriver() 59 | print("Server is running... (ctrl+c to close)") 60 | while True: 61 | server.process(0.1) 62 | -------------------------------------------------------------------------------- /examples/pva/image.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 279 10 | 231 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 10 20 | 10 21 | 261 22 | 211 23 | 24 | 25 | 26 | 27 | 28 | 29 | pva://PyDM:PVA:Image 30 | 31 | 32 | 33 | 34 | 35 | PyDMImageView 36 | QWidget 37 |
pydm.widgets.image
38 |
39 |
40 | 41 | 42 |
43 | -------------------------------------------------------------------------------- /examples/pva/nt_enum.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 240 20 | 160 21 | 79 22 | 23 23 | 24 | 25 | 26 | 27 | 28 | 29 | pva://PyDM:PVA:Enum 30 | 31 | 32 | 33 | 34 | 35 | 90 36 | 130 37 | 98 38 | 100 39 | 40 | 41 | 42 | 43 | 44 | 45 | pva://PyDM:PVA:Enum 46 | 47 | 48 | 49 | 50 | 51 | 160 52 | 70 53 | 61 54 | 15 55 | 56 | 57 | 58 | 59 | 60 | 61 | pva://PyDM:PVA:Enum 62 | 63 | 64 | 65 | 66 | 67 | PyDMLabel 68 | QLabel 69 |
pydm.widgets.label
70 |
71 | 72 | PyDMEnumButton 73 | QWidget 74 |
pydm.widgets.enum_button
75 |
76 | 77 | PyDMEnumComboBox 78 | QComboBox 79 |
pydm.widgets.enum_combo_box
80 |
81 |
82 | 83 | 84 |
85 | -------------------------------------------------------------------------------- /examples/related_displays/display1.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 431 10 | 128 11 | 12 | 13 | 14 | Related Display Demo: Display 1 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 42 28 | 75 29 | true 30 | 31 | 32 | 33 | This is Display 1 34 | 35 | 36 | Qt::AlignCenter 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | true 53 | 54 | 55 | ca://MTEST:Float 56 | 57 | 58 | Open Display 2... 59 | 60 | 61 | display2.ui 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | PyDMRelatedDisplayButton 70 | QFrame 71 |
pydm.widgets.related_display_button
72 |
73 |
74 | 75 | 76 |
77 | -------------------------------------------------------------------------------- /examples/related_displays/display2.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 353 10 | 128 11 | 12 | 13 | 14 | Related Display Demo: Display 1 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 42 28 | 75 29 | true 30 | 31 | 32 | 33 | This is Display 2 34 | 35 | 36 | Qt::AlignCenter 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | false 50 | 51 | 52 | ca://MTEST:Float 53 | 54 | 55 | Open Display 1... 56 | 57 | 58 | display1.ui 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | PyDMRelatedDisplayButton 67 | QFrame 68 |
pydm.widgets.related_display_button
69 |
70 |
71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /examples/related_displays/multiple_files.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Related Display Button with Multiple Files 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Multiple Displays... 24 | 25 | 26 | 27 | display1.ui 28 | display2.ui 29 | 30 | 31 | 32 | 33 | Display 1 34 | 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | PyDMRelatedDisplayButton 46 | QPushButton 47 |
pydm.widgets.related_display_button
48 |
49 |
50 | 51 | 52 |
53 | -------------------------------------------------------------------------------- /examples/rpc/rpc_testing_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is an example of a simple client that sends RPCs. 3 | To demo, first run 'python examples/testing_ioc/rpc_testing_ioc.py' 4 | from another terminal, 5 | then run this file with 'python rpc_testing_client.py' 6 | """ 7 | 8 | from p4p.client.thread import Context 9 | from p4p.nt import NTURI 10 | 11 | ctx = Context("pva") 12 | 13 | # NTURI() lets us wrap argument into Value type needed in rpc call 14 | # https://mdavidsaver.github.io/p4p/nt.html#p4p.nt.NTURI 15 | AidaBPMSURI = NTURI([("a", "i"), ("b", "i")]) 16 | 17 | request = AidaBPMSURI.wrap("pv:call:add_two_ints", scheme="pva", kws={"a": 7, "b": 3}) 18 | response = ctx.rpc("pv:call:add_two_ints", request, timeout=10) 19 | 20 | print(response) # should print something like 'Wed Dec 31 16:00:00 1969 10' 21 | -------------------------------------------------------------------------------- /examples/shell_command/example_cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script can be called by a PyDMShellCommand widget, 4 | # allowing it to make use of command chaining and other shell features. 5 | echo "Hello World!" && echo "Hello Again!" 6 | -------------------------------------------------------------------------------- /examples/symbol/go.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Untitled 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/symbol/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/symbol/symbol.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 280 10 | 273 11 | 12 | 13 | 14 | Symbol Widget Example 15 | 16 | 17 | 18 | 19 | 20 | 21 | 50 22 | 50 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ca://MTEST:Run 33 | 34 | 35 | {"1": "go.svg", "0": "stop.svg"} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0 44 | 0 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ca://MTEST:Run 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | PyDMEnumComboBox 63 | QComboBox 64 |
pydm.widgets.enum_combo_box
65 |
66 | 67 | PyDMSymbol 68 | QWidget 69 |
pydm.widgets.symbol
70 |
71 |
72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /examples/template_repeater/flow_template_repeater.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 624 10 | 321 11 | 12 | 13 | 14 | Template Repeater Example 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 0 26 | 0 27 | 1103 28 | 295 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | PyDMTemplateRepeater::Vertical 39 | 40 | 41 | 3 42 | 43 | 44 | magnet-list-item.ui 45 | 46 | 47 | xcor_list_small.json 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | PyDMTemplateRepeater 60 | QFrame 61 |
pydm.widgets.template_repeater
62 |
63 |
64 | 65 | 66 |
67 | -------------------------------------------------------------------------------- /examples/template_repeater/readme.md: -------------------------------------------------------------------------------- 1 | The entry points for the two template repeater examples are template_repeater.ui 2 | and flow_template_repeater.ui. Before running either example, start the magnet_ioc.py 3 | script in another terminal. Note: magnet_ioc.py requires the 'caproto' module, and 4 | is only compatible with Python>=3.6. -------------------------------------------------------------------------------- /examples/template_repeater/template_repeater.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1131 10 | 271 11 | 12 | 13 | 14 | Template Repeater Example 15 | 16 | 17 | 18 | 19 | 20 | Qt::ScrollBarAlwaysOff 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 1105 31 | 257 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | PyDMTemplateRepeater::Vertical 42 | 43 | 44 | 3 45 | 46 | 47 | magnet-list-item.ui 48 | 49 | 50 | xcor_list_small.json 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | PyDMTemplateRepeater 63 | QFrame 64 |
pydm.widgets.template_repeater
65 |
66 |
67 | 68 | 69 |
70 | -------------------------------------------------------------------------------- /examples/template_repeater/xcor_list_small.json: -------------------------------------------------------------------------------- 1 | [{"devname": "XCOR:IN20:121"}, {"devname": "XCOR:IN20:221"}, {"devname": "XCOR:IN20:311"}, {"devname": "XCOR:IN20:341"}, {"devname": "XCOR:IN20:381"}, {"devname": "XCOR:IN20:411"}, {"devname": "XCOR:IN20:491"}, {"devname": "XCOR:IN20:521"}, {"devname": "XCOR:IN20:641"}, {"devname": "XCOR:IN20:721"}, {"devname": "XCOR:IN20:761"}] -------------------------------------------------------------------------------- /examples/template_repeater_json/main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 2 24 | 25 | 26 | template.ui 27 | 28 | 29 | [{"MESSAGE": "First Message"}, {"MESSAGE": "Second Message"}, {"MESSAGE": "Hi"}, {"MESSAGE": "Hello"}] 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PyDMTemplateRepeater 38 | QFrame 39 |
pydm.widgets.template_repeater
40 |
41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /examples/template_repeater_json/template.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 271 10 | 34 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | ${MESSAGE} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/terminator/terminator.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 369 10 | 197 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | <html><head/><body><p>This screen will close after 10 seconds of inactivity...</p><p><br/></p><p>Move the mouse around to avoid it from closing.</p></body></html> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | PushButton 31 | 32 | 33 | 34 | 35 | 36 | 37 | Qt::Horizontal 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 10 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | PyDMTerminator 56 | QLabel 57 |
pydm.widgets.terminator
58 |
59 |
60 | 61 | 62 |
63 | -------------------------------------------------------------------------------- /examples/testing_ioc/access_rules.as: -------------------------------------------------------------------------------- 1 | ASG(default) { 2 | INPA($(P)ReadOnly) 3 | RULE(1, READ) 4 | RULE(1, WRITE){ 5 | CALC("A<1") 6 | } 7 | } -------------------------------------------------------------------------------- /examples/testing_ioc/rpc_testing_ioc.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is an example of a server that sends back RPC results, mimicking the behavior of an ioc. 3 | The server defines three functions with differing names, number of args, and arg types. 4 | To view demo, first run this file with 'python rpc_testing_ioc.py', 5 | and then run 'pydm examples/rpc/rpc_lables.ui' from another terminal. 6 | (code adapted from p4p docs: https://mdavidsaver.github.io/p4p/rpc.html) 7 | """ 8 | 9 | from p4p.rpc import rpc, quickRPCServer 10 | from p4p.nt import NTScalar 11 | import random 12 | 13 | 14 | class Demo(object): 15 | @rpc(NTScalar("i")) 16 | def add_two_ints(self, a, b): 17 | return a + b 18 | 19 | @rpc(NTScalar("f")) 20 | def add_int_float(self, a, b): 21 | return a + b 22 | 23 | @rpc(NTScalar("i")) 24 | def add_three_ints_negate_option(self, a, b, negate): 25 | res = a + b 26 | return -1 * res if negate else res 27 | 28 | @rpc(NTScalar("s")) 29 | def take_return_string(self, a): 30 | return a + "!!" 31 | 32 | @rpc(NTScalar("f")) 33 | def no_args(self): 34 | randomFloat = random.uniform(0, 10) 35 | return randomFloat 36 | 37 | 38 | adder = Demo() 39 | quickRPCServer(provider="Example", prefix="pv:call:", target=adder) 40 | -------------------------------------------------------------------------------- /examples/tutorial/main.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from pydm import Display 3 | from scipy.ndimage.measurements import maximum_position 4 | 5 | 6 | class BeamPositioning(Display): 7 | def __init__(self, parent=None, args=None): 8 | super().__init__(parent=parent, args=args) 9 | # Attach our custom process_image method 10 | self.ui.imageView.process_image = self.process_image 11 | # Hook up to the newImageSignal so we can update 12 | # our widgets after the new image is done 13 | self.ui.imageView.newImageSignal.connect(self.show_blob) 14 | # Store blob coordinate 15 | self.blob = (0, 0) 16 | 17 | def ui_filename(self): 18 | # Point to our UI file 19 | return "main.ui" 20 | 21 | def ui_filepath(self): 22 | # Return the full path to the UI file 23 | return path.join(path.dirname(path.realpath(__file__)), self.ui_filename()) 24 | 25 | def show_blob(self, *args, **kwargs): 26 | # If we have a blob, present the coordinates at label 27 | if self.blob != (0, 0): 28 | blob_txt = "Blob Found:" 29 | blob_txt += " ({}, {})".format(self.blob[1], self.blob[0]) 30 | else: 31 | # If no blob was found, present the "Not Found" message 32 | blob_txt = "Blob Not Found" 33 | # Update the label text 34 | self.ui.lbl_blobs.setText(blob_txt) 35 | 36 | def process_image(self, new_image): 37 | # Consider the maximum as the Blob since we have only 38 | # one blob. 39 | self.blob = maximum_position(new_image) 40 | # Send the original image data to the image widget 41 | return new_image 42 | -------------------------------------------------------------------------------- /examples/tutorial/motor_db.txt: -------------------------------------------------------------------------------- 1 | IOC:m1 2 | IOC:m2 3 | IOC:m3 4 | IOC:m4 5 | IOC:m5 6 | IOC:m6 7 | IOC:m7 8 | IOC:m8 9 | -------------------------------------------------------------------------------- /examples/waveformplot/waveform_plot.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 626 10 | 533 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Point # 28 | 29 | 30 | 31 | 32 | Value 33 | 34 | 35 | 36 | true 37 | 38 | 39 | 40 | {"y_channel": "ca://MTEST:Waveform", "x_channel": "ca://MTEST:TimeBase", "name": "Sine", "color": "white", "lineStyle": 1, "lineWidth": 1, "symbol": null, "symbolSize": 10, "redraw_mode": 2} 41 | {"y_channel": "ca://MTEST:Cosine", "x_channel": "ca://MTEST:TimeBase", "name": "Cosine", "color": "red", "lineStyle": 1, "lineWidth": 1, "symbol": null, "symbolSize": 10, "redraw_mode": 2} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | PyDMWaveformPlot 51 | QGraphicsView 52 |
pydm.widgets.waveformplot
53 |
54 |
55 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /examples/waveformtable/wavetable.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 628 10 | 462 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Displays a waveform in table form. 24 | 25 | 26 | ca://MTEST:Waveform 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | PyDMWaveformTable 39 | QTableWidget 40 |
pydm.widgets.waveformtable
41 |
42 |
43 | 44 | 45 |
46 | -------------------------------------------------------------------------------- /pydm/PyQt/Qt.py: -------------------------------------------------------------------------------- 1 | from qtpy.Qt import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/PyQt/QtCore.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtCore import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/PyQt/QtDesigner.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtDesigner import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/PyQt/QtGui.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtGui import * # noqa: F403 2 | from qtpy.QtWidgets import * # noqa: F403 3 | -------------------------------------------------------------------------------- /pydm/PyQt/QtSvg.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtSvg import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/PyQt/QtWidgets.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtWidgets import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/PyQt/__init__.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "'pydm.PyQt' is deprecated, please directly import from the 'qtpy' module. pydm.PyQt will be removed in PyDM v1.8" 5 | ) 6 | -------------------------------------------------------------------------------- /pydm/PyQt/uic.py: -------------------------------------------------------------------------------- 1 | from qtpy.uic import * # noqa: F403 2 | -------------------------------------------------------------------------------- /pydm/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import PyDMApplication 2 | from .display import Display 3 | from .data_plugins import set_read_only 4 | from .widgets import PyDMChannel 5 | 6 | try: 7 | from ._version import version as __version__ 8 | except ImportError: 9 | __version__ = "unknown" 10 | 11 | 12 | __all__ = [ 13 | "PyDMApplication", 14 | "Display", 15 | "set_read_only", 16 | "PyDMChannel", 17 | ] 18 | -------------------------------------------------------------------------------- /pydm/about_pydm/__init__.py: -------------------------------------------------------------------------------- 1 | from .about import AboutWindow 2 | 3 | __all__ = [ 4 | "AboutWindow", 5 | ] 6 | -------------------------------------------------------------------------------- /pydm/about_pydm/contributors.txt: -------------------------------------------------------------------------------- 1 | Development Team 2 | Jesse Bellister (@jbellister-slac, jesseb@slac.stanford.edu) 3 | Nolan Stelter (@nstelter-slac, nstelter@slac.stanford.edu) 4 | Yekta Yazar (@YektaY, yazar@slac.stanford.edu) 5 | Matt Gibbs (@mattgibbs, mgibbs@slac.stanford.edu) 6 | 7 | Contributors 8 | @teddyrendahl 9 | @hhslepicka 10 | @hmbui 11 | @ZLLentz 12 | @klauer 13 | @cristinasewell 14 | @fernandohds564 15 | @Ryan-McClanahan 16 | @laispc 17 | @mcb64 18 | @prjemian 19 | @tacaswell 20 | @colbychang 21 | @jacquelinegarrahan 22 | @tcope 23 | @anacso17 24 | @flowln 25 | @ninenein 26 | @lvhuihui1988 27 | @herodotus77 28 | @gabrielfedel 29 | @justincslac 30 | @c-tsoi 31 | @anleslac 32 | @kleleux 33 | @vespos 34 | @ivany4 35 | @scalverson 36 | @KurtJacobson 37 | @xresende 38 | @vosarah 39 | @tangkong 40 | @ronpandolfi 41 | @gu1lermelp 42 | -------------------------------------------------------------------------------- /pydm/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __all__ = ["DEFAULT_PROTOCOL", "DESIGNER_ONLINE", "STYLESHEET", "STYLESHEET_INCLUDE_DEFAULT", "CONFIRM_QUIT"] 4 | 5 | 6 | DEFAULT_PROTOCOL = os.getenv("PYDM_DEFAULT_PROTOCOL") 7 | if DEFAULT_PROTOCOL is not None: 8 | # Get rid of the "://" part if it exists 9 | DEFAULT_PROTOCOL = DEFAULT_PROTOCOL.split("://")[0] 10 | 11 | DESIGNER_ONLINE = os.getenv("PYDM_DESIGNER_ONLINE", None) is not None 12 | 13 | STYLESHEET = os.getenv("PYDM_STYLESHEET", None) 14 | 15 | STYLESHEET_INCLUDE_DEFAULT = os.getenv("PYDM_STYLESHEET_INCLUDE_DEFAULT", False) 16 | 17 | CONFIRM_QUIT = os.getenv("PYDM_CONFIRM_QUIT", "n").lower() in ("y", "t", "1", "true") 18 | 19 | # Environment variable pointing to a pydm display to return to when the home button is clicked 20 | HOME_FILE = os.getenv("PYDM_HOME_FILE") 21 | 22 | ENTRYPOINT_EXTERNAL_TOOL = "pydm.tool" 23 | ENTRYPOINT_DATA_PLUGIN = "pydm.data_plugin" 24 | ENTRYPOINT_WIDGET = "pydm.widget" 25 | 26 | EXTERNAL_TOOL_SUFFIX = "_tool.py" 27 | DATA_PLUGIN_SUFFIX = "_plugin.py" 28 | -------------------------------------------------------------------------------- /pydm/connection_inspector/__init__.py: -------------------------------------------------------------------------------- 1 | from .connection_inspector import ConnectionInspector 2 | 3 | __all__ = [ 4 | "ConnectionInspector", 5 | ] 6 | -------------------------------------------------------------------------------- /pydm/data_plugins/epics_plugin.py: -------------------------------------------------------------------------------- 1 | # If the user has PyEpics, use it... which is slower but more commonly used 2 | # Otherwise, if PSP and pyca installed, use psp, which is faster. 3 | # To force a particular library, set the PYDM_EPICS_LIB environment 4 | # variable to either pyepics or pyca. 5 | import os 6 | 7 | EPICS_LIB = os.getenv("PYDM_EPICS_LIB", "").upper() 8 | if EPICS_LIB == "PYEPICS": 9 | from pydm.data_plugins.epics_plugins.pyepics_plugin_component import PyEPICSPlugin 10 | 11 | EPICSPlugin = PyEPICSPlugin 12 | elif EPICS_LIB == "PYCA": 13 | from pydm.data_plugins.epics_plugins.psp_plugin_component import PSPPlugin 14 | 15 | EPICSPlugin = PSPPlugin 16 | elif EPICS_LIB == "CAPROTO": 17 | from pydm.data_plugins.epics_plugins.caproto_plugin_component import CaprotoPlugin 18 | 19 | EPICSPlugin = CaprotoPlugin 20 | else: 21 | try: 22 | from pydm.data_plugins.epics_plugins.pyepics_plugin_component import PyEPICSPlugin 23 | 24 | EPICSPlugin = PyEPICSPlugin 25 | except ImportError: 26 | from pydm.data_plugins.epics_plugins.psp_plugin_component import PSPPlugin 27 | 28 | EPICSPlugin = PSPPlugin 29 | EPICSPlugin.protocol = "ca" 30 | -------------------------------------------------------------------------------- /pydm/data_plugins/epics_plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/data_plugins/epics_plugins/__init__.py -------------------------------------------------------------------------------- /pydm/data_plugins/fake_plugin.py: -------------------------------------------------------------------------------- 1 | from pydm.data_plugins.plugin import PyDMPlugin, PyDMConnection 2 | from qtpy.QtCore import QTimer 3 | import random 4 | 5 | 6 | class Connection(PyDMConnection): 7 | def __init__(self, widget, address, protocol=None, parent=None): 8 | super().__init__(widget, address, protocol, parent) 9 | self.add_listener(widget) 10 | self.value = address 11 | self.rand = 0 12 | self.timer = QTimer(self) 13 | self.timer.timeout.connect(self.send_new_value) 14 | self.timer.start(1000) 15 | self.connected = True 16 | 17 | def send_new_value(self): 18 | val_to_send = "{0}-{1}".format(self.value, random.randint(0, 9)) 19 | self.new_value_signal[str].emit(str(val_to_send)) 20 | 21 | def send_connection_state(self, conn): 22 | self.connection_state_signal.emit(conn) 23 | 24 | def add_listener(self, widget): 25 | super().add_listener(widget) 26 | self.send_connection_state(True) 27 | 28 | 29 | class FakePlugin(PyDMPlugin): 30 | protocol = "fake" 31 | connection_class = Connection 32 | -------------------------------------------------------------------------------- /pydm/data_plugins/pva_plugin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | # This file gives us flexibility in allowing multiple options for pvAccess plugins in the future if needed. 7 | PVA_LIB = os.getenv("PYDM_PVA_LIB", "").upper() 8 | try: 9 | if PVA_LIB == "P4P" or not PVA_LIB: 10 | from pydm.data_plugins.epics_plugins.p4p_plugin_component import P4PPlugin 11 | 12 | PVAPlugin = P4PPlugin 13 | elif PVA_LIB == "PVAPY": 14 | logger.error("PVAPY is not currently supported by PyDM") 15 | PVAPlugin = None 16 | except ImportError: 17 | PVAPlugin = None 18 | logger.info("No PVAccess Python library available. Ignoring pva plugin.") 19 | 20 | if PVAPlugin: 21 | PVAPlugin.protocol = "pva" 22 | -------------------------------------------------------------------------------- /pydm/display_module.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from pydm.display import Display, ScreenTarget, load_file, load_py_file, load_ui_file 3 | 4 | warnings.warn( 5 | "The display_module was renamed to display.Please modify your code to reflect this change.", 6 | DeprecationWarning, 7 | stacklevel=2, 8 | ) 9 | 10 | __all__ = ["Display", "ScreenTarget", "load_file", "load_py_file", "load_ui_file"] 11 | -------------------------------------------------------------------------------- /pydm/help_files/__init__.py: -------------------------------------------------------------------------------- 1 | from .help_window import HelpWindow 2 | 3 | __all__ = [ 4 | "HelpWindow", 5 | ] 6 | -------------------------------------------------------------------------------- /pydm/help_files/help_window.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from qtpy.QtWidgets import QTextBrowser, QVBoxLayout, QWidget 3 | from qtpy.QtCore import Qt 4 | from typing import Optional 5 | 6 | 7 | class HelpWindow(QWidget): 8 | """ 9 | A window for displaying a help file for a PyDM display 10 | 11 | Parameters 12 | ---------- 13 | help_file_path : str 14 | The path to the help file to be displayed 15 | """ 16 | 17 | def __init__(self, help_file_path: str, parent: Optional[QWidget] = None): 18 | super().__init__(parent, Qt.Window) 19 | self.resize(500, 400) 20 | 21 | path = Path(help_file_path) 22 | self.setWindowTitle(f"Help for {path.stem}") 23 | 24 | self.display_content = QTextBrowser() 25 | 26 | with open(help_file_path) as file: 27 | if path.suffix == ".txt": 28 | self.display_content.setText(file.read()) 29 | else: 30 | self.display_content.setHtml(file.read()) 31 | 32 | self.vBoxLayout = QVBoxLayout() 33 | self.vBoxLayout.addWidget(self.display_content) 34 | self.setLayout(self.vBoxLayout) 35 | -------------------------------------------------------------------------------- /pydm/pydm_designer_plugin.py: -------------------------------------------------------------------------------- 1 | print("Loading PyDM Widgets") 2 | from pydm.widgets.qtplugins import * # noqa: E402, F403 3 | -------------------------------------------------------------------------------- /pydm/show_macros/__init__.py: -------------------------------------------------------------------------------- 1 | from .show_macros import MacroWindow 2 | 3 | __all__ = [ 4 | "MacroWindow", 5 | ] 6 | -------------------------------------------------------------------------------- /pydm/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/tests/__init__.py -------------------------------------------------------------------------------- /pydm/tests/data_plugins/test_pyepics_plugin_component.py: -------------------------------------------------------------------------------- 1 | from pydm.data_plugins.epics_plugins.pyepics_plugin_component import Connection 2 | from pydm.tests.conftest import ConnectionSignals 3 | from pydm.widgets.channel import PyDMChannel 4 | 5 | 6 | def test_update_ctrl_vars(signals: ConnectionSignals): 7 | """Invoke our callback for updating the control values for a PV as if we had a monitor on it. Verify 8 | that the signals sent are received as expected. 9 | """ 10 | values_received = [] 11 | mock_channel = PyDMChannel() 12 | mock_pyepics_connection = Connection(mock_channel, "Test:PV:1") 13 | mock_pyepics_connection.upper_alarm_limit_signal.connect(lambda x: values_received.append(x)) 14 | mock_pyepics_connection.lower_alarm_limit_signal.connect(lambda x: values_received.append(x)) 15 | mock_pyepics_connection.lower_warning_limit_signal.connect(lambda x: values_received.append(x)) 16 | mock_pyepics_connection.upper_warning_limit_signal.connect(lambda x: values_received.append(x)) 17 | mock_pyepics_connection.upper_ctrl_limit_signal.connect(lambda x: values_received.append(x)) 18 | mock_pyepics_connection.lower_ctrl_limit_signal.connect(lambda x: values_received.append(x)) 19 | 20 | mock_pyepics_connection.update_ctrl_vars( 21 | upper_ctrl_limit=70, 22 | lower_ctrl_limit=20, 23 | upper_alarm_limit=100, 24 | lower_alarm_limit=2, 25 | upper_warning_limit=90, 26 | lower_warning_limit=10, 27 | ) 28 | 29 | expected_values = [70, 20, 100, 2, 90, 10] 30 | assert values_received == expected_values 31 | -------------------------------------------------------------------------------- /pydm/tests/test_about_screen.py: -------------------------------------------------------------------------------- 1 | from pydm.about_pydm import AboutWindow 2 | 3 | 4 | def test_about_window_launches(qtbot): 5 | """Make sure the About window doesn't crash.""" 6 | a = AboutWindow(parent=None) 7 | qtbot.addWidget(a) 8 | a.show() 9 | -------------------------------------------------------------------------------- /pydm/tests/test_data/macro_test.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 285 10 | 141 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ${test_label} 23 | 24 | 25 | 26 | 27 | 28 | 29 | ${channel_with_dec_option} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ${test_command} 38 | ${test_command_2} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pydm/tests/test_data/no_display_test_file.py: -------------------------------------------------------------------------------- 1 | # A sample test file of a python class that does not inherit from PyDM's display, but we try to load as a display anyway 2 | from qtpy.QtCore import QObject 3 | 4 | 5 | class InvalidDisplayExample(QObject): 6 | """A simple class that inherits from QObject only""" 7 | 8 | def __init__(self, parent=None): 9 | super().__init__(parent=parent) 10 | -------------------------------------------------------------------------------- /pydm/tests/test_data/subfolder/test_relative_filename_child.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 285 10 | 141 11 | 12 | 13 | 14 | Child 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Number of Blobs: 23 | 24 | 25 | 26 | 27 | 28 | 29 | TextLabel 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /pydm/tests/test_data/test.txt: -------------------------------------------------------------------------------- 1 | This is a test help file for the test.ui display 2 | -------------------------------------------------------------------------------- /pydm/tests/test_data/test.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 285 10 | 141 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Number of Blobs: 23 | 24 | 25 | 26 | 27 | 28 | 29 | TextLabel 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /pydm/tests/test_data/test_emb_style.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 285 10 | 141 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | QLabel { font-weight: bold; } 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Number of Blobs: 26 | 27 | 28 | 29 | 30 | 31 | 32 | TextLabel 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /pydm/tests/test_data/test_stylesheet.css: -------------------------------------------------------------------------------- 1 | PyDMLabel { font-weight: bold; } -------------------------------------------------------------------------------- /pydm/tests/test_data/valid_display_test_file.py: -------------------------------------------------------------------------------- 1 | """This file is intended for use in Display related test files.""" 2 | 3 | import os 4 | from pydm import Display 5 | from pydm.widgets import PyDMPushButton, PyDMLabel 6 | 7 | # Ensure loading of modules in the same directory works as expected when this file is loaded as a PyDM Display 8 | import no_display_test_file 9 | 10 | 11 | class DisplayExample(Display): 12 | """An example of a simple display that can be loaded by `load_py_file` in `display.py`""" 13 | 14 | def __init__(self, parent=None, args=None, macros=None): 15 | super().__init__(parent=parent, args=args, macros=macros) 16 | self.button = PyDMPushButton() 17 | self.button.clicked.connect(self.delete_widget) 18 | 19 | self.label = PyDMLabel(init_channel="TST:Val1") 20 | 21 | def print_file(self): 22 | print(f"{no_display_test_file}") 23 | 24 | def delete_widget(self): 25 | self.label.deleteLater() 26 | 27 | def ui_filename(self): 28 | return "test.ui" 29 | 30 | def ui_filepath(self): 31 | return os.path.join(os.path.dirname(os.path.realpath(__file__)), self.ui_filename()) 32 | -------------------------------------------------------------------------------- /pydm/tests/test_show_macros.py: -------------------------------------------------------------------------------- 1 | from pydm.show_macros import MacroWindow 2 | 3 | 4 | def test_macro_window_launches(qtbot): 5 | """Make sure the Macro window doesn't crash.""" 6 | a = MacroWindow(parent=None) 7 | qtbot.addWidget(a) 8 | a.show() 9 | -------------------------------------------------------------------------------- /pydm/tests/test_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | External tool tests 3 | """ 4 | 5 | import pathlib 6 | from typing import Any, Set 7 | 8 | import pytest 9 | 10 | from pydm import tools 11 | from pydm.application import PyDMApplication 12 | 13 | EXAMPLE_PATH = pathlib.Path(__file__).parents[2] / "examples" 14 | EXAMPLE_EXT_TOOL_PATH = EXAMPLE_PATH / "external_tool" 15 | 16 | examples_required = pytest.mark.skipif( 17 | not EXAMPLE_PATH.exists(), reason="Not a source checkout - no examples available" 18 | ) 19 | 20 | 21 | class ValidTool(tools.ExternalTool): ... 22 | 23 | 24 | class InvalidTool: ... 25 | 26 | 27 | @pytest.mark.parametrize( 28 | "cls, valid", 29 | [ 30 | pytest.param(ValidTool, True, id="valid"), 31 | pytest.param(InvalidTool, False, id="invalid"), 32 | pytest.param(None, False, id="invalid-none"), 33 | ], 34 | ) 35 | def test_valid_external_tool(cls: Any, valid: bool): 36 | assert tools._is_valid_external_tool_class(cls) is valid 37 | 38 | 39 | @examples_required 40 | @pytest.mark.parametrize( 41 | "source_file, expected_tools", 42 | [ 43 | pytest.param( 44 | EXAMPLE_EXT_TOOL_PATH / "dummy_tool.py", 45 | {"DummyTool", "DummyTool3"}, 46 | id="dummy_tool", 47 | ), 48 | pytest.param( 49 | EXAMPLE_EXT_TOOL_PATH / "lookup_path" / "new_tool.py", 50 | {"DummyTool2"}, 51 | id="new_tool", 52 | ), 53 | pytest.param( 54 | EXAMPLE_EXT_TOOL_PATH / "lookup_path" / "root_tool.py", 55 | {"RootTool"}, 56 | id="root_tool", 57 | ), 58 | ], 59 | ) 60 | def test_tools_from_source(source_file: pathlib.Path, expected_tools: Set[str], qapp: PyDMApplication): 61 | assert source_file.exists() 62 | loaded_tools = [tool.__class__.__name__ for tool in tools._get_tools_from_source(str(source_file))] 63 | assert set(loaded_tools) == expected_tools 64 | 65 | 66 | def test_smoke_get_entrypoint_tools(qapp): 67 | list(tools.get_entrypoint_tools()) 68 | 69 | 70 | def test_smoke_get_tools_from_path(qapp): 71 | list(tools.get_tools_from_path()) 72 | 73 | 74 | def test_smoke_load_external_tools(qapp): 75 | tools.load_external_tools() 76 | -------------------------------------------------------------------------------- /pydm/tests/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/tests/utilities/__init__.py -------------------------------------------------------------------------------- /pydm/tests/utilities/test_colors.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pydm.utilities import colors 4 | 5 | 6 | def test_read_file(): 7 | assert colors.svg_color_to_hex_map is not None 8 | assert colors.hex_to_svg_color_map is not None 9 | 10 | 11 | def test_svg_color_from_hex(): 12 | svg = colors.svg_color_from_hex("#000000", hex_on_fail=False) 13 | assert svg == "black" 14 | svg = colors.svg_color_from_hex("#000000", hex_on_fail=True) 15 | assert svg == "black" 16 | 17 | with pytest.raises(KeyError): 18 | colors.svg_color_from_hex("#XXXXXXX", hex_on_fail=False) 19 | svg = colors.svg_color_from_hex("#XXXXXXX", hex_on_fail=True) 20 | assert svg == "#XXXXXXX" 21 | 22 | 23 | def test_hex_from_svg_color(): 24 | hex = colors.hex_from_svg_color("black") 25 | assert hex == "#000000" 26 | 27 | with pytest.raises(KeyError): 28 | colors.hex_from_svg_color("invalid_color") 29 | -------------------------------------------------------------------------------- /pydm/tests/utilities/test_iconfont.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydm.utilities import iconfont 3 | from qtpy import QtGui, QtCore 4 | 5 | 6 | def test_icon_font_constructor(qtbot): 7 | icon_f = iconfont.IconFont() 8 | icon_f2 = iconfont.IconFont() 9 | assert icon_f is icon_f2 10 | 11 | 12 | def test_icon_font_load_font(qtbot): 13 | icon_f = iconfont.IconFont() 14 | with pytest.raises(OSError): 15 | icon_f.char_map = None 16 | icon_f.load_font("foo", icon_f.charmap_file) 17 | with pytest.raises(OSError): 18 | icon_f.char_map = None 19 | icon_f.load_font(icon_f.charmap_file, "foo") 20 | icon_f.load_font(icon_f.font_file, icon_f.charmap_file) 21 | assert icon_f.char_map is not None 22 | 23 | 24 | def test_icon_font_get_char_for_name(qtbot): 25 | icon_f = iconfont.IconFont() 26 | c = icon_f.get_char_for_name("cogs") 27 | assert c == "\uf085" 28 | 29 | with pytest.raises(ValueError): 30 | icon_f.get_char_for_name("foo") 31 | 32 | 33 | def test_icon_font_font(qtbot): 34 | icon_f = iconfont.IconFont() 35 | f = icon_f.font(12) 36 | assert f.family() == icon_f.font_name 37 | assert f.pixelSize() == 12 38 | 39 | 40 | def test_icon_font_icon(qtbot): 41 | icon_f = iconfont.IconFont() 42 | icon_f.icon("cogs", color=None) 43 | icon_f.icon("cogs", color=QtGui.QColor(255, 0, 0)) 44 | with pytest.raises(ValueError): 45 | icon_f.icon("foo", color=None) 46 | 47 | 48 | def test_char_icon_engine(qtbot): 49 | engine = iconfont.CharIconEngine(iconfont.IconFont(), "cogs", color=None) 50 | engine.pixmap(QtCore.QSize(32, 32), mode=QtGui.QIcon.Normal, state=QtGui.QIcon.On) 51 | engine.pixmap(QtCore.QSize(32, 32), mode=QtGui.QIcon.Disabled, state=QtGui.QIcon.On) 52 | -------------------------------------------------------------------------------- /pydm/tests/utilities/test_macro.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import pytest 4 | 5 | from pydm.utilities.macro import substitute_in_file, parse_macro_string 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "text, macros, expected", 10 | [ 11 | ( 12 | "This is a ${what} to ensure that macros work. ${A} ${B} ${C}", 13 | {"what": "test", "A": "X", "B": "Y", "C": "Z"}, 14 | "This is a test to ensure that macros work. X Y Z", 15 | ), 16 | ( 17 | "This is a ${what} to ensure that macros work. ${A} ${B} ${C}", 18 | {"what": "test", "A": "X", "B": "Y", "C": "Z", "D": "W"}, 19 | "This is a test to ensure that macros work. X Y Z", 20 | ), 21 | ( 22 | "This is a ${what} to ensure that macros work. ${A} ${B} ${C}", 23 | {"what": "test", "A": "X", "B": "Y"}, 24 | "This is a test to ensure that macros work. X Y ${C}", 25 | ), 26 | ], 27 | ) 28 | def test_substitute_in_file(text, macros, expected): 29 | fd, name = tempfile.mkstemp(text=True) 30 | # use a context manager to open the file at that path and close it again 31 | with open(name, "w") as f: 32 | f.write(text) 33 | # close the file descriptor 34 | os.close(fd) 35 | 36 | nf = substitute_in_file(name, macros) 37 | nt = nf.read() 38 | assert nt == expected 39 | 40 | 41 | @pytest.mark.parametrize( 42 | "macro_string, expected_dict", 43 | [ 44 | ('{"A": "1", "B": "2"}', {"A": "1", "B": "2"}), 45 | ("A=1,B=2", {"A": "1", "B": "2"}), 46 | ("A=$(other_macro),B=2,C=3", {"A": "$(other_macro)", "B": "2", "C": "3"}), 47 | ("A=$(other_macro=3)", {"A": "$(other_macro=3)"}), 48 | ("TITLE='1,2', B=2, C=3", {"TITLE": "1,2", "B": "2", "C": "3"}), 49 | ("TITLE=1\,2,B=2,C=3", {"TITLE": "1,2", "B": "2", "C": "3"}), 50 | ('TITLE="e=mc^2",B=2,C=3', {"TITLE": "e=mc^2", "B": "2", "C": "3"}), 51 | ("", {}), 52 | (None, {}), 53 | ], 54 | ) 55 | def test_macro_parser(macro_string, expected_dict): 56 | """ 57 | Test the parser, using a couple of normal cases, and a bunch of perverse 58 | macro strings that only some insane macro genius (or huge macro idiot) 59 | would ever attempt. 60 | """ 61 | assert parse_macro_string(macro_string) == expected_dict 62 | -------------------------------------------------------------------------------- /pydm/tests/utilities/test_remove_protocol.py: -------------------------------------------------------------------------------- 1 | from pydm.utilities.remove_protocol import remove_protocol 2 | from pydm.utilities.remove_protocol import protocol_and_address 3 | from pydm.utilities.remove_protocol import parsed_address 4 | 5 | 6 | def test_remove_protocol(): 7 | out = remove_protocol("foo://bar") 8 | assert out == "bar" 9 | 10 | out = remove_protocol("bar") 11 | assert out == "bar" 12 | 13 | out = remove_protocol("foo://bar://foo2") 14 | assert out == "bar://foo2" 15 | 16 | 17 | def test_protocol_and_address(): 18 | out = protocol_and_address("foo://bar") 19 | assert out == ("foo", "bar") 20 | 21 | out = protocol_and_address("foo:/bar") 22 | assert out == (None, "foo:/bar") 23 | 24 | 25 | def test_parsed_address(): 26 | out = parsed_address(1) 27 | assert out is None 28 | 29 | out = parsed_address("foo:/bar") 30 | assert out is None 31 | 32 | out = parsed_address("foo://bar") 33 | assert out == ("foo", "bar", "", "") 34 | 35 | out = parsed_address("foo://bar#baz") 36 | assert out == ("foo", "bar#baz", "", "") 37 | 38 | out = parsed_address("foo:///aj") 39 | assert out == ("foo", "", "/aj", "") 40 | 41 | out = parsed_address("foo://rd?question!") 42 | assert out == ("foo", "rd", "", "question!") 43 | 44 | out = parsed_address("alpha://beta/delta?gamma") 45 | assert out.scheme == "alpha" and out.netloc == "beta" and out.path == "/delta" and out.query == "gamma" 46 | 47 | out = parsed_address("foo://test:channel.{'f': {'lo':0, 'hi':10} }?and_query_too") 48 | assert out == ("foo", "test:channel.{'f': {'lo':0, 'hi':10} }", "", "and_query_too") 49 | 50 | out = parsed_address("foo://TEST:PV[3]") 51 | assert out == ("foo", "TEST:PV[3]", "", "") 52 | 53 | out = parsed_address("loc://my_variable_name?type=variable_type&init=initial_values") 54 | assert out == ("loc", "my_variable_name", "", "type=variable_type&init=initial_values") 55 | -------------------------------------------------------------------------------- /pydm/tests/utilities/test_units.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from scipy import constants 3 | from pydm.utilities import units 4 | 5 | 6 | @pytest.mark.parametrize("typ, expected", [("cm", "length"), ("non_existent", None)]) 7 | def test_find_unittype(typ, expected): 8 | tp = units.find_unittype(typ) 9 | assert tp == expected 10 | 11 | 12 | @pytest.mark.parametrize("unit, expected", [("cm", constants.centi), ("non_existent", None)]) 13 | def test_find_unit(unit, expected): 14 | r = units.find_unit(unit) 15 | assert r == expected 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "unit, desired, expected", [("m", "cm", 1 / constants.centi), ("m", "rad", None), ("non_existent", "rad", None)] 20 | ) 21 | def test_convert(unit, desired, expected): 22 | r = units.convert(unit, desired) 23 | assert r == expected 24 | 25 | 26 | @pytest.mark.parametrize("unit, expected", [("V", ["MV", "kV", "V", "mV", "uV"]), ("foo", None)]) 27 | def test_find_unit_options(unit, expected): 28 | opts = units.find_unit_options(unit) 29 | assert opts == expected 30 | -------------------------------------------------------------------------------- /pydm/tests/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/tests/widgets/__init__.py -------------------------------------------------------------------------------- /pydm/tests/widgets/test_colormaps.py: -------------------------------------------------------------------------------- 1 | # Unit Tests for the Color Map 2 | 3 | 4 | import numpy as np 5 | 6 | from pydm.widgets.colormaps import ( 7 | PyDMColorMap, 8 | cmaps, 9 | magma, 10 | inferno, 11 | plasma, 12 | viridis, 13 | jet, 14 | monochrome, 15 | hot, 16 | cmap_names, 17 | ) 18 | 19 | 20 | # -------------------- 21 | # POSITIVE TEST CASES 22 | # -------------------- 23 | 24 | 25 | def test_construct(): 26 | """ 27 | Test the construction of the ColorMaps, and the creations of auxiliary helper objects. 28 | 29 | Expectations: 30 | The default values are assigned to the attributes correctly. 31 | """ 32 | 33 | assert np.array_equal(magma, cmaps[PyDMColorMap.Magma]) 34 | assert np.array_equal(inferno, cmaps[PyDMColorMap.Inferno]) 35 | assert np.array_equal(plasma, cmaps[PyDMColorMap.Plasma]) 36 | assert np.array_equal(viridis, cmaps[PyDMColorMap.Viridis]) 37 | assert np.array_equal(jet, cmaps[PyDMColorMap.Jet]) 38 | assert np.array_equal(monochrome, cmaps[PyDMColorMap.Monochrome]) 39 | assert np.array_equal(hot, cmaps[PyDMColorMap.Hot]) 40 | 41 | assert cmap_names[PyDMColorMap.Magma] == "Magma" 42 | assert cmap_names[PyDMColorMap.Inferno] == "Inferno" 43 | assert cmap_names[PyDMColorMap.Plasma] == "Plasma" 44 | assert cmap_names[PyDMColorMap.Viridis] == "Viridis" 45 | assert cmap_names[PyDMColorMap.Jet] == "Jet" 46 | assert cmap_names[PyDMColorMap.Monochrome] == "Monochrome" 47 | assert cmap_names[PyDMColorMap.Hot] == "Hot" 48 | -------------------------------------------------------------------------------- /pydm/tests/widgets/test_embedded_display.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import sys 4 | from qtpy.QtWidgets import QApplication 5 | 6 | test_ui_path_with_relative_path = os.path.join( 7 | os.path.dirname(os.path.realpath(__file__)), "../test_data", "test_relative_filename_parent.ui" 8 | ) 9 | 10 | 11 | def test_show_with_relative_filename(qtbot): 12 | QApplication.instance().make_main_window() 13 | main_window = QApplication.instance().main_window 14 | main_window.open(test_ui_path_with_relative_path) 15 | main_window.setWindowTitle("Embedded Display Test") 16 | qtbot.addWidget(main_window) 17 | display = main_window.home_widget.embeddedDisplay 18 | 19 | # Default behavior should be to not follow symlinks (for backwards compat.). 20 | # Same effect as: display.followSymlinks = False 21 | def check_embed(): 22 | assert display.embedded_widget is not None 23 | 24 | qtbot.waitUntil(check_embed) 25 | 26 | 27 | @pytest.mark.skipif( 28 | sys.platform == "win32" and sys.version_info < (3, 8), 29 | reason="os.path.realpath on Python 3.7 and prior does not resolve symlinks on Windows", 30 | ) 31 | def test_show_with_relative_filename_and_symlink(qtbot, tmp_path): 32 | symlinked_ui_file = tmp_path / "test_ui_with_relative_path.ui" 33 | try: 34 | os.symlink(test_ui_path_with_relative_path, symlinked_ui_file) 35 | except Exception: 36 | pytest.skip("Unable to create a symlink for testing purposes.") 37 | 38 | QApplication.instance().make_main_window() 39 | main_window = QApplication.instance().main_window 40 | main_window.open(symlinked_ui_file) 41 | main_window.setWindowTitle("Embedded Display Test") 42 | qtbot.addWidget(main_window) 43 | display = main_window.home_widget.embeddedDisplay 44 | display.followSymlinks = True 45 | 46 | def check_embed(): 47 | assert display.embedded_widget is not None 48 | 49 | qtbot.waitUntil(check_embed) 50 | -------------------------------------------------------------------------------- /pydm/tests/widgets/test_eventplot.py: -------------------------------------------------------------------------------- 1 | from pydm.widgets.eventplot import PyDMEventPlot 2 | 3 | 4 | def test_add_channel(qtbot): 5 | """A quick check to ensure adding a channel to an event plot works as expected""" 6 | event_plot = PyDMEventPlot() 7 | qtbot.addWidget(event_plot) 8 | 9 | curve = "TEST:EVENT:PLOT" 10 | event_plot.addChannel(curve) 11 | 12 | # We need redrawPlot here to stop a specific pyside6 error where the internal C++ object for the plot gets 13 | # deleted early. This only happens in the case of running all the tests together with pytest. 14 | event_plot.redrawPlot() 15 | 16 | assert event_plot.curveAtIndex(0).channel.address == "TEST:EVENT:PLOT" 17 | -------------------------------------------------------------------------------- /pydm/tests/widgets/test_logdisplay.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import logging 3 | 4 | from qtpy.QtWidgets import QApplication, QWidget 5 | from pydm.widgets.logdisplay import PyDMLogDisplay 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def log(): 10 | log = logging.getLogger("log_test.pydm") 11 | return log 12 | 13 | 14 | def test_write(qtbot, log): 15 | parent = QWidget() 16 | qtbot.addWidget(parent) 17 | 18 | logd = PyDMLogDisplay(parent=parent, logname=log.name, level=logging.INFO) 19 | qtbot.addWidget(logd) 20 | logd.show() 21 | assert logd.logLevel == logging.INFO 22 | assert logd.logName == log.name 23 | assert logd.parent() == parent 24 | 25 | # Watch our error message show up in the log 26 | err_msg = "This is a test of the emergency broadcast system" 27 | log.error(err_msg) 28 | assert err_msg in logd.text.toPlainText() 29 | # Debug shouldn't show up 30 | debug_msg = "Pay no attention to the man behind the curtain" 31 | log.debug(debug_msg) 32 | assert debug_msg not in logd.text.toPlainText() 33 | # Change the level so debug does show up 34 | logd.setLevel("DEBUG") 35 | assert logd.handler.level == logging.DEBUG 36 | assert logd.log.level <= logging.DEBUG 37 | log.debug(debug_msg) 38 | assert debug_msg in logd.text.toPlainText() 39 | # Change the name and make sure we still see what we need 40 | logd.logname = "log_test" 41 | info_msg = "The more things change the more they stay the same" 42 | log.info(info_msg) 43 | assert info_msg in logd.text.toPlainText() 44 | logd.clear() 45 | assert logd.text.toPlainText() == "" 46 | 47 | # Calling .deleteLater() here on pyside6 causes weird behavior with the underlying c++ object in next testcase, 48 | # maybe since the logger is associated with the previous logd instance when it gets deleted? 49 | 50 | 51 | def test_handler_cleanup(qtbot, log): 52 | logd = PyDMLogDisplay(logname=log.name, level=logging.DEBUG) 53 | qtbot.addWidget(logd) 54 | del logd 55 | log.error("This will explode if the handler does not exist") 56 | assert log.handlers == [] 57 | -------------------------------------------------------------------------------- /pydm/tests/widgets/test_multistate.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from qtpy.QtGui import QColor 4 | from qtpy.QtCore import Qt 5 | from pydm.widgets.byte import PyDMMultiStateIndicator 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "value, expectedColor", 10 | [ 11 | (0, QColor(Qt.black)), 12 | (4, QColor(Qt.red)), 13 | (7, QColor(Qt.darkGreen)), 14 | (15, QColor(Qt.yellow)), 15 | ], 16 | ) 17 | def test_state_change(qtbot, signals, value, expectedColor): 18 | """ 19 | Test the widget's handling of the value changed event. 20 | 21 | Expectations: 22 | 1. Widget state is update to reflect incoming signal value 23 | 2. Widgets color after state-update is the correct color set for the new state 24 | 25 | Parameters 26 | ---------- 27 | qtbot : fixture 28 | pytest-qt window for widget testing 29 | signals : fixture 30 | The signals fixture, which provides access signals to be bound to the appropriate slots 31 | expected : int 32 | Expected resulting color after state-update 33 | """ 34 | pydm_multistate = PyDMMultiStateIndicator() 35 | qtbot.addWidget(pydm_multistate) 36 | pydm_multistate.show() 37 | 38 | pydm_multistate.state4Color = QColor(Qt.red) 39 | pydm_multistate.state7Color = QColor(Qt.darkGreen) 40 | pydm_multistate.state15Color = QColor(Qt.yellow) 41 | 42 | pydm_multistate._connected = True 43 | 44 | signals.new_value_signal[type(value)].connect(pydm_multistate.channelValueChanged) 45 | signals.new_value_signal[type(value)].emit(value) 46 | 47 | assert pydm_multistate._curr_state == value 48 | assert pydm_multistate._curr_color == expectedColor 49 | -------------------------------------------------------------------------------- /pydm/tests/widgets/test_template_repeater.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pydm.widgets import PyDMSlider, PyDMTemplateRepeater 3 | 4 | test_template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test_data", "template.ui") 5 | 6 | 7 | def test_template_file(qtbot): 8 | # Test that loading a template and setting data instantiates an instance 9 | # of the template. 10 | template_repeater = PyDMTemplateRepeater() 11 | qtbot.addWidget(template_repeater) 12 | template_repeater.templateFilename = test_template_path 13 | test_data = [{"devname": "test_device"}] 14 | template_repeater.data = test_data 15 | assert template_repeater.count() == len(test_data) 16 | slider = template_repeater.findChild(PyDMSlider, "bCtrlSlider") 17 | assert slider is not None 18 | assert slider.channel == "ca://{}:BCTRL".format(test_data[0]["devname"]) 19 | -------------------------------------------------------------------------------- /pydm/utilities/colors.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import os 3 | 4 | current_dir = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | svg_color_to_hex_map = None 7 | hex_to_svg_color_map = None 8 | with open(os.path.join(current_dir, "hex2color.pkl"), "rb") as f: 9 | hex_to_svg_color_map = pickle.load(f) 10 | with open(os.path.join(current_dir, "color2hex.pkl"), "rb") as f: 11 | svg_color_to_hex_map = pickle.load(f) 12 | 13 | 14 | def svg_color_from_hex(hex_string, hex_on_fail=False): 15 | """ 16 | Returns the SVG color based on its HEX equivalent. 17 | 18 | Parameters 19 | ---------- 20 | hex_string: str 21 | The color code in hex. E.g. #000000 22 | 23 | hex_on_fail: bool, Optional 24 | Whether or not to return the `hex_string` when an invalid color is submitted. 25 | Default is False. 26 | 27 | Returns 28 | ------- 29 | str 30 | The SVG color string. 31 | """ 32 | if not hex_on_fail: 33 | return hex_to_svg_color_map[str(hex_string).lower()] 34 | try: 35 | return hex_to_svg_color_map[str(hex_string).lower()] 36 | except KeyError: 37 | return hex_string 38 | 39 | 40 | def hex_from_svg_color(color_string): 41 | """ 42 | Returns the HEX color based on its SVG equivalent. 43 | 44 | Parameters 45 | ---------- 46 | color_string: str 47 | The SVG color string. E.g. black 48 | 49 | Returns 50 | ------- 51 | str 52 | The HEX color string. 53 | """ 54 | return svg_color_to_hex_map[str(color_string).lower()] 55 | 56 | 57 | default_colors = [ 58 | "white", 59 | "red", 60 | "dodgerblue", 61 | "forestgreen", 62 | "yellow", 63 | "fuchsia", 64 | "turquoise", 65 | "deeppink", 66 | "lime", 67 | "orange", 68 | "whitesmoke", 69 | "beige", 70 | "purple", 71 | "teal", 72 | "darksalmon", 73 | "brown", 74 | ] 75 | -------------------------------------------------------------------------------- /pydm/utilities/connection.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from qtpy.QtWidgets import QWidget 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | def _change_connection_status(widget, status): 9 | """ 10 | Connect or disconnect the inner channels of widgets on the 11 | given widget based on the status parameter. 12 | 13 | Parameters 14 | ---------- 15 | widget : QWidget 16 | The widget which will be iterated over for channel connection. 17 | 18 | status : bool 19 | If True, will call connect on the channels otherwise it will call 20 | disconnect. 21 | """ 22 | widgets = [widget] 23 | widgets.extend(widget.findChildren(QWidget)) 24 | for child_widget in widgets: 25 | try: 26 | if hasattr(child_widget, "channels"): 27 | if child_widget.channels() is None: 28 | continue 29 | for channel in child_widget.channels(): 30 | if channel is None: 31 | continue 32 | if status: 33 | channel.connect() 34 | else: 35 | channel.disconnect() 36 | except NameError: 37 | continue 38 | 39 | 40 | def establish_widget_connections(widget): 41 | """ 42 | Connect the inner channels of widgets on the given widget. 43 | 44 | Parameters 45 | ---------- 46 | widget : QWidget 47 | The widget which will be iterated over for channel connection. 48 | """ 49 | _change_connection_status(widget, True) 50 | 51 | 52 | def close_widget_connections(widget): 53 | """ 54 | Disconnect the inner channels of widgets on the given widget. 55 | 56 | Parameters 57 | ---------- 58 | widget : QWidget 59 | The widget which will be iterated over for channel disconnection. 60 | """ 61 | _change_connection_status(widget, False) 62 | -------------------------------------------------------------------------------- /pydm/utilities/display_loading.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn("pydm.utilities.display_loading was deprecated in favor of pydm.display", DeprecationWarning) 4 | 5 | 6 | def load_py_file(*args, **kwargs): 7 | from pydm.display import load_py_file 8 | 9 | return load_py_file(*args, **kwargs) 10 | -------------------------------------------------------------------------------- /pydm/utilities/fontawesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/utilities/fontawesome.otf -------------------------------------------------------------------------------- /pydm/utilities/shortcuts.py: -------------------------------------------------------------------------------- 1 | from qtpy import QtWidgets, QtGui, QtCore 2 | 3 | 4 | def install_connection_inspector(parent, keys=None): 5 | """ 6 | Install a QShortcut at the application which opens the PyDM Connection 7 | Inspector 8 | 9 | Parameters 10 | ---------- 11 | parent : QWidget 12 | A shortcut is "listened for" by Qt's event loop when the shortcut's 13 | parent widget is receiving events. 14 | keys : QKeySequence, optional 15 | Default value is `Alt+C` 16 | """ 17 | from pydm.connection_inspector import ConnectionInspector 18 | 19 | def show_inspector(): 20 | c = ConnectionInspector(parent=parent) 21 | c.show() 22 | 23 | parent = parent or QtWidgets.QApplication.desktop() 24 | 25 | if keys is None: 26 | keys = QtGui.QKeySequence(QtCore.Qt.ALT | QtCore.Qt.Key_C) 27 | shortcut = QtWidgets.QShortcut(keys, parent) 28 | shortcut.setContext(QtCore.Qt.ApplicationShortcut) 29 | shortcut.activated.connect(show_inspector) 30 | -------------------------------------------------------------------------------- /pydm/widgets/checkbox.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtWidgets import QCheckBox 2 | from .base import PyDMWritableWidget, PostParentClassInitSetup 3 | 4 | 5 | class PyDMCheckbox(QCheckBox, PyDMWritableWidget): 6 | """ 7 | A QCheckbox with support for Channels and more from PyDM 8 | 9 | Parameters 10 | ---------- 11 | parent : QWidget 12 | The parent widget for the Label 13 | init_channel : str, optional 14 | The channel to be used by the widget. 15 | 16 | """ 17 | 18 | def __init__(self, parent=None, init_channel=None): 19 | QCheckBox.__init__(self, parent) 20 | PyDMWritableWidget.__init__(self, init_channel=init_channel) 21 | self.clicked.connect(self.send_value) 22 | # Execute setup calls that must be done here in the widget class's __init__, 23 | # and after it's parent __init__ calls have completed. 24 | # (so we can avoid pyside6 throwing an error, see func def for more info) 25 | PostParentClassInitSetup(self) 26 | 27 | # On pyside6, we need to expilcity call pydm's base class's eventFilter() call or events 28 | # will not propagate to the parent classes properly. 29 | def eventFilter(self, obj, event): 30 | return PyDMWritableWidget.eventFilter(self, obj, event) 31 | 32 | def value_changed(self, new_val): 33 | """ 34 | Callback invoked when the Channel value is changed. 35 | Sets the checkbox checked or not based on the new value. 36 | 37 | Parameters 38 | ---------- 39 | new_val : int 40 | The new value from the channel. 41 | """ 42 | super().value_changed(new_val) 43 | if new_val is None: 44 | return 45 | if new_val > 0: 46 | self.setChecked(True) 47 | else: 48 | self.setChecked(False) 49 | 50 | def send_value(self, checked): 51 | """ 52 | Method that emit the signal to notify the Channel that a new 53 | value was written at the widget. 54 | 55 | Parameters 56 | ---------- 57 | checked : bool 58 | True in case the checkbox was checked, False otherwise. 59 | """ 60 | if checked: 61 | self.send_value_signal.emit(1) 62 | else: 63 | self.send_value_signal.emit(0) 64 | -------------------------------------------------------------------------------- /pydm/widgets/icons/terminator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm/widgets/icons/terminator.png -------------------------------------------------------------------------------- /pydm/widgets/tab_bar_qtplugin.py: -------------------------------------------------------------------------------- 1 | from .qtplugin_base import PyDMDesignerPlugin, WidgetCategory 2 | from .tab_bar import PyDMTabWidget 3 | 4 | 5 | class TabWidgetPlugin(PyDMDesignerPlugin): 6 | """TabWidgetPlugin needs a custom plugin so that it can 7 | populate itself with an initial tab.""" 8 | 9 | TabClass = PyDMTabWidget 10 | 11 | def __init__(self, extensions=None): 12 | super().__init__(self.TabClass, group=WidgetCategory.CONTAINER, extensions=extensions) 13 | 14 | def domXml(self): 15 | """ 16 | XML Description of the widget's properties. 17 | """ 18 | return ( 19 | '\n' 20 | ' \n' 21 | " {1}\n" 22 | " \n" 23 | ' \n' 24 | " {2}\n" 25 | " \n" 26 | '\n' 27 | "\n" 28 | " \n" 29 | "\n" 30 | "\n" 31 | '\n' 32 | ' \n' 33 | " Page 1\n" 34 | " \n" 35 | "\n" 36 | "" 37 | ).format(self.name(), self.toolTip(), self.whatsThis()) 38 | -------------------------------------------------------------------------------- /pydm_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_banner.png -------------------------------------------------------------------------------- /pydm_banner_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_banner_full.png -------------------------------------------------------------------------------- /pydm_launcher/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["main"] # noqa: F405 2 | 3 | from pydm_launcher import * # noqa: F403,F405 4 | -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_128.png -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_16.png -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_24.png -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_256.png -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_32.png -------------------------------------------------------------------------------- /pydm_launcher/icons/pydm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaclab/pydm/c9a430639e8839fa2aeaa512c0015e1664469fa4/pydm_launcher/icons/pydm_64.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "setuptools_scm[toml]"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pydm" 7 | description = "A PyQt-based framework for building user interfaces for control systems" 8 | readme = "README.md" 9 | authors = [ {name = "SLAC National Accelerator Laboratory"} ] 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Natural Language :: English", 13 | "Programming Language :: Python :: 3", 14 | "Operating System :: OS Independent" 15 | ] 16 | requires-python = ">=3.9" 17 | dynamic = ["version", "dependencies", "optional-dependencies"] 18 | 19 | [project.scripts] 20 | pydm = "pydm_launcher.main:main" 21 | 22 | [project.urls] 23 | Homepage = "https://github.com/slaclab/pydm" 24 | Documentation = "https://slaclab.github.io/pydm/" 25 | 26 | [project.license] 27 | file = "LICENSE.md" 28 | 29 | [tool.setuptools_scm] 30 | write_to = "pydm/_version.py" 31 | 32 | [tool.setuptools.packages.find] 33 | where = ["."] 34 | include = ["pydm*"] 35 | namespaces = false 36 | 37 | [tool.setuptools.dynamic.dependencies] 38 | file = "requirements.txt" 39 | 40 | [tool.setuptools.dynamic.optional-dependencies.test] 41 | file = "dev-requirements.txt" 42 | 43 | [tool.setuptools.dynamic.optional-dependencies.test-no-optional] 44 | file = "windows-dev-requirements.txt" 45 | 46 | [tool.setuptools.dynamic.optional-dependencies.doc] 47 | file = "docs-requirements.txt" 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | entrypoints 2 | numpy>=1.11.0 3 | pyepics>=3.2.7 4 | pyqtgraph>=0.12.0 5 | qtpy>=2.2.0 6 | scipy>=0.12.0 7 | six 8 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import pytest 5 | 6 | if __name__ == "__main__": 7 | # Show output results from every test function 8 | # Show the message output for skipped and expected failures 9 | args = ["-v", "-vrxs"] 10 | 11 | # Add extra arguments 12 | if len(sys.argv) > 1: 13 | args.extend(sys.argv[1:]) 14 | 15 | # Show coverage 16 | if "--show-cov" in args: 17 | args.extend(["--cov=pydm", "--cov-report", "term-missing"]) 18 | args.remove("--show-cov") 19 | 20 | # Exclude p4p and pyca tests on Windows until p4p/pyepics compatibility issue is resolved 21 | # and a Windows PyCA build exists 22 | if os.name == "nt": 23 | args.append("--ignore=pydm/tests/data_plugins/test_p4p_plugin_component.py") 24 | args.append("--ignore=pydm/tests/data_plugins/test_psp_plugin_component.py") 25 | 26 | print("pytest arguments: {}".format(args)) 27 | 28 | sys.exit(pytest.main(args)) 29 | -------------------------------------------------------------------------------- /windows-dev-requirements.txt: -------------------------------------------------------------------------------- 1 | codecov 2 | pytest>=3.6 3 | pytest-qt 4 | pytest-cov 5 | pytest-timeout 6 | --------------------------------------------------------------------------------