├── requirements.txt ├── .docbuild ├── requirements.txt ├── build-docs.sh ├── Makefile ├── make.bat ├── index.rst └── conf.py ├── .gitattributes ├── phoebusgen ├── _version.pyi ├── widget │ ├── fonts.yaml │ ├── __init__.py │ ├── colors.yaml │ ├── widget.py │ └── widgets.py ├── config │ ├── 4.7_widgets.def │ ├── 4.7.1_widgets.def │ ├── 4.7.2_widgets.def │ ├── 4.7.3_widgets.def │ ├── color.def │ ├── font.def │ └── classes.bcf ├── screen │ ├── __init__.py │ └── screen.py ├── __init__.py └── _shared_property_helpers.py ├── reload_local_pip.sh ├── MANIFEST.in ├── .codecov.yml ├── .pre-commit-config.yaml ├── upload_pip.sh ├── .github └── workflows │ ├── pre-commit.yml │ ├── python-publish.yml │ ├── build-docs.yml │ └── ci.yml ├── .gitignore ├── examples ├── exampleInPhoebus.py ├── example1.py ├── example2.py ├── example_image.py ├── example_xyplot.py └── exampleInPhoebus.bob ├── pyproject.toml ├── tests ├── test_screen.py ├── test_widget.py └── test_widgets.py ├── README.md ├── CHANGELOG.md └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.docbuild/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_autodoc_typehints 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false 2 | *.js linguist-detectable=false 3 | *.css linguist-detectable=false 4 | -------------------------------------------------------------------------------- /phoebusgen/_version.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | version: str 4 | version_tuple: tuple[int, int, int] | tuple[int, int, int, str, str] 5 | -------------------------------------------------------------------------------- /reload_local_pip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo y | pip3 uninstall phoebusgen 4 | rm -rf build 5 | rm -rf phoebusgen.egg-info 6 | rm -rf dist 7 | python3 -m build 8 | pip3 install --user . 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include phoebusgen/config/*.def 2 | include phoebusgen/config/*.bcf 3 | include phoebusgen/examples/*.py 4 | include README.md 5 | include LICENSE 6 | include requirements.txt 7 | include phoebusgen/_version.py 8 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 80% 6 | patch: 7 | default: 8 | target: 50% 9 | fixes: 10 | - "/home/runner/work/phoebusgen/phoebusgen/::" 11 | -------------------------------------------------------------------------------- /.docbuild/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | make clean 6 | rm -rf source/ 7 | rm -rf ../docs 8 | 9 | sphinx-apidoc -o ./source ../phoebusgen 10 | 11 | make html 12 | 13 | cp -r _build/html/ ../docs 14 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: end-of-file-fixer 6 | - id: trailing-whitespace 7 | - id: check-docstring-first 8 | - id: double-quote-string-fixer 9 | -------------------------------------------------------------------------------- /upload_pip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | printf "\n##################################" 4 | printf "\nThis is done in github actions now\n" 5 | printf "##################################\n\n" 6 | 7 | #rm -rf build 8 | #rm -rf phoebusgen.egg-info 9 | #rm -rf dist 10 | #python3 setup.py sdist bdist_wheel 11 | #python3 -m twine upload dist/* --verbose 12 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | pre-commit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v3 17 | - uses: pre-commit/action@v3.0.0 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | venv/ 7 | .idea/ 8 | 9 | 10 | 11 | # Python files # 12 | build 13 | dist 14 | phoebusgen.egg-info/ 15 | 16 | *.swp 17 | 18 | examples/*.bob 19 | !examples/exampleInPhoebus.bob 20 | 21 | local/ 22 | 23 | .docbuild/_build 24 | .docbuild/source 25 | docs 26 | 27 | phoebusgen/_version.py 28 | -------------------------------------------------------------------------------- /examples/exampleInPhoebus.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import phoebusgen 3 | 4 | pv_arg = sys.argv[1] 5 | 6 | print('Received the following arg from phoebus: {}'.format(pv_arg)) 7 | 8 | my_screen = phoebusgen.screen.Screen('My Generated Screen', '/home/tford/als/hlc/phoebusgen/examples/generated.bob') 9 | 10 | y = 0 11 | for i in range(int(pv_arg)): 12 | idx = str(i) 13 | label = phoebusgen.widget.Label('label{}'.format(idx), 'Label {}'.format(idx), 0, y, 100, 20) 14 | my_screen.add_widget(label) 15 | y += 30 16 | 17 | my_screen.write_screen() 18 | -------------------------------------------------------------------------------- /phoebusgen/widget/fonts.yaml: -------------------------------------------------------------------------------- 1 | comment: 2 | family: 'Liberation Sans' 3 | size: '14' 4 | style: 'Italic' 5 | default: 6 | family: 'Liberation Sans' 7 | size: '14' 8 | style: 'Regular' 9 | default bold: 10 | family: 'Liberation Sans' 11 | size: '14' 12 | style: 'Bold' 13 | fine print: 14 | family: 'Liberation Sans' 15 | size: '12' 16 | style: 'Regular' 17 | header 1: 18 | family: 'Liberation Sans' 19 | size: '22' 20 | style: 'Bold' 21 | header 2: 22 | family: 'Liberation Sans' 23 | size: '18' 24 | style: 'Bold' 25 | header 3: 26 | family: 'Liberation Sans' 27 | size: '16' 28 | style: 'Bold' 29 | oddball: 30 | family: 'Liberation Sans' 31 | size: '40' 32 | style: 'Regular' 33 | -------------------------------------------------------------------------------- /.docbuild/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.docbuild/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.docbuild/index.rst: -------------------------------------------------------------------------------- 1 | .. phoebusgen documentation master file, created by 2 | sphinx-quickstart on Thu Sep 30 13:07:09 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to phoebusgen's documentation! 7 | ====================================== 8 | 9 | 10 | The code of this project is on Github: `phoebusgen `_ 11 | 12 | Phoebusgen can be downloded via pip, `see here `_ 13 | 14 | Look at :doc:`the widget docs ` and :doc:`the screen docs `. 15 | 16 | To update the widget versioning, check the phoebusgen.phoebus_version and phoebusgen.widget_versions variables, along with 17 | the phoebusgen.change_widget_version method. 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | :caption: Contents: 22 | 23 | 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /phoebusgen/config/4.7_widgets.def: -------------------------------------------------------------------------------- 1 | # 4.7 Widgets 2 | 3 | # Graphics 4 | arc = 2.0.0 5 | ellipse = 2.0.0 6 | label = 2.0.0 7 | picture = 2.0.0 8 | polygon = 2.0.0 9 | polyline = 2.0.0 10 | rectangle = 2.0.0 11 | 12 | # Monitors 13 | byte_monitor = 2.0.0 14 | led = 2.0.0 15 | multi_state_led = 2.0.0 16 | meter = 3.0.0 17 | progressbar = 2.0.0 18 | symbol = 2.0.0 19 | table = 2.0.0 20 | tank = 2.0.0 21 | text-symbol = 2.0.0 22 | textupdate = 2.0.0 23 | thermometer = 2.0.0 24 | 25 | # Controls 26 | action_button = 2.0.0 27 | bool_button = 2.0.0 28 | checkbox = 2.0.0 29 | choice = 2.0.0 30 | combo = 2.0.0 31 | fileselector = 2.0.0 32 | radio = 2.0.0 33 | scaledslider = 2.0.0 34 | scrollbar = 2.0.0 35 | slide_button = 2.0.0 36 | spinner = 2.0.0 37 | textentry = 2.0.0 38 | thumbwheel = 2.0.0 39 | 40 | # Plots 41 | databrowser = 2.0.0 42 | image = 2.0.0 43 | stripchart = 2.1.0 44 | xyplot = 2.0.0 45 | 46 | # Structure 47 | array = 2.0.0 48 | tabs = 2.0.0 49 | embedded = 2.0.0 50 | group = 2.0.0 51 | navtabs = 2.0.0 52 | template = 2.0.0 53 | 54 | # Miscellaneous 55 | 3dviewer = 2.0.0 56 | webbrowser = 2.0.0 57 | -------------------------------------------------------------------------------- /phoebusgen/config/4.7.1_widgets.def: -------------------------------------------------------------------------------- 1 | # 4.7.1 Widgets 2 | 3 | # Graphics 4 | arc = 2.0.0 5 | ellipse = 2.0.0 6 | label = 2.0.0 7 | picture = 2.0.0 8 | polygon = 2.0.0 9 | polyline = 2.0.0 10 | rectangle = 2.0.0 11 | 12 | # Monitors 13 | byte_monitor = 2.0.0 14 | led = 2.0.0 15 | multi_state_led = 2.0.0 16 | meter = 3.0.0 17 | progressbar = 2.0.0 18 | symbol = 2.0.0 19 | table = 2.0.0 20 | tank = 2.0.0 21 | text-symbol = 2.0.0 22 | textupdate = 2.0.0 23 | thermometer = 2.0.0 24 | 25 | # Controls 26 | action_button = 2.0.0 27 | bool_button = 2.0.0 28 | checkbox = 2.0.0 29 | choice = 2.0.0 30 | combo = 2.0.0 31 | fileselector = 2.0.0 32 | radio = 2.0.0 33 | scaledslider = 2.0.0 34 | scrollbar = 2.0.0 35 | slide_button = 2.0.0 36 | spinner = 2.0.0 37 | textentry = 2.0.0 38 | thumbwheel = 2.0.0 39 | 40 | # Plots 41 | databrowser = 2.0.0 42 | image = 2.0.0 43 | stripchart = 2.1.0 44 | xyplot = 3.0.0 45 | 46 | # Structure 47 | array = 2.0.0 48 | tabs = 2.0.0 49 | embedded = 2.0.0 50 | group = 2.0.0 51 | navtabs = 2.0.0 52 | template = 2.0.0 53 | 54 | # Miscellaneous 55 | 3dviewer = 2.0.0 56 | webbrowser = 2.0.0 57 | -------------------------------------------------------------------------------- /phoebusgen/config/4.7.2_widgets.def: -------------------------------------------------------------------------------- 1 | # 4.7.2 Widgets 2 | 3 | # Graphics 4 | arc = 2.0.0 5 | ellipse = 2.0.0 6 | label = 2.0.0 7 | picture = 2.0.0 8 | polygon = 2.0.0 9 | polyline = 2.0.0 10 | rectangle = 2.0.0 11 | 12 | # Monitors 13 | byte_monitor = 2.0.0 14 | led = 2.0.0 15 | multi_state_led = 2.0.0 16 | meter = 3.0.0 17 | progressbar = 2.0.0 18 | symbol = 2.0.0 19 | table = 2.0.0 20 | tank = 2.0.0 21 | text-symbol = 2.0.0 22 | textupdate = 2.0.0 23 | thermometer = 2.0.0 24 | 25 | # Controls 26 | action_button = 3.0.0 27 | bool_button = 2.0.0 28 | checkbox = 2.0.0 29 | choice = 2.0.0 30 | combo = 2.0.0 31 | fileselector = 2.0.0 32 | radio = 2.0.0 33 | scaledslider = 2.0.0 34 | scrollbar = 2.0.0 35 | slide_button = 2.0.0 36 | spinner = 2.0.0 37 | textentry = 2.0.0 38 | thumbwheel = 2.0.0 39 | 40 | # Plots 41 | databrowser = 2.0.0 42 | image = 2.0.0 43 | stripchart = 2.1.0 44 | xyplot = 3.0.0 45 | 46 | # Structure 47 | array = 2.0.0 48 | tabs = 2.0.0 49 | embedded = 2.0.0 50 | group = 2.0.0 51 | navtabs = 2.0.0 52 | template = 2.0.0 53 | 54 | # Miscellaneous 55 | 3dviewer = 2.0.0 56 | webbrowser = 2.0.0 57 | -------------------------------------------------------------------------------- /phoebusgen/config/4.7.3_widgets.def: -------------------------------------------------------------------------------- 1 | # 4.3.7 Widgets 2 | 3 | # Graphics 4 | arc = 2.0.0 5 | ellipse = 2.0.0 6 | label = 2.0.0 7 | picture = 2.0.0 8 | polygon = 2.0.0 9 | polyline = 2.0.0 10 | rectangle = 2.0.0 11 | 12 | # Monitors 13 | byte_monitor = 2.0.0 14 | led = 2.0.0 15 | multi_state_led = 2.0.0 16 | meter = 3.0.0 17 | progressbar = 2.0.0 18 | symbol = 2.0.0 19 | table = 2.0.0 20 | tank = 2.0.0 21 | text-symbol = 2.0.0 22 | textupdate = 2.0.0 23 | thermometer = 2.0.0 24 | 25 | # Controls 26 | action_button = 3.0.0 27 | bool_button = 2.0.0 28 | checkbox = 2.0.0 29 | choice = 2.0.0 30 | combo = 2.0.0 31 | fileselector = 2.0.0 32 | radio = 2.0.0 33 | scaledslider = 2.0.0 34 | scrollbar = 2.0.0 35 | slide_button = 2.0.0 36 | spinner = 2.0.0 37 | textentry = 2.0.0 38 | thumbwheel = 2.0.0 39 | 40 | # Plots 41 | databrowser = 2.0.0 42 | image = 2.0.0 43 | stripchart = 2.1.0 44 | xyplot = 3.0.0 45 | 46 | # Structure 47 | array = 2.0.0 48 | tabs = 2.0.0 49 | embedded = 2.0.0 50 | group = 3.0.0 51 | navtabs = 2.0.0 52 | template = 2.0.0 53 | 54 | # Miscellaneous 55 | 3dviewer = 2.0.0 56 | webbrowser = 2.0.0 57 | -------------------------------------------------------------------------------- /examples/example1.py: -------------------------------------------------------------------------------- 1 | """Example 1 2 | Create ./example1.bob with 3 widgets 3 | """ 4 | 5 | import phoebusgen 6 | 7 | pv_prefix = 'loc://example1' 8 | my_screen = phoebusgen.screen.Screen('Phoebusgen Example 1') 9 | my_screen.macro('PV_PREFIX', pv_prefix) 10 | my_screen.background_color(204, 255, 255) 11 | 12 | widgets = [] 13 | 14 | # don't worry about width and height, we will use auto-size after setting font size 15 | title = phoebusgen.widget.Label('TitleLabel', 'Example 1 Phoebusgen Title', 0, 0, 0, 0) 16 | title.font_size(36) 17 | title.auto_size() 18 | widgets.append(title) 19 | 20 | pv_name = pv_prefix + ':BOOL(0, "OFF", "ON")' 21 | 22 | check_box = phoebusgen.widget.CheckBox('MyCheckBox', 'Boolean PV', pv_name, 0, 80, 190, 70) 23 | check_box.font_style_bold() 24 | check_box.font_size(24) 25 | widgets.append(check_box) 26 | 27 | led = phoebusgen.widget.LED('MyLED', pv_name, 230, 60, 140, 110) 28 | led.off_color(255, 0, 0) 29 | widgets.append(led) 30 | 31 | # add all widgets to our screen 32 | my_screen.add_widget(widgets) 33 | 34 | # write to specified file 35 | my_screen.write_screen('./example1.bob') 36 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Fetch all history for all tags and branches 26 | run: git fetch --prune --unshallow 27 | - name: Set up Python 28 | uses: actions/setup-python@v3 29 | with: 30 | python-version: '3.x' 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install build 35 | - name: Build package 36 | run: python -m build 37 | - name: Publish package 38 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 39 | with: 40 | user: __token__ 41 | password: ${{ secrets.PYPI_API_TOKEN }} 42 | -------------------------------------------------------------------------------- /phoebusgen/screen/__init__.py: -------------------------------------------------------------------------------- 1 | """ phoebusgen.screen Module 2 | 3 | This module contains a Python class representation of a Phoebus screen. A screen 4 | can be created via the Screen class and then widgets from Phoebusgen.widget can be 5 | added to the Python screen object. At the end, the screen object can write the XML 6 | to a .bob file which can be opened immeditaely into Phoebus 7 | 8 | Example: 9 | >>> import phoebusgen.screen 10 | >>> import phoebusgen.widget 11 | >>> my_screen = phoebusgen.screen.Screen("my screen") 12 | >>> print(my_screen) 13 | 14 | 15 | my screen 16 | 17 | 18 | >>> my_widget = phoebusgen.widget.TextUpdate("test", "test:PV", 10, 10 ,10 ,10) 19 | >>> my_screen.add_widget(my_widget) 20 | >>> print(my_screen) 21 | 22 | 23 | my screen 24 | 25 | test 26 | 10 27 | 10 28 | 10 29 | 10 30 | test:PV 31 | 32 | 33 | """ 34 | 35 | # Copyright (c) 2022 Lawrence Berkeley National Laboratory, 36 | # Advanced Light Source, Engineering Division 37 | 38 | from phoebusgen.screen.screen import * 39 | 40 | __all__ = ['screen'] 41 | -------------------------------------------------------------------------------- /phoebusgen/widget/__init__.py: -------------------------------------------------------------------------------- 1 | """ phoebusgen.widget Module 2 | 3 | This module contains Python class representations of each widget. Widgets can be created 4 | by calling the widget class constructor, i.e. phoebusgen.widget.TextUpdate(...) or 5 | phoebusgen.widget.ScaledSlider(...). Once the class is created and assigned to a Python 6 | variable, additional methods to change widget properties are available. 7 | 8 | Example: 9 | >>> import phoebusgen 10 | >>> text_update_widget = phoebusgen.widget.TextUpdate('test widget', 'TEST:PV', 10, 20, 20, 50) 11 | >>> text_update_widget.predefined_foreground_color(phoebusgen.colors.OK) 12 | >>> text_update_widget.font_style_bold() 13 | >>> print(text_update_widget) 14 | 15 | 16 | test widget 17 | 10 18 | 20 19 | 20 20 | 50 21 | TEST:PV 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | """ 30 | 31 | # Copyright (c) 2022 Lawrence Berkeley National Laboratory, 32 | # Advanced Light Source, Engineering Division 33 | 34 | from phoebusgen.widget.widgets import * 35 | 36 | __all__ = ['widgets'] 37 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "setuptools_scm>=8"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = 'phoebusgen' 7 | authors = [ 8 | {name = 'Tynan Ford', email = 'tford@lbl.gov'}, 9 | {name = 'Madeline Park', email = 'MadelinePark@lbl.gov'} 10 | ] 11 | description = 'Screen generator for CS-Studio Phoebus displays' 12 | readme = {file = 'README.md', content-type = 'text/markdown'} 13 | license = {file = "LICENSE"} 14 | dynamic = ["version"] 15 | requires-python = '>=3.5' 16 | classifiers=[ 17 | 'Programming Language :: Python :: 3', 18 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 19 | 'Operating System :: OS Independent', 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Topic :: Software Development :: Code Generators' 22 | ] 23 | 24 | [project.optional-dependencies] 25 | dev = [ 26 | "pre-commit", 27 | ] 28 | docs = [ 29 | "sphinx", 30 | "sphinx_rtd_theme", 31 | "sphinx_autodoc_typehints", 32 | ] 33 | 34 | [tool.setuptools_scm] 35 | version_file = "phoebusgen/_version.py" 36 | 37 | [project.urls] 38 | Homepage = "https://github.com/als-epics/phoebusgen" 39 | Documentation = "https://als-epics.github.io/phoebusgen" 40 | Repository = "https://github.com/als-epics/phoebusgen" 41 | "Bug Tracker" = "https://github.com/als-epics/phoebusgen/issues" 42 | Changelog = "https://github.com/als-epics/phoebusgen/blob/master/CHANGELOG.md" 43 | -------------------------------------------------------------------------------- /examples/example2.py: -------------------------------------------------------------------------------- 1 | from phoebusgen import widget as w 2 | from phoebusgen import screen as s 3 | from phoebusgen import colors, fonts 4 | 5 | widget_height = 20 6 | widget_length = 120 7 | 8 | def make_group(num_pvs): 9 | group = w.Group('Example Group', 0, 0, 400, 240) 10 | group.predefined_font(fonts.Header2) 11 | for i in range(1, num_pvs+1): 12 | y_position = (i-1)*widget_height 13 | pv_name = '$(P):update{}({})'.format(i, i*100) 14 | label = w.Label('Label{}'.format(i), 'Here is a Label', 0, y_position, widget_length, widget_height) 15 | text_update = w.TextUpdate('TextUpdate{}'.format(i), pv_name, widget_length, y_position, widget_length, widget_height) 16 | comment = w.Label('Comment{}'.format(i), 'Label explanation', widget_length*2, y_position, widget_length, widget_height) 17 | comment.predefined_font(fonts.Comment) 18 | comment.font_style_bold_italic() 19 | label.foreground_color(100, 0, 100) 20 | comment.predefined_foreground_color(colors.Attention) 21 | group.add_widget([label, text_update, comment]) 22 | return group 23 | 24 | def make_screen(): 25 | example_screen = s.Screen('Phoebusgen Example 2', './example2.bob') 26 | example_screen.width(1000) 27 | example_screen.height(1000) 28 | example_screen.macro('P', 'loc://example3') 29 | example_screen.background_color(188, 188, 188) 30 | group = make_group(10) 31 | example_screen.add_widget(group) 32 | example_screen.write_screen() 33 | 34 | 35 | if __name__ == '__main__': 36 | make_screen() 37 | -------------------------------------------------------------------------------- /examples/example_image.py: -------------------------------------------------------------------------------- 1 | """Example 2 | Create ./example_image.bob with an image and properties. 3 | """ 4 | 5 | import phoebusgen 6 | import phoebusgen.widget 7 | 8 | my_screen = phoebusgen.screen.Screen('Image Example') 9 | widgets = [] 10 | 11 | image1 = phoebusgen.widget.Image('Example Image 1', None, 10, 10, 500, 500) 12 | image1.pv_name('sim://sinewave(10, 50, 10000, 0.5)') 13 | 14 | # Add an x-axis 15 | x_axis = phoebusgen.widget.ImageXAxis() 16 | x_axis.minimum(-100) 17 | x_axis.maximum(100) 18 | image1.add_x_axis(x_axis) 19 | 20 | # Add a y-axis 21 | y_axis = phoebusgen.widget.ImageYAxis() 22 | y_axis.minimum(-100) 23 | y_axis.maximum(100) 24 | image1.add_y_axis(y_axis) 25 | 26 | # Color maps have predefined color sets 27 | image1.predefined_color_map('JET') 28 | 29 | # But you can also customize your own 30 | # Parameters: value (order of colors), RGB 31 | # All with values 0-255 32 | color1 = phoebusgen.widget.ColorMapColor(0, 255, 100, 255) 33 | color3 = phoebusgen.widget.ColorMapColor(100, 255, 0, 100) 34 | color2 = phoebusgen.widget.ColorMapColor(255, 100, 0, 255) 35 | 36 | image1.add_color_map(color1) 37 | image1.add_color_map(color2) 38 | image1.add_color_map(color3) 39 | 40 | # add an interactive region of interest 41 | roi1 = phoebusgen.widget.RegionOfInterest() 42 | image1.add_roi(roi1) 43 | roi1.x_pv('loc://roi_x(10)') 44 | roi1.y_pv('loc://roi_y(10)') 45 | roi1.width_pv('loc://roi_w(50)') 46 | roi1.height_pv('loc://roi_h(10)') 47 | roi1.interactive(True) 48 | roi1.color(0, 0, 0) 49 | 50 | widgets.append(image1) 51 | 52 | my_screen.add_widget(widgets) 53 | my_screen.write_screen('./example_image.bob') 54 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | #pull_request: 8 | # branches: 9 | # - master 10 | 11 | jobs: 12 | docs: 13 | permissions: 14 | contents: write 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python 19 | uses: actions/setup-python@v3 20 | with: 21 | python-version: "3.10" 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install sphinx 26 | pip install sphinx_autodoc_typehints 27 | pip install sphinx-rtd-theme 28 | - name: Build Docs 29 | run: ./.docbuild/build-docs.sh 30 | - name: Archive docs 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: save-docs 34 | path: docs 35 | - name: Commit documentation changes 36 | run: | 37 | git clone https://github.com/als-epics/phoebusgen.git --branch gh-pages --single-branch gh-pages 38 | cp -r docs/* gh-pages/ 39 | cd gh-pages 40 | git config --local user.email "action@github.com" 41 | git config --local user.name "GitHub Action" 42 | git add . 43 | git commit -m "Update documentation" -a || true 44 | # The above command will fail if no changes were present, so we ignore 45 | # the return code. 46 | - name: Push changes 47 | uses: ad-m/github-push-action@master 48 | with: 49 | branch: gh-pages 50 | directory: gh-pages 51 | github_token: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /phoebusgen/widget/colors.yaml: -------------------------------------------------------------------------------- 1 | activetext: 2 | red: '255' 3 | green: '255' 4 | blue: '0' 5 | alpha: '255' 6 | attention: 7 | red: '255' 8 | green: '160' 9 | blue: '0' 10 | alpha: '255' 11 | background: 12 | red: '255' 13 | green: '255' 14 | blue: '255' 15 | alpha: '255' 16 | button_background: 17 | red: '210' 18 | green: '210' 19 | blue: '210' 20 | alpha: '255' 21 | disconnected: 22 | red: '200' 23 | green: '0' 24 | blue: '200' 25 | alpha: '200' 26 | grid: 27 | red: '128' 28 | green: '128' 29 | blue: '128' 30 | alpha: '255' 31 | header_background: 32 | red: '77' 33 | green: '77' 34 | blue: '77' 35 | alpha: '255' 36 | header_foreground: 37 | red: '255' 38 | green: '255' 39 | blue: '255' 40 | alpha: '255' 41 | invalid: 42 | red: '255' 43 | green: '0' 44 | blue: '255' 45 | alpha: '255' 46 | major: 47 | red: '255' 48 | green: '0' 49 | blue: '0' 50 | alpha: '255' 51 | minor: 52 | red: '255' 53 | green: '128' 54 | blue: '0' 55 | alpha: '255' 56 | 'off': 57 | red: '60' 58 | green: '100' 59 | blue: '60' 60 | alpha: '255' 61 | ok: 62 | red: '0' 63 | green: '255' 64 | blue: '0' 65 | alpha: '255' 66 | 'on': 67 | red: '0' 68 | green: '255' 69 | blue: '0' 70 | alpha: '255' 71 | read_background: 72 | red: '240' 73 | green: '240' 74 | blue: '240' 75 | alpha: '255' 76 | stop: 77 | red: '255' 78 | green: '0' 79 | blue: '0' 80 | alpha: '255' 81 | text: 82 | red: '0' 83 | green: '0' 84 | blue: '0' 85 | alpha: '255' 86 | transparent: 87 | red: '255' 88 | green: '255' 89 | blue: '255' 90 | alpha: '0' 91 | write_background: 92 | red: '128' 93 | green: '255' 94 | blue: '255' 95 | alpha: '255' 96 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Python unittest 2 | permissions: 3 | contents: read 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | container: ${{ matrix.container }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json 20 | include: 21 | - python-version: "3.6" 22 | container: "python:3.6" 23 | - python-version: "3.7" 24 | container: "python:3.7" 25 | - python-version: "3.8" 26 | - python-version: "3.9" 27 | - python-version: "3.10" 28 | - python-version: "3.11" 29 | - python-version: "3.12" 30 | - python-version: "3.13" 31 | - python-version: "3.14" 32 | name: Python ${{ matrix.python-version }} 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Setup python 36 | if: ${{ !matrix.container }} 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | architecture: x64 41 | - name: Install dependencies 42 | run: | 43 | python -m pip install --upgrade pip 44 | pip install coverage 45 | pip install -r requirements.txt 46 | - name: Run tests with unittest and codecov 47 | run: coverage run -m unittest discover tests -b 48 | - name: Upload coverage to Codecov 49 | uses: codecov/codecov-action@v1 50 | with: 51 | fail_ci_if_error: false 52 | verbose: false 53 | -------------------------------------------------------------------------------- /phoebusgen/config/color.def: -------------------------------------------------------------------------------- 1 | # Named colors 2 | # 3 | # Format: 4 | # NameOfColor = red, green, blue [, alpha ] | PreviouslyDefinedNameOfColor 5 | # with values in 0..255 range. 6 | # 7 | # Whenever possible, use named colors in displays 8 | # instead of arbitrary red/green/blue values. 9 | 10 | # ------- Predefined colors ---------------- 11 | # May be overridden in here 12 | 13 | # Alarm related 14 | OK = 0, 255, 0 15 | MINOR = 255, 128, 0 16 | MAJOR = 255, 0, 0 17 | INVALID = 255, 0, 255 18 | DISCONNECTED = 200, 0, 200, 200 19 | 20 | # Default color for text 21 | Text=0,0,0 22 | 23 | # Default color for 'active' text that's being edited 24 | ActiveText=255, 255, 0 25 | 26 | # Display background 27 | Background = 255, 255, 255 28 | 29 | # .. for widgets that read/write a value 30 | Read_Background = 240, 240, 240 31 | Write_Background = 128, 255, 255 32 | 33 | # .. for buttons 34 | Button_Background = 210, 210, 210 35 | 36 | # ------- Examples for additional colors ---------------- 37 | # Also show ideas for site-specific guidelines that 38 | # are required to make sense of the color names. 39 | 40 | # Styling 41 | Header_Background=77,77,77 42 | Header_ForeGround=255,255,255 43 | 44 | # Use alarm colors only when you mean to indicate an alarm. 45 | # Avoid using 'Red' which might suggest an alarm 46 | # just because you like the look of red. 47 | # STOP looks similar to red=MAJOR alarm, and is allowed 48 | # for 'STOP' type of buttons 49 | STOP = MAJOR 50 | 51 | # Attention looks similar to a MINOR alarm. 52 | # It is meant to draw attention 53 | Attention = 255,160,0 54 | 55 | # The colors for On/Off, Open/Close etc,. 56 | # "On" does not necessarily mean 'device is turned on', but 57 | # stands for 'indicator is on, active, illuminated'. 58 | # For a 'motor is at target' type indicator, the motor would 59 | # actually be 'off' while the indicator uses the 'On' color. 60 | # 61 | # If one of the states represents an alarm, the corresponding alarm color may be used. 62 | # For example, a limit switch indicator could use colors "Off" and "MAJOR": 63 | # Off when idle, MAJOR when the limit switch was hit and this is an abnormal situation 64 | # that requires attention. 65 | On = OK 66 | Off = 60,100,60 67 | -------------------------------------------------------------------------------- /phoebusgen/config/font.def: -------------------------------------------------------------------------------- 1 | // Named font definitions 2 | // 3 | // Entries in this file are read in sequence. 4 | // A later entry in the file can override 5 | // an earlier entry in the file. 6 | // 7 | // In a production setup, this file may be constructed 8 | // by concatenating a generic file with a more specific file, 9 | // and the specific entries would then override generic entries 10 | // of the same name. 11 | 12 | // Format: 13 | // 14 | // NamedFont['(' OS ')'] = Family '-' Style '-' Size | '@'PreviouslyDefinedNamedFont 15 | // 16 | // Family: Font family name "Liberation Sans", "Liberation Mono", "Liberation Serif" 17 | // Style: "regular", "bold", "italic", "bold italic" 18 | // Size: Font height in pixels 19 | // OS: "windows", "linux", "macosx" 20 | // 21 | // Leading/trailing spaces around each element are OK, but if the font family 22 | // is "Liberation Sans", it has to be typed with just that one space between 23 | // "Liberation" and "Sans" 24 | // 25 | // Examples of named fonts 26 | // 27 | // Default = Liberation Sans - regular - 14 28 | // Default Bold = Liberation Sans - bold - 14 29 | // Header 1 = @Default Bold 30 | // 31 | // Speaking of "Liberation Sans": 32 | // The display builder includes the "Liberation" fonts 33 | // from https://fedorahosted.org/liberation-fonts. 34 | // Their use is encouraged because the resulting displays 35 | // will always render correctly. 36 | // When using other fonts, for example "Arial" on Windows, 37 | // the font might not be available to a display builder 38 | // runtime that is executing on Mac OS or Linux. 39 | 40 | // Predefined fonts that this file could re-define 41 | Default = Liberation Sans - regular - 14 42 | Default Bold = Liberation Sans - bold - 14 43 | Header 1 = Liberation Sans - bold - 22 44 | Header 2 = Liberation Sans - bold - 18 45 | Header 3 = Liberation Sans - bold - 16 46 | Comment = Liberation Sans - italic - 14 47 | Fine Print = Liberation Sans - regular - 12 48 | 49 | 50 | // Example for a named font 51 | Oddball = Comic Sans MS-regular-40 52 | 53 | // On Linux resp. MacOS, these will be used instead, 54 | // replacnig the definition shown above. 55 | // Operating system selectors are: "windows", "linux", "macosx" 56 | Oddball(linux) = PakTypeNaqsh-regular-40 57 | Oddball(macosx) = Herculanum-regular-40 58 | -------------------------------------------------------------------------------- /examples/example_xyplot.py: -------------------------------------------------------------------------------- 1 | """Example 2 | Create ./example_xyplot.bob with 3 plots with properties. 3 | """ 4 | 5 | 6 | import phoebusgen 7 | import phoebusgen.widget 8 | 9 | my_screen = phoebusgen.screen.Screen('XYPlot Example') 10 | widgets = [] 11 | 12 | 13 | #XYPlot 1: An XYPlot with two y-axes and multiple traces. 14 | 15 | xy_plot1 = phoebusgen.widget.XYPlot('Example Plot 1', 10, 10, 500, 500) 16 | xy_plot1.title('Example Plot 1') 17 | 18 | # Although this y-axis exists by default, adding the widget allows access to 19 | # the methods associated with y-axes 20 | yaxis1 = phoebusgen.widget.XYPlotYAxis() 21 | yaxis1.title('Y Axis 1') 22 | xy_plot1.add_y_axis(yaxis1) 23 | 24 | # This is a second y-axis. 25 | yaxis2 = phoebusgen.widget.XYPlotYAxis() 26 | yaxis2.title('Y Axis 2') 27 | yaxis2.on_right(True) 28 | xy_plot1.add_y_axis(yaxis2) 29 | 30 | # Traces are associated with the first y-axis by default 31 | trace1 = phoebusgen.widget.XYPlotTrace() 32 | # traces can have both an x and y PV 33 | trace1.x_pv('sim://sawtooth(3, 0, 50, 0.2, 25, 75)') 34 | trace1.y_pv('sim://sinewave(1, 50, 100, 0.1, 10, 50)') 35 | xy_plot1.add_trace(trace1) 36 | 37 | # This trace is based on the second y-axis 38 | trace2 = phoebusgen.widget.XYPlotTrace() 39 | trace2.y_pv('sim://gaussianwave(10, 4, 100, 1)') 40 | yaxis2.auto_scale(True) # scales y-axis based on trace 41 | trace2.axis(1) 42 | xy_plot1.add_trace(trace2) 43 | 44 | widgets.append(xy_plot1) 45 | 46 | 47 | # XYPlot 2: An XYPlot that uses trace methods. 48 | 49 | xy_plot2 = phoebusgen.widget.XYPlot('Example Plot 2', 520, 10, 500, 500) 50 | xy_plot2.title('Example Plot 2') 51 | 52 | trace3 = phoebusgen.widget.XYPlotTrace() 53 | trace3.y_pv('sim://noisewave(25, 50, 0.5)') 54 | xy_plot2.add_trace(trace3) 55 | 56 | # Traces have many styles 57 | trace3.line_style_dot() 58 | 59 | # Their width and points can also be changed 60 | trace3.point_type_triangles() 61 | trace3.line_width(2) 62 | 63 | # Traces can also be given custom colors 64 | trace3.color(255, 100, 255, 255) # alpha value is optional 65 | widgets.append(xy_plot2) 66 | 67 | 68 | # XYPlot 3: A stylized XY Plot 69 | 70 | xy_plot3 = phoebusgen.widget.XYPlot('Example Plot 3', 1040, 10, 500, 500) 71 | xy_plot3.title('Example Plot 3') 72 | xy_plot3.title_font_family('Lato') 73 | xy_plot3.title_font_style_bold_italic() 74 | xy_plot3.title_font_size(24) 75 | 76 | trace4 = phoebusgen.widget.XYPlotTrace() 77 | trace4.y_pv('sim://sinewave(0, 100, 110, 0, 0, 100)') 78 | trace4.x_pv('sim://sinewave(1, 100, 110, 0.1, 0, 100)') 79 | xy_plot3.add_trace(trace4) 80 | 81 | widgets.append(xy_plot3) 82 | 83 | my_screen.add_widget(widgets) 84 | my_screen.write_screen('./example_xyplot.bob') 85 | -------------------------------------------------------------------------------- /examples/exampleInPhoebus.bob: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generate Screen 4 | 5 | 25 6 | 7 | 8 | Label 9 | TITLE 10 | Generate Phoebus screen with Phoebusgen 11 | 0 12 | 0 13 | 680 14 | 31 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | true 24 | 25 | 26 | Action Button 27 | 28 | 29 | 39 | Execute Script 40 | 41 | 42 | loc://test 43 | Generate Screen 44 | 140 45 | 210 46 | 60 47 | 48 | 49 | Text Update 50 | loc://test<VLong>(2) 51 | 420 52 | 60 53 | 160 54 | 55 | 56 | Text Entry 57 | loc://test<VLong>(2) 58 | 240 59 | 60 60 | 160 61 | 62 | 63 | Label_1 64 | Number of local PVs to display 65 | 60 66 | 250 67 | 68 | 69 | Action Button_1 70 | 71 | 72 | ./generated.bob 73 | window 74 | Open Display 75 | 76 | 77 | loc://test 78 | Open Screen 79 | 250 80 | 140 81 | 210 82 | 60 83 | 84 | 85 | -------------------------------------------------------------------------------- /.docbuild/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | import sphinx_rtd_theme 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'phoebusgen' 22 | copyright = '2022, Lawrence Berkeley National Laboratory' 23 | author = 'Tynan Ford' 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = '2.3.2' 27 | 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx_autodoc_typehints'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 43 | 44 | autodoc_default_options = { 45 | 'members': 'widgets, screen, widget, properties', 46 | 'private-members': 'widgets, screen', 47 | 'inherited-members': 'widgets', 48 | 'exclude-members': '_prettify, _add_action, _add_font_style, _add_rotation_step, _get_font_element, _add_mode, _add_horizontal_alignment, _add_vertical_alignment, _add_file_component, _add_resize_behavior, _add_state, _add_style' 49 | } 50 | 51 | 52 | # -- Options for HTML output ------------------------------------------------- 53 | 54 | # The theme to use for HTML and HTML Help pages. See the documentation for 55 | # a list of builtin themes. 56 | # 57 | #html_theme = 'classic' 58 | html_theme = 'sphinx_rtd_theme' 59 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 60 | 61 | # Add any paths that contain custom static files (such as style sheets) here, 62 | # relative to this directory. They are copied after the builtin static files, 63 | # so a file named "default.css" will overwrite the builtin "default.css". 64 | html_static_path = ['_static'] 65 | -------------------------------------------------------------------------------- /tests/test_screen.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(1, '../phoebusgen/screen/') 3 | sys.path.insert(1, './phoebusgen/screen/') 4 | sys.path.insert(1, '../phoebusgen/widget/') 5 | sys.path.insert(1, './phoebusgen/widget/') 6 | sys.path.insert(1, '../phoebusgen/') 7 | sys.path.insert(1, './phoebusgen/') 8 | import unittest 9 | from unittest.mock import patch, mock_open 10 | import screen as s 11 | import widgets 12 | from phoebusgen import colors 13 | 14 | 15 | class TestScreen(unittest.TestCase): 16 | def setUp(self): 17 | self.name = 'test screen' 18 | self.file_name = './test.bob' 19 | self.test_screen = s.Screen(self.name, self.file_name) 20 | print(self.test_screen) 21 | 22 | def child_element_test(self, parent_tag, tag_name, value, attrib, do_not_remove=False): 23 | parent = self.test_screen.find_widget(parent_tag) 24 | self.assertIsNotNone(parent) 25 | child = parent.find(tag_name) 26 | self.assertIsNotNone(child) 27 | if value is None: 28 | self.assertIsNone(child.text) 29 | else: 30 | self.assertEqual(child.text, str(value)) 31 | self.assertEqual(child.attrib, attrib) 32 | 33 | def test_screen_write(self): 34 | test_screen_write = s.Screen('TEST', './test_write.bob') 35 | open_mock = mock_open() 36 | with patch('builtins.open', open_mock, create=True): 37 | test_screen_write.write_screen() 38 | 39 | open_mock.assert_called_with('./test_write.bob', 'w') 40 | self.assertEqual(open_mock.return_value.write.call_count, 13) 41 | 42 | def test_screen_blank(self): 43 | self.assertEqual(len(self.test_screen.root), 1) 44 | element = widgets.TextUpdate('Text Update 1', 'TEST:ME', 200, 200, 160, 20) 45 | self.test_screen.add_widget(element) 46 | self.assertEqual(len(self.test_screen.root), 2) 47 | element2 = widgets.TextUpdate('Text Update 2', 'TEST:ME', 200, 200, 160, 20) 48 | element3 = widgets.TextUpdate('Text Update 3', 'TEST:ME', 200, 200, 160, 20) 49 | self.test_screen.add_widget([element2, element3]) 50 | self.assertEqual(len(self.test_screen.root), 4) 51 | 52 | def test_width(self): 53 | self.test_screen.width(500) 54 | elem = self.test_screen.find_widget('width') 55 | self.assertIsNotNone(elem) 56 | self.assertEqual(str(500), elem.text) 57 | 58 | def test_height(self): 59 | self.test_screen.height(102.2) 60 | elem = self.test_screen.find_widget('height') 61 | self.assertIsNotNone(elem) 62 | self.assertEqual(str(102.2), elem.text) 63 | 64 | def test_macro(self): 65 | self.test_screen.macro('test', 'mac1') 66 | self.child_element_test('macros', 'test', 'mac1', {}, True) 67 | self.test_screen.macro('test2', 'mac2') 68 | self.child_element_test('macros', 'test', 'mac1', {}, True) 69 | 70 | def test_predefined_background_color(self): 71 | tag_name = 'background_color' 72 | self.test_screen.predefined_background_color(colors.MINOR) 73 | self.child_element_test(tag_name, 'color', None, {'name': 'MINOR', 'red': '255', 'green': '128', 'blue': '0', 'alpha': '255'}) 74 | 75 | def test_background_color(self): 76 | tag_name = 'background_color' 77 | self.test_screen.background_color(5, 10, 15) 78 | self.child_element_test(tag_name, 'color', None, {'red': '5', 'green': '10', 'blue': '15', 'alpha': '255'}) 79 | 80 | 81 | if __name__ == '__main__': 82 | unittest.main() 83 | -------------------------------------------------------------------------------- /phoebusgen/config/classes.bcf: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget Classes 4 | 5 | TITLE 6 | TITLE 7 | TITLE 8 | 0 9 | 0 10 | 620 11 | 30 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | COMMENT 24 | COMMENT 25 | 60 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | true 35 | true 36 | 37 | 38 | ON_OFF 39 | 120 40 | 150 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | WARNING 52 | 120 53 | 190 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | SECTION 65 | SECTION 66 | 30 67 | 120 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | true 77 | 78 | 79 | ON_OFF 80 | 150 81 | 120 82 | 83 | 84 | 85 | 86 | 87 | 88 | WARNING 89 | 190 90 | 120 91 | 92 | 93 | 94 | 95 | 96 | 97 | This file defines widget classes. 98 | 99 | The 'name' of each widget defines the class. 100 | Properties are marked to be included in the class definition. 101 | 300 102 | 20 103 | 310 104 | 150 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /phoebusgen/screen/screen.py: -------------------------------------------------------------------------------- 1 | from xml.etree.ElementTree import Element, SubElement, tostring 2 | from xml.dom import minidom 3 | from phoebusgen._shared_property_helpers import _SharedPropertyFunctions 4 | from typing import Union 5 | 6 | 7 | def prettify(elem): 8 | """Return a pretty-printed XML string for the Element. 9 | From: https://pymotw.com/3/xml.etree.ElementTree/create.html 10 | """ 11 | rough_string = tostring(elem, 'utf-8') 12 | reparse_xml = minidom.parseString(rough_string) 13 | return reparse_xml.toprettyxml(indent=' ', newl='\n') 14 | 15 | 16 | class Screen(object): 17 | """ Phoebus Screen object that holds widgets and can be written to .bob file """ 18 | def __init__(self, name: str, f_name: str = None) -> None: 19 | """ 20 | Create Phoebus screen object. File name is optional and can be specified later 21 | 22 | :param name: Screen Name 23 | :param f_name: File name for Phoebus screen 24 | """ 25 | self.bob_file = f_name 26 | self.root = Element('display', version='2.0.0') 27 | name_child = SubElement(self.root, 'name') 28 | name_child.text = name 29 | self._shared = _SharedPropertyFunctions(self.root) 30 | 31 | def write_screen(self, file_name: str = None) -> bool: 32 | """ 33 | Writes screen XML to file. File name parameter is optional, if not given Screen bob_file member will be used 34 | 35 | :param file_name: File name to write to 36 | :return: True is successful write, False otherwise 37 | """ 38 | rough_string = tostring(self.root, 'utf-8') 39 | reparse_xml = minidom.parseString(rough_string) 40 | if file_name is None: 41 | if self.bob_file is None: 42 | print('Output Phoebus file name is not set! First set bob_file or use file_name parameter') 43 | return False 44 | file_name = self.bob_file 45 | with open(file_name, 'w') as f: 46 | reparse_xml.writexml(f, indent=' ', addindent=' ', newl='\n', encoding='UTF-8') 47 | return True 48 | 49 | def find_widget(self, widget_tag_name: str) -> Element: 50 | """ 51 | Find widget in the screen 52 | 53 | :param widget_tag_name: Tag name of widget to find 54 | :return: None if not found or widget that was found 55 | """ 56 | elements = self.root.findall(widget_tag_name) 57 | if len(elements) > 1: 58 | print('Warning, more than one element of the same tag! Returning a list') 59 | return elements 60 | elif len(elements) == 0: 61 | return None 62 | else: 63 | return elements[0] 64 | 65 | def add_widget(self, elem: Union[list, object]) -> None: 66 | """ 67 | Add widget or list of widgets to screen 68 | 69 | :param elem: List of Phoebusgen.widget's or a single widget to add 70 | """ 71 | if isinstance(elem, list): 72 | for e in elem: 73 | self.root.append(e.root) 74 | else: 75 | self.root.append(elem.root) 76 | 77 | def width(self, val: int) -> None: 78 | """ 79 | Change width of screen 80 | 81 | :param val: Screen width 82 | """ 83 | self._shared.number_property(self.root, 'width', val) 84 | 85 | def height(self, val: int) -> None: 86 | """ 87 | Change height of screen 88 | 89 | :param val: Screen height 90 | """ 91 | self._shared.number_property(self.root, 'height', val) 92 | 93 | def macro(self, name: str, val: Union[str, int, float]) -> None: 94 | """ 95 | Add macro to screen 96 | 97 | :param name: Macro name 98 | :param val: Macro value 99 | """ 100 | self._shared.add_macro(name, val) 101 | 102 | def background_color(self, red: int, green: int, blue: int, alpha: int = 255) -> None: 103 | """ 104 | Add background color to screen RGB values 105 | 106 | :param red: 0-255 107 | :param green: 0-255 108 | :param blue: 0-255 109 | :param alpha: 0-255. Default is 255 110 | """ 111 | e = self._shared.create_element(self.root, 'background_color') 112 | self._shared.create_color_element(e, None, red, green, blue, alpha) 113 | 114 | def predefined_background_color(self, name: object) -> None: 115 | """ 116 | Add named background color to screen 117 | 118 | :param name: Predefined color name 119 | """ 120 | e = self._shared.create_element(self.root, 'background_color') 121 | self._shared.create_color_element(e, name, None, None, None, None) 122 | 123 | def __str__(self): 124 | return prettify(self.root) 125 | 126 | def __repr__(self): 127 | return prettify(self.root) 128 | -------------------------------------------------------------------------------- /phoebusgen/__init__.py: -------------------------------------------------------------------------------- 1 | """ phoebusgen Module 2 | 3 | This module contains a Python class representation of a Phoebus screen and all widgets. 4 | With the module, you can create Python scripts to write Phoebus screens to a .bob file. 5 | Almost all possible functionality should exist in Phoebusgen that you can do in the Display 6 | Builder Editor. 7 | 8 | In addition, phoebusgen.colors and phoebusgen.fonts are Enum objects that are available 9 | to use to add predefined colors/fonts to widgets. 10 | 11 | Example: text_update_widget.predefined_foreground_color(phoebusgen.colors.OK) 12 | 13 | A custom site specific color.def or font.def in ~/.phoebusgen/ to force phoebusgen.colors or phoebusgen.fonts 14 | to reflect your site's custom definitions. 15 | """ 16 | 17 | # Copyright (c) 2022 Lawrence Berkeley National Laboratory, 18 | # Advanced Light Source, Engineering Division 19 | 20 | import phoebusgen.widget 21 | import phoebusgen.screen 22 | 23 | from os import path as _path 24 | from sys import platform as _platform 25 | import re as _re 26 | from enum import Enum as _enum 27 | 28 | 29 | _curr_path = _path.dirname(__file__) 30 | _color_def = _curr_path + '/config/color.def' 31 | _font_def = _curr_path + '/config/font.def' 32 | _classes_bcf = _curr_path + '/config/classes.bcf' 33 | _local_color_def = _path.expanduser('~/.phoebusgen/color.def') 34 | _local_font_def = _path.expanduser('~/.phoebusgen/font.def') 35 | 36 | phoebus_version = '4.7.3' 37 | _version_def = _curr_path + '/config/' + phoebus_version + '_widgets.def' 38 | _widget_version_def = _path.expanduser('~/.phoebusgen/widgets.def') # highest priority 39 | _local_version_def = _path.expanduser('~/.phoebusgen/' + phoebus_version + '_widgets.def') 40 | widget_versions = {} 41 | 42 | if _path.isfile(_local_color_def): 43 | _color_def = _local_color_def 44 | if _path.isfile(_local_font_def): 45 | _font_def = _local_font_def 46 | if _path.isfile(_widget_version_def): 47 | _version_def = _widget_version_def 48 | elif _path.isfile(_local_version_def): 49 | _version_def = _local_version_def 50 | 51 | def _update_color_def(file_path): 52 | #print('Using color.def file at: {}'.format(file_path)) 53 | predefined_colors = {} 54 | if not _path.isfile(file_path): 55 | print('File at this path does not exist: {}'.format(file_path)) 56 | with open(file_path, 'r') as color_file: 57 | for line in color_file: 58 | line = line.partition('#')[0].rstrip() 59 | if line != '': 60 | color, value = line.split('=') 61 | color = color.strip() 62 | vals = [v.strip() for v in value.split(',')] 63 | if len(vals) == 1: 64 | predefined_colors[color] = predefined_colors[vals[0]] 65 | else: 66 | if len(vals) == 4: 67 | alpha = vals[3] 68 | else: 69 | alpha = 255 70 | predefined_colors[color] = {'name': color, 'red': str(vals[0]), 'green': str(vals[1]), 71 | 'blue': str(vals[2]), 'alpha': str(alpha)} 72 | return predefined_colors 73 | 74 | def _update_font_def(file_path): 75 | if not _path.isfile(file_path): 76 | print('File at this path does not exist: {}'.format(file_path)) 77 | with open(file_path, 'r') as font_file: 78 | predefined_fonts = {} 79 | os = _platform.lower() 80 | if 'linux' in os: 81 | os = 'linux' 82 | elif 'darwin' in os: 83 | os = 'macosx' 84 | elif 'win' in os: 85 | os = 'windows' 86 | for line in font_file: 87 | line = line.partition('//')[0].rstrip() 88 | if line != '': 89 | font, value = line.split('=') 90 | font = font.strip() 91 | os_name = _re.search(r'\(([^)]+)\)', font) 92 | if os_name: 93 | os_name = os_name.group(1) 94 | if os_name != os: 95 | continue 96 | else: 97 | font = font.replace('(', '') 98 | font = font.replace(')', '') 99 | font = font.replace(os, '') 100 | vals = [v.strip() for v in value.split('-')] 101 | if len(vals) == 1: 102 | predefined_fonts[font] = predefined_fonts[vals[0].strip('@')] 103 | else: 104 | family = vals[0] 105 | style = vals[1] 106 | size = vals[2] 107 | if style.lower() == 'regular': 108 | style = 'REGULAR' 109 | elif style.lower() == 'bold': 110 | style = 'BOLD' 111 | elif style.lower() == 'italic': 112 | style = 'ITALIC' 113 | elif style.lower() == 'bold italic': 114 | style = 'BOLD_ITALIC' 115 | predefined_fonts[font.replace(' ', '')] = {'name': font, 'family': family, 'style': style, 'size': size} 116 | return predefined_fonts 117 | 118 | def _update_version_def(file_path): # modifies widget_version dict in place 119 | if not _path.isfile(file_path): 120 | print('File at this path does not exist: {}'.format(file_path)) 121 | return 122 | with open(file_path, 'r') as version_file: 123 | for line in version_file: 124 | line = line.partition('#')[0].rstrip() 125 | if line != '': 126 | widget, version = line.split('=') 127 | widget = widget.strip() 128 | widget_versions[widget] = version.strip() 129 | 130 | def change_phoebus_version(version): 131 | global phoebus_version 132 | global _versions 133 | phoebus_version = version 134 | _version_def = _curr_path + '/config/' + phoebus_version + '_widgets.def' 135 | 136 | if _path.isfile(_widget_version_def): 137 | _version_def = _widget_version_def 138 | elif _path.isfile(_local_version_def): 139 | _version_def = _local_version_def 140 | _update_version_def(_version_def) # modifies widget_versions 141 | _versions = _enum('_versions', widget_versions) 142 | print('Phoebus version manually changed to ' + version + '.') 143 | 144 | _predefined_colors = _update_color_def(_color_def) 145 | colors = _enum('colors', _predefined_colors) 146 | _predefined_fonts = _update_font_def(_font_def) 147 | fonts = _enum('fonts', _predefined_fonts) 148 | _update_version_def(_version_def) # sets widget_versions 149 | _versions = _enum('_versions', widget_versions) 150 | 151 | try: 152 | from ._version import version as __version__ 153 | from ._version import version_tuple as __version_tuple__ 154 | except ImportError: 155 | __version__ = 'unknown version' 156 | __version_tuple__ = (0, 0, 'unknown version') 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Module to Generate Phoebus Control Screens 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/phoebusgen)](https://pypi.org/project/phoebusgen/) 4 | [![Python unittest Status](https://github.com/als-epics/phoebusgen/workflows/Python%20unittest/badge.svg)](https://github.com/als-epics/phoebusgen/actions) 5 | [![codecov](https://codecov.io/gh/als-epics/phoebusgen/branch/master/graph/badge.svg?token=Ue2BauI8IW)](https://codecov.io/gh/als-epics/phoebusgen) 6 | [![Build Docs](https://github.com/als-epics/phoebusgen/actions/workflows/build-docs.yml/badge.svg)](https://github.com/als-epics/phoebusgen/actions/workflows/build-docs.yml) 7 | [![Upload Python Package](https://github.com/als-epics/phoebusgen/actions/workflows/python-publish.yml/badge.svg)](https://github.com/als-epics/phoebusgen/actions/workflows/python-publish.yml) 8 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e16f9c35657f47fcb31347fbd4f92367)](https://www.codacy.com/gh/als-epics/phoebusgen/dashboard?utm_source=github.com&utm_medium=referral&utm_content=als-epics/phoebusgen&utm_campaign=Badge_Grade) 9 | 10 | Phoebus is the next generation of Control System Studio, a graphical platform for EPICS control systems. 11 | https://github.com/ControlSystemStudio/phoebus 12 | 13 | This module provides a way through Python to generate the Display Builder XML format used by Phoebus (and other tools like DBWR). See examples [here](examples). 14 | 15 | API docs here: [https://als-epics.github.io/phoebusgen](https://als-epics.github.io/phoebusgen/) 16 | 17 | Suggestions, comments, and pull requests are welcome. 18 | 19 | ## Requirements 20 | 21 | - Python >= 3.6 22 | 23 | ## Install 24 | Pip Package: [phoebusgen](https://pypi.org/project/phoebusgen/) 25 | ```shell 26 | pip install phoebusgen 27 | ``` 28 | 29 | ## Intro 30 | 31 | Phoebus widgets and a Phoebus screen are all Python objects. Widgets can be added to a screen or even to other widgets (for things like Group or Tab widgets). 32 | 33 | ```pycon 34 | >>> import phoebusgen 35 | >>> text_update_widget = phoebusgen.widget.TextUpdate('test widget', 'TEST:PV', 10, 20, 20, 50) 36 | >>> text_update_widget.predefined_foreground_color(phoebusgen.colors.OK) 37 | >>> text_update_widget.font_style_bold() 38 | >>> print(text_update_widget) 39 | 40 | 41 | test widget 42 | 10 43 | 20 44 | 20 45 | 50 46 | TEST:PV 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ``` 56 | 57 | ## Modules 58 | 59 | ### phoebusgen.widget 60 | 61 | Python API to directly create Phoebus widgets. All standard Phoebus widgets and their specific properties are available as of version 3.0.0 62 | 63 | [Widgets Docs](https://als-epics.github.io/phoebusgen/source/phoebusgen.widget.html#module-phoebusgen.widget.widgets) 64 | 65 | Example 66 | - ```text_update_xml = phoebusgen.widget.TextUpdate(widget_name, pv_name, x, y, width, height)``` 67 | 68 | ### phoebusgen.screen 69 | 70 | Python object to represent a Phoebus screen. Widgets can be added to the screen object and the screen object can be written to a .bob file to be opened in Phoebus. 71 | 72 | [Screen Docs](https://als-epics.github.io/phoebusgen/source/phoebusgen.screen.html#module-phoebusgen.screen.screen) 73 | 74 | Example 75 | ```pycon 76 | >>> import phoebusgen.screen 77 | >>> import phoebusgen.widget 78 | >>> my_screen = phoebusgen.screen.Screen("my screen") 79 | >>> print(my_screen) 80 | 81 | 82 | my screen 83 | 84 | 85 | >>> my_widget = phoebusgen.widget.TextUpdate("test", "test:PV", 10, 10 ,10 ,10) 86 | >>> my_screen.add_widget(my_widget) 87 | >>> print(my_screen) 88 | 89 | 90 | my screen 91 | 92 | test 93 | 10 94 | 10 95 | 10 96 | 10 97 | test:PV 98 | 99 | 100 | ``` 101 | 102 | ## Phoebus Version Support 103 | 104 | In some cases, the XML definitions for a widget can differ between CS Studio Phoebus versions. With phoebusgen 3.0.0 and above, 105 | versioning is supported via several methods. 106 | 107 | phoebusgen will default to use the latest Phoebus released version. At this time for example: 108 | 109 | ```python 110 | >>> import phoebusgen 111 | >>> phoebusgen.phoebus_version 112 | '4.7.3' 113 | >>> phoebusgen.widget_versions 114 | {'arc': '2.0.0', 'ellipse': '2.0.0', 'label': '2.0.0', 'picture': '2.0.0', 'polygon': '2.0.0', 'polyline': '2.0.0', 115 | 'rectangle': '2.0.0', 'byte_monitor': '2.0.0', 'led': '2.0.0', 'multi_state_led': '2.0.0', 'meter': '3.0.0', 116 | 'progressbar': '2.0.0', 'symbol': '2.0.0', 'table': '2.0.0', 'tank': '2.0.0', 'text-symbol': '2.0.0', 117 | 'textupdate': '2.0.0', 'thermometer': '2.0.0', 'action_button': '2.0.0', 'bool_button': '2.0.0', 'checkbox': '2.0.0', 118 | 'choice': '2.0.0', 'combo': '2.0.0', 'fileselector': '2.0.0', 'radio': '2.0.0', 'scaledslider': '2.0.0', 119 | 'scrollbar': '2.0.0', 'slide_button': '2.0.0', 'spinner': '2.0.0', 'textentry': '2.0.0', 'thumbwheel': '2.0.0', 120 | 'databrowser': '2.0.0', 'image': '2.0.0', 'stripchart': '2.1.0', 'xyplot': '3.0.0', 'array': '2.0.0', 'tabs': '2.0.0', 121 | 'embedded': '2.0.0', 'group': '3.0.0', 'navtabs': '2.0.0', 'template': '2.0.0', '3dviewer': '2.0.0', 122 | 'webbrowser': '2.0.0'} 123 | ``` 124 | 125 | Here are several ways to change the widget versioning: 126 | 127 | - Call the change_phoebus_version method. Currently version 4.7, 4.7.1, 4.7.2, and 4.7.3 are supported. This will update 128 | all the widget version to what they were in that specific release of phoebus. 129 | - `phoebusgen.change_phoebus_version("4.7.2")` 130 | - Add your own version definition file (overrides any other def files in ~/.phoebusgen) 131 | - `~/.phoebusgen/widgets.def` 132 | - Add your own version definition file that matches `phoebusgen.phoebus_version` 133 | - `~/.phoebusgen/4.7.2_widgets.def` 134 | - Change the version on an individual widget object 135 | - `my_group = phoebusgen.widget.Group("test", 2,2,2,2); my_group.version("2.0.0")` 136 | 137 | See the available versions supported in phoebusgen here: [config directory](./phoebusgen/config) 138 | 139 | ```python 140 | >>> import phoebusgen 141 | >>> phoebusgen.phoebus_version 142 | '4.7.3' 143 | >>> phoebusgen.change_phoebus_version("4.7.2") 144 | Phoebus version manually changed to 4.7.2. 145 | >>> b = phoebusgen.widget.Group("test", 2,2,2,2) 146 | >>> b 147 | 148 | 149 | test 150 | 2 151 | 2 152 | 2 153 | 2 154 | 155 | >>> b.predefined_line_color("OK") 156 | Line color not compatible with group widget version less than 3.0.0. 157 | ``` 158 | 159 | ## Site specific color and font definitions 160 | 161 | Place a custom `color.def` or `font.def` in `~/.phoebusgen/` to force phoebusgen.colors or phoebusgen.fonts to reflect your site's custom definitions. 162 | 163 | ```python 164 | my_widget.predefined_font(phoebusgen.fonts.Header1) 165 | ``` 166 | ```python 167 | my_widget.predefined_color(phoebusgen.colors.OK) 168 | ``` 169 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.1.0] - 2025-08-13 9 | 10 | ## Added 11 | 12 | - horizontal and vertical alignment to action button, boolean button, choice button 13 | 14 | ## [3.0.0] - 2024-09-08 15 | 16 | ### Added 17 | 18 | - Widget versioning support via the change_phoebus_version method and optional files in ~/.phoebusgen 19 | - More tests 20 | 21 | ### Changed 22 | 23 | - line_color and predefined_line_color to check the version for the Group widget 24 | 25 | ### Fixed 26 | 27 | - Missing Color property on the XY Plot widget 28 | 29 | ## [2.8.0] - 2024-09-08 30 | 31 | ### Added 32 | 33 | - Full property support for image widget 34 | - Full property support for strip chart widget 35 | - Full property support for XY plot widget 36 | - This CHANGELOG file to hopefully serve as an evolving example of a 37 | standardized open source project CHANGELOG. 38 | 39 | ### Changed 40 | 41 | - Widget base class now inherits from a shared Generic class which is also 42 | used for new classes StripChartTrace, XYPlotTrace, StripChartYAxis, etc. 43 | - Switched from versioneer to setuptools_scm for package versioning 44 | - Switched from setup.py to pyproject.toml 45 | 46 | ### Removed 47 | 48 | - setup.py and setup.cfg files in favor of pyproject.toml 49 | 50 | ### Fixed 51 | 52 | - Bug in symbols property, only could have one symbol previously 53 | - Missing line_color support for group widget since phoebus v4.7.3 54 | 55 | ## [2.7.0] - 2023-09-18 56 | 57 | ### Added 58 | 59 | - Ability to use color as value in rules 60 | 61 | ### Fixed 62 | 63 | - Bug in items property, only could have one item previously 64 | 65 | ## [2.6.2] - 2023-08-01 66 | 67 | ### Fixed 68 | 69 | - Bug in confirmation dialog for action button 70 | 71 | ## [2.6.1] - 2023-06-07 72 | 73 | ### Added 74 | 75 | - Execute script methods for the action button widget 76 | 77 | ## [2.6.0] - 2023-06-05 78 | 79 | ### Added 80 | 81 | - Alarm border property to text update widget 82 | 83 | ## [2.5.1] - 2023-04-13 84 | 85 | ### Changed 86 | 87 | - Confirmed github workflow for pip package upload 88 | 89 | ## [2.5.0] - 2023-04-13 90 | 91 | ### Fixed 92 | 93 | - Version tag should be x.x.x 94 | 95 | ## [2.5] - 2023-04-13 96 | 97 | ### Added 98 | 99 | - precommit to repository 100 | 101 | ### Changed 102 | 103 | - Documentation to readthedocs 104 | 105 | ### Fixed 106 | 107 | - Bug in rule support, convert non-string inputs to strings 108 | 109 | ## [2.4.5] - 2022-07-24 110 | 111 | ### Added 112 | 113 | - Rule and script support for all widgets 114 | 115 | ## [2.4.4] - 2022-06-27 116 | 117 | ### Added 118 | 119 | - Codacy checks to repository 120 | 121 | ### Changed 122 | 123 | - Use isinstance instead of == for type checks 124 | 125 | ### Removed 126 | 127 | - Unused test file 128 | 129 | ## [2.4.3] - 2022-06-10 130 | 131 | ### Added 132 | 133 | - Alarm border to action button widget 134 | 135 | ## [2.4.2] - 2022-05-27 136 | 137 | ### Added 138 | 139 | - Horizontal and vertical alignment to text entry widget 140 | 141 | ## [2.4.1] - 2022-05-21 142 | 143 | ### Added 144 | 145 | - Interpolation, and cursor support to image widget 146 | - Grid color support to XY plot widget 147 | 148 | ## [2.4] - 2022-05-21 149 | 150 | ### Added 151 | 152 | - title_font, scale_font, and label_font properties 153 | 154 | ### Changed 155 | 156 | - Moved shared font functions to shared file to help with other font properties 157 | 158 | ### Fixed 159 | 160 | - Bug in predefined font logic, removed the .lower() call on the name string 161 | 162 | ## [2.3.6] - 2022-05-20 163 | 164 | ### Added 165 | 166 | - time_range property to StripChart widget 167 | 168 | ## [2.3.5] - 2022-05-19 169 | 170 | ### Fixed 171 | 172 | - Github Action for publishing pip package 173 | 174 | ## [2.3.4] - 2022-05-19 175 | 176 | ### Added 177 | 178 | - Build sphinx docs Github Action 179 | 180 | ## [2.3.3] - 2022-05-19 181 | 182 | ### Added 183 | 184 | - Versioneer support 185 | - Github Action to publish pip package 186 | - Python 3.11 to CI 187 | 188 | ## [2.3.2] - 2021-10-01 189 | 190 | ### Fixed 191 | 192 | - Fix bug in title property where it was thought to be specific to StripChart, 193 | changed class name and added to XY plot as well 194 | 195 | ## [2.3.1] - 2021-09-30 196 | 197 | ### Added 198 | 199 | - More documentation 200 | 201 | ## [2.3.0] - 2021-09-30 202 | 203 | ### Added 204 | 205 | - Full property support for table widget 206 | - Sphinx documentation 207 | 208 | ## [2.2.0] - 2021-09-28 209 | 210 | ### Added 211 | 212 | - Full property support for LED multi state widget 213 | - Full property support for symbol widget 214 | - Full property support for text symbol widget 215 | - Full property support for scaled slider widget 216 | 217 | ## [2.1.0] - 2021-09-28 218 | 219 | ### Added 220 | 221 | - Full property support for byte monitor widget 222 | - Full property support for navigation tab widget 223 | - Full property support for tab widget 224 | 225 | ## [2.0.0] - 2021-09-28 226 | 227 | ### Added 228 | 229 | - Polygon widget 230 | - Polyline widget 231 | 232 | ### Changed 233 | 234 | - Refactored the widget property classes to contain the logic for adding 235 | XML inside the properties.py file (used to be called property_stubs). 236 | 237 | ### Removed 238 | 239 | - Removed the Property class and private classes in property_stubs.py in favor 240 | of using classes for each property that are inherited by the widgets that 241 | use them. 242 | 243 | ## [1.0.1] - 2021-09-23 244 | 245 | ### Added 246 | 247 | - Macros for open display action 248 | 249 | ## [1.0.0] - 2021-08-25 250 | 251 | ### Added 252 | 253 | - Support for most widgets but not all properties on all widgets 254 | 255 | [unreleased]: https://github.com/als-epics/phoebusgen/compare/3.0.0...HEAD 256 | [3.0.0]: https://github.com/als-epics/phoebusgen/compare/2.8.0...3.0.0 257 | [2.8.0]: https://github.com/als-epics/phoebusgen/compare/2.7.0...2.8.0 258 | [2.7.0]: https://github.com/als-epics/phoebusgen/compare/2.6.2...2.7.0 259 | [2.6.2]: https://github.com/als-epics/phoebusgen/compare/2.6.1...2.6.2 260 | [2.6.1]: https://github.com/als-epics/phoebusgen/compare/2.6.0...2.6.1 261 | [2.6.0]: https://github.com/als-epics/phoebusgen/compare/2.5.1...2.6.0 262 | [2.5.1]: https://github.com/als-epics/phoebusgen/compare/2.5.0...2.5.1 263 | [2.5.0]: https://github.com/als-epics/phoebusgen/compare/2.5...2.5.0 264 | [2.5]: https://github.com/als-epics/phoebusgen/compare/2.4.5...2.5 265 | [2.4.5]: https://github.com/als-epics/phoebusgen/compare/2.4.4...2.4.5 266 | [2.4.4]: https://github.com/als-epics/phoebusgen/compare/2.4.3...2.4.4 267 | [2.4.3]: https://github.com/als-epics/phoebusgen/compare/2.4.2...2.4.3 268 | [2.4.2]: https://github.com/als-epics/phoebusgen/compare/2.4.1...2.4.2 269 | [2.4.1]: https://github.com/als-epics/phoebusgen/compare/2.4...2.4.1 270 | [2.4]: https://github.com/als-epics/phoebusgen/compare/2.3.6...2.4 271 | [2.3.6]: https://github.com/als-epics/phoebusgen/compare/2.3.5...2.3.6 272 | [2.3.5]: https://github.com/als-epics/phoebusgen/compare/2.3.4...2.3.5 273 | [2.3.4]: https://github.com/als-epics/phoebusgen/compare/2.3.3...2.3.4 274 | [2.3.3]: https://github.com/als-epics/phoebusgen/compare/2.3.2...2.3.3 275 | [2.3.2]: https://github.com/als-epics/phoebusgen/compare/2.3.1...2.3.2 276 | [2.3.1]: https://github.com/als-epics/phoebusgen/compare/2.3.0...2.3.1 277 | [2.3.0]: https://github.com/als-epics/phoebusgen/compare/2.2.0...2.3.0 278 | [2.2.0]: https://github.com/als-epics/phoebusgen/compare/2.1.0...2.2.0 279 | [2.1.0]: https://github.com/als-epics/phoebusgen/compare/2.0.0...2.1.0 280 | [2.0.0]: https://github.com/als-epics/phoebusgen/compare/1.0.1...2.0.0 281 | [1.0.1]: https://github.com/als-epics/phoebusgen/compare/1.0.0...1.0.1 282 | [1.0.0]: https://github.com/als-epics/phoebusgen/releases/tag/1.0.0 283 | -------------------------------------------------------------------------------- /phoebusgen/_shared_property_helpers.py: -------------------------------------------------------------------------------- 1 | from xml.etree.ElementTree import Element, SubElement 2 | from enum import Enum 3 | 4 | class _SharedPropertyFunctions(object): 5 | def __init__(self, root_element): 6 | self.root = root_element 7 | from phoebusgen import colors, _predefined_colors, fonts, _predefined_fonts, widget_versions, _versions 8 | self.predefined_colors = _predefined_colors 9 | self.predefined_fonts = _predefined_fonts 10 | self.colors = colors 11 | self.fonts = fonts 12 | self.versions = _versions 13 | self.widget_versions = widget_versions 14 | self.arrow_types = {'None': 0, 'From': 1, 'To': 2, 'Both': 3} 15 | self.formats_array = ['default', 'decimal', 'exponential', 'engineering', 'hexadecimal', 16 | 'compact', 'string', 'sexagesimal hh:mm:ss', 'sexagesimal hms 24h rad', 17 | 'sexagesimal dms 360deg rad', 'binary'] 18 | 19 | def add_macro(self, name, val, root_elem=None): 20 | if root_elem is None: 21 | root_elem = self.root 22 | root_macro = root_elem.find('macros') 23 | if root_macro is None: 24 | root_macro = SubElement(root_elem, 'macros') 25 | macro = SubElement(root_macro, name) 26 | macro.text = str(val) 27 | 28 | def generic_property(self, root_element, prop_type, val=None): 29 | root_element.append(self.create_element(root_element, prop_type, val)) 30 | 31 | def list_property(self, root_element, prop_type, val=None): 32 | root_element.append(self.create_list_element(prop_type, val)) 33 | 34 | def integer_property(self, root_element, prop_type, val): 35 | if isinstance(val, int) or isinstance(val, float): 36 | self.generic_property(root_element, prop_type, int(val)) 37 | else: 38 | print('Property {} must be an integer! Not: {}'.format(prop_type, val)) 39 | 40 | def number_property(self, root_element, prop_type, val): 41 | if isinstance(val, int) or isinstance(val, float): 42 | self.generic_property(root_element, prop_type, val) 43 | else: 44 | print('Property {} must be a number! Not: {}'.format(prop_type, val)) 45 | 46 | def boolean_property(self, root_element, prop_type, val): 47 | if isinstance(val, bool): 48 | self.generic_property(root_element, prop_type, str(val).lower()) 49 | elif isinstance(val, int): 50 | self.generic_property(root_element, prop_type, str(bool(val)).lower()) 51 | elif val.lower() == 'true' or val.lower() == 'false': 52 | self.generic_property(root_element, prop_type, val.lower()) 53 | else: 54 | print('Property {} must be a boolean value! Not: {}'.format(prop_type, val)) 55 | 56 | def create_element(self, root_element, prop_type, val=None): 57 | element = root_element.find(prop_type) 58 | if element is not None: 59 | root_element.remove(element) 60 | element = Element(prop_type) 61 | if val is not None: 62 | if isinstance(val, bool): 63 | element.text = str(val).lower() 64 | else: 65 | element.text = str(val) 66 | return element 67 | 68 | def create_list_element(self, prop_type, val=None): 69 | element = Element(prop_type) 70 | if val is not None: 71 | if isinstance(val, bool): 72 | element.text = str(val).lower() 73 | else: 74 | element.text = str(val) 75 | return element 76 | 77 | def valid_rgb_value(self, val): 78 | try: 79 | val = int(val) 80 | except ValueError: 81 | print('Color RGB value must be a number! Not: {}'.format(val)) 82 | return False 83 | if 0 <= val <= 255: 84 | return True 85 | else: 86 | print('Color RGB must be between 0 and 255') 87 | return False 88 | 89 | def create_color_element(self, root_color_elem, name, red, green, blue, alpha, add_to_root=True): 90 | sub_e = self.create_element(self.root, 'color') 91 | if name is None: 92 | for color in [red, green, blue, alpha]: 93 | if not self.valid_rgb_value(color): 94 | return 95 | sub_e.attrib = {'red': str(red), 'blue': str(blue), 'green': str(green), 'alpha': str(alpha)} 96 | else: 97 | if isinstance(name, Enum): 98 | sub_e.attrib['name'] = name.name 99 | sub_e.attrib = name.value 100 | elif isinstance(name, dict): 101 | sub_e.attrib = name 102 | elif isinstance(name, str): 103 | color_attrib = self.predefined_colors.get(name) 104 | if color_attrib is None: 105 | print('Color name is undefined') 106 | return 107 | sub_e.attrib = color_attrib 108 | sub_e.attrib['name'] = name 109 | else: 110 | print('Predefined color input must be phoebusgen.colors., not: {} of type: {}'.format(name, type(name))) 111 | return 112 | root_color_elem.append(sub_e) 113 | if add_to_root: 114 | self.root.append(root_color_elem) 115 | 116 | def get_font_element(self, root_elem, font_elem_name): 117 | font_root_elem = root_elem.find(font_elem_name) 118 | if font_root_elem is None: 119 | font_root_elem = self.create_element(root_elem, font_elem_name) 120 | root_elem.append(font_root_elem) 121 | child_font_elem = font_root_elem.find('font') 122 | if child_font_elem is None: 123 | child_font_elem = Element('font') 124 | child_font_elem.attrib = {'family': 'Liberation Sans', 'size': '14', 'style': 'REGULAR'} 125 | font_root_elem.append(child_font_elem) 126 | return child_font_elem 127 | 128 | def add_font_style(self, root_elem, font_elem_name, val): 129 | if not isinstance(val, self.FontStyle): 130 | print('The font style parameter must be of type FontStyle enum! Not: {}'.format(type(val))) 131 | return 132 | child_elem = self.get_font_element(root_elem, font_elem_name) 133 | child_elem.attrib['style'] = val.value 134 | 135 | def create_named_font_element(self, root_elem, font_elem_name, name): 136 | root_font_elem = self.create_element(root_elem, font_elem_name) 137 | child_font_elem = self.create_element(root_font_elem, 'font') 138 | if isinstance(name, Enum): 139 | font_attrib = name.value 140 | elif isinstance(name, dict): 141 | font_attrib = name 142 | elif isinstance(name, str): 143 | font_attrib = self.predefined_fonts.get(name) 144 | if font_attrib is None: 145 | print('Font name is undefined') 146 | return 147 | font_attrib['style'] = font_attrib['style'].upper() 148 | else: 149 | print('Predefined font input must be phoebusgen.fonts., not: {} of type: {}'.format(name, type(name))) 150 | return 151 | child_font_elem.attrib = font_attrib 152 | root_font_elem.append(child_font_elem) 153 | self.root.append(root_font_elem) 154 | 155 | class FontStyle(Enum): 156 | regular = 'REGULAR' 157 | italic = 'ITALIC' 158 | bold = 'BOLD' 159 | bold_and_italic = 'BOLD_ITALIC' 160 | 161 | class HorizontalAlignment(Enum): 162 | left = 0 163 | center = 1 164 | right = 2 165 | 166 | class VerticalAlignment(Enum): 167 | top = 0 168 | middle = 1 169 | bottom = 2 170 | 171 | class RotationStep(Enum): 172 | zero = 0 173 | ninety = 1 174 | one_hundred_eighty = 2 175 | negative_ninety = 3 176 | 177 | class Mode(Enum): 178 | toggle = 0 179 | push = 1 180 | push_inverted = 2 181 | 182 | class Interpolation(Enum): 183 | none = 0 184 | interpolate = 1 185 | automatic = 2 186 | 187 | class ColorMode(Enum): 188 | TYPE_CUSTOM = 0 189 | TYPE_MONO = 1 190 | TYPE_BAYER = 2 191 | TYPE_RGB1 = 3 192 | TYPE_RGB2 = 4 193 | TYPE_RGB3 = 5 194 | TYPE_YUV444 = 6 195 | TYPE_YUV422 = 7 196 | TYPE_YUV411 = 8 197 | TYPE_3BYTE_BGR = 9 198 | TYPE_4BYTE_ABGR = 10 199 | TYPE_4BYTE_ABGR_PRE = 11 200 | TYPE_BYTE_BINARY = 12 201 | TYPE_BYTE_GRAY = 13 202 | TYPE_BYTE_INDEXED = 14 203 | TYPE_INT_ARGB = 15 204 | TYPE_INT_ARGB_PRE = 16 205 | TYPE_INT_BGR = 17 206 | TYPE_INT_RGB = 18 207 | TYPE_USHORT_555_RGB = 19 208 | TYPE_USHORT_565_RGB = 20 209 | TYPE_USHORT_GRAY = 21 210 | 211 | class GroupStyle(Enum): 212 | group_box = 0 213 | title_bar = 1 214 | line = 2 215 | none = 3 216 | 217 | class Resize(Enum): 218 | no_resize = 0 219 | size_content_to_fit_widget = 1 220 | size_widget_to_match_content = 2 221 | stretch_content_to_fit_widget = 3 222 | crop_content = 4 223 | 224 | class FileComponent(Enum): 225 | full_path = 0 226 | directory = 1 227 | name_and_extension = 2 228 | base_name = 3 229 | 230 | class TraceType(Enum): 231 | none = 0 232 | line = 1 233 | step = 2 234 | err_bars = 3 235 | line_err_bars = 4 236 | bars = 5 237 | 238 | class LineStyle(Enum): 239 | solid = 0 240 | dashed = 1 241 | dot = 2 242 | dash_dot = 3 243 | dash_dot_dot = 4 244 | 245 | class PointType(Enum): 246 | none = 0 247 | squares = 1 248 | circles = 2 249 | diamonds = 3 250 | x = 4 251 | triangles = 5 252 | 253 | class ColorMap(Enum): 254 | viridis = 'VIRIDIS' 255 | grayscale = 'GRAY' 256 | jet = 'JET' 257 | color_spectrum = 'SPECTRUM' 258 | hot = 'HOT' 259 | cool = 'COOL' 260 | shaded = 'SHADED' 261 | magma = 'MAGMA' 262 | -------------------------------------------------------------------------------- /phoebusgen/widget/widget.py: -------------------------------------------------------------------------------- 1 | from xml.etree.ElementTree import Element, SubElement, tostring 2 | from xml.dom import minidom 3 | from phoebusgen._shared_property_helpers import _SharedPropertyFunctions 4 | 5 | 6 | def prettify(elem): 7 | """Return a pretty-printed XML string for the Element. 8 | From: https://pymotw.com/3/xml.etree.ElementTree/create.html 9 | """ 10 | rough_string = tostring(elem, 'utf-8') 11 | reparse_xml = minidom.parseString(rough_string) 12 | return reparse_xml.toprettyxml(indent=' ', newl='\n') 13 | 14 | class _Generic(object): 15 | def __init__(self, w_type: str) -> None: 16 | self.root = Element(w_type) 17 | self._shared = _SharedPropertyFunctions(self.root) 18 | 19 | def find_element(self, tag: str) -> Element: 20 | """ 21 | Find first XML element in widget by tag name 22 | 23 | :param tag: Tag name to search for 24 | :return: Return XML element or None if not found 25 | """ 26 | elements = self.root.findall(tag) 27 | # check to make sure there are not more than 1 elements 28 | # we don't want duplicate tags 29 | if len(elements) > 1: 30 | print('Warning, more than one element of the same tag! Returning a list') 31 | return elements 32 | elif len(elements) == 0: 33 | return None 34 | else: 35 | return elements[0] 36 | 37 | def remove_element(self, tag: str) -> None: 38 | """ 39 | Delete XML element in widget by tag name 40 | 41 | :param tag: Tag name to delete 42 | """ 43 | element = self.find_element(tag) 44 | if element is not None: 45 | self.root.remove(element) 46 | 47 | def get_element_value(self, tag: str) -> str: 48 | """ 49 | Get value of an XML element by tag name 50 | 51 | :param tag: Tag name to get value from 52 | :return: Value of XML tag 53 | """ 54 | return self.find_element(tag).text 55 | 56 | def visible(self, visible: bool) -> None: 57 | """ 58 | Change visible property for widget 59 | 60 | :param visible: Is widget visible? 61 | """ 62 | self._shared.boolean_property(self.root, 'visible', visible) 63 | 64 | def __str__(self): 65 | return prettify(self.root) 66 | 67 | def __repr__(self): 68 | return prettify(self.root) 69 | 70 | class _Widget(_Generic): 71 | """ Base Class for all Phoebus widgets """ 72 | def __init__(self, w_type: str, name: str, x_pos: int, y_pos: int, width: int, height: int) -> None: 73 | """ 74 | Base Class for all Phoebus widgets 75 | 76 | :param w_type: Widget type to be written into XML 77 | :param name: Widget name 78 | :param x: X position 79 | :param y: Y position 80 | :param width: Widget width 81 | :param height: Widget height: 82 | """ 83 | 84 | super().__init__('widget') 85 | self.root.attrib['type'] = w_type 86 | if w_type in self._shared.widget_versions: 87 | self.root.attrib['version'] = self._shared.widget_versions[w_type] 88 | else: 89 | self.root.attrib['version'] = '2.0.0' 90 | name_child = SubElement(self.root, 'name') 91 | name_child.text = name 92 | 93 | self._shared.integer_property(self.root, 'x', x_pos) 94 | self._shared.integer_property(self.root, 'y', y_pos) 95 | self._shared.integer_property(self.root, 'width', width) 96 | self._shared.integer_property(self.root, 'height', height) 97 | 98 | def version(self, version: str) -> None: 99 | """ 100 | Change widget version in root widget. i.e. 101 | 102 | :param version: Version string 103 | """ 104 | self.root.attrib['version'] = version 105 | 106 | def name(self, name: str) -> None: 107 | """ 108 | Change widget name 109 | 110 | :param name: Widget name 111 | """ 112 | self._shared.generic_property(self.root, 'name', name) 113 | 114 | def width(self, width: int) -> None: 115 | """ 116 | Change widget width 117 | 118 | :param width: Width 119 | """ 120 | self._shared.integer_property(self.root, 'width', width) 121 | 122 | def height(self, height: int) -> None: 123 | """ 124 | Change widget height 125 | 126 | :param height: height 127 | """ 128 | self._shared.integer_property(self.root, 'height', height) 129 | 130 | def x(self, val: int) -> None: 131 | """ 132 | Change widget x position 133 | 134 | :param val: x 135 | """ 136 | self._shared.integer_property(self.root, 'x', val) 137 | 138 | def y(self, val: int) -> None: 139 | """ 140 | Change widget y position 141 | 142 | :param val: y 143 | """ 144 | self._shared.integer_property(self.root, 'y', val) 145 | 146 | 147 | def rule(self, name: str, widget_property: str, pv_dict: dict, 148 | expression_dict: dict, value_as_expression: bool = False) -> None: 149 | """ 150 | Add a rule to the widget to control a property based on some logic 151 | 152 | :param name: Name of the rule 153 | :param widget_property: Property for rule to control, i.e. name, foreground_color, etc. 154 | :param pv_dict: Dictionary of PVs for the rule, format - { pvName: triggerOnPV } 155 | :param expression_dict: Dictionary of expressions for the rules, format - { boolean expression : value } 156 | :param value_as_expression: Defaults to False. If True, use value as expression 157 | """ 158 | root_rules = self.root.find('rules') 159 | if root_rules is None: 160 | root_rules = SubElement(self.root, 'rules') 161 | root_rule = SubElement(root_rules, 'rule') 162 | root_rule.attrib['name'] = name 163 | root_rule.attrib['prop_id'] = widget_property 164 | if value_as_expression is True: 165 | root_rule.attrib['out_exp'] = 'true' 166 | else: 167 | root_rule.attrib['out_exp'] = 'false' 168 | if expression_dict is not None: 169 | for expression, value in expression_dict.items(): 170 | expression_element = SubElement(root_rule, 'exp', {'bool_exp': expression}) 171 | if value_as_expression is True: 172 | val_as_exp_element = SubElement(expression_element, 'expression') 173 | val_as_exp_element.text = str(value) 174 | else: 175 | val_element = SubElement(expression_element, 'value') 176 | if 'color' in widget_property: 177 | name = None 178 | red = None 179 | green = None 180 | blue = None 181 | alpha = None 182 | 183 | # red, green, blue, alpha entered in a tuple (R,G,B,A) 184 | # alpha optional (defaults to 255) 185 | if str(type(value)) == "": 186 | red = value[0] 187 | green = value[1] 188 | blue = value[2] 189 | alpha = '255' 190 | 191 | if len(value) == 4: 192 | alpha = value[3] 193 | 194 | else: # predefined colors 195 | name = str(value) 196 | self._shared.create_color_element(val_element, name, red, green, blue, alpha, False) 197 | else: 198 | val_element.text = str(value) 199 | if pv_dict is not None: 200 | for pv, trigger in pv_dict.items(): 201 | pv_element = SubElement(root_rule, 'pv_name', {'trigger': str(trigger).lower()}) 202 | pv_element.text = pv 203 | 204 | def _script(self, file_name, script_contents, pv_dict, only_trigger_if_connected): 205 | root_scripts = self.root.find('scripts') 206 | if root_scripts is None: 207 | root_scripts = SubElement(self.root, 'scripts') 208 | root_script = SubElement(root_scripts, 'script') 209 | root_script.attrib['file'] = file_name 210 | if only_trigger_if_connected is False: 211 | root_script.attrib['check_connections'] = 'false' 212 | if script_contents is not None: 213 | # self._shared.generic_property(root_script, 'text', "") 214 | # from what I can tell, Phoebus will automatically add after saving in editor 215 | self._shared.generic_property(root_script, 'text', script_contents) 216 | if pv_dict is not None: 217 | for pv, trigger in pv_dict.items(): 218 | pv_element = SubElement(root_script, 'pv_name', {'trigger': str(trigger).lower()}) 219 | pv_element.text = pv 220 | 221 | def embedded_python_script(self, python_script: str, pv_dict: dict, only_trigger_if_connected: bool = True) -> None: 222 | """ 223 | Add an embedded Jython (Python) script to the widget 224 | 225 | :param python_script: Usually multi-line string representing the actual python code to attach to widget 226 | :param pv_dict: Dictionary of PVs for the script, format - { pvName: triggerOnPV } 227 | :param only_trigger_if_connected: Defaults to True. If False, script will run even if PVs are not connected 228 | """ 229 | file_name = 'EmbeddedPy' 230 | self._script(file_name, python_script, pv_dict, only_trigger_if_connected) 231 | 232 | def embedded_javascript_script(self, js_script: str, pv_dict: dict, only_trigger_if_connected: bool = True) -> None: 233 | """ 234 | Add an embedded JS script to the widget 235 | 236 | :param js_script: Usually multi-line string representing the actual JS code to attach to widget 237 | :param pv_dict: Dictionary of PVs for the script, format - { pvName: triggerOnPV } 238 | :param only_trigger_if_connected: Defaults to True. If False, script will run even if PVs are not connected 239 | """ 240 | file_name = 'EmbeddedJs' 241 | self._script(file_name, js_script, pv_dict, only_trigger_if_connected) 242 | 243 | def external_script(self, file_name: str, pv_dict: dict, only_trigger_if_connected: bool = True) -> None: 244 | """ 245 | Add an external script to the widget, either jython (.py) or javascript (.js) 246 | 247 | :param file_name: Path and file name of the external script to attach to widget 248 | :param pv_dict: Dictionary of PVs for the script, format - { pvName: triggerOnPV } 249 | :param only_trigger_if_connected: Defaults to True. If False, script will run even if PVs are not connected 250 | """ 251 | self._script(file_name, None, pv_dict, only_trigger_if_connected) 252 | 253 | def tool_tip(self, tool_tip: str) -> None: 254 | """ 255 | Add tool tip string to widget 256 | 257 | :param tool_tip: Tool tip string 258 | """ 259 | child = SubElement(self.root, 'tooltip') 260 | child.text = tool_tip 261 | -------------------------------------------------------------------------------- /tests/test_widget.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(1, '../phoebusgen/widget/') 3 | sys.path.insert(1, './phoebusgen/widget/') 4 | import unittest 5 | import widget 6 | 7 | 8 | class TestWidgetClass(unittest.TestCase): 9 | def setUp(self): 10 | self.base_type = 'test_widget_type' 11 | self.base_name = 'Just Basic Widget Test' 12 | self.base_x = 10 13 | self.base_y = 12 14 | self.base_width = 14 15 | self.base_height = 15 16 | 17 | def create_basic_widget(self): 18 | return widget._Widget(self.base_type, self.base_name, self.base_x, 19 | self.base_y, self.base_width, self.base_height) 20 | 21 | def test_basic_widget(self): 22 | widget_type = 'label' 23 | name = 'Label_1' 24 | x = 10 25 | y = 12 26 | width = 14 27 | height = 15 28 | w = widget._Widget(widget_type, name, x, y, width, height) 29 | self.assertEqual(w.root.tag, 'widget') 30 | self.assertEqual(w.root.attrib['type'], 'label') 31 | self.assertEqual(w.root.attrib['version'], '2.0.0') 32 | 33 | self.assertEqual(len(w.root), 5) 34 | for child in w.root: 35 | if child.tag == 'name': 36 | self.assertEqual(child.text, name) 37 | elif child.tag == 'x': 38 | self.assertEqual(child.text, str(x)) 39 | elif child.tag == 'y': 40 | self.assertEqual(child.text, str(y)) 41 | elif child.tag == 'width': 42 | self.assertEqual(child.text, str(width)) 43 | elif child.tag == 'height': 44 | self.assertEqual(child.text, str(height)) 45 | 46 | def test_visible(self): 47 | w = self.create_basic_widget() 48 | 49 | self.assertEqual(w.root.tag, 'widget') 50 | self.assertEqual(w.root.attrib['type'], 'test_widget_type') 51 | self.assertEqual(w.root.attrib['version'], '2.0.0') 52 | 53 | self.assertEqual(len(w.root), 5) 54 | for child in w.root: 55 | if child.tag == 'name': 56 | self.assertEqual(child.text, self.base_name) 57 | elif child.tag == 'x': 58 | self.assertEqual(child.text, str(self.base_x)) 59 | elif child.tag == 'y': 60 | self.assertEqual(child.text, str(self.base_y)) 61 | elif child.tag == 'width': 62 | self.assertEqual(child.text, str(self.base_width)) 63 | elif child.tag == 'height': 64 | self.assertEqual(child.text, str(self.base_height)) 65 | 66 | w.visible(False) 67 | self.assertEqual(len(w.root), 6) 68 | for child in w.root: 69 | if child.tag == 'visible': 70 | self.assertEqual(child.text, 'false') 71 | 72 | self.base_x = 10 73 | self.base_y = 12 74 | self.base_width = 14 75 | self.base_height = 15 76 | 77 | def test_name(self): 78 | w = self.create_basic_widget() 79 | 80 | self.assertEqual(w.root.tag, 'widget') 81 | self.assertEqual(w.root.attrib['type'], 'test_widget_type') 82 | self.assertEqual(w.root.attrib['version'], '2.0.0') 83 | 84 | self.assertEqual(len(w.root), 5) 85 | 86 | w.name('awesome new name') 87 | for child in w.root: 88 | if child.tag == 'name': 89 | self.assertEqual(child.text, 'awesome new name') 90 | 91 | def test_x_and_y(self): 92 | w = self.create_basic_widget() 93 | 94 | self.assertEqual(w.root.tag, 'widget') 95 | self.assertEqual(w.root.attrib['type'], 'test_widget_type') 96 | self.assertEqual(w.root.attrib['version'], '2.0.0') 97 | 98 | self.assertEqual(len(w.root), 5) 99 | 100 | w.x(24) 101 | for child in w.root: 102 | if child.tag == 'x': 103 | self.assertEqual(child.text, str(24)) 104 | w.y(43) 105 | for child in w.root: 106 | if child.tag == 'y': 107 | self.assertEqual(child.text, str(43)) 108 | 109 | def test_height_and_width(self): 110 | w = self.create_basic_widget() 111 | 112 | self.assertEqual(w.root.tag, 'widget') 113 | self.assertEqual(w.root.attrib['type'], 'test_widget_type') 114 | self.assertEqual(w.root.attrib['version'], '2.0.0') 115 | 116 | self.assertEqual(len(w.root), 5) 117 | 118 | w.height(12) 119 | for child in w.root: 120 | if child.tag == 'height': 121 | self.assertEqual(child.text, str(12)) 122 | w.width(324) 123 | for child in w.root: 124 | if child.tag == 'width': 125 | self.assertEqual(child.text, str(324)) 126 | 127 | def test_embedded_python_script(self): 128 | w = self.create_basic_widget() 129 | script = """# Embedded python script 130 | from org.csstudio.display.builder.runtime.script import PVUtil, ScriptUtil 131 | print 'Hello' 132 | # widget.setPropertyValue('text', PVUtil.getString(pvs[0]))""" 133 | pvs = {'pv0': True, '$(pv_name)': False, 'pv2': True} 134 | w.embedded_python_script(script, pvs, False) 135 | self.assertEqual(len(w.root.findall('scripts')), 1) 136 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')), 1) 137 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')[0].findall('pv_name')), 3) 138 | pv_elements = w.root.findall('scripts')[0].findall('script')[0].findall('pv_name') 139 | for pv_element in pv_elements: 140 | self.assertEqual(pv_element.attrib['trigger'], str(pvs[pv_element.text]).lower()) 141 | script_element = w.root.findall('scripts')[0].findall('script')[0] 142 | self.assertEqual(script_element.attrib['file'], 'EmbeddedPy') 143 | 144 | def test_embedded_javascript_script(self): 145 | w = self.create_basic_widget() 146 | script = """/* Embedded javascript */ 147 | importClass(org.csstudio.display.builder.runtime.script.PVUtil); 148 | importClass(org.csstudio.display.builder.runtime.script.ScriptUtil); 149 | logger = ScriptUtil.getLogger(); 150 | logger.info("Hello"); 151 | /* widget.setPropertyValue("text", PVUtil.getString(pvs[0])); */""" 152 | pvs = {'pv0': True, '$(pv_name)': False, 'pv2': True} 153 | w.embedded_javascript_script(script, pvs, True) 154 | self.assertEqual(len(w.root.findall('scripts')), 1) 155 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')), 1) 156 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')[0].findall('pv_name')), 3) 157 | pv_elements = w.root.findall('scripts')[0].findall('script')[0].findall('pv_name') 158 | for pv_element in pv_elements: 159 | self.assertEqual(pv_element.attrib['trigger'], str(pvs[pv_element.text]).lower()) 160 | script_element = w.root.findall('scripts')[0].findall('script')[0] 161 | self.assertEqual(script_element.attrib['file'], 'EmbeddedJs') 162 | 163 | def test_external_script(self): 164 | w = self.create_basic_widget() 165 | pvs = {'pv0': True, '$(pv_name)': False} 166 | file_name = '/path/to/the/amazing/script.py' 167 | w.external_script(file_name, pvs, False) 168 | self.assertEqual(len(w.root.findall('scripts')), 1) 169 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')), 1) 170 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')[0].findall('pv_name')), 2) 171 | pv_elements = w.root.findall('scripts')[0].findall('script')[0].findall('pv_name') 172 | for pv_element in pv_elements: 173 | self.assertEqual(pv_element.attrib['trigger'], str(pvs[pv_element.text]).lower()) 174 | script_element = w.root.findall('scripts')[0].findall('script')[0] 175 | self.assertEqual(script_element.attrib['file'], file_name) 176 | self.assertEqual(script_element.attrib['check_connections'], 'false') 177 | 178 | def test_multiple_scripts(self): 179 | w = self.create_basic_widget() 180 | pvs = {'pv0': True, '$(pv_name)': False} 181 | pvs2 = {'$(pv_name)': False} 182 | file_name = '/path/to/the/amazing/script.py' 183 | script = """/* Embedded javascript */ 184 | importClass(org.csstudio.display.builder.runtime.script.PVUtil); 185 | importClass(org.csstudio.display.builder.runtime.script.ScriptUtil); 186 | logger = ScriptUtil.getLogger(); 187 | logger.info("Hello"); 188 | /* widget.setPropertyValue("text", PVUtil.getString(pvs[0])); */""" 189 | w.external_script(file_name, pvs, False) 190 | w.embedded_javascript_script(script, pvs2, True) 191 | self.assertEqual(len(w.root.findall('scripts')), 1) 192 | self.assertEqual(len(w.root.findall('scripts')[0].findall('script')), 2) 193 | 194 | script_element1 = w.root.findall('scripts')[0].findall('script')[0] 195 | self.assertEqual(script_element1.attrib['file'], file_name) 196 | self.assertEqual(script_element1.attrib['check_connections'], 'false') 197 | 198 | script_element2 = w.root.findall('scripts')[0].findall('script')[1] 199 | self.assertEqual(script_element2.attrib['file'], 'EmbeddedJs') 200 | 201 | def test_rule(self): 202 | w = self.create_basic_widget() 203 | pvs = {'pv0': True, '$(pv_name)': False} 204 | expressions = {'pv0 == pvStr1': 'test:analog'} 205 | rule_name = 'My Cool Cool Rule' 206 | w.rule(rule_name, 'pv_name', pvs, expressions, False) 207 | self.assertEqual(len(w.root.findall('rules')), 1) 208 | self.assertEqual(len(w.root.findall('rules')[0].findall('rule')), 1) 209 | 210 | rule_element = w.root.findall('rules')[0].findall('rule')[0] 211 | self.assertEqual(rule_element.attrib['name'], rule_name) 212 | self.assertEqual(rule_element.attrib['prop_id'], 'pv_name') 213 | self.assertEqual(rule_element.attrib['out_exp'], 'false') 214 | 215 | self.assertEqual(len(rule_element.findall('exp')), 1) 216 | expression_element = rule_element.findall('exp')[0] 217 | self.assertEqual(expression_element.attrib['bool_exp'], 'pv0 == pvStr1') 218 | self.assertEqual(expression_element.findall('value')[0].text, 'test:analog') 219 | 220 | def test_rule_integer_param(self): 221 | w = self.create_basic_widget() 222 | pvs = {'pv0': True, '$(pv_name)': False} 223 | expressions = {'pv0 == pvStr1': 'test:analog', 'pv1 == 0': 2} 224 | rule_name = 'My New Rule' 225 | w.rule(rule_name, 'pv_name', pvs, expressions, False) 226 | self.assertEqual(len(w.root.findall('rules')), 1) 227 | self.assertEqual(len(w.root.findall('rules')[0].findall('rule')), 1) 228 | 229 | rule_element = w.root.findall('rules')[0].findall('rule')[0] 230 | self.assertEqual(rule_element.attrib['name'], rule_name) 231 | self.assertEqual(rule_element.attrib['prop_id'], 'pv_name') 232 | self.assertEqual(rule_element.attrib['out_exp'], 'false') 233 | 234 | self.assertEqual(len(rule_element.findall('exp')), 2) 235 | expression_element = rule_element.findall('exp')[0] 236 | self.assertEqual(expression_element.attrib['bool_exp'], 'pv0 == pvStr1') 237 | self.assertEqual(expression_element.findall('value')[0].text, 'test:analog') 238 | expression_element_two = rule_element.findall('exp')[1] 239 | self.assertEqual(expression_element_two.attrib['bool_exp'], 'pv1 == 0') 240 | self.assertEqual(expression_element_two.findall('value')[0].text, '2') 241 | 242 | def test_rule_color_predefined(self): 243 | w = self.create_basic_widget() 244 | pvs = {'pv0': True, '$(pv_name)': False} 245 | color = {'pvStr0': 'OK'} 246 | rule_name = 'color rule again' 247 | w.rule(rule_name, 'color', pvs, color, False) 248 | 249 | rule_element = w.root.findall('rules')[0].findall('rule')[0] 250 | self.assertEqual(rule_element.attrib['name'], rule_name) 251 | self.assertEqual(rule_element.attrib['prop_id'], 'color') 252 | 253 | def test_rule_color(self): 254 | w = self.create_basic_widget() 255 | pvs = {'pv0': True, '$(pv_name)': False} 256 | color = {'pvStr0': (5, 10, 15, 20)} 257 | rule_name = 'color rule' 258 | w.rule(rule_name, 'color', pvs, color, False) 259 | 260 | rule_element = w.root.findall('rules')[0].findall('rule')[0] 261 | self.assertEqual(rule_element.attrib['name'], rule_name) 262 | self.assertEqual(rule_element.attrib['prop_id'], 'color') 263 | 264 | 265 | class TestGenericWidget(unittest.TestCase): 266 | def setUp(self): 267 | self.base_type = 'test_widget_type' 268 | 269 | def create_basic_generic_widget(self): 270 | return widget._Generic(self.base_type) 271 | 272 | def test_basic_generic_widget(self): 273 | g = self.create_basic_generic_widget() 274 | self.assertEqual(g.root.tag, 'test_widget_type') 275 | 276 | self.assertEqual(len(g.root), 0) 277 | 278 | 279 | if __name__ == '__main__': 280 | unittest.main() 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /tests/test_widgets.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(1, '../phoebusgen/widget/') 3 | sys.path.insert(1, './phoebusgen/widget/') 4 | import unittest 5 | import widgets 6 | import property_helpers as ph 7 | 8 | 9 | class TestArc(unittest.TestCase, ph.TestMacro, ph.TestAngle, ph.TestLineWidth, ph.TestLineColor, 10 | ph.TestBackgroundColor, ph.TestTransparent): 11 | def setUp(self): 12 | self.name = 'My_Arc' 13 | self.type = 'arc' 14 | self.x = 10 15 | self.y = 12 16 | self.width = 14 17 | self.height = 15 18 | self.element = widgets.Arc(self.name, self.x, self.y, self.width, self.height) 19 | 20 | class TestEllipse(unittest.TestCase, ph.TestMacro, ph.TestLineWidth, ph.TestLineColor, ph.TestBackgroundColor, 21 | ph.TestTransparent): 22 | def setUp(self): 23 | self.name = 'My_Ellipse' 24 | self.type = 'ellipse' 25 | self.x = 10 26 | self.y = 12 27 | self.width = 14 28 | self.height = 15 29 | self.element = widgets.Ellipse(self.name, self.x, self.y, self.width, self.height) 30 | 31 | class TestRectangle(unittest.TestCase, ph.TestMacro, ph.TestLineWidth, ph.TestLineColor, ph.TestBackgroundColor, 32 | ph.TestTransparent, ph.TestCorner): 33 | def setUp(self): 34 | self.name = 'My_Rectangle' 35 | self.type = 'rectangle' 36 | self.x = 10 37 | self.y = 12 38 | self.width = 14 39 | self.height = 15 40 | self.element = widgets.Rectangle(self.name, self.x, self.y, self.width, self.height) 41 | 42 | class TestLabel(unittest.TestCase, ph.TestMacro, ph.TestText, ph.TestForegroundColor, ph.TestBackgroundColor, 43 | ph.TestTransparent, ph.TestHorizontalAlignment, ph.TestVerticalAlignment, ph.TestRotationStep, 44 | ph.TestAutoSize, ph.TestWrapWords, ph.TestBorder): 45 | def setUp(self): 46 | self.name = 'Label_1' 47 | self.type = 'label' 48 | self.x = 10 49 | self.y = 12 50 | self.width = 14 51 | self.height = 15 52 | self.text = 'TEST Label' 53 | self.element = widgets.Label(self.name, self.text, self.x, self.y, self.width, self.height) 54 | 55 | class TestPicture(unittest.TestCase, ph.TestMacro, ph.TestFile, ph.TestStretchToFit, ph.TestRotation): 56 | def setUp(self): 57 | self.name = 'Picture_1' 58 | self.type = 'picture' 59 | self.x = 123 60 | self.y = 12 61 | self.width = 10 62 | self.height = 12 63 | self.file = '/home/user/my-pic.png' 64 | self.element = widgets.Picture(self.name, self.file, self.x, self.y, self.width, self.height) 65 | 66 | class TestPolygon(unittest.TestCase, ph.TestMacro, ph.TestLineWidth, ph.TestLineColor, 67 | ph.TestBackgroundColor, ph.TestPoints): 68 | def setUp(self): 69 | self.name = 'polygon' 70 | self.type = 'polygon' 71 | self.x = 123 72 | self.y = 12 73 | self.width = 10 74 | self.height = 12 75 | self.element = widgets.Polygon(self.name, self.x, self.y, self.width, self.height) 76 | 77 | class TestPolyline(unittest.TestCase, ph.TestMacro, ph.TestLineWidth, ph.TestLineColor, 78 | ph.TestLineStyle, ph.TestPoints, ph.TestArrow): 79 | def setUp(self): 80 | self.name = 'polyline1' 81 | self.type = 'polyline' 82 | self.x = 123 83 | self.y = 12 84 | self.width = 10 85 | self.height = 12 86 | self.element = widgets.Polyline(self.name, self.x, self.y, self.width, self.height) 87 | 88 | class TestByteMonitor(unittest.TestCase, ph.TestPVName, ph.TestStartBit, ph.TestNumBits, ph.TestReverseBits, 89 | ph.TestHorizontal, ph.TestSquare, ph.TestOffColor, ph.TestOnColor, ph.TestForegroundColor, 90 | ph.TestFont, ph.TestLabels, ph.TestAlarmBorder): 91 | def setUp(self): 92 | self.name = 'bytemonitorname' 93 | self.pv_name = 'TEST:PV:ENTRY' 94 | self.type = 'byte_monitor' 95 | self.x = 10 96 | self.y = 12 97 | self.width = 14 98 | self.height = 15 99 | self.element = widgets.ByteMonitor(self.name, self.pv_name, self.x, self.y, self.width, self.height) 100 | 101 | 102 | class TestLED(unittest.TestCase, ph.TestPVName, ph.TestBit, ph.TestOn, ph.TestOff, ph.TestFont, ph.TestForegroundColor, 103 | ph.TestLineColor, ph.TestSquare, ph.TestLabelsFromPV, ph.TestAlarmBorder): 104 | def setUp(self): 105 | self.name = 'Label_1' 106 | self.pv_name = 'TEST:PV:ENTRY' 107 | self.type = 'led' 108 | self.x = 10 109 | self.y = 12 110 | self.width = 14 111 | self.height = 15 112 | self.element = widgets.LED(self.name, self.pv_name, self.x, self.y, self.width, self.height) 113 | 114 | class TestLEDMultiState(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestLineColor, 115 | ph.TestSquare, ph.TestAlarmBorder, ph.TestStates, ph.TestFallback): 116 | def setUp(self): 117 | self.name = 'Ledmulti' 118 | self.pv_name = 'PV:Multi' 119 | self.type = 'multi_state_led' 120 | self.x = 10 121 | self.y = 12 122 | self.width = 14 123 | self.height = 15 124 | self.element = widgets.LEDMultiState(self.name, self.pv_name, self.x, self.y, self.width, self.height) 125 | 126 | class TestMeter(unittest.TestCase, ph.TestPVName, ph.TestForegroundColor, ph.TestBackgroundColor, ph.TestFont, 127 | ph.TestFormat, ph.TestPrecision, ph.TestShowValue, ph.TestShowUnits, ph.TestAlarmBorder, ph.TestShowLimits, 128 | ph.TestLimitsFromPV, ph.TestMinMax, ph.TestNeedleColor, ph.TestKnobColor): 129 | def setUp(self): 130 | self.name = 'Cool meter' 131 | self.type = 'meter' 132 | self.x = 123 133 | self.y = 12 134 | self.width = 10 135 | self.height = 12 136 | self.pv_name = 'test:pv' 137 | self.element = widgets.Meter(self.name, self.pv_name, self.x, self.y, self.width, self.height) 138 | 139 | class TestProgressBar(unittest.TestCase, ph.TestPVName, ph.TestFillColor, ph.TestBackgroundColor, 140 | ph.TestHorizontal, ph.TestAlarmBorder, ph.TestLimitsFromPV, ph.TestMinMax): 141 | def setUp(self): 142 | self.type = 'progressbar' 143 | self.name = 'Progress bar' 144 | self.x = 123 145 | self.y = 12 146 | self.width = 10 147 | self.height = 12 148 | self.pv_name = 'test:pv:forprogressbar' 149 | self.element = widgets.ProgressBar(self.name, self.pv_name, self.x, self.y, self.width, self.height) 150 | 151 | class TestSymbol(unittest.TestCase, ph.TestPVName, ph.TestRotation, ph.TestBackgroundColor, ph.TestArrayIndex, 152 | ph.TestTransparent, ph.TestAlarmBorder, ph.TestAutoSize, ph.TestEnabled, ph.TestSymbols, 153 | ph.TestInitialIndex, ph.TestShowIndex, ph.TestPreserveRatio): 154 | def setUp(self): 155 | self.type = 'symbol' 156 | self.name = 'SymbolWidget' 157 | self.x = 13 158 | self.y = 1422 159 | self.width = 2310 160 | self.height = 109 161 | self.pv_name = 'test:pv' 162 | self.element = widgets.Symbol(self.name, self.pv_name, self.x, self.y, self.width, self.height) 163 | 164 | class TestTable(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 165 | ph.TestShowToolbar, ph.TestAlarmBorder, ph.TestEditable, ph.TestSelectRows, ph.TestSelectionPV, 166 | ph.TestColumns): 167 | def setUp(self): 168 | self.type = 'table' 169 | self.name = 'TableName' 170 | self.pv_name = 'test:pv' 171 | self.x = 13 172 | self.y = 1422 173 | self.width = 2310 174 | self.height = 109 175 | self.element = widgets.Table(self.name, self.pv_name, self.x, self.y, self.width, self.height) 176 | 177 | 178 | class TestTank(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 179 | ph.TestFillColor, ph.TestEmptyColor, ph.TestScaleVisible, ph.TestAlarmBorder, ph.TestLimitsFromPV): 180 | def setUp(self): 181 | self.name = 'my cool tank' 182 | self.type = 'tank' 183 | self.x = 24 184 | self.y = 12 185 | self.width = 2424 186 | self.height = 92 187 | self.pv_name = 'TANK' 188 | self.element = widgets.Tank(self.name, self.pv_name, self.x, self.y, self.width, self.height) 189 | 190 | class TestTextSymbol(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 191 | ph.TestTransparent, ph.TestHorizontalAlignment, ph.TestVerticalAlignment, ph.TestRotation, 192 | ph.TestWrapWords, ph.TestAlarmBorder, ph.TestEnabled, ph.TestArrayIndex, ph.TestSymbols): 193 | def setUp(self): 194 | self.name = 'text symbol widget' 195 | self.type = 'text-symbol' 196 | self.x = 24 197 | self.y = 12 198 | self.width = 24 199 | self.height = 92 200 | self.pv_name = 'symbol' 201 | self.element = widgets.TextSymbol(self.name, self.pv_name, self.x, self.y, self.width, self.height) 202 | 203 | 204 | class TestTextUpdate(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, 205 | ph.TestBackgroundColor, ph.TestTransparent, ph.TestFormat, ph.TestPrecision, 206 | ph.TestShowUnits, ph.TestHorizontalAlignment, ph.TestVerticalAlignment, 207 | ph.TestWrapWords, ph.TestRotationStep, ph.TestBorder, ph.TestAlarmBorder): 208 | def setUp(self): 209 | self.pv_name = 'TEST:ME' 210 | self.name = 'Generic TextUpdate' 211 | self.type = 'textupdate' 212 | self.x = 500 213 | self.y = 300 214 | self.width = 100 215 | self.height = 20 216 | self.element = widgets.TextUpdate(self.name, self.pv_name, self.x, self.y, self.width, self.height) 217 | 218 | class TestThermometer(unittest.TestCase, ph.TestPVName, ph.TestFillColor, ph.TestAlarmBorder, 219 | ph.TestLimitsFromPV, ph.TestMinMax): 220 | def setUp(self): 221 | self.name = 'testing thermometer' 222 | self.type = 'thermometer' 223 | self.x = 123 224 | self.y = 12 225 | self.width = 10 226 | self.height = 12 227 | self.pv_name = 'test:temp' 228 | self.element = widgets.Thermometer(self.name, self.pv_name, self.x, self.y, self.width, self.height) 229 | 230 | class TestActionButton(unittest.TestCase, ph.TestPVName, ph.TestText, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 231 | ph.TestTransparent, ph.TestRotationStep, ph.TestEnabled, ph.TestConfirmation, ph.TestActions, 232 | ph.TestAlarmBorder, ph.TestVerticalAlignment, ph.TestHorizontalAlignment): 233 | def setUp(self): 234 | self.name = 'Label_1' 235 | self.type = 'action_button' 236 | self.pv_name = 'TEST:PV:ENTRY' 237 | self.text = 'TEST TTEST TEST' 238 | self.x = 10 239 | self.y = 12 240 | self.width = 14 241 | self.height = 15 242 | self.element = widgets.ActionButton(self.name, self.text, self.pv_name, self.x, self.y, self.width, self.height) 243 | 244 | class TestBooleanButton(unittest.TestCase, ph.TestOffImage, ph.TestPVName, ph.TestBit, ph.TestShowLED, ph.TestFont, 245 | ph.TestForegroundColor, ph.TestBackgroundColor, ph.TestLabelsFromPV, ph.TestAlarmBorder, 246 | ph.TestEnabled, ph.TestMode, ph.TestConfirmation, ph.TestOnImage, ph.TestHorizontalAlignment, ph.TestVerticalAlignment): 247 | def setUp(self): 248 | self.name = 'boolean button' 249 | self.type = 'bool_button' 250 | self.pv_name = 'tester_pv' 251 | self.x = 23 252 | self.y = 3245 253 | self.width = 1 254 | self.height = 2 255 | self.element = widgets.BooleanButton(self.name, self.pv_name, 23.2, 3245.9, 1.1, 2.8) 256 | 257 | class TestCheckBox(unittest.TestCase, ph.TestPVName, ph.TestBit, ph.TestFont, ph.TestForegroundColor, 258 | ph.TestAutoSize, ph.TestAlarmBorder, ph.TestConfirmation, ph.TestLabel): 259 | def setUp(self): 260 | self.name = 'Check box 1' 261 | self.pv_name = 'TEST:PV:BOOL' 262 | self.type = 'checkbox' 263 | self.label = 'My check box' 264 | self.x = 10 265 | self.y = 12 266 | self.width = 14 267 | self.height = 15 268 | self.element = widgets.CheckBox(self.name, self.label, self.pv_name, self.x, self.y, self.width, self.height) 269 | 270 | class TestChoiceButton(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 271 | ph.TestSelectedColor, ph.TestHorizontal, ph.TestAlarmBorder, ph.TestItems, ph.TestItemsFromPV, 272 | ph.TestConfirmation, ph.TestHorizontalAlignment, ph.TestVerticalAlignment): 273 | def setUp(self): 274 | self.name = 'choice box' 275 | self.pv_name = 'TEST:PV:BOOL' 276 | self.type = 'choice' 277 | self.x = 10 278 | self.y = 12 279 | self.width = 14 280 | self.height = 15 281 | self.element = widgets.ChoiceButton(self.name, self.pv_name, self.x, self.y, self.width, self.height) 282 | 283 | class TestComboBox(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 284 | ph.TestItems, ph.TestAlarmBorder, ph.TestItemsFromPV, ph.TestEditable, ph.TestEnabled, ph.TestConfirmation): 285 | def setUp(self): 286 | self.name = '1' 287 | self.pv_name = 'TEST:PV:BOOL' 288 | self.type = 'combo' 289 | self.x = 10 290 | self.y = 12 291 | self.width = 14 292 | self.height = 15 293 | self.element = widgets.ComboBox(self.name, self.pv_name, self.x, self.y, self.width, self.height) 294 | 295 | class TestFileSelector(unittest.TestCase, ph.TestPVName, ph.TestFileComponent, ph.TestAlarmBorder, ph.TestEnabled): 296 | def setUp(self): 297 | self.name = 'My file selector' 298 | self.pv_name = 'TEST:PV:BOOL' 299 | self.type = 'fileselector' 300 | self.x = 10 301 | self.y = 12 302 | self.width = 14 303 | self.height = 15 304 | self.element = widgets.FileSelector(self.name, self.pv_name, self.x, self.y, self.width, self.height) 305 | 306 | class TestRadioButton(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestHorizontal, 307 | ph.TestAlarmBorder, ph.TestItemsFromPV, ph.TestItems, ph.TestEnabled, ph.TestConfirmation): 308 | def setUp(self): 309 | self.name = 'Radio_1' 310 | self.pv_name = 'TEST:PV:BOOL' 311 | self.type = 'radio' 312 | self.x = 10 313 | self.y = 12 314 | self.width = 14 315 | self.height = 15 316 | self.element = widgets.RadioButton(self.name, self.pv_name, self.x, self.y, self.width, self.height) 317 | 318 | class TestScaledSlider(unittest.TestCase, ph.TestPVName, ph.TestHorizontal, ph.TestForegroundColor, ph.TestBackgroundColor, 319 | ph.TestTransparent, ph.TestFont, ph.TestAlarmBorder, ph.TestIncrement, ph.TestMinMax, 320 | ph.TestLimitsFromPV, ph.TestEnabled, ph.TestShowScale, ph.TestShowMinorTicks, 321 | ph.TestMajorTicksPixelDist, ph.TestScaleFormat, ph.TestLevelsAndShow): 322 | def setUp(self): 323 | self.name = 'Radio_1' 324 | self.pv_name = 'TEST:PV:BOOL' 325 | self.type = 'scaledslider' 326 | self.x = 10 327 | self.y = 12 328 | self.width = 14 329 | self.height = 15 330 | self.element = widgets.ScaledSlider(self.name, self.pv_name, self.x, self.y, self.width, self.height) 331 | 332 | class TestScrollbar(unittest.TestCase, ph.TestPVName, ph.TestHorizontal, ph.TestShowValueTip, ph.TestAlarmBorder, 333 | ph.TestMinMax, ph.TestLimitsFromPV, ph.TestBarLength, ph.TestIncrement, ph.TestEnabled): 334 | def setUp(self): 335 | self.name = 'Radio_1' 336 | self.pv_name = 'TEST:PV:BOOL' 337 | self.type = 'scrollbar' 338 | self.x = 10 339 | self.y = 12 340 | self.width = 14 341 | self.height = 15 342 | self.element = widgets.Scrollbar(self.name, self.pv_name, self.x, self.y, self.width, self.height) 343 | 344 | class TestSlideButton(unittest.TestCase, ph.TestPVName, ph.TestBit, ph.TestLabel, ph.TestFont, ph.TestForegroundColor, 345 | ph.TestAutoSize, ph.TestAlarmBorder, ph.TestEnabled, ph.TestConfirmation, ph.TestOffColor, 346 | ph.TestOnColor): 347 | def setUp(self): 348 | self.name = 'slider button' 349 | self.pv_name = 'TEST:PV' 350 | self.type = 'slide_button' 351 | self.label = 'this is a slide button' 352 | self.x = 24 353 | self.y = 234 354 | self.width = 12 355 | self.height = 24 356 | self.element = widgets.SlideButton(self.name, self.label, self.pv_name, self.x, self.y, self.width, self.height) 357 | 358 | class TestSpinner(unittest.TestCase, ph.TestPVName, ph.TestFormat, ph.TestPrecision, ph.TestShowUnits, ph.TestForegroundColor, 359 | ph.TestBackgroundColor, ph.TestButtonsOnLeft, ph.TestAlarmBorder, ph.TestMinMax, ph.TestLimitsFromPV, 360 | ph.TestIncrement, ph.TestEnabled): 361 | def setUp(self): 362 | self.name = 'SpinnerWidget' 363 | self.pv_name = 'TESTpv' 364 | self.type = 'spinner' 365 | self.x = 10 366 | self.y = 12 367 | self.width = 14 368 | self.height = 15 369 | self.element = widgets.Spinner(self.name, self.pv_name, self.x, self.y, self.width, self.height) 370 | 371 | class TestTextEntry(unittest.TestCase, ph.TestPVName, ph.TestFont, ph.TestForegroundColor, ph.TestBackgroundColor, 372 | ph.TestFormat, ph.TestPrecision, ph.TestShowUnits, ph.TestWrapWords, ph.TestMultiLine, 373 | ph.TestAlarmBorder, ph.TestEnabled, ph.TestBorder, ph.TestVerticalAlignment, 374 | ph.TestHorizontalAlignment): 375 | def setUp(self): 376 | self.name = 'Label_1' 377 | self.pv_name = 'TEST:PV:ENTRY' 378 | self.type = 'textentry' 379 | self.x = 10 380 | self.y = 12 381 | self.width = 14 382 | self.height = 15 383 | self.element = widgets.TextEntry(self.name, self.pv_name, self.x, self.y, self.width, self.height) 384 | 385 | class TestDataBrowser(unittest.TestCase, ph.TestMacro, ph.TestFile, ph.TestShowToolbar, ph.TestSelectionValuePV): 386 | def setUp(self): 387 | self.name = 'my data browser' 388 | self.file = '/home/tynan/plots/my-cool-plot.plt' 389 | self.type = 'databrowser' 390 | self.x = 24 391 | self.y = 1224 392 | self.width = 1239 393 | self.height = 1 394 | self.element = widgets.DataBrowser(self.name, self.file, self.x, self.y, self.width, self.height) 395 | 396 | class TestImage(unittest.TestCase, ph.TestPVName, ph.TestForegroundColor, ph.TestBackgroundColor, 397 | ph.TestShowToolbar, ph.TestAlarmBorder, ph.TestMinMax, ph.TestAutoScale, ph.TestDataHeightAndWidth, 398 | ph.TestLogScale, ph.TestUnsignedData, ph.TestCursor, ph.TestInterpolation, ph.TestXAxes, 399 | ph.TestYAxisSingle, ph.TestColorMode, ph.TestRegionsOfInterest, ph.TestColorBar, ph.TestColorMap): 400 | def setUp(self): 401 | self.name = 'my data browser' 402 | self.pv_name = 'Image:PV' 403 | self.type = 'image' 404 | self.x = 24 405 | self.y = 1224 406 | self.width = 1239 407 | self.height = 1 408 | self.element = widgets.Image(self.name, self.pv_name, self.x, self.y, self.width, self.height) 409 | self.yaxis2 = widgets.ImageYAxis() 410 | self.yaxis1 = widgets.ImageYAxis() 411 | self.xaxis1 = widgets.ImageXAxis() 412 | self.xaxis2 = widgets.ImageXAxis() 413 | self.roi1 = widgets.RegionOfInterest() 414 | self.roi2 = widgets.RegionOfInterest() 415 | self.bar1 = widgets.ColorBar() 416 | self.bar2 = widgets.ColorBar() 417 | self.map1 = widgets.ColorMapColor(0, 1, 2, 3) 418 | self.map2 = widgets.ColorMapColor(255, 4, 5, 6) 419 | self.map3 = widgets.ColorMapColor(100, 7, 8, 9) 420 | 421 | class TestStripChart(unittest.TestCase, ph.TestForegroundColor, ph.TestBackgroundColor, 422 | ph.TestShowToolbar, ph.TestTitle, ph.TestShowLegend, ph.TestShowGrid, 423 | ph.TestTimeRange, ph.TestTitleFont, ph.TestScaleFont, ph.TestLabelFont): 424 | def setUp(self): 425 | self.name = 'strippers' 426 | self.type = 'stripchart' 427 | self.x = 24 428 | self.y = 1224 429 | self.width = 1239 430 | self.height = 1 431 | self.element = widgets.StripChart(self.name, self.x, self.y, self.width, self.height) 432 | 433 | class TestXYPlot(unittest.TestCase, ph.TestForegroundColor, ph.TestBackgroundColor, 434 | ph.TestShowToolbar, ph.TestTitle, ph.TestTitleFont, ph.TestGridColor, ph.TestTraces, 435 | ph.TestYAxes, ph.TestXAxes, ph.TestMarkers): 436 | def setUp(self): 437 | self.name = 'xyxyxyxyx' 438 | self.type = 'xyplot' 439 | self.x = 24 440 | self.y = 1224 441 | self.width = 1239 442 | self.height = 1 443 | self.element = widgets.XYPlot(self.name, self.x, self.y, self.width, self.height) 444 | self.trace1 = widgets.XYPlotTrace() 445 | self.trace2 = widgets.XYPlotTrace() 446 | self.yaxis1 = widgets.XYPlotYAxis() 447 | self.yaxis2 = widgets.XYPlotYAxis() 448 | self.yaxis_wrong = widgets.StripChartYAxis() 449 | self.trace_wrong = widgets.StripChartTrace() 450 | self.xaxis1 = widgets.XYPlotXAxis() 451 | self.xaxis2 = widgets.XYPlotXAxis() 452 | self.marker1 = widgets.Marker() 453 | self.marker2 = widgets.Marker() 454 | 455 | class TestArray(unittest.TestCase, ph.TestPVName, ph.TestMacro, ph.TestForegroundColor, ph.TestBackgroundColor, 456 | ph.TestAlarmBorder): 457 | def setUp(self): 458 | self.name = 'test array' 459 | self.type = 'array' 460 | self.x = 124 461 | self.y = 1 462 | self.width = 129 463 | self.height = 20 464 | self.pv_name = 'MY:ARRAY:PV' 465 | self.element = widgets.Array(self.name, self.pv_name, self.x, self.y, self.width, self.height) 466 | 467 | class TestEmbeddedDisplay(unittest.TestCase, ph.TestMacro, ph.TestFile, ph.TestResizeBehavior, 468 | ph.TestGroupName, ph.TestTransparent, ph.TestBorder): 469 | def setUp(self): 470 | self.name = 'EmbeddedDisplay' 471 | self.type = 'embedded' 472 | self.x = 123 473 | self.y = 12 474 | self.width = 10 475 | self.height = 12 476 | self.file = '/home/user/_my-embedded-file.bob' 477 | self.element = widgets.EmbeddedDisplay(self.name, self.file, self.x, self.y, self.width, self.height) 478 | 479 | class TestGroup(unittest.TestCase, ph.TestMacro, ph.TestStyle, ph.TestForegroundColor, 480 | ph.TestBackgroundColor, ph.TestTransparent, ph.TestLineColor, ph.TestStructure): 481 | def setUp(self): 482 | self.name = 'MyGroup Display' 483 | self.type = 'group' 484 | self.x = 123 485 | self.y = 12 486 | self.width = 10 487 | self.height = 12 488 | self.element = widgets.Group(self.name, self.x, self.y, self.width, self.height) 489 | self.widget_list = [widgets.Label('Label1', 'hello', 0, 0, 10, 10), widgets.XYPlot('XYPlot1', 0, 0, 100, 100)] 490 | self.widget_obj = widgets.XYPlot('XYPlot2', 0, 0, 100, 100) 491 | 492 | class TestNavigationTabs(unittest.TestCase, ph.TestNavTabs, ph.TestActiveTab, ph.TestTabWidth, ph.TestTabSpacing, 493 | ph.TestTabHeight, ph.TestSelectedColor, ph.TestDeselectedColor, ph.TestDirection, 494 | ph.TestFont): 495 | def setUp(self): 496 | self.name = 'Tab widget' 497 | self.type = 'navtabs' 498 | self.x = 123 499 | self.y = 12 500 | self.width = 10 501 | self.height = 12 502 | self.element = widgets.NavigationTabs(self.name, self.x, self.y, self.width, self.height) 503 | 504 | class TestTabs(unittest.TestCase, ph.TestMacro, ph.TestTabs, ph.TestFont, ph.TestActiveTab, 505 | ph.TestTabHeight, ph.TestBackgroundColor, ph.TestDirection): 506 | def setUp(self): 507 | self.name = 'Tab widget' 508 | self.type = 'tabs' 509 | self.x = 123 510 | self.y = 12 511 | self.width = 10 512 | self.height = 12 513 | self.element = widgets.Tabs(self.name, self.x, self.y, self.width, self.height) 514 | self.widget = widgets.Group('testGroup', 0, 0, 0, 0) # to test add_widget 515 | 516 | 517 | class TestThreeDViewer(unittest.TestCase, ph.TestFile): 518 | def setUp(self): 519 | self.name = 'cool 3d viewer' 520 | self.file = '/users/test/3dviewerfile' 521 | self.type = '3dviewer' 522 | self.x = 123 523 | self.y = 12 524 | self.width = 10 525 | self.height = 12 526 | self.element = widgets.ThreeDViewer(self.name, self.file, self.x, self.y, self.width, self.height) 527 | 528 | class TestWebBrowser(unittest.TestCase, ph.TestUrl, ph.TestShowToolbar): 529 | def setUp(self): 530 | self.name = 'my web browser' 531 | self.url = 'https://tynanford.com' 532 | self.type = 'webbrowser' 533 | self.x = 123 534 | self.y = 12 535 | self.width = 10 536 | self.height = 12 537 | self.element = widgets.WebBrowser(self.name, self.url, self.x, self.y, self.width, self.height) 538 | 539 | class TestStripChartTrace(unittest.TestCase, ph.TestName, ph.TestYPV, ph.TestYAxis, ph.TestTraceType, 540 | ph.TestColor, ph.TestLineWidthInternal, ph.TestPointType, ph.TestPointSize): 541 | def setUp(self): 542 | self.element = widgets.StripChartTrace() 543 | 544 | class TestXYPlotTrace(unittest.TestCase, ph.TestName, ph.TestXPV, ph.TestYPV, ph.TestErrPV, ph.TestYAxis, ph.TestTraceType, 545 | ph.TestColor, ph.TestLineWidthInternal, ph.TestLineStyleInternal, ph.TestPointType, ph.TestPointSize): 546 | def setUp(self): 547 | self.element = widgets.XYPlotTrace() 548 | 549 | class TestStripChartYAxis(unittest.TestCase, ph.TestTitleInternal, ph.TestAutoScaleInternal, ph.TestLogScaleInternal, 550 | ph.TestMinMaxInternal, ph.TestShowGridInternal, ph.TestColor): 551 | def setUp(self): 552 | self.element = widgets.XYPlotYAxis() 553 | 554 | class TestXYPlotYAxis(unittest.TestCase, ph.TestTitleInternal, ph.TestAutoScaleInternal, ph.TestLogScaleInternal, 555 | ph.TestMinMaxInternal, ph.TestShowGridInternal, ph.TestColor, ph.TestTitleFontInternal, 556 | ph.TestOnRight): 557 | def setUp(self): 558 | self.element = widgets.XYPlotYAxis() 559 | 560 | class TestImageYAxis(unittest.TestCase, ph.TestTitleInternal, ph.TestMinMaxInternal, ph.TestTitleFontInternal, 561 | ph.TestScaleFontInternal): 562 | def setUp(self): 563 | self.element = widgets.ImageYAxis() 564 | 565 | class TestXYPlotXAxis(unittest.TestCase, ph.TestTitleInternal, ph.TestAutoScaleInternal, ph.TestLogScaleInternal, 566 | ph.TestMinMaxInternal, ph.TestShowGridInternal, ph.TestTitleFontInternal, ph.TestScaleFontInternal): 567 | def setUp(self): 568 | self.element = widgets.XYPlotXAxis() 569 | 570 | class TestImageXAxis(unittest.TestCase, ph.TestTitleInternal, ph.TestMinMaxInternal, ph.TestTitleFontInternal, 571 | ph.TestScaleFontInternal): 572 | def setUp(self): 573 | self.element = widgets.ImageXAxis() 574 | 575 | class TestMarker(unittest.TestCase, ph.TestColor, ph.TestPVNameInternal, ph.TestInteractive): 576 | def setUp(self): 577 | self.element = widgets.Marker() 578 | 579 | class TestROI(unittest.TestCase, ph.TestName, ph.TestColor, ph.TestInteractive, ph.TestXPV, ph.TestYPV, ph.TestWidthPV, 580 | ph.TestHeightPV, ph.TestFileInternal): 581 | def setUp(self): 582 | self.element = widgets.RegionOfInterest() 583 | 584 | class TestColorBar(unittest.TestCase, ph.TestColorBarSize, ph.TestScaleFontInternal): 585 | def setUp(self): 586 | self.element = widgets.ColorBar() 587 | 588 | if __name__ == '__main__': 589 | unittest.main() 590 | -------------------------------------------------------------------------------- /phoebusgen/widget/widgets.py: -------------------------------------------------------------------------------- 1 | from phoebusgen.widget.widget import _Widget, _Generic 2 | from phoebusgen.widget import properties as _p 3 | 4 | # Displays 5 | class Arc(_Widget, _p._Macro, _p._Angle, _p._LineWidth, _p._LineColor, _p._BackgroundColor, _p._Transparent): 6 | """ Arc Phoebus Widget """ 7 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 8 | """ 9 | Create Arc Widget 10 | 11 | :param name: Widget name 12 | :param x: X position 13 | :param y: Y position 14 | :param width: Widget width 15 | :param height: Widget height 16 | """ 17 | _Widget.__init__(self, 'arc', name, x, y, width, height) 18 | 19 | class Ellipse(_Widget, _p._Macro, _p._LineWidth, _p._LineColor, _p._BackgroundColor, _p._Transparent): 20 | """ Ellipse Phoebus Widget """ 21 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 22 | """ 23 | Create Ellipse Widget 24 | 25 | :param name: Widget name 26 | :param x: X position 27 | :param y: Y position 28 | :param width: Widget width 29 | :param height: Widget height 30 | """ 31 | _Widget.__init__(self, 'ellipse', name, x, y, width, height) 32 | 33 | class Label(_Widget, _p._Text, _p._Macro, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._Transparent, _p._HorizontalAlignment, 34 | _p._VerticalAlignment, _p._RotationStep, _p._WrapWords, _p._AutoSize, _p._Border): 35 | """ Label Phoebus Widget """ 36 | def __init__(self, name: str, text: str, x: int, y: int, width: int, height: int) -> None: 37 | """ 38 | Create Label Widget 39 | 40 | :param name: Widget name 41 | :param text: Text of label 42 | :param x: X position 43 | :param y: Y position 44 | :param width: Widget width 45 | :param height: Widget height 46 | """ 47 | _Widget.__init__(self, 'label', name, x, y, width, height) 48 | self.text(text) 49 | 50 | class Picture(_Widget, _p._Macro, _p._File, _p._StretchToFit, _p._Rotation): 51 | """ Picture Phoebus Widget """ 52 | def __init__(self, name: str, file: str, x: int, y: int, width: int, height: int) -> None: 53 | """ 54 | Create Picture Widget 55 | 56 | :param name: Widget name 57 | :param file: File path to image 58 | :param x: X position 59 | :param y: Y position 60 | :param width: Widget width 61 | :param height: Widget height 62 | """ 63 | _Widget.__init__(self, 'picture', name, x, y, width, height) 64 | self.file(file) 65 | 66 | class Polygon(_Widget, _p._Macro, _p._LineWidth, _p._LineColor, _p._BackgroundColor, _p._Points): 67 | """ Polygon Phoebus Widget """ 68 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 69 | """ 70 | Create Polygon Widget 71 | 72 | :param name: Widget name 73 | :param x: X position 74 | :param y: Y position 75 | :param width: Widget width 76 | :param height: Widget height 77 | """ 78 | _Widget.__init__(self, 'polygon', name, x, y, width, height) 79 | 80 | class Polyline(_Widget, _p._Macro, _p._LineWidth, _p._LineColor, _p._LineStyle, _p._Arrow, _p._Points): 81 | """ Polyline Phoebus Widget """ 82 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 83 | """ 84 | Create Polyline Widget 85 | 86 | :param name: Widget name 87 | :param x: X position 88 | :param y: Y position 89 | :param width: Widget width 90 | :param height: Widget height 91 | """ 92 | _Widget.__init__(self, 'polyline', name, x, y, width, height) 93 | 94 | class Rectangle(_Widget, _p._Macro, _p._LineWidth, _p._LineColor, _p._BackgroundColor, _p._Transparent, _p._Corner): 95 | """ Rectangle Phoebus Widget """ 96 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 97 | """ 98 | Create Rectangle Widget 99 | 100 | :param name: Widget name 101 | :param x: X position 102 | :param y: Y position 103 | :param width: Widget width 104 | :param height: Widget height 105 | """ 106 | _Widget.__init__(self, 'rectangle', name, x, y, width, height) 107 | 108 | # Monitors 109 | class ByteMonitor(_Widget, _p._PVName, _p._StartBit, _p._NumBits, _p._ReverseBits, _p._Horizontal, _p._Square, 110 | _p._OffColor, _p._OnColor, _p._ForegroundColor, _p._Font, _p._Labels, _p._AlarmBorder): 111 | """ ByteMonitor Phoebus Widget """ 112 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 113 | """ 114 | Create ByteMonitor Widget 115 | 116 | :param name: Widget name 117 | :param pv_name: Widget PV 118 | :param x: X position 119 | :param y: Y position 120 | :param width: Widget width 121 | :param height: Widget height 122 | """ 123 | _Widget.__init__(self, 'byte_monitor', name, x, y, width, height) 124 | self.pv_name(pv_name) 125 | 126 | class LED(_Widget, _p._PVName, _p._Bit, _p._Off, _p._On, _p._Font, _p._ForegroundColor, _p._LineColor, 127 | _p._Square, _p._LabelsFromPV, _p._AlarmBorder): 128 | """ LED Phoebus Widget """ 129 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 130 | """ 131 | Create LED Widget 132 | 133 | :param name: Widget name 134 | :param pv_name: Widget PV 135 | :param x: X position 136 | :param y: Y position 137 | :param width: Widget width 138 | :param height: Widget height 139 | """ 140 | _Widget.__init__(self, 'led', name, x, y, width, height) 141 | self.pv_name(pv_name) 142 | 143 | class LEDMultiState(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._LineColor, _p._Square, 144 | _p._AlarmBorder, _p._States, _p._Fallback): 145 | """ LEDMultiState Phoebus Widget """ 146 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 147 | """ 148 | Create LEDMultiState Widget 149 | 150 | :param name: Widget name 151 | :param pv_name: Widget PV 152 | :param x: X position 153 | :param y: Y position 154 | :param width: Widget width 155 | :param height: Widget height 156 | """ 157 | _Widget.__init__(self, 'multi_state_led', name, x, y, width, height) 158 | self.pv_name(pv_name) 159 | 160 | class Meter(_Widget, _p._PVName, _p._ForegroundColor, _p._BackgroundColor, _p._Font, _p._Format, 161 | _p._Precision, _p._ShowValue, _p._ShowUnits, _p._ShowLimits, _p._AlarmBorder, 162 | _p._LimitsFromPV, _p._MinMax, _p._NeedleColor, _p._KnobColor): 163 | """ Meter Phoebus Widget """ 164 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 165 | """ 166 | Create Meter Widget 167 | 168 | :param name: Widget name 169 | :param pv_name: Widget PV 170 | :param x: X position 171 | :param y: Y position 172 | :param width: Widget width 173 | :param height: Widget height 174 | """ 175 | _Widget.__init__(self, 'meter', name, x, y, width, height) 176 | self.pv_name(pv_name) 177 | 178 | class ProgressBar(_Widget, _p._PVName, _p._FillColor, _p._BackgroundColor, _p._Horizontal, 179 | _p._AlarmBorder, _p._LimitsFromPV, _p._MinMax): 180 | """ ProgressBar Phoebus Widget """ 181 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 182 | """ 183 | Create ProgressBar Widget 184 | 185 | :param name: Widget name 186 | :param pv_name: Widget PV 187 | :param x: X position 188 | :param y: Y position 189 | :param width: Widget width 190 | :param height: Widget height 191 | """ 192 | _Widget.__init__(self, 'progressbar', name, x, y, width, height) 193 | self.pv_name(pv_name) 194 | 195 | class Symbol(_Widget, _p._Symbols, _p._PVName, _p._BackgroundColor, _p._InitialIndex, 196 | _p._Rotation, _p._ShowIndex, _p._Transparent, _p._AlarmBorder, _p._ArrayIndex, 197 | _p._AutoSize, _p._Enabled, _p._PreserveRatio): 198 | """ Symbol Phoebus Widget """ 199 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 200 | """ 201 | Create Symbol Widget 202 | 203 | :param name: Widget name 204 | :param pv_name: Widget PV 205 | :param x: X position 206 | :param y: Y position 207 | :param width: Widget width 208 | :param height: Widget height 209 | """ 210 | _Widget.__init__(self, 'symbol', name, x, y, width, height) 211 | self.pv_name(pv_name) 212 | 213 | class Table(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._ShowToolbar, 214 | _p._AlarmBorder, _p._Editable, _p._SelectRows, _p._SelectionPV, _p._Columns): 215 | """ Table Phoebus Widget """ 216 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 217 | """ 218 | Create Table Widget 219 | 220 | :param name: Widget name 221 | :param pv_name: Widget PV 222 | :param x: X position 223 | :param y: Y position 224 | :param width: Widget width 225 | :param height: Widget height 226 | """ 227 | _Widget.__init__(self, 'table', name, x, y, width, height) 228 | self.pv_name(pv_name) 229 | 230 | class Tank(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, 231 | _p._FillColor, _p._EmptyColor, _p._ScaleVisible, _p._AlarmBorder, _p._LimitsFromPV, 232 | _p._MinMax): 233 | """ Tank Phoebus Widget """ 234 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 235 | """ 236 | Create Tank Widget 237 | 238 | :param name: Widget name 239 | :param pv_name: Widget PV 240 | :param x: X position 241 | :param y: Y position 242 | :param width: Widget width 243 | :param height: Widget height: 244 | """ 245 | _Widget.__init__(self, 'tank', name, x, y, width, height) 246 | self.pv_name(pv_name) 247 | 248 | class TextSymbol(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._Transparent, 249 | _p._HorizontalAlignment, _p._VerticalAlignment, _p._Rotation, _p._WrapWords, 250 | _p._AlarmBorder, _p._Enabled, _p._ArrayIndex, _p._Symbols): 251 | """ TextSymbol Phoebus Widget """ 252 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 253 | """ 254 | Create TextSymbol Widget 255 | 256 | :param name: Widget name 257 | :param pv_name: Widget PV 258 | :param x: X position 259 | :param y: Y position 260 | :param width: Widget width 261 | :param height: Widget height 262 | """ 263 | _Widget.__init__(self, 'text-symbol', name, x, y, width, height) 264 | self.pv_name(pv_name) 265 | 266 | class TextUpdate(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._Transparent, 267 | _p._Format, _p._Precision, _p._ShowUnits, _p._HorizontalAlignment, _p._VerticalAlignment, _p._WrapWords, 268 | _p._RotationStep, _p._Border, _p._AlarmBorder): 269 | """ TextUpdate Phoebus Widget """ 270 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 271 | """ 272 | Create TextUpdate Widget 273 | 274 | :param name: Widget name 275 | :param pv_name: Widget PV 276 | :param x: X position 277 | :param y: Y position 278 | :param width: Widget width 279 | :param height: Widget height 280 | """ 281 | _Widget.__init__(self, 'textupdate', name, x, y, width, height) 282 | self.pv_name(pv_name) 283 | 284 | class Thermometer(_Widget, _p._PVName, _p._FillColor, _p._AlarmBorder, _p._LimitsFromPV, _p._MinMax): 285 | """ Thermometer Phoebus Widget """ 286 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 287 | """ 288 | Create Thermometer Widget 289 | 290 | :param name: Widget name 291 | :param pv_name: Widget PV 292 | :param x: X position 293 | :param y: Y position 294 | :param width: Widget width 295 | :param height: Widget height 296 | """ 297 | _Widget.__init__(self, 'thermometer', name, x, y, width, height) 298 | self.pv_name(pv_name) 299 | 300 | # Controls 301 | class ActionButton(_Widget, _p._PVName, _p._Actions, _p._Text, _p._Font, _p._ForegroundColor, _p._BackgroundColor, 302 | _p._Transparent, _p._HorizontalAlignment, _p._VerticalAlignment, _p._RotationStep, _p._Enabled, _p._AlarmBorder, _p._Confirmation): 303 | """ ActionButton Phoebus Widget """ 304 | def __init__(self, name: str, text: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 305 | """ 306 | Create ActionButton Widget 307 | 308 | :param name: Widget name 309 | :param text: Button text 310 | :param pv_name: Widget PV 311 | :param x: X position 312 | :param y: Y position 313 | :param width: Widget width 314 | :param height: Widget height 315 | """ 316 | _Widget.__init__(self, 'action_button', name, x, y, width, height) 317 | self.pv_name(pv_name) 318 | self.text(text) 319 | 320 | class BooleanButton(_Widget, _p._PVName, _p._Bit, _p._OffImage, _p._OnImage, _p._ShowLED, _p._Font, _p._ForegroundColor, _p._BackgroundColor, 321 | _p._LabelsFromPV, _p._AlarmBorder, _p._Enabled, _p._Mode, _p._Confirmation, _p._HorizontalAlignment, _p._VerticalAlignment): 322 | """ BooleanButton Phoebus Widget """ 323 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 324 | """ 325 | Create BooleanButton Widget 326 | 327 | :param name: Widget name 328 | :param pv_name: Widget PV 329 | :param x: X position 330 | :param y: Y position 331 | :param width: Widget width 332 | :param height: Widget height 333 | """ 334 | _Widget.__init__(self, 'bool_button', name, x, y, width, height) 335 | self.pv_name(pv_name) 336 | 337 | class CheckBox(_Widget, _p._PVName, _p._Bit, _p._Label, _p._Font, _p._ForegroundColor, _p._AutoSize, 338 | _p._AlarmBorder, _p._Confirmation): 339 | """ CheckBox Phoebus Widget """ 340 | def __init__(self, name: str, label: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 341 | """ 342 | Create CheckBox Widget 343 | 344 | :param name: Widget name 345 | :param label: Label text 346 | :param pv_name: Widget PV 347 | :param x: X position 348 | :param y: Y position 349 | :param width: Widget width 350 | :param height: Widget height 351 | """ 352 | _Widget.__init__(self, 'checkbox', name, x, y, width, height) 353 | self.pv_name(pv_name) 354 | self.label(label) 355 | 356 | class ChoiceButton(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._SelectedColor, _p._Horizontal, 357 | _p._AlarmBorder, _p._Items, _p._ItemsFromPV, _p._Confirmation, _p._HorizontalAlignment, _p._VerticalAlignment): 358 | """ ChoiceButton Phoebus Widget """ 359 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 360 | """ 361 | Create ChoiceButton Widget 362 | 363 | :param name: Widget name 364 | :param pv_name: Widget PV 365 | :param x: X position 366 | :param y: Y position 367 | :param width: Widget width 368 | :param height: Widget height 369 | """ 370 | _Widget.__init__(self, 'choice', name, x, y, width, height) 371 | self.pv_name(pv_name) 372 | 373 | class ComboBox(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._AlarmBorder, _p._Items, 374 | _p._ItemsFromPV, _p._Editable, _p._Enabled, _p._Confirmation): 375 | """ ComboBox Phoebus Widget """ 376 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 377 | """ 378 | Create ComboBox Widget 379 | 380 | :param name: Widget name 381 | :param pv_name: Widget PV 382 | :param x: X position 383 | :param y: Y position 384 | :param width: Widget width 385 | :param height: Widget height 386 | """ 387 | _Widget.__init__(self, 'combo', name, x, y, width, height) 388 | self.pv_name(pv_name) 389 | 390 | 391 | class FileSelector(_Widget, _p._PVName, _p._FileComponent, _p._AlarmBorder, _p._Enabled): 392 | """ FileSelector Phoebus Widget """ 393 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 394 | """ 395 | Create FileSelector Widget 396 | 397 | :param name: Widget name 398 | :param pv_name: Widget PV 399 | :param x: X position 400 | :param y: Y position 401 | :param width: Widget width 402 | :param height: Widget height 403 | """ 404 | _Widget.__init__(self, 'fileselector', name, x, y, width, height) 405 | self.pv_name(pv_name) 406 | 407 | class RadioButton(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._Horizontal, _p._AlarmBorder, 408 | _p._Items, _p._ItemsFromPV, _p._Enabled, _p._Confirmation): 409 | """ RadioButton Phoebus Widget """ 410 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 411 | """ 412 | Create RadioButton Widget 413 | 414 | :param name: Widget name 415 | :param pv_name: Widget PV 416 | :param x: X position 417 | :param y: Y position 418 | :param width: Widget width 419 | :param height: Widget height 420 | """ 421 | _Widget.__init__(self, 'radio', name, x, y, width, height) 422 | self.pv_name(pv_name) 423 | 424 | class ScaledSlider(_Widget, _p._PVName, _p._Horizontal, _p._ForegroundColor, _p._BackgroundColor, _p._Transparent, _p._Font, 425 | _p._ShowScale, _p._ShowMinorTicks, _p._MajorTicksPixelDist, _p._ScaleFormat, _p._LevelsAndShow, _p._AlarmBorder, 426 | _p._Increment, _p._MinMax, _p._LimitsFromPV, _p._Enabled): 427 | """ ScaledSlider Phoebus Widget """ 428 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 429 | """ 430 | Create ScaledSlider Widget 431 | 432 | :param name: Widget name 433 | :param pv_name: Widget PV 434 | :param x: X position 435 | :param y: Y position 436 | :param width: Widget width 437 | :param height: Widget height 438 | """ 439 | _Widget.__init__(self, 'scaledslider', name, x, y, width, height) 440 | self.pv_name(pv_name) 441 | 442 | class Scrollbar(_Widget, _p._PVName, _p._Horizontal, _p._ShowValueTip, _p._AlarmBorder, _p._MinMax, 443 | _p._LimitsFromPV, _p._BarLength, _p._Increment, _p._Enabled): 444 | """ Scrollbar Phoebus Widget """ 445 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 446 | """ 447 | Create Scrollbar Widget 448 | 449 | :param name: Widget name 450 | :param pv_name: Widget PV 451 | :param x: X position 452 | :param y: Y position 453 | :param width: Widget width 454 | :param height: Widget height 455 | """ 456 | _Widget.__init__(self, 'scrollbar', name, x, y, width, height) 457 | self.pv_name(pv_name) 458 | 459 | class SlideButton(_Widget, _p._PVName, _p._Bit, _p._Label, _p._OffColor, _p._OnColor, _p._Font, _p._ForegroundColor, 460 | _p._AutoSize, _p._AlarmBorder, _p._Enabled, _p._Confirmation): 461 | """ SlideButton Phoebus Widget """ 462 | def __init__(self, name: str, label: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 463 | """ 464 | Create SlideButton Widget 465 | 466 | :param name: Widget name 467 | :param label: Label text 468 | :param pv_name: Widget PV 469 | :param x: X position 470 | :param y: Y position 471 | :param width: Widget width 472 | :param height: Widget height 473 | """ 474 | _Widget.__init__(self, 'slide_button', name, x, y, width, height) 475 | self.pv_name(pv_name) 476 | self.label(label) 477 | 478 | class Spinner(_Widget, _p._PVName, _p._Format, _p._Precision, _p._ShowUnits, _p._ForegroundColor, _p._BackgroundColor, 479 | _p._ButtonsOnLeft, _p._AlarmBorder, _p._MinMax, _p._LimitsFromPV, _p._Increment, _p._Enabled): 480 | """ Spinner Phoebus Widget """ 481 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 482 | """ 483 | Create Spinner Widget 484 | 485 | :param name: Widget name 486 | :param pv_name: Widget PV 487 | :param x: X position 488 | :param y: Y position 489 | :param width: Widget width 490 | :param height: Widget height 491 | """ 492 | _Widget.__init__(self, 'spinner', name, x, y, width, height) 493 | self.pv_name(pv_name) 494 | 495 | class TextEntry(_Widget, _p._PVName, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._Format, 496 | _p._Precision, _p._ShowUnits, _p._WrapWords, _p._MultiLine, _p._AlarmBorder, _p._Enabled, 497 | _p._Border, _p._HorizontalAlignment, _p._VerticalAlignment): 498 | """ TextEntry Phoebus Widget """ 499 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 500 | """ 501 | Create TextEntry Widget 502 | 503 | :param name: Widget name 504 | :param pv_name: Widget PV 505 | :param x: X position 506 | :param y: Y position 507 | :param width: Widget width 508 | :param height: Widget height 509 | """ 510 | _Widget.__init__(self, 'textentry', name, x, y, width, height) 511 | self.pv_name(pv_name) 512 | 513 | # Plots 514 | class StripChartTrace(_Generic, _p._Name, _p._YPV, _p._Axis, _p._TraceType, _p._Color, _p._LineWidth, 515 | _p._PointType, _p._PointSize): 516 | def __init__(self) -> None: 517 | _Generic.__init__(self, 'trace') 518 | 519 | class XYPlotTrace(StripChartTrace, _p._XPV, _p._ErrPV, _p._LineStyle): 520 | def __init__(self) -> None: 521 | super().__init__() 522 | 523 | class StripChartYAxis(_Generic, _p._Title, _p._AutoScale, _p._LogScale, _p._MinMax, _p._ShowGrid, 524 | _p._Color): 525 | def __init__(self) -> None: 526 | _Generic.__init__(self, 'y_axis') 527 | 528 | class XYPlotYAxis(StripChartYAxis, _p._TitleFont, _p._ScaleFont, _p._OnRight, _p._Color): 529 | def __init__(self) -> None: 530 | super().__init__() 531 | 532 | class ImageYAxis(_Generic, _p._Title, _p._MinMax, _p._TitleFont, _p._ScaleFont): 533 | def __init__(self) -> None: 534 | _Generic.__init__(self, 'y_axis') 535 | 536 | class XYPlotXAxis(_Generic, _p._Title, _p._AutoScale, _p._LogScale, _p._MinMax, _p._ShowGrid, _p._TitleFont, 537 | _p._ScaleFont): 538 | def __init__(self) -> None: 539 | _Generic.__init__(self, 'x_axis') 540 | 541 | class ImageXAxis(_Generic, _p._Title, _p._MinMax, _p._TitleFont, _p._ScaleFont): 542 | def __init__(self) -> None: 543 | _Generic.__init__(self, 'x_axis') 544 | 545 | class Marker(_Generic, _p._Color, _p._PVName, _p._Interactive): 546 | def __init__(self) -> None: 547 | _Generic.__init__(self, 'marker') 548 | 549 | class RegionOfInterest(_Generic, _p._Name, _p._Color, _p._Interactive, _p._XPV, _p._YPV, _p._WidthPV, _p._HeightPV, _p._File): 550 | def __init__(self) -> None: 551 | _Generic.__init__(self, 'roi') 552 | 553 | class ColorBar(_Generic, _p._ScaleFont, _p._ColorBarSize): 554 | def __init__(self) -> None: 555 | _Generic.__init__(self, 'color_bar') 556 | 557 | class ColorMapColor(_Generic): 558 | def __init__(self, value, red, green, blue): 559 | _Generic.__init__(self, 'section') 560 | self.root.attrib['value'] = str(value) 561 | self.root.attrib['red'] = str(red) 562 | self.root.attrib['green'] = str(green) 563 | self.root.attrib['blue'] = str(blue) 564 | 565 | class DataBrowser(_Widget, _p._Macro, _p._File, _p._ShowToolbar, _p._SelectionValuePV): 566 | """ DataBrowser Phoebus Widget """ 567 | def __init__(self, name: str, file: str, x: int, y: int, width: int, height: int) -> None: 568 | """ 569 | Create DataBrowser Widget 570 | 571 | :param name: Widget name 572 | :param pv_name: Widget PV 573 | :param x: X position 574 | :param y: Y position 575 | :param width: Widget width 576 | :param height: Widget height 577 | """ 578 | _Widget.__init__(self, 'databrowser', name, x, y, width, height) 579 | self.file(file) 580 | 581 | # colormap, colorbar, xaxis, yaxis, datawidth, dataheight, interpolation, colormode, unsigneddata, autoscale, 582 | # logscale, cursor, roi 583 | #class Image(_Widget, _p._PVName, _p._ForegroundColor, _p._BackgroundColor, _p._ShowToolbar, _p._ColorMap, _p._ColorBar, 584 | # _p._XAxis, _p._YAxis, _p._AlarmBorder, _p._DataWidth, _p._Interpolation, _p._ColorMode, _p._UnsignedData, 585 | # _p._AutoScale, _p._LogScale, _p._MinMax, _p._Cursor, _p._RegionsOfInterest): 586 | # pass 587 | class Image(_Widget, _p._PVName, _p._ForegroundColor, _p._BackgroundColor, _p._ShowToolbar, 588 | _p._AlarmBorder, _p._MinMax, _p._AutoScale, _p._DataHeightAndWidth, _p._UnsignedData, 589 | _p._LogScale, _p._Cursor, _p._Interpolation, _p._XAxis, _p._YAxis, _p._ColorMode, 590 | _p._RegionsOfInterest, _p._ColorBar, _p._ColorMap): 591 | """ Image Phoebus Widget """ 592 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 593 | """ 594 | Create Image Widget 595 | 596 | :param name: Widget name 597 | :param pv_name: Widget PV 598 | :param x: X position 599 | :param y: Y position 600 | :param width: Widget width 601 | :param height: Widget height 602 | """ 603 | _Widget.__init__(self, 'image', name, x, y, width, height) 604 | self.pv_name(pv_name) 605 | 606 | # showgrid, labelfont, scalefont, yaxes, traces 607 | #class StripChart(_Widget, _p._ForegroundColor, _p._BackgroundColor, _p._ShowGrid, _p._Title, _p._LabelFont, _p._ScaleFont, 608 | # _p._ShowToolbar, _p._TimeRange, _p._YAxes, _p._Traces): 609 | # pass 610 | class StripChart(_Widget, _p._ForegroundColor, _p._BackgroundColor, _p._ShowToolbar, _p._Title, 611 | _p._TitleFont, _p._ShowLegend, _p._ShowGrid, _p._TimeRange, _p._LabelFont, 612 | _p._ScaleFont, _p._YAxes, _p._Traces): 613 | """ StripChart Phoebus Widget """ 614 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 615 | """ 616 | Create StripChart Widget 617 | 618 | :param name: Widget name 619 | :param x: X position 620 | :param y: Y position 621 | :param width: Widget width 622 | :param height: Widget height 623 | """ 624 | _Widget.__init__(self, 'stripchart', name, x, y, width, height) 625 | 626 | # title, showlegend, xaxis, yaxes, traces, markers 627 | #class XYPlot(_Widget, _p._ForegroundColor, _p._BackgroundColor, _p._GridColor, _p._Title, _p._ShowToolbar, _p._ShowLegend, 628 | # _p._XAxis, _p._YAxes, _p._Traces, _p._Markers): 629 | # pass 630 | class XYPlot(_Widget, _p._ForegroundColor, _p._BackgroundColor, _p._ShowToolbar, _p._Title, 631 | _p._TitleFont, _p._GridColor, _p._XAxis, _p._YAxes, _p._Traces, _p._Markers): 632 | """ XYPlot Phoebus Widget """ 633 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 634 | """ 635 | Create XYPlot Widget 636 | 637 | :param name: Widget name 638 | :param x: X position 639 | :param y: Y position 640 | :param width: Widget width 641 | :param height: Widget height 642 | """ 643 | _Widget.__init__(self, 'xyplot', name, x, y, width, height) 644 | 645 | # Structure 646 | class Array(_Widget, _p._PVName, _p._Macro, _p._ForegroundColor, _p._BackgroundColor, _p._AlarmBorder): 647 | """ Array Phoebus Widget """ 648 | def __init__(self, name: str, pv_name: str, x: int, y: int, width: int, height: int) -> None: 649 | """ 650 | Create Array Widget 651 | 652 | :param name: Widget name 653 | :param pv_name: Widget PV 654 | :param x: X position 655 | :param y: Y position 656 | :param width: Widget width 657 | :param height: Widget height 658 | """ 659 | _Widget.__init__(self, 'array', name, x, y, width, height) 660 | self.pv_name(pv_name) 661 | 662 | class EmbeddedDisplay(_Widget, _p._Macro, _p._File, _p._ResizeBehavior, _p._GroupName, _p._Transparent, _p._Border): 663 | """ EmbeddedDisplay Phoebus Widget """ 664 | def __init__(self, name: str, file: str, x: int, y: int, width: int, height: int) -> None: 665 | """ 666 | Create EmbeddedDisplay Widget 667 | 668 | :param name: Widget name 669 | :param file: File path 670 | :param x: X position 671 | :param y: Y position 672 | :param width: Widget width 673 | :param height: Widget height 674 | """ 675 | _Widget.__init__(self, 'embedded', name, x, y, width, height) 676 | self.file(file) 677 | 678 | class Group(_Widget, _p._Structure, _p._Macro, _p._Style, _p._Font, _p._ForegroundColor, _p._BackgroundColor, _p._Transparent, 679 | _p._LineColor): 680 | """ Group Phoebus Widget """ 681 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 682 | """ 683 | Create Group Widget 684 | 685 | :param name: Widget name 686 | :param x: X position 687 | :param y: Y position 688 | :param width: Widget width 689 | :param height: Widget height 690 | """ 691 | _Widget.__init__(self, 'group', name, x, y, width, height) 692 | 693 | class NavigationTabs(_Widget, _p._NavTabs, _p._ActiveTab, _p._TabWidth, _p._TabSpacing, _p._TabHeight, 694 | _p._SelectedColor, _p._DeselectedColor, _p._Direction, _p._Font): 695 | """ NavigationTabs Phoebus Widget """ 696 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 697 | """ 698 | Create NavigationTabs Widget 699 | 700 | :param name: Widget name 701 | :param x: X position 702 | :param y: Y position 703 | :param width: Widget width 704 | :param height: Widget height 705 | """ 706 | _Widget.__init__(self, 'navtabs', name, x, y, width, height) 707 | 708 | class Tabs(_Widget, _p._Macro, _p._Tabs, _p._ActiveTab, _p._TabHeight, _p._Font, _p._BackgroundColor, _p._Direction): 709 | """ Tabs Phoebus Widget """ 710 | def __init__(self, name: str, x: int, y: int, width: int, height: int) -> None: 711 | """ 712 | Create Tabs Widget 713 | 714 | :param name: Widget name 715 | :param x: X position 716 | :param y: Y position 717 | :param width: Widget width 718 | :param height: Widget height 719 | """ 720 | _Widget.__init__(self, 'tabs', name, x, y, width, height) 721 | 722 | # Miscellaneous 723 | 724 | class ThreeDViewer(_Widget, _p._File): 725 | """ ThreeDViewer Phoebus Widget """ 726 | def __init__(self, name: str, file: str, x: int, y: int, width: int, height: int) -> None: 727 | """ 728 | Create ThreeDViewer Widget 729 | 730 | :param name: Widget name 731 | :param file: File path 732 | :param x: X position 733 | :param y: Y position 734 | :param width: Widget width 735 | :param height: Widget height 736 | """ 737 | _Widget.__init__(self, '3dviewer', name, x, y, width, height) 738 | self.file(file) 739 | 740 | class WebBrowser(_Widget, _p._URL, _p._ShowToolbar): 741 | """ WebBrowser Phoebus Widget """ 742 | def __init__(self, name: str, url: str, x: int, y: int, width: int, height: int) -> None: 743 | """ 744 | Create WebBrowser Widget 745 | 746 | :param name: Widget name 747 | :param url: URL 748 | :param x: X position 749 | :param y: Y position 750 | :param width: Widget width 751 | :param height: Widget height 752 | """ 753 | _Widget.__init__(self, 'webbrowser', name, x, y, width, height) 754 | self.url(url) 755 | --------------------------------------------------------------------------------